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:
parent
50efb8f0c3
commit
0eb5bf33a5
@ -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)
|
||||
|
@ -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}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
})
|
||||
|
@ -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)!
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user