From dc09b7781ac310646014f05db23844ab2c6d63f4 Mon Sep 17 00:00:00 2001 From: Dmitri Aizenberg Date: Wed, 31 Aug 2016 06:24:14 -0700 Subject: 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 --- api/command_away.go | 42 ++++++++++++++++++++++++++++++++++++++++++ api/command_offline.go | 42 ++++++++++++++++++++++++++++++++++++++++++ api/command_online.go | 42 ++++++++++++++++++++++++++++++++++++++++++ api/command_statuses_test.go | 40 ++++++++++++++++++++++++++++++++++++++++ api/context.go | 2 +- api/post.go | 6 +++--- api/status.go | 40 ++++++++++++++++++++++++++++++---------- api/status_test.go | 6 +++--- api/web_conn.go | 4 ++-- api/web_hub.go | 2 +- 10 files changed, 206 insertions(+), 20 deletions(-) create mode 100644 api/command_away.go create mode 100644 api/command_offline.go create mode 100644 api/command_online.go create mode 100644 api/command_statuses_test.go (limited to 'api') 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 { -- cgit v1.2.3-1-g7c22