Shared channels plugin APIs for MS Teams plugin (#25805)
New plugin APIs and hooks for accessing Shared Channels service via plugin. - RegisterPluginForSharedChannels(opts model.RegisterPluginOpts) (remoteID string, err error) - UnregisterPluginForSharedChannels(pluginID string) error - ShareChannel(sc *model.SharedChannel) (*model.SharedChannel, error) - UpdateSharedChannel(sc *model.SharedChannel) (*model.SharedChannel, error) - UnshareChannel(channelID string) (unshared bool, err error) - UpdateSharedChannelCursor(channelID, remoteID string, cusror model.GetPostsSinceForSyncCursor) error - SyncSharedChannel(channelID string) error - InviteRemoteToChannel(channelID string, remoteID string, userID string) error - UninviteRemoteFromChannel(channelID string, remoteID string) error Hooks - OnSharedChannelsSyncMsg(msg *model.SyncMsg, rc *model.RemoteCluster) (model.SyncResponse, error) - OnSharedChannelsPing(rc *model.RemoteCluster) bool
Этот коммит содержится в:
@@ -7643,6 +7643,24 @@ func (s *OpenTracingLayerRemoteClusterStore) GetAll(filter model.RemoteClusterQu
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerRemoteClusterStore) GetByPluginID(pluginID string) (*model.RemoteCluster, error) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "RemoteClusterStore.GetByPluginID")
|
||||
s.Root.Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
s.Root.Store.SetContext(origCtx)
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
result, err := s.RemoteClusterStore.GetByPluginID(pluginID)
|
||||
if err != nil {
|
||||
span.LogFields(spanlog.Error(err))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerRemoteClusterStore) Save(rc *model.RemoteCluster) (*model.RemoteCluster, error) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "RemoteClusterStore.Save")
|
||||
|
||||
@@ -8684,6 +8684,27 @@ func (s *RetryLayerRemoteClusterStore) GetAll(filter model.RemoteClusterQueryFil
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerRemoteClusterStore) GetByPluginID(pluginID string) (*model.RemoteCluster, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.RemoteClusterStore.GetByPluginID(pluginID)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return result, err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return result, err
|
||||
}
|
||||
timepkg.Sleep(100 * timepkg.Millisecond)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerRemoteClusterStore) Save(rc *model.RemoteCluster) (*model.RemoteCluster, error) {
|
||||
|
||||
tries := 0
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
@@ -50,6 +51,19 @@ func (s sqlRemoteClusterStore) Save(remoteCluster *model.RemoteCluster) (*model.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// check for pluginID collisions - on collision treat as idempotent
|
||||
if remoteCluster.PluginID != "" {
|
||||
rc, err := s.GetByPluginID(remoteCluster.PluginID)
|
||||
if err == nil {
|
||||
// if this plugin id already exists, just return it
|
||||
return rc, nil
|
||||
}
|
||||
if !errors.Is(err, sql.ErrNoRows) {
|
||||
// anything other than NotFound is unexpected
|
||||
return nil, errors.Wrapf(err, "failed to lookup RemoteCluster by pluginID %s", remoteCluster.PluginID)
|
||||
}
|
||||
}
|
||||
|
||||
query := `INSERT INTO RemoteClusters
|
||||
(RemoteId, RemoteTeamId, Name, DisplayName, SiteURL, CreateAt,
|
||||
LastPingAt, Token, RemoteToken, Topics, CreatorId, PluginID, Options)
|
||||
@@ -69,6 +83,7 @@ func (s sqlRemoteClusterStore) Update(remoteCluster *model.RemoteCluster) (*mode
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// not all fields can be updated.
|
||||
query := `UPDATE RemoteClusters
|
||||
SET Token = :Token,
|
||||
RemoteTeamId = :RemoteTeamId,
|
||||
@@ -129,6 +144,24 @@ func (s sqlRemoteClusterStore) Get(remoteId string) (*model.RemoteCluster, error
|
||||
return &rc, nil
|
||||
}
|
||||
|
||||
func (s sqlRemoteClusterStore) GetByPluginID(pluginID string) (*model.RemoteCluster, error) {
|
||||
query := s.getQueryBuilder().
|
||||
Select(remoteClusterFields("")...).
|
||||
From("RemoteClusters").
|
||||
Where(sq.Eq{"PluginID": pluginID})
|
||||
|
||||
queryString, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "remote_cluster_get_by_pluginid_tosql")
|
||||
}
|
||||
|
||||
var rc model.RemoteCluster
|
||||
if err := s.GetReplicaX().Get(&rc, queryString, args...); err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to find RemoteCluster by plugin_id")
|
||||
}
|
||||
return &rc, nil
|
||||
}
|
||||
|
||||
func (s sqlRemoteClusterStore) GetAll(filter model.RemoteClusterQueryFilter) ([]*model.RemoteCluster, error) {
|
||||
query := s.getQueryBuilder().
|
||||
Select(remoteClusterFields("rc")...).
|
||||
|
||||
@@ -543,6 +543,7 @@ type RemoteClusterStore interface {
|
||||
Update(rc *model.RemoteCluster) (*model.RemoteCluster, error)
|
||||
Delete(remoteClusterId string) (bool, error)
|
||||
Get(remoteClusterId string) (*model.RemoteCluster, error)
|
||||
GetByPluginID(pluginID string) (*model.RemoteCluster, error)
|
||||
GetAll(filter model.RemoteClusterQueryFilter) ([]*model.RemoteCluster, error)
|
||||
UpdateTopics(remoteClusterId string, topics string) (*model.RemoteCluster, error)
|
||||
SetLastPingAt(remoteClusterId string) error
|
||||
|
||||
@@ -90,6 +90,32 @@ func (_m *RemoteClusterStore) GetAll(filter model.RemoteClusterQueryFilter) ([]*
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetByPluginID provides a mock function with given fields: pluginID
|
||||
func (_m *RemoteClusterStore) GetByPluginID(pluginID string) (*model.RemoteCluster, error) {
|
||||
ret := _m.Called(pluginID)
|
||||
|
||||
var r0 *model.RemoteCluster
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(string) (*model.RemoteCluster, error)); ok {
|
||||
return rf(pluginID)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string) *model.RemoteCluster); ok {
|
||||
r0 = rf(pluginID)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.RemoteCluster)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string) error); ok {
|
||||
r1 = rf(pluginID)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Save provides a mock function with given fields: rc
|
||||
func (_m *RemoteClusterStore) Save(rc *model.RemoteCluster) (*model.RemoteCluster, error) {
|
||||
ret := _m.Called(rc)
|
||||
|
||||
@@ -15,10 +15,6 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
testPluginID = "com.sample.blap"
|
||||
)
|
||||
|
||||
func TestRemoteClusterStore(t *testing.T, rctx request.CTX, ss store.Store) {
|
||||
t.Run("RemoteClusterGetAllInChannel", func(t *testing.T) { testRemoteClusterGetAllInChannel(t, rctx, ss) })
|
||||
t.Run("RemoteClusterGetAllNotInChannel", func(t *testing.T) { testRemoteClusterGetAllNotInChannel(t, rctx, ss) })
|
||||
@@ -30,12 +26,17 @@ func TestRemoteClusterStore(t *testing.T, rctx request.CTX, ss store.Store) {
|
||||
t.Run("RemoteClusterUpdateTopics", func(t *testing.T) { testRemoteClusterUpdateTopics(t, rctx, ss) })
|
||||
}
|
||||
|
||||
func makeSiteURL() string {
|
||||
return "www.example.com/" + model.NewId()
|
||||
}
|
||||
|
||||
func testRemoteClusterSave(t *testing.T, rctx request.CTX, ss store.Store) {
|
||||
t.Run("Save", func(t *testing.T) {
|
||||
rc := &model.RemoteCluster{
|
||||
Name: "some_remote",
|
||||
SiteURL: "somewhere.com",
|
||||
SiteURL: makeSiteURL(),
|
||||
CreatorId: model.NewId(),
|
||||
PluginID: model.NewId(),
|
||||
}
|
||||
|
||||
rcSaved, err := ss.RemoteCluster().Save(rc)
|
||||
@@ -44,12 +45,13 @@ func testRemoteClusterSave(t *testing.T, rctx request.CTX, ss store.Store) {
|
||||
require.Equal(t, rc.SiteURL, rcSaved.SiteURL)
|
||||
require.Greater(t, rc.CreateAt, int64(0))
|
||||
require.Equal(t, rc.LastPingAt, int64(0))
|
||||
require.Equal(t, rc.PluginID, rcSaved.PluginID)
|
||||
require.Equal(t, rc.Options, model.Bitmask(0))
|
||||
})
|
||||
|
||||
t.Run("Save missing display name", func(t *testing.T) {
|
||||
rc := &model.RemoteCluster{
|
||||
SiteURL: "somewhere.com",
|
||||
SiteURL: makeSiteURL(),
|
||||
CreatorId: model.NewId(),
|
||||
}
|
||||
_, err := ss.RemoteCluster().Save(rc)
|
||||
@@ -59,34 +61,84 @@ func testRemoteClusterSave(t *testing.T, rctx request.CTX, ss store.Store) {
|
||||
t.Run("Save missing creator id", func(t *testing.T) {
|
||||
rc := &model.RemoteCluster{
|
||||
Name: "some_remote_2",
|
||||
SiteURL: "somewhere.com",
|
||||
SiteURL: makeSiteURL(),
|
||||
}
|
||||
_, err := ss.RemoteCluster().Save(rc)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("Save pluginID collision", func(t *testing.T) {
|
||||
const testPluginID = "com.example.collision"
|
||||
|
||||
rc := &model.RemoteCluster{
|
||||
Name: "some_remote",
|
||||
SiteURL: makeSiteURL(),
|
||||
CreatorId: model.NewId(),
|
||||
PluginID: testPluginID,
|
||||
}
|
||||
_, err := ss.RemoteCluster().Save(rc)
|
||||
require.NoError(t, err)
|
||||
|
||||
rc2 := &model.RemoteCluster{
|
||||
Name: "another_remote",
|
||||
SiteURL: makeSiteURL(),
|
||||
CreatorId: model.NewId(),
|
||||
PluginID: testPluginID,
|
||||
}
|
||||
|
||||
rcSaved, err := ss.RemoteCluster().Save(rc2)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, rcSaved)
|
||||
|
||||
// original remotecluster should be returned
|
||||
require.Equal(t, rc.Name, rcSaved.Name)
|
||||
require.Equal(t, rc.SiteURL, rcSaved.SiteURL)
|
||||
require.Greater(t, rc.CreateAt, int64(0))
|
||||
require.Equal(t, rc.PluginID, rcSaved.PluginID)
|
||||
})
|
||||
|
||||
t.Run("Save multiple with blank pluginID", func(t *testing.T) {
|
||||
rc := &model.RemoteCluster{
|
||||
Name: model.NewId(),
|
||||
SiteURL: makeSiteURL(),
|
||||
CreatorId: model.NewId(),
|
||||
}
|
||||
_, err := ss.RemoteCluster().Save(rc)
|
||||
require.NoError(t, err)
|
||||
|
||||
rc2 := &model.RemoteCluster{
|
||||
Name: model.NewId(),
|
||||
SiteURL: makeSiteURL(),
|
||||
CreatorId: model.NewId(),
|
||||
}
|
||||
_, err = ss.RemoteCluster().Save(rc2)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("Save for plugin with options", func(t *testing.T) {
|
||||
rc := &model.RemoteCluster{
|
||||
Name: "plugin_remote",
|
||||
SiteURL: "plugin.example.com",
|
||||
SiteURL: makeSiteURL(),
|
||||
CreatorId: model.NewId(),
|
||||
PluginID: testPluginID,
|
||||
PluginID: model.NewId(),
|
||||
Options: model.BitflagOptionAutoShareDMs,
|
||||
}
|
||||
|
||||
rcSaved, err := ss.RemoteCluster().Save(rc)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, testPluginID, rcSaved.PluginID)
|
||||
require.Equal(t, rc.PluginID, rcSaved.PluginID)
|
||||
require.Equal(t, model.BitflagOptionAutoShareDMs, rcSaved.Options)
|
||||
require.True(t, rcSaved.IsOptionFlagSet(model.BitflagOptionAutoShareDMs))
|
||||
|
||||
rc.Name = "plugin_remote_2"
|
||||
rc.SiteURL = makeSiteURL()
|
||||
rc.PluginID = model.NewId()
|
||||
rc.SiteURL = "plugin2.example.com"
|
||||
rc.UnsetOptionFlag(model.BitflagOptionAutoShareDMs)
|
||||
|
||||
rcSaved, err = ss.RemoteCluster().Save(rc)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, testPluginID, rcSaved.PluginID)
|
||||
require.Equal(t, rc.PluginID, rcSaved.PluginID)
|
||||
require.Equal(t, model.Bitmask(0), rcSaved.Options)
|
||||
require.False(t, rcSaved.IsOptionFlagSet(model.BitflagOptionAutoShareDMs))
|
||||
})
|
||||
@@ -96,7 +148,7 @@ func testRemoteClusterDelete(t *testing.T, rctx request.CTX, ss store.Store) {
|
||||
t.Run("Delete", func(t *testing.T) {
|
||||
rc := &model.RemoteCluster{
|
||||
Name: "shortlived_remote",
|
||||
SiteURL: "nowhere.com",
|
||||
SiteURL: makeSiteURL(),
|
||||
CreatorId: model.NewId(),
|
||||
}
|
||||
rcSaved, err := ss.RemoteCluster().Save(rc)
|
||||
@@ -118,9 +170,9 @@ func testRemoteClusterGet(t *testing.T, rctx request.CTX, ss store.Store) {
|
||||
t.Run("Get", func(t *testing.T) {
|
||||
rc := &model.RemoteCluster{
|
||||
Name: "shortlived_remote_2",
|
||||
SiteURL: "nowhere.com",
|
||||
SiteURL: makeSiteURL(),
|
||||
CreatorId: model.NewId(),
|
||||
PluginID: testPluginID,
|
||||
PluginID: model.NewId(),
|
||||
}
|
||||
rc.SetOptionFlag(model.BitflagOptionAutoShareDMs)
|
||||
rcSaved, err := ss.RemoteCluster().Save(rc)
|
||||
@@ -129,7 +181,7 @@ func testRemoteClusterGet(t *testing.T, rctx request.CTX, ss store.Store) {
|
||||
rcGet, err := ss.RemoteCluster().Get(rcSaved.RemoteId)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, rcSaved.RemoteId, rcGet.RemoteId)
|
||||
require.Equal(t, testPluginID, rcGet.PluginID)
|
||||
require.Equal(t, rcSaved.PluginID, rcGet.PluginID)
|
||||
require.True(t, rcGet.IsOptionFlagSet(model.BitflagOptionAutoShareDMs))
|
||||
})
|
||||
|
||||
@@ -147,10 +199,10 @@ func testRemoteClusterGetAll(t *testing.T, rctx request.CTX, ss store.Store) {
|
||||
pingLongAgo := model.GetMillis() - (model.RemoteOfflineAfterMillis * 3)
|
||||
|
||||
data := []*model.RemoteCluster{
|
||||
{Name: "offline_remote", CreatorId: userId, SiteURL: "somewhere.com", LastPingAt: pingLongAgo, Topics: " shared incident "},
|
||||
{Name: "some_online_remote", CreatorId: userId, SiteURL: "nowhere.com", LastPingAt: now, Topics: " shared incident "},
|
||||
{Name: "another_online_remote", CreatorId: model.NewId(), SiteURL: "underwhere.com", LastPingAt: now, Topics: ""},
|
||||
{Name: "another_offline_remote", CreatorId: model.NewId(), SiteURL: "knowhere.com", LastPingAt: pingLongAgo, Topics: " shared "},
|
||||
{Name: "offline_remote", CreatorId: userId, SiteURL: makeSiteURL(), LastPingAt: pingLongAgo, Topics: " shared incident "},
|
||||
{Name: "some_online_remote", CreatorId: userId, SiteURL: makeSiteURL(), LastPingAt: now, Topics: " shared incident "},
|
||||
{Name: "another_online_remote", CreatorId: model.NewId(), SiteURL: makeSiteURL(), LastPingAt: now, Topics: ""},
|
||||
{Name: "another_offline_remote", CreatorId: model.NewId(), SiteURL: makeSiteURL(), LastPingAt: pingLongAgo, Topics: " shared "},
|
||||
{Name: "brand_new_offline_remote", CreatorId: userId, SiteURL: "", LastPingAt: 0, Topics: " bogus shared stuff "},
|
||||
}
|
||||
|
||||
@@ -245,6 +297,11 @@ func testRemoteClusterGetAll(t *testing.T, rctx request.CTX, ss store.Store) {
|
||||
}
|
||||
|
||||
func testRemoteClusterGetAllInChannel(t *testing.T, rctx request.CTX, ss store.Store) {
|
||||
const (
|
||||
testPluginID_1 = "com.sample.blap"
|
||||
testPluginID_2 = "com.sample.bloop"
|
||||
)
|
||||
|
||||
require.NoError(t, clearRemoteClusters(ss))
|
||||
now := model.GetMillis()
|
||||
|
||||
@@ -272,8 +329,8 @@ func testRemoteClusterGetAllInChannel(t *testing.T, rctx request.CTX, ss store.S
|
||||
|
||||
// Create some remote clusters
|
||||
rcData := []*model.RemoteCluster{
|
||||
{Name: "AAAA_Inc", CreatorId: userId, SiteURL: "aaaa.com", RemoteId: model.NewId(), LastPingAt: now, PluginID: testPluginID},
|
||||
{Name: "BBBB_Inc", CreatorId: userId, SiteURL: "bbbb.com", RemoteId: model.NewId(), LastPingAt: 0, PluginID: testPluginID},
|
||||
{Name: "AAAA_Inc", CreatorId: userId, SiteURL: "aaaa.com", RemoteId: model.NewId(), LastPingAt: now, PluginID: testPluginID_1},
|
||||
{Name: "BBBB_Inc", CreatorId: userId, SiteURL: "bbbb.com", RemoteId: model.NewId(), LastPingAt: 0, PluginID: testPluginID_2},
|
||||
{Name: "CCCC_Inc", CreatorId: userId, SiteURL: "cccc.com", RemoteId: model.NewId(), LastPingAt: now},
|
||||
{Name: "DDDD_Inc", CreatorId: userId, SiteURL: "dddd.com", RemoteId: model.NewId(), LastPingAt: now},
|
||||
{Name: "EEEE_Inc", CreatorId: userId, SiteURL: "eeee.com", RemoteId: model.NewId(), LastPingAt: 0},
|
||||
@@ -305,8 +362,8 @@ func testRemoteClusterGetAllInChannel(t *testing.T, rctx request.CTX, ss store.S
|
||||
require.Len(t, list, 2, "channel 1 should have 2 remote clusters")
|
||||
ids := getIds(list)
|
||||
require.ElementsMatch(t, []string{rcData[0].RemoteId, rcData[1].RemoteId}, ids)
|
||||
require.Equal(t, testPluginID, rcData[0].PluginID)
|
||||
require.Equal(t, testPluginID, rcData[1].PluginID)
|
||||
require.Equal(t, testPluginID_1, rcData[0].PluginID)
|
||||
require.Equal(t, testPluginID_2, rcData[1].PluginID)
|
||||
})
|
||||
|
||||
t.Run("Channel 1 online only", func(t *testing.T) {
|
||||
|
||||
@@ -6905,6 +6905,22 @@ func (s *TimerLayerRemoteClusterStore) GetAll(filter model.RemoteClusterQueryFil
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerRemoteClusterStore) GetByPluginID(pluginID string) (*model.RemoteCluster, error) {
|
||||
start := time.Now()
|
||||
|
||||
result, err := s.RemoteClusterStore.GetByPluginID(pluginID)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("RemoteClusterStore.GetByPluginID", success, elapsed)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerRemoteClusterStore) Save(rc *model.RemoteCluster) (*model.RemoteCluster, error) {
|
||||
start := time.Now()
|
||||
|
||||
|
||||
Ссылка в новой задаче
Block a user