Merge pull request #21883 from mattermost/mpa-playbooks

Add services and functions for playbooks to work in mpa mode
Этот коммит содержится в:
Giorgi Bochorishvili
2023-01-24 14:35:13 +04:00
коммит произвёл GitHub
родитель 45a5d6abe1 a16192045a
Коммит 7f5ac299ce
17 изменённых файлов: 257 добавлений и 10 удалений

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

@@ -93,6 +93,22 @@ else
BUILD_BOARDS = false
endif
# Playbooks
BUILD_PLAYBOOKS_DIR ?= ../mattermost-plugin-playbooks
BUILD_PLAYBOOKS ?= false
BUILD_HASH_PLAYBOOKS = none
ifneq ($(wildcard $(BUILD_PLAYBOOKS_DIR)/.),)
ifeq ($(BUILD_PLAYBOOKS),true)
BUILD_PLAYBOOKS = true
BUILD_HASH_PLAYBOOKS = $(shell cd $(BUILD_PLAYBOOKS_DIR) && git rev-parse HEAD)
else
BUILD_PLAYBOOKS = false
endif
else
BUILD_PLAYBOOKS = false
endif
# We need current user's UID for `run-haserver` so docker compose does not run server
# as root and mess up file permissions for devs. When running like this HOME will be blank
# and docker will add '/', so we need to set the go-build cache location or we'll get
@@ -116,6 +132,7 @@ LDFLAGS += -X "github.com/mattermost/mattermost-server/v6/model.BuildHashEnterpr
LDFLAGS += -X "github.com/mattermost/mattermost-server/v6/model.BuildEnterpriseReady=$(BUILD_ENTERPRISE_READY)"
LDFLAGS += -X "github.com/mattermost/mattermost-server/v6/model.BuildHashBoards=$(BUILD_HASH_BOARDS)"
LDFLAGS += -X "github.com/mattermost/mattermost-server/v6/model.BuildBoards=$(BUILD_BOARDS)"
LDFLAGS += -X "github.com/mattermost/mattermost-server/v6/model.BuildHashPlaybooks=$(BUILD_HASH_PLAYBOOKS)"
GO_MAJOR_VERSION = $(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f1)
GO_MINOR_VERSION = $(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f2)
@@ -198,6 +215,12 @@ else
IGNORE:=$(shell rm -f imports/boards_imports.go)
endif
ifeq ($(BUILD_PLAYBOOKS),true)
IGNORE:=$(shell cp $(BUILD_PLAYBOOKS_DIR)/product/imports/playbooks_imports.go imports/)
else
IGNORE:=$(shell rm -f imports/playbooks_imports.go)
endif
all: run ## Alias for 'run'.
-include config.override.mk
@@ -421,6 +444,7 @@ endif
setup-go-work: export BUILD_ENTERPRISE_READY := $(BUILD_ENTERPRISE_READY)
setup-go-work: export BUILD_BOARDS := $(BUILD_BOARDS)
setup-go-work: export BUILD_PLAYBOOKS := $(BUILD_PLAYBOOKS)
setup-go-work: ## Sets up your go.work file
./scripts/setup_go_work.sh $(IGNORE_GO_WORK_IF_EXISTS)

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

