1
0
mirror of https://github.com/mattermost/focalboard.git synced 2025-01-26 18:48:15 +02:00

Refactor BoardTree: handle deleted boards

This commit is contained in:
Chen-I Lim 2020-12-09 15:35:01 -08:00
parent 50efb8f0c3
commit 0eb5bf33a5
4 changed files with 105 additions and 59 deletions

View File

@ -76,11 +76,11 @@ class OctoUtils {
}
}
static hydrateBlocks(blocks: IBlock[]): MutableBlock[] {
static hydrateBlocks(blocks: readonly IBlock[]): MutableBlock[] {
return blocks.map((block) => this.hydrateBlock(block))
}
static mergeBlocks(blocks: IBlock[], updatedBlocks: IBlock[]): IBlock[] {
static mergeBlocks(blocks: readonly IBlock[], updatedBlocks: readonly IBlock[]): IBlock[] {
const updatedBlockIds = updatedBlocks.map((o) => o.id)
const newBlocks = blocks.filter((o) => !updatedBlockIds.includes(o.id))
const updatedAndNotDeletedBlocks = updatedBlocks.filter((o) => o.deleteAt === 0)
@ -89,7 +89,7 @@ class OctoUtils {
}
// Creates a copy of the blocks with new ids and parentIDs
static duplicateBlockTree(blocks: IBlock[], sourceBlockId: string): [MutableBlock[], MutableBlock, Readonly<Record<string, string>>] {
static duplicateBlockTree(blocks: readonly IBlock[], sourceBlockId: string): [MutableBlock[], MutableBlock, Readonly<Record<string, string>>] {
const idMap: Record<string, string> = {}
const newBlocks = blocks.map((block) => {
const newBlock = this.hydrateBlock(block)

View File

@ -169,20 +169,28 @@ export default class BoardPage extends React.Component<Props, State> {
)
if (boardId) {
const boardTree = new MutableBoardTree(boardId)
await boardTree.sync()
const boardTree = await MutableBoardTree.sync(boardId)
if (boardTree && boardTree.board) {
// Default to first view
boardTree.setActiveView(viewId || boardTree.views[0].id)
boardTree.setActiveView(viewId || boardTree.views[0].id)
// TODO: Handle error (viewId not found)
// TODO: Handle error (viewId not found)
this.setState({
boardTree,
boardId,
viewId: boardTree.activeView!.id,
})
Utils.log(`sync complete: ${boardTree.board.id} (${boardTree.board.title})`)
this.setState({
boardTree,
boardId,
viewId: boardTree.activeView!.id,
})
Utils.log(`sync complete: ${boardTree.board?.id} (${boardTree.board?.title})`)
} else {
// Board may have been deleted
this.setState({
boardTree: undefined,
viewId: '',
})
Utils.log(`sync complete: board ${boardId} not found`)
}
}
}
@ -196,11 +204,21 @@ export default class BoardPage extends React.Component<Props, State> {
newState = {...newState, workspaceTree: newWorkspaceTree}
}
if (boardTree || this.state.boardId) {
const newBoardTree = boardTree ? boardTree.mutableCopy() : new MutableBoardTree(this.state.boardId)
if (newBoardTree.incrementalUpdate(blocks)) {
if (boardTree) {
const newBoardTree = MutableBoardTree.incrementalUpdate(boardTree, blocks)
if (newBoardTree) {
newBoardTree.setActiveView(this.state.viewId)
newState = {...newState, boardTree: newBoardTree}
} else {
newState = {...newState, boardTree: undefined}
}
} else if (this.state.boardId) {
const newBoardTree = MutableBoardTree.buildTree(this.state.boardId, blocks)
if (newBoardTree) {
newBoardTree.setActiveView(this.state.viewId)
newState = {...newState, boardTree: newBoardTree}
} else {
newState = {...newState, boardTree: undefined}
}
}

View File

@ -30,9 +30,11 @@ test('BoardTree', async () => {
// Sync
FetchMock.fn.mockReturnValueOnce(FetchMock.jsonResponse(JSON.stringify([board, view, view2, card, cardTemplate])))
const boardTree = new MutableBoardTree(board.id)
await boardTree.sync()
let boardTree = await MutableBoardTree.sync(board.id)
expect(boardTree).not.toBeUndefined()
if (!boardTree) {
fail('sync')
}
expect(FetchMock.fn).toBeCalledTimes(1)
expect(boardTree.board).toEqual(board)
expect(boardTree.views).toEqual([view, view2])
@ -60,7 +62,11 @@ test('BoardTree', async () => {
const cardTemplate2 = TestBlockFactory.createCard(board)
cardTemplate2.isTemplate = true
expect(boardTree.incrementalUpdate([view3, card2, cardTemplate2])).toBe(true)
boardTree = MutableBoardTree.incrementalUpdate(boardTree, [view3, card2, cardTemplate2])
expect(boardTree).not.toBeUndefined()
if (!boardTree) {
fail('incrementalUpdate')
}
expect(boardTree.views).toEqual([view, view2, view3])
expect(boardTree.allCards).toEqual([card, card2])
expect(boardTree.cardTemplates).toEqual([cardTemplate, cardTemplate2])
@ -78,7 +84,11 @@ test('BoardTree', async () => {
// Incremental update: No change
const anotherBoard = TestBlockFactory.createBoard()
const card4 = TestBlockFactory.createCard(anotherBoard)
expect(boardTree.incrementalUpdate([anotherBoard, card4])).toBe(false)
boardTree = MutableBoardTree.incrementalUpdate(boardTree, [anotherBoard, card4])
expect(boardTree).not.toBeUndefined()
if (!boardTree) {
fail('incrementalUpdate')
}
// Copy
const boardTree2 = boardTree.mutableCopy()
@ -101,15 +111,20 @@ test('BoardTree: defaults', async () => {
// Sync
FetchMock.fn.mockReturnValueOnce(FetchMock.jsonResponse(JSON.stringify([board])))
const boardTree = new MutableBoardTree(board.id)
await boardTree.sync()
const boardTree = await MutableBoardTree.sync(board.id)
expect(boardTree).not.toBeUndefined()
if (!boardTree) {
fail('sync')
}
expect(FetchMock.fn).toBeCalledTimes(1)
expect(boardTree.board).not.toBeUndefined()
expect(boardTree.activeView).not.toBeUndefined()
expect(boardTree.views.length).toEqual(1)
expect(boardTree.allCards).toEqual([])
expect(boardTree.cardTemplates).toEqual([])
// Match everything except for cardProperties
board.cardProperties = boardTree.board.cardProperties
board.cardProperties = boardTree.board!.cardProperties
expect(boardTree.board).toEqual(board)
})

View File

@ -1,6 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {IBlock, IMutableBlock} from '../blocks/block'
import {IBlock} from '../blocks/block'
import {Board, IPropertyOption, IPropertyTemplate, MutableBoard} from '../blocks/board'
import {BoardView, MutableBoardView} from '../blocks/boardView'
import {Card, MutableCard} from '../blocks/card'
@ -35,59 +35,70 @@ interface BoardTree {
}
class MutableBoardTree implements BoardTree {
board!: MutableBoard
board: MutableBoard
views: MutableBoardView[] = []
cards: MutableCard[] = []
cardTemplates: MutableCard[] = []
visibleGroups: Group[] = []
hiddenGroups: Group[] = []
activeView!: MutableBoardView
groupByProperty?: IPropertyTemplate
private rawBlocks: IBlock[] = []
private searchText?: string
allCards: MutableCard[] = []
get allBlocks(): IBlock[] {
return [this.board, ...this.views, ...this.allCards, ...this.cardTemplates]
}
constructor(private boardId: string) {
}
async sync(): Promise<void> {
this.rawBlocks = await octoClient.getSubtree(this.boardId)
this.rebuild(OctoUtils.hydrateBlocks(this.rawBlocks))
}
incrementalUpdate(updatedBlocks: IBlock[]): boolean {
const relevantBlocks = updatedBlocks.filter((block) => block.deleteAt !== 0 || block.id === this.boardId || block.parentId === this.boardId)
if (relevantBlocks.length < 1) {
return false
const blocks: IBlock[] = [...this.views, ...this.allCards, ...this.cardTemplates]
if (this.board) {
blocks.unshift(this.board)
}
this.rawBlocks = OctoUtils.mergeBlocks(this.rawBlocks, relevantBlocks)
this.rebuild(OctoUtils.hydrateBlocks(this.rawBlocks))
return true
return blocks
}
private rebuild(blocks: IMutableBlock[]) {
this.board = blocks.find((block) => block.type === 'board') as MutableBoard
this.views = blocks.filter((block) => block.type === 'view').
sort((a, b) => a.title.localeCompare(b.title)) as MutableBoardView[]
this.allCards = blocks.filter((block) => block.type === 'card' && !(block as Card).isTemplate) as MutableCard[]
this.cardTemplates = blocks.filter((block) => block.type === 'card' && (block as Card).isTemplate).
sort((a, b) => a.title.localeCompare(b.title)) as MutableCard[]
this.cards = []
constructor(board: MutableBoard) {
this.board = board
}
this.ensureMinimumSchema()
// Factory methods
static async sync(boardId: string): Promise<MutableBoardTree | undefined> {
const rawBlocks = await octoClient.getSubtree(boardId)
return this.buildTree(boardId, rawBlocks)
}
static incrementalUpdate(boardTree: BoardTree, updatedBlocks: IBlock[]): MutableBoardTree | undefined {
const relevantBlocks = updatedBlocks.filter((block) => block.deleteAt !== 0 || block.id === boardTree.board.id || block.parentId === boardTree.board.id)
if (relevantBlocks.length < 1) {
// No change
return boardTree.mutableCopy()
}
const rawBlocks = OctoUtils.mergeBlocks(boardTree.allBlocks, relevantBlocks)
return this.buildTree(boardTree.board.id, rawBlocks)
}
static buildTree(boardId: string, sourceBlocks: readonly IBlock[]): MutableBoardTree | undefined {
const blocks = OctoUtils.hydrateBlocks(sourceBlocks)
const board = blocks.find((block) => block.type === 'board' && block.id === boardId) as MutableBoard
if (!board) {
return undefined
}
const boardTree = new MutableBoardTree(board)
boardTree.views = blocks.filter((block) => block.type === 'view').
sort((a, b) => a.title.localeCompare(b.title)) as MutableBoardView[]
boardTree.allCards = blocks.filter((block) => block.type === 'card' && !(block as Card).isTemplate) as MutableCard[]
boardTree.cardTemplates = blocks.filter((block) => block.type === 'card' && (block as Card).isTemplate).
sort((a, b) => a.title.localeCompare(b.title)) as MutableCard[]
boardTree.cards = []
boardTree.ensureMinimumSchema()
return boardTree
}
private ensureMinimumSchema(): boolean {
let didChange = false
// At least one select property
const selectProperties = this.board?.cardProperties.find((o) => o.type === 'select')
const selectProperties = this.board.cardProperties.find((o) => o.type === 'select')
if (!selectProperties) {
const newBoard = new MutableBoard(this.board)
newBoard.rootId = newBoard.id
@ -146,6 +157,10 @@ class MutableBoardTree implements BoardTree {
}
private applyFilterSortAndGroup(): void {
if (!this.activeView) {
Utils.assertFailure('activeView')
return
}
Utils.assert(this.allCards !== undefined)
this.cards = this.filterCards(this.allCards) as MutableCard[]
@ -401,9 +416,7 @@ class MutableBoardTree implements BoardTree {
}
mutableCopy(): MutableBoardTree {
const boardTree = new MutableBoardTree(this.boardId)
boardTree.incrementalUpdate(this.rawBlocks)
return boardTree
return MutableBoardTree.buildTree(this.board.id, this.allBlocks)!
}
}