Skip to content

Commit

Permalink
Merge pull request #1 from Kuuchuu/main
Browse files Browse the repository at this point in the history
14 PR Merges
  • Loading branch information
Kuuchuu committed May 9, 2024
2 parents e3471c4 + 798a5fe commit 07f3aab
Show file tree
Hide file tree
Showing 48 changed files with 3,887 additions and 312 deletions.
1 change: 1 addition & 0 deletions import/csv/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
test
16 changes: 16 additions & 0 deletions import/csv/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# CSV importer

This node app converts a CSV into a Focalboard archive. To use:
1. Run `npm install` from within `focalboard/webapp`
2. Run `npm install` from within `focalboard/import/csv`
3. Run `npx ts-node importCsv.ts -i <path to csv> -o archive.boardarchive`
- If the csv was exported by testrails, pass `-t true` into the command line arguments
4. In Focalboard, click `Settings`, then `Import archive` and select `archive.boardarchive`

## Import scope

Currently, the script imports all cards from a single board, including their properties and markdown content.

The script currently imports all card properties as a Select type. You can change the type after importing into Focalboard.

[Contribute code](https://mattermost.github.io/focalboard/) to expand this.
171 changes: 171 additions & 0 deletions import/csv/importCsv.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import csv from 'csvtojson'
import * as fs from 'fs'
import minimist from 'minimist'
import path from 'path'
import {exit} from 'process'
import {ArchiveUtils} from '../util/archive'
import {Block} from '../../webapp/src/blocks/block'
import {Board} from '../../webapp/src/blocks/board'
import {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 {Utils} from './utils'

(global.window as any) = {}

const optionColors = [
'propColorGray',
'propColorBrown',
'propColorOrange',
'propColorYellow',
'propColorGreen',
'propColorBlue',
'propColorPurple',
'propColorPink',
'propColorRed',
]
let optionColorIndex = 0

async function main() {
const args: minimist.ParsedArgs = minimist(process.argv.slice(2))

const inputFile = args['i']
const outputFile = args['o'] || 'test/archive.boardarchive'
const testrailFormat = (args['t'] === 'true') || false

if (!inputFile) {
showHelp()
}

if (!fs.existsSync(inputFile)){
console.log(`File not found: ${inputFile}`)
exit(2)
}

console.log(`InputFile: ${inputFile}`)
const input = await csv().fromFile(inputFile)
console.log(`Read ${input.length} rows.`)
console.log(input)

const title = path.basename(inputFile, '.csv')
console.log(`Title: ${title}`)

const [boards, blocks] = convert(input, title, testrailFormat)
const outputData = ArchiveUtils.buildBlockArchive(boards, blocks)

fs.writeFileSync(outputFile, outputData)
console.log(`Exported to ${outputFile}`)
}

function convert(input: any[], title: string, testrailFormat: boolean): [Board[], Block[]] {
const boards: Board[] = []
const blocks: Block[] = []

// Board
const board = createBoard()
console.log(`Board: ${title}`)
board.title = title

// Each column is a card property
const columns = getColumns(input)
columns.forEach(column => {
if(column === "Steps" && testrailFormat) {
return
} else {
const cardProperty: IPropertyTemplate = {
id: Utils.createGuid(),
name: column,
type: 'select',
options: []
}
board.cardProperties.push(cardProperty)
}
})

// Set all column types to select
// TODO: Detect column type
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
input.forEach(row => {
const keys = Object.keys(row)
console.log(keys)
if (keys.length < 1) {
console.error(`Expected at least one column`)
return blocks
}
const titleKey = keys[0]
const title = row[titleKey]

console.log(`Card: ${title}`)

const outCard = createCard()
outCard.title = title
outCard.boardId = board.id
outCard.parentId = board.id

// Card properties, skip first key which is the title
for (const key of keys.slice(1)) {
const value = row[key]
if(key === "Steps" && testrailFormat) {
const block = createTextBlock()
block.title = value
block.boardId = board.id
block.parentId = outCard.id
blocks.push(block)

outCard.fields.contentOrder = [block.id]
continue
}
if (!value) {
// Skip empty values
continue
}

const cardProperty = board.cardProperties.find((o) => o.name === key)!
let option = cardProperty.options.find((o) => o.value === value)
if (!option) {
const color = optionColors[optionColorIndex % optionColors.length]
optionColorIndex += 1
option = {
id: Utils.createGuid(),
value,
color: color,
}
cardProperty.options.push(option)
}

outCard.fields.properties[cardProperty.id] = option.id
}

blocks.push(outCard)
})

console.log('')
console.log(`Found ${input.length} card(s).`)

return [boards, blocks]
}

function getColumns(input: any[]) {
const row = input[0]
const keys = Object.keys(row)
// The first key (column) is the card title
return keys.slice(1)
}

function showHelp() {
console.log('import -i <input.csv> -o [output.boardarchive]')
exit(1)
}

main()
Loading

0 comments on commit 07f3aab

Please sign in to comment.