Focalboard Asana 导入指南:将 Asana JSON 归档转换为 Focalboard 看板
【免费下载链接】focalboardFocalboard is an open source, self-hosted alternative to Trello, Notion, and Asana.项目地址: https://gitcode.com/GitHub_Trending/fo/focalboard
Focalboard 是一套开源、可自托管的看板与项目管理工具(对标 Trello、Notion、Asana)。本文基于 import/asana 目录下的官方导入脚本,完整讲解如何把 Asana 导出的 JSON 归档转换为 Focalboard 可识别的.boardarchive文件,并深入剖析其源码实现与数据映射规则。读完本文,你将能独立完成"导出 Asana → 转换归档 → 导入 Focalboard"的完整迁移流程,并理解该工具内部的工作原理与当前能力边界。
一、工具定位与整体思路
Focalboard 在 import/README.md 中明确说明:import子目录集中存放从其他系统导入数据的脚本,目前处于早期阶段,已包含 Trello、Asana、Notion、Jira、Todoist、Nextcloud Deck 等来源的基础导入示例。
Asana 导入器是一个独立的 Node.js/TypeScript 命令行应用,其核心思路是一条单向转换流水线:
Asana JSON 归档 → parse(JSON 解析) → convert(映射为 Focalboard 模型) → buildBlockArchive(生成 JSONL 归档) → .boardarchive 文件从源码结构看,整个链路由 importAsana.ts 的main()函数驱动,配合 asana.ts 的输入类型定义、utils.ts 的 GUID 生成工具,以及 import/util/archive.ts 的归档序列化工具完成。
二、环境准备与依赖安装
Asana 导入器运行在 Node.js 环境,依赖ts-node直接执行 TypeScript 源码。由于脚本引用了 webapp/src/blocks 下的 Board、Card、Block 等前端类型定义,因此需要先在 webapp 目录安装一次依赖,再在导入器目录安装自身依赖:
# 1. 安装 webapp 依赖(导入器引用了 webapp 下的类型与工厂函数) npm install # 在 focalboard/webapp 目录执行 # 2. 安装导入器自身依赖 npm install # 在 focalboard/import/asana 目录执行从 import/asana/package.json 可以看到运行期核心依赖仅为minimist(命令行参数解析),开发期依赖包括ts-node、typescript、eslint及对应类型声明,整体十分轻量。
三、从 Asana 导出 JSON
在 Asana 网页端操作:
- 打开目标看板,点击看板标题旁的下拉菜单(Board Menu);
- 选择
Export / Print(导出 / 打印); - 选择
JSON格式导出; - 将文件保存到本地,例如命名为
asana.json。
导出的 JSON 遵循 Asana 的 REST 数据模型。仓库中 asana.ts 给出了该文件的结构化类型定义,顶层结构为:
export interface Asana { data: Datum[]; }其中每个Datum对应一张 Asana 任务(task),包含gid、name(任务名)、notes(任务备注)、completed、memberships(成员关系,含所属 project 与 section)、projects(所属项目列表)、custom_fields(自定义字段)、subtasks(子任务)等字段。特别地,Membership定义了任务与项目、分栏(section)的对应关系:
export interface Membership { project: Workspace; section: Workspace; }这是后续把任务映射回 Focalboard 看板分组的关键数据来源。
四、运行转换命令
在focalboard/import/asana目录下执行:
npx ts-node importAsana.ts -i <asana.json> -o archive.boardarchive命令行参数详解
脚本通过minimist解析参数(见 importAsana.ts),支持两个参数:
| 参数 | 含义 | 默认值 | 说明 |
|---|---|---|---|
-i | 输入文件路径 | 无(必填) | Asana 导出的 JSON 归档路径;未提供时打印帮助并退出(退出码 1),文件不存在时报错并退出(退出码 2) |
-o | 输出文件路径 | archive.boardarchive | 转换生成的 Focalboard 归档文件路径 |
实际执行效果如下(源码中通过console.log输出进度):
Board: 项目名称 Card: 任务名称 1 Card: 任务名称 2 ... Found N card(s). Exported to archive.boardarchive成功后会生成archive.boardarchive文件。
验证脚本
import/asana/package.json 中内置了测试脚本,可直接验证转换流程:
npm test该命令等价于ts-node importAsana.ts -i test/asana.json -o test/asana-import.focalboard,将样例 JSON 转换为归档文件。调试模式可用npm run debug:test(以--inspect=5858启动 Node 调试器)。
五、导入到 Focalboard
在 Focalboard 界面完成导入:
- 点击
Settings(设置); - 选择
Import archive(导入归档); - 选择上一步生成的
archive.boardarchive文件。
"Import archive" 入口在界面中位于设置菜单中,对应前端源码 webapp/src/components/globalHeader/globalHeaderSettingsMenu.tsx 中的Sidebar.import-archive菜单项(侧边栏设置菜单 sidebarSettingsMenu.test.tsx.snap 中亦有对应断言)。
六、导入范围与数据映射规则
原文档明确了当前脚本的导入范围:导入单个看板中的所有卡片,包含它们所属的分栏(column/section)、名称(name)和备注(notes)。结合源码可以进一步确认具体的数据映射细节:
| Asana 元素 | Focalboard 目标 | 实现位置 |
|---|---|---|
| 项目(project) | 看板(Board),标题取项目名 | importAsana.ts |
| 分栏(section) | 看板上的 Select 类型属性Section,每个分栏对应一个选项 | importAsana.ts |
| 任务(task) | 卡片(Card),标题取任务名 | importAsana.ts |
| 任务所属分栏 | 卡片上Section属性的取值 | importAsana.ts |
| 任务备注(notes) | 卡片下的文本内容块(text block) | importAsana.ts |
值得注意的是,映射策略是"分栏变属性":脚本会为每个 section 生成一个带颜色的选项,并在卡片上打上对应属性值,而不是把分栏直接映射为看板视图的列。这样导入后 Focalboard 通过 Board View 的按Section属性分组即可还原出类似 Asana 的看板列布局。
七、源码实现深度剖析
7.1 入口与转换主流程
main()(importAsana.ts)的执行顺序为:
- 解析
-i/-o参数,校验输入文件存在性; JSON.parse读取 Asana 归档;- 调用
convert(input)得到[boards, blocks]; - 调用
ArchiveUtils.buildBlockArchive(boards, blocks)序列化输出。
其中有一个值得注意的细节:脚本通过(global.window as any) = {}补了一个空window对象,目的是让依赖浏览器环境的Utils.createGuid正常工作(importAsana.ts)。
7.2 数据抽取:getProjects 与 getSections
getProjects()(importAsana.ts):遍历所有任务,收集任务projects字段中出现的项目,按gid去重;getSections()(importAsana.ts):遍历任务,通过memberships中与指定项目匹配的成员关系,收集该项目下出现的全部分栏。
脚本目前只处理第一个项目(源码注释标注了TODO: Handle multiple projects,importAsana.ts),若输入中没有任何项目则打印No projects found并返回空结果。
7.3 分栏到 Select 属性的映射
每个分栏会被分配一个由Utils.createGuid()生成的随机 ID(基于 Nodecrypto.randomBytes,见 utils.ts),同时从预设的 10 色调色板optionColors(Gray、Brown、Orange、Yellow、Green、Blue、Purple、Pink、Red,按序循环取色,importAsana.ts)中挑选颜色,构成IPropertyOption(其类型定义位于 webapp/src/blocks/board.ts)。最终这些选项被组装进名为Section、类型为select的IPropertyTemplate,并赋给board.cardProperties。
这里隐式覆盖了createBoard()默认创建的Statusselect 属性(见 board.ts 的逻辑),即导入后的看板属性以Section为准。
7.4 视图与卡片生成
脚本创建了一个viewType为board的看板视图(importAsana.ts),viewType的类型定义可参考 webapp/src/blocks/boardView.ts,支持board | table | gallery | calendar四种视图。
每张卡片:
- 标题取任务名,
boardId与parentId均指向新看板; - 通过
memberships找到任务在当前项目下所属的 section,把对应选项 ID 写入卡片属性properties[cardProperty.id];若找不到映射会打印Invalid idList/Missing idList警告但不会中断; - 若任务存在
notes,则创建一个标题为备注全文的文本内容块(createTextBlock(),类型定义见 webapp/src/blocks/textBlock.ts),将其挂到卡片下并写入outCard.fields.contentOrder。
7.5 归档文件格式:JSONL
生成.boardarchive的核心是 import/util/archive.ts 的buildBlockArchive():它输出一种按行分隔的 JSON(JSONL)格式——
{"version":1,"date":<时间戳>} ← 第一行:归档头 {"type":"board","data":{...}} ← 随后每行一个 Board {"type":"block","data":{...}} ← 再随后每行一个 Block该 schema 通过type字段预留了扩展更多行类型的空间;parseBlockArchive()则作为逆向解析器,供导入归档时校验 header(要求version >= 1)并逐行还原 Block。理解这一格式有助于排查归档导入失败的问题——例如 header 行缺失会导致解析器报ERROR parsing header。
八、当前限制与后续扩展方向
综合文档与源码,当前 Asana 导入器存在以下明确边界:
- 仅支持单看板:
convert()只取getProjects()返回的第一个项目,多项目归档需等待TODO: Handle multiple projects的实现; - 字段覆盖有限:仅导入分栏、任务名与备注。任务的 assignee、due date、completed 状态、custom_fields、子任务、关注者等字段均未映射(
asana.ts的类型定义中虽有这些字段,但转换逻辑未消费); - 备注以纯文本块呈现:notes 中的富文本格式(若有)不会被保留;
- 无外部依赖去重:项目/分栏按
gid去重,但归档内其他实体均生成全新 GUID,属"重建式"导入而非增量同步。
如需扩展导入范围,可遵循文档建议参与 Contribute code 指引:在convert()中补充字段映射、处理多项目,或参照 asana.ts 的类型定义解析更多 Asana 字段,最终统一经ArchiveUtils.buildBlockArchive()输出归档,即可被 Focalboard 的Import archive功能识别。
【免费下载链接】focalboardFocalboard is an open source, self-hosted alternative to Trello, Notion, and Asana.项目地址: https://gitcode.com/GitHub_Trending/fo/focalboard
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考