在 Testlify,工程团队的工时记录一直依赖一款昂贵的第三方应用,与日常 Jira 工作流脱节。我主导了 Timesheets by Appforest 的前端开发,这是一款集成到 Jira 中的应用,用于工时记录、团队产能规划和项目成本分摊。该产品已在 Atlassian Marketplace 上发布,并在公司内部全面部署。

我构建的内容
- Atlassian Marketplace 集成: 构建在 Atlassian Forge 之上,这是 Atlassian 的沙盒化无服务器应用运行时,使用严格限定、最小权限的 Jira 读写权限,而非自建后端。
- 嵌入式 Jira 界面: 一个直接嵌入 Jira 工单中的工时记录面板,用于快速录入,再加上一个完整的仪表盘应用,提供日历视图和每日/每周工时提交,无需切换上下文。
- 报表与分析: 结合 Jira 数据与内部工时记录数据的报表视图,用于冲刺核算和项目成本追踪。

为何重要
这款插件在 Atlassian Marketplace 上向外部客户发布,同时也在内部取代了我们旧的工时追踪软件,每年节省 9,000 美元以上的授权费用,并让管理层实时了解工程资源的分配情况。
团队与职责
我在一个小团队中主导前端开发:自 2024 年年中以来,4 名贡献者共提交了 397 次 commit,至今仍在积极维护。这不是一个人的作品,而是一个持续多年、多人协作的产品,我的角色仅限于前端,而非全栈负责。
用的是 Forge,不是 Connect
这款应用运行在 Atlassian Forge 上,这是一个沙盒化的无服务器运行时,而非较早的 Connect 框架(一种自建后端、通过 iframe 和 webhook 与 Jira 通信的自托管应用)。使用 Forge 意味着面向 Jira 的那部分不需要我们自己运维或打补丁的服务器:Atlassian 托管执行环境,强制执行清单中声明的权限范围,并施加无服务器式的限制(ARM64 上的 Node 22、1024MB 内存、每次调用的执行时间上限),而不是把这套基础设施留给我们自己搭建。
两个界面服务于两种不同意图
这款产品刻意分成两个界面,而非合而为一。工单上下文面板处理最常见的场景:针对正在查看的工单,一键记录工时,无需离开它。另一个完整的仪表盘应用(日历视图、报表、管理设置、成本追踪)负责规划和监督,这些任务需要的是一个真正的工作空间,而不是一个侧边栏面板。每个界面都是为其自身职责而设计,而不是强行用一个界面兼顾两者。
一次真正的跨项目集成
这不是一个孤立的应用。解析器函数会明确接收并清除来自 Google 日历、Outlook 日历以及配套的 Appforest Timetracker VS Code 扩展的工时录入建议,这样开发者在 VS Code 中的编码活动就能作为建议的工时记录出现在这里,而无需手动重新录入。
数据模型与规模
- Forge 权限范围狭窄且明确(
read:jira-work、write:jira-work、manage:jira-configuration,以及用户/令牌读取),而非宽泛的一揽子授权。 - UI 组件使用 Atlassian 自己的设计令牌(
@atlaskit),使插件在视觉上与原生 Jira 保持一致,而不像一个后加的第三方产品。 - 仪表盘应用是使用 Pinia 做状态管理的 Vue 3,共 46 个组件,与基于 React 的工单上下文面板分离;一个产品中有两套前端技术栈,而不是把一套共享代码库人为拉伸去覆盖两个界面。
- Forge 函数的内存分配是在遇到真实性能问题后从 256MB 提升到 1024MB 的,而不是一开始就默认设为 1024MB。
支撑这些成本/冲刺视图的报表引擎会根据报表规模自行调整查询策略:它会按报表体量将 Jira JQL 搜索请求拆分为每次 25、50 或 100 个工单一批,每个工单拉取 16 个字段,而不是逐个工单发起请求,也不是发起一个在大型冲刺中可能超时的无限制单一查询。
Build vs. Buy
选择 Forge 而非自托管的 Connect 应用:Atlassian 的运行时和权限模型取代了这个团队原本需要自行构建和维护的基础设施。选择原生 Atlassian 设计令牌而非自建 UI 套件:插件继承了 Jira 本身的外观和交互模式,而不是为这一个应用单独维护一套并行的设计系统。
技术栈
Atlassian Forge(Node 22,ARM64),工单上下文面板使用 React 18,仪表盘应用使用 Vue 3 搭配 Pinia,Atlassian Design System 的令牌与基础组件,通过 Atlassian Marketplace 部署。