2021-02-18 01:55:59 +02:00
|
|
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
|
|
// See LICENSE.txt for license information.
|
2021-02-17 22:03:45 +02:00
|
|
|
import * as fs from 'fs'
|
|
|
|
import minimist from 'minimist'
|
|
|
|
import {exit} from 'process'
|
2021-03-02 23:21:55 +02:00
|
|
|
import {ArchiveUtils} from '../../webapp/src/blocks/archive'
|
2021-02-17 22:03:45 +02:00
|
|
|
import {IBlock} from '../../webapp/src/blocks/block'
|
|
|
|
import {IPropertyOption, IPropertyTemplate, MutableBoard} from '../../webapp/src/blocks/board'
|
|
|
|
import {MutableBoardView} from '../../webapp/src/blocks/boardView'
|
|
|
|
import {MutableCard} from '../../webapp/src/blocks/card'
|
|
|
|
import {MutableTextBlock} from '../../webapp/src/blocks/textBlock'
|
|
|
|
import {Asana, Workspace} from './asana'
|
|
|
|
import {Utils} from './utils'
|
|
|
|
|
|
|
|
// HACKHACK: To allow Utils.CreateGuid to work
|
|
|
|
(global.window as any) = {}
|
|
|
|
|
2021-02-22 20:54:41 +02:00
|
|
|
const optionColors = [
|
|
|
|
// 'propColorDefault',
|
|
|
|
'propColorGray',
|
|
|
|
'propColorBrown',
|
|
|
|
'propColorOrange',
|
|
|
|
'propColorYellow',
|
|
|
|
'propColorGreen',
|
|
|
|
'propColorBlue',
|
|
|
|
'propColorPurple',
|
|
|
|
'propColorPink',
|
|
|
|
'propColorRed',
|
|
|
|
]
|
|
|
|
let optionColorIndex = 0
|
|
|
|
|
2021-02-17 22:03:45 +02:00
|
|
|
function main() {
|
|
|
|
const args: minimist.ParsedArgs = minimist(process.argv.slice(2))
|
|
|
|
|
|
|
|
const inputFile = args['i']
|
|
|
|
const outputFile = args['o'] || 'archive.focalboard'
|
|
|
|
|
|
|
|
if (!inputFile) {
|
|
|
|
showHelp()
|
|
|
|
}
|
|
|
|
|
2021-02-18 01:55:59 +02:00
|
|
|
if (!fs.existsSync(inputFile)) {
|
|
|
|
console.error(`File not found: ${inputFile}`)
|
|
|
|
exit(2)
|
|
|
|
}
|
|
|
|
|
2021-02-17 22:03:45 +02:00
|
|
|
// Read input
|
|
|
|
const inputData = fs.readFileSync(inputFile, 'utf-8')
|
|
|
|
const input = JSON.parse(inputData) as Asana
|
|
|
|
|
|
|
|
// Convert
|
2021-03-02 23:21:55 +02:00
|
|
|
const blocks = convert(input)
|
2021-02-17 22:03:45 +02:00
|
|
|
|
|
|
|
// Save output
|
2021-03-02 23:21:55 +02:00
|
|
|
// TODO: Stream output
|
|
|
|
const outputData = ArchiveUtils.buildBlockArchive(blocks)
|
2021-02-17 22:03:45 +02:00
|
|
|
fs.writeFileSync(outputFile, outputData)
|
|
|
|
|
|
|
|
console.log(`Exported to ${outputFile}`)
|
|
|
|
}
|
|
|
|
|
|
|
|
function getProjects(input: Asana): Workspace[] {
|
2021-02-18 01:55:59 +02:00
|
|
|
const projectMap = new Map<string, Workspace>()
|
2021-02-17 22:03:45 +02:00
|
|
|
|
2021-02-18 01:55:59 +02:00
|
|
|
input.data.forEach(datum => {
|
|
|
|
datum.projects.forEach(project => {
|
|
|
|
if (!projectMap.get(project.gid)) {
|
|
|
|
projectMap.set(project.gid, project)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
2021-02-17 22:03:45 +02:00
|
|
|
|
2021-02-18 01:55:59 +02:00
|
|
|
return [...projectMap.values()]
|
2021-02-17 22:03:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function getSections(input: Asana, projectId: string): Workspace[] {
|
2021-02-18 01:55:59 +02:00
|
|
|
const sectionMap = new Map<string, Workspace>()
|
|
|
|
|
|
|
|
input.data.forEach(datum => {
|
|
|
|
const membership = datum.memberships.find(o => o.project.gid === projectId)
|
|
|
|
if (membership) {
|
|
|
|
if (!sectionMap.get(membership.section.gid)) {
|
|
|
|
sectionMap.set(membership.section.gid, membership.section)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
return [...sectionMap.values()]
|
2021-02-17 22:03:45 +02:00
|
|
|
}
|
|
|
|
|
2021-03-02 23:21:55 +02:00
|
|
|
function convert(input: Asana): IBlock[] {
|
2021-02-18 01:55:59 +02:00
|
|
|
const projects = getProjects(input)
|
|
|
|
if (projects.length < 1) {
|
|
|
|
console.error('No projects found')
|
2021-03-02 23:21:55 +02:00
|
|
|
return []
|
2021-02-18 01:55:59 +02:00
|
|
|
}
|
2021-02-17 22:03:45 +02:00
|
|
|
|
2021-02-18 01:55:59 +02:00
|
|
|
// TODO: Handle multiple projects
|
|
|
|
const project = projects[0]
|
2021-02-17 22:03:45 +02:00
|
|
|
|
|
|
|
const blocks: IBlock[] = []
|
|
|
|
|
|
|
|
// Board
|
|
|
|
const board = new MutableBoard()
|
|
|
|
console.log(`Board: ${project.name}`)
|
|
|
|
board.rootId = board.id
|
|
|
|
board.title = project.name
|
|
|
|
|
|
|
|
// Convert sections (columns) to a Select property
|
|
|
|
const optionIdMap = new Map<string, string>()
|
|
|
|
const options: IPropertyOption[] = []
|
2021-02-18 01:55:59 +02:00
|
|
|
const sections = getSections(input, project.gid)
|
2021-02-17 22:03:45 +02:00
|
|
|
sections.forEach(section => {
|
|
|
|
const optionId = Utils.createGuid()
|
|
|
|
optionIdMap.set(section.gid, optionId)
|
2021-02-22 20:54:41 +02:00
|
|
|
const color = optionColors[optionColorIndex % optionColors.length]
|
|
|
|
optionColorIndex += 1
|
2021-02-17 22:03:45 +02:00
|
|
|
const option: IPropertyOption = {
|
|
|
|
id: optionId,
|
|
|
|
value: section.name,
|
2021-02-22 20:54:41 +02:00
|
|
|
color,
|
2021-02-17 22:03:45 +02:00
|
|
|
}
|
|
|
|
options.push(option)
|
|
|
|
})
|
|
|
|
|
|
|
|
const cardProperty: IPropertyTemplate = {
|
|
|
|
id: Utils.createGuid(),
|
|
|
|
name: 'Section',
|
|
|
|
type: 'select',
|
|
|
|
options
|
|
|
|
}
|
|
|
|
board.cardProperties = [cardProperty]
|
|
|
|
blocks.push(board)
|
|
|
|
|
|
|
|
// Board view
|
|
|
|
const view = new MutableBoardView()
|
|
|
|
view.title = 'Board View'
|
|
|
|
view.viewType = 'board'
|
|
|
|
view.rootId = board.id
|
|
|
|
view.parentId = board.id
|
|
|
|
blocks.push(view)
|
|
|
|
|
|
|
|
// Cards
|
|
|
|
input.data.forEach(card => {
|
|
|
|
console.log(`Card: ${card.name}`)
|
|
|
|
|
|
|
|
const outCard = new MutableCard()
|
|
|
|
outCard.title = card.name
|
|
|
|
outCard.rootId = board.id
|
|
|
|
outCard.parentId = board.id
|
|
|
|
|
|
|
|
// Map lists to Select property options
|
2021-02-18 01:55:59 +02:00
|
|
|
const membership = card.memberships.find(o => o.project.gid === project.gid)
|
2021-02-17 22:03:45 +02:00
|
|
|
if (membership) {
|
|
|
|
const optionId = optionIdMap.get(membership.section.gid)
|
|
|
|
if (optionId) {
|
|
|
|
outCard.properties[cardProperty.id] = optionId
|
|
|
|
} else {
|
|
|
|
console.warn(`Invalid idList: ${membership.section.gid} for card: ${card.name}`)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
console.warn(`Missing idList for card: ${card.name}`)
|
|
|
|
}
|
|
|
|
|
|
|
|
blocks.push(outCard)
|
|
|
|
|
|
|
|
if (card.notes) {
|
|
|
|
// console.log(`\t${card.notes}`)
|
|
|
|
const text = new MutableTextBlock()
|
|
|
|
text.title = card.notes
|
|
|
|
text.rootId = board.id
|
|
|
|
text.parentId = outCard.id
|
|
|
|
blocks.push(text)
|
|
|
|
|
|
|
|
outCard.contentOrder = [text.id]
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
console.log('')
|
|
|
|
console.log(`Found ${input.data.length} card(s).`)
|
|
|
|
|
2021-03-02 23:21:55 +02:00
|
|
|
return blocks
|
2021-02-17 22:03:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function showHelp() {
|
|
|
|
console.log('import -i <input.json> -o [output.focalboard]')
|
2021-02-18 01:55:59 +02:00
|
|
|
exit(1)
|
2021-02-17 22:03:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
main()
|