每个规划周期开始时,目标通常很清楚;真正困难的是几周之后,团队还能不能从数百张 Jira 工单中看出这些目标是否正在推进。新需求、缺陷和临时任务不断进入队列,工单数量持续增长,但“我们是否仍在做当初约定的事情”却越来越难回答。
LaserFocus 针对的正是这个信息缺口:不是再增加一个复杂仪表盘,而是用一个页面讲清楚目标、进度、停滞点和偏离情况。
一个页面应该回答什么
Jira 擅长保存事实:负责人、状态、更新时间、优先级和依赖关系都可以记录下来。但这些字段不会自动组成一段关于目标的叙事。
一个面向目标的页面至少应该回答四个问题:
- 当前周期有哪些目标? 数量应保持有限,并使用团队都能理解的名称。
- 每个目标关联了哪些工作? 工单必须通过标签、自定义字段、Epic 或其他明确规则映射到目标。
- 哪些目标正在停滞? 不能只看“进行中”的工单数,还要检查多久没有更新、是否存在阻塞,以及是否没有任何已完成事项。
- 有多少工作没有服务于任何目标? 未映射工单不一定没有价值,但它们会暴露临时需求和范围漂移。
因此,“单屏”并不意味着把所有 Jira 数据压缩到一张密集报表里。更合理的做法是只保留决策所需的信号,并允许用户在需要时再进入 Jira 查看细节。
先建立目标与工单之间的稳定关系
实现这类视图时,最关键的不是图表组件,而是数据模型。团队需要选择一种稳定、可审计的映射方式,例如:
- 每个目标使用一个 Jira 标签;
- 使用名为
Goal的单选自定义字段; - 让一个目标对应一个 Initiative 或 Epic;
- 在独立配置文件中为每个目标维护一条 JQL。
标签部署最快,但容易出现拼写和维护问题;自定义字段更规范,却需要 Jira 管理权限;Epic 层级适合产品研发结构,但未必能覆盖运营、支持和基础设施工作。实践中应选择团队能够持续维护的方案,而不是理论上最完整的方案。
还要明确区分两类指标:
- 交付信号:完成了多少工单、多久没有更新、是否被阻塞;
- 结果指标:转化率、延迟、故障率、留存率等目标真正要改变的业务或技术结果。
Jira 可以很好地提供前一类数据,但“关闭了 80% 的工单”不等于“实现了 80% 的目标”。LaserFocus 式页面应把工单进度视为预警信号,而不是最终结果。
可以这样实践:从 Jira 生成单页目标报告
下面是一个可以改造的最小示例。它假设:
- 使用 Jira Cloud;
- 每个目标由一条 JQL 定义;
- “完成”根据 Jira 的
statusCategory判断; - 未完成且 14 天没有更新的工单被标记为停滞;
- 每个目标最多读取 100 张工单,规模更大时需要补充分页。
先创建 goals.json,把项目键和标签替换成自己的值:
[
{
"name": "提高新用户留存",
"jql": "project = PAY AND labels = \"goal-retention\""
},
{
"name": "降低支付接口延迟",
"jql": "project = PAY AND labels = \"goal-latency\""
}
]
然后保存以下脚本为 goal_report.py:
import html
import json
import os
from datetime import datetime, timedelta, timezone
from pathlib import Path
import requests
base_url = os.environ["JIRA_BASE_URL"].rstrip("/")
email = os.environ["JIRA_EMAIL"]
api_token = os.environ["JIRA_API_TOKEN"]
goals = json.loads(Path("goals.json").read_text(encoding="utf-8"))
session = requests.Session()
session.auth = (email, api_token)
session.headers.update({"Accept": "application/json"})
cutoff = datetime.now(timezone.utc) - timedelta(days=14)
def search_issues(jql):
response = session.get(
f"{base_url}/rest/api/3/search/jql",
params={
"jql": jql,
"fields": "summary,status,updated",
"maxResults": 100,
},
timeout=30,
)
response.raise_for_status()
return response.json().get("issues", [])
def parse_time(value):
return datetime.fromisoformat(value.replace("Z", "+00:00"))
rows = []
for goal in goals:
issues = search_issues(goal["jql"])
done = [
issue for issue in issues
if issue["fields"]["status"]["statusCategory"]["key"] == "done"
]
open_issues = [issue for issue in issues if issue not in done]
stale = [
issue for issue in open_issues
if parse_time(issue["fields"]["updated"]) < cutoff
]
total = len(issues)
progress = round(len(done) * 100 / total) if total else 0
if total == 0:
signal = "Not broken down"
elif stale:
signal = f"{len(stale)} stale"
else:
signal = "Moving"
rows.append(
"<tr>"
f"<td>{html.escape(goal['name'])}</td>"
f"<td>{len(done)} / {total}</td>"
f"<td><progress value='{progress}' max='100'></progress> {progress}%</td>"
f"<td>{html.escape(signal)}</td>"
"</tr>"
)
report = f"""<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Goal Focus</title>
<style>
body {{ font: 16px system-ui; margin: 40px auto; max-width: 960px; padding: 0 20px; }}
table {{ width: 100%; border-collapse: collapse; }}
th, td {{ padding: 12px; border-bottom: 1px solid #ddd; text-align: left; }}
progress {{ width: 150px; }}
</style>
</head>
<body>
<h1>Goal Focus</h1>
<p>Generated at {datetime.now(timezone.utc).isoformat()}</p>
<table>
<thead><tr><th>Goal</th><th>Done</th><th>Ticket progress</th><th>Signal</th></tr></thead>
<tbody>{''.join(rows)}</tbody>
</table>
</body>
</html>"""
Path("goal-report.html").write_text(report, encoding="utf-8")
print("Wrote goal-report.html")
安装依赖并运行:
python -m venv .venv
source .venv/bin/activate
pip install requests
export JIRA_BASE_URL="https://your-company.atlassian.net"
export JIRA_EMAIL="developer@example.com"
export JIRA_API_TOKEN="replace-with-your-token"
python goal_report.py
生成的 goal-report.html 可以直接在浏览器中打开,也可以由 CI 定时生成并发布到内部静态站点。不要把 API Token 写进仓库;在 CI 中应使用加密变量或密钥管理服务。
这个示例只展示了交付层信号。进一步改造时,可以加入目标负责人、阻塞工单、业务指标链接和“未映射工单”区域,但不要让页面重新膨胀成另一个 Jira 首页。
上线前检查:让页面推动决策,而不只是展示数据
目标视图是否有效,可以用以下清单检验:
- 每个目标是否有明确负责人和更新时间?
- 每张重要工单是否只能映射到一个主要目标,或有清晰的多目标规则?
- “停滞”是否有统一定义,例如 14 天无更新或存在阻塞状态?
- 是否单独显示未映射工作,以暴露范围漂移?
- 页面是否同时提供结果指标,而不只统计工单完成率?
- 点击摘要后能否进入对应 Jira 查询,继续调查细节?
- 服务账号是否遵循最小权限原则,报告中是否可能泄露敏感工单?
真正有价值的单屏视图,不是把 Jira 换一套颜色,而是缩短从“看到异常”到“采取行动”的距离。先从少量目标、简单映射和一个可靠的停滞规则开始;当团队确实用它做周期复盘和优先级取舍时,再增加指标与自动化。