Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions skills/rhdh-release/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ compatibility: "acli on PATH. Python 3 + gog CLI for Google Sheets/Docs."

<essential_principles>

<principle name="use_parse_issues_for_team_counts">
Always use `parse_issues.py --enrich` for team counts — never count manually. The Team custom field cannot be queried via JQL. Use `acli jira workitem search --json | python ~/.claude/skills/rhdh-jira/scripts/parse_issues.py --enrich` and filter by team in the output.
<principle name="use_cloud_id_for_team_queries">
Use `"Team[Team]" = "{{CLOUD_ID}}"` in JQL to filter by team. Cloud IDs are in the RHDH Team Mapping spreadsheet (column "Cloud ID"). Use the `open_issues_by_team`, `feature_freeze_issues_by_team`, or `code_freeze_issues_by_team` JQL templates. This is fast and does not require `parse_issues.py --enrich`.
</principle>

<principle name="include_jira_links">
Expand Down
34 changes: 34 additions & 0 deletions skills/rhdh-release/references/jql-release.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,37 @@ project IN (RHIDP, RHDHBugs, RHDHPLAN, RHDHSUPP) AND fixVersion = "{{RELEASE_VER

- **Placeholders:** `{{RELEASE_VERSION}}`
- **Notes:** All open work. Same as `open_issues` — used for Code Freeze announcements.

## open_issues_by_team

Find all open issues for a release filtered by team using Cloud ID.

```jql
project IN (RHIDP, RHDHBugs, RHDHPLAN, RHDHSUPP) AND fixVersion = "{{RELEASE_VERSION}}" AND status != closed AND "Team[Team]" = "{{CLOUD_ID}}"
```

- **Placeholders:** `{{RELEASE_VERSION}}`, `{{CLOUD_ID}}`
- **Example:** `... AND fixVersion = "2.1.0" AND status != closed AND "Team[Team]" = "ec74d716-af36-4b3c-950f-f79213d08f71-4403"`
- **Notes:** Cloud ID is the Jira Cloud team identifier from the RHDH Team Mapping spreadsheet (column "Cloud ID"). This is the fastest way to filter by team — no enrichment needed.

## feature_freeze_issues_by_team

Find feature work outstanding at Feature Freeze filtered by team.

```jql
project IN (RHIDP, RHDHBugs, RHDHPLAN, RHDHSUPP) AND fixVersion = "{{RELEASE_VERSION}}" and resolution is EMPTY AND component not in (AI, Build, Certification, "Continuous Improvement", Documentation, Knowledge, Performance, Quality, Quickstart, Release, "RHDH Local", Security, Segment, Serviceability, Support, "Team Operations", "Test Framework", "Test Infrastructure", "Upstream & Community", UX) AND Type not in (Bug, Vulnerability, sub-task) AND status not in ("Dev Complete", "Release Pending", Done, Closed) AND (labels is EMPTY OR labels != stretch-goal) AND "Team[Team]" = "{{CLOUD_ID}}"
```

- **Placeholders:** `{{RELEASE_VERSION}}`, `{{CLOUD_ID}}`
- **Notes:** Same as `feature_freeze_issues` but scoped to a single team by Cloud ID.

## code_freeze_issues_by_team

Find all issues outstanding at Code Freeze filtered by team.

```jql
project IN (RHIDP, RHDHBugs, RHDHPLAN, RHDHSUPP) AND fixVersion = "{{RELEASE_VERSION}}" AND status != closed AND "Team[Team]" = "{{CLOUD_ID}}"
```

