Plugin API for Shared Channels: support auto invite (#25834)
* option for auto inviting plugin to all shared channels. * auto-invite remotes to shared channels when flag set * fix unit test --------- Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
@@ -9,6 +9,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost/server/public/model"
|
"github.com/mattermost/mattermost/server/public/model"
|
||||||
|
"github.com/mattermost/mattermost/server/public/shared/mlog"
|
||||||
"github.com/mattermost/mattermost/server/public/shared/request"
|
"github.com/mattermost/mattermost/server/public/shared/request"
|
||||||
"github.com/mattermost/mattermost/server/v8/channels/store"
|
"github.com/mattermost/mattermost/server/v8/channels/store"
|
||||||
)
|
)
|
||||||
@@ -325,11 +326,13 @@ func (a *App) OnSharedChannelsSyncMsg(msg *model.SyncMsg, rc *model.RemoteCluste
|
|||||||
func (a *App) OnSharedChannelsPing(rc *model.RemoteCluster) bool {
|
func (a *App) OnSharedChannelsPing(rc *model.RemoteCluster) bool {
|
||||||
pluginsEnvironment := a.GetPluginsEnvironment()
|
pluginsEnvironment := a.GetPluginsEnvironment()
|
||||||
if pluginsEnvironment == nil {
|
if pluginsEnvironment == nil {
|
||||||
|
a.Log().Error("Ping for shared channels cannot get plugins env")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
pluginHooks, err := pluginsEnvironment.HooksForPlugin(rc.PluginID)
|
pluginHooks, err := pluginsEnvironment.HooksForPlugin(rc.PluginID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
a.Log().Error("Ping for shared channels cannot get plugin hooks", mlog.String("plugin_id", rc.PluginID), mlog.Err(err))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -191,6 +191,10 @@ func (s sqlRemoteClusterStore) GetAll(filter model.RemoteClusterQueryFilter) ([]
|
|||||||
query = query.Where(sq.Eq{"rc.PluginID": filter.PluginID})
|
query = query.Where(sq.Eq{"rc.PluginID": filter.PluginID})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if filter.RequireOptions != 0 {
|
||||||
|
query = query.Where(sq.NotEq{fmt.Sprintf("(rc.Options & %d)", filter.RequireOptions): 0})
|
||||||
|
}
|
||||||
|
|
||||||
if filter.Topic != "" {
|
if filter.Topic != "" {
|
||||||
trimmed := strings.TrimSpace(filter.Topic)
|
trimmed := strings.TrimSpace(filter.Topic)
|
||||||
if trimmed == "" || trimmed == "*" {
|
if trimmed == "" || trimmed == "*" {
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ func (rcs *Service) pingGenerator(pingChan chan *model.RemoteCluster, done <-cha
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, rc := range remotes {
|
for _, rc := range remotes {
|
||||||
if rc.SiteURL != "" { // filter out unconfirmed invites
|
if rc.SiteURL != "" || rc.PluginID != "" { // filter out unconfirmed invites
|
||||||
pingChan <- rc
|
pingChan <- rc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -248,6 +248,16 @@ func (scs *Service) processTask(task syncTask) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// add all remotes that have the autoinvited option.
|
||||||
|
filter = model.RemoteClusterQueryFilter{
|
||||||
|
RequireOptions: model.BitflagOptionAutoInvited,
|
||||||
|
}
|
||||||
|
remotesAutoInvited, err := scs.server.GetStore().RemoteCluster().GetAll(filter)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
remotes = append(remotes, remotesAutoInvited...)
|
||||||
} else {
|
} else {
|
||||||
rc, err := scs.server.GetStore().RemoteCluster().Get(task.remoteID)
|
rc, err := scs.server.GetStore().RemoteCluster().Get(task.remoteID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -79,7 +79,22 @@ func (scs *Service) syncForRemote(task syncTask, rc *model.RemoteCluster) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
scr, err := scs.server.GetStore().SharedChannel().GetRemoteByIds(task.channelID, rc.RemoteId)
|
scr, err := scs.server.GetStore().SharedChannel().GetRemoteByIds(task.channelID, rc.RemoteId)
|
||||||
if err != nil {
|
if isNotFoundError(err) && rc.IsOptionFlagSet(model.BitflagOptionAutoInvited) {
|
||||||
|
// if SharedChannelRemote not found and remote has autoinvite flag, create a scr for it, thus inviting the remote.
|
||||||
|
scr = &model.SharedChannelRemote{
|
||||||
|
Id: model.NewId(),
|
||||||
|
ChannelId: task.channelID,
|
||||||
|
CreatorId: rc.CreatorId,
|
||||||
|
IsInviteAccepted: true,
|
||||||
|
IsInviteConfirmed: true,
|
||||||
|
RemoteId: rc.RemoteId,
|
||||||
|
LastPostCreateAt: model.GetMillis(),
|
||||||
|
LastPostUpdateAt: model.GetMillis(),
|
||||||
|
}
|
||||||
|
if scr, err = scs.server.GetStore().SharedChannel().SaveRemote(scr); err != nil {
|
||||||
|
return fmt.Errorf("cannot auto-create shared channel remote (channel_id=%s, remote_id=%s): %w", task.channelID, rc.RemoteId, err)
|
||||||
|
}
|
||||||
|
} else if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -105,6 +105,10 @@ func mungEmail(remotename string, maxLen int) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func isConflictError(err error) (string, bool) {
|
func isConflictError(err error) (string, bool) {
|
||||||
|
if err == nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
var errConflict *store.ErrConflict
|
var errConflict *store.ErrConflict
|
||||||
if errors.As(err, &errConflict) {
|
if errors.As(err, &errConflict) {
|
||||||
return strings.ToLower(errConflict.Resource), true
|
return strings.ToLower(errConflict.Resource), true
|
||||||
@@ -117,3 +121,12 @@ func isConflictError(err error) (string, bool) {
|
|||||||
}
|
}
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isNotFoundError(err error) bool {
|
||||||
|
if err == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var errNotFound *store.ErrNotFound
|
||||||
|
return errors.As(err, &errNotFound)
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ const (
|
|||||||
RemoteNameMinLength = 1
|
RemoteNameMinLength = 1
|
||||||
RemoteNameMaxLength = 64
|
RemoteNameMaxLength = 64
|
||||||
|
|
||||||
BitflagOptionAutoShareDMs Bitmask = 1 << iota
|
BitflagOptionAutoShareDMs Bitmask = 1 << iota // Any new DM/GM is automatically shared
|
||||||
|
BitflagOptionAutoInvited // Remote is automatically invited to all shared channels
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -31,6 +32,18 @@ var (
|
|||||||
|
|
||||||
type Bitmask uint32
|
type Bitmask uint32
|
||||||
|
|
||||||
|
func (bm *Bitmask) IsBitSet(flag Bitmask) bool {
|
||||||
|
return *bm != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bm *Bitmask) SetBit(flag Bitmask) {
|
||||||
|
*bm |= flag
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bm *Bitmask) UnsetBit(flag Bitmask) {
|
||||||
|
*bm &= ^flag
|
||||||
|
}
|
||||||
|
|
||||||
type RemoteCluster struct {
|
type RemoteCluster struct {
|
||||||
RemoteId string `json:"remote_id"`
|
RemoteId string `json:"remote_id"`
|
||||||
RemoteTeamId string `json:"remote_team_id"`
|
RemoteTeamId string `json:"remote_team_id"`
|
||||||
@@ -105,15 +118,15 @@ func (rc *RemoteCluster) IsValid() *AppError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (rc *RemoteCluster) IsOptionFlagSet(flag Bitmask) bool {
|
func (rc *RemoteCluster) IsOptionFlagSet(flag Bitmask) bool {
|
||||||
return rc.Options&flag != 0
|
return rc.Options.IsBitSet(flag)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rc *RemoteCluster) SetOptionFlag(flag Bitmask) {
|
func (rc *RemoteCluster) SetOptionFlag(flag Bitmask) {
|
||||||
rc.Options |= flag
|
rc.Options.SetBit(flag)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rc *RemoteCluster) UnsetOptionFlag(flag Bitmask) {
|
func (rc *RemoteCluster) UnsetOptionFlag(flag Bitmask) {
|
||||||
rc.Options &= ^flag
|
rc.Options.UnsetBit(flag)
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsValidRemoteName(s string) bool {
|
func IsValidRemoteName(s string) bool {
|
||||||
@@ -340,4 +353,5 @@ type RemoteClusterQueryFilter struct {
|
|||||||
CreatorId string
|
CreatorId string
|
||||||
OnlyConfirmed bool
|
OnlyConfirmed bool
|
||||||
PluginID string
|
PluginID string
|
||||||
|
RequireOptions Bitmask
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -305,6 +305,7 @@ type RegisterPluginOpts struct {
|
|||||||
PluginID string // id of this plugin registering
|
PluginID string // id of this plugin registering
|
||||||
CreatorID string // id of the user/bot registering
|
CreatorID string // id of the user/bot registering
|
||||||
AutoShareDMs bool // when true, all DMs are automatically shared to this remote
|
AutoShareDMs bool // when true, all DMs are automatically shared to this remote
|
||||||
|
AutoInvited bool // when true, the plugin is automatically invited and sync'd with all shared channels.
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetOptionFlags returns a Bitmask of option flags as specified by the boolean options.
|
// GetOptionFlags returns a Bitmask of option flags as specified by the boolean options.
|
||||||
@@ -313,5 +314,8 @@ func (po RegisterPluginOpts) GetOptionFlags() Bitmask {
|
|||||||
if po.AutoShareDMs {
|
if po.AutoShareDMs {
|
||||||
flags |= BitflagOptionAutoShareDMs
|
flags |= BitflagOptionAutoShareDMs
|
||||||
}
|
}
|
||||||
|
if po.AutoInvited {
|
||||||
|
flags |= BitflagOptionAutoInvited
|
||||||
|
}
|
||||||
return flags
|
return flags
|
||||||
}
|
}
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user