1
0
mirror of https://github.com/mattermost/focalboard.git synced 2024-11-24 08:22:29 +02:00

Import scripts

This commit is contained in:
Chen-I Lim 2021-02-16 11:34:58 -08:00
parent 76f859c4ef
commit 0f7dc4ad8d
10 changed files with 2143 additions and 0 deletions

4
import/README.md Normal file
View File

@ -0,0 +1,4 @@
# Import scripts
This subfolder contains scripts to import data from other systems. It is at an early stage. At present, there is an example of importing from Trello. [Contribute code](https://www.focalboard.com/contribute/getting-started/) to expand this.

4
import/asana/README.md Normal file
View File

@ -0,0 +1,4 @@
# Asana importer
Placeholder for now. [Contribute code](https://www.focalboard.com/contribute/getting-started/) to expand this.

4
import/notion/README.md Normal file
View File

@ -0,0 +1,4 @@
# Notion importer
Placeholder for now. [Contribute code](https://www.focalboard.com/contribute/getting-started/) to expand this.

14
import/trello/README.md Normal file
View File

@ -0,0 +1,14 @@
# Trello importer
This node app converts a Trello json archive into a Focalboard archive. To use:
1. From the Trello Board Menu, select `More`, then `Print and Export`, and `Export to JSON`
2. Save it locally, e.g. to `trello.json`
3. Run `npm install`
4. Run `npx ts-node importTrello.ts -i <trello.json> -o archive.focalboard`
5. In Focalboard, click `Settings`, then `Import archive` and select `archive.focalboard`
## Import scope
Currently, the script imports all cards from a single board, including their list (column) membership, names, and descriptions.

View File

@ -0,0 +1,133 @@
import * as fs from 'fs'
import minimist from 'minimist'
import {exit} from 'process'
import {IArchive} from '../../webapp/src/blocks/archive'
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 {Trello} from './trello'
import {Utils} from './utils'
// HACKHACK: To allow Utils.CreateGuid to work
(global.window as any) = {}
function main() {
const args: minimist.ParsedArgs = minimist(process.argv.slice(2))
const inputFile = args['i']
const outputFile = args['o'] || 'archive.focalboard'
if (!inputFile) {
showHelp()
}
// Read input
const inputData = fs.readFileSync(inputFile, 'utf-8')
const input = JSON.parse(inputData) as Trello
// Convert
const output = convert(input)
// Save output
const outputData = JSON.stringify(output)
fs.writeFileSync(outputFile, outputData)
console.log(`Exported to ${outputFile}`)
}
function convert(input: Trello): IArchive {
const blocks: IBlock[] = []
// Board
const board = new MutableBoard()
console.log(`Board: ${input.name}`)
board.rootId = board.id
board.title = input.name
board.description = input.desc
// Convert lists (columns) to a Select property
const optionIdMap = new Map<string, string>()
const options: IPropertyOption[] = []
input.lists.forEach(list => {
const optionId = Utils.createGuid()
optionIdMap.set(list.id, optionId)
const option: IPropertyOption = {
id: optionId,
value: list.name,
color: 'propColorDefault',
}
options.push(option)
})
const cardProperty: IPropertyTemplate = {
id: Utils.createGuid(),
name: 'List',
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.cards.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
if (card.idList) {
const optionId = optionIdMap.get(card.idList)
if (optionId) {
outCard.properties[cardProperty.id] = optionId
} else {
console.warn(`Invalid idList: ${card.idList} for card: ${card.name}`)
}
} else {
console.warn(`Missing idList for card: ${card.name}`)
}
blocks.push(outCard)
if (card.desc) {
// console.log(`\t${card.desc}`)
const text = new MutableTextBlock()
text.title = card.desc
text.rootId = board.id
text.parentId = outCard.id
blocks.push(text)
outCard.contentOrder = [text.id]
}
})
const archive: IArchive = {
version: 1,
date: Date.now(),
blocks
}
console.log('')
console.log(`Found ${input.cards.length} card(s).`)
return archive
}
function showHelp() {
console.log('import -i <input.json> -o [output.focalboard]')
exit(-1)
}
main()

1317
import/trello/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,24 @@
{
"name": "focalboard-trello-importer",
"version": "1.0.0",
"private": true,
"description": "",
"main": "importTrello.js",
"scripts": {
"test": "ts-node importTrello.ts -i test/trello.json -o test/archive.focalboard"
},
"keywords": [],
"author": "",
"devDependencies": {
"@types/minimist": "^1.2.1",
"@types/node": "^14.14.28",
"@typescript-eslint/eslint-plugin": "^4.15.0",
"@typescript-eslint/parser": "^4.15.0",
"eslint": "^7.20.0",
"ts-node": "^9.1.1",
"typescript": "^4.1.5"
},
"dependencies": {
"minimist": "^1.2.5"
}
}

599
import/trello/trello.ts Normal file
View File

@ -0,0 +1,599 @@
// Generated by https://quicktype.io
//
// To change quicktype's target language, run command:
//
// "Set quicktype target language"
export interface Trello {
id: IDBoardEnum;
name: BoardName;
desc: string;
descData: null;
closed: boolean;
idOrganization: null;
shortLink: ShortLink;
powerUps: any[];
dateLastActivity: string;
idTags: any[];
datePluginDisable: null;
creationMethod: null;
idBoardSource: string;
idMemberCreator: null;
idEnterprise: null;
pinned: boolean;
starred: boolean;
url: string;
shortUrl: string;
enterpriseOwned: boolean;
premiumFeatures: any[];
ixUpdate: string;
limits: TrelloLimits;
prefs: Prefs;
subscribed: boolean;
templateGallery: null;
dateLastView: string;
labelNames: LabelNames;
actions: Action[];
cards: CardElement[];
labels: Label[];
lists: List[];
members: MemberElement[];
checklists: ChecklistElement[];
customFields: CustomFieldElement[];
memberships: Membership[];
pluginData: PluginDatum[];
}
export interface Action {
id: string;
idMemberCreator: IDMemberCreator;
data: Data;
type: string;
date: string;
appCreator: AppCreator | null;
limits: ActionLimits;
memberCreator: MemberCreatorClass;
member?: MemberCreatorClass;
}
export interface AppCreator {
id: string;
name: string;
icon: Icon;
}
export interface Icon {
url: string;
}
export interface Data {
old?: Old;
customField?: DataCustomField;
customFieldItem?: CustomFieldItem;
board: Board;
card?: DataCard;
list?: ListClass;
listBefore?: ListClass;
listAfter?: ListClass;
idMember?: IDMemberCreator;
member?: ListClass;
fromCopy?: boolean;
cardSource?: Board;
deactivated?: boolean;
text?: string;
checklist?: ListClass;
checkItem?: DataCheckItem;
boardSource?: BoardSource;
}
export interface Board {
id: IDBoardEnum;
name: BoardName;
shortLink: ShortLink;
idShort?: number;
}
export enum IDBoardEnum {
The5F4800F49696D280D52Bb2Ff = "5f4800f49696d280d52bb2ff",
The5F58E6144A949F4C1879A32A = "5f58e6144a949f4c1879a32a",
}
export enum BoardName {
AgileSprintBoard = "Agile Sprint Board",
StandardTask = "Standard Task",
}
export enum ShortLink {
WduAIKhy = "wduAiKhy",
ZCbHMXU8 = "ZCbHMxU8",
}
export interface BoardSource {
id: string;
}
export interface DataCard {
id: string;
name: string;
idShort: number;
shortLink: string;
pos?: number;
idList?: IDMemberCreator;
due?: string;
dueReminder?: number;
cover?: OldCover;
desc?: string;
}
export interface OldCover {
color: null | string;
idAttachment: null;
idUploadedBackground: IDUploadedBackground | null;
size: MemberType;
brightness: Brightness;
url?: string;
}
export enum Brightness {
Dark = "dark",
Light = "light",
}
export enum IDUploadedBackground {
The5F46Cbb00E54E3660C1A7B22 = "5f46cbb00e54e3660c1a7b22",
The5F46Cbe1C839Ef48989Cd124 = "5f46cbe1c839ef48989cd124",
The5F46Cbe8B0A6Bb3B7F91A0B8 = "5f46cbe8b0a6bb3b7f91a0b8",
}
export enum MemberType {
Full = "full",
Normal = "normal",
}
export enum IDMemberCreator {
The5F4800A4621Dfe2935798972 = "5f4800a4621dfe2935798972",
The5F4800F49696D280D52Bb300 = "5f4800f49696d280d52bb300",
The5F4800F49696D280D52Bb301 = "5f4800f49696d280d52bb301",
The5F4800F49696D280D52Bb302 = "5f4800f49696d280d52bb302",
The5F4800F49696D280D52Bb303 = "5f4800f49696d280d52bb303",
The5F4800F49696D280D52Bb304 = "5f4800f49696d280d52bb304",
The5F480131E778365Be477Add3 = "5f480131e778365be477add3",
}
export interface DataCheckItem {
id: string;
name: string;
state: string;
}
export interface ListClass {
id: IDMemberCreator;
name: FullNameEnum;
}
export enum FullNameEnum {
Backlog = "Backlog",
Checklist = "Checklist",
JohnSmith = "John Smith",
InProgress = "In Progress",
SprintBacklog = "Sprint Backlog",
The8217SprintComplete = "8.2.17 Sprint - Complete",
The8917SprintComplete = "8.9.17 Sprint - Complete",
}
export interface DataCustomField {
id: IDCustomFieldEnum;
name: string;
type?: string;
}
export enum IDCustomFieldEnum {
The5F4802F5905B9A640C49Be08 = "5f4802f5905b9a640c49be08",
The5F480309D1D96A703F2F3143 = "5f480309d1d96a703f2f3143",
}
export interface CustomFieldItem {
id: string;
value: CustomFieldItemValue;
idCustomField: IDCustomFieldEnum;
idModel: string;
modelType: ModelType;
}
export enum ModelType {
Card = "card",
}
export interface CustomFieldItemValue {
number?: string;
checked?: string;
}
export interface Old {
value?: OldValue | null;
pos?: number;
idList?: IDMemberCreator;
name?: string;
due?: null;
dueReminder?: null;
cover?: OldCover;
desc?: string;
}
export interface OldValue {
number: string;
}
export interface ActionLimits {
reactions?: Reactions;
}
export interface Reactions {
perAction: PerBoard;
uniquePerAction: PerBoard;
}
export interface PerBoard {
status: Status;
disableAt: number;
warnAt: number;
}
export enum Status {
Ok = "ok",
}
export interface MemberCreatorClass {
id: IDMemberCreator;
username: Username;
activityBlocked: boolean;
avatarHash: AvatarHash;
avatarUrl: string;
fullName: FullNameEnum;
idMemberReferrer: null;
initials: Initials;
nonPublic: NonPublic;
nonPublicAvailable: boolean;
}
export enum AvatarHash {
Ea6D6D7Da6B79Dc0Cf31301Bc672487F = "ea6d6d7da6b79dc0cf31301bc672487f",
}
export enum Initials {
Cl = "CL",
}
export interface NonPublic {
fullName: FullNameEnum;
initials: Initials;
avatarHash: null;
}
export enum Username {
johnsmith = "johnsmith",
}
export interface CardElement {
id: string;
address: null;
checkItemStates: null;
closed: boolean;
coordinates: null;
creationMethod: null;
dateLastActivity: string;
desc: string;
descData: DescDataClass | null;
dueReminder: number | null;
idBoard: IDBoardEnum;
idLabels: string[];
idList: IDMemberCreator;
idMembersVoted: any[];
idShort: number;
idAttachmentCover: null;
locationName: null;
manualCoverAttachment: boolean;
name: string;
pos: number;
shortLink: string;
isTemplate: boolean;
cardRole: null;
badges: Badges;
dueComplete: boolean;
due: null | string;
email: string;
idChecklists: string[];
idMembers: IDMemberCreator[];
labels: Label[];
limits: CardLimits;
shortUrl: string;
start: null;
subscribed: boolean;
url: string;
cover: PurpleCover;
attachments: Attachment[];
pluginData: any[];
customFieldItems: CustomFieldItem[];
}
export interface Attachment {
bytes: number | null;
date: string;
edgeColor: null | string;
idMember: string;
isUpload: boolean;
mimeType: null | string;
name: string;
previews: Scaled[];
url: string;
pos: number;
id: string;
fileName?: string;
}
export interface Scaled {
_id: string;
id: string;
scaled: boolean;
url: string;
bytes: number;
height: number;
width: number;
}
export interface Badges {
attachmentsByType: AttachmentsByType;
location: boolean;
votes: number;
viewingMemberVoted: boolean;
subscribed: boolean;
fogbugz: string;
checkItems: number;
checkItemsChecked: number;
checkItemsEarliestDue: null;
comments: number;
attachments: number;
description: boolean;
due: null | string;
dueComplete: boolean;
start: null;
}
export interface AttachmentsByType {
trello: TrelloClass;
}
export interface TrelloClass {
board: number;
card: number;
}
export interface PurpleCover {
idAttachment: null;
color: null;
idUploadedBackground: IDUploadedBackground | null;
size: MemberType;
brightness: Brightness;
idPlugin: null;
scaled?: Scaled[];
edgeColor?: string;
sharedSourceUrl?: string;
}
export interface DescDataClass {
emoji: Emoji;
}
export interface Emoji {
}
export interface Label {
id: string;
idBoard: IDBoardEnum;
name: string;
color: string;
}
export interface CardLimits {
attachments: Stickers;
checklists: Stickers;
stickers: Stickers;
}
export interface Stickers {
perCard: PerBoard;
}
export interface ChecklistElement {
id: string;
name: FullNameEnum;
idCard: string;
pos: number;
creationMethod: null;
idBoard: IDBoardEnum;
limits: ChecklistLimits;
checkItems: CheckItemElement[];
}
export interface CheckItemElement {
idChecklist: string;
state: string;
id: string;
name: string;
nameData: DescDataClass | null;
pos: number;
due: null;
idMember: null | string;
}
export interface ChecklistLimits {
checkItems: CheckItems;
}
export interface CheckItems {
perChecklist: PerBoard;
}
export interface CustomFieldElement {
id: IDCustomFieldEnum;
idModel: IDBoardEnum;
modelType: string;
fieldGroup: string;
display: Display;
name: string;
pos: number;
type: string;
isSuggestedField: boolean;
}
export interface Display {
cardFront: boolean;
}
export interface LabelNames {
green: string;
yellow: string;
orange: string;
red: string;
purple: string;
blue: string;
sky: string;
lime: string;
pink: string;
black: string;
}
export interface TrelloLimits {
attachments: Attachments;
boards: Boards;
cards: PurpleCards;
checklists: Attachments;
checkItems: CheckItems;
customFields: CustomFields;
customFieldOptions: CustomFieldOptions;
labels: CustomFields;
lists: Lists;
stickers: Stickers;
reactions: Reactions;
}
export interface Attachments {
perBoard: PerBoard;
perCard: PerBoard;
}
export interface Boards {
totalMembersPerBoard: PerBoard;
}
export interface PurpleCards {
openPerBoard: PerBoard;
openPerList: PerBoard;
totalPerBoard: PerBoard;
totalPerList: PerBoard;
}
export interface CustomFieldOptions {
perField: PerBoard;
}
export interface CustomFields {
perBoard: PerBoard;
}
export interface Lists {
openPerBoard: PerBoard;
totalPerBoard: PerBoard;
}
export interface List {
id: IDMemberCreator;
name: FullNameEnum;
closed: boolean;
pos: number;
softLimit: null;
creationMethod: null;
idBoard: IDBoardEnum;
limits: ListLimits;
subscribed: boolean;
}
export interface ListLimits {
cards: FluffyCards;
}
export interface FluffyCards {
openPerList: PerBoard;
totalPerList: PerBoard;
}
export interface MemberElement {
id: IDMemberCreator;
bio: string;
bioData: null;
confirmed: boolean;
memberType: MemberType;
username: Username;
activityBlocked: boolean;
avatarHash: AvatarHash;
avatarUrl: string;
fullName: FullNameEnum;
idEnterprise: null;
idEnterprisesDeactivated: any[];
idMemberReferrer: null;
idPremOrgsAdmin: any[];
initials: Initials;
nonPublic: NonPublic;
nonPublicAvailable: boolean;
products: any[];
url: string;
status: string;
}
export interface Membership {
id: string;
idMember: IDMemberCreator;
memberType: string;
unconfirmed: boolean;
deactivated: boolean;
}
export interface PluginDatum {
id: string;
idPlugin: string;
scope: string;
idModel: IDBoardEnum;
value: string;
access: string;
}
export interface Prefs {
permissionLevel: string;
hideVotes: boolean;
voting: string;
comments: string;
invitations: string;
selfJoin: boolean;
cardCovers: boolean;
isTemplate: boolean;
cardAging: string;
calendarFeedEnabled: boolean;
background: string;
backgroundImage: string;
backgroundImageScaled: BackgroundImageScaled[];
backgroundTile: boolean;
backgroundBrightness: Brightness;
backgroundBottomColor: string;
backgroundTopColor: string;
canBePublic: boolean;
canBeEnterprise: boolean;
canBeOrg: boolean;
canBePrivate: boolean;
canInvite: boolean;
}
export interface BackgroundImageScaled {
width: number;
height: number;
url: string;
}

View File

@ -0,0 +1,27 @@
{
"compilerOptions": {
"jsx": "react",
"target": "es2019",
"module": "commonjs",
"esModuleInterop": true,
"noImplicitAny": true,
"strict": true,
"strictNullChecks": true,
"forceConsistentCasingInFileNames": true,
"sourceMap": true,
"allowJs": true,
"resolveJsonModule": true,
"incremental": false,
"outDir": "./dist",
"moduleResolution": "node"
},
"include": [
"."
],
"exclude": [
".git",
"**/node_modules/*",
"dist",
"pack"
]
}

17
import/trello/utils.ts Normal file
View File

@ -0,0 +1,17 @@
import * as crypto from 'crypto'
class Utils {
static createGuid(): string {
function randomDigit() {
if (crypto && crypto.randomBytes) {
const rands = crypto.randomBytes(1)
return (rands[0] % 16).toString(16)
}
return (Math.floor((Math.random() * 16))).toString(16)
}
return 'xxxxxxxx-xxxx-4xxx-8xxx-xxxxxxxxxxxx'.replace(/x/g, randomDigit)
}
}
export { Utils }