MM-53924 - Implement push notifications plugin hook and plugin api method (#24350)

* Revert "MM-52804 - Implement SendPushNotification plugin api method (#24273)"

This reverts commit 8418eefb75.

* Revert "MM-53924 - Implement NotificationWillBePushed plugin hook (#24263)"

This reverts commit f13a531bca.

* implement NotificationWillBePushed plugin hook

* implement SendPushNotification plugin api method

* move where we're setting post and channel type

* fix comment

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
Christopher Poile
2023-08-24 12:33:53 -04:00
коммит произвёл GitHub
родитель dad579daee
Коммит 69c11cfe14
16 изменённых файлов: 169 добавлений и 166 удалений

Просмотреть файл

@@ -1,21 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
// PluginPushNotification is sent to the plugin when a push notification is going to be sent (via the
// NotificationWillBePushed hook), and is used as the source data for the plugin api SendPluginPushNotification method.
//
// Note: Please keep in mind that Mattermost push notifications always refer to a specific post. Therefore, when used
// as source data for `SendPluginPushNotification`, Post and Channel must be valid in order to create a correct
// model.PushNotification.
// Note: Post and Channel are pointers so that plugins using the NotificationWillBePushed hook will not need to query the
// database on every push notification.
type PluginPushNotification struct {
Post *Post // The post that will be used as the source of the push notification.
Channel *Channel // The channel that the post appeared in.
UserID string // The receiver of the push notification.
ExplicitMention bool // Used to construct the generic "@sender mentioned you" msg when `cfg.EmailSettings.PushNotificationContents` is not set to `full`
ChannelWideMention bool // Used to construct the generic "@sender notified the channel" msg when `cfg.EmailSettings.PushNotificationContents` is not set to `full`
ReplyToThreadType string // Used to construct the generic CRT msgs when `cfg.EmailSettings.PushNotificationContents` is not set to `full`; see `App.getPushNotificationMessage` for details.
}

Просмотреть файл

@@ -44,29 +44,31 @@ type PushNotificationAck struct {
}
type PushNotification struct {
AckId string `json:"ack_id"`
Platform string `json:"platform"`
ServerId string `json:"server_id"`
DeviceId string `json:"device_id"`
PostId string `json:"post_id"`
Category string `json:"category,omitempty"`
Sound string `json:"sound,omitempty"`
Message string `json:"message,omitempty"`
Badge int `json:"badge,omitempty"`
ContentAvailable int `json:"cont_ava,omitempty"`
TeamId string `json:"team_id,omitempty"`
ChannelId string `json:"channel_id,omitempty"`
RootId string `json:"root_id,omitempty"`
ChannelName string `json:"channel_name,omitempty"`
Type string `json:"type,omitempty"`
SenderId string `json:"sender_id,omitempty"`
SenderName string `json:"sender_name,omitempty"`
OverrideUsername string `json:"override_username,omitempty"`
OverrideIconURL string `json:"override_icon_url,omitempty"`
FromWebhook string `json:"from_webhook,omitempty"`
Version string `json:"version,omitempty"`
IsCRTEnabled bool `json:"is_crt_enabled"`
IsIdLoaded bool `json:"is_id_loaded"`
AckId string `json:"ack_id"`
Platform string `json:"platform"`
ServerId string `json:"server_id"`
DeviceId string `json:"device_id"`
PostId string `json:"post_id"`
Category string `json:"category,omitempty"`
Sound string `json:"sound,omitempty"`
Message string `json:"message,omitempty"`
Badge int `json:"badge,omitempty"`
ContentAvailable int `json:"cont_ava,omitempty"`
TeamId string `json:"team_id,omitempty"`
ChannelId string `json:"channel_id,omitempty"`
RootId string `json:"root_id,omitempty"`
ChannelName string `json:"channel_name,omitempty"`
Type string `json:"type,omitempty"`
SenderId string `json:"sender_id,omitempty"`
SenderName string `json:"sender_name,omitempty"`
OverrideUsername string `json:"override_username,omitempty"`
OverrideIconURL string `json:"override_icon_url,omitempty"`
FromWebhook string `json:"from_webhook,omitempty"`
Version string `json:"version,omitempty"`
IsCRTEnabled bool `json:"is_crt_enabled"`
IsIdLoaded bool `json:"is_id_loaded"`
PostType string `json:"-"`
ChannelType ChannelType `json:"-"`
}
func (pn *PushNotification) DeepCopy() *PushNotification {

Просмотреть файл

@@ -1181,14 +1181,17 @@ type API interface {
// Minimum server version: 7.6
GetUploadSession(uploadID string) (*model.UploadSession, error)
// SendPluginPushNotification will attempt to send a push notification to `notification.User`, using
// `notification.Post` as the source of the notification. The server will use the PluginPushNotification
// data to construct the final push notification according to the server's configuration and license. Refer
// to `App.BuildPushNotificationMessage` for the logic used to construct the push notification.
// Note: the NotificationWillBePushed hook will be run after SendPluginPushNotification is called.
// SendPushNotification will send a push notification to all of user's sessions.
//
// It is the responsibility of the plugin to respect the server's configuration and licence,
// especially related to `cfg.EmailSettings.PushNotificationContents`, particularly
// `model.IdLoadedNotification` and the generic settings.
// Refer to `app.sendPushNotificationSync` for the logic used to construct push notifications.
//
// Note: the NotificationWillBePushed hook will be run after SendPushNotification is called.
//
// Minimum server version: 9.0
SendPluginPushNotification(notification *model.PluginPushNotification) error
SendPushNotification(notification *model.PushNotification, userID string) *model.AppError
}
var handshake = plugin.HandshakeConfig{

Просмотреть файл

@@ -1267,9 +1267,9 @@ func (api *apiTimerLayer) GetUploadSession(uploadID string) (*model.UploadSessio
return _returnsA, _returnsB
}
func (api *apiTimerLayer) SendPluginPushNotification(notification *model.PluginPushNotification) error {
func (api *apiTimerLayer) SendPushNotification(notification *model.PushNotification, userID string) *model.AppError {
startTime := timePkg.Now()
_returnsA := api.apiImpl.SendPluginPushNotification(notification)
api.recordTime(startTime, "SendPluginPushNotification", _returnsA == nil)
_returnsA := api.apiImpl.SendPushNotification(notification, userID)
api.recordTime(startTime, "SendPushNotification", _returnsA == nil)
return _returnsA
}

Просмотреть файл

@@ -848,29 +848,31 @@ func init() {
}
type Z_NotificationWillBePushedArgs struct {
A *model.PluginPushNotification
A *model.PushNotification
B string
}
type Z_NotificationWillBePushedReturns struct {
A bool
A *model.PushNotification
B string
}
func (g *hooksRPCClient) NotificationWillBePushed(pushNotification *model.PluginPushNotification) (cancel bool) {
_args := &Z_NotificationWillBePushedArgs{pushNotification}
func (g *hooksRPCClient) NotificationWillBePushed(pushNotification *model.PushNotification, userID string) (*model.PushNotification, string) {
_args := &Z_NotificationWillBePushedArgs{pushNotification, userID}
_returns := &Z_NotificationWillBePushedReturns{}
if g.implemented[NotificationWillBePushedID] {
if err := g.client.Call("Plugin.NotificationWillBePushed", _args, _returns); err != nil {
g.log.Error("RPC call NotificationWillBePushed to plugin failed.", mlog.Err(err))
}
}
return _returns.A
return _returns.A, _returns.B
}
func (s *hooksRPCServer) NotificationWillBePushed(args *Z_NotificationWillBePushedArgs, returns *Z_NotificationWillBePushedReturns) error {
if hook, ok := s.impl.(interface {
NotificationWillBePushed(pushNotification *model.PluginPushNotification) (cancel bool)
NotificationWillBePushed(pushNotification *model.PushNotification, userID string) (*model.PushNotification, string)
}); ok {
returns.A = hook.NotificationWillBePushed(args.A)
returns.A, returns.B = hook.NotificationWillBePushed(args.A, args.B)
} else {
return encodableError(fmt.Errorf("Hook NotificationWillBePushed called but not implemented."))
}
@@ -5869,31 +5871,31 @@ func (s *apiRPCServer) GetUploadSession(args *Z_GetUploadSessionArgs, returns *Z
return nil
}
type Z_SendPluginPushNotificationArgs struct {
A *model.PluginPushNotification
type Z_SendPushNotificationArgs struct {
A *model.PushNotification
B string
}
type Z_SendPluginPushNotificationReturns struct {
A error
type Z_SendPushNotificationReturns struct {
A *model.AppError
}
func (g *apiRPCClient) SendPluginPushNotification(notification *model.PluginPushNotification) error {
_args := &Z_SendPluginPushNotificationArgs{notification}
_returns := &Z_SendPluginPushNotificationReturns{}
if err := g.client.Call("Plugin.SendPluginPushNotification", _args, _returns); err != nil {
log.Printf("RPC call to SendPluginPushNotification API failed: %s", err.Error())
func (g *apiRPCClient) SendPushNotification(notification *model.PushNotification, userID string) *model.AppError {
_args := &Z_SendPushNotificationArgs{notification, userID}
_returns := &Z_SendPushNotificationReturns{}
if err := g.client.Call("Plugin.SendPushNotification", _args, _returns); err != nil {
log.Printf("RPC call to SendPushNotification API failed: %s", err.Error())
}
return _returns.A
}
func (s *apiRPCServer) SendPluginPushNotification(args *Z_SendPluginPushNotificationArgs, returns *Z_SendPluginPushNotificationReturns) error {
func (s *apiRPCServer) SendPushNotification(args *Z_SendPushNotificationArgs, returns *Z_SendPushNotificationReturns) error {
if hook, ok := s.impl.(interface {
SendPluginPushNotification(notification *model.PluginPushNotification) error
SendPushNotification(notification *model.PushNotification, userID string) *model.AppError
}); ok {
returns.A = hook.SendPluginPushNotification(args.A)
returns.A = encodableError(returns.A)
returns.A = hook.SendPushNotification(args.A, args.B)
} else {
return encodableError(fmt.Errorf("API SendPluginPushNotification called but not implemented."))
return encodableError(fmt.Errorf("API SendPushNotification called but not implemented."))
}
return nil
}

Просмотреть файл

@@ -287,13 +287,15 @@ type Hooks interface {
ConfigurationWillBeSaved(newCfg *model.Config) (*model.Config, error)
// NotificationWillBePushed is invoked before a push notification is sent to the push
// notification server. The intention is to allow plugins to cancel a push notification.
// notification server.
//
// To cancel a push notification, return true.
// To reject a notification, return an non-empty string describing why the notification was rejected.
// To modify the notification, return the replacement, non-nil *model.PushNotification and an empty string.
// To allow the notification without modification, return a nil *model.PushNotification and an empty string.
//
// Note that this method will be called for push notification sent by plugins, including
// the plugin that created the push notification.
// Note that this method will be called for push notifications created by plugins, including the plugin that
// created the notification.
//
// Minimum server version: 9.0
NotificationWillBePushed(pushNotification *model.PluginPushNotification) (cancel bool)
NotificationWillBePushed(pushNotification *model.PushNotification, userID string) (*model.PushNotification, string)
}

Просмотреть файл

@@ -219,9 +219,9 @@ func (hooks *hooksTimerLayer) ConfigurationWillBeSaved(newCfg *model.Config) (*m
return _returnsA, _returnsB
}
func (hooks *hooksTimerLayer) NotificationWillBePushed(pushNotification *model.PluginPushNotification) (cancel bool) {
func (hooks *hooksTimerLayer) NotificationWillBePushed(pushNotification *model.PushNotification, userID string) (*model.PushNotification, string) {
startTime := timePkg.Now()
_returnsA := hooks.hooksImpl.NotificationWillBePushed(pushNotification)
_returnsA, _returnsB := hooks.hooksImpl.NotificationWillBePushed(pushNotification, userID)
hooks.recordTime(startTime, "NotificationWillBePushed", true)
return _returnsA
return _returnsA, _returnsB
}

Просмотреть файл

@@ -3639,15 +3639,17 @@ func (_m *API) SendMail(to string, subject string, htmlBody string) *model.AppEr
return r0
}
// SendPluginPushNotification provides a mock function with given fields: notification
func (_m *API) SendPluginPushNotification(notification *model.PluginPushNotification) error {
ret := _m.Called(notification)
// SendPushNotification provides a mock function with given fields: notification, userID
func (_m *API) SendPushNotification(notification *model.PushNotification, userID string) *model.AppError {
ret := _m.Called(notification, userID)
var r0 error
if rf, ok := ret.Get(0).(func(*model.PluginPushNotification) error); ok {
r0 = rf(notification)
var r0 *model.AppError
if rf, ok := ret.Get(0).(func(*model.PushNotification, string) *model.AppError); ok {
r0 = rf(notification, userID)
} else {
r0 = ret.Error(0)
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.AppError)
}
}
return r0

Просмотреть файл

@@ -193,18 +193,30 @@ func (_m *Hooks) MessageWillBeUpdated(c *plugin.Context, newPost *model.Post, ol
return r0, r1
}
// NotificationWillBePushed provides a mock function with given fields: pushNotification
func (_m *Hooks) NotificationWillBePushed(pushNotification *model.PluginPushNotification) bool {
ret := _m.Called(pushNotification)
// NotificationWillBePushed provides a mock function with given fields: pushNotification, userID
func (_m *Hooks) NotificationWillBePushed(pushNotification *model.PushNotification, userID string) (*model.PushNotification, string) {
ret := _m.Called(pushNotification, userID)
var r0 bool
if rf, ok := ret.Get(0).(func(*model.PluginPushNotification) bool); ok {
r0 = rf(pushNotification)
var r0 *model.PushNotification
var r1 string
if rf, ok := ret.Get(0).(func(*model.PushNotification, string) (*model.PushNotification, string)); ok {
return rf(pushNotification, userID)
}
if rf, ok := ret.Get(0).(func(*model.PushNotification, string) *model.PushNotification); ok {
r0 = rf(pushNotification, userID)
} else {
r0 = ret.Get(0).(bool)
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.PushNotification)
}
}
return r0
if rf, ok := ret.Get(1).(func(*model.PushNotification, string) string); ok {
r1 = rf(pushNotification, userID)
} else {
r1 = ret.Get(1).(string)
}
return r0, r1
}
// OnActivate provides a mock function with given fields:

Просмотреть файл

@@ -119,7 +119,7 @@ type ConfigurationWillBeSavedIFace interface {
}
type NotificationWillBePushedIFace interface {
NotificationWillBePushed(pushNotification *model.PluginPushNotification) (cancel bool)
NotificationWillBePushed(pushNotification *model.PushNotification, userID string) (*model.PushNotification, string)
}
type HooksAdapter struct {
@@ -615,11 +615,11 @@ func (a *HooksAdapter) ConfigurationWillBeSaved(newCfg *model.Config) (*model.Co
}
func (a *HooksAdapter) NotificationWillBePushed(pushNotification *model.PluginPushNotification) (cancel bool) {
func (a *HooksAdapter) NotificationWillBePushed(pushNotification *model.PushNotification, userID string) (*model.PushNotification, string) {
if _, ok := a.implemented[NotificationWillBePushedID]; !ok {
panic("product hooks must implement NotificationWillBePushed")
}
return a.productHooks.(NotificationWillBePushedIFace).NotificationWillBePushed(pushNotification)
return a.productHooks.(NotificationWillBePushedIFace).NotificationWillBePushed(pushNotification, userID)
}