- **Placeholders:** `{{RELEASE_VERSION}}`, `{{CLOUD_ID}}`
- **Notes:** Same as `code_freeze_issues` but scoped to a single team by Cloud ID.
6 changes: 5 additions & 1 deletion skills/rhdh-release/scripts/jql.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ def render(
*,
version: str | None = None,
issue_type: str | None = None,
cloud_id: str | None = None,
path: Path | None = None,
) -> str:
"""Render a JQL template with placeholder substitution."""
Expand All @@ -81,6 +82,8 @@ def render(
jql = jql.replace("{{RELEASE_VERSION}}", version)
if issue_type is not None:
jql = jql.replace("{{ISSUE_TYPE}}", issue_type)
if cloud_id is not None:
jql = jql.replace("{{CLOUD_ID}}", cloud_id)
return jql


Expand All @@ -94,10 +97,11 @@ def render_with_url(
*,
version: str | None = None,
issue_type: str | None = None,
cloud_id: str | None = None,
path: Path | None = None,
) -> tuple[str, str]:
"""Render a JQL template and return (jql, jira_url)."""
jql = render(name, version=version, issue_type=issue_type, path=path)
jql = render(name, version=version, issue_type=issue_type, cloud_id=cloud_id, path=path)
return jql, jira_url(jql)


Expand Down
69 changes: 27 additions & 42 deletions skills/rhdh-release/scripts/release.py
Original file line number Diff line number Diff line change
Expand Up @@ -619,39 +619,32 @@ def cmd_teams(args: argparse.Namespace, fmt: OutputFormatter) -> None:


def cmd_team_breakdown(args: argparse.Namespace, fmt: OutputFormatter) -> None:
"""Per-team issue counts for a release (requires parse_issues.py --enrich)."""
"""Per-team issue counts for a release using JQL team filter."""
version = args.version
jql = jql_mod.render("open_issues", version=version)
issues = _acli_json_enriched(jql, select="key,summary,status,team")

teams = _fetch_teams(category="Engineering")

counts: dict[str, int] = {}
for issue in issues:
team = issue.get("team", "Unassigned") or "Unassigned"
norm = _normalize_team_name(team)
counts[norm] = counts.get(norm, 0) + 1

rows = []
for t in teams:
name = t["team_name"]
norm = _normalize_team_name(name)
count = counts.pop(norm, 0)
cid = t.get("cloud_id", "")
if not cid:
rows.append({"team_name": name, "cloud_id": "", "count": 0,
"leads": t.get("leads", ""), "slack_handles": t.get("slack_handles", [])})
continue
jql, url = jql_mod.render_with_url("open_issues_by_team", version=version, cloud_id=cid)
count = _acli_count(jql, fmt)
fmt.log_info(f"{name:<25} {count:>5}")
rows.append(
{
"team_name": name,
"team_id": t.get("team_id"),
"cloud_id": cid,
"count": count,
"jira_url": url,
"leads": t.get("leads", ""),
"slack_handles": t.get("slack_handles", []),
}
)

matched_norms = {_normalize_team_name(t["team_name"]) for t in teams}
for norm_name, count in sorted(counts.items()):
if norm_name not in matched_norms:
rows.append({"team_name": norm_name, "count": count})

fmt.header(f"RHDH {version} — Issues by Team")
for r in rows:
fmt.log_info(f"{r['team_name']:<25} {r['count']:>5}")
Expand Down Expand Up @@ -793,27 +786,23 @@ def cmd_slack_feature_freeze_update(args: argparse.Namespace, fmt: OutputFormatt
rn_jql, rn_url = jql_mod.render_with_url("release_notes", version=version)
rn_count = _acli_count(rn_jql, fmt)

ff_jql = jql_mod.render("feature_freeze_issues", version=version)
issues = _acli_json_enriched(ff_jql, select="key,summary,status,team")

team_counts: dict[str, int] = {}
for issue in issues:
team = issue.get("team", "Unassigned") or "Unassigned"
norm = _normalize_team_name(team)
team_counts[norm] = team_counts.get(norm, 0) + 1

team_lines = []
for t in teams:
name = t["team_name"]
norm = _normalize_team_name(name)
count = team_counts.get(norm, 0)
cid = t.get("cloud_id", "")
if not cid:
continue
jql, url = jql_mod.render_with_url(
"feature_freeze_issues_by_team", version=version, cloud_id=cid
)
count = _acli_count(jql, fmt)
slack_handles = t.get("slack_handles", [])
lead_slack = slack_handles[0] if slack_handles else t.get("leads", "")
team_lines.append(
{
"TEAM_NAME": name,
"ISSUE_COUNT": str(count),
"JIRA_LINK": jql_mod.jira_url(ff_jql),
"JIRA_LINK": url,
"LEAD_SLACK": lead_slack,
}
)
Expand Down Expand Up @@ -898,27 +887,23 @@ def cmd_slack_code_freeze_update(args: argparse.Namespace, fmt: OutputFormatter)
fs_jql, fs_url = jql_mod.render_with_url("feature_subtasks", version=version)
fs_count = _acli_count(fs_jql, fmt)

cf_jql = jql_mod.render("code_freeze_issues", version=version)
issues = _acli_json_enriched(cf_jql, select="key,summary,status,team")

team_counts: dict[str, int] = {}
for issue in issues:
team = issue.get("team", "Unassigned") or "Unassigned"
norm = _normalize_team_name(team)
team_counts[norm] = team_counts.get(norm, 0) + 1

team_lines = []
for t in teams:
name = t["team_name"]
norm = _normalize_team_name(name)
count = team_counts.get(norm, 0)
cid = t.get("cloud_id", "")
if not cid:
continue
jql, url = jql_mod.render_with_url(
"code_freeze_issues_by_team", version=version, cloud_id=cid
)
count = _acli_count(jql, fmt)
slack_handles = t.get("slack_handles", [])
lead_slack = slack_handles[0] if slack_handles else t.get("leads", "")
team_lines.append(
{
"TEAM_NAME": name,
"TEAM_ISSUE_COUNT": str(count),
"JIRA_LINK": jql_mod.jira_url(cf_jql),
"JIRA_LINK": url,
"LEAD_SLACK": lead_slack,
}
)
Expand Down
18 changes: 9 additions & 9 deletions skills/rhdh-release/workflows/issues-by-team.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,32 +27,32 @@ If the CLI succeeds, use its output directly. If it fails, follow the manual ste
gog sheets get 1vQXfvID72qwqvLb17eyGOvnZXrZG7NBzTGv6RP9wvyM Team --json --results-only
```

Filter to category "Engineering" and status "Active". This gives team names and `team_id` values.
Filter to category "Engineering" and status "Active". This gives team names and `cloud_id` values.

## Step 3 (fallback): Query issues and filter by team
## Step 3 (fallback): Query issues by team using Cloud ID

Fetch all open issues for the release, enriched with team data:
Use the `open_issues_by_team` JQL template with the team's Cloud ID:

```bash
acli jira workitem search --jql 'project IN (RHIDP, RHDHBugs, RHDHPLAN, RHDHSUPP) AND fixVersion = "{{RELEASE_VERSION}}" AND status != closed' --limit 200 --json | python ~/.claude/skills/rhdh-jira/scripts/parse_issues.py --enrich -s key,summary,status,team
acli jira workitem search --jql 'project IN (RHIDP, RHDHBugs, RHDHPLAN, RHDHSUPP) AND fixVersion = "{{RELEASE_VERSION}}" AND status != closed AND "Team[Team]" = "{{CLOUD_ID}}"' --count
```

To filter to a specific team by team ID:
To get full issue details for a team:

```bash
acli jira workitem search --jql 'project IN (RHIDP, RHDHBugs, RHDHPLAN, RHDHSUPP) AND fixVersion = "{{RELEASE_VERSION}}" AND status != closed' --limit 200 --json | python ~/.claude/skills/rhdh-jira/scripts/parse_issues.py --enrich -f team_id={{TEAM_ID}} -s key,summary,status
acli jira workitem search --jql 'project IN (RHIDP, RHDHBugs, RHDHPLAN, RHDHSUPP) AND fixVersion = "{{RELEASE_VERSION}}" AND status != closed AND "Team[Team]" = "{{CLOUD_ID}}"' --json
```

**Important:** Always use `parse_issues.py --enrich` for team counts — the Team field is a custom field that cannot be queried via JQL directly.
The Cloud ID for each team is in the "Cloud ID" column of the RHDH Team Mapping spreadsheet (e.g., `ec74d716-af36-4b3c-950f-f79213d08f71-4403` for COPE).

## Step 4 (fallback): Build per-team counts

For each active engineering team, count the matching issues and build a Jira search link.

## Step 5 (fallback): Format output

| Team | Team ID | Issue Count | Lead | Jira Link |
|------|---------|-------------|------|-----------|
| Team | Cloud ID | Issue Count | Lead | Jira Link |
|------|----------|-------------|------|-----------|
| {{TEAM_NAME}} | {{TEAM_ID}} | {{COUNT}} | @{{LEAD_SLACK}} | [View](https://issues.redhat.com/issues/?jql=...) |

</process>
Expand Down
2 changes: 1 addition & 1 deletion skills/rhdh-release/workflows/teams-and-leads.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ Include link to source: [RHDH Team Mapping](https://docs.google.com/spreadsheets
<gotchas>

- By default only active teams are returned. The CLI filters by `status = Active` in the Google Sheet.
- The `team_id` is the numeric ID used by `parse_issues.py --enrich` for team-based filtering — the Team custom field cannot be queried via JQL directly.
- The `cloud_id` is the Jira Cloud team identifier used by `parse_issues.py --enrich` for team-based filtering — the Team custom field cannot be queried via JQL directly.

</gotchas>

Expand Down
Loading