@@ -960,6 +960,7 @@ type AppIface interface {
RegenOutgoingWebhookToken(hook *model.OutgoingWebhook) (*model.OutgoingWebhook, *model.AppError)
RegenerateOAuthAppSecret(app *model.OAuthApp) (*model.OAuthApp, *model.AppError)
RegenerateTeamInviteId(teamID string) (*model.Team, *model.AppError)
RegisterCollectionAndTopic(pluginID, collectionType, topicType string) error
RegisterPluginCommand(pluginID string, command *model.Command) error
ReloadConfig() error
RemoveAllDeactivatedMembersFromChannel(c request.CTX, channel *model.Channel) *model.AppError

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

@@ -27,30 +27,82 @@ import (
// channelsWrapper provides an implementation of `product.ChannelService` to be used by products.
type channelsWrapper struct {
srv *Server
app *App
}
func (s *channelsWrapper) GetDirectChannel(userID1, userID2 string) (*model.Channel, *model.AppError) {
return s.srv.getDirectChannel(request.EmptyContext(s.srv.Log()), userID1, userID2)
return s.app.getDirectChannel(request.EmptyContext(s.app.Log()), userID1, userID2)
}
// GetChannelByID gets a Channel by its ID.
func (s *channelsWrapper) GetChannelByID(channelID string) (*model.Channel, *model.AppError) {
return s.srv.getChannel(request.EmptyContext(s.srv.Log()), channelID)
return s.app.GetChannel(request.EmptyContext(s.app.Log()), channelID)
}
// GetChannelMember gets a channel member by userID.
func (s *channelsWrapper) GetChannelMember(channelID string, userID string) (*model.ChannelMember, *model.AppError) {
return s.srv.getChannelMember(request.EmptyContext(s.srv.Log()), channelID, userID)
return s.app.GetChannelMember(request.EmptyContext(s.app.Log()), channelID, userID)
}
func (s *channelsWrapper) GetChannelsForTeamForUser(teamID string, userID string, opts *model.ChannelSearchOpts) (model.ChannelList, *model.AppError) {
return s.srv.getChannelsForTeamForUser(request.EmptyContext(s.srv.Log()), teamID, userID, opts)
return s.app.GetChannelsForTeamForUser(request.EmptyContext(s.app.Log()), teamID, userID, opts)
}
func (s *channelsWrapper) GetChannelSidebarCategories(userID, teamID string) (*model.OrderedSidebarCategories, *model.AppError) {
return s.app.GetSidebarCategoriesForTeamForUser(request.EmptyContext(s.app.Log()), userID, teamID)
}
func (s *channelsWrapper) GetChannelMembers(channelID string, page, perPage int) (model.ChannelMembers, *model.AppError) {
return s.app.GetChannelMembersPage(request.EmptyContext(s.app.Log()), channelID, page, perPage)
}
func (s *channelsWrapper) CreateChannelSidebarCategory(userID, teamID string, newCategory *model.SidebarCategoryWithChannels) (*model.SidebarCategoryWithChannels, *model.AppError) {
return s.app.CreateSidebarCategory(request.EmptyContext(s.app.Log()), userID, teamID, newCategory)
}
func (s *channelsWrapper) UpdateChannelSidebarCategories(userID, teamID string, categories []*model.SidebarCategoryWithChannels) ([]*model.SidebarCategoryWithChannels, *model.AppError) {
return s.app.UpdateSidebarCategories(request.EmptyContext(s.app.Log()), userID, teamID, categories)
}
func (s *channelsWrapper) CreateChannel(channel *model.Channel) (*model.Channel, *model.AppError) {
return s.app.CreateChannel(request.EmptyContext(s.app.Log()), channel, false)
}
func (s *channelsWrapper) AddUserToChannel(channelID, userID, asUserID string) (*model.ChannelMember, *model.AppError) {
ctx := request.EmptyContext(s.app.Log())
channel, err := s.app.GetChannel(ctx, channelID)
if err != nil {
return nil, err
}
return s.app.AddChannelMember(ctx, userID, channel, ChannelMemberOpts{
UserRequestorID: asUserID,
})
}
func (s *channelsWrapper) UpdateChannelMemberRoles(channelID, userID, newRoles string) (*model.ChannelMember, *model.AppError) {
return s.app.UpdateChannelMemberRoles(request.EmptyContext(s.app.Log()), channelID, userID, newRoles)
}
func (s *channelsWrapper) DeleteChannelMember(channelID, userID string) *model.AppError {
return s.app.LeaveChannel(request.EmptyContext(s.app.Log()), channelID, userID)
}
func (s *channelsWrapper) AddChannelMember(channelID, userID string) (*model.ChannelMember, *model.AppError) {
channel, err := s.GetChannelByID(channelID)
if err != nil {
return nil, err
}
return s.app.AddChannelMember(request.EmptyContext(s.app.Log()), userID, channel, ChannelMemberOpts{
// For now, don't allow overriding these via the plugin API.
UserRequestorID: "",
PostRootID: "",
})
}
func (s *channelsWrapper) GetDirectChannelOrCreate(userID1, userID2 string) (*model.Channel, *model.AppError) {
return s.app.GetOrCreateDirectChannel(request.EmptyContext(s.srv.Log()), userID1, userID2)
return s.app.GetOrCreateDirectChannel(request.EmptyContext(s.app.Log()), userID1, userID2)
}
// Ensure the wrapper implements the product service.

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

@@ -214,7 +214,6 @@ func NewChannels(services map[product.ServiceKey]any) (*Channels, error) {
pluginsRoute.HandleFunc("/{anything:.*}", ch.ServePluginRequest)
services[product.ChannelKey] = &channelsWrapper{
srv: s,
app: &App{ch: ch},
}
@@ -244,6 +243,10 @@ func NewChannels(services map[product.ServiceKey]any) (*Channels, error) {
app: &App{ch: ch},
}
services[product.CommandKey] = &App{ch: ch}
services[product.ThreadsKey] = &App{ch: ch}
return ch, nil
}

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

@@ -10,7 +10,7 @@ import (
"github.com/mattermost/mattermost-server/v6/shared/mlog"
)
func (a *App) registerCollectionAndTopic(pluginID, collectionType, topicType string) error {
func (a *App) RegisterCollectionAndTopic(pluginID, collectionType, topicType string) error {
// we have a race condition due to multiple plugins calling this method
a.ch.collectionAndTopicTypesMut.Lock()
defer a.ch.collectionAndTopicTypesMut.Unlock()

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

@@ -13682,6 +13682,28 @@ func (a *OpenTracingAppLayer) RegenerateTeamInviteId(teamID string) (*model.Team
return resultVar0, resultVar1
}
func (a *OpenTracingAppLayer) RegisterCollectionAndTopic(pluginID string, collectionType string, topicType string) error {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.RegisterCollectionAndTopic")
a.ctx = newCtx
a.app.Srv().Store().SetContext(newCtx)
defer func() {
a.app.Srv().Store().SetContext(origCtx)
a.ctx = origCtx
}()
defer span.Finish()
resultVar0 := a.app.RegisterCollectionAndTopic(pluginID, collectionType, topicType)
if resultVar0 != nil {
span.LogFields(spanlog.Error(resultVar0))
ext.Error.Set(span, true)
}
return resultVar0
}
func (a *OpenTracingAppLayer) RegisterPluginCommand(pluginID string, command *model.Command) error {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.RegisterPluginCommand")

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

@@ -41,6 +41,10 @@ func (s *permissionsServiceWrapper) HasPermissionToChannel(askingUserID string,
return s.app.HasPermissionToChannel(request.EmptyContext(s.app.Log()), askingUserID, channelID, permission)
}
func (s *permissionsServiceWrapper) RolesGrantPermission(roleNames []string, permissionId string) bool {
return s.app.RolesGrantPermission(roleNames, permissionId)
}
func (a *App) ResetPermissionsSystem() *model.AppError {
// Reset all Teams to not have a scheme.
if err := a.Srv().Store().Team().ResetAllTeamSchemes(); err != nil {

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

@@ -1235,7 +1235,7 @@ func (api *PluginAPI) GetCloudLimits() (*model.ProductLimits, error) {
// RegisterCollectionAndTopic informs the server that this plugin handles
// the given collection and topic types.
func (api *PluginAPI) RegisterCollectionAndTopic(collectionType, topicType string) error {
return api.app.registerCollectionAndTopic(api.id, collectionType, topicType)
return api.app.RegisterCollectionAndTopic(api.id, collectionType, topicType)
}
func (api *PluginAPI) CreateUploadSession(us *model.UploadSession) (*model.UploadSession, error) {

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

@@ -46,6 +46,26 @@ func (s *postServiceWrapper) CreatePost(ctx *request.Context, post *model.Post)
return s.app.CreatePostMissingChannel(ctx, post, true)
}
func (s *postServiceWrapper) GetPostsByIds(postIDs []string) ([]*model.Post, int64, *model.AppError) {
return s.app.GetPostsByIds(postIDs)
}
func (s *postServiceWrapper) SendEphemeralPost(ctx *request.Context, userID string, post *model.Post) *model.Post {
return s.app.SendEphemeralPost(ctx, userID, post)
}
func (s *postServiceWrapper) GetPost(postID string) (*model.Post, *model.AppError) {
return s.app.GetSinglePost(postID, false)
}
func (s *postServiceWrapper) DeletePost(ctx *request.Context, postID, productID string) (*model.Post, *model.AppError) {
return s.app.DeletePost(ctx, postID, productID)
}
func (s *postServiceWrapper) UpdatePost(ctx *request.Context, post *model.Post, safeUpdate bool) (*model.Post, *model.AppError) {
return s.app.UpdatePost(ctx, post, false)
}
func (a *App) CreatePostAsUser(c request.CTX, post *model.Post, currentSessionId string, setOnline bool) (*model.Post, *model.AppError) {
// Check that channel has not been deleted
channel, errCh := a.Srv().Store().Channel().Get(post.ChannelId, true)

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

@@ -241,7 +241,6 @@ func NewServer(options ...Option) (*Server, error) {
app := New(ServerConnector(s.Channels()))
serviceMap := map[product.ServiceKey]any{
ServerKey: s,
product.ChannelKey: &channelsWrapper{srv: s, app: app},
product.ConfigKey: s.platform,
product.LicenseKey: s.licenseWrapper,
product.FilestoreKey: s.platform.FileBackend(),
@@ -253,6 +252,8 @@ func NewServer(options ...Option) (*Server, error) {
product.KVStoreKey: s.platform,
product.StoreKey: store.NewStoreServiceAdapter(s.Store()),
product.SystemKey: &systemServiceAdapter{server: s},
product.SessionKey: app,
product.FrontendKey: app,
}
// Step 4: Initialize products.
@@ -262,6 +263,13 @@ func NewServer(options ...Option) (*Server, error) {
return nil, errors.Wrap(err, "failed to initialize products")
}
// After channel is initialized set it to the App object
channelsWrapper, ok := serviceMap[product.ChannelKey].(*channelsWrapper)
if !ok {
return nil, errors.Wrap(err, "channels product is not initialized")
}
app.ch = channelsWrapper.app.ch
// It is important to initialize the hub only after the global logger is set
// to avoid race conditions while logging from inside the hub.
// Step 5: Start hub in platform which the hub depends on s.Channels() (step 4)

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

@@ -44,6 +44,19 @@ func (w *teamServiceWrapper) CreateMember(ctx *request.Context, teamID, userID s
return w.app.AddTeamMember(ctx, teamID, userID)
}
func (w *teamServiceWrapper) GetGroup(groupID string) (*model.Group, *model.AppError) {
return w.app.GetGroup(groupID, nil, nil)
}
func (w *teamServiceWrapper) GetTeam(teamID string) (*model.Team, *model.AppError) {
return w.app.GetTeam(teamID)
}
func (w *teamServiceWrapper) GetGroupMemberUsers(groupID string, page, perPage int) ([]*model.User, *model.AppError) {
users, _, err := w.app.GetGroupMemberUsersPage(groupID, page, perPage, nil)
return users, err
}
// Ensure the wrapper implements the product service.
var _ product.TeamService = (*teamServiceWrapper)(nil)

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

@@ -225,6 +225,7 @@ func GenerateLimitedClientConfig(c *model.Config, telemetryID string, license *m
props["BuildEnterpriseReady"] = model.BuildEnterpriseReady
props["BuildHashBoards"] = model.BuildHashBoards
props["BuildBoards"] = model.BuildBoards
props["BuildHashPlaybooks"] = model.BuildHashPlaybooks
props["EnableBotAccountCreation"] = strconv.FormatBool(*c.ServiceSettings.EnableBotAccountCreation)
props["EnableFile"] = strconv.FormatBool(*c.LogSettings.EnableFile)

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

@@ -118,6 +118,7 @@ var BuildHashEnterprise string
var BuildEnterpriseReady string
var BuildHashBoards string
var BuildBoards string
var BuildHashPlaybooks string
var versionsWithoutHotFixes []string
func init() {

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

@@ -33,6 +33,11 @@ type RouterService interface {
// The service shall be registered via app.PostKey service key.
type PostService interface {
CreatePost(context *request.Context, post *model.Post) (*model.Post, *model.AppError)
GetPostsByIds(postIDs []string) ([]*model.Post, int64, *model.AppError)
SendEphemeralPost(ctx *request.Context, userID string, post *model.Post) *model.Post
GetPost(postID string) (*model.Post, *model.AppError)
DeletePost(ctx *request.Context, postID, productID string) (*model.Post, *model.AppError)
UpdatePost(c *request.Context, post *model.Post, safeUpdate bool) (*model.Post, *model.AppError)
}
// PermissionService provides permissions related utilities. For now, the service implementation
@@ -44,6 +49,7 @@ type PermissionService interface {
HasPermissionTo(userID string, permission *model.Permission) bool
HasPermissionToTeam(userID, teamID string, permission *model.Permission) bool
HasPermissionToChannel(askingUserID string, channelID string, permission *model.Permission) bool
RolesGrantPermission(roleNames []string, permissionID string) bool
}
// ClusterService enables to publish cluster events. In addition to that, It's being used for
@@ -66,6 +72,15 @@ type ChannelService interface {
GetChannelByID(channelID string) (*model.Channel, *model.AppError)
GetChannelMember(channelID string, userID string) (*model.ChannelMember, *model.AppError)
GetChannelsForTeamForUser(teamID string, userID string, opts *model.ChannelSearchOpts) (model.ChannelList, *model.AppError)
GetChannelSidebarCategories(userID, teamID string) (*model.OrderedSidebarCategories, *model.AppError)
GetChannelMembers(channelID string, page, perPage int) (model.ChannelMembers, *model.AppError)
CreateChannelSidebarCategory(userID, teamID string, newCategory *model.SidebarCategoryWithChannels) (*model.SidebarCategoryWithChannels, *model.AppError)
UpdateChannelSidebarCategories(userID, teamID string, categories []*model.SidebarCategoryWithChannels) ([]*model.SidebarCategoryWithChannels, *model.AppError)
CreateChannel(channel *model.Channel) (*model.Channel, *model.AppError)
AddUserToChannel(channelID, userID, asUserID string) (*model.ChannelMember, *model.AppError)
UpdateChannelMemberRoles(channelID, userID, newRoles string) (*model.ChannelMember, *model.AppError)
DeleteChannelMember(channelID, userID string) *model.AppError
AddChannelMember(channelID, userID string) (*model.ChannelMember, *model.AppError)
}
// LicenseService provides license related utilities.
@@ -96,6 +111,9 @@ type UserService interface {
type TeamService interface {
GetMember(teamID, userID string) (*model.TeamMember, *model.AppError)
CreateMember(ctx *request.Context, teamID, userID string) (*model.TeamMember, *model.AppError)
GetGroup(groupId string) (*model.Group, *model.AppError)
GetTeam(teamID string) (*model.Team, *model.AppError)
GetGroupMemberUsers(groupID string, page, perPage int) ([]*model.User, *model.AppError)
}
// BotService is just a copy implementation of mattermost-plugin-api EnsureBot method.
@@ -161,6 +179,9 @@ type CloudService interface {
// The service shall be registered via app.KVStoreKey service key.
type KVStoreService interface {
SetPluginKeyWithOptions(pluginID string, key string, value []byte, options model.PluginKVSetOptions) (bool, *model.AppError)
KVGet(productID, key string) ([]byte, *model.AppError)
KVDelete(productID, key string) *model.AppError
KVList(productID string, page, perPage int) ([]string, *model.AppError)
}
// LogService is the API for accessing the log service APIs.
@@ -212,3 +233,32 @@ type BoardsService interface {
HasPermissionToBoard(userID, boardID string, permission *model.Permission) bool
DuplicateBoard(boardID string, userID string, toTeam string, asTemplate bool) (*fb_model.BoardsAndBlocks, []*fb_model.BoardMember, error)
}
// SessionService is the API for accessing the session.
//
// The service shall be registered via app.SessionKey service key.
type SessionService interface {
GetSessionById(sessionID string) (*model.Session, *model.AppError)
}
// FrontendService is the API for interacting with front end.
//
// The service shall be registered via app.FrontendKey service key.
type FrontendService interface {
OpenInteractiveDialog(dialog model.OpenDialogRequest) *model.AppError
}
// CommandService is the API for interacting with front end.
//
// The service shall be registered via app.CommandKey service key.
type CommandService interface {
ExecuteCommand(c request.CTX, args *model.CommandArgs) (*model.CommandResponse, *model.AppError)
RegisterPluginCommand(pluginID string, command *model.Command) error
}
// ThreadsService is the API for interacting with threads anywhere.
//
// The service shall be registered via app.ThreadsKey service key.
type ThreadsService interface {
RegisterCollectionAndTopic(productID string, collectionType, topicType string) error
}

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

@@ -26,4 +26,8 @@ const (
SystemKey ServiceKey = "systemkey"
PreferencesKey ServiceKey = "preferenceskey"
BoardsKey ServiceKey = "boards"
SessionKey ServiceKey = "sessionkey"
FrontendKey ServiceKey = "frontendkey"
CommandKey ServiceKey = "commandkey"
ThreadsKey ServiceKey = "threadskey"
)

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

@@ -16,5 +16,10 @@ then
txt="${txt}use ../focalboard/server\nuse ../focalboard/mattermost-plugin\n"
fi
if [ "$BUILD_PLAYBOOKS" == "true" ]
then
txt="${txt}use ../mattermost-plugin-playbooks\n"
fi
printf "$txt" > "go.work"
fi

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

@@ -13,6 +13,7 @@ import (
"strings"
"github.com/mattermost/go-i18n/i18n"
"github.com/mattermost/go-i18n/i18n/bundle"
"github.com/mattermost/mattermost-server/v6/shared/mlog"
)
@@ -22,6 +23,9 @@ const defaultLocale = "en"
// TranslateFunc is the type of the translate functions
type TranslateFunc func(translationID string, args ...any) string
// TranslationFuncByLocal is the type of function that takes local as a string and returns the translation function
type TranslationFuncByLocal func(locale string) TranslateFunc
// T is the translate function using the default server language as fallback language
var T TranslateFunc
@@ -74,6 +78,41 @@ func initTranslationsWithDir(dir string) error {
return nil
}
// GetTranslationFuncForDir loads translations from the filesystem into a new instance of the bundle.
// It returns a function to access loaded translations.
func GetTranslationFuncForDir(dir string) (TranslationFuncByLocal, error) {
var availableLocals map[string]string = make(map[string]string)
bundle := bundle.New()
files, _ := os.ReadDir(dir)
for _, f := range files {
if filepath.Ext(f.Name()) != ".json" {
continue
}
filename := f.Name()
availableLocals[strings.Split(filename, ".")[0]] = filepath.Join(dir, filename)
if err := bundle.LoadTranslationFile(filepath.Join(dir, filename)); err != nil {
return nil, err
}
}
return func(locale string) TranslateFunc {
if _, ok := availableLocals[locale]; !ok {
locale = defaultLocale
}
t, _ := bundle.Tfunc(locale)
return func(translationID string, args ...any) string {
if translated := t(translationID, args...); translated != translationID {
return translated
}
t, _ := bundle.Tfunc(defaultLocale)
return t(translationID, args...)
}
}, nil
}
func getTranslationsBySystemLocale() (TranslateFunc, error) {
locale := defaultServerLocale
if _, ok := locales[locale]; !ok {