Skip to content

OCPEDGE-2970: Add edge-contribution plugin - #300

Open
copejon wants to merge 4 commits into
openshift-eng:mainfrom
copejon:ocpedge-2970-edge-contribution-plugin
Open

copejon wants to merge 4 commits into
openshift-eng:mainfrom
copejon:ocpedge-2970-edge-contribution-plugin

Conversation

@copejon

@copejon copejon commented Sep 30, 2026

Copy link
Copy Markdown
Contributor

Add the edge-contribution Claude Code plugin: 3 skills providing a manager-user 2 different views of edge-team member work distribution across work streams. Users are provided a data-driven analysis of contribution diversity (how many workstreams a member contributed to) and workstream silo-ing (how many contributions each workstream received). Accepts times spans in FYQ (e.g. 2026Q1), quarter-to-date, and arbitrary time spans.

Skills
/edge-contribution:heatmap
Screenshot 2026-09-30 at 11 28 35 AM

/edge-contribution:summary
Screenshot 2026-09-30 at 12 25 12 PM

/edge-contribution:export Exports the raw data in csv format
Screenshot 2026-09-30 at 12 29 58 PM

@coderabbitai

coderabbitai Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration
  • Configuration used: Repository YAML (base), Central YAML (inherited)
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: cbb6babb-54df-493c-afae-c303dcfa31fc

Comment @coderabbitai help to get the list of available commands.

@openshift-ci

openshift-ci Bot commented Sep 30, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: copejon

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-ci openshift-ci Bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Sep 30, 2026
@copejon
copejon force-pushed the ocpedge-2970-edge-contribution-plugin branch from 9ad25e7 to 799f404 Compare October 1, 2026 14:10

@brandisher brandisher left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strong test coverage and the collectors are appropriately narrow in what data they pull. But stepping back, this plugin has ~2,700 lines of pipeline code (collectors, metrics, rendering, CSV export) trying to deterministically pre-solve a lot of problems that the model is already good at solving live: calling gh/Jira APIs in a loop per team member, aggregating results, formatting a table or summary. A skill doesn't need a hand-built GraphQL batching engine, a custom Jira REST client, or a 1,100-line renderer with duplicated text/markdown code paths — it can just tell the model what data to gather and how to present it, and let it write and run that logic on the fly each time.

Suggest cutting this down significantly: lean on gh and the mcp-atlassian MCP tools directly instead of reimplementing their transports, and trust the model to loop over the roster, compute the aggregates, and render the output per-invocation rather than shipping a large deterministic pipeline to maintain. Specific spots called out inline, but the overall ask is to simplify broadly, not just at these points.

name: str
github: str
role: str
location: str

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

location is parsed from the roster and serialized into roster.json (via asdict(member) at line 224), but nothing downstream (metrics.py, render.py, report.py, export_csv.py) ever reads it. Same for kerberos as a standalone field — it's only needed transiently to build jira_username (line 105), so persisting it separately is redundant. Suggest dropping both from the Member dataclass, or at least excluding them from the serialized output, so we're not carrying extra personal fields into roster.json for no reason.

