[MM-52926] Deprecating work templates (#23466)
Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
@@ -354,9 +354,6 @@ telemetry-mocks: ## Creates mock files.
|
||||
store-layers: ## Generate layers for the store
|
||||
$(GO) generate $(GOFLAGS) ./channels/store
|
||||
|
||||
generate-worktemplates: ## Generate work templates
|
||||
$(GO) generate $(GOFLAGS) ./channels/app/worktemplates
|
||||
|
||||
new-migration: ## Creates a new migration. Run with make new-migration name=<>
|
||||
$(GO) install github.com/mattermost/morph/cmd/morph@master
|
||||
@echo "Generating new migration for mysql"
|
||||
@@ -395,7 +392,6 @@ sharedchannel-mocks: ## Creates mock files for shared channels.
|
||||
misc-mocks: ## Creates mocks for misc interfaces.
|
||||
$(GO) install github.com/vektra/mockery/v2/...@v2.23.2
|
||||
$(GOBIN)/mockery --dir channels/utils --name LicenseValidatorIface --output channels/utils/mocks --note 'Regenerate this file using `make misc-mocks`.'
|
||||
$(GOBIN)/mockery --dir channels/app --name WorkTemplateExecutor --output channels/app/mocks --note 'Regenerate this file using `make misc-mocks`.'
|
||||
|
||||
email-mocks: ## Creates mocks for misc interfaces.
|
||||
$(GO) install github.com/vektra/mockery/v2/...@v2.23.2
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package integrationtests
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// This test is there to guarantee that the board templates needed for
|
||||
// the work template are present in the default templates.
|
||||
// If this fails, you might need to sync with the channels team.
|
||||
func TestGetTemplatesForWorkTemplate(t *testing.T) {
|
||||
// map[name]trackingTemplateId
|
||||
knownInWorkTemplates := map[string]string{
|
||||
"Company Goals & OKRs": "7ba22ccfdfac391d63dea5c4b8cde0de",
|
||||
"Competitive Analysis": "06f4bff367a7c2126fab2380c9dec23c",
|
||||
"Content Calendar": "c75fbd659d2258b5183af2236d176ab4",
|
||||
"Meeting Agenda ": "54fcf9c610f0ac5e4c522c0657c90602",
|
||||
"Personal Goals ": "7f32dc8d2ae008cfe56554e9363505cc",
|
||||
"Personal Tasls ": "dfb70c146a4584b8a21837477c7b5431",
|
||||
"Project Tasks ": "a4ec399ab4f2088b1051c3cdf1dde4c3",
|
||||
"Roadmap ": "b728c6ca730e2cfc229741c5a4712b65",
|
||||
"Sales Pipeline CRM": "ecc250bb7dff0fe02247f1110f097544",
|
||||
"Sprint Planner ": "99b74e26d2f5d0a9b346d43c0a7bfb09",
|
||||
"Team Retrospective": "e4f03181c4ced8edd4d53d33d569a086",
|
||||
"User Research Sessions": "6c345c7f50f6833f78b7d0f08ce450a3",
|
||||
}
|
||||
th := SetupTestHelper(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
err := th.Server.App().InitTemplates()
|
||||
require.NoError(t, err, "InitTemplates should not fail")
|
||||
|
||||
rBoards, resp := th.Client.GetTemplatesForTeam("0")
|
||||
th.CheckOK(resp)
|
||||
require.NotNil(t, rBoards)
|
||||
|
||||
trackingTemplateIDs := []string{}
|
||||
for _, board := range rBoards {
|
||||
property, _ := board.GetPropertyString("trackingTemplateId")
|
||||
if property != "" {
|
||||
trackingTemplateIDs = append(trackingTemplateIDs, property)
|
||||
}
|
||||
}
|
||||
|
||||
// make sure all known templates are in trackingTemplateIds
|
||||
for name, ttID := range knownInWorkTemplates {
|
||||
found := false
|
||||
for _, trackingTemplateID := range trackingTemplateIDs {
|
||||
if trackingTemplateID == ttID {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
require.True(t, found, "trackingTemplateId %s for %s not found", ttID, name)
|
||||
}
|
||||
}
|
||||
@@ -140,8 +140,6 @@ type Routes struct {
|
||||
|
||||
Usage *mux.Router // 'api/v4/usage'
|
||||
|
||||
WorkTemplates *mux.Router // 'api/v4/worktemplates'
|
||||
|
||||
HostedCustomer *mux.Router // 'api/v4/hosted_customer'
|
||||
|
||||
Drafts *mux.Router // 'api/v4/drafts'
|
||||
@@ -271,8 +269,6 @@ func Init(srv *app.Server) (*API, error) {
|
||||
|
||||
api.BaseRoutes.Usage = api.BaseRoutes.APIRoot.PathPrefix("/usage").Subrouter()
|
||||
|
||||
api.BaseRoutes.WorkTemplates = api.BaseRoutes.APIRoot.PathPrefix("/worktemplates").Subrouter()
|
||||
|
||||
api.BaseRoutes.HostedCustomer = api.BaseRoutes.APIRoot.PathPrefix("/hosted_customer").Subrouter()
|
||||
|
||||
api.BaseRoutes.Drafts = api.BaseRoutes.APIRoot.PathPrefix("/drafts").Subrouter()
|
||||
@@ -320,7 +316,6 @@ func Init(srv *app.Server) (*API, error) {
|
||||
api.InitExport()
|
||||
api.InitInsights()
|
||||
api.InitUsage()
|
||||
api.InitWorkTemplate()
|
||||
api.InitHostedCustomer()
|
||||
api.InitDrafts()
|
||||
if err := api.InitGraphQL(); err != nil {
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package api4
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/mattermost/mattermost-server/server/public/model"
|
||||
"github.com/mattermost/mattermost-server/server/v8/channels/app/worktemplates"
|
||||
)
|
||||
|
||||
const WorkTemplateContextOnboarding = "onboarding"
|
||||
|
||||
func (api *API) InitWorkTemplate() {
|
||||
api.BaseRoutes.WorkTemplates.Handle("/categories", api.APISessionRequired(getWorkTemplateCategories)).Methods("GET")
|
||||
api.BaseRoutes.WorkTemplates.Handle("/categories/{category}/templates", api.APISessionRequired(getWorkTemplates)).Methods("GET")
|
||||
api.BaseRoutes.WorkTemplates.Handle("/execute", api.APIHandler(executeWorkTemplate)).Methods("POST")
|
||||
}
|
||||
|
||||
func areWorkTemplatesEnabled(c *Context) *model.AppError {
|
||||
if !c.App.Config().FeatureFlags.WorkTemplate {
|
||||
return model.NewAppError("areWorkTemplatesEnabled", "api.work_templates.disabled", nil, "feature flag is off", http.StatusNotFound)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getWorkTemplateCategories(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
appErr := areWorkTemplatesEnabled(c)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
t := c.AppContext.GetT()
|
||||
|
||||
categories, appErr := c.App.GetWorkTemplateCategories(t)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
b, err := json.Marshal(categories)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError("getWorkTemplateCategories", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
w.Write(b)
|
||||
}
|
||||
|
||||
func getWorkTemplates(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
appErr := areWorkTemplatesEnabled(c)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
c.RequireCategory()
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
t := c.AppContext.GetT()
|
||||
|
||||
context := r.URL.Query().Get("context")
|
||||
isOnboarding := false
|
||||
if context == WorkTemplateContextOnboarding {
|
||||
isOnboarding = true
|
||||
}
|
||||
|
||||
workTemplates, appErr := c.App.GetWorkTemplates(c.Params.Category, c.App.Config().FeatureFlags.ToMap(), isOnboarding, t)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
b, err := json.Marshal(workTemplates)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError("getWorkTemplates", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
w.Write(b)
|
||||
}
|
||||
|
||||
func executeWorkTemplate(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
appErr := areWorkTemplatesEnabled(c)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
wtcr := &worktemplates.ExecutionRequest{}
|
||||
err := json.NewDecoder(r.Body).Decode(wtcr)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError("executeWorkTemplate", "api.unmarshal_error", nil, "", http.StatusBadRequest).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
canCreatePublicChannel := c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), wtcr.TeamID, model.PermissionCreatePublicChannel)
|
||||
canCreatePrivateChannel := c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), wtcr.TeamID, model.PermissionCreatePrivateChannel)
|
||||
// focalboard uses channel permissions for board creation
|
||||
canCreatePublicBoard := canCreatePublicChannel
|
||||
canCreatePrivateBoard := canCreatePrivateChannel
|
||||
canCreatePublicPlaybook := c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), wtcr.TeamID, model.PermissionPublicPlaybookCreate)
|
||||
canCreatePrivatePlaybook := c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), wtcr.TeamID, model.PermissionPrivatePlaybookCreate)
|
||||
appErr = wtcr.CanBeExecuted(worktemplates.PermissionSet{
|
||||
License: c.App.License(),
|
||||
CanCreatePublicChannel: canCreatePublicChannel,
|
||||
CanCreatePrivateChannel: canCreatePrivateChannel,
|
||||
CanCreatePublicBoard: canCreatePublicBoard,
|
||||
CanCreatePrivateBoard: canCreatePrivateBoard,
|
||||
CanCreatePublicPlaybook: canCreatePublicPlaybook,
|
||||
CanCreatePrivatePlaybook: canCreatePrivatePlaybook,
|
||||
})
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
canInstallPlugin := c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionSysconsoleWritePlugins)
|
||||
if !*c.App.Config().PluginSettings.Enable || !*c.App.Config().PluginSettings.EnableMarketplace || *c.App.Config().PluginSettings.MarketplaceURL != model.PluginSettingsDefaultMarketplaceURL {
|
||||
canInstallPlugin = false
|
||||
}
|
||||
|
||||
res, appErr := c.App.ExecuteWorkTemplate(c.AppContext, wtcr, canInstallPlugin)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
err = json.NewEncoder(w).Encode(res)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError("executeWorkTemplate", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,6 @@ import (
|
||||
"github.com/mattermost/mattermost-server/server/public/shared/timezones"
|
||||
"github.com/mattermost/mattermost-server/server/v8/channels/app/platform"
|
||||
"github.com/mattermost/mattermost-server/server/v8/channels/app/request"
|
||||
"github.com/mattermost/mattermost-server/server/v8/channels/app/worktemplates"
|
||||
"github.com/mattermost/mattermost-server/server/v8/channels/audit"
|
||||
"github.com/mattermost/mattermost-server/server/v8/channels/product"
|
||||
"github.com/mattermost/mattermost-server/server/v8/channels/store"
|
||||
@@ -566,7 +565,6 @@ type AppIface interface {
|
||||
DownloadFromURL(downloadURL string) ([]byte, error)
|
||||
EnableUserAccessToken(token *model.UserAccessToken) *model.AppError
|
||||
EnvironmentConfig(filter func(reflect.StructField) bool) map[string]any
|
||||
ExecuteWorkTemplate(c *request.Context, wtcr *worktemplates.ExecutionRequest, installPlugins bool) (*WorkTemplateExecutionResult, *model.AppError)
|
||||
ExportPermissions(w io.Writer) error
|
||||
ExtractContentFromFileInfo(fileInfo *model.FileInfo) error
|
||||
FetchSamlMetadataFromIdp(url string) ([]byte, *model.AppError)
|
||||
@@ -865,8 +863,6 @@ type AppIface interface {
|
||||
GetViewUsersRestrictions(userID string) (*model.ViewUsersRestrictions, *model.AppError)
|
||||
GetWarnMetricsBot() (*model.Bot, *model.AppError)
|
||||
GetWarnMetricsStatus() (map[string]*model.WarnMetricStatus, *model.AppError)
|
||||
GetWorkTemplateCategories(t i18n.TranslateFunc) ([]*model.WorkTemplateCategory, *model.AppError)
|
||||
GetWorkTemplates(category string, featureFlags map[string]string, includeOnboardingTemplates bool, t i18n.TranslateFunc) ([]*model.WorkTemplate, *model.AppError)
|
||||
HTTPService() httpservice.HTTPService
|
||||
Handle404(w http.ResponseWriter, r *http.Request)
|
||||
HandleCommandResponse(c request.CTX, command *model.Command, args *model.CommandArgs, response *model.CommandResponse, builtIn bool) (*model.CommandResponse, *model.AppError)
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
// Code generated by mockery v2.23.2. DO NOT EDIT.
|
||||
|
||||
// Regenerate this file using `make misc-mocks`.
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
model "github.com/mattermost/mattermost-server/server/public/model"
|
||||
request "github.com/mattermost/mattermost-server/server/v8/channels/app/request"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
worktemplates "github.com/mattermost/mattermost-server/server/v8/channels/app/worktemplates"
|
||||
)
|
||||
|
||||
// WorkTemplateExecutor is an autogenerated mock type for the WorkTemplateExecutor type
|
||||
type WorkTemplateExecutor struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// CreateBoard provides a mock function with given fields: c, wtcr, cBoard, linkToChannelID
|
||||
func (_m *WorkTemplateExecutor) CreateBoard(c *request.Context, wtcr *worktemplates.ExecutionRequest, cBoard *model.WorkTemplateBoard, linkToChannelID string) (string, error) {
|
||||
ret := _m.Called(c, wtcr, cBoard, linkToChannelID)
|
||||
|
||||
var r0 string
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(*request.Context, *worktemplates.ExecutionRequest, *model.WorkTemplateBoard, string) (string, error)); ok {
|
||||
return rf(c, wtcr, cBoard, linkToChannelID)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(*request.Context, *worktemplates.ExecutionRequest, *model.WorkTemplateBoard, string) string); ok {
|
||||
r0 = rf(c, wtcr, cBoard, linkToChannelID)
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(*request.Context, *worktemplates.ExecutionRequest, *model.WorkTemplateBoard, string) error); ok {
|
||||
r1 = rf(c, wtcr, cBoard, linkToChannelID)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// CreateChannel provides a mock function with given fields: c, wtcr, cChannel
|
||||
func (_m *WorkTemplateExecutor) CreateChannel(c *request.Context, wtcr *worktemplates.ExecutionRequest, cChannel *model.WorkTemplateChannel) (string, error) {
|
||||
ret := _m.Called(c, wtcr, cChannel)
|
||||
|
||||
var r0 string
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(*request.Context, *worktemplates.ExecutionRequest, *model.WorkTemplateChannel) (string, error)); ok {
|
||||
return rf(c, wtcr, cChannel)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(*request.Context, *worktemplates.ExecutionRequest, *model.WorkTemplateChannel) string); ok {
|
||||
r0 = rf(c, wtcr, cChannel)
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(*request.Context, *worktemplates.ExecutionRequest, *model.WorkTemplateChannel) error); ok {
|
||||
r1 = rf(c, wtcr, cChannel)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// CreatePlaybook provides a mock function with given fields: c, wtcr, playbook, channel
|
||||
func (_m *WorkTemplateExecutor) CreatePlaybook(c *request.Context, wtcr *worktemplates.ExecutionRequest, playbook *model.WorkTemplatePlaybook, channel model.WorkTemplateChannel) (string, error) {
|
||||
ret := _m.Called(c, wtcr, playbook, channel)
|
||||
|
||||
var r0 string
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(*request.Context, *worktemplates.ExecutionRequest, *model.WorkTemplatePlaybook, model.WorkTemplateChannel) (string, error)); ok {
|
||||
return rf(c, wtcr, playbook, channel)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(*request.Context, *worktemplates.ExecutionRequest, *model.WorkTemplatePlaybook, model.WorkTemplateChannel) string); ok {
|
||||
r0 = rf(c, wtcr, playbook, channel)
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(*request.Context, *worktemplates.ExecutionRequest, *model.WorkTemplatePlaybook, model.WorkTemplateChannel) error); ok {
|
||||
r1 = rf(c, wtcr, playbook, channel)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// InstallPlugin provides a mock function with given fields: c, wtcr, cIntegration, sendToChannelID
|
||||
func (_m *WorkTemplateExecutor) InstallPlugin(c *request.Context, wtcr *worktemplates.ExecutionRequest, cIntegration *model.WorkTemplateIntegration, sendToChannelID string) error {
|
||||
ret := _m.Called(c, wtcr, cIntegration, sendToChannelID)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(*request.Context, *worktemplates.ExecutionRequest, *model.WorkTemplateIntegration, string) error); ok {
|
||||
r0 = rf(c, wtcr, cIntegration, sendToChannelID)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
type mockConstructorTestingTNewWorkTemplateExecutor interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}
|
||||
|
||||
// NewWorkTemplateExecutor creates a new instance of WorkTemplateExecutor. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
func NewWorkTemplateExecutor(t mockConstructorTestingTNewWorkTemplateExecutor) *WorkTemplateExecutor {
|
||||
mock := &WorkTemplateExecutor{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
@@ -45,16 +45,6 @@ func (a *App) CompleteOnboarding(c *request.Context, request *model.CompleteOnbo
|
||||
}
|
||||
}
|
||||
|
||||
if request.Role != "" {
|
||||
err := a.Srv().Store().System().SaveOrUpdate(&model.System{
|
||||
Name: model.SystemFirstAdminRole,
|
||||
Value: request.Role,
|
||||
})
|
||||
if err != nil {
|
||||
a.Log().Error("failed to save first admin role", mlog.Err(err))
|
||||
}
|
||||
}
|
||||
|
||||
pluginsEnvironment := a.Channels().GetPluginsEnvironment()
|
||||
if pluginsEnvironment == nil {
|
||||
return a.markAdminOnboardingComplete(c)
|
||||
|
||||
@@ -28,21 +28,3 @@ func TestOnboardingSavesOrganizationName(t *testing.T) {
|
||||
require.NoError(t, storeErr)
|
||||
require.Equal(t, "Mattermost In Tests", sys.Value)
|
||||
}
|
||||
|
||||
func TestOnboardingFirstAdminRole(t *testing.T) {
|
||||
th := Setup(t)
|
||||
defer th.TearDown()
|
||||
|
||||
err := th.App.CompleteOnboarding(&request.Context{}, &mm_model.CompleteOnboardingRequest{
|
||||
Organization: "myorg",
|
||||
Role: "engineering",
|
||||
})
|
||||
require.Nil(t, err)
|
||||
defer func() {
|
||||
th.App.Srv().Store().System().PermanentDeleteByName(mm_model.SystemFirstAdminRole)
|
||||
}()
|
||||
|
||||
sys, storeErr := th.App.Srv().Store().System().GetByName(mm_model.SystemFirstAdminRole)
|
||||
require.NoError(t, storeErr)
|
||||
require.Equal(t, "engineering", sys.Value)
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ import (
|
||||
"github.com/mattermost/mattermost-server/server/v8/channels/app"
|
||||
"github.com/mattermost/mattermost-server/server/v8/channels/app/platform"
|
||||
"github.com/mattermost/mattermost-server/server/v8/channels/app/request"
|
||||
"github.com/mattermost/mattermost-server/server/v8/channels/app/worktemplates"
|
||||
"github.com/mattermost/mattermost-server/server/v8/channels/audit"
|
||||
"github.com/mattermost/mattermost-server/server/v8/channels/product"
|
||||
"github.com/mattermost/mattermost-server/server/v8/channels/store"
|
||||
@@ -4105,28 +4104,6 @@ func (a *OpenTracingAppLayer) ExecuteCommand(c request.CTX, args *model.CommandA
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) ExecuteWorkTemplate(c *request.Context, wtcr *worktemplates.ExecutionRequest, installPlugins bool) (*app.WorkTemplateExecutionResult, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.ExecuteWorkTemplate")
|
||||
|
||||
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.ExecuteWorkTemplate(c, wtcr, installPlugins)
|
||||
|
||||
if resultVar1 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar1))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) ExportPermissions(w io.Writer) error {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.ExportPermissions")
|
||||
@@ -11382,50 +11359,6 @@ func (a *OpenTracingAppLayer) GetWarnMetricsStatus() (map[string]*model.WarnMetr
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) GetWorkTemplateCategories(t i18n.TranslateFunc) ([]*model.WorkTemplateCategory, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetWorkTemplateCategories")
|
||||
|
||||
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.GetWorkTemplateCategories(t)
|
||||
|
||||
if resultVar1 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar1))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) GetWorkTemplates(category string, featureFlags map[string]string, includeOnboardingTemplates bool, t i18n.TranslateFunc) ([]*model.WorkTemplate, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetWorkTemplates")
|
||||
|
||||
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.GetWorkTemplates(category, featureFlags, includeOnboardingTemplates, t)
|
||||
|
||||
if resultVar1 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar1))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) Handle404(w http.ResponseWriter, r *http.Request) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.Handle404")
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package slashcommands
|
||||
|
||||
import (
|
||||
"github.com/mattermost/mattermost-server/server/public/model"
|
||||
"github.com/mattermost/mattermost-server/server/public/shared/i18n"
|
||||
"github.com/mattermost/mattermost-server/server/v8/channels/app"
|
||||
"github.com/mattermost/mattermost-server/server/v8/channels/app/request"
|
||||
)
|
||||
|
||||
type TemplatesProvider struct {
|
||||
}
|
||||
|
||||
const (
|
||||
CmdTemplates = "templates"
|
||||
)
|
||||
|
||||
func init() {
|
||||
app.RegisterCommandProvider(&TemplatesProvider{})
|
||||
}
|
||||
|
||||
func (h *TemplatesProvider) GetTrigger() string {
|
||||
return CmdTemplates
|
||||
}
|
||||
|
||||
func (h *TemplatesProvider) GetCommand(a *app.App, T i18n.TranslateFunc) *model.Command {
|
||||
workTemplateEnabled := a.Config().FeatureFlags.WorkTemplate
|
||||
pbActive, err := a.IsPluginActive(model.PluginIdPlaybooks)
|
||||
if err != nil {
|
||||
pbActive = false
|
||||
}
|
||||
hasBoard, err := a.HasBoardProduct()
|
||||
if err != nil {
|
||||
hasBoard = false
|
||||
}
|
||||
|
||||
return &model.Command{
|
||||
Trigger: CmdTemplates,
|
||||
AutoComplete: hasBoard && pbActive && workTemplateEnabled,
|
||||
AutoCompleteDesc: T("api.command_templates.desc"),
|
||||
DisplayName: T("api.command_templates.name"),
|
||||
}
|
||||
}
|
||||
|
||||
func (h *TemplatesProvider) DoCommand(a *app.App, c request.CTX, args *model.CommandArgs, message string) *model.CommandResponse {
|
||||
// This command is handled client-side and shouldn't hit the server.
|
||||
return &model.CommandResponse{
|
||||
Text: args.T("api.command_templates.unsupported.app_error"),
|
||||
ResponseType: model.CommandResponseTypeEphemeral,
|
||||
}
|
||||
}
|
||||
@@ -1,302 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/mattermost/mattermost-server/server/public/plugin"
|
||||
pbclient "github.com/mattermost/mattermost-server/server/v8/playbooks/client"
|
||||
|
||||
fb_model "github.com/mattermost/mattermost-server/server/v8/boards/model"
|
||||
|
||||
"github.com/mattermost/mattermost-server/server/public/model"
|
||||
"github.com/mattermost/mattermost-server/server/public/shared/mlog"
|
||||
"github.com/mattermost/mattermost-server/server/v8/channels/app/request"
|
||||
"github.com/mattermost/mattermost-server/server/v8/channels/app/worktemplates"
|
||||
"github.com/mattermost/mattermost-server/server/v8/channels/product"
|
||||
"github.com/mattermost/mattermost-server/server/v8/channels/store"
|
||||
)
|
||||
|
||||
type WorkTemplateExecutor interface {
|
||||
CreatePlaybook(c *request.Context, wtcr *worktemplates.ExecutionRequest, playbook *model.WorkTemplatePlaybook, channel model.WorkTemplateChannel) (string, error)
|
||||
CreateChannel(c *request.Context, wtcr *worktemplates.ExecutionRequest, cChannel *model.WorkTemplateChannel) (string, error)
|
||||
CreateBoard(c *request.Context, wtcr *worktemplates.ExecutionRequest, cBoard *model.WorkTemplateBoard, linkToChannelID string) (string, error)
|
||||
InstallPlugin(c *request.Context, wtcr *worktemplates.ExecutionRequest, cIntegration *model.WorkTemplateIntegration, sendToChannelID string) error
|
||||
}
|
||||
|
||||
type appWorkTemplateExecutor struct {
|
||||
app *App
|
||||
}
|
||||
|
||||
func (e *appWorkTemplateExecutor) CreatePlaybook(
|
||||
c *request.Context,
|
||||
wtcr *worktemplates.ExecutionRequest,
|
||||
playbook *model.WorkTemplatePlaybook,
|
||||
channel model.WorkTemplateChannel) (string, error) {
|
||||
// determine playbook name
|
||||
name := playbook.Name
|
||||
if wtcr.Name != "" {
|
||||
name += " " + wtcr.Name
|
||||
}
|
||||
|
||||
// get the correct playbook pbTemplate
|
||||
pbTemplate, err := wtcr.FindPlaybookTemplate(playbook.Template)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to find playbook template: %w", err)
|
||||
}
|
||||
|
||||
pbTemplate.TeamID = wtcr.TeamID
|
||||
pbTemplate.Title = name
|
||||
pbTemplate.Public = wtcr.Visibility == model.WorkTemplateVisibilityPublic
|
||||
pbTemplate.CreatePublicPlaybookRun = wtcr.Visibility == model.WorkTemplateVisibilityPublic
|
||||
data, err := json.Marshal(pbTemplate)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to marshal playbook template: %w", err)
|
||||
}
|
||||
|
||||
resp, appErr := e.app.doPluginRequest(c, http.MethodPost, "/plugins/playbooks/api/v0/playbooks", nil, data)
|
||||
if appErr != nil {
|
||||
return "", fmt.Errorf("unable to create playbook: %w", appErr)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
pbcResp := playbookCreateResponse{}
|
||||
err = json.NewDecoder(resp.Body).Decode(&pbcResp)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to decode playbook create response: %w", err)
|
||||
}
|
||||
|
||||
runName := channel.Name
|
||||
if wtcr.Name != "" {
|
||||
runName = wtcr.Name
|
||||
}
|
||||
data, err = json.Marshal(pbclient.PlaybookRunCreateOptions{
|
||||
Name: runName,
|
||||
OwnerUserID: c.Session().UserId,
|
||||
TeamID: wtcr.TeamID,
|
||||
PlaybookID: pbcResp.ID,
|
||||
})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to marshal playbook run create request: %w", err)
|
||||
}
|
||||
resp, appErr = e.app.doPluginRequest(c, http.MethodPost, "/plugins/playbooks/api/v0/runs", nil, data)
|
||||
if appErr != nil {
|
||||
return "", fmt.Errorf("unable to create playbook run: %w", appErr)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
pbrResp := playbookRunCreateResponse{}
|
||||
err = json.NewDecoder(resp.Body).Decode(&pbrResp)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to decode playbook run create response: %w", err)
|
||||
}
|
||||
|
||||
// using pbrResp.ChannelID, update the channel to add metadata
|
||||
dbChannel, err := e.app.Srv().Store().Channel().Get(pbrResp.ChannelID, false)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to find channel: %w", err)
|
||||
}
|
||||
if dbChannel == nil {
|
||||
return "", fmt.Errorf("channel not found")
|
||||
}
|
||||
dbChannel.AddProp(model.WorkTemplateIDChannelProp, wtcr.WorkTemplate.ID)
|
||||
_, err = e.app.Srv().Store().Channel().Update(dbChannel)
|
||||
if err != nil {
|
||||
e.app.Srv().Log().Error("Failed to update playbook channel metadata", mlog.Err(err))
|
||||
}
|
||||
|
||||
return pbrResp.ChannelID, nil
|
||||
}
|
||||
|
||||
func (e *appWorkTemplateExecutor) CreateChannel(
|
||||
c *request.Context,
|
||||
wtcr *worktemplates.ExecutionRequest,
|
||||
cChannel *model.WorkTemplateChannel,
|
||||
) (string, error) {
|
||||
channelID := ""
|
||||
channelDisplayName := cChannel.Name
|
||||
if wtcr.Name != "" {
|
||||
channelDisplayName = wtcr.Name
|
||||
}
|
||||
|
||||
var channelCreationAppErr *model.AppError = &model.AppError{}
|
||||
cleanChannelName := cleanChannelName(channelDisplayName)
|
||||
channelName := cleanChannelName
|
||||
if len(channelName) > model.ChannelNameMaxLength {
|
||||
channelName = channelName[:model.ChannelNameMaxLength]
|
||||
}
|
||||
|
||||
// Mostly because of the "quick use" feature, we might try to create channel that have the exact same "Name"
|
||||
// This loop ensures that if the original name is taken, we try again by adding a suffix to the Name
|
||||
for channelCreationAppErr != nil {
|
||||
// create channel
|
||||
var newChan *model.Channel
|
||||
newChan, channelCreationAppErr = e.app.CreateChannelWithUser(c, &model.Channel{
|
||||
TeamId: wtcr.TeamID,
|
||||
Name: channelName,
|
||||
DisplayName: channelDisplayName,
|
||||
Type: model.ChannelTypeOpen,
|
||||
Purpose: cChannel.Purpose,
|
||||
Props: map[string]any{
|
||||
model.WorkTemplateIDChannelProp: wtcr.WorkTemplate.ID,
|
||||
},
|
||||
}, c.Session().UserId)
|
||||
if channelCreationAppErr != nil {
|
||||
if channelCreationAppErr.Id == store.ChannelExistsError {
|
||||
// compute a new unique name
|
||||
suffix := fmt.Sprintf("-%s", model.NewId()[0:4])
|
||||
channelName = cleanChannelName
|
||||
if len(cleanChannelName)+len(suffix) > model.ChannelNameMaxLength {
|
||||
channelName = cleanChannelName[:model.ChannelNameMaxLength-len(suffix)]
|
||||
}
|
||||
channelName = channelName + suffix
|
||||
continue
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("error while creating channel: %w", channelCreationAppErr)
|
||||
}
|
||||
channelID = newChan.Id
|
||||
}
|
||||
|
||||
return channelID, nil
|
||||
}
|
||||
|
||||
func (e *appWorkTemplateExecutor) CreateBoard(
|
||||
c *request.Context,
|
||||
wtcr *worktemplates.ExecutionRequest,
|
||||
cBoard *model.WorkTemplateBoard,
|
||||
linkToChannelID string,
|
||||
) (string, error) {
|
||||
boardService := e.app.Srv().services[product.BoardsKey].(product.BoardsService)
|
||||
templates, err := boardService.GetTemplates("0", c.Session().UserId)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error while getting templates: %w", err)
|
||||
}
|
||||
|
||||
var template *fb_model.Board = nil
|
||||
for _, t := range templates {
|
||||
v, ok := t.Properties["trackingTemplateId"]
|
||||
if ok && v == cBoard.Template {
|
||||
template = t
|
||||
break
|
||||
}
|
||||
}
|
||||
if template == nil {
|
||||
return "", fmt.Errorf("template not found")
|
||||
}
|
||||
|
||||
title := cBoard.Name
|
||||
if wtcr.Name != "" {
|
||||
title += " " + wtcr.Name
|
||||
}
|
||||
|
||||
// Duplicate board From template
|
||||
boardsAndBlocks, _, err := boardService.DuplicateBoard(template.ID, c.Session().UserId, wtcr.TeamID, false)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create new board from template: %w", err)
|
||||
}
|
||||
if len(boardsAndBlocks.Boards) != 1 {
|
||||
return "", fmt.Errorf("only one board was expected, found %d", len(boardsAndBlocks.Boards))
|
||||
}
|
||||
|
||||
// Apply patch for the title and linked channel
|
||||
patchedBoard, err := boardService.PatchBoard(&fb_model.BoardPatch{
|
||||
Title: &title,
|
||||
ChannelID: &linkToChannelID,
|
||||
}, boardsAndBlocks.Boards[0].ID, c.Session().UserId)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to patch board: %w", err)
|
||||
}
|
||||
|
||||
return patchedBoard.ID, nil
|
||||
}
|
||||
|
||||
func (e *appWorkTemplateExecutor) InstallPlugin(
|
||||
c *request.Context,
|
||||
wtcr *worktemplates.ExecutionRequest,
|
||||
cIntegration *model.WorkTemplateIntegration,
|
||||
sendToChannelID string,
|
||||
) error {
|
||||
// check if this plugin is already installed
|
||||
pluginID := cIntegration.ID
|
||||
_, appErr := e.app.GetPluginStatus(pluginID)
|
||||
if appErr != nil {
|
||||
if appErr.Id == "app.plugin.not_installed.app_error" {
|
||||
// we install them in the background as we don't want user to wait for this
|
||||
manifest, installAppErr := e.app.Channels().InstallMarketplacePlugin(&model.InstallMarketplacePluginRequest{
|
||||
Id: pluginID,
|
||||
Version: "",
|
||||
})
|
||||
if installAppErr != nil {
|
||||
return fmt.Errorf("unable to install plugin: %w", installAppErr)
|
||||
}
|
||||
if sendToChannelID != "" {
|
||||
e.app.SendEphemeralPost(c, c.Session().UserId, &model.Post{
|
||||
ChannelId: sendToChannelID,
|
||||
Message: fmt.Sprintf("plugin %s has been installed", manifest.Name),
|
||||
CreateAt: model.GetMillis(),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("unable to get plugin status: %w", appErr)
|
||||
}
|
||||
}
|
||||
|
||||
// get plugin state
|
||||
if err := e.app.EnablePlugin(pluginID); err != nil {
|
||||
return fmt.Errorf("unable to enable plugin: %w", err)
|
||||
}
|
||||
|
||||
hooks, err := e.app.ch.HooksForPluginOrProduct(pluginID)
|
||||
if err != nil {
|
||||
mlog.Warn("Getting hooks for plugin failed", mlog.String("plugin_id", pluginID), mlog.Err(err))
|
||||
return nil
|
||||
}
|
||||
|
||||
event := model.OnInstallEvent{
|
||||
UserId: c.Session().UserId,
|
||||
}
|
||||
|
||||
if err = hooks.OnInstall(&plugin.Context{
|
||||
RequestId: c.RequestId(),
|
||||
SessionId: c.Session().Id,
|
||||
IPAddress: c.IPAddress(),
|
||||
AcceptLanguage: c.AcceptLanguage(),
|
||||
UserAgent: c.UserAgent(),
|
||||
}, event); err != nil {
|
||||
mlog.Error("Plugin OnInstall hook failed", mlog.String("plugin_id", pluginID), mlog.Err(err))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type playbookCreateResponse struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
type playbookRunCreateResponse struct {
|
||||
ID string `json:"id"`
|
||||
ChannelID string `json:"channel_id"`
|
||||
}
|
||||
|
||||
// cleaning channel name code bellow comes from the playbook repository.
|
||||
var allNonSpaceNonWordRegex = regexp.MustCompile(`[^\w\s]`)
|
||||
|
||||
func cleanChannelName(channelName string) string {
|
||||
// Lower case only
|
||||
channelName = strings.ToLower(channelName)
|
||||
// Trim spaces
|
||||
channelName = strings.TrimSpace(channelName)
|
||||
// Change all dashes to whitespace, remove everything that's not a word or whitespace, all space becomes dashes
|
||||
channelName = strings.ReplaceAll(channelName, "-", " ")
|
||||
channelName = allNonSpaceNonWordRegex.ReplaceAllString(channelName, "")
|
||||
channelName = strings.ReplaceAll(channelName, " ", "-")
|
||||
// Remove all leading and trailing dashes
|
||||
channelName = strings.Trim(channelName, "-")
|
||||
|
||||
return channelName
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/mattermost/mattermost-server/server/public/model"
|
||||
"github.com/mattermost/mattermost-server/server/public/shared/i18n"
|
||||
"github.com/mattermost/mattermost-server/server/v8/channels/app/request"
|
||||
"github.com/mattermost/mattermost-server/server/v8/channels/app/worktemplates"
|
||||
)
|
||||
|
||||
func (a *App) GetWorkTemplateCategories(t i18n.TranslateFunc) ([]*model.WorkTemplateCategory, *model.AppError) {
|
||||
categories, err := worktemplates.ListCategories()
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("GetWorkTemplateCategories", "app.worktemplates.get_categories.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
modelCategories := make([]*model.WorkTemplateCategory, len(categories))
|
||||
for i := range categories {
|
||||
modelCategories[i] = &model.WorkTemplateCategory{
|
||||
ID: categories[i].ID,
|
||||
Name: t(categories[i].Name),
|
||||
}
|
||||
}
|
||||
|
||||
return modelCategories, nil
|
||||
}
|
||||
|
||||
func (a *App) GetWorkTemplates(category string, featureFlags map[string]string, includeOnboardingTemplates bool, t i18n.TranslateFunc) ([]*model.WorkTemplate, *model.AppError) {
|
||||
templates, err := worktemplates.ListByCategory(category, includeOnboardingTemplates)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("GetWorkTemplates", "app.worktemplates.get_templates.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
// filter out templates that are not enabled by feature Flag
|
||||
enabledTemplates := []*model.WorkTemplate{}
|
||||
for _, template := range templates {
|
||||
mTemplate := template.ToModelWorkTemplate(t)
|
||||
if template.FeatureFlag == nil {
|
||||
enabledTemplates = append(enabledTemplates, mTemplate)
|
||||
continue
|
||||
}
|
||||
|
||||
if featureFlags[template.FeatureFlag.Name] == template.FeatureFlag.Value {
|
||||
enabledTemplates = append(enabledTemplates, mTemplate)
|
||||
}
|
||||
}
|
||||
|
||||
return enabledTemplates, nil
|
||||
}
|
||||
|
||||
func (a *App) ExecuteWorkTemplate(c *request.Context, wtcr *worktemplates.ExecutionRequest, installPlugins bool) (*WorkTemplateExecutionResult, *model.AppError) {
|
||||
e := &appWorkTemplateExecutor{app: a}
|
||||
return a.executeWorkTemplate(c, wtcr, e, installPlugins)
|
||||
}
|
||||
|
||||
func (a *App) executeWorkTemplate(
|
||||
c *request.Context,
|
||||
wtcr *worktemplates.ExecutionRequest,
|
||||
e WorkTemplateExecutor,
|
||||
installPlugins bool,
|
||||
) (*WorkTemplateExecutionResult, *model.AppError) {
|
||||
res := &WorkTemplateExecutionResult{
|
||||
ChannelWithPlaybookIDs: []string{},
|
||||
ChannelIDs: []string{},
|
||||
}
|
||||
|
||||
if wtcr.Name != "" {
|
||||
if len(wtcr.Name) > model.ChannelNameMaxLength {
|
||||
return res, model.NewAppError("ExecuteWorkTemplate", "app.worktemplates.execute_work_template.name_too_long", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
}
|
||||
|
||||
contentByType := map[string][]model.WorkTemplateContent{
|
||||
"channel": {},
|
||||
"board": {},
|
||||
"playbook": {},
|
||||
"integration": {},
|
||||
}
|
||||
for _, content := range wtcr.WorkTemplate.Content {
|
||||
if content.Channel != nil {
|
||||
contentByType["channel"] = append(contentByType["channel"], content)
|
||||
}
|
||||
if content.Board != nil {
|
||||
contentByType["board"] = append(contentByType["board"], content)
|
||||
}
|
||||
if content.Playbook != nil {
|
||||
contentByType["playbook"] = append(contentByType["playbook"], content)
|
||||
}
|
||||
if content.Integration != nil {
|
||||
contentByType["integration"] = append(contentByType["integration"], content)
|
||||
}
|
||||
}
|
||||
|
||||
firstChannelId := ""
|
||||
channelIDByWorkTemplateID := map[string]string{}
|
||||
for _, pbContent := range contentByType["playbook"] {
|
||||
cPlaybook := pbContent.Playbook
|
||||
|
||||
// find associated channel
|
||||
var associatedChannel *model.WorkTemplateChannel
|
||||
for _, channelContent := range contentByType["channel"] {
|
||||
if channelContent.Channel.Playbook == cPlaybook.ID {
|
||||
associatedChannel = channelContent.Channel
|
||||
break
|
||||
}
|
||||
}
|
||||
if associatedChannel == nil {
|
||||
return res, model.NewAppError("ExecuteWorkTemplate", "app.worktemplates.execute_work_template.playbooks.find_channel_error", nil, "no associated channel found for playbook", http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
channelID, err := e.CreatePlaybook(c, wtcr, cPlaybook, *associatedChannel)
|
||||
if err != nil {
|
||||
return res, model.NewAppError("ExecuteWorkTemplate", "app.worktemplates.execute_work_template.playbooks.create_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
if firstChannelId == "" {
|
||||
firstChannelId = channelID
|
||||
}
|
||||
res.ChannelWithPlaybookIDs = append(res.ChannelWithPlaybookIDs, channelID)
|
||||
channelIDByWorkTemplateID[associatedChannel.ID] = channelID
|
||||
}
|
||||
|
||||
// loop through all channels
|
||||
for _, channelContent := range contentByType["channel"] {
|
||||
cChannel := channelContent.Channel
|
||||
// we only need to create a channel if there's no playbook
|
||||
if cChannel.Playbook == "" {
|
||||
chanID, err := e.CreateChannel(c, wtcr, cChannel)
|
||||
if err != nil {
|
||||
return res, model.NewAppError("ExecuteWorkTemplate", "app.worktemplates.execute_work_template.channels.create_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
if firstChannelId == "" {
|
||||
firstChannelId = chanID
|
||||
}
|
||||
res.ChannelIDs = append(res.ChannelIDs, chanID)
|
||||
channelIDByWorkTemplateID[cChannel.ID] = chanID
|
||||
}
|
||||
}
|
||||
|
||||
for _, boardContent := range contentByType["board"] {
|
||||
cBoard := boardContent.Board
|
||||
channelID := ""
|
||||
if cBoard.Channel != "" {
|
||||
channel, ok := channelIDByWorkTemplateID[cBoard.Channel]
|
||||
if !ok {
|
||||
return res, model.NewAppError("ExecuteWorkTemplate", "app.worktemplates.execute_work_template.app_error", nil, "no associated channel found for board", http.StatusInternalServerError)
|
||||
}
|
||||
channelID = channel
|
||||
}
|
||||
|
||||
_, err := e.CreateBoard(c, wtcr, cBoard, channelID)
|
||||
if err != nil {
|
||||
return res, model.NewAppError("ExecuteWorkTemplate", "app.worktemplates.execute_work_template.boards.create_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
}
|
||||
|
||||
if installPlugins {
|
||||
for _, integrationContent := range contentByType["integration"] {
|
||||
cIntegration := integrationContent.Integration
|
||||
// this can take a long time so we just start those as background tasks
|
||||
go e.InstallPlugin(c, wtcr, cIntegration, firstChannelId)
|
||||
}
|
||||
}
|
||||
|
||||
for _, ch := range res.ChannelWithPlaybookIDs {
|
||||
message := model.NewWebSocketEvent(model.WebsocketEventChannelCreated, "", "", c.Session().UserId, nil, "")
|
||||
message.Add("channel_id", ch)
|
||||
message.Add("team_id", wtcr.TeamID)
|
||||
a.Publish(message)
|
||||
}
|
||||
for _, ch := range res.ChannelIDs {
|
||||
message := model.NewWebSocketEvent(model.WebsocketEventChannelCreated, "", "", c.Session().UserId, nil, "")
|
||||
message.Add("channel_id", ch)
|
||||
message.Add("team_id", wtcr.TeamID)
|
||||
a.Publish(message)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
type WorkTemplateExecutionResult struct {
|
||||
ChannelWithPlaybookIDs []string `json:"channel_with_playbook_ids"`
|
||||
ChannelIDs []string `json:"channel_ids"`
|
||||
}
|
||||
@@ -1,257 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/mattermost/mattermost-server/server/public/model"
|
||||
"github.com/mattermost/mattermost-server/server/v8/channels/app/mocks"
|
||||
"github.com/mattermost/mattermost-server/server/v8/channels/app/request"
|
||||
"github.com/mattermost/mattermost-server/server/v8/channels/app/worktemplates"
|
||||
|
||||
pbclient "github.com/mattermost/mattermost-server/server/v8/playbooks/client"
|
||||
)
|
||||
|
||||
func TestGetWorkTemplateCategories(t *testing.T) {
|
||||
th := SetupWithStoreMock(t)
|
||||
defer th.TearDown()
|
||||
assert := require.New(t)
|
||||
|
||||
worktemplates.OrderedWorkTemplateCategories = wtGetCategories()
|
||||
|
||||
categories, appErr := th.App.GetWorkTemplateCategories(wtTranslationFunc)
|
||||
assert.Nil(appErr)
|
||||
assert.Len(categories, 2)
|
||||
assert.Equal("Translated test.1", categories[0].Name)
|
||||
assert.Equal("Translated test.2", categories[1].Name)
|
||||
}
|
||||
|
||||
func TestGetWorkTemplatesByCategory(t *testing.T) {
|
||||
// Setup
|
||||
th := SetupWithStoreMock(t)
|
||||
defer th.TearDown()
|
||||
assert := require.New(t)
|
||||
|
||||
existingFFkey := "test-feature-flag"
|
||||
existingFFvalue := "true"
|
||||
ff := map[string]string{
|
||||
existingFFkey: existingFFvalue,
|
||||
}
|
||||
|
||||
worktemplates.OrderedWorkTemplateCategories = wtGetCategories()
|
||||
firstCat := worktemplates.OrderedWorkTemplateCategories[0]
|
||||
worktemplates.OrderedWorkTemplates = []*worktemplates.WorkTemplate{
|
||||
{
|
||||
ID: "test-template",
|
||||
Category: firstCat.ID,
|
||||
UseCase: "test use case",
|
||||
Description: worktemplates.Description{
|
||||
Channel: &worktemplates.TranslatableString{
|
||||
ID: "test-template-channel-description",
|
||||
DefaultMessage: "test template channel description",
|
||||
},
|
||||
},
|
||||
},
|
||||
{ // this one should not be returned because of the FF
|
||||
ID: "test-template-2",
|
||||
Category: firstCat.ID,
|
||||
UseCase: "test use case 2",
|
||||
FeatureFlag: &worktemplates.FeatureFlag{
|
||||
Name: "nonexistant-random-test-feature-flag",
|
||||
Value: "hi",
|
||||
},
|
||||
Description: worktemplates.Description{
|
||||
Channel: &worktemplates.TranslatableString{
|
||||
ID: "test-template-2-channel-description",
|
||||
DefaultMessage: "test template 2 channel description",
|
||||
},
|
||||
},
|
||||
},
|
||||
{ // this one should be present and match the FF
|
||||
ID: "test-template-3",
|
||||
Category: firstCat.ID,
|
||||
UseCase: "test use case 3",
|
||||
FeatureFlag: &worktemplates.FeatureFlag{
|
||||
Name: existingFFkey,
|
||||
Value: existingFFvalue,
|
||||
},
|
||||
Description: worktemplates.Description{
|
||||
Channel: &worktemplates.TranslatableString{
|
||||
ID: "unknown", // simulating an unknown translation, we return the default message in this case
|
||||
DefaultMessage: "default message picked for unknown",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "test-template-4",
|
||||
Category: firstCat.ID,
|
||||
UseCase: "test use case 4",
|
||||
OnboardingOnly: true,
|
||||
},
|
||||
{ // this one should not be returned because of the category
|
||||
ID: "test-template-4",
|
||||
Category: "cat-test2",
|
||||
UseCase: "test use case 4",
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("not onboarding", func(t *testing.T) {
|
||||
// Act
|
||||
worktemplates, appErr := th.App.GetWorkTemplates(firstCat.ID, ff, false, wtTranslationFunc)
|
||||
|
||||
// Assert
|
||||
assert.Nil(appErr)
|
||||
assert.Len(worktemplates, 2)
|
||||
// assert the correct work templates have been returned
|
||||
assert.Equal("test-template", worktemplates[0].ID)
|
||||
assert.Equal("test-template-3", worktemplates[1].ID)
|
||||
// assert the descriptions have been translated
|
||||
assert.Equal("Translated test-template-channel-description", worktemplates[0].Description.Channel.Message)
|
||||
assert.Equal("default message picked for unknown", worktemplates[1].Description.Channel.Message)
|
||||
})
|
||||
|
||||
t.Run("onboarding", func(t *testing.T) {
|
||||
// Act
|
||||
worktemplates, appErr := th.App.GetWorkTemplates(firstCat.ID, ff, true, wtTranslationFunc)
|
||||
|
||||
// Assert
|
||||
assert.Nil(appErr)
|
||||
assert.Len(worktemplates, 3)
|
||||
assert.Equal("test-template-4", worktemplates[2].ID)
|
||||
})
|
||||
}
|
||||
|
||||
// helpers
|
||||
func wtTranslationFunc(id string, args ...interface{}) string {
|
||||
if id == "unknown" {
|
||||
return ""
|
||||
}
|
||||
|
||||
return "Translated " + id
|
||||
}
|
||||
|
||||
func wtGetCategories() []*worktemplates.WorkTemplateCategory {
|
||||
return []*worktemplates.WorkTemplateCategory{
|
||||
{
|
||||
ID: "cat-test1",
|
||||
Name: "test.1",
|
||||
},
|
||||
{
|
||||
ID: "cat-test2",
|
||||
Name: "test.2",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecuteWorkTemplate(t *testing.T) {
|
||||
th := SetupWithStoreMock(t)
|
||||
defer th.TearDown()
|
||||
|
||||
c := request.EmptyContext(th.App.Log())
|
||||
c.SetSession(&model.Session{UserId: model.NewId()})
|
||||
|
||||
req := &worktemplates.ExecutionRequest{
|
||||
TeamID: "team-1",
|
||||
Name: "test",
|
||||
WorkTemplate: model.WorkTemplate{
|
||||
ID: "test-template",
|
||||
Content: []model.WorkTemplateContent{
|
||||
{
|
||||
Playbook: &model.WorkTemplatePlaybook{
|
||||
Name: "test playbook",
|
||||
Template: "test template pb",
|
||||
ID: "test-playbook",
|
||||
},
|
||||
},
|
||||
{
|
||||
Channel: &model.WorkTemplateChannel{
|
||||
// this will not create a channel directly
|
||||
// playbooks will do it
|
||||
Name: "test channel",
|
||||
Playbook: "test-playbook",
|
||||
},
|
||||
},
|
||||
{
|
||||
Channel: &model.WorkTemplateChannel{
|
||||
Name: "test channel 2 that will be created",
|
||||
ID: "channel-2",
|
||||
},
|
||||
},
|
||||
{
|
||||
Board: &model.WorkTemplateBoard{
|
||||
Name: "test board",
|
||||
Template: "test template board",
|
||||
Channel: "channel-2",
|
||||
},
|
||||
},
|
||||
{
|
||||
Board: &model.WorkTemplateBoard{
|
||||
Name: "test board with no channel linked",
|
||||
Template: "test template board",
|
||||
},
|
||||
},
|
||||
{
|
||||
Integration: &model.WorkTemplateIntegration{
|
||||
ID: "test-plugin",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
PlaybookTemplates: []*worktemplates.PlaybookTemplate{
|
||||
{
|
||||
Title: "test template pb",
|
||||
Template: pbclient.PlaybookCreateOptions{
|
||||
Title: "test playbook",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
t.Run("with install plugin enabled", func(t *testing.T) {
|
||||
executorMock := &mocks.WorkTemplateExecutor{}
|
||||
executorMock.On("CreatePlaybook", c, req, req.WorkTemplate.Content[0].Playbook, *req.WorkTemplate.Content[1].Channel).Return("channel-1", nil)
|
||||
executorMock.On("CreateChannel", c, req, req.WorkTemplate.Content[2].Channel).Return("channel-2", nil)
|
||||
executorMock.On("CreateBoard", c, req, req.WorkTemplate.Content[3].Board, "channel-2").Return("", nil)
|
||||
executorMock.On("CreateBoard", c, req, req.WorkTemplate.Content[4].Board, "").Return("", nil)
|
||||
executorMock.On("InstallPlugin", c, req, req.WorkTemplate.Content[5].Integration, "channel-1").Return(nil)
|
||||
|
||||
res, appErr := th.App.executeWorkTemplate(c, req, executorMock, true)
|
||||
assert.Nil(t, appErr)
|
||||
assert.Equal(t, "channel-1", res.ChannelWithPlaybookIDs[0])
|
||||
assert.Equal(t, "channel-2", res.ChannelIDs[0])
|
||||
// give some time as plugin are called in a gorouting
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
executorMock.AssertExpectations(t)
|
||||
})
|
||||
t.Run("with install plugin disabled", func(t *testing.T) {
|
||||
executorMock := &mocks.WorkTemplateExecutor{}
|
||||
executorMock.On("CreatePlaybook", c, req, req.WorkTemplate.Content[0].Playbook, *req.WorkTemplate.Content[1].Channel).Return("channel-1", nil)
|
||||
executorMock.On("CreateChannel", c, req, req.WorkTemplate.Content[2].Channel).Return("channel-2", nil)
|
||||
executorMock.On("CreateBoard", c, req, req.WorkTemplate.Content[3].Board, "channel-2").Return("", nil)
|
||||
executorMock.On("CreateBoard", c, req, req.WorkTemplate.Content[4].Board, "").Return("", nil)
|
||||
// the lack of call to InstallPlugin is the difference with the previous test
|
||||
|
||||
res, appErr := th.App.executeWorkTemplate(c, req, executorMock, false)
|
||||
assert.Nil(t, appErr)
|
||||
assert.Equal(t, "channel-1", res.ChannelWithPlaybookIDs[0])
|
||||
assert.Equal(t, "channel-2", res.ChannelIDs[0])
|
||||
// give some time as plugin are called in a gorouting
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
executorMock.AssertExpectations(t)
|
||||
})
|
||||
t.Run("with name too long", func(t *testing.T) {
|
||||
req.Name = strings.Repeat("a", model.ChannelNameMaxLength+1)
|
||||
_, appErr := th.App.executeWorkTemplate(c, req, nil, false)
|
||||
assert.NotNil(t, appErr)
|
||||
assert.Equal(t, http.StatusBadRequest, appErr.StatusCode)
|
||||
})
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
- id: product_teams
|
||||
name: worktemplate.category.product_teams
|
||||
- id: devops
|
||||
name: worktemplate.category.devops
|
||||
- id: leadership
|
||||
name: worktemplate.category.leadership
|
||||
- id: engineering
|
||||
name: worktemplate.category.engineering
|
||||
- id: project_management
|
||||
name: worktemplate.category.project_management
|
||||
- id: marketing
|
||||
name: worktemplate.category.marketing
|
||||
- id: design
|
||||
name: worktemplate.category.design
|
||||
- id: qa
|
||||
name: worktemplate.category.qa
|
||||
- id: other
|
||||
name: worktemplate.category.other
|
||||
@@ -1,207 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"sort"
|
||||
"text/template"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/tools/imports"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/mattermost/mattermost-server/server/v8/channels/app/worktemplates"
|
||||
)
|
||||
|
||||
type WorkTemplateWithMD5 struct {
|
||||
worktemplates.WorkTemplate
|
||||
MD5 string
|
||||
}
|
||||
|
||||
type WorkTemplateCategoryWithMD5 struct {
|
||||
worktemplates.WorkTemplateCategory `yaml:",inline"`
|
||||
MD5 string
|
||||
}
|
||||
|
||||
func getFileContent(filename string) ([]byte, error) {
|
||||
return os.ReadFile(path.Join(filename))
|
||||
}
|
||||
|
||||
func main() {
|
||||
// parse categories first
|
||||
dat, err := getFileContent("categories.yaml")
|
||||
if err != nil {
|
||||
log.Fatal(errors.Wrap(err, "failed to read categories.yaml"))
|
||||
}
|
||||
|
||||
illustrations := []string{}
|
||||
|
||||
h := md5.New()
|
||||
|
||||
cats := []WorkTemplateCategoryWithMD5{} // meow
|
||||
err = yaml.Unmarshal(dat, &cats)
|
||||
if err != nil {
|
||||
log.Fatal(errors.Wrap(err, "failed to unmarshal categories.yaml"))
|
||||
}
|
||||
|
||||
// validate categories
|
||||
categoryIds := map[string]struct{}{}
|
||||
lastCategory := ""
|
||||
for id := range cats {
|
||||
cat := cats[id]
|
||||
|
||||
if cat.ID == "" && cat.Name == "" {
|
||||
// skip empty array element
|
||||
continue
|
||||
}
|
||||
|
||||
if cat.ID == "" {
|
||||
log.Fatal(errors.New("category ID cannot be empty"))
|
||||
}
|
||||
if cat.Name == "" {
|
||||
log.Fatal(errors.New("category name cannot be empty"))
|
||||
}
|
||||
lastCategory = cat.ID
|
||||
categoryIds[cat.ID] = struct{}{}
|
||||
|
||||
h.Write([]byte(cat.ID))
|
||||
cats[id].MD5 = fmt.Sprintf("%x", h.Sum(nil))
|
||||
h.Reset()
|
||||
}
|
||||
if lastCategory != "other" {
|
||||
log.Fatal(errors.New("category 'other' must exist AND be the last category"))
|
||||
}
|
||||
|
||||
dat, err = getFileContent("templates.yaml")
|
||||
if err != nil {
|
||||
log.Fatal(errors.Wrap(err, "failed to read templates.yaml"))
|
||||
}
|
||||
|
||||
dec := yaml.NewDecoder(bytes.NewReader(dat))
|
||||
ts := []WorkTemplateWithMD5{}
|
||||
for {
|
||||
t := worktemplates.WorkTemplate{}
|
||||
err = dec.Decode(&t)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
log.Fatal(err)
|
||||
}
|
||||
if t.ID == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
h.Write([]byte(t.ID))
|
||||
err = t.Validate(categoryIds)
|
||||
if err != nil {
|
||||
log.Fatal(errors.Wrap(err, "failed to validate template"))
|
||||
}
|
||||
|
||||
ts = append(ts, WorkTemplateWithMD5{
|
||||
WorkTemplate: t,
|
||||
MD5: fmt.Sprintf("%x", h.Sum(nil)),
|
||||
})
|
||||
h.Reset()
|
||||
|
||||
// add illustrations to the list
|
||||
illustrations = append(illustrations, t.Illustration)
|
||||
if t.Description.Channel != nil && t.Description.Channel.Illustration != "" {
|
||||
illustrations = append(illustrations, t.Description.Channel.Illustration)
|
||||
}
|
||||
if t.Description.Board != nil && t.Description.Board.Illustration != "" {
|
||||
illustrations = append(illustrations, t.Description.Board.Illustration)
|
||||
}
|
||||
if t.Description.Integration != nil && t.Description.Integration.Illustration != "" {
|
||||
illustrations = append(illustrations, t.Description.Integration.Illustration)
|
||||
}
|
||||
if t.Description.Playbook != nil && t.Description.Playbook.Illustration != "" {
|
||||
illustrations = append(illustrations, t.Description.Playbook.Illustration)
|
||||
}
|
||||
|
||||
for i := range t.Content {
|
||||
if t.Content[i].Channel != nil && t.Content[i].Channel.Illustration != "" {
|
||||
illustrations = append(illustrations, t.Content[i].Channel.Illustration)
|
||||
}
|
||||
if t.Content[i].Board != nil && t.Content[i].Board.Illustration != "" {
|
||||
illustrations = append(illustrations, t.Content[i].Board.Illustration)
|
||||
}
|
||||
if t.Content[i].Playbook != nil && t.Content[i].Playbook.Illustration != "" {
|
||||
illustrations = append(illustrations, t.Content[i].Playbook.Illustration)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
code := bytes.NewBuffer(nil)
|
||||
tmpl, err := template.New("worktemplates").Parse(tpl)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
tmpl.Execute(code, struct {
|
||||
Templates []WorkTemplateWithMD5
|
||||
Categories []WorkTemplateCategoryWithMD5
|
||||
}{
|
||||
Templates: ts,
|
||||
Categories: cats,
|
||||
})
|
||||
|
||||
formattedCode, err := imports.Process(path.Join("worktemplate_generated.go"), code.Bytes(), &imports.Options{Comments: true})
|
||||
if err != nil {
|
||||
log.Fatal(errors.Wrap(err, "failed to format code"))
|
||||
}
|
||||
|
||||
err = os.WriteFile(path.Join("worktemplate_generated.go"), formattedCode, 0644)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// order ts by category alphabetically and by name alphabetically
|
||||
sort.Slice(ts, func(i, j int) bool {
|
||||
if ts[i].Category == ts[j].Category {
|
||||
return ts[i].UseCase < ts[j].UseCase
|
||||
}
|
||||
return ts[i].Category < ts[j].Category
|
||||
})
|
||||
|
||||
// print all translatable content
|
||||
fmt.Println("\nTranslation helpers:\n====================")
|
||||
for _, t := range ts {
|
||||
translationHelper(t.Description.Board)
|
||||
translationHelper(t.Description.Channel)
|
||||
translationHelper(t.Description.Integration)
|
||||
translationHelper(t.Description.Playbook)
|
||||
}
|
||||
|
||||
fmt.Println("Missing illustrations:")
|
||||
for _, illustration := range illustrations {
|
||||
// check if file exists
|
||||
illusPath := path.Join("../../../../webapp/channels/src/images", illustration[8:])
|
||||
if _, err := os.Stat(illusPath); os.IsNotExist(err) {
|
||||
fmt.Println("\t" + illusPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var translationHelperTemplate = `{
|
||||
"id": %q,
|
||||
"translation": %q
|
||||
},`
|
||||
|
||||
func translationHelper(t *worktemplates.TranslatableString) {
|
||||
if t != nil && t.ID != "" && t.DefaultMessage != "" {
|
||||
fmt.Printf(translationHelperTemplate, t.ID, t.DefaultMessage)
|
||||
fmt.Println("")
|
||||
}
|
||||
}
|
||||
|
||||
//go:embed worktemplate.tmpl
|
||||
var tpl string
|
||||
@@ -1,103 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// Code generated by "make generate-worktemplates"
|
||||
// DO NOT EDIT
|
||||
|
||||
package worktemplates
|
||||
|
||||
func init() {
|
||||
{{- range .Categories}}
|
||||
registerWorkTemplateCategory("{{.ID}}", wtc{{.MD5}})
|
||||
{{- end -}}
|
||||
{{range .Templates}}
|
||||
registerWorkTemplate("{{.ID}}", wt{{.MD5}})
|
||||
{{- end}}
|
||||
|
||||
// Register categories strings
|
||||
{{range .Categories -}}
|
||||
_ = T("{{.Name}}")
|
||||
{{end}}
|
||||
|
||||
// Register translation strings
|
||||
{{range .Templates -}}
|
||||
{{if and (.Description.Channel) (ne .Description.Channel.ID "")}}_ = T("{{.Description.Channel.ID}}")
|
||||
{{end -}}
|
||||
{{if and (.Description.Board) (ne .Description.Board.ID "")}}_ = T("{{.Description.Board.ID}}")
|
||||
{{end -}}
|
||||
{{if and (.Description.Playbook) (ne .Description.Playbook.ID "")}}_ = T("{{.Description.Playbook.ID}}")
|
||||
{{end -}}
|
||||
{{if and (.Description.Integration) (ne .Description.Integration.ID "")}}_ = T("{{.Description.Integration.ID}}")
|
||||
{{end -}}
|
||||
{{end -}}
|
||||
}
|
||||
|
||||
{{range .Categories}}
|
||||
var wtc{{.MD5}} = &WorkTemplateCategory{
|
||||
ID: "{{.ID}}",
|
||||
Name: "{{.Name}}",
|
||||
}
|
||||
{{end}}
|
||||
|
||||
{{range .Templates}}
|
||||
var wt{{.MD5}} = &WorkTemplate{
|
||||
ID: "{{.ID}}",
|
||||
Category: "{{.Category}}",
|
||||
UseCase: "{{.UseCase}}",
|
||||
Illustration: "{{.Illustration}}",
|
||||
Visibility: "{{.Visibility}}",
|
||||
OnboardingOnly: {{.OnboardingOnly}},
|
||||
{{if .FeatureFlag}}FeatureFlag: &FeatureFlag{
|
||||
Name: "{{.FeatureFlag.Name}}",
|
||||
Value: "{{.FeatureFlag.Value}}",
|
||||
},{{end}}
|
||||
Description: Description{
|
||||
{{if .Description.Channel}}Channel: &TranslatableString{
|
||||
ID: "{{.Description.Channel.ID}}",
|
||||
DefaultMessage: "{{.Description.Channel.DefaultMessage}}",
|
||||
Illustration: "{{.Description.Channel.Illustration}}",
|
||||
},{{end}}
|
||||
{{if .Description.Board}}Board: &TranslatableString{
|
||||
ID: "{{.Description.Board.ID}}",
|
||||
DefaultMessage: "{{.Description.Board.DefaultMessage}}",
|
||||
Illustration: "{{.Description.Board.Illustration}}",
|
||||
},{{end}}
|
||||
{{if .Description.Playbook}}Playbook: &TranslatableString{
|
||||
ID: "{{.Description.Playbook.ID}}",
|
||||
DefaultMessage: "{{.Description.Playbook.DefaultMessage}}",
|
||||
Illustration: "{{.Description.Playbook.Illustration}}",
|
||||
},{{end}}
|
||||
{{if .Description.Integration}}Integration: &TranslatableString{
|
||||
ID: "{{.Description.Integration.ID}}",
|
||||
DefaultMessage: "{{.Description.Integration.DefaultMessage}}",
|
||||
Illustration: "{{.Description.Integration.Illustration}}",
|
||||
},{{end}}
|
||||
},
|
||||
Content: []Content{
|
||||
{{range .Content}}{
|
||||
{{if .Channel}}Channel: &Channel{
|
||||
ID: "{{.Channel.ID}}",
|
||||
Name: "{{.Channel.Name}}",
|
||||
Purpose: "{{.Channel.Purpose}}",
|
||||
Playbook: "{{.Channel.Playbook}}",
|
||||
Illustration: "{{.Channel.Illustration}}",
|
||||
},{{end}}{{if .Board}}Board: &Board{
|
||||
ID: "{{.Board.ID}}",
|
||||
Template: "{{.Board.Template}}",
|
||||
Name: "{{.Board.Name}}",
|
||||
Channel: "{{.Board.Channel}}",
|
||||
Illustration: "{{.Board.Illustration}}",
|
||||
},{{end}}{{if .Playbook}}Playbook: &Playbook{
|
||||
Template: "{{.Playbook.Template}}",
|
||||
Name: "{{.Playbook.Name}}",
|
||||
ID: "{{.Playbook.ID}}",
|
||||
Illustration: "{{.Playbook.Illustration}}",
|
||||
},{{end}}{{if .Integration}}Integration: &Integration{
|
||||
ID: "{{.Integration.ID}}",
|
||||
Recommended: {{.Integration.Recommended}},
|
||||
},{{end}}
|
||||
},
|
||||
{{end}}
|
||||
},
|
||||
}
|
||||
{{end}}
|
||||
@@ -1,106 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
package worktemplates
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
pbclient "github.com/mattermost/mattermost-server/server/v8/playbooks/client"
|
||||
|
||||
"github.com/mattermost/mattermost-server/server/public/model"
|
||||
)
|
||||
|
||||
type ExecutionRequest struct {
|
||||
TeamID string `json:"team_id"`
|
||||
Name string `json:"name"`
|
||||
Visibility string `json:"visibility"`
|
||||
WorkTemplate model.WorkTemplate `json:"work_template"`
|
||||
PlaybookTemplates []*PlaybookTemplate `json:"playbook_templates"`
|
||||
|
||||
foundPlaybookTemplates map[string]*pbclient.PlaybookCreateOptions
|
||||
}
|
||||
|
||||
type PermissionSet struct {
|
||||
License *model.License
|
||||
|
||||
// channels
|
||||
CanCreatePublicChannel bool
|
||||
CanCreatePrivateChannel bool
|
||||
// playbooks
|
||||
CanCreatePublicPlaybook bool
|
||||
CanCreatePrivatePlaybook bool
|
||||
// boards
|
||||
CanCreatePublicBoard bool
|
||||
CanCreatePrivateBoard bool
|
||||
}
|
||||
|
||||
func (r *ExecutionRequest) CanBeExecuted(p PermissionSet) *model.AppError {
|
||||
public := r.Visibility == model.WorkTemplateVisibilityPublic
|
||||
for _, c := range r.WorkTemplate.Content {
|
||||
if c.Channel != nil {
|
||||
if public && !p.CanCreatePublicChannel {
|
||||
return model.NewAppError("WorkTemplateExecutionRequest.CanBeExecuted", "app.worktemplate.execution_request.cannot_create_public_channel", nil, "", http.StatusForbidden)
|
||||
}
|
||||
if !public && !p.CanCreatePrivateChannel {
|
||||
return model.NewAppError("WorkTemplateExecutionRequest.CanBeExecuted", "app.worktemplate.execution_request.cannot_create_private_channel", nil, "", http.StatusForbidden)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if c.Board != nil {
|
||||
if public && !p.CanCreatePublicBoard {
|
||||
return model.NewAppError("WorkTemplateExecutionRequest.CanBeExecuted", "app.worktemplate.execution_request.cannot_create_public_board", nil, "", http.StatusForbidden)
|
||||
}
|
||||
if !public && !p.CanCreatePrivateBoard {
|
||||
return model.NewAppError("WorkTemplateExecutionRequest.CanBeExecuted", "app.worktemplate.execution_request.cannot_create_private_board", nil, "", http.StatusForbidden)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if c.Playbook != nil {
|
||||
if public && !p.CanCreatePublicPlaybook {
|
||||
return model.NewAppError("WorkTemplateExecutionRequest.CanBeExecuted", "app.worktemplate.execution_request.cannot_create_public_playbook", nil, "", http.StatusForbidden)
|
||||
}
|
||||
if !public && !p.CanCreatePrivatePlaybook {
|
||||
return model.NewAppError("WorkTemplateExecutionRequest.CanBeExecuted", "app.worktemplate.execution_request.cannot_create_private_playbook", nil, "", http.StatusForbidden)
|
||||
}
|
||||
// private playbook is an E20/Enterprise feature
|
||||
if !public && (p.License == nil || (p.License.SkuShortName != model.LicenseShortSkuE20 && p.License.SkuShortName != model.LicenseShortSkuEnterprise)) {
|
||||
return model.NewAppError("WorkTemplateExecutionRequest.CanBeExecuted", "app.worktemplate.execution_request.license_cannot_create_private_playbook", nil, "", http.StatusForbidden)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FindPlaybookTemplate returns the playbook template with the given title.
|
||||
// it also feed a cache to avoid looking for the same template twice.
|
||||
func (r *ExecutionRequest) FindPlaybookTemplate(templateTitle string) (*pbclient.PlaybookCreateOptions, error) {
|
||||
if r.foundPlaybookTemplates == nil {
|
||||
r.foundPlaybookTemplates = make(map[string]*pbclient.PlaybookCreateOptions)
|
||||
}
|
||||
|
||||
if pt, ok := r.foundPlaybookTemplates[templateTitle]; ok {
|
||||
if pt == nil {
|
||||
return nil, errors.New("playbook template not found")
|
||||
}
|
||||
return pt, nil
|
||||
}
|
||||
|
||||
for _, pt := range r.PlaybookTemplates {
|
||||
if pt.Title == templateTitle {
|
||||
r.foundPlaybookTemplates[templateTitle] = &pt.Template
|
||||
return &pt.Template, nil
|
||||
}
|
||||
}
|
||||
r.foundPlaybookTemplates[templateTitle] = nil
|
||||
return nil, errors.New("playbook template not found")
|
||||
}
|
||||
|
||||
type PlaybookTemplate struct {
|
||||
Title string `json:"title"`
|
||||
Template pbclient.PlaybookCreateOptions `json:"template"`
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
package worktemplates
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/mattermost/mattermost-server/server/public/model"
|
||||
|
||||
pbclient "github.com/mattermost/mattermost-server/server/v8/playbooks/client"
|
||||
)
|
||||
|
||||
func TestCanBeExecuted(t *testing.T) {
|
||||
wtcr := &ExecutionRequest{
|
||||
Visibility: model.WorkTemplateVisibilityPublic,
|
||||
WorkTemplate: model.WorkTemplate{
|
||||
Content: []model.WorkTemplateContent{
|
||||
{
|
||||
Playbook: &model.WorkTemplatePlaybook{
|
||||
Name: "test playbook",
|
||||
ID: "test-pb",
|
||||
Template: "test template pb",
|
||||
},
|
||||
},
|
||||
{
|
||||
Channel: &model.WorkTemplateChannel{
|
||||
ID: "test-channel",
|
||||
Name: "test channel",
|
||||
Playbook: "test-pb",
|
||||
},
|
||||
},
|
||||
{
|
||||
Board: &model.WorkTemplateBoard{
|
||||
Name: "test board",
|
||||
Channel: "test-channel",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
PlaybookTemplates: []*PlaybookTemplate{
|
||||
{
|
||||
Title: "test template pb",
|
||||
Template: pbclient.PlaybookCreateOptions{
|
||||
CreatePublicPlaybookRun: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("can run when all permissions are good", func(t *testing.T) {
|
||||
appErr := wtcr.CanBeExecuted(PermissionSet{
|
||||
CanCreatePublicChannel: true,
|
||||
CanCreatePublicPlaybook: true,
|
||||
CanCreatePublicBoard: true,
|
||||
})
|
||||
assert.Nil(t, appErr)
|
||||
})
|
||||
|
||||
t.Run("cannot create private playbook if the license is not enterprise", func(t *testing.T) {
|
||||
wtcrMod := *wtcr
|
||||
wtcrMod.Visibility = model.WorkTemplateVisibilityPrivate
|
||||
appErr := wtcrMod.CanBeExecuted(PermissionSet{
|
||||
License: model.NewTestLicenseSKU(model.LicenseShortSkuProfessional, ""),
|
||||
CanCreatePrivateChannel: true,
|
||||
CanCreatePrivateBoard: true,
|
||||
CanCreatePrivatePlaybook: true,
|
||||
CanCreatePublicChannel: true, // needed for the channel run
|
||||
})
|
||||
require.NotNil(t, appErr)
|
||||
|
||||
appErr = wtcrMod.CanBeExecuted(PermissionSet{
|
||||
License: nil,
|
||||
CanCreatePrivateChannel: true,
|
||||
CanCreatePrivateBoard: true,
|
||||
CanCreatePrivatePlaybook: true,
|
||||
CanCreatePublicChannel: true,
|
||||
})
|
||||
require.NotNil(t, appErr)
|
||||
|
||||
// enterprise and E20 ok
|
||||
appErr = wtcrMod.CanBeExecuted(PermissionSet{
|
||||
License: model.NewTestLicenseSKU(model.LicenseShortSkuEnterprise, ""),
|
||||
CanCreatePrivateChannel: true,
|
||||
CanCreatePrivateBoard: true,
|
||||
CanCreatePrivatePlaybook: true,
|
||||
CanCreatePublicChannel: true,
|
||||
})
|
||||
require.Nil(t, appErr)
|
||||
appErr = wtcrMod.CanBeExecuted(PermissionSet{
|
||||
License: model.NewTestLicenseSKU(model.LicenseShortSkuE20, ""),
|
||||
CanCreatePrivateChannel: true,
|
||||
CanCreatePrivateBoard: true,
|
||||
CanCreatePrivatePlaybook: true,
|
||||
CanCreatePublicChannel: true,
|
||||
})
|
||||
require.Nil(t, appErr)
|
||||
})
|
||||
|
||||
t.Run("fails when something is not allowed", func(t *testing.T) {
|
||||
appErr := wtcr.CanBeExecuted(PermissionSet{
|
||||
CanCreatePublicChannel: true,
|
||||
CanCreatePublicPlaybook: false,
|
||||
CanCreatePublicBoard: true,
|
||||
})
|
||||
require.NotNil(t, appErr)
|
||||
})
|
||||
}
|
||||
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
@@ -1,348 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package worktemplates
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/mattermost/mattermost-server/server/public/model"
|
||||
"github.com/mattermost/mattermost-server/server/public/shared/i18n"
|
||||
)
|
||||
|
||||
type WorkTemplateCategory struct {
|
||||
ID string `yaml:"id"`
|
||||
Name string `yaml:"name"`
|
||||
}
|
||||
|
||||
type WorkTemplate struct {
|
||||
ID string `yaml:"id"`
|
||||
Category string `yaml:"category"`
|
||||
UseCase string `yaml:"useCase"`
|
||||
Illustration string `yaml:"illustration"`
|
||||
Visibility string `yaml:"visibility"`
|
||||
OnboardingOnly bool `yaml:"onboardingOnly"`
|
||||
FeatureFlag *FeatureFlag `yaml:"featureFlag,omitempty"`
|
||||
Description Description `yaml:"description"`
|
||||
Content []Content `yaml:"content"`
|
||||
}
|
||||
|
||||
func (wt WorkTemplate) ToModelWorkTemplate(t i18n.TranslateFunc) *model.WorkTemplate {
|
||||
mwt := &model.WorkTemplate{
|
||||
ID: wt.ID,
|
||||
Category: wt.Category,
|
||||
UseCase: wt.UseCase,
|
||||
Illustration: wt.Illustration,
|
||||
Visibility: wt.Visibility,
|
||||
}
|
||||
|
||||
if wt.FeatureFlag != nil {
|
||||
mwt.FeatureFlag = &model.WorkTemplateFeatureFlag{
|
||||
Name: wt.FeatureFlag.Name,
|
||||
Value: wt.FeatureFlag.Value,
|
||||
}
|
||||
}
|
||||
|
||||
if wt.Description.Channel != nil {
|
||||
mwt.Description.Channel = &model.DescriptionContent{
|
||||
Message: wt.Description.Channel.Translate(t),
|
||||
Illustration: wt.Description.Channel.Illustration,
|
||||
}
|
||||
}
|
||||
|
||||
if wt.Description.Board != nil {
|
||||
mwt.Description.Board = &model.DescriptionContent{
|
||||
Message: wt.Description.Board.Translate(t),
|
||||
Illustration: wt.Description.Board.Illustration,
|
||||
}
|
||||
}
|
||||
|
||||
if wt.Description.Playbook != nil {
|
||||
mwt.Description.Playbook = &model.DescriptionContent{
|
||||
Message: wt.Description.Playbook.Translate(t),
|
||||
Illustration: wt.Description.Playbook.Illustration,
|
||||
}
|
||||
}
|
||||
|
||||
if wt.Description.Integration != nil {
|
||||
mwt.Description.Integration = &model.DescriptionContent{
|
||||
Message: wt.Description.Integration.Translate(t),
|
||||
Illustration: wt.Description.Integration.Illustration,
|
||||
}
|
||||
}
|
||||
|
||||
for _, content := range wt.Content {
|
||||
if content.Channel != nil {
|
||||
mwt.Content = append(mwt.Content, model.WorkTemplateContent{
|
||||
Channel: &model.WorkTemplateChannel{
|
||||
ID: content.Channel.ID,
|
||||
Name: content.Channel.Name,
|
||||
Purpose: content.Channel.Purpose,
|
||||
Playbook: content.Channel.Playbook,
|
||||
Illustration: content.Channel.Illustration,
|
||||
},
|
||||
})
|
||||
}
|
||||
if content.Board != nil {
|
||||
mwt.Content = append(mwt.Content, model.WorkTemplateContent{
|
||||
Board: &model.WorkTemplateBoard{
|
||||
ID: content.Board.ID,
|
||||
Name: content.Board.Name,
|
||||
Template: content.Board.Template,
|
||||
Channel: content.Board.Channel,
|
||||
Illustration: content.Board.Illustration,
|
||||
},
|
||||
})
|
||||
}
|
||||
if content.Playbook != nil {
|
||||
mwt.Content = append(mwt.Content, model.WorkTemplateContent{
|
||||
Playbook: &model.WorkTemplatePlaybook{
|
||||
ID: content.Playbook.ID,
|
||||
Name: content.Playbook.Name,
|
||||
Template: content.Playbook.Template,
|
||||
Illustration: content.Playbook.Illustration,
|
||||
},
|
||||
})
|
||||
}
|
||||
if content.Integration != nil {
|
||||
mwt.Content = append(mwt.Content, model.WorkTemplateContent{
|
||||
Integration: &model.WorkTemplateIntegration{
|
||||
ID: content.Integration.ID,
|
||||
Recommended: content.Integration.Recommended,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return mwt
|
||||
}
|
||||
|
||||
func (wt WorkTemplate) Validate(categoryIds map[string]struct{}) error {
|
||||
if wt.ID == "" {
|
||||
return errors.New("id is required")
|
||||
}
|
||||
if wt.Category == "" {
|
||||
return errors.New("category is required")
|
||||
}
|
||||
if _, ok := categoryIds[wt.Category]; !ok {
|
||||
return fmt.Errorf("category %s does not exist", wt.Category)
|
||||
}
|
||||
if wt.UseCase == "" {
|
||||
return errors.New("useCase is required")
|
||||
}
|
||||
if wt.Illustration == "" {
|
||||
return errors.New("illustration is required")
|
||||
}
|
||||
if wt.Visibility == "" {
|
||||
return errors.New("visibility is required")
|
||||
}
|
||||
hasChannel := false
|
||||
hasBoard := false
|
||||
hasPlaybook := false
|
||||
hasIntegration := false
|
||||
foundChannels := map[string]struct{}{}
|
||||
foundPlaybooks := map[string]struct{}{}
|
||||
foundBoards := map[string]struct{}{}
|
||||
foundIntegrations := map[string]struct{}{}
|
||||
mustHaveChannels := []string{}
|
||||
mustHavePlaybooks := []string{}
|
||||
|
||||
currentIdx := 0
|
||||
for _, content := range wt.Content {
|
||||
if content.Channel != nil {
|
||||
hasChannel = true
|
||||
if cErr := content.Channel.Validate(); cErr != nil {
|
||||
return wrapContentError(cErr, currentIdx)
|
||||
}
|
||||
if _, ok := foundChannels[content.Channel.ID]; ok {
|
||||
return wrapContentError(fmt.Errorf("duplicate channel %s found", content.Channel.ID), currentIdx)
|
||||
}
|
||||
foundChannels[content.Channel.ID] = struct{}{}
|
||||
|
||||
if content.Channel.Playbook != "" {
|
||||
mustHavePlaybooks = append(mustHavePlaybooks, content.Channel.Playbook)
|
||||
}
|
||||
}
|
||||
|
||||
if content.Board != nil {
|
||||
hasBoard = true
|
||||
if cErr := content.Board.Validate(); cErr != nil {
|
||||
return wrapContentError(cErr, currentIdx)
|
||||
}
|
||||
if _, ok := foundBoards[content.Board.ID]; ok {
|
||||
return wrapContentError(fmt.Errorf("duplicate board %s found", content.Board.ID), currentIdx)
|
||||
}
|
||||
foundBoards[content.Board.ID] = struct{}{}
|
||||
|
||||
if content.Board.Channel != "" {
|
||||
mustHaveChannels = append(mustHaveChannels, content.Board.Channel)
|
||||
}
|
||||
}
|
||||
if content.Playbook != nil {
|
||||
hasPlaybook = true
|
||||
if cErr := content.Playbook.Validate(); cErr != nil {
|
||||
return wrapContentError(cErr, currentIdx)
|
||||
}
|
||||
if _, ok := foundPlaybooks[content.Playbook.ID]; ok {
|
||||
return wrapContentError(fmt.Errorf("duplicate playbook %s found", content.Playbook.ID), currentIdx)
|
||||
}
|
||||
foundPlaybooks[content.Playbook.ID] = struct{}{}
|
||||
}
|
||||
if content.Integration != nil {
|
||||
hasIntegration = true
|
||||
if cErr := content.Integration.Validate(); cErr != nil {
|
||||
return wrapContentError(cErr, currentIdx)
|
||||
}
|
||||
if _, ok := foundIntegrations[content.Integration.ID]; ok {
|
||||
return wrapContentError(fmt.Errorf("duplicate integration %s found", content.Integration.ID), currentIdx)
|
||||
}
|
||||
foundIntegrations[content.Integration.ID] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
if !wt.OnboardingOnly {
|
||||
if hasChannel && wt.Description.Channel == nil {
|
||||
return errors.New("description.channel is required")
|
||||
}
|
||||
if hasBoard && wt.Description.Board == nil {
|
||||
return errors.New("description.board is required")
|
||||
}
|
||||
if hasPlaybook && wt.Description.Playbook == nil {
|
||||
return errors.New("description.playbook is required")
|
||||
}
|
||||
if hasIntegration && wt.Description.Integration == nil {
|
||||
return errors.New("description.integration is required")
|
||||
}
|
||||
}
|
||||
|
||||
for _, channel := range mustHaveChannels {
|
||||
if _, ok := foundChannels[channel]; !ok {
|
||||
return fmt.Errorf("channel %s is required", channel)
|
||||
}
|
||||
}
|
||||
|
||||
for _, playbook := range mustHavePlaybooks {
|
||||
if _, ok := foundPlaybooks[playbook]; !ok {
|
||||
return fmt.Errorf("playbook %s is required", playbook)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type FeatureFlag struct {
|
||||
Name string `yaml:"name"`
|
||||
Value string `yaml:"value"`
|
||||
}
|
||||
|
||||
type TranslatableString struct {
|
||||
ID string `yaml:"id"`
|
||||
DefaultMessage string `yaml:"defaultMessage"`
|
||||
Illustration string `yaml:"illustration"`
|
||||
}
|
||||
|
||||
func (ts TranslatableString) Translate(t i18n.TranslateFunc) string {
|
||||
if ts.ID != "" {
|
||||
msg := t(ts.ID)
|
||||
if msg != ts.ID && msg != "" {
|
||||
return msg
|
||||
}
|
||||
}
|
||||
|
||||
return ts.DefaultMessage
|
||||
}
|
||||
|
||||
type Description struct {
|
||||
Channel *TranslatableString `yaml:"channel"`
|
||||
Board *TranslatableString `yaml:"board"`
|
||||
Playbook *TranslatableString `yaml:"playbook"`
|
||||
Integration *TranslatableString `yaml:"integration"`
|
||||
}
|
||||
|
||||
type Channel struct {
|
||||
ID string `yaml:"id"`
|
||||
Name string `yaml:"name"`
|
||||
Purpose string `yaml:"purpose"`
|
||||
Playbook string `yaml:"playbook"`
|
||||
Illustration string `yaml:"illustration"`
|
||||
}
|
||||
|
||||
func (c *Channel) Validate() error {
|
||||
if c.ID == "" {
|
||||
return errors.New("id is required")
|
||||
}
|
||||
if c.Name == "" {
|
||||
return errors.New("name is required")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type Board struct {
|
||||
ID string `yaml:"id"`
|
||||
Template string `yaml:"template"`
|
||||
Name string `yaml:"name"`
|
||||
Channel string `yaml:"channel"`
|
||||
Illustration string `yaml:"illustration"`
|
||||
}
|
||||
|
||||
func (b Board) Validate() error {
|
||||
if b.ID == "" {
|
||||
return errors.New("id is required")
|
||||
}
|
||||
if b.Template == "" {
|
||||
return errors.New("template is required")
|
||||
}
|
||||
if b.Name == "" {
|
||||
return errors.New("name is required")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type Playbook struct {
|
||||
Template string `yaml:"template"`
|
||||
Name string `yaml:"name"`
|
||||
ID string `yaml:"id"`
|
||||
Illustration string `yaml:"illustration"`
|
||||
}
|
||||
|
||||
func (p *Playbook) Validate() error {
|
||||
if p.ID == "" {
|
||||
return errors.New("id is required")
|
||||
}
|
||||
if p.Template == "" {
|
||||
return errors.New("template is required")
|
||||
}
|
||||
if p.Name == "" {
|
||||
return errors.New("name is required")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type Integration struct {
|
||||
ID string `yaml:"id"`
|
||||
Recommended bool `yaml:"recommended"`
|
||||
}
|
||||
|
||||
func (i *Integration) Validate() error {
|
||||
if i.ID == "" {
|
||||
return errors.New("id is required")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type Content struct {
|
||||
Channel *Channel `yaml:"channel,omitempty"`
|
||||
Board *Board `yaml:"board,omitempty"`
|
||||
Playbook *Playbook `yaml:"playbook,omitempty"`
|
||||
Integration *Integration `yaml:"integration,omitempty"`
|
||||
}
|
||||
|
||||
func wrapContentError(err error, index int) error {
|
||||
return errors.Wrapf(err, "content #%d validation failed", index)
|
||||
}
|
||||
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
@@ -1,41 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
//go:generate go run generator/main.go
|
||||
|
||||
package worktemplates
|
||||
|
||||
var OrderedWorkTemplates = []*WorkTemplate{}
|
||||
var OrderedWorkTemplateCategories = []*WorkTemplateCategory{}
|
||||
|
||||
// T is a placeholder to allow the translation tool to register the strings
|
||||
func T(id string) string {
|
||||
return id
|
||||
}
|
||||
|
||||
func registerWorkTemplate(id string, wt *WorkTemplate) {
|
||||
OrderedWorkTemplates = append(OrderedWorkTemplates, wt)
|
||||
}
|
||||
|
||||
func registerWorkTemplateCategory(id string, wtc *WorkTemplateCategory) {
|
||||
OrderedWorkTemplateCategories = append(OrderedWorkTemplateCategories, wtc)
|
||||
}
|
||||
|
||||
func ListCategories() ([]*WorkTemplateCategory, error) {
|
||||
return OrderedWorkTemplateCategories, nil
|
||||
}
|
||||
|
||||
func ListByCategory(category string, includeOnboardingTemplate bool) ([]*WorkTemplate, error) {
|
||||
wts := []*WorkTemplate{}
|
||||
for i := range OrderedWorkTemplates {
|
||||
if OrderedWorkTemplates[i].Category == category {
|
||||
// do not include work template with onboarding only flag if includeOnboardingTemplate is false
|
||||
if !includeOnboardingTemplate && OrderedWorkTemplates[i].OnboardingOnly {
|
||||
continue
|
||||
}
|
||||
wts = append(wts, OrderedWorkTemplates[i])
|
||||
}
|
||||
}
|
||||
|
||||
return wts, nil
|
||||
}
|
||||
@@ -92,7 +92,6 @@ require (
|
||||
gopkg.in/mail.v2 v2.3.1
|
||||
gopkg.in/olivere/elastic.v6 v6.2.37
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -232,6 +231,7 @@ require (
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
lukechampine.com/uint128 v1.3.0 // indirect
|
||||
modernc.org/cc/v3 v3.40.0 // indirect
|
||||
modernc.org/ccgo/v3 v3.16.13 // indirect
|
||||
|
||||
@@ -1482,18 +1482,6 @@
|
||||
"id": "api.command_shrug.name",
|
||||
"translation": "shrug"
|
||||
},
|
||||
{
|
||||
"id": "api.command_templates.desc",
|
||||
"translation": "Open the create from template window"
|
||||
},
|
||||
{
|
||||
"id": "api.command_templates.name",
|
||||
"translation": "templates"
|
||||
},
|
||||
{
|
||||
"id": "api.command_templates.unsupported.app_error",
|
||||
"translation": "The templates command is not supported on your device."
|
||||
},
|
||||
{
|
||||
"id": "api.config.client.old_format.app_error",
|
||||
"translation": "New format for the client configuration is not supported yet. Please specify format=old in the query string."
|
||||
@@ -4555,10 +4543,6 @@
|
||||
"id": "api.websocket_handler.server_busy.app_error",
|
||||
"translation": "Server is busy, non-critical services are temporarily unavailable."
|
||||
},
|
||||
{
|
||||
"id": "api.work_templates.disabled",
|
||||
"translation": "Work templates are disabled."
|
||||
},
|
||||
{
|
||||
"id": "app.acknowledgement.delete.app_error",
|
||||
"translation": "Unable to delete acknowledgement."
|
||||
@@ -7335,66 +7319,6 @@
|
||||
"id": "app.webhooks.update_outgoing.app_error",
|
||||
"translation": "Unable to update the webhook."
|
||||
},
|
||||
{
|
||||
"id": "app.worktemplate.execution_request.cannot_create_private_board",
|
||||
"translation": "You don't have permissions to create a private board."
|
||||
},
|
||||
{
|
||||
"id": "app.worktemplate.execution_request.cannot_create_private_channel",
|
||||
"translation": "You don't have permissions to create a private channel."
|
||||
},
|
||||
{
|
||||
"id": "app.worktemplate.execution_request.cannot_create_private_playbook",
|
||||
"translation": "You don't have permissions to create a private playbook."
|
||||
},
|
||||
{
|
||||
"id": "app.worktemplate.execution_request.cannot_create_public_board",
|
||||
"translation": "You don't have permissions to create a public board."
|
||||
},
|
||||
{
|
||||
"id": "app.worktemplate.execution_request.cannot_create_public_channel",
|
||||
"translation": "You don't have permissions to create a public channel."
|
||||
},
|
||||
{
|
||||
"id": "app.worktemplate.execution_request.cannot_create_public_playbook",
|
||||
"translation": "You don't have permissions to create a public playbook."
|
||||
},
|
||||
{
|
||||
"id": "app.worktemplate.execution_request.license_cannot_create_private_playbook",
|
||||
"translation": "Your license does not support private playbooks."
|
||||
},
|
||||
{
|
||||
"id": "app.worktemplates.execute_work_template.app_error",
|
||||
"translation": "Error while executing a work template."
|
||||
},
|
||||
{
|
||||
"id": "app.worktemplates.execute_work_template.boards.create_error",
|
||||
"translation": "Error while creating a board."
|
||||
},
|
||||
{
|
||||
"id": "app.worktemplates.execute_work_template.channels.create_error",
|
||||
"translation": "Error while creating a channel."
|
||||
},
|
||||
{
|
||||
"id": "app.worktemplates.execute_work_template.name_too_long",
|
||||
"translation": "The name field cannot contain more than 64 characters."
|
||||
},
|
||||
{
|
||||
"id": "app.worktemplates.execute_work_template.playbooks.create_error",
|
||||
"translation": "Error while creating a playbook."
|
||||
},
|
||||
{
|
||||
"id": "app.worktemplates.execute_work_template.playbooks.find_channel_error",
|
||||
"translation": "Unable to find channel associated with a playbook."
|
||||
},
|
||||
{
|
||||
"id": "app.worktemplates.get_categories.app_error",
|
||||
"translation": "Unable to get work template categories"
|
||||
},
|
||||
{
|
||||
"id": "app.worktemplates.get_templates.app_error",
|
||||
"translation": "Unable to get work templates"
|
||||
},
|
||||
{
|
||||
"id": "bleveengine.already_started.error",
|
||||
"translation": "Bleve is already started."
|
||||
@@ -10122,557 +10046,5 @@
|
||||
{
|
||||
"id": "web.incoming_webhook.user.app_error",
|
||||
"translation": "Couldn't find the user."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.category.design",
|
||||
"translation": "Design"
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.category.devops",
|
||||
"translation": "DevOps"
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.category.engineering",
|
||||
"translation": "Engineering"
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.category.leadership",
|
||||
"translation": "Leadership"
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.category.marketing",
|
||||
"translation": "Marketing"
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.category.other",
|
||||
"translation": "Other"
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.category.product_teams",
|
||||
"translation": "Product"
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.category.project_management",
|
||||
"translation": "Project Management"
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.category.qa",
|
||||
"translation": "QA"
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.design.content_calendar.board",
|
||||
"translation": "Use the Content Calendar boad to track ideas, plan your content themes, manage the creation process, and set milestones."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.design.content_calendar.channel",
|
||||
"translation": "Share content ideas, trending posts, blog links, and mentions in a dedicated channel."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.design.create_project.board",
|
||||
"translation": "Use a Kanban board to define and track your project tasks and progress."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.design.create_project.channel",
|
||||
"translation": "Chat with your team about your new project and decide how you’re going to structure it, in a collaborative channel."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.design.create_project.integration",
|
||||
"translation": "Increase productivity in your channel by integrating your most commonly used tools. These will be downloaded for you."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.design.feature_release.description.board",
|
||||
"translation": "Keep meetings on track with the Meeting Agenda board. Manage your workload with the Project Tasks board."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.design.feature_release.description.channel",
|
||||
"translation": "Chat with your team about any release blockers and changes in a channel that connects easily with your boards, playbooks and other integrations."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.design.feature_release.description.integration",
|
||||
"translation": "Increase productivity in your channel by integrating your most commonly used tools to support your feature release, such as GitHub. These will be downloaded for you."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.design.feature_release.description.playbook",
|
||||
"translation": "Boost cross-functional team collaboration with task checklists and automation that support your feature development process. When you’re done, run a retrospective and make improvements for your next release."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.design.product_release.board",
|
||||
"translation": "Use the Product Release board to support your release timeframe and process, ensuring everyone knows which tasks are due."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.design.product_release.channel",
|
||||
"translation": "Chat with your team about daily milestones, any blockers, and changes to deliverables, easily and quickly."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.design.product_release.playbook",
|
||||
"translation": "Create repeatable workflows that are easy to follow and implement so product releases are reliable and on time."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.design.sprint_planning.board",
|
||||
"translation": "Track your team's progress toward weekly goals with sprint breakdowns, prioritization, owner assignment, and comments."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.design.sprint_planning.channel",
|
||||
"translation": "Chat with your team in a channel that connects easily with your boards and integrations."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.design.sprint_planning.integration",
|
||||
"translation": "Increase productivity in your channel by integrating your most commonly used tools, such as Zoom. These will be downloaded for you."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.devops.bug_bash.channel",
|
||||
"translation": "Plan and manage bug reports and resolutions in a single channel, that’s easily accessible to your team and organization."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.devops.bug_bash.integration",
|
||||
"translation": "Increase productivity in your channel by integrating your most commonly used tools, such as Jira, to track your bug bash progress. These will be downloaded for you."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.devops.bug_bash.playbook",
|
||||
"translation": "Use checklists to assign testing areas and automated tasks to run a comprehensive bug bash process. Use a retrospective to review your process and improve it for next time."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.devops.create_project.board",
|
||||
"translation": "Use a Kanban board to define and track your project tasks and progress."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.devops.create_project.channel",
|
||||
"translation": "Chat with your team about your new project and decide how you’re going to structure it, in a collaborative channel."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.devops.create_project.integration",
|
||||
"translation": "Increase productivity in your channel by integrating your most commonly used tools. These will be downloaded for you."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.devops.incident_resolution.description.board",
|
||||
"translation": "Use the Incident Resolution board to support repeatable processes and assign defined tasks across the team."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.devops.incident_resolution.description.channel",
|
||||
"translation": "Chat with your team about priorities, add stakeholders, provide updates, and work toward a resolution in a single channel."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.devops.incident_resolution.description.playbook",
|
||||
"translation": "Use checklists and automation to bring in key team members, and share how your incident is tracking toward resolution."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.devops.product_release.board",
|
||||
"translation": "Use the Product Release board to support your release timeframe and process, ensuring everyone knows which tasks are due."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.devops.product_release.channel",
|
||||
"translation": "Chat with your team about daily milestones, any blockers, and changes to deliverables, easily and quickly."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.devops.product_release.playbook",
|
||||
"translation": "Create repeatable workflows that are easy to follow and implement so product releases are reliable and on time."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.devops.sprint_planning.board",
|
||||
"translation": "Track your team's progress toward weekly goals with sprint breakdowns, prioritization, owner assignment, and comments."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.devops.sprint_planning.channel",
|
||||
"translation": "Chat with your team in a channel that connects easily with your boards and integrations."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.devops.sprint_planning.integration",
|
||||
"translation": "Increase productivity in your channel by integrating your most commonly used tools, such as Zoom. These will be downloaded for you."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.engineering.bug_bash.channel",
|
||||
"translation": "Plan and manage bug reports and resolutions in a single channel, that’s easily accessible to your team and organization."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.engineering.bug_bash.integration",
|
||||
"translation": "Increase productivity in your channel by integrating your most commonly used tools, such as Jira, to track your bug bash progress. These will be downloaded for you."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.engineering.bug_bash.playbook",
|
||||
"translation": "Use checklists to assign testing areas and automated tasks to run a comprehensive bug bash process. Use a retrospective to review your process and improve it for next time."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.engineering.create_project.board",
|
||||
"translation": "Use a Kanban board to define and track your project tasks and progress."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.engineering.create_project.channel",
|
||||
"translation": "Chat with your team about your new project and decide how you’re going to structure it, in a collaborative channel."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.engineering.create_project.integration",
|
||||
"translation": "Increase productivity in your channel by integrating your most commonly used tools. These will be downloaded for you."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.engineering.feature_release.description.board",
|
||||
"translation": "Keep meetings on track with the Meeting Agenda board. Manage your workload with the Project Tasks board."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.engineering.feature_release.description.channel",
|
||||
"translation": "Chat with your team about any release blockers and changes in a channel that connects easily with your boards, playbooks and other integrations."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.engineering.feature_release.description.integration",
|
||||
"translation": "Increase productivity in your channel by integrating your most commonly used tools to support your feature release, such as GitHub. These will be downloaded for you."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.engineering.feature_release.description.playbook",
|
||||
"translation": "Boost cross-functional team collaboration with task checklists and automation that support your feature development process. When you’re done, run a retrospective and make improvements for your next release."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.engineering.goals_and_okrs.board",
|
||||
"translation": "Track your team's progress toward organizational goals with the Goals and OKR board. Keep meetings on track with the Meeting Agenda board."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.engineering.goals_and_okrs.channel",
|
||||
"translation": "Chat about your goals and progress with your team, async or real-time, and stay up to date with changes in a single channel."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.engineering.goals_and_okrs.integration",
|
||||
"translation": "Increase productivity in your channel by integrating your most commonly used tools, such as Zoom, to facilitate easy collaboration. These will be downloaded for you."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.engineering.sprint_planning.board",
|
||||
"translation": "Track your team's progress toward weekly goals with sprint breakdowns, prioritization, owner assignment, and comments."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.engineering.sprint_planning.channel",
|
||||
"translation": "Chat with your team in a channel that connects easily with your boards and integrations."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.engineering.sprint_planning.integration",
|
||||
"translation": "Increase productivity in your channel by integrating your most commonly used tools, such as Zoom. These will be downloaded for you."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.leadership.content_calendar.board",
|
||||
"translation": "Use the Content Calendar boad to track ideas, plan your content themes, manage the creation process, and set milestones."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.leadership.content_calendar.channel",
|
||||
"translation": "Share content ideas, trending posts, blog links, and mentions in a dedicated channel."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.leadership.create_project.board",
|
||||
"translation": "Use a Kanban board to define and track your project tasks and progress."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.leadership.create_project.channel",
|
||||
"translation": "Chat with your team about your new project and decide how you’re going to structure it, in a collaborative channel."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.leadership.create_project.integration",
|
||||
"translation": "Increase productivity in your channel by integrating your most commonly used tools. These will be downloaded for you."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.leadership.goals_and_okrs.board",
|
||||
"translation": "Track your team's progress toward organizational goals with the Goals and OKR board. Keep meetings on track with the Meeting Agenda board."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.leadership.goals_and_okrs.channel",
|
||||
"translation": "Chat about your goals and progress with your team, async or real-time, and stay up to date with changes in a single channel."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.leadership.goals_and_okrs.integration",
|
||||
"translation": "Increase productivity in your channel by integrating your most commonly used tools, such as Zoom, to facilitate easy collaboration. These will be downloaded for you."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.leadership.incident_resolution.description.board",
|
||||
"translation": "Use the Incident Resolution board to support repeatable processes and assign defined tasks across the team."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.leadership.incident_resolution.description.channel",
|
||||
"translation": "Chat with your team about priorities, add stakeholders, provide updates, and work toward a resolution in a single channel."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.leadership.incident_resolution.description.playbook",
|
||||
"translation": "Use checklists and automation to bring in key team members, and share how your incident is tracking toward resolution."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.marketing.content_calendar.board",
|
||||
"translation": "Use the Content Calendar boad to track ideas, plan your content themes, manage the creation process, and set milestones."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.marketing.content_calendar.channel",
|
||||
"translation": "Share content ideas, trending posts, blog links, and mentions in a dedicated channel."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.marketing.create_project.board",
|
||||
"translation": "Use a Kanban board to define and track your project tasks and progress."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.marketing.create_project.channel",
|
||||
"translation": "Chat with your team about your new project and decide how you’re going to structure it, in a collaborative channel."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.marketing.create_project.integration",
|
||||
"translation": "Increase productivity in your channel by integrating your most commonly used tools. These will be downloaded for you."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.marketing.goals_and_okrs.board",
|
||||
"translation": "Track your team's progress toward organizational goals with the Goals and OKR board. Keep meetings on track with the Meeting Agenda board."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.marketing.goals_and_okrs.channel",
|
||||
"translation": "Chat about your goals and progress with your team, async or real-time, and stay up to date with changes in a single channel."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.marketing.goals_and_okrs.integration",
|
||||
"translation": "Increase productivity in your channel by integrating your most commonly used tools, such as Zoom, to facilitate easy collaboration. These will be downloaded for you."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.marketing.product_release.board",
|
||||
"translation": "Use the Product Release board to support your release timeframe and process, ensuring everyone knows which tasks are due."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.marketing.product_release.channel",
|
||||
"translation": "Chat with your team about daily milestones, any blockers, and changes to deliverables, easily and quickly."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.marketing.product_release.playbook",
|
||||
"translation": "Create repeatable workflows that are easy to follow and implement so product releases are reliable and on time."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.other.create_project.board",
|
||||
"translation": "Use a Kanban board to define and track your project tasks and progress."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.other.create_project.channel",
|
||||
"translation": "Chat with your team about your new project and decide how you’re going to structure it, in a collaborative channel."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.other.create_project.integration",
|
||||
"translation": "Increase productivity in your channel by integrating your most commonly used tools. These will be downloaded for you."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.other.feature_release.description.board",
|
||||
"translation": "Keep meetings on track with the Meeting Agenda board. Manage your workload with the Project Tasks board."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.other.feature_release.description.channel",
|
||||
"translation": "Chat with your team about any release blockers and changes in a channel that connects easily with your boards, playbooks and other integrations."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.other.feature_release.description.integration",
|
||||
"translation": "Increase productivity in your channel by integrating your most commonly used tools to support your feature release, such as GitHub. These will be downloaded for you."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.other.feature_release.description.playbook",
|
||||
"translation": "Boost cross-functional team collaboration with task checklists and automation that support your feature development process. When you’re done, run a retrospective and make improvements for your next release."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.other.goals_and_okrs.board",
|
||||
"translation": "Track your team's progress toward organizational goals with the Goals and OKR board. Keep meetings on track with the Meeting Agenda board."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.other.goals_and_okrs.channel",
|
||||
"translation": "Chat about your goals and progress with your team, async or real-time, and stay up to date with changes in a single channel."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.other.goals_and_okrs.integration",
|
||||
"translation": "Increase productivity in your channel by integrating your most commonly used tools, such as Zoom, to facilitate easy collaboration. These will be downloaded for you."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.other.incident_resolution.description.board",
|
||||
"translation": "Use the Incident Resolution board to support repeatable processes and assign defined tasks across the team."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.other.incident_resolution.description.channel",
|
||||
"translation": "Chat with your team about priorities, add stakeholders, provide updates, and work toward a resolution in a single channel."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.other.incident_resolution.description.playbook",
|
||||
"translation": "Use checklists and automation to bring in key team members, and share how your incident is tracking toward resolution."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.other.product_release.board",
|
||||
"translation": "Use the Product Release board to support your release timeframe and process, ensuring everyone knows which tasks are due."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.other.product_release.channel",
|
||||
"translation": "Chat with your team about daily milestones, any blockers, and changes to deliverables, easily and quickly."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.other.product_release.playbook",
|
||||
"translation": "Create repeatable workflows that are easy to follow and implement so product releases are reliable and on time."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.product_teams.bug_bash.channel",
|
||||
"translation": "Plan and manage bug reports and resolutions in a single channel, that’s easily accessible to your team and organization."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.product_teams.bug_bash.integration",
|
||||
"translation": "Increase productivity in your channel by integrating your most commonly used tools, such as Jira, to track your bug bash progress. These will be downloaded for you."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.product_teams.bug_bash.playbook",
|
||||
"translation": "Use checklists to assign testing areas and automated tasks to run a comprehensive bug bash process. Use a retrospective to review your process and improve it for next time."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.product_teams.feature_release.description.board",
|
||||
"translation": "Keep meetings on track with the Meeting Agenda board. Manage your workload with the Project Tasks board."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.product_teams.feature_release.description.channel",
|
||||
"translation": "Chat with your team about any release blockers and changes in a channel that connects easily with your boards, playbooks and other integrations."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.product_teams.feature_release.description.integration",
|
||||
"translation": "Increase productivity in your channel by integrating your most commonly used tools to support your feature release, such as GitHub. These will be downloaded for you."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.product_teams.feature_release.description.playbook",
|
||||
"translation": "Boost cross-functional team collaboration with task checklists and automation that support your feature development process. When you’re done, run a retrospective and make improvements for your next release."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.product_teams.goals_and_okrs.board",
|
||||
"translation": "Track your team's progress toward organizational goals with the Goals and OKR board. Keep meetings on track with the Meeting Agenda board."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.product_teams.goals_and_okrs.channel",
|
||||
"translation": "Chat about your goals and progress with your team, async or real-time, and stay up to date with changes in a single channel."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.product_teams.goals_and_okrs.integration",
|
||||
"translation": "Increase productivity in your channel by integrating your most commonly used tools, such as Zoom, to facilitate easy collaboration. These will be downloaded for you."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.product_teams.product_roadmap.board",
|
||||
"translation": "Use the Product Roadmap board to manage user feedback, assign resources, view deliverables in a calendar view, and prioritize issues."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.product_teams.product_roadmap.channel",
|
||||
"translation": "Chat with your team about your customers' feedback, prioritization, and get aligned on progress together."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.product_teams.sprint_planning.board",
|
||||
"translation": "Track your team's progress toward weekly goals with sprint breakdowns, prioritization, owner assignment, and comments."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.product_teams.sprint_planning.channel",
|
||||
"translation": "Chat with your team in a channel that connects easily with your boards and integrations."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.product_teams.sprint_planning.integration",
|
||||
"translation": "Increase productivity in your channel by integrating your most commonly used tools, such as Zoom. These will be downloaded for you."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.project_management.create_project.board",
|
||||
"translation": "Use a Kanban board to define and track your project tasks and progress."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.project_management.create_project.channel",
|
||||
"translation": "Chat with your team about your new project and decide how you’re going to structure it, in a collaborative channel."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.project_management.create_project.integration",
|
||||
"translation": "Increase productivity in your channel by integrating your most commonly used tools. These will be downloaded for you."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.project_management.feature_release.description.board",
|
||||
"translation": "Keep meetings on track with the Meeting Agenda board. Manage your workload with the Project Tasks board."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.project_management.feature_release.description.channel",
|
||||
"translation": "Chat with your team about any release blockers and changes in a channel that connects easily with your boards, playbooks and other integrations."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.project_management.feature_release.description.integration",
|
||||
"translation": "Increase productivity in your channel by integrating your most commonly used tools to support your feature release, such as GitHub. These will be downloaded for you."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.project_management.feature_release.description.playbook",
|
||||
"translation": "Boost cross-functional team collaboration with task checklists and automation that support your feature development process. When you’re done, run a retrospective and make improvements for your next release."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.project_management.goals_and_okrs.board",
|
||||
"translation": "Track your team's progress toward organizational goals with the Goals and OKR board. Keep meetings on track with the Meeting Agenda board."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.project_management.goals_and_okrs.channel",
|
||||
"translation": "Chat about your goals and progress with your team, async or real-time, and stay up to date with changes in a single channel."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.project_management.goals_and_okrs.integration",
|
||||
"translation": "Increase productivity in your channel by integrating your most commonly used tools, such as Zoom, to facilitate easy collaboration. These will be downloaded for you."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.project_management.product_release.board",
|
||||
"translation": "Use the Product Release board to support your release timeframe and process, ensuring everyone knows which tasks are due."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.project_management.product_release.channel",
|
||||
"translation": "Chat with your team about daily milestones, any blockers, and changes to deliverables, easily and quickly."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.project_management.product_release.playbook",
|
||||
"translation": "Create repeatable workflows that are easy to follow and implement so product releases are reliable and on time."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.project_management.product_roadmap.board",
|
||||
"translation": "Use the Product Roadmap board to manage user feedback, assign resources, view deliverables in a calendar view, and prioritize issues."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.project_management.product_roadmap.channel",
|
||||
"translation": "Chat with your team about your customers' feedback, prioritization, and get aligned on progress together."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.qa.bug_bash.channel",
|
||||
"translation": "Plan and manage bug reports and resolutions in a single channel, that’s easily accessible to your team and organization."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.qa.bug_bash.integration",
|
||||
"translation": "Increase productivity in your channel by integrating your most commonly used tools, such as Jira, to track your bug bash progress. These will be downloaded for you."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.qa.bug_bash.playbook",
|
||||
"translation": "Use checklists to assign testing areas and automated tasks to run a comprehensive bug bash process. Use a retrospective to review your process and improve it for next time."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.qa.create_project.board",
|
||||
"translation": "Use a Kanban board to define and track your project tasks and progress."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.qa.create_project.channel",
|
||||
"translation": "Chat with your team about your new project and decide how you’re going to structure it, in a collaborative channel."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.qa.create_project.integration",
|
||||
"translation": "Increase productivity in your channel by integrating your most commonly used tools. These will be downloaded for you."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.qa.incident_resolution.description.board",
|
||||
"translation": "Use the Incident Resolution board to support repeatable processes and assign defined tasks across the team."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.qa.incident_resolution.description.channel",
|
||||
"translation": "Chat with your team about priorities, add stakeholders, provide updates, and work toward a resolution in a single channel."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.qa.incident_resolution.description.playbook",
|
||||
"translation": "Use checklists and automation to bring in key team members, and share how your incident is tracking toward resolution."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.qa.product_release.board",
|
||||
"translation": "Use the Product Release board to support your release timeframe and process, ensuring everyone knows which tasks are due."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.qa.product_release.channel",
|
||||
"translation": "Chat with your team about daily milestones, any blockers, and changes to deliverables, easily and quickly."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.qa.product_release.playbook",
|
||||
"translation": "Create repeatable workflows that are easy to follow and implement so product releases are reliable and on time."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.qa.sprint_planning.board",
|
||||
"translation": "Track your team's progress toward weekly goals with sprint breakdowns, prioritization, owner assignment, and comments."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.qa.sprint_planning.channel",
|
||||
"translation": "Chat with your team in a channel that connects easily with your boards and integrations."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.qa.sprint_planning.integration",
|
||||
"translation": "Increase productivity in your channel by integrating your most commonly used tools, such as Zoom. These will be downloaded for you."
|
||||
}
|
||||
]
|
||||
|
||||
@@ -8773,37 +8773,6 @@ func (c *Client4) CheckCWSConnection(userId string) (*Response, error) {
|
||||
return BuildResponse(r), nil
|
||||
}
|
||||
|
||||
// Worktemplates sections
|
||||
|
||||
func (c *Client4) worktemplatesRoute() string {
|
||||
return "/worktemplates"
|
||||
}
|
||||
|
||||
// GetWorktemplateCategories returns categories of worktemplates
|
||||
func (c *Client4) GetWorktemplateCategories() ([]*WorkTemplateCategory, *Response, error) {
|
||||
r, err := c.DoAPIGet(c.worktemplatesRoute()+"/categories", "")
|
||||
if err != nil {
|
||||
return nil, BuildResponse(r), err
|
||||
}
|
||||
defer closeBody(r)
|
||||
|
||||
var categories []*WorkTemplateCategory
|
||||
err = json.NewDecoder(r.Body).Decode(&categories)
|
||||
return categories, BuildResponse(r), err
|
||||
}
|
||||
|
||||
func (c *Client4) GetWorkTemplatesByCategory(category string) ([]*WorkTemplate, *Response, error) {
|
||||
r, err := c.DoAPIGet(c.worktemplatesRoute()+"/categories/"+category+"/templates", "")
|
||||
if err != nil {
|
||||
return nil, BuildResponse(r), err
|
||||
}
|
||||
defer closeBody(r)
|
||||
|
||||
var templates []*WorkTemplate
|
||||
err = json.NewDecoder(r.Body).Decode(&templates)
|
||||
return templates, BuildResponse(r), err
|
||||
}
|
||||
|
||||
func (c *Client4) SubmitTrueUpReview(req map[string]any) (*Response, error) {
|
||||
reqBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
|
||||
@@ -54,8 +54,6 @@ type FeatureFlags struct {
|
||||
// A/B Test on posting a welcome message
|
||||
SendWelcomePost bool
|
||||
|
||||
WorkTemplate bool
|
||||
|
||||
PostPriority bool
|
||||
|
||||
// Enable WYSIWYG text editor
|
||||
@@ -101,7 +99,6 @@ func (f *FeatureFlags) SetDefaults() {
|
||||
f.SendWelcomePost = true
|
||||
f.PostPriority = true
|
||||
f.PeopleProduct = false
|
||||
f.WorkTemplate = false
|
||||
f.ReduceOnBoardingTaskList = false
|
||||
f.ThreadsEverywhere = false
|
||||
f.GlobalDrafts = true
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
// CompleteOnboardingRequest describes parameters of the requested plugin.
|
||||
type CompleteOnboardingRequest struct {
|
||||
Organization string `json:"organization"` // Organization is the name of the organization
|
||||
Role string `json:"role"` // Role is the role selected by first admin
|
||||
InstallPlugins []string `json:"install_plugins"` // InstallPlugins is a list of plugins to be installed
|
||||
}
|
||||
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
const (
|
||||
// used to assign the work template id to newly created channels
|
||||
WorkTemplateIDChannelProp = "work_template_id"
|
||||
|
||||
// Visibility
|
||||
WorkTemplateVisibilityPublic = "public"
|
||||
WorkTemplateVisibilityPrivate = "private"
|
||||
)
|
||||
|
||||
type WorkTemplateCategory struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type WorkTemplate struct {
|
||||
ID string `json:"id"`
|
||||
Category string `json:"category"`
|
||||
UseCase string `json:"useCase"`
|
||||
Illustration string `json:"illustration"`
|
||||
Visibility string `json:"visibility"`
|
||||
FeatureFlag *WorkTemplateFeatureFlag `json:"featureFlag,omitempty"`
|
||||
Description Description `json:"description"`
|
||||
Content []WorkTemplateContent `json:"content"`
|
||||
}
|
||||
|
||||
type WorkTemplateFeatureFlag struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type DescriptionContent struct {
|
||||
Message string `json:"message"`
|
||||
Illustration string `json:"illustration"`
|
||||
}
|
||||
|
||||
type Description struct {
|
||||
Channel *DescriptionContent `json:"channel"`
|
||||
Board *DescriptionContent `json:"board"`
|
||||
Playbook *DescriptionContent `json:"playbook"`
|
||||
Integration *DescriptionContent `json:"integration"`
|
||||
}
|
||||
|
||||
type WorkTemplateChannel struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Purpose string `json:"purpose"`
|
||||
Playbook string `json:"playbook"`
|
||||
Illustration string `json:"illustration"`
|
||||
}
|
||||
|
||||
type WorkTemplateBoard struct {
|
||||
ID string `json:"id"`
|
||||
Template string `json:"template"`
|
||||
Name string `json:"name"`
|
||||
Channel string `json:"channel"`
|
||||
Illustration string `json:"illustration"`
|
||||
}
|
||||
|
||||
type WorkTemplatePlaybook struct {
|
||||
Template string `json:"template"`
|
||||
Name string `json:"name"`
|
||||
ID string `json:"id"`
|
||||
Illustration string `json:"illustration"`
|
||||
}
|
||||
|
||||
type WorkTemplateIntegration struct {
|
||||
ID string `json:"id"`
|
||||
Recommended bool `json:"recommended"`
|
||||
}
|
||||
|
||||
type WorkTemplateContent struct {
|
||||
Channel *WorkTemplateChannel `json:"channel,omitempty"`
|
||||
Board *WorkTemplateBoard `json:"board,omitempty"`
|
||||
Playbook *WorkTemplatePlaybook `json:"playbook,omitempty"`
|
||||
Integration *WorkTemplateIntegration `json:"integration,omitempty"`
|
||||
}
|
||||
|
||||
type WorkTemplateExecutionResult struct {
|
||||
ChannelWithPlaybookIDs []string `json:"channel_with_playbook_ids"`
|
||||
ChannelIDs []string `json:"channel_ids"`
|
||||
}
|
||||
Ссылка в новой задаче
Block a user