diff options
author | Dmitri Aizenberg <dmitri.aiz@gmail.com> | 2016-08-31 06:24:14 -0700 |
---|---|---|
committer | Joram Wilander <jwawilander@gmail.com> | 2016-08-31 09:24:14 -0400 |
commit | dc09b7781ac310646014f05db23844ab2c6d63f4 (patch) | |
tree | 906f13501b8e30be3551fa18078429445e5ee094 | |
parent | db660bdf9cbea09197d8292a8ec8efda8ac41f38 (diff) | |
download | chat-dc09b7781ac310646014f05db23844ab2c6d63f4.tar.gz chat-dc09b7781ac310646014f05db23844ab2c6d63f4.tar.bz2 chat-dc09b7781ac310646014f05db23844ab2c6d63f4.zip |
PLT-1527 Add a slash command to set yourself away (#3752)
* added handlers for slash commands
* added manual status persistance
* added tests
* removed extra debug output and comments
* rebase - fixing the PR
* making echo messages after slash commands ephemeral
-rw-r--r-- | api/command_away.go | 42 | ||||
-rw-r--r-- | api/command_offline.go | 42 | ||||
-rw-r--r-- | api/command_online.go | 42 | ||||
-rw-r--r-- | api/command_statuses_test.go | 40 | ||||
-rw-r--r-- | api/context.go | 2 | ||||
-rw-r--r-- | api/post.go | 6 | ||||
-rw-r--r-- | api/status.go | 40 | ||||
-rw-r--r-- | api/status_test.go | 6 | ||||
-rw-r--r-- | api/web_conn.go | 4 | ||||
-rw-r--r-- | api/web_hub.go | 2 | ||||
-rw-r--r-- | i18n/en.json | 36 | ||||
-rw-r--r-- | model/status.go | 1 | ||||
-rw-r--r-- | model/status_test.go | 6 | ||||
-rw-r--r-- | store/sql_status_store.go | 6 | ||||
-rw-r--r-- | store/sql_status_store_test.go | 11 |
15 files changed, 260 insertions, 26 deletions
diff --git a/api/command_away.go b/api/command_away.go new file mode 100644 index 000000000..55ce7846e --- /dev/null +++ b/api/command_away.go @@ -0,0 +1,42 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package api + +import ( + "github.com/mattermost/platform/model" +) + +type AwayProvider struct { +} + +const ( + CMD_AWAY = "away" +) + +func init() { + RegisterCommandProvider(&AwayProvider{}) +} + +func (me *AwayProvider) GetTrigger() string { + return CMD_AWAY +} + +func (me *AwayProvider) GetCommand(c *Context) *model.Command { + return &model.Command{ + Trigger: CMD_AWAY, + AutoComplete: true, + AutoCompleteDesc: c.T("api.command_away.desc"), + DisplayName: c.T("api.command_away.name"), + } +} + +func (me *AwayProvider) DoCommand(c *Context, channelId string, message string) *model.CommandResponse { + rmsg := c.T("api.command_away.success") + if len(message) > 0 { + rmsg = message + " " + rmsg + } + SetStatusAwayIfNeeded(c.Session.UserId, true) + + return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, Text: rmsg} +} diff --git a/api/command_offline.go b/api/command_offline.go new file mode 100644 index 000000000..a9e2123d3 --- /dev/null +++ b/api/command_offline.go @@ -0,0 +1,42 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package api + +import ( + "github.com/mattermost/platform/model" +) + +type OfflineProvider struct { +} + +const ( + CMD_OFFLINE = "offline" +) + +func init() { + RegisterCommandProvider(&OfflineProvider{}) +} + +func (me *OfflineProvider) GetTrigger() string { + return CMD_OFFLINE +} + +func (me *OfflineProvider) GetCommand(c *Context) *model.Command { + return &model.Command{ + Trigger: CMD_OFFLINE, + AutoComplete: true, + AutoCompleteDesc: c.T("api.command_offline.desc"), + DisplayName: c.T("api.command_offline.name"), + } +} + +func (me *OfflineProvider) DoCommand(c *Context, channelId string, message string) *model.CommandResponse { + rmsg := c.T("api.command_offline.success") + if len(message) > 0 { + rmsg = message + " " + rmsg + } + SetStatusOffline(c.Session.UserId, true) + + return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, Text: rmsg} +} diff --git a/api/command_online.go b/api/command_online.go new file mode 100644 index 000000000..71ada5b7a --- /dev/null +++ b/api/command_online.go @@ -0,0 +1,42 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package api + +import ( + "github.com/mattermost/platform/model" +) + +type OnlineProvider struct { +} + +const ( + CMD_ONLINE = "online" +) + +func init() { + RegisterCommandProvider(&OnlineProvider{}) +} + +func (me *OnlineProvider) GetTrigger() string { + return CMD_ONLINE +} + +func (me *OnlineProvider) GetCommand(c *Context) *model.Command { + return &model.Command{ + Trigger: CMD_ONLINE, + AutoComplete: true, + AutoCompleteDesc: c.T("api.command_online.desc"), + DisplayName: c.T("api.command_online.name"), + } +} + +func (me *OnlineProvider) DoCommand(c *Context, channelId string, message string) *model.CommandResponse { + rmsg := c.T("api.command_online.success") + if len(message) > 0 { + rmsg = message + " " + rmsg + } + SetStatusOnline(c.Session.UserId, c.Session.Id, true) + + return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, Text: rmsg} +} diff --git a/api/command_statuses_test.go b/api/command_statuses_test.go new file mode 100644 index 000000000..1c8026a9f --- /dev/null +++ b/api/command_statuses_test.go @@ -0,0 +1,40 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package api + +import ( + "testing" + "time" + + "github.com/mattermost/platform/model" +) + +func TestStatusCommands(t *testing.T) { + th := Setup().InitBasic() + commandAndTest(t, th, "away") + commandAndTest(t, th, "offline") + commandAndTest(t, th, "online") +} + +func commandAndTest(t *testing.T, th *TestHelper, status string) { + Client := th.BasicClient + channel := th.BasicChannel + user := th.BasicUser + + r1 := Client.Must(Client.Command(channel.Id, "/"+status, false)).Data.(*model.CommandResponse) + if r1 == nil { + t.Fatal("Command failed to execute") + } + + time.Sleep(300 * time.Millisecond) + + statuses := Client.Must(Client.GetStatuses()).Data.(map[string]string) + + if status == "offline" { + status = "" + } + if statuses[user.Id] != status { + t.Fatal("Error setting status " + status) + } +} diff --git a/api/context.go b/api/context.go index c2b7a42cb..37b426c21 100644 --- a/api/context.go +++ b/api/context.go @@ -207,7 +207,7 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } if c.Err == nil && h.isUserActivity && token != "" && len(c.Session.UserId) > 0 { - SetStatusOnline(c.Session.UserId, c.Session.Id) + SetStatusOnline(c.Session.UserId, c.Session.Id, false) } if c.Err == nil { diff --git a/api/post.go b/api/post.go index e49be2248..8639938e2 100644 --- a/api/post.go +++ b/api/post.go @@ -679,7 +679,7 @@ func sendNotifications(c *Context, post *model.Post, team *model.Team, channel * var status *model.Status var err *model.AppError if status, err = GetStatus(id); err != nil { - status = &model.Status{id, model.STATUS_OFFLINE, 0} + status = &model.Status{id, model.STATUS_OFFLINE, false, 0} } if userAllowsEmails && status.Status != model.STATUS_ONLINE { @@ -727,7 +727,7 @@ func sendNotifications(c *Context, post *model.Post, team *model.Team, channel * var status *model.Status var err *model.AppError if status, err = GetStatus(id); err != nil { - status = &model.Status{id, model.STATUS_OFFLINE, 0} + status = &model.Status{id, model.STATUS_OFFLINE, false, 0} } if profileMap[id].StatusAllowsPushNotification(status) { @@ -740,7 +740,7 @@ func sendNotifications(c *Context, post *model.Post, team *model.Team, channel * var status *model.Status var err *model.AppError if status, err = GetStatus(id); err != nil { - status = &model.Status{id, model.STATUS_OFFLINE, 0} + status = &model.Status{id, model.STATUS_OFFLINE, false, 0} } if profileMap[id].StatusAllowsPushNotification(status) { diff --git a/api/status.go b/api/status.go index d19105e3b..d83eac033 100644 --- a/api/status.go +++ b/api/status.go @@ -65,19 +65,24 @@ func GetAllStatuses() (map[string]interface{}, *model.AppError) { } } -func SetStatusOnline(userId string, sessionId string) { +func SetStatusOnline(userId string, sessionId string, manual bool) { + l4g.Debug(userId, "online") broadcast := false var status *model.Status var err *model.AppError if status, err = GetStatus(userId); err != nil { - status = &model.Status{userId, model.STATUS_ONLINE, model.GetMillis()} + status = &model.Status{userId, model.STATUS_ONLINE, false, model.GetMillis()} broadcast = true } else { + if status.Manual && !manual { + return // manually set status always overrides non-manual one + } if status.Status != model.STATUS_ONLINE { broadcast = true } status.Status = model.STATUS_ONLINE + status.Manual = false // for "online" there's no manually or auto set status.LastActivityAt = model.GetMillis() } @@ -107,8 +112,14 @@ func SetStatusOnline(userId string, sessionId string) { } } -func SetStatusOffline(userId string) { - status := &model.Status{userId, model.STATUS_OFFLINE, model.GetMillis()} +func SetStatusOffline(userId string, manual bool) { + l4g.Debug(userId, "offline") + status, err := GetStatus(userId) + if err == nil && status.Manual && !manual { + return // manually set status always overrides non-manual one + } + + status = &model.Status{userId, model.STATUS_OFFLINE, manual, model.GetMillis()} AddStatusCache(status) @@ -121,21 +132,30 @@ func SetStatusOffline(userId string) { go Publish(event) } -func SetStatusAwayIfNeeded(userId string) { +func SetStatusAwayIfNeeded(userId string, manual bool) { + l4g.Debug(userId, "away") status, err := GetStatus(userId) + if err != nil { - status = &model.Status{userId, model.STATUS_OFFLINE, 0} + status = &model.Status{userId, model.STATUS_OFFLINE, manual, 0} } - if status.Status == model.STATUS_AWAY { - return + if !manual && status.Manual { + return // manually set status always overrides non-manual one } - if !IsUserAway(status.LastActivityAt) { - return + if !manual { + if status.Status == model.STATUS_AWAY { + return + } + + if !IsUserAway(status.LastActivityAt) { + return + } } status.Status = model.STATUS_AWAY + status.Manual = manual AddStatusCache(status) diff --git a/api/status_test.go b/api/status_test.go index a035cf8bf..451f39e14 100644 --- a/api/status_test.go +++ b/api/status_test.go @@ -83,7 +83,7 @@ func TestStatuses(t *testing.T) { } } - SetStatusAwayIfNeeded(th.BasicUser2.Id) + SetStatusAwayIfNeeded(th.BasicUser2.Id, false) awayTimeout := *utils.Cfg.TeamSettings.UserStatusAwayTimeout defer func() { @@ -93,8 +93,8 @@ func TestStatuses(t *testing.T) { time.Sleep(1500 * time.Millisecond) - SetStatusAwayIfNeeded(th.BasicUser2.Id) - SetStatusAwayIfNeeded(th.BasicUser2.Id) + SetStatusAwayIfNeeded(th.BasicUser2.Id, false) + SetStatusAwayIfNeeded(th.BasicUser2.Id, false) WebSocketClient2.Close() time.Sleep(300 * time.Millisecond) diff --git a/api/web_conn.go b/api/web_conn.go index 8741873fd..c842e2df1 100644 --- a/api/web_conn.go +++ b/api/web_conn.go @@ -32,7 +32,7 @@ type WebConn struct { } func NewWebConn(c *Context, ws *websocket.Conn) *WebConn { - go SetStatusOnline(c.Session.UserId, c.Session.Id) + go SetStatusOnline(c.Session.UserId, c.Session.Id, false) return &WebConn{ Send: make(chan model.WebSocketMessage, 64), @@ -55,7 +55,7 @@ func (c *WebConn) readPump() { c.WebSocket.SetReadDeadline(time.Now().Add(PONG_WAIT)) c.WebSocket.SetPongHandler(func(string) error { c.WebSocket.SetReadDeadline(time.Now().Add(PONG_WAIT)) - go SetStatusAwayIfNeeded(c.UserId) + go SetStatusAwayIfNeeded(c.UserId, false) return nil }) diff --git a/api/web_hub.go b/api/web_hub.go index 85aa01a6d..89f9891f8 100644 --- a/api/web_hub.go +++ b/api/web_hub.go @@ -100,7 +100,7 @@ func (h *Hub) Start() { } if !found { - go SetStatusOffline(userId) + go SetStatusOffline(userId, false) } case userId := <-h.invalidateUser: for webCon := range h.connections { diff --git a/i18n/en.json b/i18n/en.json index 1d3c8b8a1..d8df8e835 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -432,6 +432,42 @@ "translation": "Inappropriate permissions to regenerate command token" }, { + "id": "api.command_away.desc", + "translation": "Set your status away" + }, + { + "id": "api.command_away.name", + "translation": "away" + }, + { + "id": "api.command_away.success", + "translation": "You are now away" + }, + { + "id": "api.command_online.desc", + "translation": "Set your status online" + }, + { + "id": "api.command_online.name", + "translation": "online" + }, + { + "id": "api.command_online.success", + "translation": "You are now online" + }, + { + "id": "api.command_offline.desc", + "translation": "Set your status offline" + }, + { + "id": "api.command_offline.name", + "translation": "offline" + }, + { + "id": "api.command_offline.success", + "translation": "You are now offline" + }, + { "id": "api.command_collapse.desc", "translation": "Turn on auto-collapsing of image previews" }, diff --git a/model/status.go b/model/status.go index 8bf26f2f0..477b750d5 100644 --- a/model/status.go +++ b/model/status.go @@ -18,6 +18,7 @@ const ( type Status struct { UserId string `json:"user_id"` Status string `json:"status"` + Manual bool `json:"manual"` LastActivityAt int64 `json:"last_activity_at"` } diff --git a/model/status_test.go b/model/status_test.go index ccdac53b6..b5e876bc5 100644 --- a/model/status_test.go +++ b/model/status_test.go @@ -9,7 +9,7 @@ import ( ) func TestStatus(t *testing.T) { - status := Status{NewId(), STATUS_ONLINE, 0} + status := Status{NewId(), STATUS_ONLINE, true, 0} json := status.ToJson() status2 := StatusFromJson(strings.NewReader(json)) @@ -24,4 +24,8 @@ func TestStatus(t *testing.T) { if status.LastActivityAt != status2.LastActivityAt { t.Fatal("LastActivityAt should have matched") } + + if status.Manual != status2.Manual { + t.Fatal("Manual should have matched") + } } diff --git a/store/sql_status_store.go b/store/sql_status_store.go index f5259c212..99d89f0d5 100644 --- a/store/sql_status_store.go +++ b/store/sql_status_store.go @@ -5,6 +5,7 @@ package store import ( "database/sql" + "github.com/mattermost/platform/model" ) @@ -22,12 +23,17 @@ func NewSqlStatusStore(sqlStore *SqlStore) StatusStore { for _, db := range sqlStore.GetAllConns() { table := db.AddTableWithName(model.Status{}, "Status").SetKeys(false, "UserId") table.ColMap("UserId").SetMaxSize(26) + table.ColMap("Manual") table.ColMap("Status").SetMaxSize(32) } return s } +func (s SqlStatusStore) UpgradeSchemaIfNeeded() { + s.CreateColumnIfNotExists("Status", "Manual", "BOOLEAN", "BOOLEAN", "0") +} + func (s SqlStatusStore) CreateIndexesIfNotExists() { s.CreateIndexIfNotExists("idx_status_user_id", "Status", "UserId") s.CreateIndexIfNotExists("idx_status_status", "Status", "Status") diff --git a/store/sql_status_store_test.go b/store/sql_status_store_test.go index 139915bad..52759a4b1 100644 --- a/store/sql_status_store_test.go +++ b/store/sql_status_store_test.go @@ -4,14 +4,15 @@ package store import ( - "github.com/mattermost/platform/model" "testing" + + "github.com/mattermost/platform/model" ) func TestSqlStatusStore(t *testing.T) { Setup() - status := &model.Status{model.NewId(), model.STATUS_ONLINE, 0} + status := &model.Status{model.NewId(), model.STATUS_ONLINE, false, 0} if err := (<-store.Status().SaveOrUpdate(status)).Err; err != nil { t.Fatal(err) @@ -27,12 +28,12 @@ func TestSqlStatusStore(t *testing.T) { t.Fatal(err) } - status2 := &model.Status{model.NewId(), model.STATUS_AWAY, 0} + status2 := &model.Status{model.NewId(), model.STATUS_AWAY, false, 0} if err := (<-store.Status().SaveOrUpdate(status2)).Err; err != nil { t.Fatal(err) } - status3 := &model.Status{model.NewId(), model.STATUS_OFFLINE, 0} + status3 := &model.Status{model.NewId(), model.STATUS_OFFLINE, false, 0} if err := (<-store.Status().SaveOrUpdate(status3)).Err; err != nil { t.Fatal(err) } @@ -80,7 +81,7 @@ func TestSqlStatusStore(t *testing.T) { func TestActiveUserCount(t *testing.T) { Setup() - status := &model.Status{model.NewId(), model.STATUS_ONLINE, model.GetMillis()} + status := &model.Status{model.NewId(), model.STATUS_ONLINE, false, model.GetMillis()} Must(store.Status().SaveOrUpdate(status)) if result := <-store.Status().GetTotalActiveUsersCount(); result.Err != nil { |