# Managers on the Eng/QE roster by role title who are not IC contributors.
# Filtering them from the team median prevents distortion of allocation signals.
# Stored as the hash256 of the member's jira username
EXCLUDED_MEMBERS = ("82e5282cc07f498ff0daf8352e7403e2f16b017dd17c3e589276e406480bb5be"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are SHA-256 hashes of manager jira_usernames, presumably so the names aren't readable directly in source. But the roster itself (roster.json) has these same people in plaintext, and the comment above already explains why they're excluded (managers skew the team median). Hashing here doesn't add real privacy — it just makes the exclusion list unreviewable at a glance in code review, and brittle if anyone's jira username changes. Would a plaintext list (or pulling the exclusion from the role field already in the roster, e.g. anyone not is_engineering_or_qe) be simpler and equally safe here?

Comment thread plugins/edge-contribution/bin/render.py Outdated
return lines


def _summary_team_text(summary: ExecutiveSummary) -> List[str]:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Starting here through ~line 1125, every summary section has a paired _..._text / _..._markdown function (7 pairs: header, team, workstreams, people, data_health, what_counted, how_attributed, what_excluded). They're near-identical aside from formatting tokens (## headers, | tables vs plain lines). That's roughly 400 lines of duplicated logic. Worth collapsing into one function per section that takes a markdown: bool (or format enum) and branches only on the line-formatting, rather than two full copies of the content-building logic.

Comment thread plugins/edge-contribution/TODO.md Outdated
@@ -0,0 +1,153 @@
# edge-contribution — known gaps

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest dropping this file entirely rather than shipping a roadmap with the plugin. It's a lot of speculative future work (P1/P2/P3 items, live-data validation targets) baked into the repo, and it'll drift out of sync fast. Let's land this plugin as scoped, then plan enhancements based on actual user feedback rather than a pre-written backlog.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed - this one slipped in but it's only meant for local dev work.

return JiraConfig(base_url=base_url, username=username, api_token=api_token)


class JiraClient:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This implements a full custom Jira REST client (auth, HTTP, pagination) via requests. This workspace already has mcp-atlassian configured with Jira search tools — could this plugin depend on that instead of maintaining its own HTTP/auth layer? Would cut a meaningful chunk of _common.py plus the test_common.py coverage for it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point. I don't see that wouldn't work. Will try and see

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The short answer is "kind of." Models communicate w/ mcps over jsonrpc 2.0. So instead of implementing a REST client here, it would be a jsonrpc2.0 client, add dependency on another plugin, but gain nothing (AFAICT).

return f"{window.start.isoformat()}..{window.end.isoformat()}"


def build_batch_query(slots: List[SearchSlot], window: Window) -> str:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest dropping the custom batching layer here in favor of plain gh calls — SearchSlot, build_batch_query, parse_batch_response, and graphql_rate_limit_adapter (~350 lines across lines 83–430) amount to a bespoke GraphQL query engine built on top of gh api graphql. gh search prs or per-member gh api calls would get the same data with a fraction of the code and none of the custom rate-limit/parsing logic to maintain. Team-roster-sized workloads shouldn't need this level of batching — let's lean on gh directly and cut this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I started out with using gh directly, but ran into some limitations that forced me onto graphql. Namely because running 1 gh search per user was consistently triggering API rate limiting. Batching via graphql has eliminated that problem.

…map)

Introduce the edge-contribution Claude Code plugin: two skills over a
shared, standalone, testable data-collection layer that report
cross-workstream contribution for a quarter or date range.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@copejon
copejon force-pushed the ocpedge-2970-edge-contribution-plugin branch from 799f404 to 5604d33 Compare October 5, 2026 18:38
copejon and others added 3 commits October 5, 2026 20:22
The location and kerberos fields were parsed from the roster and
serialized to roster.json, but nothing downstream (metrics.py,
render.py, report.py, export_csv.py) ever read them.

kerberos is still derived from the Rover URL and used transiently
to build jira_username, but it's no longer persisted as a separate
field. location is dropped entirely.

Addresses code review feedback on PR openshift-eng#300.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Previously, each summary section had two functions: _foo_text and
_foo_markdown, duplicating logic with only formatting differences
(markdown headers, pipe tables vs plain text). This resulted in
16 functions (8 pairs) with duplicated code.

Now each section has one function with a format parameter that
branches only on the line-formatting tokens. Output is byte-for-byte
identical to the previous implementation.

Functions refactored:
- _summary_header (was _summary_header_text + _summary_header_markdown)
- _summary_team
- _summary_workstreams
- _summary_people
- _summary_data_health
- _summary_what_counted
- _summary_how_attributed
- _summary_what_excluded

Reduces render.py from 1143 to 1112 lines. All 408 tests pass.

Addresses code review feedback on PR openshift-eng#300.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This was a local development planning file that accidentally
slipped into the PR. Removing per code review feedback.

Addresses code review feedback on PR openshift-eng#300.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants