Merge branch 'master' into MM-50966-in-product-expansion-backend
Этот коммит содержится в:
@@ -72,7 +72,6 @@ func (a *API) handleGetBlocks(w http.ResponseWriter, r *http.Request) {
|
||||
query := r.URL.Query()
|
||||
parentID := query.Get("parent_id")
|
||||
blockType := query.Get("type")
|
||||
all := query.Get("all")
|
||||
blockID := query.Get("block_id")
|
||||
boardID := mux.Vars(r)["boardID"]
|
||||
|
||||
@@ -122,18 +121,11 @@ func (a *API) handleGetBlocks(w http.ResponseWriter, r *http.Request) {
|
||||
auditRec.AddMeta("boardID", boardID)
|
||||
auditRec.AddMeta("parentID", parentID)
|
||||
auditRec.AddMeta("blockType", blockType)
|
||||
auditRec.AddMeta("all", all)
|
||||
auditRec.AddMeta("blockID", blockID)
|
||||
|
||||
var blocks []*model.Block
|
||||
var block *model.Block
|
||||
switch {
|
||||
case all != "":
|
||||
blocks, err = a.app.GetBlocksForBoard(boardID)
|
||||
if err != nil {
|
||||
a.errorResponse(w, r, err)
|
||||
return
|
||||
}
|
||||
case blockID != "":
|
||||
block, err = a.app.GetBlockByID(blockID)
|
||||
if err != nil {
|
||||
@@ -148,7 +140,12 @@ func (a *API) handleGetBlocks(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
blocks = append(blocks, block)
|
||||
default:
|
||||
blocks, err = a.app.GetBlocks(boardID, parentID, blockType)
|
||||
opts := model.QueryBlocksOptions{
|
||||
BoardID: boardID,
|
||||
ParentID: parentID,
|
||||
BlockType: model.BlockType(blockType),
|
||||
}
|
||||
blocks, err = a.app.GetBlocks(opts)
|
||||
if err != nil {
|
||||
a.errorResponse(w, r, err)
|
||||
return
|
||||
|
||||
@@ -18,20 +18,11 @@ import (
|
||||
|
||||
var ErrBlocksFromMultipleBoards = errors.New("the block set contain blocks from multiple boards")
|
||||
|
||||
func (a *App) GetBlocks(boardID, parentID string, blockType string) ([]*model.Block, error) {
|
||||
if boardID == "" {
|
||||
func (a *App) GetBlocks(opts model.QueryBlocksOptions) ([]*model.Block, error) {
|
||||
if opts.BoardID == "" {
|
||||
return []*model.Block{}, nil
|
||||
}
|
||||
|
||||
if blockType != "" && parentID != "" {
|
||||
return a.store.GetBlocksWithParentAndType(boardID, parentID, blockType)
|
||||
}
|
||||
|
||||
if blockType != "" {
|
||||
return a.store.GetBlocksWithType(boardID, blockType)
|
||||
}
|
||||
|
||||
return a.store.GetBlocksWithParent(boardID, parentID)
|
||||
return a.store.GetBlocks(opts)
|
||||
}
|
||||
|
||||
func (a *App) DuplicateBlock(boardID string, blockID string, userID string, asTemplate bool) ([]*model.Block, error) {
|
||||
@@ -514,10 +505,6 @@ func (a *App) GetBlockCountsByType() (map[string]int64, error) {
|
||||
return a.store.GetBlockCountsByType()
|
||||
}
|
||||
|
||||
func (a *App) GetBlocksForBoard(boardID string) ([]*model.Block, error) {
|
||||
return a.store.GetBlocksForBoard(boardID)
|
||||
}
|
||||
|
||||
func (a *App) notifyBlockChanged(action notify.Action, block *model.Block, oldBlock *model.Block, modifiedByID string) {
|
||||
// don't notify if notifications service disabled, or block change is generated via system user.
|
||||
if a.notifications == nil || modifiedByID == model.SystemUserID {
|
||||
|
||||
@@ -207,10 +207,17 @@ func TestIsWithinViewsLimit(t *testing.T) {
|
||||
Views: mm_model.NewInt(2),
|
||||
},
|
||||
}
|
||||
|
||||
opts := model.QueryBlocksOptions{
|
||||
BoardID: "board_id",
|
||||
ParentID: "parent_id",
|
||||
BlockType: model.BlockType("view"),
|
||||
}
|
||||
|
||||
th.Store.EXPECT().GetCloudLimits().Return(cloudLimit, nil)
|
||||
th.Store.EXPECT().GetUsedCardsCount().Return(1, nil)
|
||||
th.Store.EXPECT().GetCardLimitTimestamp().Return(int64(1), nil)
|
||||
th.Store.EXPECT().GetBlocksWithParentAndType("board_id", "parent_id", "view").Return([]*model.Block{{}}, nil)
|
||||
th.Store.EXPECT().GetBlocks(opts).Return([]*model.Block{{}}, nil)
|
||||
|
||||
withinLimits, err := th.App.isWithinViewsLimit("board_id", &model.Block{ParentID: "parent_id"})
|
||||
assert.NoError(t, err)
|
||||
@@ -225,10 +232,17 @@ func TestIsWithinViewsLimit(t *testing.T) {
|
||||
Views: mm_model.NewInt(1),
|
||||
},
|
||||
}
|
||||
|
||||
opts := model.QueryBlocksOptions{
|
||||
BoardID: "board_id",
|
||||
ParentID: "parent_id",
|
||||
BlockType: model.BlockType("view"),
|
||||
}
|
||||
|
||||
th.Store.EXPECT().GetCloudLimits().Return(cloudLimit, nil)
|
||||
th.Store.EXPECT().GetUsedCardsCount().Return(1, nil)
|
||||
th.Store.EXPECT().GetCardLimitTimestamp().Return(int64(1), nil)
|
||||
th.Store.EXPECT().GetBlocksWithParentAndType("board_id", "parent_id", "view").Return([]*model.Block{{}}, nil)
|
||||
th.Store.EXPECT().GetBlocks(opts).Return([]*model.Block{{}}, nil)
|
||||
|
||||
withinLimits, err := th.App.isWithinViewsLimit("board_id", &model.Block{ParentID: "parent_id"})
|
||||
assert.NoError(t, err)
|
||||
@@ -243,10 +257,17 @@ func TestIsWithinViewsLimit(t *testing.T) {
|
||||
Views: mm_model.NewInt(2),
|
||||
},
|
||||
}
|
||||
|
||||
opts := model.QueryBlocksOptions{
|
||||
BoardID: "board_id",
|
||||
ParentID: "parent_id",
|
||||
BlockType: model.BlockType("view"),
|
||||
}
|
||||
|
||||
th.Store.EXPECT().GetCloudLimits().Return(cloudLimit, nil)
|
||||
th.Store.EXPECT().GetUsedCardsCount().Return(1, nil)
|
||||
th.Store.EXPECT().GetCardLimitTimestamp().Return(int64(1), nil)
|
||||
th.Store.EXPECT().GetBlocksWithParentAndType("board_id", "parent_id", "view").Return([]*model.Block{{}, {}, {}}, nil)
|
||||
th.Store.EXPECT().GetBlocks(opts).Return([]*model.Block{{}, {}, {}}, nil)
|
||||
|
||||
withinLimits, err := th.App.isWithinViewsLimit("board_id", &model.Block{ParentID: "parent_id"})
|
||||
assert.NoError(t, err)
|
||||
@@ -261,10 +282,17 @@ func TestIsWithinViewsLimit(t *testing.T) {
|
||||
Views: mm_model.NewInt(2),
|
||||
},
|
||||
}
|
||||
|
||||
opts := model.QueryBlocksOptions{
|
||||
BoardID: "board_id",
|
||||
ParentID: "parent_id",
|
||||
BlockType: model.BlockType("view"),
|
||||
}
|
||||
|
||||
th.Store.EXPECT().GetCloudLimits().Return(cloudLimit, nil)
|
||||
th.Store.EXPECT().GetUsedCardsCount().Return(1, nil)
|
||||
th.Store.EXPECT().GetCardLimitTimestamp().Return(int64(1), nil)
|
||||
th.Store.EXPECT().GetBlocksWithParentAndType("board_id", "parent_id", "view").Return([]*model.Block{}, nil)
|
||||
th.Store.EXPECT().GetBlocks(opts).Return([]*model.Block{}, nil)
|
||||
|
||||
withinLimits, err := th.App.isWithinViewsLimit("board_id", &model.Block{ParentID: "parent_id"})
|
||||
assert.NoError(t, err)
|
||||
@@ -333,10 +361,17 @@ func TestInsertBlocks(t *testing.T) {
|
||||
Views: mm_model.NewInt(2),
|
||||
},
|
||||
}
|
||||
|
||||
opts := model.QueryBlocksOptions{
|
||||
BoardID: "test-board-id",
|
||||
ParentID: "parent_id",
|
||||
BlockType: model.BlockType("view"),
|
||||
}
|
||||
|
||||
th.Store.EXPECT().GetCloudLimits().Return(cloudLimit, nil)
|
||||
th.Store.EXPECT().GetUsedCardsCount().Return(1, nil)
|
||||
th.Store.EXPECT().GetCardLimitTimestamp().Return(int64(1), nil)
|
||||
th.Store.EXPECT().GetBlocksWithParentAndType("test-board-id", "parent_id", "view").Return([]*model.Block{{}}, nil)
|
||||
th.Store.EXPECT().GetBlocks(opts).Return([]*model.Block{{}}, nil)
|
||||
|
||||
_, err := th.App.InsertBlocks([]*model.Block{block}, "user-id-1")
|
||||
require.NoError(t, err)
|
||||
@@ -365,10 +400,17 @@ func TestInsertBlocks(t *testing.T) {
|
||||
Views: mm_model.NewInt(2),
|
||||
},
|
||||
}
|
||||
|
||||
opts := model.QueryBlocksOptions{
|
||||
BoardID: "test-board-id",
|
||||
ParentID: "parent_id",
|
||||
BlockType: model.BlockType("view"),
|
||||
}
|
||||
|
||||
th.Store.EXPECT().GetCloudLimits().Return(cloudLimit, nil)
|
||||
th.Store.EXPECT().GetUsedCardsCount().Return(1, nil)
|
||||
th.Store.EXPECT().GetCardLimitTimestamp().Return(int64(1), nil)
|
||||
th.Store.EXPECT().GetBlocksWithParentAndType("test-board-id", "parent_id", "view").Return([]*model.Block{{}, {}}, nil)
|
||||
th.Store.EXPECT().GetBlocks(opts).Return([]*model.Block{{}, {}}, nil)
|
||||
|
||||
_, err := th.App.InsertBlocks([]*model.Block{block}, "user-id-1")
|
||||
require.Error(t, err)
|
||||
@@ -406,10 +448,17 @@ func TestInsertBlocks(t *testing.T) {
|
||||
Views: mm_model.NewInt(2),
|
||||
},
|
||||
}
|
||||
|
||||
opts := model.QueryBlocksOptions{
|
||||
BoardID: "test-board-id",
|
||||
ParentID: "parent_id",
|
||||
BlockType: model.BlockType("view"),
|
||||
}
|
||||
|
||||
th.Store.EXPECT().GetCloudLimits().Return(cloudLimit, nil).Times(2)
|
||||
th.Store.EXPECT().GetUsedCardsCount().Return(1, nil).Times(2)
|
||||
th.Store.EXPECT().GetCardLimitTimestamp().Return(int64(1), nil).Times(2)
|
||||
th.Store.EXPECT().GetBlocksWithParentAndType("test-board-id", "parent_id", "view").Return([]*model.Block{{}}, nil).Times(2)
|
||||
th.Store.EXPECT().GetBlocks(opts).Return([]*model.Block{{}}, nil).Times(2)
|
||||
|
||||
_, err := th.App.InsertBlocks([]*model.Block{view1, view2}, "user-id-1")
|
||||
require.Error(t, err)
|
||||
|
||||
@@ -86,7 +86,7 @@ func (a *App) writeArchiveBoard(zw *zip.Writer, board model.Board, opt model.Exp
|
||||
var files []string
|
||||
// write the board's blocks
|
||||
// TODO: paginate this
|
||||
blocks, err := a.GetBlocksForBoard(board.ID)
|
||||
blocks, err := a.GetBlocks(model.QueryBlocksOptions{BoardID: board.ID})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -58,6 +58,10 @@ func (a *Auth) IsValidReadToken(boardID string, readToken string) (bool, error)
|
||||
return false, err
|
||||
}
|
||||
|
||||
if !a.config.EnablePublicSharedBoards {
|
||||
return false, errors.New("public shared boards disabled")
|
||||
}
|
||||
|
||||
if sharing != nil && (sharing.ID == boardID && sharing.Enabled && sharing.Token == readToken) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ func TestCreateBoardsAndBlocks(t *testing.T) {
|
||||
require.Equal(t, "public board", board1.Title)
|
||||
require.Equal(t, model.BoardTypeOpen, board1.Type)
|
||||
require.NotEqual(t, "board-id-1", board1.ID)
|
||||
blocks1, err := th.Server.App().GetBlocksForBoard(board1.ID)
|
||||
blocks1, err := th.Server.App().GetBlocks(model.QueryBlocksOptions{BoardID: board1.ID})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, blocks1, 1)
|
||||
require.Equal(t, "block 1", blocks1[0].Title)
|
||||
@@ -147,7 +147,7 @@ func TestCreateBoardsAndBlocks(t *testing.T) {
|
||||
require.Equal(t, "private board", board2.Title)
|
||||
require.Equal(t, model.BoardTypePrivate, board2.Type)
|
||||
require.NotEqual(t, "board-id-2", board2.ID)
|
||||
blocks2, err := th.Server.App().GetBlocksForBoard(board2.ID)
|
||||
blocks2, err := th.Server.App().GetBlocks(model.QueryBlocksOptions{BoardID: board2.ID})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, blocks2, 1)
|
||||
require.Equal(t, "block 2", blocks2[0].Title)
|
||||
|
||||
@@ -62,7 +62,7 @@ func TestExportBoard(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Len(t, boardsImported, 1)
|
||||
boardImported := boardsImported[0]
|
||||
blocksImported, err := th.Server.App().GetBlocksForBoard(boardImported.ID)
|
||||
blocksImported, err := th.Server.App().GetBlocks(model.QueryBlocksOptions{BoardID: boardImported.ID})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, blocksImported, 1)
|
||||
require.Equal(t, block.Title, blocksImported[0].Title)
|
||||
|
||||
@@ -585,6 +585,35 @@ func TestPermissionsGetBoard(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestPermissionsGetBoardPublic(t *testing.T) {
|
||||
ttCases := []TestCase{
|
||||
{"/boards/{PRIVATE_BOARD_ID}?read_token=invalid", methodGet, "", userAnon, http.StatusUnauthorized, 0},
|
||||
{"/boards/{PRIVATE_BOARD_ID}?read_token=valid", methodGet, "", userAnon, http.StatusUnauthorized, 1},
|
||||
{"/boards/{PRIVATE_BOARD_ID}?read_token=invalid", methodGet, "", userNoTeamMember, http.StatusForbidden, 0},
|
||||
{"/boards/{PRIVATE_BOARD_ID}?read_token=valid", methodGet, "", userTeamMember, http.StatusForbidden, 1},
|
||||
}
|
||||
t.Run("plugin", func(t *testing.T) {
|
||||
th := SetupTestHelperPluginMode(t)
|
||||
defer th.TearDown()
|
||||
cfg := th.Server.Config()
|
||||
cfg.EnablePublicSharedBoards = false
|
||||
th.Server.UpdateAppConfig()
|
||||
clients := setupClients(th)
|
||||
testData := setupData(t, th)
|
||||
runTestCases(t, ttCases, testData, clients)
|
||||
})
|
||||
t.Run("local", func(t *testing.T) {
|
||||
th := SetupTestHelperLocalMode(t)
|
||||
defer th.TearDown()
|
||||
cfg := th.Server.Config()
|
||||
cfg.EnablePublicSharedBoards = false
|
||||
th.Server.UpdateAppConfig()
|
||||
clients := setupLocalClients(th)
|
||||
testData := setupData(t, th)
|
||||
runTestCases(t, ttCases, testData, clients)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPermissionsPatchBoard(t *testing.T) {
|
||||
ttCases := []TestCase{
|
||||
{"/boards/{PRIVATE_BOARD_ID}", methodPatch, "{\"title\": \"test\"}", userAnon, http.StatusUnauthorized, 0},
|
||||
|
||||
@@ -176,7 +176,7 @@ type QueryBlocksOptions struct {
|
||||
ParentID string // if not empty then filter for blocks belonging to specified parent
|
||||
BlockType BlockType // if not empty and not `TypeUnknown` then filter for records of specified block type
|
||||
Page int // page number to select when paginating
|
||||
PerPage int // number of blocks per page (default=-1, meaning unlimited)
|
||||
PerPage int // number of blocks per page (default=0, meaning unlimited)
|
||||
}
|
||||
|
||||
// QuerySubtreeOptions are query options that can be passed to GetSubTree methods.
|
||||
|
||||
@@ -536,66 +536,6 @@ func (mr *MockStoreMockRecorder) GetBlocksComplianceHistory(arg0 interface{}) *g
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBlocksComplianceHistory", reflect.TypeOf((*MockStore)(nil).GetBlocksComplianceHistory), arg0)
|
||||
}
|
||||
|
||||
// GetBlocksForBoard mocks base method.
|
||||
func (m *MockStore) GetBlocksForBoard(arg0 string) ([]*model0.Block, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetBlocksForBoard", arg0)
|
||||
ret0, _ := ret[0].([]*model0.Block)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetBlocksForBoard indicates an expected call of GetBlocksForBoard.
|
||||
func (mr *MockStoreMockRecorder) GetBlocksForBoard(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBlocksForBoard", reflect.TypeOf((*MockStore)(nil).GetBlocksForBoard), arg0)
|
||||
}
|
||||
|
||||
// GetBlocksWithParent mocks base method.
|
||||
func (m *MockStore) GetBlocksWithParent(arg0, arg1 string) ([]*model0.Block, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetBlocksWithParent", arg0, arg1)
|
||||
ret0, _ := ret[0].([]*model0.Block)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetBlocksWithParent indicates an expected call of GetBlocksWithParent.
|
||||
func (mr *MockStoreMockRecorder) GetBlocksWithParent(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBlocksWithParent", reflect.TypeOf((*MockStore)(nil).GetBlocksWithParent), arg0, arg1)
|
||||
}
|
||||
|
||||
// GetBlocksWithParentAndType mocks base method.
|
||||
func (m *MockStore) GetBlocksWithParentAndType(arg0, arg1, arg2 string) ([]*model0.Block, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetBlocksWithParentAndType", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].([]*model0.Block)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetBlocksWithParentAndType indicates an expected call of GetBlocksWithParentAndType.
|
||||
func (mr *MockStoreMockRecorder) GetBlocksWithParentAndType(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBlocksWithParentAndType", reflect.TypeOf((*MockStore)(nil).GetBlocksWithParentAndType), arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// GetBlocksWithType mocks base method.
|
||||
func (m *MockStore) GetBlocksWithType(arg0, arg1 string) ([]*model0.Block, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetBlocksWithType", arg0, arg1)
|
||||
ret0, _ := ret[0].([]*model0.Block)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetBlocksWithType indicates an expected call of GetBlocksWithType.
|
||||
func (mr *MockStoreMockRecorder) GetBlocksWithType(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBlocksWithType", reflect.TypeOf((*MockStore)(nil).GetBlocksWithType), arg0, arg1)
|
||||
}
|
||||
|
||||
// GetBoard mocks base method.
|
||||
func (m *MockStore) GetBoard(arg0 string) (*model0.Board, error) {
|
||||
m.ctrl.T.Helper()
|
||||
|
||||
@@ -105,23 +105,6 @@ func (s *SQLStore) getBlocks(db sq.BaseRunner, opts model.QueryBlocksOptions) ([
|
||||
return s.blocksFromRows(rows)
|
||||
}
|
||||
|
||||
func (s *SQLStore) getBlocksWithParentAndType(db sq.BaseRunner, boardID, parentID string, blockType string) ([]*model.Block, error) {
|
||||
opts := model.QueryBlocksOptions{
|
||||
BoardID: boardID,
|
||||
ParentID: parentID,
|
||||
BlockType: model.BlockType(blockType),
|
||||
}
|
||||
return s.getBlocks(db, opts)
|
||||
}
|
||||
|
||||
func (s *SQLStore) getBlocksWithParent(db sq.BaseRunner, boardID, parentID string) ([]*model.Block, error) {
|
||||
opts := model.QueryBlocksOptions{
|
||||
BoardID: boardID,
|
||||
ParentID: parentID,
|
||||
}
|
||||
return s.getBlocks(db, opts)
|
||||
}
|
||||
|
||||
func (s *SQLStore) getBlocksByIDs(db sq.BaseRunner, ids []string) ([]*model.Block, error) {
|
||||
query := s.getQueryBuilder(db).
|
||||
Select(s.blockFields("")...).
|
||||
@@ -148,14 +131,6 @@ func (s *SQLStore) getBlocksByIDs(db sq.BaseRunner, ids []string) ([]*model.Bloc
|
||||
return blocks, nil
|
||||
}
|
||||
|
||||
func (s *SQLStore) getBlocksWithType(db sq.BaseRunner, boardID, blockType string) ([]*model.Block, error) {
|
||||
opts := model.QueryBlocksOptions{
|
||||
BoardID: boardID,
|
||||
BlockType: model.BlockType(blockType),
|
||||
}
|
||||
return s.getBlocks(db, opts)
|
||||
}
|
||||
|
||||
// getSubTree2 returns blocks within 2 levels of the given blockID.
|
||||
func (s *SQLStore) getSubTree2(db sq.BaseRunner, boardID string, blockID string, opts model.QuerySubtreeOptions) ([]*model.Block, error) {
|
||||
query := s.getQueryBuilder(db).
|
||||
@@ -188,13 +163,6 @@ func (s *SQLStore) getSubTree2(db sq.BaseRunner, boardID string, blockID string,
|
||||
return s.blocksFromRows(rows)
|
||||
}
|
||||
|
||||
func (s *SQLStore) getBlocksForBoard(db sq.BaseRunner, boardID string) ([]*model.Block, error) {
|
||||
opts := model.QueryBlocksOptions{
|
||||
BoardID: boardID,
|
||||
}
|
||||
return s.getBlocks(db, opts)
|
||||
}
|
||||
|
||||
func (s *SQLStore) blocksFromRows(rows *sql.Rows) ([]*model.Block, error) {
|
||||
results := []*model.Block{}
|
||||
|
||||
|
||||
@@ -166,7 +166,7 @@ func (s *SQLStore) duplicateBoard(db sq.BaseRunner, boardID string, userID strin
|
||||
}
|
||||
|
||||
bab.Boards = []*model.Board{board}
|
||||
blocks, err := s.getBlocksForBoard(db, boardID)
|
||||
blocks, err := s.getBlocks(db, model.QueryBlocksOptions{BoardID: boardID})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
@@ -326,26 +326,6 @@ func (s *SQLStore) GetBlocksComplianceHistory(opts model.QueryBlocksComplianceHi
|
||||
|
||||
}
|
||||
|
||||
func (s *SQLStore) GetBlocksForBoard(boardID string) ([]*model.Block, error) {
|
||||
return s.getBlocksForBoard(s.db, boardID)
|
||||
|
||||
}
|
||||
|
||||
func (s *SQLStore) GetBlocksWithParent(boardID string, parentID string) ([]*model.Block, error) {
|
||||
return s.getBlocksWithParent(s.db, boardID, parentID)
|
||||
|
||||
}
|
||||
|
||||
func (s *SQLStore) GetBlocksWithParentAndType(boardID string, parentID string, blockType string) ([]*model.Block, error) {
|
||||
return s.getBlocksWithParentAndType(s.db, boardID, parentID, blockType)
|
||||
|
||||
}
|
||||
|
||||
func (s *SQLStore) GetBlocksWithType(boardID string, blockType string) ([]*model.Block, error) {
|
||||
return s.getBlocksWithType(s.db, boardID, blockType)
|
||||
|
||||
}
|
||||
|
||||
func (s *SQLStore) GetBoard(id string) (*model.Board, error) {
|
||||
return s.getBoard(s.db, id)
|
||||
|
||||
|
||||
@@ -18,12 +18,8 @@ const CardLimitTimestampSystemKey = "card_limit_timestamp"
|
||||
// Store represents the abstraction of the data storage.
|
||||
type Store interface {
|
||||
GetBlocks(opts model.QueryBlocksOptions) ([]*model.Block, error)
|
||||
GetBlocksWithParentAndType(boardID, parentID string, blockType string) ([]*model.Block, error)
|
||||
GetBlocksWithParent(boardID, parentID string) ([]*model.Block, error)
|
||||
GetBlocksByIDs(ids []string) ([]*model.Block, error)
|
||||
GetBlocksWithType(boardID, blockType string) ([]*model.Block, error)
|
||||
GetSubTree2(boardID, blockID string, opts model.QuerySubtreeOptions) ([]*model.Block, error)
|
||||
GetBlocksForBoard(boardID string) ([]*model.Block, error)
|
||||
// @withTransaction
|
||||
InsertBlock(block *model.Block, userID string) error
|
||||
// @withTransaction
|
||||
|
||||
@@ -69,7 +69,7 @@ func testInsertBlock(t *testing.T, store store.Store) {
|
||||
userID := testUserID
|
||||
boardID := testBoardID
|
||||
|
||||
blocks, errBlocks := store.GetBlocksForBoard(boardID)
|
||||
blocks, errBlocks := store.GetBlocks(model.QueryBlocksOptions{BoardID: boardID})
|
||||
require.NoError(t, errBlocks)
|
||||
initialCount := len(blocks)
|
||||
|
||||
@@ -85,7 +85,7 @@ func testInsertBlock(t *testing.T, store store.Store) {
|
||||
err := store.InsertBlock(block, "user-id-1")
|
||||
require.NoError(t, err)
|
||||
|
||||
blocks, err := store.GetBlocksForBoard(boardID)
|
||||
blocks, err := store.GetBlocks(model.QueryBlocksOptions{BoardID: boardID})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, blocks, initialCount+1)
|
||||
|
||||
@@ -105,7 +105,7 @@ func testInsertBlock(t *testing.T, store store.Store) {
|
||||
err := store.InsertBlock(block, "user-id-1")
|
||||
require.Error(t, err)
|
||||
|
||||
blocks, err := store.GetBlocksForBoard(boardID)
|
||||
blocks, err := store.GetBlocks(model.QueryBlocksOptions{BoardID: boardID})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, blocks, initialCount+1)
|
||||
})
|
||||
@@ -121,7 +121,7 @@ func testInsertBlock(t *testing.T, store store.Store) {
|
||||
err := store.InsertBlock(block, "user-id-1")
|
||||
require.Error(t, err)
|
||||
|
||||
blocks, err := store.GetBlocksForBoard(boardID)
|
||||
blocks, err := store.GetBlocks(model.QueryBlocksOptions{BoardID: boardID})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, blocks, initialCount+1)
|
||||
})
|
||||
@@ -204,7 +204,7 @@ func testInsertBlock(t *testing.T, store store.Store) {
|
||||
func testInsertBlocks(t *testing.T, store store.Store) {
|
||||
userID := testUserID
|
||||
|
||||
blocks, errBlocks := store.GetBlocksForBoard("id-test")
|
||||
blocks, errBlocks := store.GetBlocks(model.QueryBlocksOptions{BoardID: "id-test"})
|
||||
require.NoError(t, errBlocks)
|
||||
initialCount := len(blocks)
|
||||
|
||||
@@ -227,7 +227,7 @@ func testInsertBlocks(t *testing.T, store store.Store) {
|
||||
err := store.InsertBlocks(newBlocks, "user-id-1")
|
||||
require.Error(t, err)
|
||||
|
||||
blocks, err := store.GetBlocksForBoard("id-test")
|
||||
blocks, err := store.GetBlocks(model.QueryBlocksOptions{BoardID: "id-test"})
|
||||
require.NoError(t, err)
|
||||
// no blocks should have been inserted
|
||||
require.Len(t, blocks, initialCount)
|
||||
@@ -249,7 +249,7 @@ func testPatchBlock(t *testing.T, store store.Store) {
|
||||
err := store.InsertBlock(block, "user-id-1")
|
||||
require.NoError(t, err)
|
||||
|
||||
blocks, errBlocks := store.GetBlocksForBoard(boardID)
|
||||
blocks, errBlocks := store.GetBlocks(model.QueryBlocksOptions{BoardID: boardID})
|
||||
require.NoError(t, errBlocks)
|
||||
initialCount := len(blocks)
|
||||
|
||||
@@ -259,7 +259,7 @@ func testPatchBlock(t *testing.T, store store.Store) {
|
||||
require.ErrorAs(t, err, &nf)
|
||||
require.True(t, model.IsErrNotFound(err))
|
||||
|
||||
blocks, err := store.GetBlocksForBoard(boardID)
|
||||
blocks, err := store.GetBlocks(model.QueryBlocksOptions{BoardID: boardID})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, blocks, initialCount)
|
||||
})
|
||||
@@ -272,7 +272,7 @@ func testPatchBlock(t *testing.T, store store.Store) {
|
||||
err := store.PatchBlock("id-test", blockPatch, "user-id-1")
|
||||
require.Error(t, err)
|
||||
|
||||
blocks, err := store.GetBlocksForBoard(boardID)
|
||||
blocks, err := store.GetBlocks(model.QueryBlocksOptions{BoardID: boardID})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, blocks, initialCount)
|
||||
})
|
||||
@@ -452,7 +452,7 @@ var (
|
||||
|
||||
func testGetSubTree2(t *testing.T, store store.Store) {
|
||||
boardID := testBoardID
|
||||
blocks, err := store.GetBlocksForBoard(boardID)
|
||||
blocks, err := store.GetBlocks(model.QueryBlocksOptions{BoardID: boardID})
|
||||
require.NoError(t, err)
|
||||
initialCount := len(blocks)
|
||||
|
||||
@@ -460,7 +460,7 @@ func testGetSubTree2(t *testing.T, store store.Store) {
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
defer DeleteBlocks(t, store, subtreeSampleBlocks, "test")
|
||||
|
||||
blocks, err = store.GetBlocksForBoard(boardID)
|
||||
blocks, err = store.GetBlocks(model.QueryBlocksOptions{BoardID: boardID})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, blocks, initialCount+6)
|
||||
|
||||
@@ -492,7 +492,7 @@ func testDeleteBlock(t *testing.T, store store.Store) {
|
||||
userID := testUserID
|
||||
boardID := testBoardID
|
||||
|
||||
blocks, err := store.GetBlocksForBoard(boardID)
|
||||
blocks, err := store.GetBlocks(model.QueryBlocksOptions{BoardID: boardID})
|
||||
require.NoError(t, err)
|
||||
initialCount := len(blocks)
|
||||
|
||||
@@ -516,7 +516,7 @@ func testDeleteBlock(t *testing.T, store store.Store) {
|
||||
InsertBlocks(t, store, blocksToInsert, "user-id-1")
|
||||
defer DeleteBlocks(t, store, blocksToInsert, "test")
|
||||
|
||||
blocks, err = store.GetBlocksForBoard(boardID)
|
||||
blocks, err = store.GetBlocks(model.QueryBlocksOptions{BoardID: boardID})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, blocks, initialCount+3)
|
||||
|
||||
@@ -550,7 +550,7 @@ func testUndeleteBlock(t *testing.T, store store.Store) {
|
||||
boardID := testBoardID
|
||||
userID := testUserID
|
||||
|
||||
blocks, err := store.GetBlocksForBoard(boardID)
|
||||
blocks, err := store.GetBlocks(model.QueryBlocksOptions{BoardID: boardID})
|
||||
require.NoError(t, err)
|
||||
initialCount := len(blocks)
|
||||
|
||||
@@ -574,7 +574,7 @@ func testUndeleteBlock(t *testing.T, store store.Store) {
|
||||
InsertBlocks(t, store, blocksToInsert, "user-id-1")
|
||||
defer DeleteBlocks(t, store, blocksToInsert, "test")
|
||||
|
||||
blocks, err = store.GetBlocksForBoard(boardID)
|
||||
blocks, err = store.GetBlocks(model.QueryBlocksOptions{BoardID: boardID})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, blocks, initialCount+3)
|
||||
|
||||
@@ -643,7 +643,7 @@ func testUndeleteBlock(t *testing.T, store store.Store) {
|
||||
|
||||
func testGetBlocks(t *testing.T, store store.Store) {
|
||||
boardID := testBoardID
|
||||
blocks, err := store.GetBlocksForBoard(boardID)
|
||||
blocks, err := store.GetBlocks(model.QueryBlocksOptions{BoardID: boardID})
|
||||
require.NoError(t, err)
|
||||
|
||||
blocksToInsert := []*model.Block{
|
||||
@@ -686,65 +686,74 @@ func testGetBlocks(t *testing.T, store store.Store) {
|
||||
InsertBlocks(t, store, blocksToInsert, "user-id-1")
|
||||
defer DeleteBlocks(t, store, blocksToInsert, "test")
|
||||
|
||||
t.Run("not existing parent", func(t *testing.T) {
|
||||
t.Run("not existing parent with type", func(t *testing.T) {
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
blocks, err = store.GetBlocksWithParentAndType(boardID, "not-exists", "test")
|
||||
opts := model.QueryBlocksOptions{BoardID: boardID, ParentID: "not-exists", BlockType: model.BlockType("test")}
|
||||
blocks, err = store.GetBlocks(opts)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, blocks)
|
||||
})
|
||||
|
||||
t.Run("not existing type", func(t *testing.T) {
|
||||
t.Run("not existing type with parent", func(t *testing.T) {
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
blocks, err = store.GetBlocksWithParentAndType(boardID, "block1", "not-existing")
|
||||
opts := model.QueryBlocksOptions{BoardID: boardID, ParentID: "block1", BlockType: model.BlockType("not-existing")}
|
||||
blocks, err = store.GetBlocks(opts)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, blocks)
|
||||
})
|
||||
|
||||
t.Run("valid parent and type", func(t *testing.T) {
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
blocks, err = store.GetBlocksWithParentAndType(boardID, "block1", "test")
|
||||
opts := model.QueryBlocksOptions{BoardID: boardID, ParentID: "block1", BlockType: model.BlockType("test")}
|
||||
blocks, err = store.GetBlocks(opts)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, blocks, 2)
|
||||
})
|
||||
|
||||
t.Run("not existing parent", func(t *testing.T) {
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
blocks, err = store.GetBlocksWithParent(boardID, "not-exists")
|
||||
opts := model.QueryBlocksOptions{BoardID: boardID, ParentID: "not-exists"}
|
||||
blocks, err = store.GetBlocks(opts)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, blocks)
|
||||
})
|
||||
|
||||
t.Run("valid parent", func(t *testing.T) {
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
blocks, err = store.GetBlocksWithParent(boardID, "block1")
|
||||
opts := model.QueryBlocksOptions{BoardID: boardID, ParentID: "block1"}
|
||||
blocks, err = store.GetBlocks(opts)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, blocks, 3)
|
||||
})
|
||||
|
||||
t.Run("not existing type", func(t *testing.T) {
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
blocks, err = store.GetBlocksWithType(boardID, "not-exists")
|
||||
opts := model.QueryBlocksOptions{BoardID: boardID, BlockType: model.BlockType("not-exists")}
|
||||
blocks, err = store.GetBlocks(opts)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, blocks)
|
||||
})
|
||||
|
||||
t.Run("valid type", func(t *testing.T) {
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
blocks, err = store.GetBlocksWithType(boardID, "test")
|
||||
opts := model.QueryBlocksOptions{BoardID: boardID, BlockType: model.BlockType("test")}
|
||||
blocks, err = store.GetBlocks(opts)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, blocks, 4)
|
||||
})
|
||||
|
||||
t.Run("not existing board", func(t *testing.T) {
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
blocks, err = store.GetBlocksForBoard("not-exists")
|
||||
opts := model.QueryBlocksOptions{BoardID: "not-exists"}
|
||||
blocks, err = store.GetBlocks(opts)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, blocks)
|
||||
})
|
||||
|
||||
t.Run("all blocks of the a board", func(t *testing.T) {
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
blocks, err = store.GetBlocksForBoard(boardID)
|
||||
opts := model.QueryBlocksOptions{BoardID: boardID}
|
||||
blocks, err = store.GetBlocks(opts)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, blocks, 5)
|
||||
})
|
||||
@@ -863,7 +872,7 @@ func testDuplicateBlock(t *testing.T, store store.Store) {
|
||||
|
||||
func testGetBlockMetadata(t *testing.T, store store.Store) {
|
||||
boardID := testBoardID
|
||||
blocks, err := store.GetBlocksForBoard(boardID)
|
||||
blocks, err := store.GetBlocks(model.QueryBlocksOptions{BoardID: boardID})
|
||||
require.NoError(t, err)
|
||||
|
||||
blocksToInsert := []*model.Block{
|
||||
@@ -1082,12 +1091,20 @@ func testUndeleteBlockChildren(t *testing.T, store store.Store) {
|
||||
require.Nil(t, block)
|
||||
|
||||
// ensure the card children were deleted
|
||||
blocks, err := store.GetBlocksWithParentAndType(cardDelete.BoardID, cardDelete.ID, model.TypeText)
|
||||
blocks, err := store.GetBlocks(model.QueryBlocksOptions{
|
||||
BoardID: cardDelete.BoardID,
|
||||
ParentID: cardDelete.ID,
|
||||
BlockType: model.TypeText},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, blocks)
|
||||
|
||||
// ensure the other card children remain.
|
||||
blocks, err = store.GetBlocksWithParentAndType(cardKeep.BoardID, cardKeep.ID, model.TypeText)
|
||||
blocks, err = store.GetBlocks(model.QueryBlocksOptions{
|
||||
BoardID: cardKeep.BoardID,
|
||||
ParentID: cardKeep.ID,
|
||||
BlockType: model.TypeText},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, blocks, len(blocksKeep))
|
||||
|
||||
@@ -1101,7 +1118,11 @@ func testUndeleteBlockChildren(t *testing.T, store store.Store) {
|
||||
require.NotNil(t, block)
|
||||
|
||||
// ensure the card children were restored
|
||||
blocks, err = store.GetBlocksWithParentAndType(cardDelete.BoardID, cardDelete.ID, model.TypeText)
|
||||
blocks, err = store.GetBlocks(model.QueryBlocksOptions{
|
||||
BoardID: cardDelete.BoardID,
|
||||
ParentID: cardDelete.ID,
|
||||
BlockType: model.TypeText},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, blocks, len(blocksDelete))
|
||||
})
|
||||
@@ -1117,12 +1138,12 @@ func testUndeleteBlockChildren(t *testing.T, store store.Store) {
|
||||
require.Nil(t, board)
|
||||
|
||||
// ensure all cards and blocks for the board were deleted
|
||||
blocks, err := store.GetBlocksForBoard(boardDelete.ID)
|
||||
blocks, err := store.GetBlocks(model.QueryBlocksOptions{BoardID: boardDelete.ID})
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, blocks)
|
||||
|
||||
// ensure the other board's cards and blocks remain.
|
||||
blocks, err = store.GetBlocksForBoard(boardKeep.ID)
|
||||
blocks, err = store.GetBlocks(model.QueryBlocksOptions{BoardID: boardKeep.ID})
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, blocks, len(blocksKeep)+len(cardsKeep))
|
||||
|
||||
@@ -1136,7 +1157,7 @@ func testUndeleteBlockChildren(t *testing.T, store store.Store) {
|
||||
require.NotNil(t, board)
|
||||
|
||||
// ensure the board's cards and blocks were restored.
|
||||
blocks, err = store.GetBlocksForBoard(boardDelete.ID)
|
||||
blocks, err = store.GetBlocks(model.QueryBlocksOptions{BoardID: boardDelete.ID})
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, blocks, len(blocksDelete)+len(cardsDelete))
|
||||
})
|
||||
|
||||
@@ -98,7 +98,7 @@ func LoadData(t *testing.T, store store.Store) {
|
||||
func testRunDataRetention(t *testing.T, store store.Store, batchSize int) {
|
||||
LoadData(t, store)
|
||||
|
||||
blocks, err := store.GetBlocksForBoard(boardID)
|
||||
blocks, err := store.GetBlocks(model.QueryBlocksOptions{BoardID: boardID})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, blocks, 4)
|
||||
initialCount := len(blocks)
|
||||
@@ -115,7 +115,7 @@ func testRunDataRetention(t *testing.T, store store.Store, batchSize int) {
|
||||
require.True(t, deletions > int64(initialCount))
|
||||
|
||||
// expect all blocks to be deleted.
|
||||
blocks, errBlocks := store.GetBlocksForBoard(boardID)
|
||||
blocks, errBlocks := store.GetBlocks(model.QueryBlocksOptions{BoardID: boardID})
|
||||
require.NoError(t, errBlocks)
|
||||
require.Equal(t, 0, len(blocks))
|
||||
|
||||
|
||||
@@ -190,6 +190,8 @@ func getSystemPing(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
s["CanReceiveNotifications"] = c.App.SendTestPushNotification(deviceID)
|
||||
}
|
||||
|
||||
s["ActiveSearchBackend"] = c.App.ActiveSearchBackend()
|
||||
|
||||
if s[model.STATUS] != model.StatusOk {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
@@ -295,7 +297,7 @@ func databaseRecycle(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
c.App.RecycleDatabaseConnection()
|
||||
c.App.RecycleDatabaseConnection(c.AppContext)
|
||||
|
||||
auditRec.Success()
|
||||
ReturnStatusOK(w)
|
||||
@@ -348,7 +350,7 @@ func queryLogs(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
logs, logerr := c.App.QueryLogs(c.Params.Page, c.Params.LogsPerPage, logFilter)
|
||||
logs, logerr := c.App.QueryLogs(c.AppContext, c.Params.Page, c.Params.LogsPerPage, logFilter)
|
||||
if logerr != nil {
|
||||
c.Err = logerr
|
||||
return
|
||||
@@ -387,7 +389,7 @@ func getLogs(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
lines, appErr := c.App.GetLogs(c.Params.Page, c.Params.LogsPerPage)
|
||||
lines, appErr := c.App.GetLogs(c.AppContext, c.Params.Page, c.Params.LogsPerPage)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
|
||||
@@ -117,6 +117,11 @@ func createTeam(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
if team.SchemeId != nil && !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionSysconsoleWriteUserManagementPermissions) {
|
||||
c.SetPermissionError(model.PermissionSysconsoleWriteUserManagementPermissions)
|
||||
return
|
||||
}
|
||||
|
||||
rteam, err := c.App.CreateTeamWithUser(c.AppContext, &team, c.AppContext.Session().UserId)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
|
||||
@@ -95,6 +95,39 @@ func TestCreateTeam(t *testing.T) {
|
||||
CheckForbiddenStatus(t, resp)
|
||||
})
|
||||
|
||||
t.Run("should verify user permissions during team creation", func(t *testing.T) {
|
||||
th.App.Srv().SetLicense(model.NewTestLicense("custom_permissions_schemes"))
|
||||
th.App.SetPhase2PermissionsMigrationStatus(true)
|
||||
|
||||
sc := th.SystemAdminClient
|
||||
scheme, _, err := sc.CreateScheme(&model.Scheme{
|
||||
DisplayName: "dn_" + model.NewId(),
|
||||
Name: model.NewId(),
|
||||
Scope: model.SchemeScopeTeam,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
team, _, err := sc.CreateTeam(&model.Team{
|
||||
DisplayName: "dn_" + model.NewId(),
|
||||
Name: GenerateTestTeamName(),
|
||||
Email: th.GenerateTestEmail(),
|
||||
Type: model.TeamOpen,
|
||||
SchemeId: &scheme.Id,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, scheme.Id, *team.SchemeId)
|
||||
|
||||
_, r, err := th.Client.CreateTeam(&model.Team{
|
||||
DisplayName: "dn_" + model.NewId(),
|
||||
Name: GenerateTestTeamName(),
|
||||
Email: th.GenerateTestEmail(),
|
||||
Type: model.TeamOpen,
|
||||
SchemeId: &scheme.Id,
|
||||
})
|
||||
require.Error(t, err)
|
||||
CheckForbiddenStatus(t, r)
|
||||
})
|
||||
|
||||
t.Run("should take under consideration the server language when creating a new team", func(t *testing.T) {
|
||||
c := th.SystemAdminClient
|
||||
cfg, _, err := c.GetConfig()
|
||||
|
||||
@@ -11,17 +11,17 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/mattermost/mattermost-server/v6/server/channels/app/request"
|
||||
"github.com/mattermost/mattermost-server/v6/server/platform/services/cache"
|
||||
"github.com/mattermost/mattermost-server/v6/server/platform/shared/i18n"
|
||||
"github.com/mattermost/mattermost-server/v6/server/platform/shared/mail"
|
||||
"github.com/mattermost/mattermost-server/v6/server/platform/shared/mlog"
|
||||
)
|
||||
|
||||
var latestVersionCache = cache.NewLRU(cache.LRUOptions{
|
||||
Size: 1,
|
||||
})
|
||||
|
||||
func (s *Server) GetLogs(page, perPage int) ([]string, *model.AppError) {
|
||||
func (s *Server) GetLogs(c request.CTX, page, perPage int) ([]string, *model.AppError) {
|
||||
var lines []string
|
||||
|
||||
license := s.License()
|
||||
@@ -33,7 +33,7 @@ func (s *Server) GetLogs(page, perPage int) ([]string, *model.AppError) {
|
||||
lines = append(lines, "-----------------------------------------------------------------------------------------------------------")
|
||||
lines = append(lines, "-----------------------------------------------------------------------------------------------------------")
|
||||
} else {
|
||||
mlog.Error("Could not get cluster info")
|
||||
c.Logger().Error("Could not get cluster info")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ func (s *Server) GetLogs(page, perPage int) ([]string, *model.AppError) {
|
||||
return lines, nil
|
||||
}
|
||||
|
||||
func (s *Server) QueryLogs(page, perPage int, logFilter *model.LogFilter) (map[string][]string, *model.AppError) {
|
||||
func (s *Server) QueryLogs(c request.CTX, page, perPage int, logFilter *model.LogFilter) (map[string][]string, *model.AppError) {
|
||||
logData := make(map[string][]string)
|
||||
|
||||
serverName := "default"
|
||||
@@ -66,7 +66,7 @@ func (s *Server) QueryLogs(page, perPage int, logFilter *model.LogFilter) (map[s
|
||||
if info := s.platform.Cluster().GetMyClusterInfo(); info != nil {
|
||||
serverName = info.Hostname
|
||||
} else {
|
||||
mlog.Error("Could not get cluster info")
|
||||
c.Logger().Error("Could not get cluster info")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,12 +111,12 @@ func AddLocalLogs(logData map[string][]string, s *Server, page, perPage int, ser
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) QueryLogs(page, perPage int, logFilter *model.LogFilter) (map[string][]string, *model.AppError) {
|
||||
return a.Srv().QueryLogs(page, perPage, logFilter)
|
||||
func (a *App) QueryLogs(c request.CTX, page, perPage int, logFilter *model.LogFilter) (map[string][]string, *model.AppError) {
|
||||
return a.Srv().QueryLogs(c, page, perPage, logFilter)
|
||||
}
|
||||
|
||||
func (a *App) GetLogs(page, perPage int) ([]string, *model.AppError) {
|
||||
return a.Srv().GetLogs(page, perPage)
|
||||
func (a *App) GetLogs(c request.CTX, page, perPage int) ([]string, *model.AppError) {
|
||||
return a.Srv().GetLogs(c, page, perPage)
|
||||
}
|
||||
|
||||
func (s *Server) GetLogsSkipSend(page, perPage int, logFilter *model.LogFilter) ([]string, *model.AppError) {
|
||||
@@ -146,15 +146,15 @@ func (s *Server) InvalidateAllCachesSkipSend() {
|
||||
|
||||
}
|
||||
|
||||
func (a *App) RecycleDatabaseConnection() {
|
||||
mlog.Info("Attempting to recycle database connections.")
|
||||
func (a *App) RecycleDatabaseConnection(c request.CTX) {
|
||||
c.Logger().Info("Attempting to recycle database connections.")
|
||||
|
||||
// This works by setting 10 seconds as the max conn lifetime for all DB connections.
|
||||
// This allows in gradually closing connections as they expire. In future, we can think
|
||||
// of exposing this as a param from the REST api.
|
||||
a.Srv().Store().RecycleDBConnections(10 * time.Second)
|
||||
|
||||
mlog.Info("Finished recycling database connections.")
|
||||
c.Logger().Info("Finished recycling database connections.")
|
||||
}
|
||||
|
||||
func (a *App) TestSiteURL(siteURL string) *model.AppError {
|
||||
|
||||
@@ -264,8 +264,6 @@ type AppIface interface {
|
||||
// MoveChannel method is prone to data races if someone joins to channel during the move process. However this
|
||||
// function is only exposed to sysadmins and the possibility of this edge case is relatively small.
|
||||
MoveChannel(c request.CTX, team *model.Team, channel *model.Channel, user *model.User) *model.AppError
|
||||
// NewWebConn returns a new WebConn instance.
|
||||
NewWebConn(cfg *platform.WebConnConfig) *platform.WebConn
|
||||
// NotifySessionsExpired is called periodically from the job server to notify any mobile sessions that have expired.
|
||||
NotifySessionsExpired() error
|
||||
// OverrideIconURLIfEmoji changes the post icon override URL prop, if it has an emoji icon,
|
||||
@@ -402,6 +400,7 @@ type AppIface interface {
|
||||
VerifyPlugin(plugin, signature io.ReadSeeker) *model.AppError
|
||||
AccountMigration() einterfaces.AccountMigrationInterface
|
||||
ActivateMfa(userID, token string) *model.AppError
|
||||
ActiveSearchBackend() string
|
||||
AddChannelsToRetentionPolicy(policyID string, channelIDs []string) *model.AppError
|
||||
AddConfigListener(listener func(*model.Config, *model.Config)) string
|
||||
AddDirectChannels(c request.CTX, teamID string, user *model.User) *model.AppError
|
||||
@@ -682,7 +681,7 @@ type AppIface interface {
|
||||
GetJobsPage(page int, perPage int) ([]*model.Job, *model.AppError)
|
||||
GetLatestTermsOfService() (*model.TermsOfService, *model.AppError)
|
||||
GetLatestVersion(latestVersionUrl string) (*model.GithubReleaseInfo, *model.AppError)
|
||||
GetLogs(page, perPage int) ([]string, *model.AppError)
|
||||
GetLogs(c request.CTX, page, perPage int) ([]string, *model.AppError)
|
||||
GetLogsSkipSend(page, perPage int, logFilter *model.LogFilter) ([]string, *model.AppError)
|
||||
GetMemberCountsByGroup(ctx context.Context, channelID string, includeTimezones bool) ([]*model.ChannelMemberCountByGroup, *model.AppError)
|
||||
GetMessageForNotification(post *model.Post, translateFunc i18n.TranslateFunc) string
|
||||
@@ -961,9 +960,9 @@ type AppIface interface {
|
||||
PublishUserTyping(userID, channelID, parentId string) *model.AppError
|
||||
PurgeBleveIndexes() *model.AppError
|
||||
PurgeElasticsearchIndexes() *model.AppError
|
||||
QueryLogs(page, perPage int, logFilter *model.LogFilter) (map[string][]string, *model.AppError)
|
||||
QueryLogs(c request.CTX, page, perPage int, logFilter *model.LogFilter) (map[string][]string, *model.AppError)
|
||||
ReadFile(path string) ([]byte, *model.AppError)
|
||||
RecycleDatabaseConnection()
|
||||
RecycleDatabaseConnection(c request.CTX)
|
||||
RegenCommandToken(cmd *model.Command) (*model.Command, *model.AppError)
|
||||
RegenOutgoingWebhookToken(hook *model.OutgoingWebhook) (*model.OutgoingWebhook, *model.AppError)
|
||||
RegenerateOAuthAppSecret(app *model.OAuthApp) (*model.OAuthApp, *model.AppError)
|
||||
|
||||
@@ -89,6 +89,23 @@ func (a *OpenTracingAppLayer) ActivateMfa(userID string, token string) *model.Ap
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) ActiveSearchBackend() string {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.ActiveSearchBackend")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store().SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store().SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0 := a.app.ActiveSearchBackend()
|
||||
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) AddChannelMember(c request.CTX, userID string, channel *model.Channel, opts app.ChannelMemberOpts) (*model.ChannelMember, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.AddChannelMember")
|
||||
@@ -7111,7 +7128,7 @@ func (a *OpenTracingAppLayer) GetLdapGroup(ldapGroupID string) (*model.Group, *m
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) GetLogs(page int, perPage int) ([]string, *model.AppError) {
|
||||
func (a *OpenTracingAppLayer) GetLogs(c request.CTX, page int, perPage int) ([]string, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetLogs")
|
||||
|
||||
@@ -7123,7 +7140,7 @@ func (a *OpenTracingAppLayer) GetLogs(page int, perPage int) ([]string, *model.A
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0, resultVar1 := a.app.GetLogs(page, perPage)
|
||||
resultVar0, resultVar1 := a.app.GetLogs(c, page, perPage)
|
||||
|
||||
if resultVar1 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar1))
|
||||
@@ -12761,23 +12778,6 @@ func (a *OpenTracingAppLayer) NewPluginAPI(c *request.Context, manifest *model.M
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) NewWebConn(cfg *platform.WebConnConfig) *platform.WebConn {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.NewWebConn")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store().SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store().SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0 := a.app.NewWebConn(cfg)
|
||||
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) NotifyAndSetWarnMetricAck(warnMetricId string, sender *model.User, forceAck bool, isBot bool) *model.AppError {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.NotifyAndSetWarnMetricAck")
|
||||
@@ -13624,7 +13624,7 @@ func (a *OpenTracingAppLayer) PurgeElasticsearchIndexes() *model.AppError {
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) QueryLogs(page int, perPage int, logFilter *model.LogFilter) (map[string][]string, *model.AppError) {
|
||||
func (a *OpenTracingAppLayer) QueryLogs(c request.CTX, page int, perPage int, logFilter *model.LogFilter) (map[string][]string, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.QueryLogs")
|
||||
|
||||
@@ -13636,7 +13636,7 @@ func (a *OpenTracingAppLayer) QueryLogs(page int, perPage int, logFilter *model.
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0, resultVar1 := a.app.QueryLogs(page, perPage, logFilter)
|
||||
resultVar0, resultVar1 := a.app.QueryLogs(c, page, perPage, logFilter)
|
||||
|
||||
if resultVar1 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar1))
|
||||
@@ -13668,7 +13668,7 @@ func (a *OpenTracingAppLayer) ReadFile(path string) ([]byte, *model.AppError) {
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) RecycleDatabaseConnection() {
|
||||
func (a *OpenTracingAppLayer) RecycleDatabaseConnection(c request.CTX) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.RecycleDatabaseConnection")
|
||||
|
||||
@@ -13680,7 +13680,7 @@ func (a *OpenTracingAppLayer) RecycleDatabaseConnection() {
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
a.app.RecycleDatabaseConnection()
|
||||
a.app.RecycleDatabaseConnection(c)
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) RegenCommandToken(cmd *model.Command) (*model.Command, *model.AppError) {
|
||||
|
||||
@@ -352,7 +352,7 @@ func (ch *Channels) syncPlugins() *model.AppError {
|
||||
}
|
||||
|
||||
mlog.Info("Syncing plugin from file store", mlog.String("bundle", plugin.path))
|
||||
if _, err := ch.installPluginLocally(reader, signature, installPluginLocallyAlways); err != nil {
|
||||
if _, err := ch.installPluginLocally(reader, signature, installPluginLocallyAlways); err != nil && err.Id != "app.plugin.blocked.app_error" && err.Id != "app.plugin.skip_installation.app_error" {
|
||||
mlog.Error("Failed to sync plugin from file store", mlog.String("bundle", plugin.path), mlog.Err(err))
|
||||
}
|
||||
}(plugin)
|
||||
@@ -952,6 +952,11 @@ func (ch *Channels) processPrepackagedPlugins(pluginsDir string) []*plugin.Prepa
|
||||
defer wg.Done()
|
||||
p, err := ch.processPrepackagedPlugin(psPath)
|
||||
if err != nil {
|
||||
var appErr *model.AppError
|
||||
// A log line already appears if the plugin is on the blocklist
|
||||
if errors.As(err, &appErr) && (appErr.Id == "app.plugin.blocked.app_error" || appErr.Id == "app.plugin.skip_installation.app_error") {
|
||||
return
|
||||
}
|
||||
mlog.Error("Failed to install prepackaged plugin", mlog.String("path", psPath.path), mlog.Err(err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -92,7 +92,10 @@ func (ch *Channels) installPluginFromData(data model.PluginEventData) {
|
||||
|
||||
manifest, appErr := ch.installPluginLocally(reader, signature, installPluginLocallyAlways)
|
||||
if appErr != nil {
|
||||
mlog.Error("Failed to sync plugin from file store", mlog.String("bundle", plugin.path), mlog.Err(appErr))
|
||||
// A log line already appears if the plugin is on the blocklist or skipped
|
||||
if appErr.Id != "app.plugin.blocked.app_error" && appErr.Id != "app.plugin.skip_installation.app_error" {
|
||||
mlog.Error("Failed to sync plugin from file store", mlog.String("bundle", plugin.path), mlog.Err(appErr))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -330,8 +333,8 @@ func (ch *Channels) installExtractedPlugin(manifest *model.Manifest, fromPluginD
|
||||
|
||||
// Check plugin id is not blocked
|
||||
if plugin.PluginIDIsBlocked(manifest.Id) {
|
||||
mlog.Debug("Skipping installation of plugin since plugin is on blocklist", mlog.String("plugin_id", manifest.Id))
|
||||
return nil, nil
|
||||
mlog.Debug("Skipping installation of plugin since plugin is on blocklist. Some plugins are blocked because they are built into this version of Mattermost.", mlog.String("plugin_id", manifest.Id))
|
||||
return nil, model.NewAppError("installExtractedPlugin", "app.plugin.blocked.app_error", map[string]any{"Id": manifest.Id}, "", http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
// Check for plugins installed with the same ID.
|
||||
@@ -365,7 +368,7 @@ func (ch *Channels) installExtractedPlugin(manifest *model.Manifest, fromPluginD
|
||||
|
||||
if version.LTE(existingVersion) {
|
||||
mlog.Debug("Skipping local installation of plugin since existing version is newer", mlog.String("plugin_id", manifest.Id))
|
||||
return nil, nil
|
||||
return nil, model.NewAppError("installExtractedPlugin", "app.plugin.skip_installation.app_error", map[string]any{"Id": manifest.Id}, "", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -172,10 +172,9 @@ func TestInstallPluginLocally(t *testing.T) {
|
||||
defer th.TearDown()
|
||||
cleanExistingBundles(t, th)
|
||||
|
||||
manifest, appErr := installPlugin(t, th, "playbooks", "0.0.1", installPluginLocallyAlways)
|
||||
require.Nil(t, appErr)
|
||||
require.Nil(t, manifest)
|
||||
|
||||
_, appErr := installPlugin(t, th, "playbooks", "0.0.1", installPluginLocallyAlways)
|
||||
require.NotNil(t, appErr)
|
||||
require.Equal(t, "app.plugin.blocked.app_error", appErr.Id)
|
||||
assertBundleInfoManifests(t, th, []*model.Manifest{})
|
||||
})
|
||||
|
||||
@@ -222,9 +221,9 @@ func TestInstallPluginLocally(t *testing.T) {
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, existingManifest)
|
||||
|
||||
manifest, appErr := installPlugin(t, th, "valid", "0.0.1", installPluginLocallyOnlyIfNewOrUpgrade)
|
||||
require.Nil(t, appErr)
|
||||
require.Nil(t, manifest)
|
||||
_, appErr = installPlugin(t, th, "valid", "0.0.1", installPluginLocallyOnlyIfNewOrUpgrade)
|
||||
require.NotNil(t, appErr)
|
||||
require.Equal(t, "app.plugin.skip_installation.app_error", appErr.Id)
|
||||
|
||||
assertBundleInfoManifests(t, th, []*model.Manifest{existingManifest})
|
||||
})
|
||||
@@ -238,9 +237,9 @@ func TestInstallPluginLocally(t *testing.T) {
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, existingManifest)
|
||||
|
||||
manifest, appErr := installPlugin(t, th, "valid", "0.0.2", installPluginLocallyOnlyIfNewOrUpgrade)
|
||||
require.Nil(t, appErr)
|
||||
require.Nil(t, manifest)
|
||||
_, appErr = installPlugin(t, th, "valid", "0.0.2", installPluginLocallyOnlyIfNewOrUpgrade)
|
||||
require.NotNil(t, appErr)
|
||||
require.Equal(t, "app.plugin.skip_installation.app_error", appErr.Id)
|
||||
|
||||
assertBundleInfoManifests(t, th, []*model.Manifest{existingManifest})
|
||||
})
|
||||
|
||||
@@ -60,3 +60,7 @@ func (a *App) PurgeBleveIndexes() *model.AppError {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) ActiveSearchBackend() string {
|
||||
return a.ch.srv.platform.SearchEngine.ActiveEngine()
|
||||
}
|
||||
|
||||
@@ -260,8 +260,17 @@ func NewServer(options ...Option) (*Server, error) {
|
||||
product.CommandKey: app,
|
||||
}
|
||||
|
||||
// Step 4: Initialize products.
|
||||
// Depends on s.httpService.
|
||||
// It is important to initialize the hub only after the global logger is set
|
||||
// to avoid race conditions while logging from inside the hub.
|
||||
// Step 4: Start platform
|
||||
s.platform.Start()
|
||||
|
||||
// NOTE: There should be no call to App.Srv().Channels() before step 5 is done
|
||||
// otherwise it will throw a panic.
|
||||
|
||||
// Step 5: Initialize products.
|
||||
// Depends on s.httpService, and depends on the hub to be initialized.
|
||||
// Otherwise we run into race conditions.
|
||||
err = s.initializeProducts(product.GetProducts(), serviceMap)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to initialize products")
|
||||
@@ -275,11 +284,6 @@ func NewServer(options ...Option) (*Server, error) {
|
||||
}
|
||||
app.ch = channelsWrapper.app.ch
|
||||
|
||||
// It is important to initialize the hub only after the global logger is set
|
||||
// to avoid race conditions while logging from inside the hub.
|
||||
// Step 5: Start hub in platform which the hub depends on s.Channels() (step 4)
|
||||
s.platform.Start()
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Everything below this is not order sensitive and safe to be moved around.
|
||||
// If you are adding a new field that is non-channels specific, please add
|
||||
|
||||
@@ -13,8 +13,3 @@ import (
|
||||
func (a *App) PopulateWebConnConfig(s *model.Session, cfg *platform.WebConnConfig, seqVal string) (*platform.WebConnConfig, error) {
|
||||
return a.Srv().Platform().PopulateWebConnConfig(s, cfg, seqVal)
|
||||
}
|
||||
|
||||
// NewWebConn returns a new WebConn instance.
|
||||
func (a *App) NewWebConn(cfg *platform.WebConnConfig) *platform.WebConn {
|
||||
return a.Srv().Platform().NewWebConn(cfg, a, a.ch)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
pbclient "github.com/mattermost/mattermost-plugin-playbooks/client"
|
||||
pbclient "github.com/mattermost/mattermost-server/v6/server/playbooks/client"
|
||||
|
||||
fb_model "github.com/mattermost/mattermost-server/v6/server/boards/model"
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
"github.com/mattermost/mattermost-server/v6/server/channels/app/request"
|
||||
"github.com/mattermost/mattermost-server/v6/server/channels/app/worktemplates"
|
||||
|
||||
pbclient "github.com/mattermost/mattermost-plugin-playbooks/client"
|
||||
pbclient "github.com/mattermost/mattermost-server/v6/server/playbooks/client"
|
||||
)
|
||||
|
||||
func TestGetWorkTemplateCategories(t *testing.T) {
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
pbclient "github.com/mattermost/mattermost-plugin-playbooks/client"
|
||||
pbclient "github.com/mattermost/mattermost-server/v6/server/playbooks/client"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
)
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
|
||||
pbclient "github.com/mattermost/mattermost-plugin-playbooks/client"
|
||||
pbclient "github.com/mattermost/mattermost-server/v6/server/playbooks/client"
|
||||
)
|
||||
|
||||
func TestCanBeExecuted(t *testing.T) {
|
||||
|
||||
@@ -5931,6 +5931,10 @@
|
||||
"id": "app.oauth.update_app.updating.app_error",
|
||||
"translation": "We encountered an error updating the app."
|
||||
},
|
||||
{
|
||||
"id": "app.plugin.blocked.app_error",
|
||||
"translation": "Plugin {{.Id}} is on the block list. Some plugins are blocked because they are built into this version of Mattermost."
|
||||
},
|
||||
{
|
||||
"id": "app.plugin.cluster.save_config.app_error",
|
||||
"translation": "The plugin configuration in your config.json file must be updated manually when using ReadOnlyConfig with clustering enabled."
|
||||
@@ -6063,6 +6067,10 @@
|
||||
"id": "app.plugin.signature_decode.app_error",
|
||||
"translation": "Unable to decode base64 signature."
|
||||
},
|
||||
{
|
||||
"id": "app.plugin.skip_installation.app_error",
|
||||
"translation": "Skipping installation of plugin {{.Id}} since existing version is equal or newer."
|
||||
},
|
||||
{
|
||||
"id": "app.plugin.store_bundle.app_error",
|
||||
"translation": "Unable to store the plugin to the configured file store."
|
||||
@@ -7787,6 +7795,10 @@
|
||||
"id": "ent.elasticsearch.indexer.index_batch.nothing_left_to_index.error",
|
||||
"translation": "Trying to index a new batch when all the entities are completed"
|
||||
},
|
||||
{
|
||||
"id": "ent.elasticsearch.max_version.app_error",
|
||||
"translation": "Elasticsearch version {{.Version}} is higher than max supported version of {{.MaxVersion}}"
|
||||
},
|
||||
{
|
||||
"id": "ent.elasticsearch.not_started.error",
|
||||
"translation": "Elasticsearch is not started"
|
||||
@@ -7855,10 +7867,6 @@
|
||||
"id": "ent.elasticsearch.search_users.unmarshall_user_failed",
|
||||
"translation": "Failed to decode search results"
|
||||
},
|
||||
{
|
||||
"id": "ent.elasticsearch.start.already_started.app_error",
|
||||
"translation": "Elasticsearch is already started."
|
||||
},
|
||||
{
|
||||
"id": "ent.elasticsearch.start.create_bulk_processor_failed.app_error",
|
||||
"translation": "Failed to create Elasticsearch bulk processor."
|
||||
|
||||
@@ -9837,5 +9837,337 @@
|
||||
{
|
||||
"id": "api.templates.license_up_for_renewal_contact_sales",
|
||||
"translation": "Contact Sales"
|
||||
},
|
||||
{
|
||||
"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.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.board",
|
||||
"translation": "Track your team's progress toward weekly goals with sprint breakdowns, prioritisation, owner assignment and comments."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.product_teams.product_roadmap.channel",
|
||||
"translation": "Chat with your team about your customers' feedback, prioritisation and get aligned on progress together."
|
||||
},
|
||||
{
|
||||
"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 prioritise issues."
|
||||
},
|
||||
{
|
||||
"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.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.board",
|
||||
"translation": "Track your team's progress toward organisational goals with the Goals and OKR board. Keep meetings on track with the Meeting Agenda board."
|
||||
},
|
||||
{
|
||||
"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.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.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.channel",
|
||||
"translation": "Plan and manage bug reports and resolutions in a single channel that’s easily accessible to your team and organisation."
|
||||
},
|
||||
{
|
||||
"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.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.board",
|
||||
"translation": "Track your team's progress toward organisational goals with the Goals and OKR board. Keep meetings on track with the Meeting Agenda board."
|
||||
},
|
||||
{
|
||||
"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.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.board",
|
||||
"translation": "Use the Product Release board to support your release timeframe and process, ensuring everyone knows which tasks are due."
|
||||
},
|
||||
{
|
||||
"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.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.board",
|
||||
"translation": "Use the Incident Resolution board to support repeatable processes and assign defined tasks across the team."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.companywide.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.companywide.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.companywide.goals_and_okrs.board",
|
||||
"translation": "Track your team's progress toward organisational goals with the Goals and OKR board. Keep meetings on track with the Meeting Agenda board."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.companywide.create_project.integration",
|
||||
"translation": "Increase productivity in your channel by integrating your most commonly used tools. These will be downloaded for you."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.companywide.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.companywide.create_project.board",
|
||||
"translation": "Use a Kanban board to define and track your project tasks and progress."
|
||||
},
|
||||
{
|
||||
"id": "model.license_record.is_valid.bytes.app_error",
|
||||
"translation": "Invalid value for bytes when uploading a licence."
|
||||
},
|
||||
{
|
||||
"id": "app.user.run.update_status.title",
|
||||
"translation": "Status update"
|
||||
},
|
||||
{
|
||||
"id": "app.user.run.update_status.submit_label",
|
||||
"translation": "Update status"
|
||||
},
|
||||
{
|
||||
"id": "app.user.run.update_status.reminder_for_next_update",
|
||||
"translation": "Reminder for next update"
|
||||
},
|
||||
{
|
||||
"id": "app.user.run.update_status.num_channel",
|
||||
"translation": {
|
||||
"one": "Provide an update to the stakeholders. This post will be broadcasted to {{.Count}} channel.",
|
||||
"other": "Provide an update to the stakeholders. This post will be broadcasted to {{.Count}} channels."
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "app.user.run.update_status.finish_run.placeholder",
|
||||
"translation": "Also mark the run as finished"
|
||||
},
|
||||
{
|
||||
"id": "app.user.run.update_status.finish_run",
|
||||
"translation": "Finish run"
|
||||
},
|
||||
{
|
||||
"id": "app.user.run.update_status.change_since_last_update",
|
||||
"translation": "Change since last update"
|
||||
},
|
||||
{
|
||||
"id": "app.user.run.status_enable",
|
||||
"translation": "@{{.Username}} enabled the status updates for [{{.RunName}}]({{.RunURL}})"
|
||||
},
|
||||
{
|
||||
"id": "app.user.run.status_disable",
|
||||
"translation": "@{{.Username}} disabled the status updates for [{{.RunName}}]({{.RunURL}})"
|
||||
},
|
||||
{
|
||||
"id": "app.user.run.request_update",
|
||||
"translation": "@here — @{{.Name}} requested a status update for [{{.RunName}}]({{.RunURL}}). \n"
|
||||
},
|
||||
{
|
||||
"id": "app.user.run.request_join_channel",
|
||||
"translation": "@{{.Name}} is a run participant and wants join this channel. Any member of the channel can invite them.\n"
|
||||
},
|
||||
{
|
||||
"id": "app.user.run.confirm_finish.title",
|
||||
"translation": "Confirm finish run"
|
||||
},
|
||||
{
|
||||
"id": "app.user.run.confirm_finish.submit_label",
|
||||
"translation": "Finish run"
|
||||
},
|
||||
{
|
||||
"id": "app.user.run.confirm_finish.num_outstanding",
|
||||
"translation": {
|
||||
"one": "There is **{{.Count}} outstanding task**. Are you sure you want to finish the run *{{.RunName}}* for all participants?",
|
||||
"other": "There are **{{.Count}} outstanding tasks**. Are you sure you want to finish the run *{{.RunName}}* for all participants?"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "app.user.run.add_to_timeline.title",
|
||||
"translation": "Add to run timeline"
|
||||
},
|
||||
{
|
||||
"id": "app.user.run.add_to_timeline.summary.placeholder",
|
||||
"translation": "Short summary shown in the timeline"
|
||||
},
|
||||
{
|
||||
"id": "app.user.run.add_to_timeline.summary.help",
|
||||
"translation": "Max 64 characters"
|
||||
},
|
||||
{
|
||||
"id": "app.user.run.add_to_timeline.summary",
|
||||
"translation": "Summary"
|
||||
},
|
||||
{
|
||||
"id": "app.user.run.add_to_timeline.submit_label",
|
||||
"translation": "Add to run timeline"
|
||||
},
|
||||
{
|
||||
"id": "app.user.run.add_to_timeline.playbook_run",
|
||||
"translation": "Playbook Run"
|
||||
},
|
||||
{
|
||||
"id": "app.user.run.add_checklist_item.title",
|
||||
"translation": "Add new task"
|
||||
},
|
||||
{
|
||||
"id": "app.user.run.add_checklist_item.submit_label",
|
||||
"translation": "Add task"
|
||||
},
|
||||
{
|
||||
"id": "app.user.run.add_checklist_item.name",
|
||||
"translation": "Name"
|
||||
},
|
||||
{
|
||||
"id": "app.user.run.add_checklist_item.description",
|
||||
"translation": "Description"
|
||||
},
|
||||
{
|
||||
"id": "app.user.new_run.title",
|
||||
"translation": "Run playbook"
|
||||
},
|
||||
{
|
||||
"id": "app.user.new_run.submit_label",
|
||||
"translation": "Start run"
|
||||
},
|
||||
{
|
||||
"id": "app.user.new_run.run_name",
|
||||
"translation": "Run name"
|
||||
},
|
||||
{
|
||||
"id": "app.user.new_run.playbook",
|
||||
"translation": "Playbook"
|
||||
},
|
||||
{
|
||||
"id": "app.user.new_run.intro",
|
||||
"translation": "**Owner** {{.Username}}"
|
||||
},
|
||||
{
|
||||
"id": "app.user.digest.tasks.zero_assigned",
|
||||
"translation": "You have 0 assigned tasks."
|
||||
},
|
||||
{
|
||||
"id": "app.user.digest.tasks.num_assigned_due_until_today",
|
||||
"translation": {
|
||||
"one": "You have {{.Count}} assigned task that is now due:",
|
||||
"other": "You have {{.Count}} assigned tasks that are now due:"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "app.user.digest.tasks.num_assigned",
|
||||
"translation": {
|
||||
"one": "You have {{.Count}} assigned task:",
|
||||
"other": "You have {{.Count}} total assigned tasks:"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "app.user.digest.tasks.heading",
|
||||
"translation": "Your assigned tasks"
|
||||
},
|
||||
{
|
||||
"id": "app.user.digest.tasks.due_yesterday",
|
||||
"translation": "Due yesterday"
|
||||
},
|
||||
{
|
||||
"id": "app.user.digest.tasks.due_x_days_ago",
|
||||
"translation": "Due {{.Count}} days ago"
|
||||
},
|
||||
{
|
||||
"id": "app.user.digest.tasks.due_today",
|
||||
"translation": "Due today"
|
||||
},
|
||||
{
|
||||
"id": "app.user.digest.tasks.due_in_x_days",
|
||||
"translation": {
|
||||
"one": "Due in {{.Count}} day",
|
||||
"other": "Due in {{.Count}} days"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "app.user.digest.tasks.due_after_today",
|
||||
"translation": {
|
||||
"one": "You have **{{.Count}} assigned task due after today**.",
|
||||
"other": "You have **{{.Count}} assigned tasks due after today**."
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "app.user.digest.tasks.all_tasks_command",
|
||||
"translation": "Please use `/playbook todo` to see all your tasks."
|
||||
},
|
||||
{
|
||||
"id": "app.user.digest.runs_in_progress.zero_in_progress",
|
||||
"translation": "You have 0 runs currently in progress."
|
||||
},
|
||||
{
|
||||
"id": "app.user.digest.runs_in_progress.num_in_progress",
|
||||
"translation": {
|
||||
"one": "You have {{.Count}} run currently in progress:",
|
||||
"other": "You have {{.Count}} runs currently in progress:"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "app.user.digest.runs_in_progress.heading",
|
||||
"translation": "Runs in Progress"
|
||||
},
|
||||
{
|
||||
"id": "app.user.digest.overdue_status_updates.zero_overdue",
|
||||
"translation": "You have 0 runs overdue."
|
||||
},
|
||||
{
|
||||
"id": "app.user.digest.overdue_status_updates.num_overdue",
|
||||
"translation": {
|
||||
"one": "You have {{.Count}} run overdue for a status update:",
|
||||
"other": "You have {{.Count}} runs overdue for a status update:"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "app.user.digest.overdue_status_updates.heading",
|
||||
"translation": "Overdue Status Updates"
|
||||
},
|
||||
{
|
||||
"id": "app.oauth.remove_auth_data_by_client_id.app_error",
|
||||
"translation": "Unable to remove OAuth data."
|
||||
},
|
||||
{
|
||||
"id": "app.command.execute.error",
|
||||
"translation": "Unable to execute command."
|
||||
},
|
||||
{
|
||||
"id": "api.server.cws.subscribe_to_newsletter.app_error",
|
||||
"translation": "CWS Server failed to subscribe to newsletter."
|
||||
},
|
||||
{
|
||||
"id": "api.license.request-trial.bad-request.business-email",
|
||||
"translation": "Invalid business email for trial"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -9829,5 +9829,45 @@
|
||||
{
|
||||
"id": "api.templates.license_up_for_renewal_contact_sales",
|
||||
"translation": "Contacteer de verkoopsafdeling"
|
||||
},
|
||||
{
|
||||
"id": "app.user.digest.tasks.due_today",
|
||||
"translation": "Vandaag te voldoen"
|
||||
},
|
||||
{
|
||||
"id": "app.user.digest.tasks.all_tasks_command",
|
||||
"translation": "Gebruik `/playbook todo` om al je taken te zien."
|
||||
},
|
||||
{
|
||||
"id": "app.user.digest.runs_in_progress.zero_in_progress",
|
||||
"translation": "Je hebt momenteel 0 runs lopen."
|
||||
},
|
||||
{
|
||||
"id": "app.user.digest.runs_in_progress.heading",
|
||||
"translation": "Runs in uitvoering"
|
||||
},
|
||||
{
|
||||
"id": "app.user.digest.overdue_status_updates.zero_overdue",
|
||||
"translation": "Je hebt 0 runs achterstand."
|
||||
},
|
||||
{
|
||||
"id": "app.user.digest.overdue_status_updates.heading",
|
||||
"translation": "Achterstallige statusupdates"
|
||||
},
|
||||
{
|
||||
"id": "app.command.execute.error",
|
||||
"translation": "Kan commando niet uitvoeren."
|
||||
},
|
||||
{
|
||||
"id": "api.server.cws.subscribe_to_newsletter.app_error",
|
||||
"translation": "CWS-server kan zich niet abonneren op nieuwsbrief."
|
||||
},
|
||||
{
|
||||
"id": "api.server.cws.needs_enterprise_edition",
|
||||
"translation": "Dienst alleen beschikbaar in Mattermost Enterprise editie"
|
||||
},
|
||||
{
|
||||
"id": "api.license.request-trial.bad-request.business-email",
|
||||
"translation": "Ongeldig zakelijk e-mailadres voor proefperiode"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1505,7 +1505,7 @@
|
||||
},
|
||||
{
|
||||
"id": "api.slackimport.slack_add_channels.added",
|
||||
"translation": "\nKanallar eklendi:\n"
|
||||
"translation": "\nEklenen kanallar:\n"
|
||||
},
|
||||
{
|
||||
"id": "api.slackimport.slack_add_channels.failed_to_add_user",
|
||||
@@ -1521,7 +1521,7 @@
|
||||
},
|
||||
{
|
||||
"id": "api.slackimport.slack_add_users.created",
|
||||
"translation": "\nKullanıcılar eklendi:\n"
|
||||
"translation": "\nEklenen kullanıcılar:\n"
|
||||
},
|
||||
{
|
||||
"id": "api.slackimport.slack_add_users.email_pwd",
|
||||
@@ -9508,7 +9508,7 @@
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.category.product_teams",
|
||||
"translation": "Ürün takımları"
|
||||
"translation": "Ürün"
|
||||
},
|
||||
{
|
||||
"id": "model.draft.is_valid.user_id.app_error",
|
||||
@@ -9833,5 +9833,345 @@
|
||||
{
|
||||
"id": "api.command_templates.desc",
|
||||
"translation": "Kalıptan oluştur penceresini aç"
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.product_teams.sprint_planning.board",
|
||||
"translation": "Acil sorunlar, önceliklendirme, sahip atama ve yorumlarla ekibinizin haftalık hedeflere doğru ilerlemesini izleyin."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.product_teams.sprint_planning.integration",
|
||||
"translation": "Zoom gibi sık kullandığınız araçlar ile bütünleştirerek kanalınızdaki üretkenliği artırın. Bunlar sizin için indirilir."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.product_teams.sprint_planning.channel",
|
||||
"translation": "Panolarınız ve bütünleştirmelerinizle kolayca bağlantı kuran bir kanalda ekibinizle sohbet edin."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.product_teams.product_roadmap.channel",
|
||||
"translation": "Müşterilerinizin geri bildirimleri ve önceliklendirme hakkında ekibinizle sohbet ederek birlikte ilerleme kaydedin."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.product_teams.product_roadmap.board",
|
||||
"translation": "Kullanıcı geri bildirimlerini yönetmek, kaynak atamak, çıktıları takvimde görüntülemek ve sorunları önceliklendirmek için ürün yol haritası panosunu kullanın."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.product_teams.goals_and_okrs.integration",
|
||||
"translation": "İşbirliğini kolaylaştırmak için Zoom gibi sık kullandığınız araçları bütünleştirin ve kanalınızdaki üretkenliği artırın. Bunlar sizin için indirilir."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.product_teams.goals_and_okrs.channel",
|
||||
"translation": "Hedefleriniz ve ilerlemeniz hakkında ekibinizle farklı zamanlarda ya da gerçek zamanlı olarak sohbet edin ve değişiklikleri tek bir kanaldan izleyerek güncel kalın."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.product_teams.goals_and_okrs.board",
|
||||
"translation": "Amaçlar ve anahtar sonuçlar (OKR) panosu ile ekibinizin kurumsal hedeflere doğru ilerlemesini izleyin. Toplantı gündemi panosu ile toplantıları izleyin."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.product_teams.feature_release.description.playbook",
|
||||
"translation": "Özellik geliştirme sürecinizi destekleyen görev kontrol listeleri ve otomasyon ile işlevsel ekipler arasında işbirliğini artırın. İşiniz bittiğinde bir geçmiş değerlendirmesi yaparak ve süreci bir sonraki sürümünüz için iyileştirin."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.product_teams.feature_release.description.integration",
|
||||
"translation": "Özellikleri yayınlamak için GitHub gibi sık kullandığınız araçları bütünleştirin ve kanalınızdaki üretkenliği artırın. Bunlar sizin için indirilir."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.product_teams.feature_release.description.channel",
|
||||
"translation": "Panolarınıza, senaryolarınıza ve diğer bütünleştirmelere kolayca bağlanan bir kanalda sürüm engelleyicileri ve değişiklikler hakkında ekibinizle sohbet edin."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.product_teams.feature_release.description.board",
|
||||
"translation": "Toplantı gündemi panosu ile toplantıları izleyin. Proje görevleri panosu ile iş yükünüzü yönetin."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.product_teams.bug_bash.playbook",
|
||||
"translation": "Kapsamlı bir hata ayıklama süreci yürütmek için deneme alanları ve otomatik görevler atamak üzere kontrol listeleri kullanın. Sürecinizi gözden geçirmek ve gelecek sefer daha iyi olmasını sağlamak için bir geçmiş değerlendirmesi yapın."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.product_teams.bug_bash.integration",
|
||||
"translation": "Hata ayıklama işlerinizi kolaylaştırmak için Jira gibi sık kullandığınız araçları bütünleştirin ve kanalınızdaki üretkenliği artırın. Bunlar sizin için indirilir."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.product_teams.bug_bash.channel",
|
||||
"translation": "Hata raporlarını ve çözümlerini ekibinizin ve kuruluşunuzun kolayca erişebileceği tek bir kanalda planlayın ve yönetin."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.leadership.goals_and_okrs.integration",
|
||||
"translation": "İşbirliğini kolaylaştırmak için Zoom gibi sık kullandığınız araçları bütünleştirin ve kanalınızdaki üretkenliği artırın. Bunlar sizin için indirilir."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.leadership.goals_and_okrs.channel",
|
||||
"translation": "Hedefleriniz ve ilerlemeniz hakkında ekibinizle farklı zamanlarda ya da gerçek zamanlı olarak sohbet edin ve değişiklikleri tek bir kanaldan izleyerek güncel kalın."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.leadership.goals_and_okrs.board",
|
||||
"translation": "Amaçlar ve anahtar sonuçlar (OKR) panosu ile ekibinizin kurumsal hedeflere doğru ilerlemesini izleyin. Toplantı gündemi panosu ile toplantıları izleyin."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.devops.product_release.playbook",
|
||||
"translation": "Ürün çıkışlarının güvenilir ve zamanında olması için izlenmesi ve uygulanması kolay, yinelenebilir iş akışları oluşturun."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.devops.product_release.channel",
|
||||
"translation": "Ekibinizle günlük kilometre taşları, engeller ve çıktılardaki değişiklikler hakkında kolay ve hızlı bir şekilde sohbet edin."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.devops.product_release.board",
|
||||
"translation": "Sürüm zaman çerçevenizi ve sürecinizi desteklemek için ürün çıkarma panosunu kullanın ve herkesin hangi görevlerin bitiş zamanının geldiğini bilmesini sağlayın."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.devops.incident_resolution.description.playbook",
|
||||
"translation": "Kilit ekip üyelerini bir araya getirmek için kontrol listeleri ve otomasyon kullanarak olayın çözüme doğru ilerlemesini paylaşın."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.devops.incident_resolution.description.channel",
|
||||
"translation": "Ekibinizle tek bir kanal kullanarak öncelikler hakkında sohbet edin, paydaşlar ekleyin, güncellemeler yayınlayın ve çözüm için çalışın."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.devops.incident_resolution.description.board",
|
||||
"translation": "Yinelenebilen süreçleri desteklemek ve ekip genelinde tanımlanmış görevler atamak için olay çözümleme panosunu kullanın."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.companywide.goals_and_okrs.integration",
|
||||
"translation": "İşbirliğini kolaylaştırmak için Zoom gibi sık kullandığınız araçları bütünleştirin ve kanalınızdaki üretkenliği artırın. Bunlar sizin için indirilir."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.companywide.goals_and_okrs.channel",
|
||||
"translation": "Hedefleriniz ve ilerlemeniz hakkında ekibinizle farklı zamanlarda ya da gerçek zamanlı olarak sohbet edin ve değişiklikleri tek bir kanaldan izleyerek güncel kalın."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.companywide.goals_and_okrs.board",
|
||||
"translation": "Amaçlar ve anahtar sonuçlar (OKR) panosu ile ekibinizin kurumsal hedeflere doğru ilerlemesini izleyin. Toplantı gündemi panosu ile toplantıları izleyin."
|
||||
},
|
||||
{
|
||||
"id": "model.license_record.is_valid.bytes.app_error",
|
||||
"translation": "Bir lisans yüklenirken bayt değeri geçersiz."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.companywide.create_project.integration",
|
||||
"translation": "Sık kullandığınız araçlar ile bütünleştirerek kanalınızdaki üretkenliği artırın. Bunlar sizin için indirilir."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.companywide.create_project.channel",
|
||||
"translation": "Ekibinizle yeni projeniz üzerine sohbet edin ve işbirlikli bir kanalda projeyi nasıl yapılandıracağınıza karar verin."
|
||||
},
|
||||
{
|
||||
"id": "worktemplate.companywide.create_project.board",
|
||||
"translation": "Proje görevlerinizi ve ilerlemenizi tanımlamak ve izlemek için bir Kanban panosu kullanın."
|
||||
},
|
||||
{
|
||||
"id": "app.user.run.update_status.title",
|
||||
"translation": "Durum güncellemesi"
|
||||
},
|
||||
{
|
||||
"id": "app.user.run.update_status.submit_label",
|
||||
"translation": "Durumu güncelle"
|
||||
},
|
||||
{
|
||||
"id": "app.user.run.update_status.reminder_for_next_update",
|
||||
"translation": "Sonraki güncelleme anımsatıcısı"
|
||||
},
|
||||
{
|
||||
"id": "app.user.run.update_status.num_channel",
|
||||
"translation": {
|
||||
"one": "Paydaşlara bir güncelleme duyurun. Bu gönderi {{.Count}} kanalında yayınlanacak.",
|
||||
"other": "Paydaşlara bir güncelleme duyurun. Bu gönderi {{.Count}} kanalında yayınlanacak."
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "app.user.run.update_status.finish_run.placeholder",
|
||||
"translation": "Ayrıca oyunu da tamamlanmış olarak işaretle"
|
||||
},
|
||||
{
|
||||
"id": "app.user.run.update_status.finish_run",
|
||||
"translation": "Oyunu tamamla"
|
||||
},
|
||||
{
|
||||
"id": "app.user.run.update_status.change_since_last_update",
|
||||
"translation": "Son güncellemeden sonraki değişiklik"
|
||||
},
|
||||
{
|
||||
"id": "app.user.run.status_enable",
|
||||
"translation": "@{{.Username}}, [{{.RunName}}]({{.RunURL}}) için durum güncellemelerini etkinleştirdi"
|
||||
},
|
||||
{
|
||||
"id": "app.user.run.status_disable",
|
||||
"translation": "@{{.Username}}, [{{.RunName}}]({{.RunURL}}) için durum güncellemelerini devre dışı bıraktı"
|
||||
},
|
||||
{
|
||||
"id": "app.user.run.request_update",
|
||||
"translation": "@here — @{{.Name}}, [{{.RunName}}]({{.RunURL}}) için bir durum güncellemesi istedi. \n"
|
||||
},
|
||||
{
|
||||
"id": "app.user.run.request_join_channel",
|
||||
"translation": "@{{.Name}} bir oyun katılımcısı ve bu kanala katılmak istiyor. Kanalın herhangi bir üyesi onu çağırabilir.\n"
|
||||
},
|
||||
{
|
||||
"id": "app.user.run.confirm_finish.title",
|
||||
"translation": "Oyunu tamamlamayı onayla"
|
||||
},
|
||||
{
|
||||
"id": "app.user.run.confirm_finish.submit_label",
|
||||
"translation": "Oyunu tamamla"
|
||||
},
|
||||
{
|
||||
"id": "app.user.run.confirm_finish.num_outstanding",
|
||||
"translation": {
|
||||
"one": "Bekleyen **{{.Count}} görev** var. *{{.RunName}}* oyununu tüm katılımcılar için tamamlamak istediğinize emin misiniz?",
|
||||
"other": "Bekleyen **{{.Count}} görev** var. *{{.RunName}}* oyununu tüm katılımcılar için tamamlamak istediğinize emin misiniz?"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "app.user.run.add_to_timeline.title",
|
||||
"translation": "Oyun zaman akışına ekle"
|
||||
},
|
||||
{
|
||||
"id": "app.user.run.add_to_timeline.summary.placeholder",
|
||||
"translation": "Zaman akışında görüntülenecek kısa açıklama"
|
||||
},
|
||||
{
|
||||
"id": "app.user.run.add_to_timeline.summary.help",
|
||||
"translation": "En fazla 64 karakter"
|
||||
},
|
||||
{
|
||||
"id": "app.user.run.add_to_timeline.summary",
|
||||
"translation": "Özet"
|
||||
},
|
||||
{
|
||||
"id": "app.user.run.add_to_timeline.submit_label",
|
||||
"translation": "Oyun zaman akışına ekle"
|
||||
},
|
||||
{
|
||||
"id": "app.user.run.add_to_timeline.playbook_run",
|
||||
"translation": "Senaryo oyunu"
|
||||
},
|
||||
{
|
||||
"id": "app.user.run.add_checklist_item.title",
|
||||
"translation": "Yeni görev ekle"
|
||||
},
|
||||
{
|
||||
"id": "app.user.run.add_checklist_item.submit_label",
|
||||
"translation": "Görev ekle"
|
||||
},
|
||||
{
|
||||
"id": "app.user.run.add_checklist_item.name",
|
||||
"translation": "Ad"
|
||||
},
|
||||
{
|
||||
"id": "app.user.run.add_checklist_item.description",
|
||||
"translation": "Açıklama"
|
||||
},
|
||||
{
|
||||
"id": "app.user.new_run.title",
|
||||
"translation": "Senaryoyu oyna"
|
||||
},
|
||||
{
|
||||
"id": "app.user.new_run.submit_label",
|
||||
"translation": "Oyunu başlat"
|
||||
},
|
||||
{
|
||||
"id": "app.user.new_run.run_name",
|
||||
"translation": "Oyun adı"
|
||||
},
|
||||
{
|
||||
"id": "app.user.new_run.playbook",
|
||||
"translation": "Senaryo"
|
||||
},
|
||||
{
|
||||
"id": "app.user.new_run.intro",
|
||||
"translation": "**Sahibi** {{.Username}}"
|
||||
},
|
||||
{
|
||||
"id": "app.user.digest.tasks.zero_assigned",
|
||||
"translation": "Size atanmış bir görev yok."
|
||||
},
|
||||
{
|
||||
"id": "app.user.digest.tasks.num_assigned_due_until_today",
|
||||
"translation": {
|
||||
"one": "Süresi dolmuş {{.Count}} atanmış göreviniz var:",
|
||||
"other": "Süresi dolmuş {{.Count}} atanmış göreviniz var:"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "app.user.digest.tasks.num_assigned",
|
||||
"translation": {
|
||||
"one": "{{.Count}} atanmış göreviniz var:",
|
||||
"other": "{{.Count}} atanmış göreviniz var:"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "app.user.digest.tasks.heading",
|
||||
"translation": "Atanmış görevleriniz"
|
||||
},
|
||||
{
|
||||
"id": "app.user.digest.tasks.due_yesterday",
|
||||
"translation": "Süresi dün doldu"
|
||||
},
|
||||
{
|
||||
"id": "app.user.digest.tasks.due_x_days_ago",
|
||||
"translation": "Süresi {{.Count}} gün önce doldu"
|
||||
},
|
||||
{
|
||||
"id": "app.user.digest.tasks.due_today",
|
||||
"translation": "Süresi bugün dolacak"
|
||||
},
|
||||
{
|
||||
"id": "app.user.digest.tasks.due_in_x_days",
|
||||
"translation": {
|
||||
"one": "{{.Count}} gün içinde süresi dolacak",
|
||||
"other": "{{.Count}} gün içinde süresi dolacak"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "app.user.digest.tasks.due_after_today",
|
||||
"translation": {
|
||||
"one": "Bugünden sonra süresi dolacak **{{.Count}} göreviniz var**.",
|
||||
"other": "Bugünden sonra süresi dolacak **{{.Count}} göreviniz var**."
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "app.user.digest.tasks.all_tasks_command",
|
||||
"translation": "Tüm görevlerinizi görüntülemek için `/playbook todo` kullanın."
|
||||
},
|
||||
{
|
||||
"id": "app.user.digest.runs_in_progress.zero_in_progress",
|
||||
"translation": "Süren bir oyununuz yok."
|
||||
},
|
||||
{
|
||||
"id": "app.user.digest.runs_in_progress.num_in_progress",
|
||||
"translation": {
|
||||
"one": "Süren {{.Count}} oyununuz var:",
|
||||
"other": "Süren {{.Count}} oyununuz var:"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "app.user.digest.runs_in_progress.heading",
|
||||
"translation": "Süren oyunlar"
|
||||
},
|
||||
{
|
||||
"id": "app.user.digest.overdue_status_updates.zero_overdue",
|
||||
"translation": "Gecikmiş bir oyununuz yok."
|
||||
},
|
||||
{
|
||||
"id": "app.user.digest.overdue_status_updates.num_overdue",
|
||||
"translation": {
|
||||
"one": "Bir durum güncellemesi için {{.Count}} oyun gecikmeniz var:",
|
||||
"other": "Bir durum güncellemesi için {{.Count}} oyun gecikmeniz var:"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "app.user.digest.overdue_status_updates.heading",
|
||||
"translation": "Gecikmiş durum güncellemeleri"
|
||||
},
|
||||
{
|
||||
"id": "app.command.execute.error",
|
||||
"translation": "Komut yürütülemedi."
|
||||
},
|
||||
{
|
||||
"id": "api.server.cws.subscribe_to_newsletter.app_error",
|
||||
"translation": "CWS sunucusu duyurulara abone olamadı."
|
||||
},
|
||||
{
|
||||
"id": "api.license.request-trial.bad-request.business-email",
|
||||
"translation": "Deneme için iş e-postası geçersiz"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -45,8 +45,19 @@ func (seb *Broker) GetActiveEngines() []SearchEngineInterface {
|
||||
if seb.ElasticsearchEngine != nil && seb.ElasticsearchEngine.IsActive() {
|
||||
engines = append(engines, seb.ElasticsearchEngine)
|
||||
}
|
||||
if seb.BleveEngine != nil && seb.BleveEngine.IsActive() {
|
||||
if seb.BleveEngine != nil && seb.BleveEngine.IsActive() && seb.BleveEngine.IsIndexingEnabled() {
|
||||
engines = append(engines, seb.BleveEngine)
|
||||
}
|
||||
return engines
|
||||
}
|
||||
|
||||
func (seb *Broker) ActiveEngine() string {
|
||||
activeEngines := seb.GetActiveEngines()
|
||||
if len(activeEngines) > 0 {
|
||||
return activeEngines[0].GetName()
|
||||
}
|
||||
if *seb.cfg.SqlSettings.DisableDatabaseSearch {
|
||||
return "none"
|
||||
}
|
||||
return "database"
|
||||
}
|
||||
|
||||
42
server/platform/services/searchengine/searchengine_test.go
Обычный файл
42
server/platform/services/searchengine/searchengine_test.go
Обычный файл
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package searchengine
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/mattermost/mattermost-server/v6/server/platform/services/searchengine/mocks"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestActiveEngine(t *testing.T) {
|
||||
cfg := &model.Config{}
|
||||
cfg.SetDefaults()
|
||||
|
||||
b := NewBroker(cfg)
|
||||
|
||||
esMock := &mocks.SearchEngineInterface{}
|
||||
esMock.On("IsActive").Return(true)
|
||||
esMock.On("GetName").Return("elasticsearch")
|
||||
|
||||
bleveMock := &mocks.SearchEngineInterface{}
|
||||
bleveMock.On("IsActive").Return(true)
|
||||
bleveMock.On("IsIndexingEnabled").Return(true)
|
||||
bleveMock.On("GetName").Return("bleve")
|
||||
|
||||
assert.Equal(t, "database", b.ActiveEngine())
|
||||
|
||||
b.ElasticsearchEngine = esMock
|
||||
assert.Equal(t, "elasticsearch", b.ActiveEngine())
|
||||
|
||||
b.ElasticsearchEngine = nil
|
||||
b.BleveEngine = bleveMock
|
||||
assert.Equal(t, "bleve", b.ActiveEngine())
|
||||
|
||||
b.BleveEngine = nil
|
||||
*b.cfg.SqlSettings.DisableDatabaseSearch = true
|
||||
|
||||
assert.Equal(t, "none", b.ActiveEngine())
|
||||
}
|
||||
@@ -1405,6 +1405,10 @@ paths:
|
||||
type: boolean
|
||||
description: A boolean indicating whether the playbook runs created from this playbook should be public or private.
|
||||
example: true
|
||||
public:
|
||||
type: boolean
|
||||
description: A boolean indicating whether the playbook is licensed as public or private. Required 'true' for free tier.
|
||||
example: true
|
||||
checklists:
|
||||
type: array
|
||||
description: The stages defined by this playbook.
|
||||
|
||||
Ссылка в новой задаче
Block a user