[MM-34076] Add Plugin API User custom status (#17435)
* add missing client for custom user status * add custom user status plugin api * update based on feedback review * add GetCustomStatus * update interfaces
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
0a3e7f4d55
Коммит
bb5a74a42b
@@ -587,6 +587,7 @@ type AppIface interface {
|
||||
GetComplianceReport(reportId string) (*model.Compliance, *model.AppError)
|
||||
GetComplianceReports(page, perPage int) (model.Compliances, *model.AppError)
|
||||
GetCookieDomain() string
|
||||
GetCustomStatus(userID string) (*model.CustomStatus, *model.AppError)
|
||||
GetDefaultProfileImage(user *model.User) ([]byte, *model.AppError)
|
||||
GetDeletedChannels(teamID string, offset int, limit int, userID string) (model.ChannelList, *model.AppError)
|
||||
GetEmoji(emojiId string) (*model.Emoji, *model.AppError)
|
||||
|
||||
@@ -5449,6 +5449,28 @@ func (a *OpenTracingAppLayer) GetCookieDomain() string {
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) GetCustomStatus(userID string) (*model.CustomStatus, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetCustomStatus")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store.SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0, resultVar1 := a.app.GetCustomStatus(userID)
|
||||
|
||||
if resultVar1 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar1))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) GetDefaultProfileImage(user *model.User) ([]byte, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetDefaultProfileImage")
|
||||
|
||||
@@ -344,6 +344,18 @@ func (api *PluginAPI) SetUserStatusTimedDND(userID string, endTime int64) (*mode
|
||||
return api.app.GetStatus(userID)
|
||||
}
|
||||
|
||||
func (api *PluginAPI) UpdateUserCustomStatus(userID string, customStatus *model.CustomStatus) *model.AppError {
|
||||
return api.app.SetCustomStatus(userID, customStatus)
|
||||
}
|
||||
|
||||
func (api *PluginAPI) RemoveUserCustomStatus(userID string) *model.AppError {
|
||||
return api.app.RemoveCustomStatus(userID)
|
||||
}
|
||||
|
||||
func (api *PluginAPI) GetUserCustomStatus(userID string) (*model.CustomStatus, *model.AppError) {
|
||||
return api.app.GetCustomStatus(userID)
|
||||
}
|
||||
|
||||
func (api *PluginAPI) GetUsersInChannel(channelID, sortBy string, page, perPage int) ([]*model.User, *model.AppError) {
|
||||
switch sortBy {
|
||||
case model.ChannelSortByUsername:
|
||||
|
||||
@@ -494,6 +494,59 @@ func TestPluginAPIGetUsersInTeam(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPluginAPIUserCustomStatus(t *testing.T) {
|
||||
th := Setup(t)
|
||||
defer th.TearDown()
|
||||
api := th.SetupPluginAPI()
|
||||
|
||||
user1, err := th.App.CreateUser(th.Context, &model.User{
|
||||
Email: strings.ToLower(model.NewId()) + "success+test@example.com",
|
||||
Username: "user_" + model.NewId(),
|
||||
Password: "password",
|
||||
})
|
||||
require.Nil(t, err)
|
||||
defer th.App.PermanentDeleteUser(th.Context, user1)
|
||||
|
||||
custom := &model.CustomStatus{
|
||||
Emoji: ":tada:",
|
||||
Text: "honk",
|
||||
}
|
||||
|
||||
err = api.UpdateUserCustomStatus(user1.Id, custom)
|
||||
assert.Nil(t, err)
|
||||
userCs, err := th.App.GetCustomStatus(user1.Id)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, custom, userCs)
|
||||
|
||||
custom.Text = ""
|
||||
err = api.UpdateUserCustomStatus(user1.Id, custom)
|
||||
assert.Nil(t, err)
|
||||
userCs, err = th.App.GetCustomStatus(user1.Id)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, custom, userCs)
|
||||
|
||||
custom.Text = "honk"
|
||||
custom.Emoji = ""
|
||||
err = api.UpdateUserCustomStatus(user1.Id, custom)
|
||||
assert.Nil(t, err)
|
||||
userCs, err = th.App.GetCustomStatus(user1.Id)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, custom, userCs)
|
||||
|
||||
custom.Text = ""
|
||||
err = api.UpdateUserCustomStatus(user1.Id, custom)
|
||||
assert.NotNil(t, err)
|
||||
assert.Equal(t, err.Error(), "SetCustomStatus: Failed to update the custom status. Please add either emoji or custom text status or both., ")
|
||||
|
||||
// Remove custom status
|
||||
err = api.RemoveUserCustomStatus(user1.Id)
|
||||
assert.Nil(t, err)
|
||||
var csClear *model.CustomStatus
|
||||
userCs, err = th.App.GetCustomStatus(user1.Id)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, csClear, userCs)
|
||||
}
|
||||
|
||||
func TestPluginAPIGetFile(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
@@ -715,7 +768,6 @@ func TestPluginAPILoadPluginConfiguration(t *testing.T) {
|
||||
]
|
||||
}}`)
|
||||
require.NoError(t, err)
|
||||
|
||||
}
|
||||
|
||||
func TestPluginAPILoadPluginConfigurationDefaults(t *testing.T) {
|
||||
@@ -755,7 +807,6 @@ func TestPluginAPILoadPluginConfigurationDefaults(t *testing.T) {
|
||||
}`)
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
}
|
||||
|
||||
func TestPluginAPIGetPlugins(t *testing.T) {
|
||||
@@ -1084,6 +1135,7 @@ func pluginAPIHookTest(t *testing.T, th *TestHelper, fileName string, id string,
|
||||
if ret != "OK" {
|
||||
return errors.New(ret)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1284,7 +1336,6 @@ func TestPluginCreateBot(t *testing.T) {
|
||||
Description: "bot2",
|
||||
})
|
||||
require.NotNil(t, err)
|
||||
|
||||
}
|
||||
|
||||
func TestPluginCreatePostWithUploadedFile(t *testing.T) {
|
||||
@@ -1748,6 +1799,7 @@ type MockSlashCommandProvider struct {
|
||||
func (*MockSlashCommandProvider) GetTrigger() string {
|
||||
return "mock"
|
||||
}
|
||||
|
||||
func (*MockSlashCommandProvider) GetCommand(a *App, T i18n.TranslateFunc) *model.Command {
|
||||
return &model.Command{
|
||||
Trigger: "mock",
|
||||
@@ -1757,6 +1809,7 @@ func (*MockSlashCommandProvider) GetCommand(a *App, T i18n.TranslateFunc) *model
|
||||
DisplayName: "mock",
|
||||
}
|
||||
}
|
||||
|
||||
func (mscp *MockSlashCommandProvider) DoCommand(a *App, c *request.Context, args *model.CommandArgs, message string) *model.CommandResponse {
|
||||
mscp.Args = args
|
||||
mscp.Message = message
|
||||
@@ -1920,7 +1973,6 @@ func TestPluginAPIUpdateCommand(t *testing.T) {
|
||||
require.NoError(t, appErr)
|
||||
require.Equal(t, "anothernewtriggeragain", newCmd4.Trigger)
|
||||
require.Equal(t, team1.Id, newCmd4.TeamId)
|
||||
|
||||
}
|
||||
|
||||
func TestPluginAPIIsEnterpriseReady(t *testing.T) {
|
||||
|
||||
@@ -406,6 +406,10 @@ func (a *App) UpdateDNDStatusOfUsers() {
|
||||
}
|
||||
|
||||
func (a *App) SetCustomStatus(userID string, cs *model.CustomStatus) *model.AppError {
|
||||
if cs == nil || (cs.Emoji == "" && cs.Text == "") {
|
||||
return model.NewAppError("SetCustomStatus", "api.custom_status.set_custom_statuses.update.app_error", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
user, err := a.GetUser(userID)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -439,6 +443,15 @@ func (a *App) RemoveCustomStatus(userID string) *model.AppError {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) GetCustomStatus(userID string) (*model.CustomStatus, *model.AppError) {
|
||||
user, err := a.GetUser(userID)
|
||||
if err != nil {
|
||||
return &model.CustomStatus{}, err
|
||||
}
|
||||
|
||||
return user.GetCustomStatus(), nil
|
||||
}
|
||||
|
||||
func (a *App) addRecentCustomStatus(userID string, status *model.CustomStatus) *model.AppError {
|
||||
var newRCS model.RecentCustomStatuses
|
||||
|
||||
|
||||
@@ -37,3 +37,30 @@ func TestSaveStatus(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCustomStatus(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
user := th.BasicUser
|
||||
|
||||
cs := &model.CustomStatus{
|
||||
Emoji: ":smile:",
|
||||
Text: "honk!",
|
||||
}
|
||||
|
||||
err := th.App.SetCustomStatus(user.Id, cs)
|
||||
require.Nil(t, err, "failed to set custom status %v", err)
|
||||
|
||||
csSaved, err := th.App.GetCustomStatus(user.Id)
|
||||
require.Nil(t, err, "failed to get custom status after save %v", err)
|
||||
require.Equal(t, cs, csSaved)
|
||||
|
||||
err = th.App.RemoveCustomStatus(user.Id)
|
||||
require.Nil(t, err, "failed to to clear custom status %v", err)
|
||||
|
||||
var csClear *model.CustomStatus
|
||||
csSaved, err = th.App.GetCustomStatus(user.Id)
|
||||
require.Nil(t, err, "failed to get custom status after clear %v", err)
|
||||
require.Equal(t, csClear, csSaved)
|
||||
}
|
||||
|
||||
@@ -1566,6 +1566,10 @@
|
||||
"id": "api.custom_status.recent_custom_statuses.delete.app_error",
|
||||
"translation": "Failed to delete the recent status. Please try adding the status first or contact your system administrator for details."
|
||||
},
|
||||
{
|
||||
"id": "api.custom_status.set_custom_statuses.update.app_error",
|
||||
"translation": "Failed to update the custom status. Please add either emoji or custom text status or both."
|
||||
},
|
||||
{
|
||||
"id": "api.email.send_warn_metric_ack.failure.app_error",
|
||||
"translation": "Failure to send admin acknowledgment email"
|
||||
|
||||
@@ -6154,6 +6154,36 @@ func (c *Client4) UpdateUserStatus(userId string, userStatus *Status) (*Status,
|
||||
return &s, BuildResponse(r), nil
|
||||
}
|
||||
|
||||
// UpdateUserCustomStatus sets a user's custom status based on the provided user id string.
|
||||
func (c *Client4) UpdateUserCustomStatus(userId string, userCustomStatus *CustomStatus) (*CustomStatus, *Response) {
|
||||
r, err := c.DoApiPut(c.GetUserStatusRoute(userId)+"/custom", userCustomStatus.ToJson())
|
||||
if err != nil {
|
||||
return nil, BuildErrorResponse(r, err)
|
||||
}
|
||||
defer closeBody(r)
|
||||
return CustomStatusFromJson(r.Body), BuildResponse(r)
|
||||
}
|
||||
|
||||
// RemoveUserCustomStatus remove a user's custom status based on the provided user id string.
|
||||
func (c *Client4) RemoveUserCustomStatus(userId string) (bool, *Response) {
|
||||
r, err := c.DoApiDelete(c.GetUserStatusRoute(userId) + "/custom")
|
||||
if err != nil {
|
||||
return false, BuildErrorResponse(r, err)
|
||||
}
|
||||
defer closeBody(r)
|
||||
return CheckStatusOK(r), BuildResponse(r)
|
||||
}
|
||||
|
||||
// RemoveRecentUserCustomStatus remove a recent user's custom status based on the provided user id string.
|
||||
func (c *Client4) RemoveRecentUserCustomStatus(userId string) (bool, *Response) {
|
||||
r, err := c.DoApiDelete(c.GetUserStatusRoute(userId) + "/custom/recent")
|
||||
if err != nil {
|
||||
return false, BuildErrorResponse(r, err)
|
||||
}
|
||||
defer closeBody(r)
|
||||
return CheckStatusOK(r), BuildResponse(r)
|
||||
}
|
||||
|
||||
// Emoji Section
|
||||
|
||||
// CreateEmoji will save an emoji to the server if the current user has permission
|
||||
|
||||
@@ -621,6 +621,15 @@ func (u *User) SetCustomStatus(cs *CustomStatus) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *User) GetCustomStatus() *CustomStatus {
|
||||
var o *CustomStatus
|
||||
|
||||
data := u.Props[UserPropsKeyCustomStatus]
|
||||
_ = json.Unmarshal([]byte(data), &o)
|
||||
|
||||
return o
|
||||
}
|
||||
|
||||
func (u *User) ClearCustomStatus() {
|
||||
u.MakeNonNil()
|
||||
u.Props[UserPropsKeyCustomStatus] = ""
|
||||
|
||||
@@ -257,6 +257,19 @@ type API interface {
|
||||
// Minimum server version: 5.8
|
||||
UpdateUserActive(userID string, active bool) *model.AppError
|
||||
|
||||
// UpdateUserCustomStatus will set a user's custom status until the user, or another integration/plugin, clear it or update the custom status.
|
||||
// The custom status have two parameters: emoji icon and custom text.
|
||||
//
|
||||
// @tag User
|
||||
// Minimum server version: 5.36
|
||||
UpdateUserCustomStatus(userID string, customStatus *model.CustomStatus) *model.AppError
|
||||
|
||||
// RemoveUserCustomStatus will remove a user's custom status.
|
||||
//
|
||||
// @tag User
|
||||
// Minimum server version: 5.36
|
||||
RemoveUserCustomStatus(userID string) *model.AppError
|
||||
|
||||
// GetUsersInChannel returns a page of users in a channel. Page counting starts at 0.
|
||||
// The sortBy parameter can be: "username" or "status".
|
||||
//
|
||||
|
||||
@@ -301,6 +301,20 @@ func (api *apiTimerLayer) UpdateUserActive(userID string, active bool) *model.Ap
|
||||
return _returnsA
|
||||
}
|
||||
|
||||
func (api *apiTimerLayer) UpdateUserCustomStatus(userID string, customStatus *model.CustomStatus) *model.AppError {
|
||||
startTime := timePkg.Now()
|
||||
_returnsA := api.apiImpl.UpdateUserCustomStatus(userID, customStatus)
|
||||
api.recordTime(startTime, "UpdateUserCustomStatus", _returnsA == nil)
|
||||
return _returnsA
|
||||
}
|
||||
|
||||
func (api *apiTimerLayer) RemoveUserCustomStatus(userID string) *model.AppError {
|
||||
startTime := timePkg.Now()
|
||||
_returnsA := api.apiImpl.RemoveUserCustomStatus(userID)
|
||||
api.recordTime(startTime, "RemoveUserCustomStatus", _returnsA == nil)
|
||||
return _returnsA
|
||||
}
|
||||
|
||||
func (api *apiTimerLayer) GetUsersInChannel(channelID, sortBy string, page, perPage int) ([]*model.User, *model.AppError) {
|
||||
startTime := timePkg.Now()
|
||||
_returnsA, _returnsB := api.apiImpl.GetUsersInChannel(channelID, sortBy, page, perPage)
|
||||
|
||||
@@ -1755,6 +1755,63 @@ func (s *apiRPCServer) UpdateUserActive(args *Z_UpdateUserActiveArgs, returns *Z
|
||||
return nil
|
||||
}
|
||||
|
||||
type Z_UpdateUserCustomStatusArgs struct {
|
||||
A string
|
||||
B *model.CustomStatus
|
||||
}
|
||||
|
||||
type Z_UpdateUserCustomStatusReturns struct {
|
||||
A *model.AppError
|
||||
}
|
||||
|
||||
func (g *apiRPCClient) UpdateUserCustomStatus(userID string, customStatus *model.CustomStatus) *model.AppError {
|
||||
_args := &Z_UpdateUserCustomStatusArgs{userID, customStatus}
|
||||
_returns := &Z_UpdateUserCustomStatusReturns{}
|
||||
if err := g.client.Call("Plugin.UpdateUserCustomStatus", _args, _returns); err != nil {
|
||||
log.Printf("RPC call to UpdateUserCustomStatus API failed: %s", err.Error())
|
||||
}
|
||||
return _returns.A
|
||||
}
|
||||
|
||||
func (s *apiRPCServer) UpdateUserCustomStatus(args *Z_UpdateUserCustomStatusArgs, returns *Z_UpdateUserCustomStatusReturns) error {
|
||||
if hook, ok := s.impl.(interface {
|
||||
UpdateUserCustomStatus(userID string, customStatus *model.CustomStatus) *model.AppError
|
||||
}); ok {
|
||||
returns.A = hook.UpdateUserCustomStatus(args.A, args.B)
|
||||
} else {
|
||||
return encodableError(fmt.Errorf("API UpdateUserCustomStatus called but not implemented."))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Z_RemoveUserCustomStatusArgs struct {
|
||||
A string
|
||||
}
|
||||
|
||||
type Z_RemoveUserCustomStatusReturns struct {
|
||||
A *model.AppError
|
||||
}
|
||||
|
||||
func (g *apiRPCClient) RemoveUserCustomStatus(userID string) *model.AppError {
|
||||
_args := &Z_RemoveUserCustomStatusArgs{userID}
|
||||
_returns := &Z_RemoveUserCustomStatusReturns{}
|
||||
if err := g.client.Call("Plugin.RemoveUserCustomStatus", _args, _returns); err != nil {
|
||||
log.Printf("RPC call to RemoveUserCustomStatus API failed: %s", err.Error())
|
||||
}
|
||||
return _returns.A
|
||||
}
|
||||
|
||||
func (s *apiRPCServer) RemoveUserCustomStatus(args *Z_RemoveUserCustomStatusArgs, returns *Z_RemoveUserCustomStatusReturns) error {
|
||||
if hook, ok := s.impl.(interface {
|
||||
RemoveUserCustomStatus(userID string) *model.AppError
|
||||
}); ok {
|
||||
returns.A = hook.RemoveUserCustomStatus(args.A)
|
||||
} else {
|
||||
return encodableError(fmt.Errorf("API RemoveUserCustomStatus called but not implemented."))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Z_GetUsersInChannelArgs struct {
|
||||
A string
|
||||
B string
|
||||
|
||||
@@ -2918,6 +2918,22 @@ func (_m *API) RemoveTeamIcon(teamID string) *model.AppError {
|
||||
return r0
|
||||
}
|
||||
|
||||
// RemoveUserCustomStatus provides a mock function with given fields: userID
|
||||
func (_m *API) RemoveUserCustomStatus(userID string) *model.AppError {
|
||||
ret := _m.Called(userID)
|
||||
|
||||
var r0 *model.AppError
|
||||
if rf, ok := ret.Get(0).(func(string) *model.AppError); ok {
|
||||
r0 = rf(userID)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// RequestTrialLicense provides a mock function with given fields: requesterID, users, termsAccepted, receiveEmailsAccepted
|
||||
func (_m *API) RequestTrialLicense(requesterID string, users int, termsAccepted bool, receiveEmailsAccepted bool) *model.AppError {
|
||||
ret := _m.Called(requesterID, users, termsAccepted, receiveEmailsAccepted)
|
||||
@@ -3531,6 +3547,22 @@ func (_m *API) UpdateUserActive(userID string, active bool) *model.AppError {
|
||||
return r0
|
||||
}
|
||||
|
||||
// UpdateUserCustomStatus provides a mock function with given fields: userID, customStatus
|
||||
func (_m *API) UpdateUserCustomStatus(userID string, customStatus *model.CustomStatus) *model.AppError {
|
||||
ret := _m.Called(userID, customStatus)
|
||||
|
||||
var r0 *model.AppError
|
||||
if rf, ok := ret.Get(0).(func(string, *model.CustomStatus) *model.AppError); ok {
|
||||
r0 = rf(userID, customStatus)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// UpdateUserStatus provides a mock function with given fields: userID, status
|
||||
func (_m *API) UpdateUserStatus(userID string, status string) (*model.Status, *model.AppError) {
|
||||
ret := _m.Called(userID, status)
|
||||
|
||||
Ссылка в новой задаче
Block a user