mirror of
https://github.com/mattermost/focalboard.git
synced 2024-12-27 13:48:52 +02:00
42ddbbdd69
* chore: update docs URL for import/export/migrate * chore: reference `.boardarchive` in import scripts & READMEs
223 lines
7.3 KiB
TypeScript
223 lines
7.3 KiB
TypeScript
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
import * as fs from 'fs'
|
|
import minimist from 'minimist'
|
|
import {exit} from 'process'
|
|
import {ArchiveUtils} from '../util/archive'
|
|
import {Block} from '../../webapp/src/blocks/block'
|
|
import {Board as FBBoard} from '../../webapp/src/blocks/board'
|
|
import {IPropertyOption, IPropertyTemplate, createBoard} from '../../webapp/src/blocks/board'
|
|
import {createBoardView} from '../../webapp/src/blocks/boardView'
|
|
import {createCard} from '../../webapp/src/blocks/card'
|
|
import {createTextBlock} from '../../webapp/src/blocks/textBlock'
|
|
import {NextcloudDeckClient, Stack, Board} from './deck'
|
|
import {Utils} from './utils'
|
|
import readline from 'readline-sync'
|
|
import {createCommentBlock} from '../../webapp/src/blocks/commentBlock'
|
|
|
|
|
|
// HACKHACK: To allow Utils.CreateGuid to work
|
|
(global.window as any) = {}
|
|
|
|
const optionColors = [
|
|
// 'propColorDefault',
|
|
'propColorGray',
|
|
'propColorBrown',
|
|
'propColorOrange',
|
|
'propColorYellow',
|
|
'propColorGreen',
|
|
'propColorBlue',
|
|
'propColorPurple',
|
|
'propColorPink',
|
|
'propColorRed',
|
|
]
|
|
|
|
async function main() {
|
|
const args: minimist.ParsedArgs = minimist(process.argv.slice(2))
|
|
|
|
console.log("Transform a nextcloud deck into a mattermost Board.")
|
|
|
|
if (args['h'] || args['help']) {
|
|
showHelp()
|
|
}
|
|
|
|
// Get Options
|
|
const url = args['url'] ?? readline.question('Nextcloud URL: ')
|
|
const username = args['u'] ?? readline.question('Username: ')
|
|
const password = args['p'] ?? readline.question('Password: ', {hideEchoBack: true})
|
|
const boardIdString = args['b']
|
|
|
|
const outputFile = args['o'] || 'archive.boardarchive'
|
|
|
|
// Create Client
|
|
const deckClient = new NextcloudDeckClient({auth: {username, password}, url})
|
|
|
|
// Select board (Either from cli or by interactive selection)
|
|
const boardId = boardIdString ? parseInt(boardIdString) : await selectBoard(deckClient)
|
|
|
|
// Get Data
|
|
const board = await deckClient.getBoardDetails(boardId)
|
|
const stacks = await Promise.all((await deckClient.getStacks(boardId)).map(async s => {
|
|
return {
|
|
...s,
|
|
cards: await Promise.all(s.cards.map(async c => {
|
|
if (c.commentsCount > 0) {
|
|
c.comments = await deckClient.getComments(c.id)
|
|
}
|
|
return c
|
|
}))
|
|
}
|
|
}))
|
|
|
|
// Convert
|
|
const [boards, blocks] = convert(board, stacks)
|
|
|
|
// // Save output
|
|
const outputData = ArchiveUtils.buildBlockArchive(boards, blocks)
|
|
fs.writeFileSync(outputFile, outputData)
|
|
|
|
console.log(`Exported to ${outputFile}`)
|
|
}
|
|
|
|
async function selectBoard(deckClient: NextcloudDeckClient): Promise<number> {
|
|
console.log("\nAvailable boards for this user:")
|
|
const boards = await deckClient.getBoards()
|
|
boards.forEach(b => console.log(`\t${b.id}: ${b.title} (${b.owner.uid})`))
|
|
return readline.questionInt("Enter Board ID: ")
|
|
}
|
|
|
|
function convert(deckBoard: Board, stacks: Stack[]): [FBBoard[], Block[]] {
|
|
const boards: FBBoard[] = []
|
|
const blocks: Block[] = []
|
|
|
|
// Board
|
|
const board = createBoard()
|
|
console.log(`Board: ${deckBoard.title}`)
|
|
board.title = deckBoard.title
|
|
|
|
let colorIndex = 0
|
|
// Convert stacks (columns) to a Select property
|
|
const stackOptionsIdMap = new Map<number, string>()
|
|
const stackOptions: IPropertyOption[] = []
|
|
stacks.forEach(stack => {
|
|
const optionId = Utils.createGuid()
|
|
stackOptionsIdMap.set(stack.id, optionId)
|
|
const color = optionColors[colorIndex % optionColors.length]
|
|
colorIndex += 1
|
|
const option: IPropertyOption = {
|
|
id: optionId,
|
|
value: stack.title,
|
|
color,
|
|
}
|
|
stackOptions.push(option)
|
|
})
|
|
const stackProperty: IPropertyTemplate = {
|
|
id: Utils.createGuid(),
|
|
name: 'List',
|
|
type: 'select',
|
|
options: stackOptions
|
|
}
|
|
|
|
// Convert labels (tags) to a Select property
|
|
const labelOptionsIdMap = new Map<number, string>()
|
|
const labelOptions: IPropertyOption[] = []
|
|
deckBoard.labels.forEach(label => {
|
|
const optionId = Utils.createGuid()
|
|
labelOptionsIdMap.set(label.id, optionId)
|
|
const color = optionColors[colorIndex % optionColors.length]
|
|
colorIndex += 1
|
|
const option: IPropertyOption = {
|
|
id: optionId,
|
|
value: label.title,
|
|
color,
|
|
}
|
|
labelOptions.push(option)
|
|
})
|
|
const labelProperty: IPropertyTemplate = {
|
|
id: Utils.createGuid(),
|
|
name: 'Label',
|
|
type: 'multiSelect',
|
|
options: labelOptions
|
|
}
|
|
const dueDateProperty: IPropertyTemplate = {
|
|
id: Utils.createGuid(),
|
|
name: 'Due Date',
|
|
type: 'date',
|
|
options: []
|
|
}
|
|
|
|
board.cardProperties = [stackProperty, labelProperty, dueDateProperty]
|
|
boards.push(board)
|
|
|
|
// Board view
|
|
const view = createBoardView()
|
|
view.title = 'Board View'
|
|
view.fields.viewType = 'board'
|
|
view.boardId = board.id
|
|
view.parentId = board.id
|
|
blocks.push(view)
|
|
|
|
// Cards
|
|
stacks.forEach(stack =>
|
|
stack.cards.forEach(
|
|
card => {
|
|
console.log(`Card: ${card.title}`)
|
|
|
|
const outCard = createCard()
|
|
outCard.title = card.title
|
|
outCard.boardId = board.id
|
|
outCard.parentId = board.id
|
|
|
|
// Map Stacks to Select property options
|
|
const stackOptionId = stackOptionsIdMap.get(card.stackId)
|
|
if (stackOptionId) {
|
|
outCard.fields.properties[stackProperty.id] = stackOptionId
|
|
} else {
|
|
console.warn(`Invalid idList: ${card.stackId} for card: ${card.title}`)
|
|
}
|
|
// Map Labels to Multiselect options
|
|
outCard.fields.properties[labelProperty.id] = card.labels?.map(label => labelOptionsIdMap.get(label.id)).filter((id): id is string => !!id)
|
|
|
|
// Add duedate
|
|
if (card.duedate) {
|
|
const duedate = new Date(card.duedate)
|
|
outCard.fields.properties[dueDateProperty.id] = `{\"from\":${duedate.getTime()}}`
|
|
}
|
|
|
|
blocks.push(outCard)
|
|
|
|
// Description
|
|
if (card.description) {
|
|
const text = createTextBlock()
|
|
text.title = card.description
|
|
text.boardId = board.id
|
|
text.parentId = outCard.id
|
|
blocks.push(text)
|
|
|
|
outCard.fields.contentOrder = [text.id]
|
|
}
|
|
|
|
// Add Comments (Author cannot be determined since uid's are different)
|
|
card.comments?.forEach(comment => {
|
|
const commentBlock = createCommentBlock()
|
|
commentBlock.title = comment.message
|
|
commentBlock.boardId = board.id
|
|
commentBlock.parentId = outCard.id
|
|
blocks.push(commentBlock)
|
|
})
|
|
|
|
})
|
|
)
|
|
console.log('')
|
|
console.log(`Transformed Board ${deckBoard.title} into ${blocks.length} blocks.`)
|
|
|
|
return [boards, blocks]
|
|
}
|
|
|
|
function showHelp() {
|
|
console.log('import [--url <nextcloud-url>] [-u <username>] [-p <password>] [-o <output-path>]')
|
|
exit(1)
|
|
}
|
|
|
|
main()
|