1
0
mirror of https://github.com/mattermost/focalboard.git synced 2024-12-24 13:43:12 +02:00

Merge branch 'main' into MM-47238_boards-dev-server-url

This commit is contained in:
Harrison Healey 2022-10-25 17:24:54 -04:00
commit 8fe9b9b2c2
78 changed files with 850 additions and 491 deletions

View File

@ -2,7 +2,7 @@
name: Documentation Request
about: Request improvement to our documentation
title: 'Doc: '
labels: Documentation
labels: Documentation, Triage
assignees: ''
---

View File

@ -2,7 +2,7 @@
name: Enhancement/Feature Idea
about: Suggest a new capability
title: 'Feature Idea: '
labels: Enhancement
labels: Enhancement, Triage
assignees: ''
---

View File

@ -27,7 +27,7 @@ func normalizeAppErr(appErr *mm_model.AppError) error {
// serviceAPIAdapter is an adapter that flattens the APIs provided by suite services so they can
// be used as per the Plugin API.
// Note: when supporting a plugin build is no longer needed this adapter may be removed as the Boards app
// can be modified to use the services in modular fashion.
// can be modified to use the services in modular fashion.
type serviceAPIAdapter struct {
api *boardsProduct
ctx *request.Context
@ -123,6 +123,10 @@ func (a *serviceAPIAdapter) CreateMember(teamID string, userID string) (*mm_mode
// Permissions service.
//
func (a *serviceAPIAdapter) HasPermissionTo(userID string, permission *mm_model.Permission) bool {
return a.api.permissionsService.HasPermissionTo(userID, permission)
}
func (a *serviceAPIAdapter) HasPermissionToTeam(userID, teamID string, permission *mm_model.Permission) bool {
return a.api.permissionsService.HasPermissionToTeam(userID, teamID, permission)
}
@ -134,6 +138,7 @@ func (a *serviceAPIAdapter) HasPermissionToChannel(askingUserID string, channelI
//
// Bot service.
//
func (a *serviceAPIAdapter) EnsureBot(bot *mm_model.Bot) (string, error) {
return a.api.botService.EnsureBot(a.ctx, boardsProductID, bot)
}
@ -141,6 +146,7 @@ func (a *serviceAPIAdapter) EnsureBot(bot *mm_model.Bot) (string, error) {
//
// License service.
//
func (a *serviceAPIAdapter) GetLicense() *mm_model.License {
return a.api.licenseService.GetLicense()
}
@ -148,6 +154,7 @@ func (a *serviceAPIAdapter) GetLicense() *mm_model.License {
//
// FileInfoStore service.
//
func (a *serviceAPIAdapter) GetFileInfo(fileID string) (*mm_model.FileInfo, error) {
fi, appErr := a.api.fileInfoStoreService.GetFileInfo(fileID)
return fi, normalizeAppErr(appErr)
@ -156,6 +163,7 @@ func (a *serviceAPIAdapter) GetFileInfo(fileID string) (*mm_model.FileInfo, erro
//
// Cluster store.
//
func (a *serviceAPIAdapter) PublishWebSocketEvent(event string, payload map[string]interface{}, broadcast *mm_model.WebsocketBroadcast) {
a.api.clusterService.PublishWebSocketEvent(boardsProductID, event, payload, broadcast)
}
@ -167,6 +175,7 @@ func (a *serviceAPIAdapter) PublishPluginClusterEvent(ev mm_model.PluginClusterE
//
// Cloud service.
//
func (a *serviceAPIAdapter) GetCloudLimits() (*mm_model.ProductLimits, error) {
return a.api.cloudService.GetCloudLimits()
}
@ -174,6 +183,7 @@ func (a *serviceAPIAdapter) GetCloudLimits() (*mm_model.ProductLimits, error) {
//
// Config service.
//
func (a *serviceAPIAdapter) GetConfig() *mm_model.Config {
return a.api.configService.Config()
}
@ -181,6 +191,7 @@ func (a *serviceAPIAdapter) GetConfig() *mm_model.Config {
//
// Logger service.
//
func (a *serviceAPIAdapter) GetLogger() mlog.LoggerIFace {
return a.api.logger
}
@ -188,6 +199,7 @@ func (a *serviceAPIAdapter) GetLogger() mlog.LoggerIFace {
//
// KVStore service.
//
func (a *serviceAPIAdapter) KVSetWithOptions(key string, value []byte, options mm_model.PluginKVSetOptions) (bool, error) {
b, appErr := a.api.kvStoreService.SetPluginKeyWithOptions(boardsProductID, key, value, options)
return b, normalizeAppErr(appErr)
@ -196,6 +208,7 @@ func (a *serviceAPIAdapter) KVSetWithOptions(key string, value []byte, options m
//
// Store service.
//
func (a *serviceAPIAdapter) GetMasterDB() (*sql.DB, error) {
return a.api.storeService.GetMasterDB(), nil
}
@ -203,6 +216,7 @@ func (a *serviceAPIAdapter) GetMasterDB() (*sql.DB, error) {
//
// System service.
//
func (a *serviceAPIAdapter) GetDiagnosticID() string {
return a.api.systemService.GetDiagnosticId()
}
@ -210,6 +224,7 @@ func (a *serviceAPIAdapter) GetDiagnosticID() string {
//
// Router service.
//
func (a *serviceAPIAdapter) RegisterRouter(sub *mux.Router) {
a.api.routerService.RegisterRouter(boardsProductName, sub)
}
@ -217,6 +232,7 @@ func (a *serviceAPIAdapter) RegisterRouter(sub *mux.Router) {
//
// Preferences service.
//
func (a *serviceAPIAdapter) GetPreferencesForUser(userID string) (mm_model.Preferences, error) {
p, appErr := a.api.preferencesService.GetPreferencesForUser(userID)
return p, normalizeAppErr(appErr)

View File

@ -125,6 +125,10 @@ func (a *pluginAPIAdapter) CreateMember(teamID string, userID string) (*mm_model
// Permissions service.
//
func (a *pluginAPIAdapter) HasPermissionTo(userID string, permission *mm_model.Permission) bool {
return a.api.HasPermissionTo(userID, permission)
}
func (a *pluginAPIAdapter) HasPermissionToTeam(userID, teamID string, permission *mm_model.Permission) bool {
return a.api.HasPermissionToTeam(userID, teamID, permission)
}
@ -136,6 +140,7 @@ func (a *pluginAPIAdapter) HasPermissionToChannel(askingUserID string, channelID
//
// Bot service.
//
func (a *pluginAPIAdapter) EnsureBot(bot *mm_model.Bot) (string, error) {
return a.api.EnsureBotUser(bot)
}
@ -143,6 +148,7 @@ func (a *pluginAPIAdapter) EnsureBot(bot *mm_model.Bot) (string, error) {
//
// License service.
//
func (a *pluginAPIAdapter) GetLicense() *mm_model.License {
return a.api.GetLicense()
}
@ -150,6 +156,7 @@ func (a *pluginAPIAdapter) GetLicense() *mm_model.License {
//
// FileInfoStore service.
//
func (a *pluginAPIAdapter) GetFileInfo(fileID string) (*mm_model.FileInfo, error) {
fi, appErr := a.api.GetFileInfo(fileID)
return fi, normalizeAppErr(appErr)
@ -158,6 +165,7 @@ func (a *pluginAPIAdapter) GetFileInfo(fileID string) (*mm_model.FileInfo, error
//
// Cluster store.
//
func (a *pluginAPIAdapter) PublishWebSocketEvent(event string, payload map[string]interface{}, broadcast *mm_model.WebsocketBroadcast) {
a.api.PublishWebSocketEvent(event, payload, broadcast)
}
@ -169,6 +177,7 @@ func (a *pluginAPIAdapter) PublishPluginClusterEvent(ev mm_model.PluginClusterEv
//
// Cloud service.
//
func (a *pluginAPIAdapter) GetCloudLimits() (*mm_model.ProductLimits, error) {
return a.api.GetCloudLimits()
}
@ -176,6 +185,7 @@ func (a *pluginAPIAdapter) GetCloudLimits() (*mm_model.ProductLimits, error) {
//
// Config service.
//
func (a *pluginAPIAdapter) GetConfig() *mm_model.Config {
return a.api.GetUnsanitizedConfig()
}
@ -183,6 +193,7 @@ func (a *pluginAPIAdapter) GetConfig() *mm_model.Config {
//
// Logger service.
//
func (a *pluginAPIAdapter) GetLogger() mlog.LoggerIFace {
return a.logger
}
@ -190,6 +201,7 @@ func (a *pluginAPIAdapter) GetLogger() mlog.LoggerIFace {
//
// KVStore service.
//
func (a *pluginAPIAdapter) KVSetWithOptions(key string, value []byte, options mm_model.PluginKVSetOptions) (bool, error) {
b, appErr := a.api.KVSetWithOptions(key, value, options)
return b, normalizeAppErr(appErr)
@ -198,6 +210,7 @@ func (a *pluginAPIAdapter) KVSetWithOptions(key string, value []byte, options mm
//
// Store service.
//
func (a *pluginAPIAdapter) GetMasterDB() (*sql.DB, error) {
return a.storeService.GetMasterDB()
}
@ -205,6 +218,7 @@ func (a *pluginAPIAdapter) GetMasterDB() (*sql.DB, error) {
//
// System service.
//
func (a *pluginAPIAdapter) GetDiagnosticID() string {
return a.api.GetDiagnosticId()
}
@ -212,6 +226,7 @@ func (a *pluginAPIAdapter) GetDiagnosticID() string {
//
// Router service.
//
func (a *pluginAPIAdapter) RegisterRouter(sub *mux.Router) {
// NOOP for plugin
}
@ -219,6 +234,7 @@ func (a *pluginAPIAdapter) RegisterRouter(sub *mux.Router) {
//
// Preferences service.
//
func (a *pluginAPIAdapter) GetPreferencesForUser(userID string) (mm_model.Preferences, error) {
preferences, appErr := a.api.GetPreferencesForUser(userID)
if appErr != nil {

View File

@ -91,11 +91,11 @@ func (a *appAPI) init(store store.Store, app appIface) {
a.app = app
}
func (a *appAPI) GetBlockHistory(blockID string, opts model.QueryBlockHistoryOptions) ([]model.Block, error) {
func (a *appAPI) GetBlockHistory(blockID string, opts model.QueryBlockHistoryOptions) ([]*model.Block, error) {
return a.store.GetBlockHistory(blockID, opts)
}
func (a *appAPI) GetSubTree2(boardID, blockID string, opts model.QuerySubtreeOptions) ([]model.Block, error) {
func (a *appAPI) GetSubTree2(boardID, blockID string, opts model.QuerySubtreeOptions) ([]*model.Block, error) {
return a.store.GetSubTree2(boardID, blockID, opts)
}

View File

@ -3,12 +3,12 @@
import React from 'react'
import {Provider as ReduxProvider} from 'react-redux'
import {render} from '@testing-library/react'
import {act, render} from '@testing-library/react'
import {mocked} from 'jest-mock'
import thunk from 'redux-thunk'
import octoClient from '../../../../webapp/src/octoClient'
import {createBoard} from '../../../../webapp/src/blocks/board'
import {BoardMember, createBoard} from '../../../../webapp/src/blocks/board'
import {mockStateStore, wrapIntl} from '../../../../webapp/src/testUtils'
import RHSChannelBoards from './rhsChannelBoards'
@ -26,6 +26,10 @@ describe('components/rhsChannelBoards', () => {
board1.channelId = 'channel-id'
board3.channelId = 'channel-id'
const boardMembership1 = {boardId: board1.id, userId: 'user-id'} as BoardMember
const boardMembership2 = {boardId: board2.id, userId: 'user-id'} as BoardMember
const boardMembership3 = {boardId: board3.id, userId: 'user-id'} as BoardMember
const team = {
id: 'team-id',
name: 'team',
@ -52,9 +56,9 @@ describe('components/rhsChannelBoards', () => {
[board3.id]: board3,
},
myBoardMemberships: {
[board1.id]: {boardId: board1.id, userId: 'user-id'},
[board2.id]: {boardId: board2.id, userId: 'user-id'},
[board3.id]: {boardId: board3.id, userId: 'user-id'},
[board1.id]: boardMembership1,
[board2.id]: boardMembership2,
[board3.id]: boardMembership3,
},
},
channels: {
@ -69,27 +73,40 @@ describe('components/rhsChannelBoards', () => {
beforeEach(() => {
mockedOctoClient.getBoards.mockResolvedValue([board1, board2, board3])
mockedOctoClient.getMyBoardMemberships.mockResolvedValue([boardMembership1, boardMembership2, boardMembership3])
jest.clearAllMocks()
})
it('renders the RHS for channel boards', async () => {
const store = mockStateStore([thunk], state)
const {container} = render(wrapIntl(
<ReduxProvider store={store}>
<RHSChannelBoards/>
</ReduxProvider>
))
let container: Element | DocumentFragment | null = null
await act(async () => {
const result = render(wrapIntl(
<ReduxProvider store={store}>
<RHSChannelBoards/>
</ReduxProvider>
))
container = result.container
})
expect(container).toMatchSnapshot()
})
it('renders with empty list of boards', async () => {
const localState = {...state, boards: {...state.boards, boards: {}}}
const store = mockStateStore([thunk], localState)
const {container} = render(wrapIntl(
<ReduxProvider store={store}>
<RHSChannelBoards/>
</ReduxProvider>
))
let container: Element | DocumentFragment | null = null
await act(async () => {
const result = render(wrapIntl(
<ReduxProvider store={store}>
<RHSChannelBoards/>
</ReduxProvider>
))
container = result.container
})
expect(container).toMatchSnapshot()
})
})

View File

@ -12,7 +12,7 @@ import {Board, BoardMember} from '../../../../webapp/src/blocks/board'
import {getCurrentTeamId} from '../../../../webapp/src/store/teams'
import {IUser} from '../../../../webapp/src/user'
import {getMe, fetchMe} from '../../../../webapp/src/store/users'
import {loadBoards} from '../../../../webapp/src/store/initialLoad'
import {loadBoards, loadMyBoardsMemberships} from '../../../../webapp/src/store/initialLoad'
import {getCurrentChannel} from '../../../../webapp/src/store/channels'
import {
getMySortedBoards,
@ -41,10 +41,14 @@ const RHSChannelBoards = () => {
const me = useAppSelector<IUser|null>(getMe)
const dispatch = useAppDispatch()
const intl = useIntl()
const [dataLoaded, setDataLoaded] = React.useState(false)
useEffect(() => {
dispatch(loadBoards())
dispatch(fetchMe())
Promise.all([
dispatch(loadBoards()),
dispatch(loadMyBoardsMemberships()),
dispatch(fetchMe()),
]).then(() => setDataLoaded(true))
}, [])
useWebsockets(teamId || '', (wsClient: WSClient) => {
@ -78,6 +82,10 @@ const RHSChannelBoards = () => {
if (!currentChannel) {
return null
}
if (!dataLoaded) {
return null
}
const channelBoards = boards.filter((b) => b.channelId === currentChannel.id)
let channelName = currentChannel.display_name

View File

@ -352,6 +352,30 @@ export default class Plugin {
return data
})
}
// Site statistics handler
if (registry.registerSiteStatisticsHandler) {
registry.registerSiteStatisticsHandler(async () => {
const siteStats = await octoClient.getSiteStatistics()
if(siteStats){
return {
boards_count: {
name: intl.formatMessage({id: 'SiteStats.total_boards', defaultMessage: 'Total Boards'}),
id: 'total_boards',
icon: 'icon-product-boards',
value: siteStats.board_count,
},
cards_count: {
name: intl.formatMessage({id: 'SiteStats.total_cards', defaultMessage: 'Total Cards'}),
id: 'total_cards',
icon: 'icon-products',
value: siteStats.card_count,
},
}
}
return {}
})
}
}
this.boardSelectorId = this.registry.registerRootComponent((props: {webSocketClient: MMWebSocketClient}) => (

View File

@ -18,6 +18,7 @@ export interface PluginRegistry {
registerRightHandSidebarComponent(component: React.ElementType, title: React.Element)
registerRootComponent(component: React.ElementType)
registerInsightsHandler(handler: (timeRange: string, page: number, perPage: number, teamId: string, insightType: string) => void)
registerSiteStatisticsHandler(handler: () => void)
// Add more if needed from https://developers.mattermost.com/extend/plugins/webapp/reference
}

View File

@ -116,7 +116,7 @@ const config = {
type: 'asset/resource',
generator: {
filename: '[name][ext]',
publicPath: '/static/',
publicPath: TARGET_IS_PRODUCT ? undefined : '/static/',
}
},
],
@ -205,12 +205,6 @@ config.plugins.push(new webpack.DefinePlugin({
if (NPM_TARGET === 'start:product') {
const url = new URL(process.env.MM_BOARDS_DEV_SERVER_URL ?? 'http://localhost:9006');
for (const rule of config.module.rules) {
if (rule.type === 'asset/resource' && rule.generator) {
rule.generator.publicPath = url.toString() + 'static/';
}
}
config.devServer = {
https: url.protocol === 'https:' && {
minVersion: process.env.MM_SERVICESETTINGS_TLSMINVER,

View File

@ -95,6 +95,7 @@ func (a *API) RegisterRoutes(r *mux.Router) {
a.registerTemplatesRoutes(apiv2)
a.registerBoardsRoutes(apiv2)
a.registerBlocksRoutes(apiv2)
a.registerStatisticsRoutes(apiv2)
// V3 routes
a.registerCardsRoutes(apiv2)

View File

@ -121,7 +121,7 @@ func (a *API) handleGetBlocks(w http.ResponseWriter, r *http.Request) {
auditRec.AddMeta("all", all)
auditRec.AddMeta("blockID", blockID)
var blocks []model.Block
var blocks []*model.Block
var block *model.Block
switch {
case all != "":
@ -142,7 +142,7 @@ func (a *API) handleGetBlocks(w http.ResponseWriter, r *http.Request) {
return
}
blocks = append(blocks, *block)
blocks = append(blocks, block)
default:
blocks, err = a.app.GetBlocks(boardID, parentID, blockType)
if err != nil {
@ -233,7 +233,7 @@ func (a *API) handlePostBlocks(w http.ResponseWriter, r *http.Request) {
return
}
var blocks []model.Block
var blocks []*model.Block
err = json.Unmarshal(requestBody, &blocks)
if err != nil {

70
server/api/statistics.go Normal file
View File

@ -0,0 +1,70 @@
package api
import (
"encoding/json"
"net/http"
"github.com/gorilla/mux"
"github.com/mattermost/focalboard/server/model"
mmModel "github.com/mattermost/mattermost-server/v6/model"
)
func (a *API) registerStatisticsRoutes(r *mux.Router) {
// statistics
r.HandleFunc("/statistics", a.sessionRequired(a.handleStatistics)).Methods("GET")
}
func (a *API) handleStatistics(w http.ResponseWriter, r *http.Request) {
// swagger:operation GET /statistics handleStatistics
//
// Fetches the statistic of the server.
//
// ---
// produces:
// - application/json
// security:
// - BearerAuth: []
// responses:
// '200':
// description: success
// schema:
// "$ref": "#/definitions/BoardStatistics"
// default:
// description: internal error
// schema:
// "$ref": "#/definitions/ErrorResponse"
if !a.MattermostAuth {
a.errorResponse(w, r, model.NewErrNotImplemented("not permitted in standalone mode"))
return
}
// user must have right to access analytics
userID := getUserID(r)
if !a.permissions.HasPermissionTo(userID, mmModel.PermissionGetAnalytics) {
a.errorResponse(w, r, model.NewErrPermission("access denied System Statistics"))
return
}
boardCount, err := a.app.GetBoardCount()
if err != nil {
a.errorResponse(w, r, err)
return
}
cardCount, err := a.app.GetUsedCardsCount()
if err != nil {
a.errorResponse(w, r, err)
return
}
stats := model.BoardsStatistics{
Boards: int(boardCount),
Cards: cardCount,
}
data, err := json.Marshal(stats)
if err != nil {
a.errorResponse(w, r, err)
return
}
jsonBytesResponse(w, http.StatusOK, data)
}

View File

@ -14,9 +14,9 @@ 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) {
func (a *App) GetBlocks(boardID, parentID string, blockType string) ([]*model.Block, error) {
if boardID == "" {
return []model.Block{}, nil
return []*model.Block{}, nil
}
if blockType != "" && parentID != "" {
@ -30,7 +30,7 @@ func (a *App) GetBlocks(boardID, parentID string, blockType string) ([]model.Blo
return a.store.GetBlocksWithParent(boardID, parentID)
}
func (a *App) DuplicateBlock(boardID string, blockID string, userID string, asTemplate bool) ([]model.Block, error) {
func (a *App) DuplicateBlock(boardID string, blockID string, userID string, asTemplate bool) ([]*model.Block, error) {
board, err := a.GetBoard(boardID)
if err != nil {
return nil, err
@ -74,7 +74,7 @@ func (a *App) PatchBlockAndNotify(blockID string, blockPatch *model.BlockPatch,
}
if a.IsCloudLimited() {
containsLimitedBlocks, lErr := a.ContainsLimitedBlocks([]model.Block{*oldBlock})
containsLimitedBlocks, lErr := a.ContainsLimitedBlocks([]*model.Block{oldBlock})
if lErr != nil {
return nil, lErr
}
@ -100,10 +100,10 @@ func (a *App) PatchBlockAndNotify(blockID string, blockPatch *model.BlockPatch,
}
a.blockChangeNotifier.Enqueue(func() error {
// broadcast on websocket
a.wsAdapter.BroadcastBlockChange(board.TeamID, *block)
a.wsAdapter.BroadcastBlockChange(board.TeamID, block)
// broadcast on webhooks
a.webhook.NotifyUpdate(*block)
a.webhook.NotifyUpdate(block)
// send notifications
if !disableNotify {
@ -145,10 +145,10 @@ func (a *App) PatchBlocksAndNotify(teamID string, blockPatches *model.BlockPatch
if err != nil {
return err
}
a.wsAdapter.BroadcastBlockChange(teamID, *newBlock)
a.webhook.NotifyUpdate(*newBlock)
a.wsAdapter.BroadcastBlockChange(teamID, newBlock)
a.webhook.NotifyUpdate(newBlock)
if !disableNotify {
a.notifyBlockChanged(notify.Update, newBlock, &oldBlocks[i], modifiedByID)
a.notifyBlockChanged(notify.Update, newBlock, oldBlocks[i], modifiedByID)
}
}
return nil
@ -156,24 +156,24 @@ func (a *App) PatchBlocksAndNotify(teamID string, blockPatches *model.BlockPatch
return nil
}
func (a *App) InsertBlock(block model.Block, modifiedByID string) error {
func (a *App) InsertBlock(block *model.Block, modifiedByID string) error {
return a.InsertBlockAndNotify(block, modifiedByID, false)
}
func (a *App) InsertBlockAndNotify(block model.Block, modifiedByID string, disableNotify bool) error {
func (a *App) InsertBlockAndNotify(block *model.Block, modifiedByID string, disableNotify bool) error {
board, bErr := a.store.GetBoard(block.BoardID)
if bErr != nil {
return bErr
}
err := a.store.InsertBlock(&block, modifiedByID)
err := a.store.InsertBlock(block, modifiedByID)
if err == nil {
a.blockChangeNotifier.Enqueue(func() error {
a.wsAdapter.BroadcastBlockChange(board.TeamID, block)
a.metrics.IncrementBlocksInserted(1)
a.webhook.NotifyUpdate(block)
if !disableNotify {
a.notifyBlockChanged(notify.Add, &block, nil, modifiedByID)
a.notifyBlockChanged(notify.Add, block, nil, modifiedByID)
}
return nil
})
@ -191,7 +191,7 @@ func (a *App) InsertBlockAndNotify(block model.Block, modifiedByID string, disab
return err
}
func (a *App) isWithinViewsLimit(boardID string, block model.Block) (bool, error) {
func (a *App) isWithinViewsLimit(boardID string, block *model.Block) (bool, error) {
limits, err := a.GetBoardsCloudLimits()
if err != nil {
return false, err
@ -213,13 +213,13 @@ func (a *App) isWithinViewsLimit(boardID string, block model.Block) (bool, error
return len(views) < limits.Views, nil
}
func (a *App) InsertBlocks(blocks []model.Block, modifiedByID string) ([]model.Block, error) {
func (a *App) InsertBlocks(blocks []*model.Block, modifiedByID string) ([]*model.Block, error) {
return a.InsertBlocksAndNotify(blocks, modifiedByID, false)
}
func (a *App) InsertBlocksAndNotify(blocks []model.Block, modifiedByID string, disableNotify bool) ([]model.Block, error) {
func (a *App) InsertBlocksAndNotify(blocks []*model.Block, modifiedByID string, disableNotify bool) ([]*model.Block, error) {
if len(blocks) == 0 {
return []model.Block{}, nil
return []*model.Block{}, nil
}
// all blocks must belong to the same board
@ -235,7 +235,7 @@ func (a *App) InsertBlocksAndNotify(blocks []model.Block, modifiedByID string, d
return nil, err
}
needsNotify := make([]model.Block, 0, len(blocks))
needsNotify := make([]*model.Block, 0, len(blocks))
for i := range blocks {
// this check is needed to whitelist inbuilt template
// initialization. They do contain more than 5 views per board.
@ -251,7 +251,7 @@ func (a *App) InsertBlocksAndNotify(blocks []model.Block, modifiedByID string, d
}
}
err := a.store.InsertBlock(&blocks[i], modifiedByID)
err := a.store.InsertBlock(blocks[i], modifiedByID)
if err != nil {
return nil, err
}
@ -266,7 +266,7 @@ func (a *App) InsertBlocksAndNotify(blocks []model.Block, modifiedByID string, d
block := b
a.webhook.NotifyUpdate(block)
if !disableNotify {
a.notifyBlockChanged(notify.Add, &block, nil, modifiedByID)
a.notifyBlockChanged(notify.Add, block, nil, modifiedByID)
}
}
return nil
@ -284,7 +284,7 @@ func (a *App) InsertBlocksAndNotify(blocks []model.Block, modifiedByID string, d
return blocks, nil
}
func (a *App) CopyCardFiles(sourceBoardID string, copiedBlocks []model.Block) error {
func (a *App) CopyCardFiles(sourceBoardID string, copiedBlocks []*model.Block) error {
// Images attached in cards have a path comprising the card's board ID.
// When we create a template from this board, we need to copy the files
// with the new board ID in path.
@ -416,7 +416,7 @@ func (a *App) GetLastBlockHistoryEntry(blockID string) (*model.Block, error) {
if len(blocks) == 0 {
return nil, nil
}
return &blocks[0], nil
return blocks[0], nil
}
func (a *App) UndeleteBlock(blockID string, modifiedBy string) (*model.Block, error) {
@ -450,9 +450,9 @@ func (a *App) UndeleteBlock(blockID string, modifiedBy string) (*model.Block, er
}
a.blockChangeNotifier.Enqueue(func() error {
a.wsAdapter.BroadcastBlockChange(board.TeamID, *block)
a.wsAdapter.BroadcastBlockChange(board.TeamID, block)
a.metrics.IncrementBlocksInserted(1)
a.webhook.NotifyUpdate(*block)
a.webhook.NotifyUpdate(block)
a.notifyBlockChanged(notify.Add, block, nil, modifiedBy)
return nil
@ -474,7 +474,7 @@ func (a *App) GetBlockCountsByType() (map[string]int64, error) {
return a.store.GetBlockCountsByType()
}
func (a *App) GetBlocksForBoard(boardID string) ([]model.Block, error) {
func (a *App) GetBlocksForBoard(boardID string) ([]*model.Block, error) {
return a.store.GetBlocksForBoard(boardID)
}

View File

@ -28,10 +28,10 @@ func TestInsertBlock(t *testing.T) {
t.Run("success scenario", func(t *testing.T) {
boardID := testBoardID
block := model.Block{BoardID: boardID}
block := &model.Block{BoardID: boardID}
board := &model.Board{ID: boardID}
th.Store.EXPECT().GetBoard(boardID).Return(board, nil)
th.Store.EXPECT().InsertBlock(&block, "user-id-1").Return(nil)
th.Store.EXPECT().InsertBlock(block, "user-id-1").Return(nil)
th.Store.EXPECT().GetMembersForBoard(boardID).Return([]*model.BoardMember{}, nil)
err := th.App.InsertBlock(block, "user-id-1")
require.NoError(t, err)
@ -39,10 +39,10 @@ func TestInsertBlock(t *testing.T) {
t.Run("error scenario", func(t *testing.T) {
boardID := testBoardID
block := model.Block{BoardID: boardID}
block := &model.Block{BoardID: boardID}
board := &model.Board{ID: boardID}
th.Store.EXPECT().GetBoard(boardID).Return(board, nil)
th.Store.EXPECT().InsertBlock(&block, "user-id-1").Return(blockError{"error"})
th.Store.EXPECT().InsertBlock(block, "user-id-1").Return(blockError{"error"})
err := th.App.InsertBlock(block, "user-id-1")
require.Error(t, err, "error")
})
@ -60,10 +60,10 @@ func TestPatchBlocks(t *testing.T) {
},
}
block1 := model.Block{ID: "block1"}
th.Store.EXPECT().GetBlocksByIDs([]string{"block1"}).Return([]model.Block{block1}, nil)
block1 := &model.Block{ID: "block1"}
th.Store.EXPECT().GetBlocksByIDs([]string{"block1"}).Return([]*model.Block{block1}, nil)
th.Store.EXPECT().PatchBlocks(gomock.Eq(&blockPatches), gomock.Eq("user-id-1")).Return(nil)
th.Store.EXPECT().GetBlock("block1").Return(&block1, nil)
th.Store.EXPECT().GetBlock("block1").Return(block1, nil)
// this call comes from the WS server notification
th.Store.EXPECT().GetMembersForBoard(gomock.Any()).Times(1)
err := th.App.PatchBlocks("team-id", &blockPatches, "user-id-1")
@ -91,7 +91,7 @@ func TestPatchBlocks(t *testing.T) {
},
}
block1 := model.Block{
block1 := &model.Block{
ID: "block1",
Type: model.TypeCard,
ParentID: "board-id",
@ -104,7 +104,7 @@ func TestPatchBlocks(t *testing.T) {
Type: model.BoardTypeOpen,
}
th.Store.EXPECT().GetBlocksByIDs([]string{"block1"}).Return([]model.Block{block1}, nil)
th.Store.EXPECT().GetBlocksByIDs([]string{"block1"}).Return([]*model.Block{block1}, nil)
th.Store.EXPECT().GetBoard("board-id").Return(board1, nil)
th.Store.EXPECT().GetLicense().Return(fakeLicense)
th.Store.EXPECT().GetCardLimitTimestamp().Return(int64(150), nil)
@ -120,11 +120,11 @@ func TestDeleteBlock(t *testing.T) {
t.Run("success scenario", func(t *testing.T) {
boardID := testBoardID
board := &model.Board{ID: boardID}
block := model.Block{
block := &model.Block{
ID: "block-id",
BoardID: board.ID,
}
th.Store.EXPECT().GetBlock(gomock.Eq("block-id")).Return(&block, nil)
th.Store.EXPECT().GetBlock(gomock.Eq("block-id")).Return(block, nil)
th.Store.EXPECT().DeleteBlock(gomock.Eq("block-id"), gomock.Eq("user-id-1")).Return(nil)
th.Store.EXPECT().GetBoard(gomock.Eq(testBoardID)).Return(board, nil)
th.Store.EXPECT().GetMembersForBoard(boardID).Return([]*model.BoardMember{}, nil)
@ -135,11 +135,11 @@ func TestDeleteBlock(t *testing.T) {
t.Run("error scenario", func(t *testing.T) {
boardID := testBoardID
board := &model.Board{ID: boardID}
block := model.Block{
block := &model.Block{
ID: "block-id",
BoardID: board.ID,
}
th.Store.EXPECT().GetBlock(gomock.Eq("block-id")).Return(&block, nil)
th.Store.EXPECT().GetBlock(gomock.Eq("block-id")).Return(block, nil)
th.Store.EXPECT().DeleteBlock(gomock.Eq("block-id"), gomock.Eq("user-id-1")).Return(blockError{"error"})
th.Store.EXPECT().GetBoard(gomock.Eq(testBoardID)).Return(board, nil)
err := th.App.DeleteBlock("block-id", "user-id-1")
@ -154,16 +154,16 @@ func TestUndeleteBlock(t *testing.T) {
t.Run("success scenario", func(t *testing.T) {
boardID := testBoardID
board := &model.Board{ID: boardID}
block := model.Block{
block := &model.Block{
ID: "block-id",
BoardID: board.ID,
}
th.Store.EXPECT().GetBlockHistory(
gomock.Eq("block-id"),
gomock.Eq(model.QueryBlockHistoryOptions{Limit: 1, Descending: true}),
).Return([]model.Block{block}, nil)
).Return([]*model.Block{block}, nil)
th.Store.EXPECT().UndeleteBlock(gomock.Eq("block-id"), gomock.Eq("user-id-1")).Return(nil)
th.Store.EXPECT().GetBlock(gomock.Eq("block-id")).Return(&block, nil)
th.Store.EXPECT().GetBlock(gomock.Eq("block-id")).Return(block, nil)
th.Store.EXPECT().GetBoard(boardID).Return(board, nil)
th.Store.EXPECT().GetMembersForBoard(boardID).Return([]*model.BoardMember{}, nil)
_, err := th.App.UndeleteBlock("block-id", "user-id-1")
@ -171,13 +171,13 @@ func TestUndeleteBlock(t *testing.T) {
})
t.Run("error scenario", func(t *testing.T) {
block := model.Block{
block := &model.Block{
ID: "block-id",
}
th.Store.EXPECT().GetBlockHistory(
gomock.Eq("block-id"),
gomock.Eq(model.QueryBlockHistoryOptions{Limit: 1, Descending: true}),
).Return([]model.Block{block}, nil)
).Return([]*model.Block{block}, nil)
th.Store.EXPECT().UndeleteBlock(gomock.Eq("block-id"), gomock.Eq("user-id-1")).Return(blockError{"error"})
_, err := th.App.UndeleteBlock("block-id", "user-id-1")
require.Error(t, err, "error")
@ -203,9 +203,9 @@ func TestIsWithinViewsLimit(t *testing.T) {
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().GetBlocksWithParentAndType("board_id", "parent_id", "view").Return([]*model.Block{{}}, nil)
withinLimits, err := th.App.isWithinViewsLimit("board_id", model.Block{ParentID: "parent_id"})
withinLimits, err := th.App.isWithinViewsLimit("board_id", &model.Block{ParentID: "parent_id"})
assert.NoError(t, err)
assert.True(t, withinLimits)
})
@ -221,9 +221,9 @@ func TestIsWithinViewsLimit(t *testing.T) {
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().GetBlocksWithParentAndType("board_id", "parent_id", "view").Return([]*model.Block{{}}, nil)
withinLimits, err := th.App.isWithinViewsLimit("board_id", model.Block{ParentID: "parent_id"})
withinLimits, err := th.App.isWithinViewsLimit("board_id", &model.Block{ParentID: "parent_id"})
assert.NoError(t, err)
assert.False(t, withinLimits)
})
@ -239,9 +239,9 @@ func TestIsWithinViewsLimit(t *testing.T) {
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().GetBlocksWithParentAndType("board_id", "parent_id", "view").Return([]*model.Block{{}, {}, {}}, nil)
withinLimits, err := th.App.isWithinViewsLimit("board_id", model.Block{ParentID: "parent_id"})
withinLimits, err := th.App.isWithinViewsLimit("board_id", &model.Block{ParentID: "parent_id"})
assert.NoError(t, err)
assert.False(t, withinLimits)
})
@ -257,9 +257,9 @@ func TestIsWithinViewsLimit(t *testing.T) {
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().GetBlocksWithParentAndType("board_id", "parent_id", "view").Return([]*model.Block{}, nil)
withinLimits, err := th.App.isWithinViewsLimit("board_id", model.Block{ParentID: "parent_id"})
withinLimits, err := th.App.isWithinViewsLimit("board_id", &model.Block{ParentID: "parent_id"})
assert.NoError(t, err)
assert.True(t, withinLimits)
})
@ -270,7 +270,7 @@ func TestIsWithinViewsLimit(t *testing.T) {
}
th.Store.EXPECT().GetLicense().Return(nonCloudLicense)
withinLimits, err := th.App.isWithinViewsLimit("board_id", model.Block{ParentID: "parent_id"})
withinLimits, err := th.App.isWithinViewsLimit("board_id", &model.Block{ParentID: "parent_id"})
assert.NoError(t, err)
assert.True(t, withinLimits)
})
@ -282,35 +282,35 @@ func TestInsertBlocks(t *testing.T) {
t.Run("success scenario", func(t *testing.T) {
boardID := testBoardID
block := model.Block{BoardID: boardID}
block := &model.Block{BoardID: boardID}
board := &model.Board{ID: boardID}
th.Store.EXPECT().GetBoard(boardID).Return(board, nil)
th.Store.EXPECT().InsertBlock(&block, "user-id-1").Return(nil)
th.Store.EXPECT().InsertBlock(block, "user-id-1").Return(nil)
th.Store.EXPECT().GetMembersForBoard(boardID).Return([]*model.BoardMember{}, nil)
_, err := th.App.InsertBlocks([]model.Block{block}, "user-id-1")
_, err := th.App.InsertBlocks([]*model.Block{block}, "user-id-1")
require.NoError(t, err)
})
t.Run("error scenario", func(t *testing.T) {
boardID := testBoardID
block := model.Block{BoardID: boardID}
block := &model.Block{BoardID: boardID}
board := &model.Board{ID: boardID}
th.Store.EXPECT().GetBoard(boardID).Return(board, nil)
th.Store.EXPECT().InsertBlock(&block, "user-id-1").Return(blockError{"error"})
_, err := th.App.InsertBlocks([]model.Block{block}, "user-id-1")
th.Store.EXPECT().InsertBlock(block, "user-id-1").Return(blockError{"error"})
_, err := th.App.InsertBlocks([]*model.Block{block}, "user-id-1")
require.Error(t, err, "error")
})
t.Run("create view within limits", func(t *testing.T) {
boardID := testBoardID
block := model.Block{
block := &model.Block{
Type: model.TypeView,
ParentID: "parent_id",
BoardID: boardID,
}
board := &model.Board{ID: boardID}
th.Store.EXPECT().GetBoard(boardID).Return(board, nil)
th.Store.EXPECT().InsertBlock(&block, "user-id-1").Return(nil)
th.Store.EXPECT().InsertBlock(block, "user-id-1").Return(nil)
th.Store.EXPECT().GetMembersForBoard(boardID).Return([]*model.BoardMember{}, nil)
// setting up mocks for limits
@ -327,15 +327,15 @@ func TestInsertBlocks(t *testing.T) {
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().GetBlocksWithParentAndType("test-board-id", "parent_id", "view").Return([]*model.Block{{}}, nil)
_, err := th.App.InsertBlocks([]model.Block{block}, "user-id-1")
_, err := th.App.InsertBlocks([]*model.Block{block}, "user-id-1")
require.NoError(t, err)
})
t.Run("create view exceeding limits", func(t *testing.T) {
boardID := testBoardID
block := model.Block{
block := &model.Block{
Type: model.TypeView,
ParentID: "parent_id",
BoardID: boardID,
@ -357,9 +357,9 @@ func TestInsertBlocks(t *testing.T) {
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().GetBlocksWithParentAndType("test-board-id", "parent_id", "view").Return([]*model.Block{{}, {}}, nil)
_, err := th.App.InsertBlocks([]model.Block{block}, "user-id-1")
_, err := th.App.InsertBlocks([]*model.Block{block}, "user-id-1")
require.Error(t, err)
})
@ -367,13 +367,13 @@ func TestInsertBlocks(t *testing.T) {
t.Skipf("Will be fixed soon")
boardID := testBoardID
view1 := model.Block{
view1 := &model.Block{
Type: model.TypeView,
ParentID: "parent_id",
BoardID: boardID,
}
view2 := model.Block{
view2 := &model.Block{
Type: model.TypeView,
ParentID: "parent_id",
BoardID: boardID,
@ -381,7 +381,7 @@ func TestInsertBlocks(t *testing.T) {
board := &model.Board{ID: boardID}
th.Store.EXPECT().GetBoard(boardID).Return(board, nil)
th.Store.EXPECT().InsertBlock(&view1, "user-id-1").Return(nil).Times(2)
th.Store.EXPECT().InsertBlock(view1, "user-id-1").Return(nil).Times(2)
th.Store.EXPECT().GetMembersForBoard(boardID).Return([]*model.BoardMember{}, nil).Times(2)
// setting up mocks for limits
@ -398,9 +398,9 @@ func TestInsertBlocks(t *testing.T) {
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().GetBlocksWithParentAndType("test-board-id", "parent_id", "view").Return([]*model.Block{{}}, nil).Times(2)
_, err := th.App.InsertBlocks([]model.Block{view1, view2}, "user-id-1")
_, err := th.App.InsertBlocks([]*model.Block{view1, view2}, "user-id-1")
require.Error(t, err)
})
}

View File

@ -130,7 +130,7 @@ func (a *App) getBoardDescendantModifiedInfo(boardID string, latest bool) (int64
}
if len(blocks) > 0 {
// Compare the board history info with the descendant block info, if it exists
block := &blocks[0]
block := blocks[0]
if latest && block.UpdateAt > timestamp {
timestamp = block.UpdateAt
modifiedBy = block.ModifiedBy
@ -228,7 +228,7 @@ func (a *App) DuplicateBoard(boardID, userID, toTeam string, asTemplate bool) (*
for _, block := range bab.Blocks {
blk := block
a.wsAdapter.BroadcastBlockChange(teamID, blk)
a.notifyBlockChanged(notify.Add, &blk, nil, userID)
a.notifyBlockChanged(notify.Add, blk, nil, userID)
}
for _, member := range members {
a.wsAdapter.BroadcastMemberChange(teamID, member.BoardID, member)

View File

@ -35,7 +35,7 @@ func (a *App) CreateBoardsAndBlocks(bab *model.BoardsAndBlocks, userID string, a
a.wsAdapter.BroadcastBlockChange(teamID, b)
a.metrics.IncrementBlocksInserted(1)
a.webhook.NotifyUpdate(b)
a.notifyBlockChanged(notify.Add, &b, nil, userID)
a.notifyBlockChanged(notify.Add, b, nil, userID)
}
if addMember {
@ -74,7 +74,7 @@ func (a *App) PatchBoardsAndBlocks(pbab *model.PatchBoardsAndBlocks, userID stri
}
}
oldBlocksMap := map[string]model.Block{}
oldBlocksMap := map[string]*model.Block{}
for _, block := range oldBlocks {
oldBlocksMap[block.ID] = block
}
@ -98,7 +98,7 @@ func (a *App) PatchBoardsAndBlocks(pbab *model.PatchBoardsAndBlocks, userID stri
a.metrics.IncrementBlocksPatched(1)
a.wsAdapter.BroadcastBlockChange(teamID, b)
a.webhook.NotifyUpdate(b)
a.notifyBlockChanged(notify.Update, &b, &oldBlock, userID)
a.notifyBlockChanged(notify.Update, b, oldBlock, userID)
}
for _, board := range bab.Boards {

View File

@ -24,12 +24,12 @@ func (a *App) CreateCard(card *model.Card, boardID string, userID string, disabl
block := model.Card2Block(card)
newBlocks, err := a.InsertBlocksAndNotify([]model.Block{*block}, userID, disableNotify)
newBlocks, err := a.InsertBlocksAndNotify([]*model.Block{block}, userID, disableNotify)
if err != nil {
return nil, fmt.Errorf("cannot create card: %w", err)
}
newCard, err := model.Block2Card(&newBlocks[0])
newCard, err := model.Block2Card(newBlocks[0])
if err != nil {
return nil, err
}
@ -53,7 +53,7 @@ func (a *App) GetCardsForBoard(boardID string, page int, perPage int) ([]*model.
cards := make([]*model.Card, 0, len(blocks))
for _, blk := range blocks {
b := blk
if card, err := model.Block2Card(&b); err != nil {
if card, err := model.Block2Card(b); err != nil {
return nil, fmt.Errorf("Block2Card fail: %w", err)
} else {
cards = append(cards, card)

View File

@ -69,9 +69,9 @@ func TestGetCards(t *testing.T) {
const cardCount = 25
// make some cards
blocks := make([]model.Block, 0, cardCount)
blocks := make([]*model.Block, 0, cardCount)
for i := 0; i < cardCount; i++ {
card := model.Block{
card := &model.Block{
ID: utils.NewID(utils.IDTypeBlock),
ParentID: board.ID,
Schema: 1,

View File

@ -55,6 +55,10 @@ func (a *App) GetBoardsCloudLimits() (*model.BoardsCloudLimits, error) {
return boardsCloudLimits, nil
}
func (a *App) GetUsedCardsCount() (int, error) {
return a.store.GetUsedCardsCount()
}
// IsCloud returns true if the server is running as a plugin in a
// cloud licensed server.
func (a *App) IsCloud() bool {
@ -123,7 +127,7 @@ func (a *App) UpdateCardLimitTimestamp() error {
// getTemplateMapForBlocks gets all board ids for the blocks, and
// builds a map with the board IDs as the key and their isTemplate
// field as the value.
func (a *App) getTemplateMapForBlocks(blocks []model.Block) (map[string]bool, error) {
func (a *App) getTemplateMapForBlocks(blocks []*model.Block) (map[string]bool, error) {
boardMap := map[string]*model.Board{}
for _, block := range blocks {
if _, ok := boardMap[block.BoardID]; !ok {
@ -146,7 +150,7 @@ func (a *App) getTemplateMapForBlocks(blocks []model.Block) (map[string]bool, er
// ApplyCloudLimits takes a set of blocks and, if the server is cloud
// limited, limits those that are outside of the card limit and don't
// belong to a template.
func (a *App) ApplyCloudLimits(blocks []model.Block) ([]model.Block, error) {
func (a *App) ApplyCloudLimits(blocks []*model.Block) ([]*model.Block, error) {
// if there is no limit currently being applied, return
if !a.IsCloudLimited() {
return blocks, nil
@ -162,7 +166,7 @@ func (a *App) ApplyCloudLimits(blocks []model.Block) ([]model.Block, error) {
return nil, err
}
limitedBlocks := make([]model.Block, len(blocks))
limitedBlocks := make([]*model.Block, len(blocks))
for i, block := range blocks {
// if the block belongs to a template, it will never be
// limited
@ -183,7 +187,7 @@ func (a *App) ApplyCloudLimits(blocks []model.Block) ([]model.Block, error) {
// ContainsLimitedBlocks checks if a list of blocks contain any block
// that references a limited card.
func (a *App) ContainsLimitedBlocks(blocks []model.Block) (bool, error) {
func (a *App) ContainsLimitedBlocks(blocks []*model.Block) (bool, error) {
cardLimitTimestamp, err := a.store.GetCardLimitTimestamp()
if err != nil {
return false, err
@ -193,7 +197,7 @@ func (a *App) ContainsLimitedBlocks(blocks []model.Block) (bool, error) {
return false, nil
}
cards := []model.Block{}
cards := []*model.Block{}
cardIDMap := map[string]bool{}
for _, block := range blocks {
switch block.Type {

View File

@ -231,7 +231,7 @@ func TestGetTemplateMapForBlocks(t *testing.T) {
IsTemplate: false,
}
blocks := []model.Block{
blocks := []*model.Block{
{
ID: "card1",
Type: model.TypeCard,
@ -274,7 +274,7 @@ func TestGetTemplateMapForBlocks(t *testing.T) {
th, tearDown := SetupTestHelper(t)
defer tearDown()
blocks := []model.Block{
blocks := []*model.Block{
{
ID: "card1",
Type: model.TypeCard,
@ -317,7 +317,7 @@ func TestApplyCloudLimits(t *testing.T) {
IsTemplate: true,
}
blocks := []model.Block{
blocks := []*model.Block{
{
ID: "card1",
Type: model.TypeCard,
@ -360,14 +360,14 @@ func TestApplyCloudLimits(t *testing.T) {
})
t.Run("if the server is limited, it should limit the blocks that are beyond the card limit timestamp", func(t *testing.T) {
findBlock := func(blocks []model.Block, id string) model.Block {
findBlock := func(blocks []*model.Block, id string) *model.Block {
for _, block := range blocks {
if block.ID == id {
return block
}
}
require.FailNow(t, "block %s not found", id)
return model.Block{} // this should never be reached
return &model.Block{} // this should never be reached
}
th, tearDown := SetupTestHelper(t)
@ -404,7 +404,7 @@ func TestContainsLimitedBlocks(t *testing.T) {
th, tearDown := SetupTestHelper(t)
defer tearDown()
blocks := []model.Block{
blocks := []*model.Block{
{
ID: "card1",
Type: model.TypeCard,
@ -425,7 +425,7 @@ func TestContainsLimitedBlocks(t *testing.T) {
th, tearDown := SetupTestHelper(t)
defer tearDown()
blocks := []model.Block{
blocks := []*model.Block{
{
ID: "card1",
Type: model.TypeCard,
@ -454,7 +454,7 @@ func TestContainsLimitedBlocks(t *testing.T) {
th, tearDown := SetupTestHelper(t)
defer tearDown()
blocks := []model.Block{
blocks := []*model.Block{
{
ID: "card1",
Type: model.TypeCard,
@ -484,7 +484,7 @@ func TestContainsLimitedBlocks(t *testing.T) {
th, tearDown := SetupTestHelper(t)
defer tearDown()
blocks := []model.Block{
blocks := []*model.Block{
{
ID: "text1",
Type: model.TypeText,
@ -494,7 +494,7 @@ func TestContainsLimitedBlocks(t *testing.T) {
},
}
card1 := model.Block{
card1 := &model.Block{
ID: "card1",
Type: model.TypeCard,
ParentID: "board1",
@ -510,7 +510,7 @@ func TestContainsLimitedBlocks(t *testing.T) {
th.App.SetCardLimit(500)
cardLimitTimestamp := int64(150)
th.Store.EXPECT().GetCardLimitTimestamp().Return(cardLimitTimestamp, nil)
th.Store.EXPECT().GetBlocksByIDs([]string{"card1"}).Return([]model.Block{card1}, nil)
th.Store.EXPECT().GetBlocksByIDs([]string{"card1"}).Return([]*model.Block{card1}, nil)
th.Store.EXPECT().GetBoard("board1").Return(board1, nil)
containsLimitedBlocks, err := th.App.ContainsLimitedBlocks(blocks)
@ -522,7 +522,7 @@ func TestContainsLimitedBlocks(t *testing.T) {
th, tearDown := SetupTestHelper(t)
defer tearDown()
blocks := []model.Block{
blocks := []*model.Block{
{
ID: "text1",
Type: model.TypeText,
@ -532,7 +532,7 @@ func TestContainsLimitedBlocks(t *testing.T) {
},
}
card1 := model.Block{
card1 := &model.Block{
ID: "card1",
Type: model.TypeCard,
ParentID: "board1",
@ -548,7 +548,7 @@ func TestContainsLimitedBlocks(t *testing.T) {
th.App.SetCardLimit(500)
cardLimitTimestamp := int64(150)
th.Store.EXPECT().GetCardLimitTimestamp().Return(cardLimitTimestamp, nil)
th.Store.EXPECT().GetBlocksByIDs([]string{"card1"}).Return([]model.Block{card1}, nil)
th.Store.EXPECT().GetBlocksByIDs([]string{"card1"}).Return([]*model.Block{card1}, nil)
th.Store.EXPECT().GetBoard("board1").Return(board1, nil)
containsLimitedBlocks, err := th.App.ContainsLimitedBlocks(blocks)
@ -560,7 +560,7 @@ func TestContainsLimitedBlocks(t *testing.T) {
th, tearDown := SetupTestHelper(t)
defer tearDown()
blocks := []model.Block{
blocks := []*model.Block{
// a content block that references a card that needs
// fetching
{
@ -598,7 +598,7 @@ func TestContainsLimitedBlocks(t *testing.T) {
},
}
card1 := model.Block{
card1 := &model.Block{
ID: "card1",
Type: model.TypeCard,
ParentID: "board1",
@ -606,7 +606,7 @@ func TestContainsLimitedBlocks(t *testing.T) {
UpdateAt: 200,
}
card3 := model.Block{
card3 := &model.Block{
ID: "card3",
Type: model.TypeCard,
ParentID: "board3",
@ -633,7 +633,7 @@ func TestContainsLimitedBlocks(t *testing.T) {
th.App.SetCardLimit(500)
cardLimitTimestamp := int64(150)
th.Store.EXPECT().GetCardLimitTimestamp().Return(cardLimitTimestamp, nil)
th.Store.EXPECT().GetBlocksByIDs(gomock.InAnyOrder([]string{"card1", "card3"})).Return([]model.Block{card1, card3}, nil)
th.Store.EXPECT().GetBlocksByIDs(gomock.InAnyOrder([]string{"card1", "card3"})).Return([]*model.Block{card1, card3}, nil)
th.Store.EXPECT().GetBoard("board1").Return(board1, nil)
th.Store.EXPECT().GetBoard("board2").Return(board2, nil)
th.Store.EXPECT().GetBoard("board3").Return(board3, nil)

View File

@ -110,7 +110,7 @@ func (a *App) writeArchiveBoard(zw *zip.Writer, board model.Board, opt model.Exp
}
// writeArchiveBlockLine writes a single block to the archive.
func (a *App) writeArchiveBlockLine(w io.Writer, block model.Block) error {
func (a *App) writeArchiveBlockLine(w io.Writer, block *model.Block) error {
b, err := json.Marshal(&block)
if err != nil {
return err
@ -199,7 +199,7 @@ func (a *App) getBoardsForArchive(boardIDs []string) ([]model.Board, error) {
return boards, nil
}
func extractImageFilename(imageBlock model.Block) (string, error) {
func extractImageFilename(imageBlock *model.Block) (string, error) {
f, ok := imageBlock.Fields["fileId"]
if !ok {
return "", model.ErrInvalidImageBlock

View File

@ -126,7 +126,7 @@ func (a *App) ImportBoardJSONL(r io.Reader, opt model.ImportArchiveOptions) (str
// TODO: Stream this once `model.GenerateBlockIDs` can take a stream of blocks.
// We don't want to load the whole file in memory, even though it's a single board.
boardsAndBlocks := &model.BoardsAndBlocks{
Blocks: make([]model.Block, 0, 10),
Blocks: make([]*model.Block, 0, 10),
Boards: make([]*model.Board, 0, 10),
}
lineReader := bufio.NewReader(r)
@ -175,20 +175,20 @@ func (a *App) ImportBoardJSONL(r io.Reader, opt model.ImportArchiveOptions) (str
boardID = board.ID
case "board_block":
// legacy archives encoded boards as blocks; we need to convert them to real boards.
var block model.Block
var block *model.Block
if err2 := json.Unmarshal(archiveLine.Data, &block); err2 != nil {
return "", fmt.Errorf("invalid board block in archive line %d: %w", lineNum, err2)
}
block.ModifiedBy = userID
block.UpdateAt = now
board, err := a.blockToBoard(&block, opt)
board, err := a.blockToBoard(block, opt)
if err != nil {
return "", fmt.Errorf("cannot convert archive line %d to block: %w", lineNum, err)
}
boardsAndBlocks.Boards = append(boardsAndBlocks.Boards, board)
boardID = board.ID
case "block":
var block model.Block
var block *model.Block
if err2 := json.Unmarshal(archiveLine.Data, &block); err2 != nil {
return "", fmt.Errorf("invalid block in archive line %d: %w", lineNum, err2)
}
@ -253,7 +253,7 @@ func (a *App) fixBoardsandBlocks(boardsAndBlocks *model.BoardsAndBlocks, opt mod
modInfoCache := make(map[string]interface{})
modBoards := make([]*model.Board, 0, len(boardsAndBlocks.Boards))
modBlocks := make([]model.Block, 0, len(boardsAndBlocks.Blocks))
modBlocks := make([]*model.Block, 0, len(boardsAndBlocks.Blocks))
for _, board := range boardsAndBlocks.Boards {
b := *board
@ -268,7 +268,7 @@ func (a *App) fixBoardsandBlocks(boardsAndBlocks *model.BoardsAndBlocks, opt mod
for _, block := range boardsAndBlocks.Blocks {
b := block
if opt.BlockModifier != nil && !opt.BlockModifier(&b, modInfoCache) {
if opt.BlockModifier != nil && !opt.BlockModifier(b, modInfoCache) {
a.logger.Debug("skipping insert block per block modifier",
mlog.String("blockID", block.ID),
)

View File

@ -19,7 +19,7 @@ func TestApp_ImportArchive(t *testing.T) {
Title: "Cross-Functional Project Plan",
}
block := model.Block{
block := &model.Block{
ID: "2c1873e0-1484-407d-8b2c-3c3b5a2a9f9e",
ParentID: board.ID,
Type: model.TypeView,
@ -28,7 +28,7 @@ func TestApp_ImportArchive(t *testing.T) {
babs := &model.BoardsAndBlocks{
Boards: []*model.Board{board},
Blocks: []model.Block{block},
Blocks: []*model.Block{block},
}
boardMember := &model.BoardMember{

View File

@ -21,7 +21,7 @@ func TestApp_initializeTemplates(t *testing.T) {
TemplateVersion: defaultTemplateVersion,
}
block := model.Block{
block := &model.Block{
ID: utils.NewID(utils.IDTypeBlock),
ParentID: board.ID,
BoardID: board.ID,
@ -31,7 +31,7 @@ func TestApp_initializeTemplates(t *testing.T) {
boardsAndBlocks := &model.BoardsAndBlocks{
Boards: []*model.Board{board},
Blocks: []model.Block{block},
Blocks: []*model.Block{block},
}
boardMember := &model.BoardMember{

View File

@ -5,7 +5,6 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"strings"
@ -56,7 +55,7 @@ func BuildErrorResponse(r *http.Response, err error) *Response {
func closeBody(r *http.Response) {
if r.Body != nil {
_, _ = io.Copy(ioutil.Discard, r.Body)
_, _ = io.Copy(io.Discard, r.Body)
_ = r.Body.Close()
}
}
@ -244,7 +243,7 @@ func (c *Client) GetUserBoardsInsights(teamID string, userID string, timeRange s
return boardInsightsList, BuildResponse(r)
}
func (c *Client) GetBlocksForBoard(boardID string) ([]model.Block, *Response) {
func (c *Client) GetBlocksForBoard(boardID string) ([]*model.Block, *Response) {
r, err := c.DoAPIGet(c.GetBlocksRoute(boardID), "")
if err != nil {
return nil, BuildErrorResponse(r, err)
@ -254,7 +253,7 @@ func (c *Client) GetBlocksForBoard(boardID string) ([]model.Block, *Response) {
return model.BlocksFromJSON(r.Body), BuildResponse(r)
}
func (c *Client) GetAllBlocksForBoard(boardID string) ([]model.Block, *Response) {
func (c *Client) GetAllBlocksForBoard(boardID string) ([]*model.Block, *Response) {
r, err := c.DoAPIGet(c.GetAllBlocksRoute(boardID), "")
if err != nil {
return nil, BuildErrorResponse(r, err)
@ -321,7 +320,7 @@ func (c *Client) UndeleteBlock(boardID, blockID string) (bool, *Response) {
return true, BuildResponse(r)
}
func (c *Client) InsertBlocks(boardID string, blocks []model.Block, disableNotify bool) ([]model.Block, *Response) {
func (c *Client) InsertBlocks(boardID string, blocks []*model.Block, disableNotify bool) ([]*model.Block, *Response) {
var queryParams string
if disableNotify {
queryParams = "?" + disableNotifyQueryParam
@ -903,3 +902,19 @@ func (c *Client) GetLimits() (*model.BoardsCloudLimits, *Response) {
return limits, BuildResponse(r)
}
func (c *Client) GetStatistics() (*model.BoardsStatistics, *Response) {
r, err := c.DoAPIGet("/statistics", "")
if err != nil {
return nil, BuildErrorResponse(r, err)
}
defer closeBody(r)
var stats *model.BoardsStatistics
err = json.NewDecoder(r.Body).Decode(&stats)
if err != nil {
return nil, BuildErrorResponse(r, err)
}
return stats, BuildResponse(r)
}

View File

@ -18,7 +18,7 @@ func TestGetBlocks(t *testing.T) {
initialID1 := utils.NewID(utils.IDTypeBlock)
initialID2 := utils.NewID(utils.IDTypeBlock)
newBlocks := []model.Block{
newBlocks := []*model.Block{
{
ID: initialID1,
BoardID: board.ID,
@ -64,7 +64,7 @@ func TestPostBlock(t *testing.T) {
t.Run("Create a single block", func(t *testing.T) {
initialID1 := utils.NewID(utils.IDTypeBlock)
block := model.Block{
block := &model.Block{
ID: initialID1,
BoardID: board.ID,
CreateAt: 1,
@ -73,7 +73,7 @@ func TestPostBlock(t *testing.T) {
Title: "New title",
}
newBlocks, resp := th.Client.InsertBlocks(board.ID, []model.Block{block}, false)
newBlocks, resp := th.Client.InsertBlocks(board.ID, []*model.Block{block}, false)
require.NoError(t, resp.Error)
require.Len(t, newBlocks, 1)
blockID1 = newBlocks[0].ID
@ -92,7 +92,7 @@ func TestPostBlock(t *testing.T) {
t.Run("Create a couple of blocks in the same call", func(t *testing.T) {
initialID2 := utils.NewID(utils.IDTypeBlock)
initialID3 := utils.NewID(utils.IDTypeBlock)
newBlocks := []model.Block{
newBlocks := []*model.Block{
{
ID: initialID2,
BoardID: board.ID,
@ -131,7 +131,7 @@ func TestPostBlock(t *testing.T) {
})
t.Run("Update a block should not be possible through the insert endpoint", func(t *testing.T) {
block := model.Block{
block := &model.Block{
ID: blockID1,
BoardID: board.ID,
CreateAt: 1,
@ -140,7 +140,7 @@ func TestPostBlock(t *testing.T) {
Title: "Updated title",
}
newBlocks, resp := th.Client.InsertBlocks(board.ID, []model.Block{block}, false)
newBlocks, resp := th.Client.InsertBlocks(board.ID, []*model.Block{block}, false)
require.NoError(t, resp.Error)
require.Len(t, newBlocks, 1)
blockID4 := newBlocks[0].ID
@ -150,7 +150,7 @@ func TestPostBlock(t *testing.T) {
require.NoError(t, resp.Error)
require.Len(t, blocks, 4)
var block4 model.Block
var block4 *model.Block
for _, b := range blocks {
if b.ID == blockID4 {
block4 = b
@ -170,7 +170,7 @@ func TestPatchBlock(t *testing.T) {
board := th.CreateBoard("team-id", model.BoardTypeOpen)
time.Sleep(10 * time.Millisecond)
block := model.Block{
block := &model.Block{
ID: initialID,
BoardID: board.ID,
CreateAt: 1,
@ -180,7 +180,7 @@ func TestPatchBlock(t *testing.T) {
Fields: map[string]interface{}{"test": "test value", "test2": "test value 2"},
}
newBlocks, resp := th.Client.InsertBlocks(board.ID, []model.Block{block}, false)
newBlocks, resp := th.Client.InsertBlocks(board.ID, []*model.Block{block}, false)
th.CheckOK(resp)
require.Len(t, newBlocks, 1)
blockID := newBlocks[0].ID
@ -198,7 +198,7 @@ func TestPatchBlock(t *testing.T) {
require.NoError(t, resp.Error)
require.Len(t, blocks, 1)
var updatedBlock model.Block
var updatedBlock *model.Block
for _, b := range blocks {
if b.ID == blockID {
updatedBlock = b
@ -223,7 +223,7 @@ func TestPatchBlock(t *testing.T) {
require.NoError(t, resp.Error)
require.Len(t, blocks, 1)
var updatedBlock model.Block
var updatedBlock *model.Block
for _, b := range blocks {
if b.ID == blockID {
updatedBlock = b
@ -246,7 +246,7 @@ func TestPatchBlock(t *testing.T) {
require.NoError(t, resp.Error)
require.Len(t, blocks, 1)
var updatedBlock model.Block
var updatedBlock *model.Block
for _, b := range blocks {
if b.ID == blockID {
updatedBlock = b
@ -269,7 +269,7 @@ func TestDeleteBlock(t *testing.T) {
var blockID string
t.Run("Create a block", func(t *testing.T) {
initialID := utils.NewID(utils.IDTypeBlock)
block := model.Block{
block := &model.Block{
ID: initialID,
BoardID: board.ID,
CreateAt: 1,
@ -278,7 +278,7 @@ func TestDeleteBlock(t *testing.T) {
Title: "New title",
}
newBlocks, resp := th.Client.InsertBlocks(board.ID, []model.Block{block}, false)
newBlocks, resp := th.Client.InsertBlocks(board.ID, []*model.Block{block}, false)
require.NoError(t, resp.Error)
require.Len(t, newBlocks, 1)
require.NotZero(t, newBlocks[0].ID)
@ -323,7 +323,7 @@ func TestUndeleteBlock(t *testing.T) {
var blockID string
t.Run("Create a block", func(t *testing.T) {
initialID := utils.NewID(utils.IDTypeBoard)
block := model.Block{
block := &model.Block{
ID: initialID,
BoardID: board.ID,
CreateAt: 1,
@ -332,7 +332,7 @@ func TestUndeleteBlock(t *testing.T) {
Title: "New title",
}
newBlocks, resp := th.Client.InsertBlocks(board.ID, []model.Block{block}, false)
newBlocks, resp := th.Client.InsertBlocks(board.ID, []*model.Block{block}, false)
require.NoError(t, resp.Error)
require.Len(t, newBlocks, 1)
require.NotZero(t, newBlocks[0].ID)

View File

@ -424,7 +424,7 @@ func TestGetAllBlocksForBoard(t *testing.T) {
childBlockID2 := utils.NewID(utils.IDTypeBlock)
t.Run("Create the block structure", func(t *testing.T) {
newBlocks := []model.Block{
newBlocks := []*model.Block{
{
ID: parentBlockID,
BoardID: board.ID,
@ -739,7 +739,7 @@ func TestGetBoardMetadata(t *testing.T) {
require.Equal(t, rBoard.ModifiedBy, boardMetadata.LastModifiedBy)
// Insert card1
card1 := model.Block{
card1 := &model.Block{
ID: "card1",
BoardID: rBoard.ID,
Title: "Card 1",
@ -760,7 +760,7 @@ func TestGetBoardMetadata(t *testing.T) {
require.Equal(t, rCard1.ModifiedBy, boardMetadata.LastModifiedBy)
// Insert card2
card2 := model.Block{
card2 := &model.Block{
ID: "card2",
BoardID: rBoard.ID,
Title: "Card 2",
@ -1946,7 +1946,7 @@ func TestDuplicateBoard(t *testing.T) {
require.Equal(t, me.ID, board.CreatedBy)
require.Equal(t, me.ID, board.ModifiedBy)
newBlocks := []model.Block{
newBlocks := []*model.Block{
{
ID: utils.NewID(utils.IDTypeBlock),
BoardID: board.ID,
@ -2031,7 +2031,7 @@ func TestDuplicateBoard(t *testing.T) {
th.CheckOK(resp)
require.NoError(t, resp.Error)
newBlocks := []model.Block{
newBlocks := []*model.Block{
{
ID: utils.NewID(utils.IDTypeBlock),
BoardID: board.ID,

View File

@ -17,7 +17,7 @@ func TestCreateBoardsAndBlocks(t *testing.T) {
newBab := &model.BoardsAndBlocks{
Boards: []*model.Board{},
Blocks: []model.Block{},
Blocks: []*model.Block{},
}
bab, resp := th.Client.CreateBoardsAndBlocks(newBab)
@ -32,7 +32,7 @@ func TestCreateBoardsAndBlocks(t *testing.T) {
t.Run("no boards", func(t *testing.T) {
newBab := &model.BoardsAndBlocks{
Boards: []*model.Board{},
Blocks: []model.Block{
Blocks: []*model.Block{
{ID: "block-id", BoardID: "board-id", Type: model.TypeCard},
},
}
@ -47,7 +47,7 @@ func TestCreateBoardsAndBlocks(t *testing.T) {
Boards: []*model.Board{
{ID: "board-id", TeamID: teamID, Type: model.BoardTypePrivate},
},
Blocks: []model.Block{},
Blocks: []*model.Block{},
}
bab, resp := th.Client.CreateBoardsAndBlocks(newBab)
@ -60,7 +60,7 @@ func TestCreateBoardsAndBlocks(t *testing.T) {
Boards: []*model.Board{
{ID: "board-id", TeamID: teamID, Type: model.BoardTypePrivate},
},
Blocks: []model.Block{
Blocks: []*model.Block{
{ID: "block-id", BoardID: "nonexistent-board-id", Type: model.TypeCard, CreateAt: 1, UpdateAt: 1},
},
}
@ -76,7 +76,7 @@ func TestCreateBoardsAndBlocks(t *testing.T) {
{ID: "board-id", TeamID: teamID, Type: model.BoardTypePrivate},
{TeamID: teamID, Type: model.BoardTypePrivate},
},
Blocks: []model.Block{
Blocks: []*model.Block{
{ID: "block-id", BoardID: "board-id", Type: model.TypeCard, CreateAt: 1, UpdateAt: 1},
},
}
@ -92,7 +92,7 @@ func TestCreateBoardsAndBlocks(t *testing.T) {
{ID: "board-id-1", TeamID: "team-id-1", Type: model.BoardTypePrivate},
{ID: "board-id-2", TeamID: "team-id-2", Type: model.BoardTypePrivate},
},
Blocks: []model.Block{
Blocks: []*model.Block{
{ID: "block-id", BoardID: "board-id-1", Type: model.TypeCard, CreateAt: 1, UpdateAt: 1},
},
}
@ -108,7 +108,7 @@ func TestCreateBoardsAndBlocks(t *testing.T) {
{ID: "board-id-1", Title: "public board", TeamID: teamID, Type: model.BoardTypeOpen},
{ID: "board-id-2", Title: "private board", TeamID: teamID, Type: model.BoardTypePrivate},
},
Blocks: []model.Block{
Blocks: []*model.Block{
{ID: "block-id-1", Title: "block 1", BoardID: "board-id-1", Type: model.TypeCard, CreateAt: 1, UpdateAt: 1},
{ID: "block-id-2", Title: "block 2", BoardID: "board-id-2", Type: model.TypeCard, CreateAt: 1, UpdateAt: 1},
},
@ -203,7 +203,7 @@ func TestPatchBoardsAndBlocks(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, board2)
newBlock1 := model.Block{
newBlock1 := &model.Block{
ID: "block-id-1",
BoardID: board1.ID,
Title: initialTitle,
@ -213,7 +213,7 @@ func TestPatchBoardsAndBlocks(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, block1)
newBlock2 := model.Block{
newBlock2 := &model.Block{
ID: "block-id-2",
BoardID: board2.ID,
Title: initialTitle,
@ -342,7 +342,7 @@ func TestPatchBoardsAndBlocks(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, board2)
newBlock1 := model.Block{
newBlock1 := &model.Block{
ID: "block-id-1",
BoardID: board1.ID,
Title: initialTitle,
@ -352,7 +352,7 @@ func TestPatchBoardsAndBlocks(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, block1)
newBlock2 := model.Block{
newBlock2 := &model.Block{
ID: "block-id-2",
BoardID: board2.ID,
Title: initialTitle,
@ -406,7 +406,7 @@ func TestPatchBoardsAndBlocks(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, board2)
newBlock1 := model.Block{
newBlock1 := &model.Block{
ID: "block-id-1",
BoardID: board1.ID,
Title: initialTitle,
@ -416,7 +416,7 @@ func TestPatchBoardsAndBlocks(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, block1)
newBlock2 := model.Block{
newBlock2 := &model.Block{
ID: "block-id-2",
BoardID: board2.ID,
Title: initialTitle,
@ -470,7 +470,7 @@ func TestPatchBoardsAndBlocks(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, board2)
newBlock1 := model.Block{
newBlock1 := &model.Block{
ID: "block-id-1",
BoardID: board1.ID,
Title: initialTitle,
@ -480,7 +480,7 @@ func TestPatchBoardsAndBlocks(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, block1)
newBlock2 := model.Block{
newBlock2 := &model.Block{
ID: "block-id-2",
BoardID: board2.ID,
Title: initialTitle,
@ -537,7 +537,7 @@ func TestPatchBoardsAndBlocks(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, board2)
newBlock1 := model.Block{
newBlock1 := &model.Block{
ID: "block-id-1",
BoardID: board1.ID,
Title: initialTitle,
@ -547,7 +547,7 @@ func TestPatchBoardsAndBlocks(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, block1)
newBlock2 := model.Block{
newBlock2 := &model.Block{
ID: "block-id-2",
BoardID: board2.ID,
Title: initialTitle,
@ -600,7 +600,7 @@ func TestPatchBoardsAndBlocks(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, board2)
newBlock1 := model.Block{
newBlock1 := &model.Block{
ID: "block-id-1",
BoardID: board1.ID,
Title: initialTitle,
@ -610,7 +610,7 @@ func TestPatchBoardsAndBlocks(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, block1)
newBlock2 := model.Block{
newBlock2 := &model.Block{
ID: "block-id-2",
BoardID: board2.ID,
Title: initialTitle,
@ -683,7 +683,7 @@ func TestDeleteBoardsAndBlocks(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, board)
newBlock := model.Block{
newBlock := &model.Block{
ID: "block-id-1",
BoardID: board.ID,
Title: "title",
@ -762,7 +762,7 @@ func TestDeleteBoardsAndBlocks(t *testing.T) {
{ID: "board-id-1", Title: "public board", TeamID: teamID, Type: model.BoardTypeOpen},
{ID: "board-id-2", Title: "private board", TeamID: teamID, Type: model.BoardTypePrivate},
},
Blocks: []model.Block{
Blocks: []*model.Block{
{ID: "block-id-1", Title: "block 1", BoardID: "board-id-1", Type: model.TypeCard, CreateAt: 1, UpdateAt: 1},
{ID: "block-id-2", Title: "block 2", BoardID: "board-id-2", Type: model.TypeCard, CreateAt: 1, UpdateAt: 1},
},

View File

@ -88,10 +88,10 @@ func TestGetCards(t *testing.T) {
cardNew, resp := th.Client.CreateCard(board.ID, card, true)
th.CheckOK(resp)
blocks := make([]model.Block, 0, 3)
blocks := make([]*model.Block, 0, 3)
for j := 0; j < 3; j++ {
now := model.GetMillis()
block := model.Block{
block := &model.Block{
ID: utils.NewID(utils.IDTypeBlock),
ParentID: cardNew.ID,
CreatedBy: userID,

View File

@ -72,6 +72,10 @@ type TestHelper struct {
type FakePermissionPluginAPI struct{}
func (*FakePermissionPluginAPI) HasPermissionTo(userID string, permission *mmModel.Permission) bool {
return userID == userAdmin
}
func (*FakePermissionPluginAPI) HasPermissionToTeam(userID string, teamID string, permission *mmModel.Permission) bool {
if userID == userNoTeamMember {
return false

View File

@ -24,7 +24,7 @@ func TestExportBoard(t *testing.T) {
UpdateAt: utils.GetMillis(),
}
block := model.Block{
block := &model.Block{
ID: utils.NewID(utils.IDTypeCard),
ParentID: board.ID,
Type: model.TypeCard,
@ -37,7 +37,7 @@ func TestExportBoard(t *testing.T) {
babs := &model.BoardsAndBlocks{
Boards: []*model.Board{board},
Blocks: []model.Block{block},
Blocks: []*model.Block{block},
}
babs, resp := th.Client.CreateBoardsAndBlocks(babs)

View File

@ -139,19 +139,19 @@ func setupData(t *testing.T, th *TestHelper) TestData {
true,
)
require.NoError(t, err)
err = th.Server.App().InsertBlock(model.Block{ID: "block-1", Title: "Test", Type: "card", BoardID: customTemplate1.ID}, userAdminID)
err = th.Server.App().InsertBlock(&model.Block{ID: "block-1", Title: "Test", Type: "card", BoardID: customTemplate1.ID}, userAdminID)
require.NoError(t, err)
customTemplate2, err := th.Server.App().CreateBoard(
&model.Board{Title: "Custom template 2", TeamID: "test-team", IsTemplate: true, Type: model.BoardTypePrivate, MinimumRole: "viewer"},
userAdminID,
true)
require.NoError(t, err)
err = th.Server.App().InsertBlock(model.Block{ID: "block-2", Title: "Test", Type: "card", BoardID: customTemplate2.ID}, userAdminID)
err = th.Server.App().InsertBlock(&model.Block{ID: "block-2", Title: "Test", Type: "card", BoardID: customTemplate2.ID}, userAdminID)
require.NoError(t, err)
board1, err := th.Server.App().CreateBoard(&model.Board{Title: "Board 1", TeamID: "test-team", Type: model.BoardTypeOpen, MinimumRole: "viewer"}, userAdminID, true)
require.NoError(t, err)
err = th.Server.App().InsertBlock(model.Block{ID: "block-3", Title: "Test", Type: "card", BoardID: board1.ID}, userAdminID)
err = th.Server.App().InsertBlock(&model.Block{ID: "block-3", Title: "Test", Type: "card", BoardID: board1.ID}, userAdminID)
require.NoError(t, err)
board2, err := th.Server.App().CreateBoard(&model.Board{Title: "Board 2", TeamID: "test-team", Type: model.BoardTypePrivate, MinimumRole: "viewer"}, userAdminID, true)
require.NoError(t, err)
@ -167,7 +167,7 @@ func setupData(t *testing.T, th *TestHelper) TestData {
require.Equal(t, boardMember.UserID, userAdminID)
require.Equal(t, boardMember.BoardID, board2.ID)
err = th.Server.App().InsertBlock(model.Block{ID: "block-4", Title: "Test", Type: "card", BoardID: board2.ID}, userAdminID)
err = th.Server.App().InsertBlock(&model.Block{ID: "block-4", Title: "Test", Type: "card", BoardID: board2.ID}, userAdminID)
require.NoError(t, err)
err = th.Server.App().UpsertSharing(model.Sharing{ID: board2.ID, Enabled: true, Token: "valid", ModifiedBy: userAdminID, UpdateAt: model.GetMillis()})
@ -1322,13 +1322,13 @@ func TestPermissionsPatchBoardBlock(t *testing.T) {
func TestPermissionsDeleteBoardBlock(t *testing.T) {
extraSetup := func(t *testing.T, th *TestHelper, testData TestData) {
err := th.Server.App().InsertBlock(model.Block{ID: "block-5", Title: "Test", Type: "card", BoardID: testData.publicTemplate.ID}, userAdmin)
err := th.Server.App().InsertBlock(&model.Block{ID: "block-5", Title: "Test", Type: "card", BoardID: testData.publicTemplate.ID}, userAdmin)
require.NoError(t, err)
err = th.Server.App().InsertBlock(model.Block{ID: "block-6", Title: "Test", Type: "card", BoardID: testData.privateTemplate.ID}, userAdmin)
err = th.Server.App().InsertBlock(&model.Block{ID: "block-6", Title: "Test", Type: "card", BoardID: testData.privateTemplate.ID}, userAdmin)
require.NoError(t, err)
err = th.Server.App().InsertBlock(model.Block{ID: "block-7", Title: "Test", Type: "card", BoardID: testData.publicBoard.ID}, userAdmin)
err = th.Server.App().InsertBlock(&model.Block{ID: "block-7", Title: "Test", Type: "card", BoardID: testData.publicBoard.ID}, userAdmin)
require.NoError(t, err)
err = th.Server.App().InsertBlock(model.Block{ID: "block-8", Title: "Test", Type: "card", BoardID: testData.privateBoard.ID}, userAdmin)
err = th.Server.App().InsertBlock(&model.Block{ID: "block-8", Title: "Test", Type: "card", BoardID: testData.privateBoard.ID}, userAdmin)
require.NoError(t, err)
}
@ -1393,13 +1393,13 @@ func TestPermissionsDeleteBoardBlock(t *testing.T) {
func TestPermissionsUndeleteBoardBlock(t *testing.T) {
extraSetup := func(t *testing.T, th *TestHelper, testData TestData) {
err := th.Server.App().InsertBlock(model.Block{ID: "block-5", Title: "Test", Type: "card", BoardID: testData.publicTemplate.ID}, userAdmin)
err := th.Server.App().InsertBlock(&model.Block{ID: "block-5", Title: "Test", Type: "card", BoardID: testData.publicTemplate.ID}, userAdmin)
require.NoError(t, err)
err = th.Server.App().InsertBlock(model.Block{ID: "block-6", Title: "Test", Type: "card", BoardID: testData.privateTemplate.ID}, userAdmin)
err = th.Server.App().InsertBlock(&model.Block{ID: "block-6", Title: "Test", Type: "card", BoardID: testData.privateTemplate.ID}, userAdmin)
require.NoError(t, err)
err = th.Server.App().InsertBlock(model.Block{ID: "block-7", Title: "Test", Type: "card", BoardID: testData.publicBoard.ID}, userAdmin)
err = th.Server.App().InsertBlock(&model.Block{ID: "block-7", Title: "Test", Type: "card", BoardID: testData.publicBoard.ID}, userAdmin)
require.NoError(t, err)
err = th.Server.App().InsertBlock(model.Block{ID: "block-8", Title: "Test", Type: "card", BoardID: testData.privateBoard.ID}, userAdmin)
err = th.Server.App().InsertBlock(&model.Block{ID: "block-8", Title: "Test", Type: "card", BoardID: testData.privateBoard.ID}, userAdmin)
require.NoError(t, err)
err = th.Server.App().DeleteBlock("block-1", userAdmin)
require.NoError(t, err)
@ -1548,13 +1548,13 @@ func TestPermissionsUndeleteBoard(t *testing.T) {
func TestPermissionsDuplicateBoardBlock(t *testing.T) {
extraSetup := func(t *testing.T, th *TestHelper, testData TestData) {
err := th.Server.App().InsertBlock(model.Block{ID: "block-5", Title: "Test", Type: "card", BoardID: testData.publicTemplate.ID}, userAdmin)
err := th.Server.App().InsertBlock(&model.Block{ID: "block-5", Title: "Test", Type: "card", BoardID: testData.publicTemplate.ID}, userAdmin)
require.NoError(t, err)
err = th.Server.App().InsertBlock(model.Block{ID: "block-6", Title: "Test", Type: "card", BoardID: testData.privateTemplate.ID}, userAdmin)
err = th.Server.App().InsertBlock(&model.Block{ID: "block-6", Title: "Test", Type: "card", BoardID: testData.privateTemplate.ID}, userAdmin)
require.NoError(t, err)
err = th.Server.App().InsertBlock(model.Block{ID: "block-7", Title: "Test", Type: "card", BoardID: testData.publicBoard.ID}, userAdmin)
err = th.Server.App().InsertBlock(&model.Block{ID: "block-7", Title: "Test", Type: "card", BoardID: testData.publicBoard.ID}, userAdmin)
require.NoError(t, err)
err = th.Server.App().InsertBlock(model.Block{ID: "block-8", Title: "Test", Type: "card", BoardID: testData.privateBoard.ID}, userAdmin)
err = th.Server.App().InsertBlock(&model.Block{ID: "block-8", Title: "Test", Type: "card", BoardID: testData.privateBoard.ID}, userAdmin)
require.NoError(t, err)
}
@ -2592,7 +2592,7 @@ func TestPermissionsUpdateUserConfig(t *testing.T) {
func TestPermissionsCreateBoardsAndBlocks(t *testing.T) {
bab := toJSON(t, model.BoardsAndBlocks{
Boards: []*model.Board{{ID: "test", Title: "Test Board", TeamID: "test-team"}},
Blocks: []model.Block{
Blocks: []*model.Block{
{ID: "test-block", BoardID: "test", Type: "card", CreateAt: model.GetMillis(), UpdateAt: model.GetMillis()},
},
})
@ -3773,3 +3773,40 @@ func TestPermissionsChannel(t *testing.T) {
runTestCases(t, ttCases, testData, clients)
})
}
func TestPermissionsGetStatistics(t *testing.T) {
t.Run("plugin", func(t *testing.T) {
th := SetupTestHelperPluginMode(t)
defer th.TearDown()
clients := setupClients(th)
testData := setupData(t, th)
ttCases := []TestCase{
{"/statistics", methodGet, "", userAnon, http.StatusUnauthorized, 0},
{"/statistics", methodGet, "", userNoTeamMember, http.StatusForbidden, 0},
{"/statistics", methodGet, "", userTeamMember, http.StatusForbidden, 0},
{"/statistics", methodGet, "", userViewer, http.StatusForbidden, 0},
{"/statistics", methodGet, "", userCommenter, http.StatusForbidden, 0},
{"/statistics", methodGet, "", userEditor, http.StatusForbidden, 0},
{"/statistics", methodGet, "", userAdmin, http.StatusOK, 1},
{"/statistics", methodGet, "", userGuest, http.StatusForbidden, 0},
}
runTestCases(t, ttCases, testData, clients)
})
t.Run("local", func(t *testing.T) {
th := SetupTestHelperLocalMode(t)
defer th.TearDown()
clients := setupLocalClients(th)
testData := setupData(t, th)
ttCases := []TestCase{
{"/statistics", methodGet, "", userAnon, http.StatusUnauthorized, 0},
{"/statistics", methodGet, "", userNoTeamMember, http.StatusNotImplemented, 0},
{"/statistics", methodGet, "", userTeamMember, http.StatusNotImplemented, 0},
{"/statistics", methodGet, "", userViewer, http.StatusNotImplemented, 0},
{"/statistics", methodGet, "", userCommenter, http.StatusNotImplemented, 0},
{"/statistics", methodGet, "", userEditor, http.StatusNotImplemented, 0},
{"/statistics", methodGet, "", userAdmin, http.StatusNotImplemented, 1},
{"/statistics", methodGet, "", userGuest, http.StatusForbidden, 0},
}
runTestCases(t, ttCases, testData, clients)
})
}

View File

@ -0,0 +1,55 @@
package integrationtests
import (
"testing"
"github.com/mattermost/focalboard/server/client"
"github.com/mattermost/focalboard/server/model"
"github.com/stretchr/testify/require"
)
func TestStatisticsLocalMode(t *testing.T) {
th := SetupTestHelper(t).InitBasic()
defer th.TearDown()
t.Run("an unauthenticated client should not be able to get statistics", func(t *testing.T) {
th.Logout(th.Client)
stats, resp := th.Client.GetStatistics()
th.CheckUnauthorized(resp)
require.Nil(t, stats)
})
t.Run("Check authenticated user, not admin", func(t *testing.T) {
th.Login1()
stats, resp := th.Client.GetStatistics()
th.CheckNotImplemented(resp)
require.Nil(t, stats)
})
}
func TestStatisticsPluginMode(t *testing.T) {
th := SetupTestHelperPluginMode(t)
defer th.TearDown()
// Permissions are tested in permissions_test.go
// This tests the functionality.
t.Run("Check authenticated user, admin", func(t *testing.T) {
th.Client = client.NewClient(th.Server.Config().ServerRoot, "")
th.Client.HTTPHeader["Mattermost-User-Id"] = userAdmin
stats, resp := th.Client.GetStatistics()
th.CheckOK(resp)
require.NotNil(t, stats)
numberCards := 2
th.CreateBoardAndCards("testTeam", model.BoardTypeOpen, numberCards)
stats, resp = th.Client.GetStatistics()
th.CheckOK(resp)
require.NotNil(t, stats)
require.Equal(t, 1, stats.Boards)
require.Equal(t, numberCards, stats.Cards)
})
}

View File

@ -31,7 +31,7 @@ func createTestSubscriptions(client *client.Client, num int) ([]*model.Subscript
}
for n := 0; n < num; n++ {
newBlock := model.Block{
newBlock := &model.Block{
ID: utils.NewID(utils.IDTypeCard),
BoardID: board.ID,
CreateAt: 1,
@ -39,7 +39,7 @@ func createTestSubscriptions(client *client.Client, num int) ([]*model.Subscript
Type: model.TypeCard,
}
newBlocks, resp := client.InsertBlocks(board.ID, []model.Block{newBlock}, false)
newBlocks, resp := client.InsertBlocks(board.ID, []*model.Block{newBlock}, false)
if resp.Error != nil {
return nil, "", fmt.Errorf("cannot insert test card block: %w", resp.Error)
}

View File

@ -118,14 +118,14 @@ type BoardModifier func(board *Board, cache map[string]interface{}) bool
// Return true to import the block or false to skip import.
type BlockModifier func(block *Block, cache map[string]interface{}) bool
func BlocksFromJSON(data io.Reader) []Block {
var blocks []Block
func BlocksFromJSON(data io.Reader) []*Block {
var blocks []*Block
_ = json.NewDecoder(data).Decode(&blocks)
return blocks
}
// LogClone implements the `mlog.LogCloner` interface to provide a subset of Block fields for logging.
func (b Block) LogClone() interface{} {
func (b *Block) LogClone() interface{} {
return struct {
ID string
ParentID string
@ -199,7 +199,7 @@ type QueryBoardHistoryOptions struct {
Descending bool // if true then the records are sorted by insert_at in descending order
}
func StampModificationMetadata(userID string, blocks []Block, auditRec *audit.Record) {
func StampModificationMetadata(userID string, blocks []*Block, auditRec *audit.Record) {
if userID == SingleUser {
userID = ""
}
@ -215,15 +215,15 @@ func StampModificationMetadata(userID string, blocks []Block, auditRec *audit.Re
}
}
func (b Block) ShouldBeLimited(cardLimitTimestamp int64) bool {
func (b *Block) ShouldBeLimited(cardLimitTimestamp int64) bool {
return b.Type == TypeCard &&
b.UpdateAt < cardLimitTimestamp
}
// Returns a limited version of the block that doesn't contain the
// contents of the block, only its IDs and type.
func (b Block) GetLimited() Block {
newBlock := Block{
func (b *Block) GetLimited() *Block {
newBlock := &Block{
Title: b.Title,
ID: b.ID,
ParentID: b.ParentID,

View File

@ -15,7 +15,7 @@ import (
func TestGenerateBlockIDs(t *testing.T) {
t.Run("Should generate a new ID for a single block with no references", func(t *testing.T) {
blockID := utils.NewID(utils.IDTypeBlock)
blocks := []Block{{ID: blockID}}
blocks := []*Block{{ID: blockID}}
blocks = GenerateBlockIDs(blocks, &mlog.Logger{})
@ -28,7 +28,7 @@ func TestGenerateBlockIDs(t *testing.T) {
blockID := utils.NewID(utils.IDTypeBlock)
boardID := utils.NewID(utils.IDTypeBlock)
parentID := utils.NewID(utils.IDTypeBlock)
blocks := []Block{{ID: blockID, BoardID: boardID, ParentID: parentID}}
blocks := []*Block{{ID: blockID, BoardID: boardID, ParentID: parentID}}
blocks = GenerateBlockIDs(blocks, &mlog.Logger{})
@ -41,14 +41,14 @@ func TestGenerateBlockIDs(t *testing.T) {
blockID1 := utils.NewID(utils.IDTypeBlock)
boardID1 := utils.NewID(utils.IDTypeBlock)
parentID1 := utils.NewID(utils.IDTypeBlock)
block1 := Block{ID: blockID1, BoardID: boardID1, ParentID: parentID1}
block1 := &Block{ID: blockID1, BoardID: boardID1, ParentID: parentID1}
blockID2 := utils.NewID(utils.IDTypeBlock)
boardID2 := blockID1
parentID2 := utils.NewID(utils.IDTypeBlock)
block2 := Block{ID: blockID2, BoardID: boardID2, ParentID: parentID2}
block2 := &Block{ID: blockID2, BoardID: boardID2, ParentID: parentID2}
blocks := []Block{block1, block2}
blocks := []*Block{block1, block2}
blocks = GenerateBlockIDs(blocks, &mlog.Logger{})
@ -69,14 +69,14 @@ func TestGenerateBlockIDs(t *testing.T) {
blockID1 := utils.NewID(utils.IDTypeBlock)
boardID1 := ""
parentID1 := utils.NewID(utils.IDTypeBlock)
block1 := Block{ID: blockID1, BoardID: boardID1, ParentID: parentID1}
block1 := &Block{ID: blockID1, BoardID: boardID1, ParentID: parentID1}
blockID2 := utils.NewID(utils.IDTypeBlock)
boardID2 := utils.NewID(utils.IDTypeBlock)
parentID2 := ""
block2 := Block{ID: blockID2, BoardID: boardID2, ParentID: parentID2}
block2 := &Block{ID: blockID2, BoardID: boardID2, ParentID: parentID2}
blocks := []Block{block1, block2}
blocks := []*Block{block1, block2}
blocks = GenerateBlockIDs(blocks, &mlog.Logger{})
@ -94,28 +94,28 @@ func TestGenerateBlockIDs(t *testing.T) {
blockID1 := utils.NewID(utils.IDTypeBlock)
boardID1 := utils.NewID(utils.IDTypeBlock)
parentID1 := utils.NewID(utils.IDTypeBlock)
block1 := Block{ID: blockID1, BoardID: boardID1, ParentID: parentID1}
block1 := &Block{ID: blockID1, BoardID: boardID1, ParentID: parentID1}
// linked to 1
blockID2 := utils.NewID(utils.IDTypeBlock)
boardID2 := blockID1
parentID2 := utils.NewID(utils.IDTypeBlock)
block2 := Block{ID: blockID2, BoardID: boardID2, ParentID: parentID2}
block2 := &Block{ID: blockID2, BoardID: boardID2, ParentID: parentID2}
// linked to 2
blockID3 := utils.NewID(utils.IDTypeBlock)
boardID3 := blockID2
parentID3 := utils.NewID(utils.IDTypeBlock)
block3 := Block{ID: blockID3, BoardID: boardID3, ParentID: parentID3}
block3 := &Block{ID: blockID3, BoardID: boardID3, ParentID: parentID3}
// linked to 1
blockID4 := utils.NewID(utils.IDTypeBlock)
boardID4 := blockID1
parentID4 := utils.NewID(utils.IDTypeBlock)
block4 := Block{ID: blockID4, BoardID: boardID4, ParentID: parentID4}
block4 := &Block{ID: blockID4, BoardID: boardID4, ParentID: parentID4}
// blocks are shuffled
blocks := []Block{block4, block2, block1, block3}
blocks := []*Block{block4, block2, block1, block3}
blocks = GenerateBlockIDs(blocks, &mlog.Logger{})
@ -147,7 +147,7 @@ func TestGenerateBlockIDs(t *testing.T) {
blockID1 := utils.NewID(utils.IDTypeBlock)
boardID1 := utils.NewID(utils.IDTypeBlock)
parentID1 := utils.NewID(utils.IDTypeBlock)
block1 := Block{
block1 := &Block{
ID: blockID1,
BoardID: boardID1,
ParentID: parentID1,
@ -156,7 +156,7 @@ func TestGenerateBlockIDs(t *testing.T) {
blockID2 := utils.NewID(utils.IDTypeBlock)
boardID2 := utils.NewID(utils.IDTypeBlock)
parentID2 := utils.NewID(utils.IDTypeBlock)
block2 := Block{
block2 := &Block{
ID: blockID2,
BoardID: boardID2,
ParentID: parentID2,
@ -167,7 +167,7 @@ func TestGenerateBlockIDs(t *testing.T) {
},
}
blocks := []Block{block1, block2}
blocks := []*Block{block1, block2}
blocks = GenerateBlockIDs(blocks, &mlog.Logger{})
@ -191,21 +191,21 @@ func TestGenerateBlockIDs(t *testing.T) {
blockID1 := utils.NewID(utils.IDTypeBlock)
boardID1 := utils.NewID(utils.IDTypeBlock)
parentID1 := utils.NewID(utils.IDTypeBlock)
block1 := Block{
block1 := &Block{
ID: blockID1,
BoardID: boardID1,
ParentID: parentID1,
}
blockID2 := utils.NewID(utils.IDTypeBlock)
block2 := Block{
block2 := &Block{
ID: blockID2,
BoardID: boardID1,
ParentID: parentID1,
}
blockID3 := utils.NewID(utils.IDTypeBlock)
block3 := Block{
block3 := &Block{
ID: blockID3,
BoardID: boardID1,
ParentID: parentID1,
@ -215,7 +215,7 @@ func TestGenerateBlockIDs(t *testing.T) {
boardID2 := utils.NewID(utils.IDTypeBlock)
parentID2 := utils.NewID(utils.IDTypeBlock)
block4 := Block{
block4 := &Block{
ID: blockID4,
BoardID: boardID2,
ParentID: parentID2,
@ -230,7 +230,7 @@ func TestGenerateBlockIDs(t *testing.T) {
},
}
blocks := []Block{block1, block2, block3, block4}
blocks := []*Block{block1, block2, block3, block4}
blocks = GenerateBlockIDs(blocks, &mlog.Logger{})
@ -258,7 +258,7 @@ func TestGenerateBlockIDs(t *testing.T) {
blockID1 := utils.NewID(utils.IDTypeBlock)
boardID1 := utils.NewID(utils.IDTypeBlock)
parentID1 := utils.NewID(utils.IDTypeBlock)
block1 := Block{
block1 := &Block{
ID: blockID1,
BoardID: boardID1,
ParentID: parentID1,
@ -267,7 +267,7 @@ func TestGenerateBlockIDs(t *testing.T) {
blockID2 := utils.NewID(utils.IDTypeBlock)
boardID2 := utils.NewID(utils.IDTypeBlock)
parentID2 := utils.NewID(utils.IDTypeBlock)
block2 := Block{
block2 := &Block{
ID: blockID2,
BoardID: boardID2,
ParentID: parentID2,
@ -276,7 +276,7 @@ func TestGenerateBlockIDs(t *testing.T) {
},
}
blocks := []Block{block1, block2}
blocks := []*Block{block1, block2}
blocks = GenerateBlockIDs(blocks, &mlog.Logger{})
@ -297,8 +297,8 @@ func TestGenerateBlockIDs(t *testing.T) {
func TestStampModificationMetadata(t *testing.T) {
t.Run("base case", func(t *testing.T) {
block := Block{}
blocks := []Block{block}
block := &Block{}
blocks := []*Block{block}
assert.Empty(t, block.ModifiedBy)
assert.Empty(t, block.UpdateAt)

View File

@ -12,7 +12,7 @@ import (
// keeping consistent any references that other blocks would made to
// the original IDs, so a tree of blocks can get new IDs and maintain
// its shape.
func GenerateBlockIDs(blocks []Block, logger mlog.LoggerIFace) []Block {
func GenerateBlockIDs(blocks []*Block, logger mlog.LoggerIFace) []*Block {
blockIDs := map[string]BlockType{}
referenceIDs := map[string]bool{}
for _, block := range blocks {
@ -93,7 +93,7 @@ func GenerateBlockIDs(blocks []Block, logger mlog.LoggerIFace) []Block {
return utils.NewID(BlockType2IDType(blockIDs[id]))
}
newBlocks := make([]Block, len(blocks))
newBlocks := make([]*Block, len(blocks))
for i, block := range blocks {
block.ID = getExistingOrNewID(block.ID)
block.BoardID = getExistingOrOldID(block.BoardID)
@ -101,11 +101,11 @@ func GenerateBlockIDs(blocks []Block, logger mlog.LoggerIFace) []Block {
blockMod := block
if _, ok := blockMod.Fields["contentOrder"]; ok {
fixFieldIDs(&blockMod, "contentOrder", getExistingOrOldID, logger)
fixFieldIDs(blockMod, "contentOrder", getExistingOrOldID, logger)
}
if _, ok := blockMod.Fields["cardOrder"]; ok {
fixFieldIDs(&blockMod, "cardOrder", getExistingOrOldID, logger)
fixFieldIDs(blockMod, "cardOrder", getExistingOrOldID, logger)
}
if _, ok := blockMod.Fields["defaultTemplateId"]; ok {

View File

@ -0,0 +1,15 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
// BoardsStatistics is the representation of the statistics for the Boards server
// swagger:model
type BoardsStatistics struct {
// The maximum number of cards on the server
// required: true
Boards int `json:"board_count"`
// The maximum number of cards on the server
// required: true
Cards int `json:"card_count"`
}

View File

@ -35,7 +35,7 @@ type BoardsAndBlocks struct {
// The blocks
// required: false
Blocks []Block `json:"blocks"`
Blocks []*Block `json:"blocks"`
}
func (bab *BoardsAndBlocks) IsValid() error {
@ -139,13 +139,13 @@ func GenerateBoardsAndBlocksIDs(bab *BoardsAndBlocks, logger mlog.LoggerIFace) (
return nil, err
}
blocksByBoard := map[string][]Block{}
blocksByBoard := map[string][]*Block{}
for _, block := range bab.Blocks {
blocksByBoard[block.BoardID] = append(blocksByBoard[block.BoardID], block)
}
boards := []*Board{}
blocks := []Block{}
blocks := []*Block{}
for _, board := range bab.Boards {
newID := utils.NewID(utils.IDTypeBoard)
for _, block := range blocksByBoard[board.ID] {

View File

@ -11,7 +11,7 @@ import (
func TestIsValidBoardsAndBlocks(t *testing.T) {
t.Run("no boards", func(t *testing.T) {
bab := &BoardsAndBlocks{
Blocks: []Block{
Blocks: []*Block{
{ID: "block-id-1", BoardID: "board-id-1", Type: TypeCard},
{ID: "block-id-2", BoardID: "board-id-2", Type: TypeCard},
},
@ -37,7 +37,7 @@ func TestIsValidBoardsAndBlocks(t *testing.T) {
{ID: "board-id-1", Type: BoardTypeOpen},
{ID: "board-id-2", Type: BoardTypePrivate},
},
Blocks: []Block{
Blocks: []*Block{
{ID: "block-id-1", BoardID: "board-id-1", Type: TypeCard},
{ID: "block-id-3", BoardID: "board-id-3", Type: TypeCard},
{ID: "block-id-2", BoardID: "board-id-2", Type: TypeCard},
@ -53,7 +53,7 @@ func TestIsValidBoardsAndBlocks(t *testing.T) {
{ID: "board-id-1", Type: BoardTypeOpen},
{ID: "board-id-2", Type: BoardTypePrivate},
},
Blocks: []Block{
Blocks: []*Block{
{ID: "block-id-1", BoardID: "board-id-1", Type: TypeCard},
{ID: "block-id-3", BoardID: "board-id-2", Type: TypeCard},
{ID: "block-id-2", BoardID: "board-id-2", Type: TypeCard},
@ -68,13 +68,13 @@ func TestGenerateBoardsAndBlocksIDs(t *testing.T) {
logger, err := mlog.NewLogger()
require.NoError(t, err)
getBlockByType := func(blocks []Block, blockType BlockType) Block {
getBlockByType := func(blocks []*Block, blockType BlockType) *Block {
for _, b := range blocks {
if b.Type == blockType {
return b
}
}
return Block{}
return &Block{}
}
getBoardByTitle := func(boards []*Board, title string) *Board {
@ -88,7 +88,7 @@ func TestGenerateBoardsAndBlocksIDs(t *testing.T) {
t.Run("invalid boards and blocks", func(t *testing.T) {
bab := &BoardsAndBlocks{
Blocks: []Block{
Blocks: []*Block{
{ID: "block-id-1", BoardID: "board-id-1", Type: TypeCard},
{ID: "block-id-2", BoardID: "board-id-2", Type: TypeCard},
},
@ -106,7 +106,7 @@ func TestGenerateBoardsAndBlocksIDs(t *testing.T) {
{ID: "board-id-2", Type: BoardTypePrivate, Title: "board2"},
{ID: "board-id-3", Type: BoardTypeOpen, Title: "board3"},
},
Blocks: []Block{
Blocks: []*Block{
{ID: "block-id-1", BoardID: "board-id-1", Type: TypeCard},
{ID: "block-id-2", BoardID: "board-id-2", Type: TypeView},
{ID: "block-id-3", BoardID: "board-id-2", Type: TypeText},

View File

@ -347,6 +347,20 @@ func (mr *MockServicesAPIMockRecorder) GetUsersFromProfiles(arg0 interface{}) *g
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUsersFromProfiles", reflect.TypeOf((*MockServicesAPI)(nil).GetUsersFromProfiles), arg0)
}
// HasPermissionTo mocks base method.
func (m *MockServicesAPI) HasPermissionTo(arg0 string, arg1 *model.Permission) bool {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "HasPermissionTo", arg0, arg1)
ret0, _ := ret[0].(bool)
return ret0
}
// HasPermissionTo indicates an expected call of HasPermissionTo.
func (mr *MockServicesAPIMockRecorder) HasPermissionTo(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasPermissionTo", reflect.TypeOf((*MockServicesAPI)(nil).HasPermissionTo), arg0, arg1)
}
// HasPermissionToChannel mocks base method.
func (m *MockServicesAPI) HasPermissionToChannel(arg0, arg1 string, arg2 *model.Permission) bool {
m.ctrl.T.Helper()

View File

@ -49,6 +49,7 @@ type ServicesAPI interface {
CreateMember(teamID string, userID string) (*mm_model.TeamMember, error)
// Permissions service
HasPermissionTo(userID string, permission *mm_model.Permission) bool
HasPermissionToTeam(userID, teamID string, permission *mm_model.Permission) bool
HasPermissionToChannel(askingUserID string, channelID string, permission *mm_model.Permission) bool

View File

@ -10,8 +10,8 @@ import (
)
type AppAPI interface {
GetBlockHistory(blockID string, opts model.QueryBlockHistoryOptions) ([]model.Block, error)
GetSubTree2(boardID, blockID string, opts model.QuerySubtreeOptions) ([]model.Block, error)
GetBlockHistory(blockID string, opts model.QueryBlockHistoryOptions) ([]*model.Block, error)
GetSubTree2(boardID, blockID string, opts model.QuerySubtreeOptions) ([]*model.Block, error)
GetBoardAndCardByID(blockID string) (board *model.Board, card *model.Block, err error)
GetUserByID(userID string) (*model.User, error)

View File

@ -68,7 +68,7 @@ func (dg *diffGenerator) generateDiffs() ([]*Diff, error) {
if len(blocks) == 0 {
return nil, fmt.Errorf("block not found for notification: %w", err)
}
block := &blocks[0]
block := blocks[0]
if dg.board == nil || dg.card == nil {
return nil, fmt.Errorf("cannot generate diff for block %s; must have a valid board and card: %w", dg.hint.BlockID, err)
@ -166,7 +166,7 @@ func (dg *diffGenerator) generateDiffsForCard(card *model.Block, schema model.Pr
continue
}
blockDiff, err := dg.generateDiffForBlock(&blocks[i], schema)
blockDiff, err := dg.generateDiffForBlock(blocks[i], schema)
if err != nil {
return nil, fmt.Errorf("could not generate diff for block %s: %w", blocks[i].ID, err)
}
@ -225,7 +225,7 @@ func (dg *diffGenerator) generateDiffForBlock(newBlock *model.Block, schema mode
var oldBlock *model.Block
if len(history) != 0 {
oldBlock = &history[0]
oldBlock = history[0]
dg.logger.Debug("generateDiffForBlock - old block",
mlog.String("block_id", oldBlock.ID),

View File

@ -23,6 +23,10 @@ func New(store permissions.Store, logger mlog.LoggerIFace) *Service {
}
}
func (s *Service) HasPermissionTo(userID string, permission *mmModel.Permission) bool {
return false
}
func (s *Service) HasPermissionToTeam(userID, teamID string, permission *mmModel.Permission) bool {
if userID == "" || teamID == "" || permission == nil {
return false

View File

@ -12,6 +12,7 @@ import (
)
type APIInterface interface {
HasPermissionTo(userID string, permission *mmModel.Permission) bool
HasPermissionToTeam(userID string, teamID string, permission *mmModel.Permission) bool
HasPermissionToChannel(userID string, channelID string, permission *mmModel.Permission) bool
}
@ -24,11 +25,19 @@ type Service struct {
func New(store permissions.Store, api APIInterface, logger mlog.LoggerIFace) *Service {
return &Service{
store: store,
api: api,
store: store,
api: api,
logger: logger,
}
}
func (s *Service) HasPermissionTo(userID string, permission *mmModel.Permission) bool {
if userID == "" || permission == nil {
return false
}
return s.api.HasPermissionTo(userID, permission)
}
func (s *Service) HasPermissionToTeam(userID, teamID string, permission *mmModel.Permission) bool {
if userID == "" || teamID == "" || permission == nil {
return false

View File

@ -11,6 +11,7 @@ import (
)
type PermissionsService interface {
HasPermissionTo(userID string, permission *mmModel.Permission) bool
HasPermissionToTeam(userID, teamID string, permission *mmModel.Permission) bool
HasPermissionToChannel(userID, channelID string, permission *mmModel.Permission) bool
HasPermissionToBoard(userID, boardID string, permission *mmModel.Permission) bool

View File

@ -800,15 +800,15 @@ func (s *MattermostAuthLayer) implicitBoardMembershipsFromRows(rows *sql.Rows) (
}
func (s *MattermostAuthLayer) GetMemberForBoard(boardID, userID string) (*model.BoardMember, error) {
bm, err := s.Store.GetMemberForBoard(boardID, userID)
bm, originalErr := s.Store.GetMemberForBoard(boardID, userID)
// Explicit membership not found
if model.IsErrNotFound(err) {
if model.IsErrNotFound(originalErr) {
if userID == model.SystemUserID {
return nil, model.NewErrNotFound(userID)
}
var user *model.User
// No synthetic memberships for guests
user, err = s.GetUserByID(userID)
user, err := s.GetUserByID(userID)
if err != nil {
return nil, err
}
@ -867,8 +867,8 @@ func (s *MattermostAuthLayer) GetMemberForBoard(boardID, userID string) (*model.
}, nil
}
}
if err != nil {
return nil, err
if originalErr != nil {
return nil, originalErr
}
return bm, nil
}

View File

@ -295,10 +295,10 @@ func (mr *MockStoreMockRecorder) DeleteSubscription(arg0, arg1 interface{}) *gom
}
// DuplicateBlock mocks base method.
func (m *MockStore) DuplicateBlock(arg0, arg1, arg2 string, arg3 bool) ([]model.Block, error) {
func (m *MockStore) DuplicateBlock(arg0, arg1, arg2 string, arg3 bool) ([]*model.Block, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DuplicateBlock", arg0, arg1, arg2, arg3)
ret0, _ := ret[0].([]model.Block)
ret0, _ := ret[0].([]*model.Block)
ret1, _ := ret[1].(error)
return ret0, ret1
}
@ -386,10 +386,10 @@ func (mr *MockStoreMockRecorder) GetBlockCountsByType() *gomock.Call {
}
// GetBlockHistory mocks base method.
func (m *MockStore) GetBlockHistory(arg0 string, arg1 model.QueryBlockHistoryOptions) ([]model.Block, error) {
func (m *MockStore) GetBlockHistory(arg0 string, arg1 model.QueryBlockHistoryOptions) ([]*model.Block, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetBlockHistory", arg0, arg1)
ret0, _ := ret[0].([]model.Block)
ret0, _ := ret[0].([]*model.Block)
ret1, _ := ret[1].(error)
return ret0, ret1
}
@ -401,10 +401,10 @@ func (mr *MockStoreMockRecorder) GetBlockHistory(arg0, arg1 interface{}) *gomock
}
// GetBlockHistoryDescendants mocks base method.
func (m *MockStore) GetBlockHistoryDescendants(arg0 string, arg1 model.QueryBlockHistoryOptions) ([]model.Block, error) {
func (m *MockStore) GetBlockHistoryDescendants(arg0 string, arg1 model.QueryBlockHistoryOptions) ([]*model.Block, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetBlockHistoryDescendants", arg0, arg1)
ret0, _ := ret[0].([]model.Block)
ret0, _ := ret[0].([]*model.Block)
ret1, _ := ret[1].(error)
return ret0, ret1
}
@ -416,10 +416,10 @@ func (mr *MockStoreMockRecorder) GetBlockHistoryDescendants(arg0, arg1 interface
}
// GetBlocks mocks base method.
func (m *MockStore) GetBlocks(arg0 model.QueryBlocksOptions) ([]model.Block, error) {
func (m *MockStore) GetBlocks(arg0 model.QueryBlocksOptions) ([]*model.Block, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetBlocks", arg0)
ret0, _ := ret[0].([]model.Block)
ret0, _ := ret[0].([]*model.Block)
ret1, _ := ret[1].(error)
return ret0, ret1
}
@ -431,10 +431,10 @@ func (mr *MockStoreMockRecorder) GetBlocks(arg0 interface{}) *gomock.Call {
}
// GetBlocksByIDs mocks base method.
func (m *MockStore) GetBlocksByIDs(arg0 []string) ([]model.Block, error) {
func (m *MockStore) GetBlocksByIDs(arg0 []string) ([]*model.Block, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetBlocksByIDs", arg0)
ret0, _ := ret[0].([]model.Block)
ret0, _ := ret[0].([]*model.Block)
ret1, _ := ret[1].(error)
return ret0, ret1
}
@ -446,10 +446,10 @@ func (mr *MockStoreMockRecorder) GetBlocksByIDs(arg0 interface{}) *gomock.Call {
}
// GetBlocksForBoard mocks base method.
func (m *MockStore) GetBlocksForBoard(arg0 string) ([]model.Block, error) {
func (m *MockStore) GetBlocksForBoard(arg0 string) ([]*model.Block, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetBlocksForBoard", arg0)
ret0, _ := ret[0].([]model.Block)
ret0, _ := ret[0].([]*model.Block)
ret1, _ := ret[1].(error)
return ret0, ret1
}
@ -461,10 +461,10 @@ func (mr *MockStoreMockRecorder) GetBlocksForBoard(arg0 interface{}) *gomock.Cal
}
// GetBlocksWithParent mocks base method.
func (m *MockStore) GetBlocksWithParent(arg0, arg1 string) ([]model.Block, error) {
func (m *MockStore) GetBlocksWithParent(arg0, arg1 string) ([]*model.Block, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetBlocksWithParent", arg0, arg1)
ret0, _ := ret[0].([]model.Block)
ret0, _ := ret[0].([]*model.Block)
ret1, _ := ret[1].(error)
return ret0, ret1
}
@ -476,10 +476,10 @@ func (mr *MockStoreMockRecorder) GetBlocksWithParent(arg0, arg1 interface{}) *go
}
// GetBlocksWithParentAndType mocks base method.
func (m *MockStore) GetBlocksWithParentAndType(arg0, arg1, arg2 string) ([]model.Block, error) {
func (m *MockStore) GetBlocksWithParentAndType(arg0, arg1, arg2 string) ([]*model.Block, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetBlocksWithParentAndType", arg0, arg1, arg2)
ret0, _ := ret[0].([]model.Block)
ret0, _ := ret[0].([]*model.Block)
ret1, _ := ret[1].(error)
return ret0, ret1
}
@ -491,10 +491,10 @@ func (mr *MockStoreMockRecorder) GetBlocksWithParentAndType(arg0, arg1, arg2 int
}
// GetBlocksWithType mocks base method.
func (m *MockStore) GetBlocksWithType(arg0, arg1 string) ([]model.Block, error) {
func (m *MockStore) GetBlocksWithType(arg0, arg1 string) ([]*model.Block, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetBlocksWithType", arg0, arg1)
ret0, _ := ret[0].([]model.Block)
ret0, _ := ret[0].([]*model.Block)
ret1, _ := ret[1].(error)
return ret0, ret1
}
@ -837,10 +837,10 @@ func (mr *MockStoreMockRecorder) GetSharing(arg0 interface{}) *gomock.Call {
}
// GetSubTree2 mocks base method.
func (m *MockStore) GetSubTree2(arg0, arg1 string, arg2 model.QuerySubtreeOptions) ([]model.Block, error) {
func (m *MockStore) GetSubTree2(arg0, arg1 string, arg2 model.QuerySubtreeOptions) ([]*model.Block, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetSubTree2", arg0, arg1, arg2)
ret0, _ := ret[0].([]model.Block)
ret0, _ := ret[0].([]*model.Block)
ret1, _ := ret[1].(error)
return ret0, ret1
}
@ -1181,7 +1181,7 @@ func (mr *MockStoreMockRecorder) InsertBlock(arg0, arg1 interface{}) *gomock.Cal
}
// InsertBlocks mocks base method.
func (m *MockStore) InsertBlocks(arg0 []model.Block, arg1 string) error {
func (m *MockStore) InsertBlocks(arg0 []*model.Block, arg1 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "InsertBlocks", arg0, arg1)
ret0, _ := ret[0].(error)

View File

@ -55,7 +55,7 @@ func (s *SQLStore) blockFields() []string {
}
}
func (s *SQLStore) getBlocks(db sq.BaseRunner, opts model.QueryBlocksOptions) ([]model.Block, error) {
func (s *SQLStore) getBlocks(db sq.BaseRunner, opts model.QueryBlocksOptions) ([]*model.Block, error) {
query := s.getQueryBuilder(db).
Select(s.blockFields()...).
From(s.tablePrefix + "blocks")
@ -91,7 +91,7 @@ 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) {
func (s *SQLStore) getBlocksWithParentAndType(db sq.BaseRunner, boardID, parentID string, blockType string) ([]*model.Block, error) {
opts := model.QueryBlocksOptions{
BoardID: boardID,
ParentID: parentID,
@ -100,7 +100,7 @@ func (s *SQLStore) getBlocksWithParentAndType(db sq.BaseRunner, boardID, parentI
return s.getBlocks(db, opts)
}
func (s *SQLStore) getBlocksWithParent(db sq.BaseRunner, boardID, parentID string) ([]model.Block, error) {
func (s *SQLStore) getBlocksWithParent(db sq.BaseRunner, boardID, parentID string) ([]*model.Block, error) {
opts := model.QueryBlocksOptions{
BoardID: boardID,
ParentID: parentID,
@ -108,7 +108,7 @@ func (s *SQLStore) getBlocksWithParent(db sq.BaseRunner, boardID, parentID strin
return s.getBlocks(db, opts)
}
func (s *SQLStore) getBlocksByIDs(db sq.BaseRunner, ids []string) ([]model.Block, error) {
func (s *SQLStore) getBlocksByIDs(db sq.BaseRunner, ids []string) ([]*model.Block, error) {
query := s.getQueryBuilder(db).
Select(s.blockFields()...).
From(s.tablePrefix + "blocks").
@ -134,7 +134,7 @@ func (s *SQLStore) getBlocksByIDs(db sq.BaseRunner, ids []string) ([]model.Block
return blocks, nil
}
func (s *SQLStore) getBlocksWithType(db sq.BaseRunner, boardID, blockType string) ([]model.Block, error) {
func (s *SQLStore) getBlocksWithType(db sq.BaseRunner, boardID, blockType string) ([]*model.Block, error) {
opts := model.QueryBlocksOptions{
BoardID: boardID,
BlockType: model.BlockType(blockType),
@ -143,7 +143,7 @@ func (s *SQLStore) getBlocksWithType(db sq.BaseRunner, boardID, blockType string
}
// 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) {
func (s *SQLStore) getSubTree2(db sq.BaseRunner, boardID string, blockID string, opts model.QuerySubtreeOptions) ([]*model.Block, error) {
query := s.getQueryBuilder(db).
Select(s.blockFields()...).
From(s.tablePrefix + "blocks").
@ -174,15 +174,15 @@ 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) {
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{}
func (s *SQLStore) blocksFromRows(rows *sql.Rows) ([]*model.Block, error) {
results := []*model.Block{}
for rows.Next() {
var block model.Block
@ -223,7 +223,7 @@ func (s *SQLStore) blocksFromRows(rows *sql.Rows) ([]model.Block, error) {
return nil, err
}
results = append(results, block)
results = append(results, &block)
}
return results, nil
@ -336,14 +336,14 @@ func (s *SQLStore) patchBlocks(db sq.BaseRunner, blockPatches *model.BlockPatchB
return nil
}
func (s *SQLStore) insertBlocks(db sq.BaseRunner, blocks []model.Block, userID string) error {
func (s *SQLStore) insertBlocks(db sq.BaseRunner, blocks []*model.Block, userID string) error {
for _, block := range blocks {
if block.BoardID == "" {
return BoardIDNilError{}
}
}
for i := range blocks {
err := s.insertBlock(db, &blocks[i], userID)
err := s.insertBlock(db, blocks[i], userID)
if err != nil {
return err
}
@ -521,7 +521,8 @@ func (s *SQLStore) getBoardCount(db sq.BaseRunner) (int64, error) {
query := s.getQueryBuilder(db).
Select("COUNT(*) AS count").
From(s.tablePrefix + "boards").
Where(sq.Eq{"delete_at": 0})
Where(sq.Eq{"delete_at": 0}).
Where(sq.Eq{"is_template": false})
row := query.QueryRow()
@ -556,10 +557,10 @@ func (s *SQLStore) getBlock(db sq.BaseRunner, blockID string) (*model.Block, err
return nil, model.NewErrNotFound("block ID=" + blockID)
}
return &blocks[0], nil
return blocks[0], nil
}
func (s *SQLStore) getBlockHistory(db sq.BaseRunner, blockID string, opts model.QueryBlockHistoryOptions) ([]model.Block, error) {
func (s *SQLStore) getBlockHistory(db sq.BaseRunner, blockID string, opts model.QueryBlockHistoryOptions) ([]*model.Block, error) {
var order string
if opts.Descending {
order = descClause
@ -593,7 +594,7 @@ func (s *SQLStore) getBlockHistory(db sq.BaseRunner, blockID string, opts model.
return s.blocksFromRows(rows)
}
func (s *SQLStore) getBlockHistoryDescendants(db sq.BaseRunner, boardID string, opts model.QueryBlockHistoryOptions) ([]model.Block, error) {
func (s *SQLStore) getBlockHistoryDescendants(db sq.BaseRunner, boardID string, opts model.QueryBlockHistoryOptions) ([]*model.Block, error) {
var order string
if opts.Descending {
order = descClause
@ -645,7 +646,7 @@ func (s *SQLStore) getBoardAndCardByID(db sq.BaseRunner, blockID string) (board
return nil, nil, model.NewErrNotFound("block history BlockID=" + blockID)
}
return s.getBoardAndCard(db, &blocks[0])
return s.getBoardAndCard(db, blocks[0])
}
// getBoardAndCard returns the first parent of type `card` and and the `board` for the specified block.
@ -677,7 +678,7 @@ func (s *SQLStore) getBoardAndCard(db sq.BaseRunner, block *model.Block) (board
if len(blocks) == 0 {
return board, card, nil
}
iter = &blocks[0]
iter = blocks[0]
}
board, err = s.getBoard(db, block.BoardID)
if err != nil {
@ -754,7 +755,7 @@ func (s *SQLStore) replaceBlockID(db sq.BaseRunner, currentID, newID, workspaceI
return nil
}
func (s *SQLStore) duplicateBlock(db sq.BaseRunner, boardID string, blockID string, userID string, asTemplate bool) ([]model.Block, error) {
func (s *SQLStore) duplicateBlock(db sq.BaseRunner, boardID string, blockID string, userID string, asTemplate bool) ([]*model.Block, error) {
blocks, err := s.getSubTree2(db, boardID, blockID, model.QuerySubtreeOptions{})
if err != nil {
return nil, err
@ -764,8 +765,8 @@ func (s *SQLStore) duplicateBlock(db sq.BaseRunner, boardID string, blockID stri
return nil, model.NewErrNotFound(message)
}
var rootBlock model.Block
allBlocks := []model.Block{}
var rootBlock *model.Block
allBlocks := []*model.Block{}
for _, block := range blocks {
if block.Type == model.TypeComment {
continue
@ -780,7 +781,7 @@ func (s *SQLStore) duplicateBlock(db sq.BaseRunner, boardID string, blockID stri
allBlocks = append(allBlocks, block)
}
}
allBlocks = append([]model.Block{rootBlock}, allBlocks...)
allBlocks = append([]*model.Block{rootBlock}, allBlocks...)
allBlocks = model.GenerateBlockIDs(allBlocks, nil)
if err := s.insertBlocks(db, allBlocks, userID); err != nil {

View File

@ -43,7 +43,7 @@ func (s *SQLStore) createBoardsAndBlocksWithAdmin(db sq.BaseRunner, bab *model.B
func (s *SQLStore) createBoardsAndBlocks(db sq.BaseRunner, bab *model.BoardsAndBlocks, userID string) (*model.BoardsAndBlocks, error) {
boards := []*model.Board{}
blocks := []model.Block{}
blocks := []*model.Block{}
for _, board := range bab.Boards {
newBoard, err := s.insertBoard(db, board, userID)
@ -56,7 +56,7 @@ func (s *SQLStore) createBoardsAndBlocks(db sq.BaseRunner, bab *model.BoardsAndB
for _, block := range bab.Blocks {
b := block
err := s.insertBlock(db, &b, userID)
err := s.insertBlock(db, b, userID)
if err != nil {
return nil, err
}
@ -90,7 +90,7 @@ func (s *SQLStore) patchBoardsAndBlocks(db sq.BaseRunner, pbab *model.PatchBoard
if err != nil {
return nil, err
}
bab.Blocks = append(bab.Blocks, *block)
bab.Blocks = append(bab.Blocks, block)
}
return bab, nil
@ -130,7 +130,7 @@ func (s *SQLStore) deleteBoardsAndBlocks(db sq.BaseRunner, dbab *model.DeleteBoa
func (s *SQLStore) duplicateBoard(db sq.BaseRunner, boardID string, userID string, toTeam string, asTemplate bool) (*model.BoardsAndBlocks, []*model.BoardMember, error) {
bab := &model.BoardsAndBlocks{
Boards: []*model.Board{},
Blocks: []model.Block{},
Blocks: []*model.Block{},
}
board, err := s.getBoard(db, boardID)
@ -162,7 +162,7 @@ func (s *SQLStore) duplicateBoard(db sq.BaseRunner, boardID string, userID strin
if err != nil {
return nil, nil, err
}
newBlocks := []model.Block{}
newBlocks := []*model.Block{}
for _, b := range blocks {
if b.Type != model.TypeComment {
newBlocks = append(newBlocks, b)

View File

@ -28,7 +28,7 @@ const (
DeletedMembershipBoardsMigrationKey = "DeletedMembershipBoardsMigrationComplete"
)
func (s *SQLStore) getBlocksWithSameID(db sq.BaseRunner) ([]model.Block, error) {
func (s *SQLStore) getBlocksWithSameID(db sq.BaseRunner) ([]*model.Block, error) {
subquery, _, _ := s.getQueryBuilder(db).
Select("id").
From(s.tablePrefix + "blocks").
@ -93,7 +93,7 @@ func (s *SQLStore) RunUniqueIDsMigration() error {
return fmt.Errorf("cannot get blocks with same ID: %w", err)
}
blocksByID := map[string][]model.Block{}
blocksByID := map[string][]*model.Block{}
for _, block := range blocks {
blocksByID[block.ID] = append(blocksByID[block.ID], block)
}
@ -655,11 +655,11 @@ func (s *SQLStore) RunFixCollationsAndCharsetsMigration() error {
}
// get collation and charSet setting that Channels is using.
// when unit testing, no channels tables exist so just set to a default.
// when personal server or unit testing, no channels tables exist so just set to a default.
var collation string
var charSet string
var err error
if os.Getenv("FOCALBOARD_UNIT_TESTING") == "1" {
if !s.isPlugin || os.Getenv("FOCALBOARD_UNIT_TESTING") == "1" {
collation = "utf8mb4_general_ci"
charSet = "utf8mb4"
} else {

View File

@ -10,7 +10,6 @@ import (
"github.com/stretchr/testify/require"
)
//nolint:gosec
func TestGetBlocksWithSameID(t *testing.T) {
t.Skip("we need to setup a test with the database migrated up to version 14 and then run these tests")
@ -22,51 +21,50 @@ func TestGetBlocksWithSameID(t *testing.T) {
container2 := "2"
container3 := "3"
block1 := model.Block{ID: "block-id-1", BoardID: "board-id-1"}
block2 := model.Block{ID: "block-id-2", BoardID: "board-id-2"}
block3 := model.Block{ID: "block-id-3", BoardID: "board-id-3"}
block1 := &model.Block{ID: "block-id-1", BoardID: "board-id-1"}
block2 := &model.Block{ID: "block-id-2", BoardID: "board-id-2"}
block3 := &model.Block{ID: "block-id-3", BoardID: "board-id-3"}
block4 := model.Block{ID: "block-id-1", BoardID: "board-id-1"}
block5 := model.Block{ID: "block-id-2", BoardID: "board-id-2"}
block4 := &model.Block{ID: "block-id-1", BoardID: "board-id-1"}
block5 := &model.Block{ID: "block-id-2", BoardID: "board-id-2"}
block6 := model.Block{ID: "block-id-1", BoardID: "board-id-1"}
block7 := model.Block{ID: "block-id-7", BoardID: "board-id-7"}
block8 := model.Block{ID: "block-id-8", BoardID: "board-id-8"}
block6 := &model.Block{ID: "block-id-1", BoardID: "board-id-1"}
block7 := &model.Block{ID: "block-id-7", BoardID: "board-id-7"}
block8 := &model.Block{ID: "block-id-8", BoardID: "board-id-8"}
for _, block := range []model.Block{block1, block2, block3} {
err := sqlStore.insertLegacyBlock(sqlStore.db, container1, &block, "user-id")
for _, block := range []*model.Block{block1, block2, block3} {
err := sqlStore.insertLegacyBlock(sqlStore.db, container1, block, "user-id")
require.NoError(t, err)
time.Sleep(100 * time.Millisecond)
}
for _, block := range []model.Block{block4, block5} {
err := sqlStore.insertLegacyBlock(sqlStore.db, container2, &block, "user-id")
for _, block := range []*model.Block{block4, block5} {
err := sqlStore.insertLegacyBlock(sqlStore.db, container2, block, "user-id")
require.NoError(t, err)
time.Sleep(100 * time.Millisecond)
}
for _, block := range []model.Block{block6, block7, block8} {
err := sqlStore.insertLegacyBlock(sqlStore.db, container3, &block, "user-id")
for _, block := range []*model.Block{block6, block7, block8} {
err := sqlStore.insertLegacyBlock(sqlStore.db, container3, block, "user-id")
require.NoError(t, err)
time.Sleep(100 * time.Millisecond)
}
blocksWithDuplicatedID := []model.Block{block1, block2, block4, block5, block6}
blocksWithDuplicatedID := []*model.Block{block1, block2, block4, block5, block6}
blocks, err := sqlStore.getBlocksWithSameID(sqlStore.db)
require.NoError(t, err)
// we process the found blocks to remove extra information and be
// able to compare both expected and found sets
foundBlocks := []model.Block{}
foundBlocks := []*model.Block{}
for _, foundBlock := range blocks {
foundBlocks = append(foundBlocks, model.Block{ID: foundBlock.ID, BoardID: foundBlock.BoardID})
foundBlocks = append(foundBlocks, &model.Block{ID: foundBlock.ID, BoardID: foundBlock.BoardID})
}
require.ElementsMatch(t, blocksWithDuplicatedID, foundBlocks)
}
//nolint:gosec
func TestReplaceBlockID(t *testing.T) {
t.Skip("we need to setup a test with the database migrated up to version 14 and then run these tests")
@ -78,33 +76,33 @@ func TestReplaceBlockID(t *testing.T) {
container2 := "2"
// blocks from team1
block1 := model.Block{ID: "block-id-1", BoardID: "board-id-1"}
block2 := model.Block{ID: "block-id-2", BoardID: "board-id-2", ParentID: "block-id-1"}
block3 := model.Block{ID: "block-id-3", BoardID: "block-id-1"}
block4 := model.Block{ID: "block-id-4", BoardID: "block-id-2"}
block5 := model.Block{ID: "block-id-5", BoardID: "block-id-1", ParentID: "block-id-1"}
block8 := model.Block{
block1 := &model.Block{ID: "block-id-1", BoardID: "board-id-1"}
block2 := &model.Block{ID: "block-id-2", BoardID: "board-id-2", ParentID: "block-id-1"}
block3 := &model.Block{ID: "block-id-3", BoardID: "block-id-1"}
block4 := &model.Block{ID: "block-id-4", BoardID: "block-id-2"}
block5 := &model.Block{ID: "block-id-5", BoardID: "block-id-1", ParentID: "block-id-1"}
block8 := &model.Block{
ID: "block-id-8", BoardID: "board-id-2", Type: model.TypeCard,
Fields: map[string]interface{}{"contentOrder": []string{"block-id-1", "block-id-2"}},
}
// blocks from team2. They're identical to blocks 1 and 2,
// but they shouldn't change
block6 := model.Block{ID: "block-id-1", BoardID: "board-id-1"}
block7 := model.Block{ID: "block-id-2", BoardID: "board-id-2", ParentID: "block-id-1"}
block9 := model.Block{
block6 := &model.Block{ID: "block-id-1", BoardID: "board-id-1"}
block7 := &model.Block{ID: "block-id-2", BoardID: "board-id-2", ParentID: "block-id-1"}
block9 := &model.Block{
ID: "block-id-8", BoardID: "board-id-2", Type: model.TypeCard,
Fields: map[string]interface{}{"contentOrder": []string{"block-id-1", "block-id-2"}},
}
for _, block := range []model.Block{block1, block2, block3, block4, block5, block8} {
err := sqlStore.insertLegacyBlock(sqlStore.db, container1, &block, "user-id")
for _, block := range []*model.Block{block1, block2, block3, block4, block5, block8} {
err := sqlStore.insertLegacyBlock(sqlStore.db, container1, block, "user-id")
require.NoError(t, err)
time.Sleep(100 * time.Millisecond)
}
for _, block := range []model.Block{block6, block7, block9} {
err := sqlStore.insertLegacyBlock(sqlStore.db, container2, &block, "user-id")
for _, block := range []*model.Block{block6, block7, block9} {
err := sqlStore.insertLegacyBlock(sqlStore.db, container2, block, "user-id")
require.NoError(t, err)
time.Sleep(100 * time.Millisecond)
}
@ -145,7 +143,6 @@ func TestReplaceBlockID(t *testing.T) {
require.Equal(t, newBlock9.Fields["contentOrder"].([]interface{})[1], "block-id-2")
}
//nolint:gosec
func TestRunUniqueIDsMigration(t *testing.T) {
t.Skip("we need to setup a test with the database migrated up to version 14 and then run these tests")
@ -164,33 +161,33 @@ func TestRunUniqueIDsMigration(t *testing.T) {
// blocks from workspace1. They shouldn't change, as the first
// duplicated ID is preserved
block1 := model.Block{ID: "block-id-1", BoardID: "board-id-1"}
block2 := model.Block{ID: "block-id-2", BoardID: "board-id-2", ParentID: "block-id-1"}
block3 := model.Block{ID: "block-id-3", BoardID: "block-id-1"}
block1 := &model.Block{ID: "block-id-1", BoardID: "board-id-1"}
block2 := &model.Block{ID: "block-id-2", BoardID: "board-id-2", ParentID: "block-id-1"}
block3 := &model.Block{ID: "block-id-3", BoardID: "block-id-1"}
// blocks from workspace2. They're identical to blocks 1, 2 and 3,
// and they should change
block4 := model.Block{ID: "block-id-1", BoardID: "board-id-1"}
block5 := model.Block{ID: "block-id-2", BoardID: "board-id-2", ParentID: "block-id-1"}
block6 := model.Block{ID: "block-id-6", BoardID: "block-id-1", ParentID: "block-id-2"}
block4 := &model.Block{ID: "block-id-1", BoardID: "board-id-1"}
block5 := &model.Block{ID: "block-id-2", BoardID: "board-id-2", ParentID: "block-id-1"}
block6 := &model.Block{ID: "block-id-6", BoardID: "block-id-1", ParentID: "block-id-2"}
// block from workspace3. It should change as well
block7 := model.Block{ID: "block-id-2", BoardID: "board-id-2"}
block7 := &model.Block{ID: "block-id-2", BoardID: "board-id-2"}
for _, block := range []model.Block{block1, block2, block3} {
err := sqlStore.insertLegacyBlock(sqlStore.db, container1, &block, "user-id-2")
for _, block := range []*model.Block{block1, block2, block3} {
err := sqlStore.insertLegacyBlock(sqlStore.db, container1, block, "user-id-2")
require.NoError(t, err)
time.Sleep(100 * time.Millisecond)
}
for _, block := range []model.Block{block4, block5, block6} {
err := sqlStore.insertLegacyBlock(sqlStore.db, container2, &block, "user-id-2")
for _, block := range []*model.Block{block4, block5, block6} {
err := sqlStore.insertLegacyBlock(sqlStore.db, container2, block, "user-id-2")
require.NoError(t, err)
time.Sleep(100 * time.Millisecond)
}
for _, block := range []model.Block{block7} {
err := sqlStore.insertLegacyBlock(sqlStore.db, container3, &block, "user-id-2")
for _, block := range []*model.Block{block7} {
err := sqlStore.insertLegacyBlock(sqlStore.db, container3, block, "user-id-2")
require.NoError(t, err)
time.Sleep(100 * time.Millisecond)
}

View File

@ -60,8 +60,8 @@ func legacyBoardFields(prefix string) []string {
// the old block model. This method is kept to enable the unique IDs
// data migration.
//nolint:unused
func (s *SQLStore) legacyBlocksFromRows(rows *sql.Rows) ([]model.Block, error) {
results := []model.Block{}
func (s *SQLStore) legacyBlocksFromRows(rows *sql.Rows) ([]*model.Block, error) {
results := []*model.Block{}
for rows.Next() {
var block model.Block
@ -103,7 +103,7 @@ func (s *SQLStore) legacyBlocksFromRows(rows *sql.Rows) ([]model.Block, error) {
return nil, err
}
results = append(results, block)
results = append(results, &block)
}
return results, nil
@ -150,7 +150,7 @@ func (s *SQLStore) getLegacyBlock(db sq.BaseRunner, workspaceID string, blockID
return nil, nil
}
return &blocks[0], nil
return blocks[0], nil
}
// insertLegacyBlock is the old insertBlock version that still uses

View File

@ -221,7 +221,7 @@ func (s *SQLStore) DeleteSubscription(blockID string, subscriberID string) error
}
func (s *SQLStore) DuplicateBlock(boardID string, blockID string, userID string, asTemplate bool) ([]model.Block, error) {
func (s *SQLStore) DuplicateBlock(boardID string, blockID string, userID string, asTemplate bool) ([]*model.Block, error) {
if s.dbType == model.SqliteDBType {
return s.duplicateBlock(s.db, boardID, blockID, userID, asTemplate)
}
@ -289,42 +289,42 @@ func (s *SQLStore) GetBlockCountsByType() (map[string]int64, error) {
}
func (s *SQLStore) GetBlockHistory(blockID string, opts model.QueryBlockHistoryOptions) ([]model.Block, error) {
func (s *SQLStore) GetBlockHistory(blockID string, opts model.QueryBlockHistoryOptions) ([]*model.Block, error) {
return s.getBlockHistory(s.db, blockID, opts)
}
func (s *SQLStore) GetBlockHistoryDescendants(boardID string, opts model.QueryBlockHistoryOptions) ([]model.Block, error) {
func (s *SQLStore) GetBlockHistoryDescendants(boardID string, opts model.QueryBlockHistoryOptions) ([]*model.Block, error) {
return s.getBlockHistoryDescendants(s.db, boardID, opts)
}
func (s *SQLStore) GetBlocks(opts model.QueryBlocksOptions) ([]model.Block, error) {
func (s *SQLStore) GetBlocks(opts model.QueryBlocksOptions) ([]*model.Block, error) {
return s.getBlocks(s.db, opts)
}
func (s *SQLStore) GetBlocksByIDs(ids []string) ([]model.Block, error) {
func (s *SQLStore) GetBlocksByIDs(ids []string) ([]*model.Block, error) {
return s.getBlocksByIDs(s.db, ids)
}
func (s *SQLStore) GetBlocksForBoard(boardID string) ([]model.Block, error) {
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) {
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) {
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) {
func (s *SQLStore) GetBlocksWithType(boardID string, blockType string) ([]*model.Block, error) {
return s.getBlocksWithType(s.db, boardID, blockType)
}
@ -439,7 +439,7 @@ func (s *SQLStore) GetSharing(rootID string) (*model.Sharing, error) {
}
func (s *SQLStore) GetSubTree2(boardID string, blockID string, opts model.QuerySubtreeOptions) ([]model.Block, error) {
func (s *SQLStore) GetSubTree2(boardID string, blockID string, opts model.QuerySubtreeOptions) ([]*model.Block, error) {
return s.getSubTree2(s.db, boardID, blockID, opts)
}
@ -573,7 +573,7 @@ func (s *SQLStore) InsertBlock(block *model.Block, userID string) error {
}
func (s *SQLStore) InsertBlocks(blocks []model.Block, userID string) error {
func (s *SQLStore) InsertBlocks(blocks []*model.Block, userID string) error {
if s.dbType == model.SqliteDBType {
return s.insertBlocks(s.db, blocks, userID)
}

View File

@ -14,19 +14,19 @@ 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)
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
DeleteBlock(blockID string, modifiedBy string) error
// @withTransaction
InsertBlocks(blocks []model.Block, userID string) error
InsertBlocks(blocks []*model.Block, userID string) error
// @withTransaction
UndeleteBlock(blockID string, modifiedBy string) error
// @withTransaction
@ -36,15 +36,15 @@ type Store interface {
GetBlock(blockID string) (*model.Block, error)
// @withTransaction
PatchBlock(blockID string, blockPatch *model.BlockPatch, userID string) error
GetBlockHistory(blockID string, opts model.QueryBlockHistoryOptions) ([]model.Block, error)
GetBlockHistoryDescendants(boardID string, opts model.QueryBlockHistoryOptions) ([]model.Block, error)
GetBlockHistory(blockID string, opts model.QueryBlockHistoryOptions) ([]*model.Block, error)
GetBlockHistoryDescendants(boardID string, opts model.QueryBlockHistoryOptions) ([]*model.Block, error)
GetBoardHistory(boardID string, opts model.QueryBoardHistoryOptions) ([]*model.Board, error)
GetBoardAndCardByID(blockID string) (board *model.Board, card *model.Block, err error)
GetBoardAndCard(block *model.Block) (board *model.Board, card *model.Block, err error)
// @withTransaction
DuplicateBoard(boardID string, userID string, toTeam string, asTemplate bool) (*model.BoardsAndBlocks, []*model.BoardMember, error)
// @withTransaction
DuplicateBlock(boardID string, blockID string, userID string, asTemplate bool) ([]model.Block, error)
DuplicateBlock(boardID string, blockID string, userID string, asTemplate bool) ([]*model.Block, error)
// @withTransaction
PatchBlocks(blockPatches *model.BlockPatchBatch, userID string) error

View File

@ -85,13 +85,13 @@ func testInsertBlock(t *testing.T, store store.Store) {
initialCount := len(blocks)
t.Run("valid block", func(t *testing.T) {
block := model.Block{
block := &model.Block{
ID: "id-test",
BoardID: boardID,
ModifiedBy: userID,
}
err := store.InsertBlock(&block, "user-id-1")
err := store.InsertBlock(block, "user-id-1")
require.NoError(t, err)
blocks, err := store.GetBlocksForBoard(boardID)
@ -100,13 +100,13 @@ func testInsertBlock(t *testing.T, store store.Store) {
})
t.Run("invalid rootid", func(t *testing.T) {
block := model.Block{
block := &model.Block{
ID: "id-test",
BoardID: "",
ModifiedBy: userID,
}
err := store.InsertBlock(&block, "user-id-1")
err := store.InsertBlock(block, "user-id-1")
require.Error(t, err)
blocks, err := store.GetBlocksForBoard(boardID)
@ -115,14 +115,14 @@ func testInsertBlock(t *testing.T, store store.Store) {
})
t.Run("invalid fields data", func(t *testing.T) {
block := model.Block{
block := &model.Block{
ID: "id-test",
BoardID: "id-test",
ModifiedBy: userID,
Fields: map[string]interface{}{"no-serialiable-value": t.Run},
}
err := store.InsertBlock(&block, "user-id-1")
err := store.InsertBlock(block, "user-id-1")
require.Error(t, err)
blocks, err := store.GetBlocksForBoard(boardID)
@ -131,24 +131,24 @@ func testInsertBlock(t *testing.T, store store.Store) {
})
t.Run("insert new block", func(t *testing.T) {
block := model.Block{
block := &model.Block{
BoardID: testBoardID,
}
err := store.InsertBlock(&block, "user-id-2")
err := store.InsertBlock(block, "user-id-2")
require.NoError(t, err)
require.Equal(t, "user-id-2", block.CreatedBy)
})
t.Run("update existing block", func(t *testing.T) {
block := model.Block{
block := &model.Block{
ID: "id-2",
BoardID: "board-id-1",
Title: "Old Title",
}
// inserting
err := store.InsertBlock(&block, "user-id-2")
err := store.InsertBlock(block, "user-id-2")
require.NoError(t, err)
// created by populated from user id for new blocks
@ -159,13 +159,13 @@ func testInsertBlock(t *testing.T, store store.Store) {
time.Sleep(1 * time.Millisecond)
// updating
newBlock := model.Block{
newBlock := &model.Block{
ID: "id-2",
BoardID: "board-id-1",
CreatedBy: "user-id-3",
Title: "New Title",
}
err = store.InsertBlock(&newBlock, "user-id-4")
err = store.InsertBlock(newBlock, "user-id-4")
require.NoError(t, err)
// created by is not altered for existing blocks
require.Equal(t, "user-id-3", newBlock.CreatedBy)
@ -179,7 +179,7 @@ func testInsertBlock(t *testing.T, store store.Store) {
assert.NoError(t, err)
t.Run("data tamper attempt", func(t *testing.T) {
block := model.Block{
block := &model.Block{
ID: "id-10",
BoardID: "board-id-1",
Title: "Old Title",
@ -190,7 +190,7 @@ func testInsertBlock(t *testing.T, store store.Store) {
}
// inserting
err := store.InsertBlock(&block, "user-id-1")
err := store.InsertBlock(block, "user-id-1")
require.NoError(t, err)
expectedTime := time.Now()
@ -213,19 +213,19 @@ func testInsertBlocks(t *testing.T, store store.Store) {
initialCount := len(blocks)
t.Run("invalid block", func(t *testing.T) {
validBlock := model.Block{
validBlock := &model.Block{
ID: "id-test",
BoardID: "id-test",
ModifiedBy: userID,
}
invalidBlock := model.Block{
invalidBlock := &model.Block{
ID: "id-test",
BoardID: "",
ModifiedBy: userID,
}
newBlocks := []model.Block{validBlock, invalidBlock}
newBlocks := []*model.Block{validBlock, invalidBlock}
time.Sleep(1 * time.Millisecond)
err := store.InsertBlocks(newBlocks, "user-id-1")
@ -242,7 +242,7 @@ func testPatchBlock(t *testing.T, store store.Store) {
userID := testUserID
boardID := "board-id-1"
block := model.Block{
block := &model.Block{
ID: "id-test",
BoardID: boardID,
Title: "oldTitle",
@ -250,7 +250,7 @@ func testPatchBlock(t *testing.T, store store.Store) {
Fields: map[string]interface{}{"test": "test value", "test2": "test value 2"},
}
err := store.InsertBlock(&block, "user-id-1")
err := store.InsertBlock(block, "user-id-1")
require.NoError(t, err)
blocks, errBlocks := store.GetBlocksForBoard(boardID)
@ -269,11 +269,11 @@ func testPatchBlock(t *testing.T, store store.Store) {
})
t.Run("invalid fields data", func(t *testing.T) {
blockPatch := model.BlockPatch{
blockPatch := &model.BlockPatch{
UpdatedFields: map[string]interface{}{"no-serialiable-value": t.Run},
}
err := store.PatchBlock("id-test", &blockPatch, "user-id-1")
err := store.PatchBlock("id-test", blockPatch, "user-id-1")
require.Error(t, err)
blocks, err := store.GetBlocksForBoard(boardID)
@ -303,7 +303,7 @@ func testPatchBlock(t *testing.T, store store.Store) {
})
t.Run("update block custom fields", func(t *testing.T) {
blockPatch := model.BlockPatch{
blockPatch := &model.BlockPatch{
UpdatedFields: map[string]interface{}{"test": "new test value", "test3": "new value"},
}
@ -311,7 +311,7 @@ func testPatchBlock(t *testing.T, store store.Store) {
time.Sleep(1 * time.Millisecond)
// inserting
err := store.PatchBlock("id-test", &blockPatch, "user-id-2")
err := store.PatchBlock("id-test", blockPatch, "user-id-2")
require.NoError(t, err)
retrievedBlock, err := store.GetBlock("id-test")
@ -325,7 +325,7 @@ func testPatchBlock(t *testing.T, store store.Store) {
})
t.Run("remove block custom fields", func(t *testing.T) {
blockPatch := model.BlockPatch{
blockPatch := &model.BlockPatch{
DeletedFields: []string{"test", "test3", "test100"},
}
@ -333,7 +333,7 @@ func testPatchBlock(t *testing.T, store store.Store) {
time.Sleep(1 * time.Millisecond)
// inserting
err := store.PatchBlock("id-test", &blockPatch, "user-id-2")
err := store.PatchBlock("id-test", blockPatch, "user-id-2")
require.NoError(t, err)
retrievedBlock, err := store.GetBlock("id-test")
@ -348,19 +348,19 @@ func testPatchBlock(t *testing.T, store store.Store) {
}
func testPatchBlocks(t *testing.T, store store.Store) {
block := model.Block{
block := &model.Block{
ID: "id-test",
BoardID: "id-test",
Title: "oldTitle",
}
block2 := model.Block{
block2 := &model.Block{
ID: "id-test2",
BoardID: "id-test2",
Title: "oldTitle2",
}
insertBlocks := []model.Block{block, block2}
insertBlocks := []*model.Block{block, block2}
err := store.InsertBlocks(insertBlocks, "user-id-1")
require.NoError(t, err)
@ -419,7 +419,7 @@ func testPatchBlocks(t *testing.T, store store.Store) {
}
var (
subtreeSampleBlocks = []model.Block{
subtreeSampleBlocks = []*model.Block{
{
ID: "parent",
BoardID: testBoardID,
@ -504,7 +504,7 @@ func testDeleteBlock(t *testing.T, store store.Store) {
require.NoError(t, err)
initialCount := len(blocks)
blocksToInsert := []model.Block{
blocksToInsert := []*model.Block{
{
ID: "block1",
BoardID: boardID,
@ -562,7 +562,7 @@ func testUndeleteBlock(t *testing.T, store store.Store) {
require.NoError(t, err)
initialCount := len(blocks)
blocksToInsert := []model.Block{
blocksToInsert := []*model.Block{
{
ID: "block1",
BoardID: boardID,
@ -654,7 +654,7 @@ func testGetBlocks(t *testing.T, store store.Store) {
blocks, err := store.GetBlocksForBoard(boardID)
require.NoError(t, err)
blocksToInsert := []model.Block{
blocksToInsert := []*model.Block{
{
ID: "block1",
BoardID: boardID,
@ -785,13 +785,13 @@ func testGetBlocks(t *testing.T, store store.Store) {
func testGetBlock(t *testing.T, store store.Store) {
t.Run("get a block", func(t *testing.T) {
block := model.Block{
block := &model.Block{
ID: "block-id-10",
BoardID: "board-id-1",
ModifiedBy: "user-id-1",
}
err := store.InsertBlock(&block, "user-id-1")
err := store.InsertBlock(block, "user-id-1")
require.NoError(t, err)
fetchedBlock, err := store.GetBlock("block-id-10")
@ -816,14 +816,14 @@ func testGetBlock(t *testing.T, store store.Store) {
func testDuplicateBlock(t *testing.T, store store.Store) {
blocksToInsert := subtreeSampleBlocks
blocksToInsert = append(blocksToInsert,
model.Block{
&model.Block{
ID: "grandchild1a",
BoardID: testBoardID,
ParentID: "child1",
ModifiedBy: testUserID,
Type: model.TypeComment,
},
model.Block{
&model.Block{
ID: "grandchild2a",
BoardID: testBoardID,
ParentID: "child2",
@ -874,7 +874,7 @@ func testGetBlockMetadata(t *testing.T, store store.Store) {
blocks, err := store.GetBlocksForBoard(boardID)
require.NoError(t, err)
blocksToInsert := []model.Block{
blocksToInsert := []*model.Block{
{
ID: "block1",
BoardID: boardID,
@ -914,7 +914,7 @@ func testGetBlockMetadata(t *testing.T, store store.Store) {
for _, v := range blocksToInsert {
time.Sleep(20 * time.Millisecond)
subBlocks := []model.Block{v}
subBlocks := []*model.Block{v}
InsertBlocks(t, store, subBlocks, testUserID)
}
defer DeleteBlocks(t, store, blocksToInsert, "test")

View File

@ -31,7 +31,7 @@ func getBoardsInsightsTest(t *testing.T, store store.Store) {
{ID: "board-id-2", TeamID: teamID, Type: model.BoardTypePrivate},
{ID: "board-id-3", TeamID: teamID, Type: model.BoardTypeOpen},
},
Blocks: []model.Block{
Blocks: []*model.Block{
{ID: "block-id-1", BoardID: "board-id-1", Type: model.TypeCard},
{ID: "block-id-2", BoardID: "board-id-2", Type: model.TypeCard},
{ID: "block-id-3", BoardID: "board-id-1", Type: model.TypeCard},
@ -52,7 +52,7 @@ func getBoardsInsightsTest(t *testing.T, store store.Store) {
require.NotNil(t, bab)
newBab = &model.BoardsAndBlocks{
Blocks: []model.Block{
Blocks: []*model.Block{
{ID: "block-id-13", BoardID: "board-id-1", Type: model.TypeCard},
{ID: "block-id-14", BoardID: "board-id-1", Type: model.TypeCard},
},

View File

@ -51,7 +51,7 @@ func testCreateBoardsAndBlocks(t *testing.T, store store.Store) {
{ID: "board-id-2", TeamID: teamID, Type: model.BoardTypePrivate},
{ID: "board-id-3", TeamID: teamID, Type: model.BoardTypeOpen},
},
Blocks: []model.Block{
Blocks: []*model.Block{
{ID: "block-id-1", BoardID: "board-id-1", Type: model.TypeCard},
{ID: "block-id-2", BoardID: "board-id-2", Type: model.TypeCard},
},
@ -84,7 +84,7 @@ func testCreateBoardsAndBlocks(t *testing.T, store store.Store) {
{ID: "board-id-5", TeamID: teamID, Type: model.BoardTypePrivate},
{ID: "board-id-6", TeamID: teamID, Type: model.BoardTypeOpen},
},
Blocks: []model.Block{
Blocks: []*model.Block{
{ID: "block-id-3", BoardID: "board-id-4", Type: model.TypeCard},
{ID: "block-id-4", BoardID: "board-id-5", Type: model.TypeCard},
},
@ -126,7 +126,7 @@ func testCreateBoardsAndBlocks(t *testing.T, store store.Store) {
{ID: "board-id-8", TeamID: teamID, Type: model.BoardTypePrivate},
{ID: "board-id-9", TeamID: teamID, Type: model.BoardTypeOpen},
},
Blocks: []model.Block{
Blocks: []*model.Block{
{ID: "block-id-5", BoardID: "board-id-7", Type: model.TypeCard},
{ID: "block-id-6", BoardID: "", Type: model.TypeCard},
},
@ -164,12 +164,12 @@ func testPatchBoardsAndBlocks(t *testing.T, store store.Store) {
_, err := store.InsertBoard(board, userID)
require.NoError(t, err)
block := model.Block{
block := &model.Block{
ID: "block-id-1",
BoardID: "board-id-1",
Title: initialTitle,
}
require.NoError(t, store.InsertBlock(&block, userID))
require.NoError(t, store.InsertBlock(block, userID))
// apply the patches
pbab := &model.PatchBoardsAndBlocks{
@ -207,7 +207,7 @@ func testPatchBoardsAndBlocks(t *testing.T, store store.Store) {
{ID: "board-id-2", TeamID: teamID, Type: model.BoardTypePrivate},
{ID: "board-id-3", Title: "initial title", TeamID: teamID, Type: model.BoardTypeOpen},
},
Blocks: []model.Block{
Blocks: []*model.Block{
{ID: "block-id-1", Title: "initial title", BoardID: "board-id-1", Type: model.TypeCard},
{ID: "block-id-2", Schema: 1, BoardID: "board-id-2", Type: model.TypeCard},
},
@ -586,7 +586,7 @@ func testDuplicateBoard(t *testing.T, store store.Store) {
{ID: "board-id-2", TeamID: teamID, Type: model.BoardTypePrivate},
{ID: "board-id-3", TeamID: teamID, Type: model.BoardTypeOpen},
},
Blocks: []model.Block{
Blocks: []*model.Block{
{ID: "block-id-1", BoardID: "board-id-1", Type: model.TypeCard},
{ID: "block-id-1a", BoardID: "board-id-1", Type: model.TypeComment},
{ID: "block-id-2", BoardID: "board-id-2", Type: model.TypeCard},

View File

@ -61,24 +61,24 @@ func testGetUsedCardsCount(t *testing.T, store storeservice.Store) {
// board 1 has three cards
for _, cardID := range []string{"card1", "card2", "card3"} {
card := model.Block{
card := &model.Block{
ID: cardID,
ParentID: "board1",
BoardID: "board1",
Type: model.TypeCard,
}
require.NoError(t, store.InsertBlock(&card, userID))
require.NoError(t, store.InsertBlock(card, userID))
}
// board 2 has two cards
for _, cardID := range []string{"card4", "card5"} {
card := model.Block{
card := &model.Block{
ID: cardID,
ParentID: "board2",
BoardID: "board2",
Type: model.TypeCard,
}
require.NoError(t, store.InsertBlock(&card, userID))
require.NoError(t, store.InsertBlock(card, userID))
}
count, err := store.GetUsedCardsCount()
@ -88,21 +88,21 @@ func testGetUsedCardsCount(t *testing.T, store storeservice.Store) {
t.Run("should not take into account content blocks", func(t *testing.T) {
// we add a couple of content blocks
text := model.Block{
text := &model.Block{
ID: "text-id",
ParentID: "card1",
BoardID: "board1",
Type: model.TypeText,
}
require.NoError(t, store.InsertBlock(&text, userID))
require.NoError(t, store.InsertBlock(text, userID))
view := model.Block{
view := &model.Block{
ID: "view-id",
ParentID: "board1",
BoardID: "board1",
Type: model.TypeView,
}
require.NoError(t, store.InsertBlock(&view, userID))
require.NoError(t, store.InsertBlock(view, userID))
// and count should not change
count, err := store.GetUsedCardsCount()
@ -113,7 +113,7 @@ func testGetUsedCardsCount(t *testing.T, store storeservice.Store) {
t.Run("should not take into account cards belonging to templates", func(t *testing.T) {
// we add a template with cards
templateID := "template-id"
boardTemplate := model.Block{
boardTemplate := &model.Block{
ID: templateID,
BoardID: templateID,
Type: model.TypeBoard,
@ -121,16 +121,16 @@ func testGetUsedCardsCount(t *testing.T, store storeservice.Store) {
"isTemplate": true,
},
}
require.NoError(t, store.InsertBlock(&boardTemplate, userID))
require.NoError(t, store.InsertBlock(boardTemplate, userID))
for _, cardID := range []string{"card6", "card7", "card8"} {
card := model.Block{
card := &model.Block{
ID: cardID,
ParentID: templateID,
BoardID: templateID,
Type: model.TypeCard,
}
require.NoError(t, store.InsertBlock(&card, userID))
require.NoError(t, store.InsertBlock(card, userID))
}
// and count should still be the same
@ -141,14 +141,14 @@ func testGetUsedCardsCount(t *testing.T, store storeservice.Store) {
t.Run("should not take into account deleted cards", func(t *testing.T) {
// we create a ninth card on the first board
card9 := model.Block{
card9 := &model.Block{
ID: "card9",
ParentID: "board1",
BoardID: "board1",
Type: model.TypeCard,
DeleteAt: utils.GetMillis(),
}
require.NoError(t, store.InsertBlock(&card9, userID))
require.NoError(t, store.InsertBlock(card9, userID))
// and count should still be the same
count, err := store.GetUsedCardsCount()
@ -215,25 +215,25 @@ func testUpdateCardLimitTimestamp(t *testing.T, store storeservice.Store) {
// board 1 has five cards
for _, cardID := range []string{"card1", "card2", "card3", "card4", "card5"} {
card := model.Block{
card := &model.Block{
ID: cardID,
ParentID: "board1",
BoardID: "board1",
Type: model.TypeCard,
}
require.NoError(t, store.InsertBlock(&card, userID))
require.NoError(t, store.InsertBlock(card, userID))
time.Sleep(10 * time.Millisecond)
}
// board 2 has five cards
for _, cardID := range []string{"card6", "card7", "card8", "card9", "card10"} {
card := model.Block{
card := &model.Block{
ID: cardID,
ParentID: "board2",
BoardID: "board2",
Type: model.TypeCard,
}
require.NoError(t, store.InsertBlock(&card, userID))
require.NoError(t, store.InsertBlock(card, userID))
time.Sleep(10 * time.Millisecond)
}

View File

@ -48,30 +48,30 @@ func LoadData(t *testing.T, store store.Store) {
board, err := store.InsertBoard(&validBoard, testUserID)
require.NoError(t, err)
validBlock := model.Block{
validBlock := &model.Block{
ID: "id-test",
BoardID: board.ID,
ModifiedBy: testUserID,
}
validBlock2 := model.Block{
validBlock2 := &model.Block{
ID: "id-test2",
BoardID: board.ID,
ModifiedBy: testUserID,
}
validBlock3 := model.Block{
validBlock3 := &model.Block{
ID: "id-test3",
BoardID: board.ID,
ModifiedBy: testUserID,
}
validBlock4 := model.Block{
validBlock4 := &model.Block{
ID: "id-test4",
BoardID: board.ID,
ModifiedBy: testUserID,
}
newBlocks := []model.Block{validBlock, validBlock2, validBlock3, validBlock4}
newBlocks := []*model.Block{validBlock, validBlock2, validBlock3, validBlock4}
err = store.InsertBlocks(newBlocks, testUserID)
require.NoError(t, err)

View File

@ -8,21 +8,21 @@ import (
"github.com/stretchr/testify/require"
)
func InsertBlocks(t *testing.T, s store.Store, blocks []model.Block, userID string) {
func InsertBlocks(t *testing.T, s store.Store, blocks []*model.Block, userID string) {
for i := range blocks {
err := s.InsertBlock(&blocks[i], userID)
err := s.InsertBlock(blocks[i], userID)
require.NoError(t, err)
}
}
func DeleteBlocks(t *testing.T, s store.Store, blocks []model.Block, modifiedBy string) {
func DeleteBlocks(t *testing.T, s store.Store, blocks []*model.Block, modifiedBy string) {
for _, block := range blocks {
err := s.DeleteBlock(block.ID, modifiedBy)
require.NoError(t, err)
}
}
func ContainsBlockWithID(blocks []model.Block, blockID string) bool {
func ContainsBlockWithID(blocks []*model.Block, blockID string) bool {
for _, block := range blocks {
if block.ID == blockID {
return true

View File

@ -13,7 +13,7 @@ import (
)
// NotifyUpdate calls webhooks.
func (wh *Client) NotifyUpdate(block model.Block) {
func (wh *Client) NotifyUpdate(block *model.Block) {
if len(wh.config.WebhookUpdate) < 1 {
return
}

View File

@ -31,7 +31,7 @@ func TestClientUpdateNotify(t *testing.T) {
client := NewClient(cfg, logger)
client.NotifyUpdate(model.Block{})
client.NotifyUpdate(&model.Block{})
if !isNotified {
t.Error("webhook url not be notified")

View File

@ -28,7 +28,7 @@ type Store interface {
}
type Adapter interface {
BroadcastBlockChange(teamID string, block model.Block)
BroadcastBlockChange(teamID string, block *model.Block)
BroadcastBlockDelete(teamID, blockID, boardID string)
BroadcastBoardChange(teamID string, board *model.Board)
BroadcastBoardDelete(teamID, boardID string)

View File

@ -14,9 +14,9 @@ type UpdateCategoryMessage struct {
// UpdateBlockMsg is sent on block updates.
type UpdateBlockMsg struct {
Action string `json:"action"`
TeamID string `json:"teamId"`
Block model.Block `json:"block"`
Action string `json:"action"`
TeamID string `json:"teamId"`
Block *model.Block `json:"block"`
}
// UpdateBoardMsg is sent on block updates.

View File

@ -26,7 +26,7 @@ type PluginAdapterInterface interface {
OnWebSocketDisconnect(webConnID, userID string)
WebSocketMessageHasBeenPosted(webConnID, userID string, req *mmModel.WebSocketRequest)
BroadcastConfigChange(clientConfig model.ClientConfig)
BroadcastBlockChange(teamID string, block model.Block)
BroadcastBlockChange(teamID string, block *model.Block)
BroadcastBlockDelete(teamID, blockID, parentID string)
BroadcastSubscriptionChange(teamID string, subscription *model.Subscription)
BroadcastCardLimitTimestampChange(cardLimitTimestamp int64)
@ -453,7 +453,7 @@ func (pa *PluginAdapter) sendBoardMessage(teamID, boardID string, payload map[st
pa.sendBoardMessageSkipCluster(teamID, boardID, payload, ensureUserIDs...)
}
func (pa *PluginAdapter) BroadcastBlockChange(teamID string, block model.Block) {
func (pa *PluginAdapter) BroadcastBlockChange(teamID string, block *model.Block) {
pa.logger.Debug("BroadcastingBlockChange",
mlog.String("teamID", teamID),
mlog.String("boardID", block.BoardID),
@ -527,7 +527,7 @@ func (pa *PluginAdapter) BroadcastCategoryBoardChange(teamID, userID string, boa
func (pa *PluginAdapter) BroadcastBlockDelete(teamID, blockID, boardID string) {
now := utils.GetMillis()
block := model.Block{}
block := &model.Block{}
block.ID = blockID
block.BoardID = boardID
block.UpdateAt = now

View File

@ -511,7 +511,7 @@ func (ws *Server) getListenersForTeamAndBoard(teamID, boardID string, ensureUser
// BroadcastBlockDelete broadcasts delete messages to clients.
func (ws *Server) BroadcastBlockDelete(teamID, blockID, boardID string) {
now := utils.GetMillis()
block := model.Block{}
block := &model.Block{}
block.ID = blockID
block.BoardID = boardID
block.UpdateAt = now
@ -521,7 +521,7 @@ func (ws *Server) BroadcastBlockDelete(teamID, blockID, boardID string) {
}
// BroadcastBlockChange broadcasts update messages to clients.
func (ws *Server) BroadcastBlockChange(teamID string, block model.Block) {
func (ws *Server) BroadcastBlockChange(teamID string, block *model.Block) {
blockIDsToNotify := []string{block.ID, block.ParentID}
message := UpdateBlockMsg{

View File

@ -257,6 +257,8 @@
"SidebarTour.SidebarCategories.Body": "All your boards are now organized under your new sidebar. No more switching between workspaces. One-time custom categories based on your prior workspaces may have automatically been created for you as part of your v7.2 upgrade. These can be removed or edited to your preference.",
"SidebarTour.SidebarCategories.Link": "Learn more",
"SidebarTour.SidebarCategories.Title": "Sidebar categories",
"SiteStats.total_boards": "Total Boards",
"SiteStats.total_cards": "Total Cards",
"TableComponent.add-icon": "Add icon",
"TableComponent.name": "Name",
"TableComponent.plus-new": "+ New",

View File

@ -4,7 +4,7 @@
import React, {useState, useEffect} from 'react'
import {useIntl, FormattedMessage} from 'react-intl'
import {generatePath, useRouteMatch} from 'react-router'
import {generatePath, useRouteMatch} from 'react-router-dom'
import Select from 'react-select/async'
import {CSSObject} from '@emotion/serialize'

View File

@ -65,6 +65,7 @@ type Props = {
const ViewHeader = (props: Props) => {
const [showFilter, setShowFilter] = useState(false)
const [lockFilterOnClose, setLockFilterOnClose] = useState(false)
const intl = useIntl()
const canEditBoardProperties = useHasCurrentBoardPermissions([Permission.ManageBoardProperties])
@ -197,7 +198,9 @@ const ViewHeader = (props: Props) => {
<ModalWrapper>
<Button
active={hasFilter}
onClick={() => setShowFilter(true)}
onClick={() => setShowFilter(!showFilter)}
onMouseOver={() => setLockFilterOnClose(true)}
onMouseLeave={() => setLockFilterOnClose(false)}
>
<FormattedMessage
id='ViewHeader.filter'
@ -208,7 +211,11 @@ const ViewHeader = (props: Props) => {
<FilterComponent
board={board}
activeView={activeView}
onClose={() => setShowFilter(false)}
onClose={() => {
if (!lockFilterOnClose) {
setShowFilter(false)
}
}}
/>}
</ModalWrapper>

View File

@ -17,6 +17,7 @@ import {Constants} from './constants'
import {BoardsCloudLimits} from './boardsCloudLimits'
import {TopBoardResponse} from './insights'
import {BoardSiteStatistics} from './statistics'
//
// OctoClient is the client interface to the server APIs
@ -921,6 +922,18 @@ class OctoClient {
return limits
}
async getSiteStatistics(): Promise<BoardSiteStatistics | undefined> {
const path = '/api/v2/statistics'
const response = await fetch(this.getBaseURL() + path, {headers: this.headers()})
if (response.status !== 200) {
return undefined
}
const stats = (await this.getJson(response, {})) as BoardSiteStatistics
Utils.log(`Site Statistics: cards=${stats.card_count} boards=${stats.board_count}`)
return stats
}
// insights
async getMyTopBoards(timeRange: string, page: number, perPage: number, teamId: string): Promise<TopBoardResponse | undefined> {
const path = `/api/v2/users/me/boards/insights?time_range=${timeRange}&page=${page}&per_page=${perPage}&team_id=${teamId}`

View File

@ -0,0 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
export interface BoardSiteStatistics {
board_count: number
card_count: number
}

View File

@ -7,7 +7,13 @@ import {default as client} from '../octoClient'
import {Board, BoardMember} from '../blocks/board'
import {IUser} from '../user'
import {initialLoad, initialReadOnlyLoad, loadBoardData, loadBoards} from './initialLoad'
import {
initialLoad,
initialReadOnlyLoad,
loadBoardData,
loadBoards,
loadMyBoardsMemberships,
} from './initialLoad'
import {addBoardUsers, removeBoardUsersById, setBoardUsers} from './users'
@ -191,6 +197,12 @@ const boardsSlice = createSlice({
state.boards[board.id] = board
})
})
builder.addCase(loadMyBoardsMemberships.fulfilled, (state, action) => {
state.myBoardMemberships = {}
action.payload.boardsMemberships.forEach((boardMember) => {
state.myBoardMemberships[boardMember.boardId] = boardMember
})
})
builder.addCase(fetchBoardMembers.fulfilled, (state, action) => {
if (action.payload.length === 0) {
return

View File

@ -81,6 +81,16 @@ export const loadBoards = createAsyncThunk(
},
)
export const loadMyBoardsMemberships = createAsyncThunk(
'loadMyBoardsMemberships',
async () => {
const boardsMemberships = await client.getMyBoardMemberships()
return {
boardsMemberships,
}
},
)
export const getUserBlockSubscriptions = (state: RootState): Subscription[] => state.users.blockSubscriptions
export const getUserBlockSubscriptionList = createSelector(

View File

@ -7,6 +7,8 @@ import {Utils} from '../../utils'
type Props = {
onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void
onMouseOver?: (e: React.MouseEvent<HTMLButtonElement>) => void
onMouseLeave?: (e: React.MouseEvent<HTMLButtonElement>) => void
onBlur?: (e: React.FocusEvent<HTMLButtonElement>) => void
children?: React.ReactNode
title?: string
@ -37,6 +39,8 @@ function Button(props: Props): JSX.Element {
<button
type={props.submit ? 'submit' : 'button'}
onClick={props.onClick}
onMouseOver={props.onMouseOver}
onMouseLeave={props.onMouseLeave}
className={Utils.generateClassName(classNames)}
title={props.title}
onBlur={props.onBlur}

View File

@ -1,7 +1,7 @@
.GuestBadge {
display: inline-flex;
align-items: center;
margin: 0 0 0 4px;
margin: 0 10px 0 4px;
}
.GuestBadge__box {