[MM-8497] Ability to set Do Not Disturb for a specified period of time (#16067)

* Add support for timed DND status

- accept a date time value in api query when dnd mode for user needs to be unset
- Create a new function to handle SetDNDStatus calls
- Create a scheduled task to unset dnd mode to wahtever mode was before setting it to DND

* update schema version

* Model changes to make fields more intuitive

- move dndendtime to status model
- add new field prev status in status to keep track of previous status of user
- update db migration function
- make use of prevstatus and dndendtime from status model

* set prev status and dndendtime appropriately after unsetting dnd mode

* add json tag for dndendtime

* unset dnd status only if not changed manually by user

* update dnd statuses after server restart

* make app-layers

* fix failing tests

* don't create sched task when setting status to DND

* get only expired statuses from db

- convert end time from any timezone to utc
- store dnd end time in unix format for usability reasons

* run update dnd status only on leader

* make mocks

* fix tests

* run UpdateDNDStatusOfUsers as recurring task

* save all statuses at once in db and update UpdateDNDStatusOfUsers logic

* add app method to get timezone of user

* store dnd end time in context.Params

* set max size of prevstatus

* update status model to take endtime input as string and store in db as unix time(int64)

* Add tests for SetStatusDoNotDisturbTimed

* if dnd_end_time is not passed the call old api to set dnd mode

* fix tests

* new plugin api to use new timed dnd mode

* get and update rows in a single db query

* dnd end time will be stored in request body and not route param

* exclude statuses which has dndendtimeunix < 0

* update and get the updated dnd statuses in single db query

* add updated status to cache

* DNDEndTimeUnix and PrevStatus need not to be visible to users

* update db schema version for migration

* Keep Status and PrevStatus varchar size same

* add test to verify status is restored after dnd end time expires

* expect endtime in utc from client

- remove store method GetTimezone as no longer needed
- add documentation for SetStatusDoNotDisturbTimed

* reduce sleep time for dnd timed restore test

* more appropriate name for new api to update user status

* update db migration function

* parse and validate time before potentially triggering db query to get status of user

* add migration changes in to existing upgrade function

* not supporting un-timed dnd status via api

* don't call Srv.Store directly, call via app layer

* rename dndendtime to statuscleartime to make it suitable for custom status usage as well

* Revert "rename dndendtime to statuscleartime to make it suitable for custom status usage as well"

This reverts commit fa69152d9a.

* mysql doesn't support RETURNING clause so add tx to get and update statuses

* add UpdateDNDStatusOfUsers mock in tests

* update store mock import path

* add mock in storelib

* Add status mocks to empty store

* Close the task during server shutdown

* Do not cancel a nil task

* update squirrel queries

* remove untimed dnd test

* start recurring task to unset statuses on leadership change

* set dndTask to nil after cancelling it upon server shutdown

* new recurring task which starts at nearest rounded time of the interval

* mock Get() call for status

* return updated statuses in case of mysql

* remove unneccessary code

* add Get() mock to empty store

* fix mocking for once and all

* address review comments

fix mysql updateStatus fn

protect dndTask with mutex

minor refactors

* move runDNDStatusExpireJob to server.go and pass App as arg instead of method receiver

* frontend will send endtime in unix epoch format so get rid of double representation

* scan for all fields and not just two

* add some tests and fix review comments

* remove extra sql query and create needed result in go

* add storetest for UpdateExpiredDNDStatuses

* add migrations to latest version

* update min supported version

* add comment to fix a bug in future

* update test to expect 1 status in return

* rename UpdateUserStatusWithDNDTimeout to SetUserStatusTimedDND

* rename DNDEndTimeUnix to DNDEndTime

* cast int to int64 for equality

* fix tests and error handling

* move updating values to retrieved statuses fields outside sql transaction

* move migrations to 5.36

Co-authored-by: Agniva De Sarker <agnivade@yahoo.co.in>
Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
This commit is contained in:
darkLord19
2021-05-07 21:50:55 +05:30
committed by GitHub
parent b581f65860
commit 77d42568f9
26 changed files with 585 additions and 21 deletions

View File

@@ -229,6 +229,12 @@ type API interface {
// Minimum server version: 5.2
UpdateUserStatus(userID, status string) (*model.Status, *model.AppError)
// SetUserStatusTimedDND will set a user's status to dnd for given time until the user,
// or another integration/plugin, sets it back to online.
// @tag User
// Minimum server version: 5.35
SetUserStatusTimedDND(userId string, endtime int64) (*model.Status, *model.AppError)
// UpdateUserActive deactivates or reactivates an user.
//
// @tag User

View File

@@ -266,6 +266,13 @@ func (api *apiTimerLayer) UpdateUserStatus(userID, status string) (*model.Status
return _returnsA, _returnsB
}
func (api *apiTimerLayer) SetUserStatusTimedDND(userId string, endtime int64) (*model.Status, *model.AppError) {
startTime := timePkg.Now()
_returnsA, _returnsB := api.apiImpl.SetUserStatusTimedDND(userId, endtime)
api.recordTime(startTime, "SetUserStatusTimedDND", _returnsB == nil)
return _returnsA, _returnsB
}
func (api *apiTimerLayer) UpdateUserActive(userID string, active bool) *model.AppError {
startTime := timePkg.Now()
_returnsA := api.apiImpl.UpdateUserActive(userID, active)

View File

@@ -1509,6 +1509,36 @@ func (s *apiRPCServer) UpdateUserStatus(args *Z_UpdateUserStatusArgs, returns *Z
return nil
}
type Z_SetUserStatusTimedDNDArgs struct {
A string
B int64
}
type Z_SetUserStatusTimedDNDReturns struct {
A *model.Status
B *model.AppError
}
func (g *apiRPCClient) SetUserStatusTimedDND(userId string, endtime int64) (*model.Status, *model.AppError) {
_args := &Z_SetUserStatusTimedDNDArgs{userId, endtime}
_returns := &Z_SetUserStatusTimedDNDReturns{}
if err := g.client.Call("Plugin.SetUserStatusTimedDND", _args, _returns); err != nil {
log.Printf("RPC call to SetUserStatusTimedDND API failed: %s", err.Error())
}
return _returns.A, _returns.B
}
func (s *apiRPCServer) SetUserStatusTimedDND(args *Z_SetUserStatusTimedDNDArgs, returns *Z_SetUserStatusTimedDNDReturns) error {
if hook, ok := s.impl.(interface {
SetUserStatusTimedDND(userId string, endtime int64) (*model.Status, *model.AppError)
}); ok {
returns.A, returns.B = hook.SetUserStatusTimedDND(args.A, args.B)
} else {
return encodableError(fmt.Errorf("API SetUserStatusTimedDND called but not implemented."))
}
return nil
}
type Z_UpdateUserActiveArgs struct {
A string
B bool

View File

@@ -3041,6 +3041,31 @@ func (_m *API) SetTeamIcon(teamID string, data []byte) *model.AppError {
return r0
}
// SetUserStatusTimedDND provides a mock function with given fields: userId, endtime
func (_m *API) SetUserStatusTimedDND(userId string, endtime int64) (*model.Status, *model.AppError) {
ret := _m.Called(userId, endtime)
var r0 *model.Status
if rf, ok := ret.Get(0).(func(string, int64) *model.Status); ok {
r0 = rf(userId, endtime)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.Status)
}
}
var r1 *model.AppError
if rf, ok := ret.Get(1).(func(string, int64) *model.AppError); ok {
r1 = rf(userId, endtime)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*model.AppError)
}
}
return r0, r1
}
// UnregisterCommand provides a mock function with given fields: teamID, trigger
func (_m *API) UnregisterCommand(teamID string, trigger string) error {
ret := _m.Called(teamID, trigger)