Add test notification tool (#28334)
* Add test notification tool * Add frontend styles * Remove option from admin view * Refactor create post and add translations * Fix several CI errors * Fix API and frontend snapshots * Refactor trailing and leading icon on buttons * Add different button states * i18n-extract * Fix wrong text * Add tests * Fix wrong string * Fix test * feat: E2E send test notifications (#28371) * Refactor send desktop notification * Address rest of the feedback * Fix tests * Add correct link * Fix test --------- Co-authored-by: Mattermost Build <build@mattermost.com> Co-authored-by: yasserfaraazkhan <attitude3cena.yf@gmail.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
30a6ddc995
Коммит
118d0346ee
@@ -45,6 +45,7 @@ func (api *API) InitSystem() {
|
||||
api.BaseRoutes.System.Handle("/timezones", api.APISessionRequired(getSupportedTimezones)).Methods(http.MethodGet)
|
||||
|
||||
api.BaseRoutes.APIRoot.Handle("/audits", api.APISessionRequired(getAudits)).Methods(http.MethodGet)
|
||||
api.BaseRoutes.APIRoot.Handle("/notifications/test", api.APISessionRequired(testNotifications)).Methods(http.MethodPost)
|
||||
api.BaseRoutes.APIRoot.Handle("/email/test", api.APISessionRequired(testEmail)).Methods(http.MethodPost)
|
||||
api.BaseRoutes.APIRoot.Handle("/site_url/test", api.APISessionRequired(testSiteURL)).Methods(http.MethodPost)
|
||||
api.BaseRoutes.APIRoot.Handle("/file/s3_test", api.APISessionRequired(testS3)).Methods(http.MethodPost)
|
||||
@@ -232,6 +233,16 @@ func getSystemPing(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func testNotifications(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
_, err := c.App.SendTestMessage(c.AppContext, c.AppContext.Session().UserId)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
ReturnStatusOK(w)
|
||||
}
|
||||
|
||||
func testEmail(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
var cfg *model.Config
|
||||
err := json.NewDecoder(r.Body).Decode(&cfg)
|
||||
|
||||
@@ -1103,6 +1103,7 @@ type AppIface interface {
|
||||
SendPasswordReset(rctx request.CTX, email string, siteURL string) (bool, *model.AppError)
|
||||
SendPersistentNotifications() error
|
||||
SendReportToUser(rctx request.CTX, job *model.Job, format string) *model.AppError
|
||||
SendTestMessage(c request.CTX, userID string) (*model.Post, *model.AppError)
|
||||
SendTestPushNotification(deviceID string) string
|
||||
ServeInterPluginRequest(w http.ResponseWriter, r *http.Request, sourcePluginId, destinationPluginId string)
|
||||
SessionHasPermissionTo(session model.Session, permission *model.Permission) bool
|
||||
|
||||
@@ -599,6 +599,10 @@ func (a *App) getMobileAppSessions(userID string) ([]*model.Session, *model.AppE
|
||||
}
|
||||
|
||||
func (a *App) ShouldSendPushNotification(user *model.User, channelNotifyProps model.StringMap, wasMentioned bool, status *model.Status, post *model.Post, isGM bool) bool {
|
||||
if prop := post.GetProp(model.PostPropsForceNotification); prop != nil && prop != "" {
|
||||
return true
|
||||
}
|
||||
|
||||
if notifyPropsAllowedReason := DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, wasMentioned, isGM); notifyPropsAllowedReason != "" {
|
||||
a.CountNotificationReason(model.NotificationStatusNotSent, model.NotificationTypePush, notifyPropsAllowedReason, model.NotificationNoPlatform)
|
||||
a.NotificationsLog().Debug("Notification not sent - notify props",
|
||||
|
||||
@@ -1103,6 +1103,39 @@ func TestSendPushNotifications(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestShouldSendPushNotifications(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
t.Run("should return true if forced", func(t *testing.T) {
|
||||
user := &model.User{Id: model.NewId(), Email: "unit@test.com", NotifyProps: make(map[string]string)}
|
||||
user.NotifyProps[model.PushNotifyProp] = model.UserNotifyNone
|
||||
|
||||
post := &model.Post{UserId: user.Id, ChannelId: model.NewId()}
|
||||
post.AddProp(model.PostPropsForceNotification, model.NewId())
|
||||
|
||||
channelNotifyProps := map[string]string{model.PushNotifyProp: model.ChannelNotifyNone, model.MarkUnreadNotifyProp: model.ChannelMarkUnreadMention}
|
||||
|
||||
status := &model.Status{UserId: user.Id, Status: model.StatusOnline, Manual: false, LastActivityAt: model.GetMillis(), ActiveChannel: post.ChannelId}
|
||||
|
||||
result := th.App.ShouldSendPushNotification(user, channelNotifyProps, false, status, post, false)
|
||||
assert.True(t, result)
|
||||
})
|
||||
|
||||
t.Run("should return false if force undefined", func(t *testing.T) {
|
||||
user := &model.User{Id: model.NewId(), Email: "unit@test.com", NotifyProps: make(map[string]string)}
|
||||
user.NotifyProps[model.PushNotifyProp] = model.UserNotifyNone
|
||||
|
||||
post := &model.Post{UserId: user.Id, ChannelId: model.NewId()}
|
||||
|
||||
channelNotifyProps := map[string]string{model.PushNotifyProp: model.ChannelNotifyNone, model.MarkUnreadNotifyProp: model.ChannelMarkUnreadMention}
|
||||
|
||||
status := &model.Status{UserId: user.Id, Status: model.StatusOnline, Manual: false, LastActivityAt: model.GetMillis(), ActiveChannel: post.ChannelId}
|
||||
|
||||
result := th.App.ShouldSendPushNotification(user, channelNotifyProps, false, status, post, false)
|
||||
assert.False(t, result)
|
||||
})
|
||||
}
|
||||
|
||||
// testPushNotificationHandler is an HTTP handler to record push notifications
|
||||
// being sent from the client.
|
||||
// It records the number of requests sent to it, and stores all the requests
|
||||
|
||||
@@ -16398,6 +16398,28 @@ func (a *OpenTracingAppLayer) SendSubscriptionHistoryEvent(userID string) (*mode
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) SendTestMessage(c request.CTX, userID string) (*model.Post, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.SendTestMessage")
|
||||
|
||||
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.SendTestMessage(c, userID)
|
||||
|
||||
if resultVar1 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar1))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) SendTestPushNotification(deviceID string) string {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.SendTestPushNotification")
|
||||
|
||||
@@ -242,6 +242,10 @@ func (a *App) CreatePost(c request.CTX, post *model.Post, channel *model.Channel
|
||||
post.AddProp(model.PostPropsFromBot, "true")
|
||||
}
|
||||
|
||||
if flags.ForceNotification {
|
||||
post.AddProp(model.PostPropsForceNotification, model.NewId())
|
||||
}
|
||||
|
||||
if c.Session().IsOAuth {
|
||||
post.AddProp(model.PostPropsFromOAuthApp, "true")
|
||||
}
|
||||
@@ -2722,3 +2726,34 @@ func (a *App) CleanUpAfterPostDeletion(c request.CTX, post *model.Post, deleteBy
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) SendTestMessage(c request.CTX, userID string) (*model.Post, *model.AppError) {
|
||||
bot, err := a.GetSystemBot(c)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("SendTestMessage", "app.notifications.send_test_message.errors.no_bot", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
channel, err := a.GetOrCreateDirectChannel(c, userID, bot.UserId)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("SendTestMessage", "app.notifications.send_test_message.errors.no_channel", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
user, err := a.GetUser(userID)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("SendTestMessage", "app.notifications.send_test_message.errors.no_user", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
T := i18n.GetUserTranslations(user.Locale)
|
||||
post := &model.Post{
|
||||
ChannelId: channel.Id,
|
||||
Message: T("app.notifications.send_test_message.message_body"),
|
||||
Type: model.PostTypeDefault,
|
||||
UserId: bot.UserId,
|
||||
}
|
||||
|
||||
post, err = a.CreatePost(c, post, channel, model.CreatePostFlags{ForceNotification: true})
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("SendTestMessage", "app.notifications.send_test_message.errors.create_post", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
return post, nil
|
||||
}
|
||||
|
||||
@@ -1178,6 +1178,37 @@ func TestCreatePost(t *testing.T) {
|
||||
|
||||
wg.Wait()
|
||||
})
|
||||
|
||||
t.Run("should sanitize the force notifications prop if the flag is not set", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
th.AddUserToChannel(th.BasicUser, th.BasicChannel)
|
||||
|
||||
postToCreate := &model.Post{
|
||||
ChannelId: th.BasicChannel.Id,
|
||||
Message: "hello world",
|
||||
UserId: th.BasicUser.Id,
|
||||
}
|
||||
postToCreate.AddProp(model.PostPropsForceNotification, model.NewId())
|
||||
createdPost, err := th.App.CreatePost(th.Context, postToCreate, th.BasicChannel, model.CreatePostFlags{})
|
||||
require.Nil(t, err)
|
||||
require.Empty(t, createdPost.GetProp(model.PostPropsForceNotification))
|
||||
})
|
||||
|
||||
t.Run("should add the force notifications prop if the flag is set", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
th.AddUserToChannel(th.BasicUser, th.BasicChannel)
|
||||
|
||||
postToCreate := &model.Post{
|
||||
ChannelId: th.BasicChannel.Id,
|
||||
Message: "hello world",
|
||||
UserId: th.BasicUser.Id,
|
||||
}
|
||||
createdPost, err := th.App.CreatePost(th.Context, postToCreate, th.BasicChannel, model.CreatePostFlags{ForceNotification: true})
|
||||
require.Nil(t, err)
|
||||
require.NotEmpty(t, createdPost.GetProp(model.PostPropsForceNotification))
|
||||
})
|
||||
}
|
||||
|
||||
func TestPatchPost(t *testing.T) {
|
||||
@@ -3739,3 +3770,13 @@ func TestPermanentDeletePost(t *testing.T) {
|
||||
assert.Len(t, infos, 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSendTestMessage(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
t.Run("Should create the post with the correct prop", func(t *testing.T) {
|
||||
post, result := th.App.SendTestMessage(th.Context, th.BasicUser.Id)
|
||||
assert.Nil(t, result)
|
||||
assert.NotEmpty(t, post.GetProp(model.PostPropsForceNotification))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -5938,6 +5938,26 @@
|
||||
"id": "app.notification.subject.notification.full",
|
||||
"translation": "[{{ .SiteName }}] Notification in {{ .TeamName}} on {{.Month}} {{.Day}}, {{.Year}}"
|
||||
},
|
||||
{
|
||||
"id": "app.notifications.send_test_message.errors.create_post",
|
||||
"translation": "The post cannot be created"
|
||||
},
|
||||
{
|
||||
"id": "app.notifications.send_test_message.errors.no_bot",
|
||||
"translation": "Cannot get the system bot"
|
||||
},
|
||||
{
|
||||
"id": "app.notifications.send_test_message.errors.no_channel",
|
||||
"translation": "Cannot get the system bot direct message"
|
||||
},
|
||||
{
|
||||
"id": "app.notifications.send_test_message.errors.no_user",
|
||||
"translation": "Cannot get the user"
|
||||
},
|
||||
{
|
||||
"id": "app.notifications.send_test_message.message_body",
|
||||
"translation": "If you received this test notification, it worked!"
|
||||
},
|
||||
{
|
||||
"id": "app.notify_admin.save.app_error",
|
||||
"translation": "Unable to save notify data."
|
||||
|
||||
@@ -344,6 +344,10 @@ func (c *Client4) testEmailRoute() string {
|
||||
return "/email/test"
|
||||
}
|
||||
|
||||
func (c *Client4) testNotificationRoute() string {
|
||||
return "/notifications/test"
|
||||
}
|
||||
|
||||
func (c *Client4) usageRoute() string {
|
||||
return "/usage"
|
||||
}
|
||||
@@ -4820,6 +4824,15 @@ func (c *Client4) TestEmail(ctx context.Context, config *Config) (*Response, err
|
||||
return BuildResponse(r), nil
|
||||
}
|
||||
|
||||
func (c *Client4) TestNotifications(ctx context.Context) (*Response, error) {
|
||||
r, err := c.DoAPIPost(ctx, c.testNotificationRoute(), "")
|
||||
if err != nil {
|
||||
return BuildResponse(r), err
|
||||
}
|
||||
defer closeBody(r)
|
||||
return BuildResponse(r), nil
|
||||
}
|
||||
|
||||
// TestSiteURL will test the validity of a site URL.
|
||||
func (c *Client4) TestSiteURL(ctx context.Context, siteURL string) (*Response, error) {
|
||||
requestBody := make(map[string]string)
|
||||
|
||||
@@ -75,6 +75,7 @@ const (
|
||||
PostPropsMentionHighlightDisabled = "mentionHighlightDisabled"
|
||||
PostPropsGroupHighlightDisabled = "disable_group_highlight"
|
||||
PostPropsPreviewedPost = "previewed_post"
|
||||
PostPropsForceNotification = "force_notification"
|
||||
|
||||
PostPriorityUrgent = "urgent"
|
||||
PostPropsRequestedAck = "requested_ack"
|
||||
@@ -339,8 +340,9 @@ func (o *Post) EncodeJSON(w io.Writer) error {
|
||||
}
|
||||
|
||||
type CreatePostFlags struct {
|
||||
TriggerWebhooks bool
|
||||
SetOnline bool
|
||||
TriggerWebhooks bool
|
||||
SetOnline bool
|
||||
ForceNotification bool
|
||||
}
|
||||
|
||||
type GetPostsSinceOptions struct {
|
||||
@@ -500,6 +502,7 @@ func (o *Post) SanitizeProps() {
|
||||
}
|
||||
membersToSanitize := []string{
|
||||
PropsAddChannelMember,
|
||||
PostPropsForceNotification,
|
||||
}
|
||||
|
||||
for _, member := range membersToSanitize {
|
||||
|
||||
@@ -115,29 +115,34 @@ func TestPostSanitizeProps(t *testing.T) {
|
||||
post1.SanitizeProps()
|
||||
|
||||
require.Nil(t, post1.GetProp(PropsAddChannelMember))
|
||||
require.Nil(t, post1.GetProp(PostPropsForceNotification))
|
||||
|
||||
post2 := &Post{
|
||||
Message: "test",
|
||||
Props: StringInterface{
|
||||
PropsAddChannelMember: "test",
|
||||
PropsAddChannelMember: "test",
|
||||
PostPropsForceNotification: "test",
|
||||
},
|
||||
}
|
||||
|
||||
post2.SanitizeProps()
|
||||
|
||||
require.Nil(t, post2.GetProp(PropsAddChannelMember))
|
||||
require.Nil(t, post2.GetProp(PostPropsForceNotification))
|
||||
|
||||
post3 := &Post{
|
||||
Message: "test",
|
||||
Props: StringInterface{
|
||||
PropsAddChannelMember: "no good",
|
||||
"attachments": "good",
|
||||
PropsAddChannelMember: "no good",
|
||||
PostPropsForceNotification: "no good",
|
||||
"attachments": "good",
|
||||
},
|
||||
}
|
||||
|
||||
post3.SanitizeProps()
|
||||
|
||||
require.Nil(t, post3.GetProp(PropsAddChannelMember))
|
||||
require.Nil(t, post3.GetProp(PostPropsForceNotification))
|
||||
|
||||
require.NotNil(t, post3.GetProp("attachments"))
|
||||
}
|
||||
|
||||
Ссылка в новой задаче
Block a user