From c4db91c257ee2a2145db35850efa5fbc76f2db78 Mon Sep 17 00:00:00 2001 From: Jan Maarten <83665577+janmaarten-a11y@users.noreply.github.com> Date: Wed, 22 Jul 2026 11:36:49 -0700 Subject: [PATCH 1/4] Timeline: tag License Compliance event stories with taxonomy data-* attributes Spread the event data-* contract from the taxonomy module onto every Timeline.Item in the License Compliance feature stories. This is the proof-of-pattern pilot for Phase 3 taxonomy tagging (github/primer#6664). Storybook-only, no consumer-facing change. Copilot-Session: ac5d1407-e965-4994-b6d5-fd1b8ec0cdef --- ...ne.license-compliance.features.stories.tsx | 87 +++++++++++++------ 1 file changed, 62 insertions(+), 25 deletions(-) diff --git a/packages/react/src/Timeline/Timeline.license-compliance.features.stories.tsx b/packages/react/src/Timeline/Timeline.license-compliance.features.stories.tsx index 36d8dda8015..20c4acca04b 100644 --- a/packages/react/src/Timeline/Timeline.license-compliance.features.stories.tsx +++ b/packages/react/src/Timeline/Timeline.license-compliance.features.stories.tsx @@ -19,6 +19,13 @@ import {Button} from '../Button' import Link from '../Link' import Octicon from '../Octicon' import {EventSubRow, Examples, MutedTime, UserActor, VariantSection} from './internal/timelineStoryHelpers' +import { + actorTypeForLogin, + LICENSE_COMPLIANCE_SCOPE, + LICENSE_COMPLIANCE_TAXONOMY, + toEventDataAttributes, + type LicenseComplianceEventType, +} from './taxonomy' import classes from './Timeline.license-compliance.features.stories.module.css' /** @@ -67,10 +74,22 @@ import classes from './Timeline.license-compliance.features.stories.module.css' * base `Timeline` component's own stories, and any docs-site representation is a * Phase 3 consideration via base-component story changes, out of scope here. * - * FUTURE FILTERING (taxonomy still open — github/primer#6663): category - * `data-*` attributes (e.g. `data-event-category="created"`) will attach to each - * `Timeline.Item` below so stories can be filtered/grouped by event family. We - * intentionally do NOT add them yet to avoid baking in a taxonomy. + * TAXONOMY `data-*` CONTRACT (proof-of-pattern pilot): every `Timeline.Item` + * below now carries the event `data-*` attributes projected from the merged + * taxonomy module (`./taxonomy`, primer/react#8180) — the single source of truth + * for Timeline event categorization (github/primer#6664, docs github/primer#6888). + * Each row spreads the output of `toEventDataAttributes` via the local `lcAttrs` + * helper, which derives `category` / `visibility` FROM the catalog entry + * (`LICENSE_COMPLIANCE_TAXONOMY`) so the stories stay in sync with the catalog, + * and resolves `data-actor-type` at runtime from each row's rendered actor login + * (`actorTypeForLogin`). The contract per rendered `
  • `: `data-event-scope`, + * `data-event-type` (the UNSCOPED leaf), `data-event-category`, + * `data-event-visibility` (defaults `primary`), and `data-actor-type` (OMITTED + * when the row renders no actor, e.g. the synthetic `appeared_in_branch`). + * License Compliance is fully cataloged (all nine leaves), so nothing here is + * left untagged — there are no parked or shared events on this surface. This is + * the proof-of-pattern surface; the same approach fans out to the other four + * timeline surfaces next. * * PROOF-OF-PATTERN HISTORY: this file landed first as an `opened`-only * scaffold to validate the template, then grew to the full nine-group set. All @@ -140,6 +159,24 @@ const PolicyLink = ({href = '../../settings/security_analysis'}: {href?: string} ) +/** + * Local projection of the taxonomy `data-*` contract for this surface. Given a + * License Compliance leaf `type` (and, when the row renders an actor, that + * actor's `login`), it returns the `data-*` attribute set to spread on the + * `Timeline.Item`. `category` and `visibility` come FROM the catalog entry so + * the stories track `LICENSE_COMPLIANCE_TAXONOMY`; `data-actor-type` is resolved + * at runtime from the login and omitted when no login is passed (actor-less + * rows). See github/primer#6664 and the taxonomy docs (github/primer#6888). + */ +const lcAttrs = (type: LicenseComplianceEventType, login?: string) => + toEventDataAttributes({ + scope: LICENSE_COMPLIANCE_SCOPE, + type, + category: LICENSE_COMPLIANCE_TAXONOMY[type].category, + visibility: LICENSE_COMPLIANCE_TAXONOMY[type].visibility, + actorType: login ? actorTypeForLogin(login) : undefined, + }) + export default { title: 'Components/Timeline/Events/License Compliance', component: Timeline, @@ -186,7 +223,7 @@ export const EventOpened = () => ( {/* Opened — license-compliance system bot, ShieldIcon on success (green) */} - + @@ -222,7 +259,7 @@ export const EventAppearedInBranch = () => ( badge, with a BranchName pill. PR sub-row is dormant (see group doc). */} - + @@ -262,7 +299,7 @@ export const EventReviewRequested = () => ( {/* Requested to close — no reason, no PR, no comment */} - + @@ -278,7 +315,7 @@ export const EventReviewRequested = () => ( {/* Requested to close as {reason}, with a requester comment sub-row */} - + @@ -296,7 +333,7 @@ export const EventReviewRequested = () => ( "Review request" button (latest request only) in Timeline.Actions. */} - + @@ -330,7 +367,7 @@ export const EventReviewApproved = () => ( {/* Approved closure request */} - + @@ -346,7 +383,7 @@ export const EventReviewApproved = () => ( {/* Approved closure request — with reviewer comment */} - + @@ -374,7 +411,7 @@ export const EventReviewDenied = () => ( {/* Denied closure request */} - + @@ -390,7 +427,7 @@ export const EventReviewDenied = () => ( {/* Denied closure request — with reviewer comment */} - + @@ -422,7 +459,7 @@ export const EventReviewExpired = () => ( {/* Request to close expired — license-compliance system bot, automatic expiry */} - + @@ -452,7 +489,7 @@ export const EventExceptionAdded = () => ( {/* Full shape — package + policy link + repo name */} - + @@ -470,7 +507,7 @@ export const EventExceptionAdded = () => ( {/* Fallback shape — body missing package info */} - + @@ -499,7 +536,7 @@ export const EventLicensesAdded = () => ( {/* Full shape — licenses list + policy link + repo name */} - + @@ -517,7 +554,7 @@ export const EventLicensesAdded = () => ( {/* Fallback shape — body missing licenses array */} - + @@ -550,7 +587,7 @@ export const EventClosed = () => ( {/* Closed as amendment — with a closing comment */} - + @@ -567,7 +604,7 @@ export const EventClosed = () => ( {/* Closed as private package */} - + @@ -583,7 +620,7 @@ export const EventClosed = () => ( {/* Closed as inaccurate license */} - + @@ -599,7 +636,7 @@ export const EventClosed = () => ( {/* Closed as policy edited */} - + @@ -615,7 +652,7 @@ export const EventClosed = () => ( {/* Closed as fixed */} - + @@ -631,7 +668,7 @@ export const EventClosed = () => ( {/* Closed as outdated — resolution Outdated */} - + @@ -647,7 +684,7 @@ export const EventClosed = () => ( {/* Closed this alert — default (no reason / resolution) */} - + From c9f61ccf03b82b2bd99a525d3c95da11cbbfdb12 Mon Sep 17 00:00:00 2001 From: Jan Maarten <83665577+janmaarten-a11y@users.noreply.github.com> Date: Wed, 22 Jul 2026 15:06:40 -0700 Subject: [PATCH 2/4] Timeline: tag Code Scanning, Dependabot, Secret Scanning, and Issues event stories with taxonomy data-* attributes Extend the taxonomy data-* tagging from License Compliance to the remaining four surfaces, so all five now carry the contract. Same approach: each Timeline.Item spreads toEventDataAttributes(), deriving category and visibility from that surface's catalog and resolving data-actor-type from the rendered login. Parked and shared events, and variants with no catalog leaf, are left untagged with inline comments. Storybook-only; no visual change. --- ...imeline.code-scanning.features.stories.tsx | 61 ++++-- .../Timeline.dependabot.features.stories.tsx | 102 +++++++--- .../Timeline.issues.features.stories.tsx | 184 ++++++++++-------- ...eline.secret-scanning.features.stories.tsx | 123 +++++++++--- 4 files changed, 323 insertions(+), 147 deletions(-) diff --git a/packages/react/src/Timeline/Timeline.code-scanning.features.stories.tsx b/packages/react/src/Timeline/Timeline.code-scanning.features.stories.tsx index 9e03994be10..1edbc345fb2 100644 --- a/packages/react/src/Timeline/Timeline.code-scanning.features.stories.tsx +++ b/packages/react/src/Timeline/Timeline.code-scanning.features.stories.tsx @@ -26,6 +26,7 @@ import { UserActor, VariantSection, } from './internal/timelineStoryHelpers' +import {CODE_SCANNING_TAXONOMY, actorTypeForLogin, toEventDataAttributes, type CodeScanningEventType} from './taxonomy' import classes from './Timeline.code-scanning.features.stories.module.css' /** @@ -172,6 +173,26 @@ const ConfigPill = ({category}: {category: string}) => ( */ const SubRow = ({children}: {children: React.ReactNode}) =>
    {children}
    +/** + * Per-row taxonomy `data-*` attributes (Phase 3 tagging, github/primer#6664, + * epic #6654). Projects a Code Scanning leaf event type through the shared + * `toEventDataAttributes` serializer from the taxonomy module (primer/react#8180), + * mirroring the License Compliance pilot (primer/react#8216). `category` and + * `visibility` are derived FROM `CODE_SCANNING_TAXONOMY` so the story never + * hand-maintains them. Pass the row's rendered `UserActor` login for USER events + * (resolves `data-actor-type` via `actorTypeForLogin`); omit it for SYSTEM rows + * so `data-actor-type` is left off entirely. Spread the result onto each + * `Timeline.Item`. + */ +const codeScanningAttrs = (type: CodeScanningEventType, login?: string) => + toEventDataAttributes({ + scope: 'code-scanning', + type, + category: CODE_SCANNING_TAXONOMY[type].category, + visibility: CODE_SCANNING_TAXONOMY[type].visibility, + actorType: login ? actorTypeForLogin(login) : undefined, + }) + export default { title: 'Components/Timeline/Events/Code Scanning', component: Timeline, @@ -219,7 +240,7 @@ export const EventDetected = () => ( sub-row, and a right-aligned tool-version Label (Timeline.Actions). */} - + @@ -253,7 +274,7 @@ export const EventDetected = () => ( card — `show_timeline_commit?` is false for this event). */} - + @@ -283,7 +304,7 @@ export const EventDetected = () => ( {category}" pill (rendered when the alert has more than one category). */} - + @@ -314,13 +335,20 @@ export const EventDetected = () => ( * Config-deleted renders the analysis category as an inline subtle mono pill * (`MonoPill`); when the category is empty the live ERB substitutes "API * Upload". + * + * TAXONOMY (github/primer#6664): the two "Fixed in branch" rows are the + * ALERT_CLOSED_BECAME_FIXED wire event and carry `data-event-type="fixed"`. The + * two "Configuration deleted" rows are visually grouped under Fixed here, but the + * taxonomy catalog folds their ALERT_CLOSED_BECAME_OUTDATED wire event into + * `closed` (a SYSTEM closure), so those rows carry `data-event-type="closed"`. + * Both are system events with no actor, so neither emits `data-actor-type`. */ export const EventFixed = () => ( {/* Fixed — selected ref → SOLID purple shield-check */} - + @@ -335,7 +363,7 @@ export const EventFixed = () => ( {/* Fixed — non-selected ref → bare default check */} - + @@ -348,9 +376,12 @@ export const EventFixed = () => ( {/* Config deleted — selected ref → SOLID purple shield-check */} + {/* Config-deletion is the ALERT_CLOSED_BECAME_OUTDATED wire event, which the + taxonomy folds into `closed` (system closure), not `fixed` — so this row + carries data-event-type="closed" despite its visual "Fixed" grouping. */} - + @@ -366,7 +397,7 @@ export const EventFixed = () => ( {/* Config deleted — non-selected ref → bare default check */} - + @@ -398,7 +429,7 @@ export const EventClosedByUser = () => ( {/* Closed as false positive — with a resolution-note sub-row */} - + @@ -415,7 +446,7 @@ export const EventClosedByUser = () => ( {/* Closed as used in tests */} - + @@ -431,7 +462,7 @@ export const EventClosedByUser = () => ( {/* Closed as won't fix */} - + @@ -448,7 +479,7 @@ export const EventClosedByUser = () => ( `resolution == :NO_RESOLUTION`. */} - + @@ -475,7 +506,7 @@ export const EventReopened = () => ( - + @@ -510,7 +541,7 @@ export const EventDismissalRequested = () => ( - + @@ -550,7 +581,7 @@ export const EventDismissalReviewed = () => ( {/* Approved — check icon + reviewer-comment sub-row */} - + @@ -567,7 +598,7 @@ export const EventDismissalReviewed = () => ( {/* Denied — x icon */} - + diff --git a/packages/react/src/Timeline/Timeline.dependabot.features.stories.tsx b/packages/react/src/Timeline/Timeline.dependabot.features.stories.tsx index 7768c168dff..b756a162b32 100644 --- a/packages/react/src/Timeline/Timeline.dependabot.features.stories.tsx +++ b/packages/react/src/Timeline/Timeline.dependabot.features.stories.tsx @@ -29,6 +29,7 @@ import { UserActor, VariantSection, } from './internal/timelineStoryHelpers' +import {actorTypeForLogin, DEPENDABOT_TAXONOMY, toEventDataAttributes, type DependabotEventType} from './taxonomy' /** * Dependabot alert Timeline event examples (Phase 2 of github/primer#6663). @@ -56,10 +57,26 @@ import { * base `Timeline` component's own stories, and any docs-site representation is a * Phase 3 consideration via base-component story changes, out of scope here. * - * FUTURE FILTERING (taxonomy still open — github/primer#6663): category - * `data-*` attributes (e.g. `data-event-category="opened"`) will attach to each - * `Timeline.Item` below so stories can be filtered/grouped by event family. We - * intentionally do NOT add them yet to avoid baking in a taxonomy. + * TAXONOMY `data-*` CONTRACT: every cataloged `Timeline.Item` below carries the + * event `data-*` attributes projected from the merged taxonomy module + * (`./taxonomy`, primer/react#8180) — the single source of truth for Timeline + * event categorization (github/primer#6664, docs github/primer#6888). Each row + * spreads the output of `toEventDataAttributes` via the local `dependabotAttrs` + * helper, which derives `category` / `visibility` FROM the catalog entry + * (`DEPENDABOT_TAXONOMY`) so the stories stay in sync with the catalog, and + * resolves `data-actor-type` at runtime from each row's rendered actor login + * (`actorTypeForLogin`): Dependabot-authored rows resolve to `bot`, user-driven + * rows to `user`. The contract per rendered `
  • `: `data-event-scope`, + * `data-event-type` (the UNSCOPED leaf), `data-event-category`, + * `data-event-visibility` (defaults `primary`), and `data-actor-type`. This + * applies the same contract proven by the License Compliance pilot + * (primer/react#8216). + * + * SHARED / PARKED EVENTS (left untagged): the Assignment and Copilot-work groups + * are cross-surface SHARED events, deliberately kept OUT of the per-surface + * Dependabot catalog (github/primer#6888), so they intentionally carry NO + * `data-*` attributes. See the code comments above `EventAssignment` and + * `EventCopilotWork`. * * SLOT USAGE (Phase 1 slots — same convention as the Issues group): * - `Timeline.Avatar` (gutter slot, #6677): the 40px LEFT-GUTTER avatar. @@ -120,6 +137,25 @@ const PushPill = ({sha}: {sha: string}) => ( ) +/** + * Local projection of the taxonomy `data-*` contract for this surface. Given a + * Dependabot leaf `type` (and, when the row renders an actor, that actor's + * `login`), it returns the `data-*` attribute set to spread on the + * `Timeline.Item`. `category` and `visibility` come FROM the catalog entry so + * the stories track `DEPENDABOT_TAXONOMY`; `data-actor-type` is resolved at + * runtime from the login (Dependabot-authored rows pass a `…[bot]` login so they + * resolve to `bot`; user-driven rows pass the rendered user login). See + * github/primer#6664 and the taxonomy docs (github/primer#6888). + */ +const dependabotAttrs = (type: DependabotEventType, login?: string) => + toEventDataAttributes({ + scope: 'dependabot', + type, + category: DEPENDABOT_TAXONOMY[type].category, + visibility: DEPENDABOT_TAXONOMY[type].visibility, + actorType: login ? actorTypeForLogin(login) : undefined, + }) + export default { title: 'Components/Timeline/Events/Dependabot', component: Timeline, @@ -160,7 +196,7 @@ export const EventOpened = () => ( {/* Opened — no source */} - + @@ -176,7 +212,7 @@ export const EventOpened = () => ( {/* OpenedFromPR — bold `#123` pull-request link (scheme: primary, bold) */} - + @@ -192,7 +228,7 @@ export const EventOpened = () => ( {/* OpenedFromPush — blue push-pill with the 7-char `after` SHA */} - + @@ -225,7 +261,7 @@ export const EventFixed = () => ( {/* Fixed — no source */} - + @@ -241,7 +277,7 @@ export const EventFixed = () => ( {/* FixedViaPR — bold `#123` pull-request link */} - + @@ -257,7 +293,7 @@ export const EventFixed = () => ( {/* FixedViaPush — blue push-pill */} - + @@ -290,7 +326,7 @@ export const EventDismissed = () => ( {/* Manual — risk is tolerable (with an optional dismissal note) */} - + @@ -309,7 +345,7 @@ export const EventDismissed = () => ( {/* Manual — fix started */} - + @@ -325,7 +361,7 @@ export const EventDismissed = () => ( {/* Manual — no bandwidth to fix this */} - + @@ -341,7 +377,7 @@ export const EventDismissed = () => ( {/* Manual — vulnerable code is not actually used */} - + @@ -357,7 +393,7 @@ export const EventDismissed = () => ( {/* Manual — inaccurate */} - + @@ -373,7 +409,7 @@ export const EventDismissed = () => ( {/* Auto — rule-based, no source (with optional rule comment) */} - + @@ -396,7 +432,7 @@ export const EventDismissed = () => ( {/* Auto — from a pull request */} - + @@ -412,7 +448,7 @@ export const EventDismissed = () => ( {/* Auto — from a push */} - + @@ -441,7 +477,7 @@ export const EventReopened = () => ( {/* Manual reopen — user actor */} - + @@ -457,7 +493,7 @@ export const EventReopened = () => ( {/* Reintroduced — no source */} - + @@ -473,7 +509,7 @@ export const EventReopened = () => ( {/* Reintroduced — from a pull request */} - + @@ -489,7 +525,7 @@ export const EventReopened = () => ( {/* Reintroduced — from a push */} - + @@ -505,7 +541,7 @@ export const EventReopened = () => ( {/* Auto-reopened — rule change (with optional rule comment) */} - + @@ -560,7 +596,7 @@ export const EventDismissalRequest = () => ( place that right-aligned control in the `Timeline.Actions` slot. */} - + @@ -584,7 +620,7 @@ export const EventDismissalRequest = () => ( {/* Dismissal approved — circle user actor, success/check badge */} - + @@ -601,7 +637,7 @@ export const EventDismissalRequest = () => ( {/* Dismissal denied — circle user actor, danger/x badge */} - + @@ -619,7 +655,7 @@ export const EventDismissalRequest = () => ( "x")` (no bg) → a bare default badge, not a danger/emphasis one. */} - + @@ -651,6 +687,12 @@ export const EventDismissalRequest = () => ( * The five variants mirror the Secret scanning assignment story: self-assign, * assign another, self-unassign, unassign another, and a combined assign + * unassign example. + * + * SHARED / PARKED — NO `data-*` TAXONOMY: assignment is a cross-surface SHARED + * event, deliberately kept OUT of the per-surface Dependabot catalog + * (`DEPENDABOT_TAXONOMY` has no assignment leaf), per the taxonomy docs + * (github/primer#6888). It has no per-surface taxonomy leaf yet, so these rows + * intentionally carry NO `data-*` attributes. */ export const EventAssignment = () => ( @@ -758,6 +800,12 @@ export const EventAssignment = () => ( * button above. * - Work finished (`with_badge(icon: "repo-push")`): default (gray) badge, no * right control. + * + * SHARED / PARKED — NO `data-*` TAXONOMY: Copilot-agent work is a cross-surface + * SHARED event, deliberately kept OUT of the per-surface Dependabot catalog + * (`DEPENDABOT_TAXONOMY` has no copilot leaf), per the taxonomy docs + * (github/primer#6888). It has no per-surface taxonomy leaf yet, so these rows + * intentionally carry NO `data-*` attributes. */ export const EventCopilotWork = () => ( diff --git a/packages/react/src/Timeline/Timeline.issues.features.stories.tsx b/packages/react/src/Timeline/Timeline.issues.features.stories.tsx index 20324b0b6ff..82d1c27d627 100644 --- a/packages/react/src/Timeline/Timeline.issues.features.stories.tsx +++ b/packages/react/src/Timeline/Timeline.issues.features.stories.tsx @@ -40,6 +40,8 @@ import Octicon from '../Octicon' import Token from '../Token' import classes from './Timeline.issues.features.stories.module.css' import {BoldLink, Examples, MutedTime, UserActor, VariantSection} from './internal/timelineStoryHelpers' +import {actorTypeForLogin, ISSUE_TAXONOMY, toEventDataAttributes} from './taxonomy' +import type {IssueEventType} from './taxonomy' /** * Issue Timeline event examples (Phase 2 of github/primer#6663). @@ -104,6 +106,23 @@ export default { ], } as Meta> +/** + * Serialize the taxonomy `data-*` contract (github/primer#6664) for one Issues + * event row. `type` is an ISSUE_TAXONOMY leaf; `category` + `visibility` are read + * from the catalog, so metadata leaves (labels, assignees, milestones, project + * fields, issue types, rename) pick up `visibility: 'auditOnly'` automatically. + * Pass the row's rendered actor login so `data-actor-type` resolves (every Issues + * event carries an actor). + */ +const issueAttrs = (type: IssueEventType, login?: string) => + toEventDataAttributes({ + scope: 'issue', + type, + category: ISSUE_TAXONOMY[type].category, + visibility: ISSUE_TAXONOMY[type].visibility, + actorType: login ? actorTypeForLogin(login) : undefined, + }) + /** * The Closed event group — `IssueTimeline.eventClosed` (audit § 2). * @@ -118,7 +137,7 @@ export const EventClosed = () => ( {/* Closed as completed */} - + @@ -139,7 +158,7 @@ export const EventClosed = () => ( (portable for docs copy-paste; matches production `TimelineRow`). */} - + ( {/* Closed via pull request */} - + @@ -185,7 +204,7 @@ export const EventClosed = () => ( {/* Closed via commit */} - + @@ -210,7 +229,7 @@ export const EventClosed = () => ( equivalent for github-ui's `ProjectV2` closer link. */} - + @@ -233,7 +252,7 @@ export const EventClosed = () => ( composed here from Primer primitives. */} - + ( {/* Closed with no state reason */} - + @@ -293,7 +312,7 @@ export const EventState = () => ( {/* Reopened — open (green) badge via useIssueState (ReopenedEvent.tsx) */} - + @@ -310,7 +329,7 @@ export const EventState = () => ( plain inline Link (the audit shows it bold; live code is canonical). */} - + @@ -329,7 +348,7 @@ export const EventState = () => ( {/* Pinned */} - + @@ -345,7 +364,7 @@ export const EventState = () => ( {/* Unpinned */} - + @@ -362,7 +381,7 @@ export const EventState = () => ( the resulting discussion by number. */} - + @@ -378,6 +397,7 @@ export const EventState = () => ( {/* Converted from draft */} + {/* Untagged: a metadata/auditOnly event that ISSUE_TAXONOMY does not yet enumerate as a distinct leaf (candidate leaf to add in a follow-up, not a defect). Emitting a data-event-type without a real catalog leaf would break the taxonomy single source of truth. */} @@ -407,7 +427,7 @@ export const EventReferences = () => ( open PR state icon inline before the PR title). */} - + @@ -431,7 +451,7 @@ export const EventReferences = () => ( icon is behind the live code here. */} - + @@ -451,7 +471,7 @@ export const EventReferences = () => ( inline (showAgoTimestamp={false}) and the commit card is sub-content. */} - + @@ -482,7 +502,7 @@ export const EventReferences = () => ( {/* Multiple commit references — same event, pluralized copy + N cards. */} - + @@ -538,7 +558,7 @@ export const EventDuplicates = () => ( IssueLink uses the open (green) state icon. */} - + @@ -562,6 +582,7 @@ export const EventDuplicates = () => ( {/* Marked as a duplicate of this issue — no right controls. */} + {/* Untagged: a metadata/auditOnly event that ISSUE_TAXONOMY does not yet enumerate as a distinct leaf (the catalog has only 'marked_as_duplicate'); candidate leaf to add in a follow-up, not a defect. */} @@ -584,6 +605,7 @@ export const EventDuplicates = () => ( {/* Unmarked this as a duplicate of . */} + {/* Untagged: a metadata/auditOnly event that ISSUE_TAXONOMY does not yet enumerate as a distinct leaf (the catalog has only 'marked_as_duplicate'); candidate leaf to add in a follow-up, not a defect. */} @@ -605,6 +627,7 @@ export const EventDuplicates = () => ( {/* Unmarked as a duplicate of this issue. */} + {/* Untagged: a metadata/auditOnly event that ISSUE_TAXONOMY does not yet enumerate as a distinct leaf (the catalog has only 'marked_as_duplicate'); candidate leaf to add in a follow-up, not a defect. */} @@ -638,7 +661,7 @@ export const EventModeration = () => ( {/* User blocked (permanent) */} - + @@ -654,7 +677,7 @@ export const EventModeration = () => ( {/* User temporarily blocked */} - + @@ -670,7 +693,7 @@ export const EventModeration = () => ( {/* Comment pinned — IssueCommentPinnedEvent.tsx links the pinned comment. */} - + @@ -689,7 +712,7 @@ export const EventModeration = () => ( {/* Comment unpinned */} - + @@ -720,7 +743,7 @@ export const EventIssueTypes = () => ( {/* Type added */} - + @@ -750,7 +773,7 @@ export const EventIssueTypes = () => ( {/* Type removed */} - + @@ -780,7 +803,7 @@ export const EventIssueTypes = () => ( {/* Type changed */} - + @@ -840,7 +863,7 @@ export const EventIssueHierarchy = () => ( {/* Sub-issue added (single) */} - + @@ -865,7 +888,7 @@ export const EventIssueHierarchy = () => ( {/* Sub-issue added (multiple) — plural copy + N reference rows */} - + @@ -897,7 +920,7 @@ export const EventIssueHierarchy = () => ( {/* Sub-issue removed (single) */} - + @@ -922,7 +945,7 @@ export const EventIssueHierarchy = () => ( {/* Sub-issues removed (multiple) */} - + @@ -954,7 +977,7 @@ export const EventIssueHierarchy = () => ( {/* Parent issue added (single) */} - + @@ -979,7 +1002,7 @@ export const EventIssueHierarchy = () => ( {/* Parent issues added (multiple) */} - + @@ -1011,7 +1034,7 @@ export const EventIssueHierarchy = () => ( {/* Parent issue removed (single) */} - + @@ -1036,7 +1059,7 @@ export const EventIssueHierarchy = () => ( {/* Parent issues removed (multiple) */} - + @@ -1084,7 +1107,7 @@ export const EventDependencies = () => ( {/* Blocked by (single) */} - + @@ -1109,7 +1132,7 @@ export const EventDependencies = () => ( {/* Blocked by (multiple) — count in copy + N rows */} - + @@ -1141,7 +1164,7 @@ export const EventDependencies = () => ( {/* Blocked by removed (single) */} - + @@ -1166,7 +1189,7 @@ export const EventDependencies = () => ( {/* Blocked by removed (multiple) */} - + @@ -1198,7 +1221,7 @@ export const EventDependencies = () => ( {/* Blocking (single) */} - + @@ -1223,7 +1246,7 @@ export const EventDependencies = () => ( {/* Blocking (multiple) */} - + @@ -1255,7 +1278,7 @@ export const EventDependencies = () => ( {/* Blocking removed (single) */} - + @@ -1280,7 +1303,7 @@ export const EventDependencies = () => ( {/* Blocking removed (multiple) */} - + @@ -1332,7 +1355,7 @@ export const EventIssueFields = () => ( {/* Set text field */} - + @@ -1353,7 +1376,7 @@ export const EventIssueFields = () => ( {/* Set number field */} - + @@ -1374,7 +1397,7 @@ export const EventIssueFields = () => ( {/* Set date field */} - + @@ -1395,7 +1418,7 @@ export const EventIssueFields = () => ( {/* Set single-select field — value is a colored token */} - + @@ -1429,7 +1452,7 @@ export const EventIssueFields = () => ( {/* Changed text field */} - + @@ -1450,7 +1473,7 @@ export const EventIssueFields = () => ( {/* Changed number field */} - + @@ -1471,7 +1494,7 @@ export const EventIssueFields = () => ( {/* Changed date field */} - + @@ -1492,7 +1515,7 @@ export const EventIssueFields = () => ( {/* Changed single-select field */} - + @@ -1523,7 +1546,7 @@ export const EventIssueFields = () => ( {/* Cleared text field — no value */} - + @@ -1540,7 +1563,7 @@ export const EventIssueFields = () => ( {/* Cleared number field */} - + @@ -1557,7 +1580,7 @@ export const EventIssueFields = () => ( {/* Cleared date field */} - + @@ -1574,7 +1597,7 @@ export const EventIssueFields = () => ( {/* Cleared single-select field */} - + @@ -1591,7 +1614,7 @@ export const EventIssueFields = () => ( {/* Rollup: updated only — multiple field updates collapsed into one row */} - + @@ -1616,7 +1639,7 @@ export const EventIssueFields = () => ( {/* Rollup: removed only */} - + @@ -1635,7 +1658,7 @@ export const EventIssueFields = () => ( {/* Rollup: updated and also removed — combined row joined by "and also" */} - + @@ -1687,7 +1710,7 @@ export const EventProject = () => ( {/* Added to project */} - + @@ -1708,7 +1731,7 @@ export const EventProject = () => ( {/* Removed from project */} - + @@ -1732,7 +1755,7 @@ export const EventProject = () => ( strings are PLAIN TEXT (not bold). Both forms shown under one caption. */} - + @@ -1746,7 +1769,7 @@ export const EventProject = () => ( - + @@ -1786,7 +1809,7 @@ export const EventLabels = () => ( {/* Label added */} - + @@ -1815,7 +1838,7 @@ export const EventLabels = () => ( {/* Label removed */} - + @@ -1845,6 +1868,7 @@ export const EventLabels = () => ( with "and" between them. */} + {/* Untagged: a metadata/auditOnly rolled-up event (labeled + unlabeled in one row) that ISSUE_TAXONOMY does not yet enumerate as a distinct leaf; candidate leaf to add in a follow-up, not a defect. */} @@ -1905,7 +1929,7 @@ export const EventTitle = () => ( {/* Title changed */} - + @@ -1940,7 +1964,7 @@ export const EventMilestones = () => ( {/* Added to milestone */} - + @@ -1960,7 +1984,7 @@ export const EventMilestones = () => ( {/* Removed from milestone */} - + @@ -1999,7 +2023,7 @@ export const EventAssignments = () => ( {/* Self-assigned */} - + @@ -2015,7 +2039,7 @@ export const EventAssignments = () => ( {/* Assigned someone else — assignee is a bold link, no avatar. */} - + @@ -2031,7 +2055,7 @@ export const EventAssignments = () => ( {/* Assigned multiple — joined with "and". */} - + @@ -2049,7 +2073,7 @@ export const EventAssignments = () => ( {/* Self-unassigned */} - + @@ -2065,7 +2089,7 @@ export const EventAssignments = () => ( {/* Unassigned someone else */} - + @@ -2081,7 +2105,7 @@ export const EventAssignments = () => ( {/* Unassigned multiple — joined with "and". */} - + @@ -2118,7 +2142,7 @@ export const EventLockUnlock = () => ( too heated). */} - + @@ -2128,7 +2152,7 @@ export const EventLockUnlock = () => ( - + @@ -2138,7 +2162,7 @@ export const EventLockUnlock = () => ( - + @@ -2148,7 +2172,7 @@ export const EventLockUnlock = () => ( - + @@ -2164,7 +2188,7 @@ export const EventLockUnlock = () => ( {/* Locked (no reason) */} - + @@ -2180,7 +2204,7 @@ export const EventLockUnlock = () => ( {/* Unlocked — UnlockIcon badge (not LockIcon). */} - + @@ -2211,7 +2235,7 @@ export const EventCommentDeleted = () => ( {/* Comment deleted */} - + @@ -2249,7 +2273,7 @@ export const EventCrossReferences = () => ( {/* Mentioned from an issue */} - + @@ -2274,7 +2298,7 @@ export const EventCrossReferences = () => ( {/* Mentioned from a pull request */} - + @@ -2299,7 +2323,7 @@ export const EventCrossReferences = () => ( {/* Linked a closing pull request */} - + diff --git a/packages/react/src/Timeline/Timeline.secret-scanning.features.stories.tsx b/packages/react/src/Timeline/Timeline.secret-scanning.features.stories.tsx index d9aa3afd1c8..c7e48924ab0 100644 --- a/packages/react/src/Timeline/Timeline.secret-scanning.features.stories.tsx +++ b/packages/react/src/Timeline/Timeline.secret-scanning.features.stories.tsx @@ -20,6 +20,12 @@ import { import {Button} from '../Button' import Octicon from '../Octicon' import {EventSubRow, MutedTime, RealisticTimeline, UserActor, VariantSection} from './internal/timelineStoryHelpers' +import { + actorTypeForLogin, + SECRET_SCANNING_TAXONOMY, + toEventDataAttributes, + type SecretScanningEventType, +} from './taxonomy' /** * Secret Scanning alert Timeline event examples (Phase 2 of github/primer#6663). @@ -53,10 +59,18 @@ import {EventSubRow, MutedTime, RealisticTimeline, UserActor, VariantSection} fr * base `Timeline` component's own stories, and any docs-site representation is a * Phase 3 consideration via base-component story changes, out of scope here. * - * FUTURE FILTERING (taxonomy still open — github/primer#6663): category - * `data-*` attributes (e.g. `data-event-category="created"`) will attach to each - * `Timeline.Item` below so stories can be filtered/grouped by event family. We - * intentionally do NOT add them yet to avoid baking in a taxonomy. + * EVENT TAXONOMY TAGGING (github/primer#6664, Phase 3): each cataloged + * `Timeline.Item` below carries the `data-*` event contract + * (`data-event-scope` / `data-event-type` / `data-event-category` / + * `data-event-visibility` / `data-actor-type`), projected from the shared + * `SECRET_SCANNING_TAXONOMY` catalog via `toEventDataAttributes`. This mirrors + * the License Compliance pilot (primer/react#8216) and consumes the merged + * taxonomy module (#8180) documented in github/primer#6888. + * + * The catalog was scoped to the five live `switch (event.type)` cases and is + * NARROWER than this story set, so MANY variants have no catalog leaf and are + * left UNTAGGED on purpose (each such item carries a comment explaining why). + * We tag ONLY the seven cataloged leaves and never invent a value. * * SLOT USAGE (Phase 1 slots — same convention as the Issues / Dependabot groups): * - `Timeline.Avatar` (gutter slot, #6677): the 40px LEFT-GUTTER avatar. @@ -97,6 +111,21 @@ import {EventSubRow, MutedTime, RealisticTimeline, UserActor, VariantSection} fr const SIX7_AVATAR = 'https://avatars.githubusercontent.com/u/4548309?v=4' const HUBOT_AVATAR = 'https://avatars.githubusercontent.com/u/480938?v=4' +// Projects a cataloged Secret Scanning leaf into the event `data-*` contract +// (github/primer#6664), same shape as the License Compliance pilot +// (primer/react#8216). `category` and `visibility` are read FROM the catalog so +// the story never re-declares the taxonomy; `login` is the actor rendered in the +// row, resolved to `data-actor-type` via `actorTypeForLogin`. Omit `login` for +// actor-less rows so no `data-actor-type` is emitted. +const secretScanningAttrs = (type: SecretScanningEventType, login?: string) => + toEventDataAttributes({ + scope: 'secret-scanning', + type, + category: SECRET_SCANNING_TAXONOMY[type].category, + visibility: SECRET_SCANNING_TAXONOMY[type].visibility, + actorType: login ? actorTypeForLogin(login) : undefined, + }) + export default { title: 'Components/Timeline/Events/Secret Scanning', component: Timeline, @@ -137,7 +166,12 @@ export const EventCreated = () => ( {/* Created — GitHub system actor, ShieldIcon on success (green) */} - + {/* `detected` leaf. Renders the GitHub SYSTEM-IDENTITY actor + (``); GitHub is not in the bot set, so + actorTypeForLogin('GitHub') classifies it as 'user' -> data-actor-type='user'. + FLAGGED: whether a system identity should classify as user vs bot is + worth confirming (see report). */} + @@ -177,7 +211,8 @@ export const EventResolution = () => ( badge. Shown WITH an optional resolution-comment sub-row. */} - + {/* `closed` leaf (Resolution, any non-reopened resolution.type); user actor. */} + @@ -196,7 +231,8 @@ export const EventResolution = () => ( {/* Closed as false positive — gray (default) ShieldSlash badge. */} - + {/* `closed` leaf; user actor. */} + @@ -212,7 +248,8 @@ export const EventResolution = () => ( {/* Closed as won't fix */} - + {/* `closed` leaf; user actor. */} + @@ -228,7 +265,8 @@ export const EventResolution = () => ( {/* Closed as used in tests */} - + {/* `closed` leaf; user actor. */} + @@ -244,7 +282,8 @@ export const EventResolution = () => ( {/* Closed as pattern deleted */} - + {/* `closed` leaf; user actor. */} + @@ -260,7 +299,8 @@ export const EventResolution = () => ( {/* Closed as pattern edited */} - + {/* `closed` leaf; user actor. */} + @@ -276,7 +316,8 @@ export const EventResolution = () => ( {/* Closed as ignored by configuration (resolution type `hidden_by_config`) */} - + {/* `closed` leaf; user actor. */} + @@ -297,7 +338,9 @@ export const EventResolution = () => ( placement rather than leaving the Break as a stray first child. */} - + {/* Context row: a `closed` leaf preceding the reopen, tagged in place + like the seven dedicated "Closed as …" variants; user actor. */} + @@ -308,7 +351,8 @@ export const EventResolution = () => ( - + {/* `reopened` leaf (Resolution, resolution.type === 'reopened'); user actor. */} + @@ -338,7 +382,8 @@ export const EventBypass = () => ( {/* Bypassed — AlertIcon, default (gray) badge */} - + {/* `bypassed` leaf; user actor. */} + @@ -355,6 +400,9 @@ export const EventBypass = () => ( repo has delegated bypass enabled. */} + {/* UNTAGGED: the delegated-BYPASS request/approve flow has no catalog + leaf. The catalog's dismissal_requested / dismissal_reviewed cover the + delegated-CLOSURE flow, a different thing. No leaf -> no data-* tag. */} @@ -371,6 +419,8 @@ export const EventBypass = () => ( {/* Bypass approved — CheckCircleIcon. Delegated bypass: gated as above. */} + {/* UNTAGGED: delegated-BYPASS flow, no catalog leaf (see the request + variant above). No leaf -> no data-* tag. */} @@ -403,7 +453,8 @@ export const EventValidityChange = () => ( {/* Active — automated (GitHub), AlertIcon on danger (red) */} - + {/* `validity_changed` leaf; automated -> GitHub system actor -> 'user'. */} + @@ -419,7 +470,8 @@ export const EventValidityChange = () => ( {/* Active — manual (user), same danger badge */} - + {/* `validity_changed` leaf; manual -> user actor. */} + @@ -435,7 +487,8 @@ export const EventValidityChange = () => ( {/* Inactive — automated (GitHub), SkipIcon on default (gray) */} - + {/* `validity_changed` leaf; automated -> GitHub system actor -> 'user'. */} + @@ -451,7 +504,8 @@ export const EventValidityChange = () => ( {/* Inactive — manual (user) */} - + {/* `validity_changed` leaf; manual -> user actor. */} + @@ -467,7 +521,8 @@ export const EventValidityChange = () => ( {/* Unknown — automated (GitHub), AlertIcon on attention (amber) */} - + {/* `validity_changed` leaf; automated -> GitHub system actor -> 'user'. */} + @@ -483,7 +538,8 @@ export const EventValidityChange = () => ( {/* Unknown — manual (user) */} - + {/* `validity_changed` leaf; manual -> user actor. */} + @@ -511,6 +567,10 @@ export const EventReport = () => ( {/* Reported — ShieldCheckIcon, default (gray) badge, user actor */} + {/* UNTAGGED: the catalog maps Report -> validity_changed, but + github/primer#6888 lists that mapping as UNCONFIRMED (open item #2). + Emitting an unconfirmed value would bake in a guess, so leave untagged + until the docs confirm the leaf. */} @@ -545,6 +605,8 @@ export const EventMetadata = () => ( {/* Metadata created — GitHub system actor, FileAddedIcon, default (gray) badge */} + {/* UNTAGGED: MetadataCreated is PARKED per github/primer#6888 ('Parked' + section), no catalog leaf. No leaf -> no data-* tag. */} @@ -561,6 +623,8 @@ export const EventMetadata = () => ( {/* Metadata removed — GitHub system actor, FileRemovedIcon, default (gray) badge */} + {/* UNTAGGED: MetadataRemoved is PARKED per github/primer#6888 ('Parked' + section), no catalog leaf. No leaf -> no data-* tag. */} @@ -606,7 +670,8 @@ export const EventClosureRequest = () => ( un-bolded `resolutionText(exemption_request.reason)`. */} - + {/* `dismissal_requested` leaf (DelegatedClosureRequestOpened); user actor. */} + @@ -630,7 +695,8 @@ export const EventClosureRequest = () => ( {/* Requested — requester view: the invisible "Cancel request" button. */} - + {/* `dismissal_requested` leaf; user actor. */} + @@ -651,7 +717,8 @@ export const EventClosureRequest = () => ( {/* Approved — CheckCircleIcon, with a reviewer comment sub-row. */} - + {/* `dismissal_reviewed` leaf; reviewer user actor (six7). */} + @@ -670,7 +737,8 @@ export const EventClosureRequest = () => ( {/* Denied — XIcon */} - + {/* `dismissal_reviewed` leaf; reviewer user actor (six7). */} + @@ -689,6 +757,8 @@ export const EventClosureRequest = () => ( {/* Cancelled — SkipIcon */} + {/* UNTAGGED: Secret Scanning has NO dismissal_cancelled leaf (unlike + Dependabot). No leaf -> no data-* tag. */} @@ -717,6 +787,9 @@ export const EventClosureRequest = () => ( */ export const EventAssignment = () => ( + {/* UNTAGGED (whole group): Assignment is a cross-surface SHARED event, + deliberately kept OUT of the per-surface Secret Scanning catalog. All five + variants below carry no data-* event tag. */} {/* Self-assigned — actor === assignee */} From 1135a786e3eae35b381c8da9cc1feacce95b1b89 Mon Sep 17 00:00:00 2001 From: Jan Maarten <83665577+janmaarten-a11y@users.noreply.github.com> Date: Fri, 24 Jul 2026 12:40:44 -0700 Subject: [PATCH 3/4] refactor(Timeline): stage data-* helper in taxonomy/eventDataAttributes; consume in stories --- ...imeline.code-scanning.features.stories.tsx | 55 +++--- .../Timeline.dependabot.features.stories.tsx | 80 ++++---- .../Timeline.issues.features.stories.tsx | 184 +++++++++--------- ...ne.license-compliance.features.stories.tsx | 81 ++++---- ...eline.secret-scanning.features.stories.tsx | 71 +++---- .../taxonomy/eventDataAttributes.test.ts | 49 +++++ .../Timeline/taxonomy/eventDataAttributes.ts | 44 +++++ packages/react/src/Timeline/taxonomy/index.ts | 1 + 8 files changed, 302 insertions(+), 263 deletions(-) create mode 100644 packages/react/src/Timeline/taxonomy/eventDataAttributes.test.ts create mode 100644 packages/react/src/Timeline/taxonomy/eventDataAttributes.ts diff --git a/packages/react/src/Timeline/Timeline.code-scanning.features.stories.tsx b/packages/react/src/Timeline/Timeline.code-scanning.features.stories.tsx index 1edbc345fb2..f1d8832ed37 100644 --- a/packages/react/src/Timeline/Timeline.code-scanning.features.stories.tsx +++ b/packages/react/src/Timeline/Timeline.code-scanning.features.stories.tsx @@ -26,7 +26,7 @@ import { UserActor, VariantSection, } from './internal/timelineStoryHelpers' -import {CODE_SCANNING_TAXONOMY, actorTypeForLogin, toEventDataAttributes, type CodeScanningEventType} from './taxonomy' +import {eventDataAttributesFor} from './taxonomy' import classes from './Timeline.code-scanning.features.stories.module.css' /** @@ -175,23 +175,14 @@ const SubRow = ({children}: {children: React.ReactNode}) =>
    , + * login?)` helper (`./taxonomy`, primer/react#8180), mirroring the License + * Compliance pilot (primer/react#8216). `category` and `visibility` are derived + * FROM the catalog so the story never hand-maintains them. Pass the row's + * rendered `UserActor` login for USER events (resolves `data-actor-type`); omit + * it for SYSTEM rows so `data-actor-type` is left off entirely. */ -const codeScanningAttrs = (type: CodeScanningEventType, login?: string) => - toEventDataAttributes({ - scope: 'code-scanning', - type, - category: CODE_SCANNING_TAXONOMY[type].category, - visibility: CODE_SCANNING_TAXONOMY[type].visibility, - actorType: login ? actorTypeForLogin(login) : undefined, - }) export default { title: 'Components/Timeline/Events/Code Scanning', @@ -240,7 +231,7 @@ export const EventDetected = () => ( sub-row, and a right-aligned tool-version Label (Timeline.Actions). */} - + @@ -274,7 +265,7 @@ export const EventDetected = () => ( card — `show_timeline_commit?` is false for this event). */} - + @@ -304,7 +295,7 @@ export const EventDetected = () => ( {category}" pill (rendered when the alert has more than one category). */} - + @@ -348,7 +339,7 @@ export const EventFixed = () => ( {/* Fixed — selected ref → SOLID purple shield-check */} - + @@ -363,7 +354,7 @@ export const EventFixed = () => ( {/* Fixed — non-selected ref → bare default check */} - + @@ -381,7 +372,7 @@ export const EventFixed = () => ( carries data-event-type="closed" despite its visual "Fixed" grouping. */} - + @@ -397,7 +388,7 @@ export const EventFixed = () => ( {/* Config deleted — non-selected ref → bare default check */} - + @@ -429,7 +420,7 @@ export const EventClosedByUser = () => ( {/* Closed as false positive — with a resolution-note sub-row */} - + @@ -446,7 +437,7 @@ export const EventClosedByUser = () => ( {/* Closed as used in tests */} - + @@ -462,7 +453,7 @@ export const EventClosedByUser = () => ( {/* Closed as won't fix */} - + @@ -479,7 +470,7 @@ export const EventClosedByUser = () => ( `resolution == :NO_RESOLUTION`. */} - + @@ -506,7 +497,7 @@ export const EventReopened = () => ( - + @@ -541,7 +532,7 @@ export const EventDismissalRequested = () => ( - + @@ -581,7 +572,7 @@ export const EventDismissalReviewed = () => ( {/* Approved — check icon + reviewer-comment sub-row */} - + @@ -598,7 +589,7 @@ export const EventDismissalReviewed = () => ( {/* Denied — x icon */} - + diff --git a/packages/react/src/Timeline/Timeline.dependabot.features.stories.tsx b/packages/react/src/Timeline/Timeline.dependabot.features.stories.tsx index b756a162b32..791cbde6fa0 100644 --- a/packages/react/src/Timeline/Timeline.dependabot.features.stories.tsx +++ b/packages/react/src/Timeline/Timeline.dependabot.features.stories.tsx @@ -29,7 +29,7 @@ import { UserActor, VariantSection, } from './internal/timelineStoryHelpers' -import {actorTypeForLogin, DEPENDABOT_TAXONOMY, toEventDataAttributes, type DependabotEventType} from './taxonomy' +import {eventDataAttributesFor} from './taxonomy' /** * Dependabot alert Timeline event examples (Phase 2 of github/primer#6663). @@ -61,11 +61,11 @@ import {actorTypeForLogin, DEPENDABOT_TAXONOMY, toEventDataAttributes, type Depe * event `data-*` attributes projected from the merged taxonomy module * (`./taxonomy`, primer/react#8180) — the single source of truth for Timeline * event categorization (github/primer#6664, docs github/primer#6888). Each row - * spreads the output of `toEventDataAttributes` via the local `dependabotAttrs` - * helper, which derives `category` / `visibility` FROM the catalog entry - * (`DEPENDABOT_TAXONOMY`) so the stories stay in sync with the catalog, and - * resolves `data-actor-type` at runtime from each row's rendered actor login - * (`actorTypeForLogin`): Dependabot-authored rows resolve to `bot`, user-driven + * spreads the output of the shared `eventDataAttributesFor` helper, which + * derives `category` / `visibility` FROM the catalog entry so the stories stay + * in sync with the catalog, and + * resolves `data-actor-type` at runtime from each row's rendered actor login: + * Dependabot-authored rows resolve to `bot`, user-driven * rows to `user`. The contract per rendered `
  • `: `data-event-scope`, * `data-event-type` (the UNSCOPED leaf), `data-event-category`, * `data-event-visibility` (defaults `primary`), and `data-actor-type`. This @@ -138,23 +138,13 @@ const PushPill = ({sha}: {sha: string}) => ( ) /** - * Local projection of the taxonomy `data-*` contract for this surface. Given a - * Dependabot leaf `type` (and, when the row renders an actor, that actor's - * `login`), it returns the `data-*` attribute set to spread on the - * `Timeline.Item`. `category` and `visibility` come FROM the catalog entry so - * the stories track `DEPENDABOT_TAXONOMY`; `data-actor-type` is resolved at - * runtime from the login (Dependabot-authored rows pass a `…[bot]` login so they - * resolve to `bot`; user-driven rows pass the rendered user login). See - * github/primer#6664 and the taxonomy docs (github/primer#6888). + * The stories below project each row's `data-*` via the shared + * `eventDataAttributesFor('dependabot', , login?)` helper (`./taxonomy`). + * `category` and `visibility` come FROM the catalog entry; `data-actor-type` + * resolves at runtime from the login (Dependabot-authored rows pass a `…[bot]` + * login so they resolve to `bot`; user-driven rows pass the rendered user + * login). See github/primer#6664 and the taxonomy docs (github/primer#6888). */ -const dependabotAttrs = (type: DependabotEventType, login?: string) => - toEventDataAttributes({ - scope: 'dependabot', - type, - category: DEPENDABOT_TAXONOMY[type].category, - visibility: DEPENDABOT_TAXONOMY[type].visibility, - actorType: login ? actorTypeForLogin(login) : undefined, - }) export default { title: 'Components/Timeline/Events/Dependabot', @@ -196,7 +186,7 @@ export const EventOpened = () => ( {/* Opened — no source */} - + @@ -212,7 +202,7 @@ export const EventOpened = () => ( {/* OpenedFromPR — bold `#123` pull-request link (scheme: primary, bold) */} - + @@ -228,7 +218,7 @@ export const EventOpened = () => ( {/* OpenedFromPush — blue push-pill with the 7-char `after` SHA */} - + @@ -261,7 +251,7 @@ export const EventFixed = () => ( {/* Fixed — no source */} - + @@ -277,7 +267,7 @@ export const EventFixed = () => ( {/* FixedViaPR — bold `#123` pull-request link */} - + @@ -293,7 +283,7 @@ export const EventFixed = () => ( {/* FixedViaPush — blue push-pill */} - + @@ -326,7 +316,7 @@ export const EventDismissed = () => ( {/* Manual — risk is tolerable (with an optional dismissal note) */} - + @@ -345,7 +335,7 @@ export const EventDismissed = () => ( {/* Manual — fix started */} - + @@ -361,7 +351,7 @@ export const EventDismissed = () => ( {/* Manual — no bandwidth to fix this */} - + @@ -377,7 +367,7 @@ export const EventDismissed = () => ( {/* Manual — vulnerable code is not actually used */} - + @@ -393,7 +383,7 @@ export const EventDismissed = () => ( {/* Manual — inaccurate */} - + @@ -409,7 +399,7 @@ export const EventDismissed = () => ( {/* Auto — rule-based, no source (with optional rule comment) */} - + @@ -432,7 +422,7 @@ export const EventDismissed = () => ( {/* Auto — from a pull request */} - + @@ -448,7 +438,7 @@ export const EventDismissed = () => ( {/* Auto — from a push */} - + @@ -477,7 +467,7 @@ export const EventReopened = () => ( {/* Manual reopen — user actor */} - + @@ -493,7 +483,7 @@ export const EventReopened = () => ( {/* Reintroduced — no source */} - + @@ -509,7 +499,7 @@ export const EventReopened = () => ( {/* Reintroduced — from a pull request */} - + @@ -525,7 +515,7 @@ export const EventReopened = () => ( {/* Reintroduced — from a push */} - + @@ -541,7 +531,7 @@ export const EventReopened = () => ( {/* Auto-reopened — rule change (with optional rule comment) */} - + @@ -596,7 +586,7 @@ export const EventDismissalRequest = () => ( place that right-aligned control in the `Timeline.Actions` slot. */} - + @@ -620,7 +610,7 @@ export const EventDismissalRequest = () => ( {/* Dismissal approved — circle user actor, success/check badge */} - + @@ -637,7 +627,7 @@ export const EventDismissalRequest = () => ( {/* Dismissal denied — circle user actor, danger/x badge */} - + @@ -655,7 +645,7 @@ export const EventDismissalRequest = () => ( "x")` (no bg) → a bare default badge, not a danger/emphasis one. */} - + diff --git a/packages/react/src/Timeline/Timeline.issues.features.stories.tsx b/packages/react/src/Timeline/Timeline.issues.features.stories.tsx index 82d1c27d627..934b22def4f 100644 --- a/packages/react/src/Timeline/Timeline.issues.features.stories.tsx +++ b/packages/react/src/Timeline/Timeline.issues.features.stories.tsx @@ -40,8 +40,7 @@ import Octicon from '../Octicon' import Token from '../Token' import classes from './Timeline.issues.features.stories.module.css' import {BoldLink, Examples, MutedTime, UserActor, VariantSection} from './internal/timelineStoryHelpers' -import {actorTypeForLogin, ISSUE_TAXONOMY, toEventDataAttributes} from './taxonomy' -import type {IssueEventType} from './taxonomy' +import {eventDataAttributesFor} from './taxonomy' /** * Issue Timeline event examples (Phase 2 of github/primer#6663). @@ -107,21 +106,14 @@ export default { } as Meta> /** - * Serialize the taxonomy `data-*` contract (github/primer#6664) for one Issues - * event row. `type` is an ISSUE_TAXONOMY leaf; `category` + `visibility` are read - * from the catalog, so metadata leaves (labels, assignees, milestones, project - * fields, issue types, rename) pick up `visibility: 'auditOnly'` automatically. - * Pass the row's rendered actor login so `data-actor-type` resolves (every Issues - * event carries an actor). + * The stories below serialize each Issues row's `data-*` contract + * (github/primer#6664) via the shared `eventDataAttributesFor('issue', , + * login?)` helper (`./taxonomy`). `category` + `visibility` are read from the + * catalog, so metadata leaves (labels, assignees, milestones, project fields, + * issue types, rename) pick up `visibility: 'auditOnly'` automatically. Pass the + * row's rendered actor login so `data-actor-type` resolves (every Issues event + * carries an actor). */ -const issueAttrs = (type: IssueEventType, login?: string) => - toEventDataAttributes({ - scope: 'issue', - type, - category: ISSUE_TAXONOMY[type].category, - visibility: ISSUE_TAXONOMY[type].visibility, - actorType: login ? actorTypeForLogin(login) : undefined, - }) /** * The Closed event group — `IssueTimeline.eventClosed` (audit § 2). @@ -137,7 +129,7 @@ export const EventClosed = () => ( {/* Closed as completed */} - + @@ -158,7 +150,7 @@ export const EventClosed = () => ( (portable for docs copy-paste; matches production `TimelineRow`). */} - + ( {/* Closed via pull request */} - + @@ -204,7 +196,7 @@ export const EventClosed = () => ( {/* Closed via commit */} - + @@ -229,7 +221,7 @@ export const EventClosed = () => ( equivalent for github-ui's `ProjectV2` closer link. */} - + @@ -252,7 +244,7 @@ export const EventClosed = () => ( composed here from Primer primitives. */} - + ( {/* Closed with no state reason */} - + @@ -312,7 +304,7 @@ export const EventState = () => ( {/* Reopened — open (green) badge via useIssueState (ReopenedEvent.tsx) */} - + @@ -329,7 +321,7 @@ export const EventState = () => ( plain inline Link (the audit shows it bold; live code is canonical). */} - + @@ -348,7 +340,7 @@ export const EventState = () => ( {/* Pinned */} - + @@ -364,7 +356,7 @@ export const EventState = () => ( {/* Unpinned */} - + @@ -381,7 +373,7 @@ export const EventState = () => ( the resulting discussion by number. */} - + @@ -427,7 +419,7 @@ export const EventReferences = () => ( open PR state icon inline before the PR title). */} - + @@ -451,7 +443,7 @@ export const EventReferences = () => ( icon is behind the live code here. */} - + @@ -471,7 +463,7 @@ export const EventReferences = () => ( inline (showAgoTimestamp={false}) and the commit card is sub-content. */} - + @@ -502,7 +494,7 @@ export const EventReferences = () => ( {/* Multiple commit references — same event, pluralized copy + N cards. */} - + @@ -558,7 +550,7 @@ export const EventDuplicates = () => ( IssueLink uses the open (green) state icon. */} - + @@ -661,7 +653,7 @@ export const EventModeration = () => ( {/* User blocked (permanent) */} - + @@ -677,7 +669,7 @@ export const EventModeration = () => ( {/* User temporarily blocked */} - + @@ -693,7 +685,7 @@ export const EventModeration = () => ( {/* Comment pinned — IssueCommentPinnedEvent.tsx links the pinned comment. */} - + @@ -712,7 +704,7 @@ export const EventModeration = () => ( {/* Comment unpinned */} - + @@ -743,7 +735,7 @@ export const EventIssueTypes = () => ( {/* Type added */} - + @@ -773,7 +765,7 @@ export const EventIssueTypes = () => ( {/* Type removed */} - + @@ -803,7 +795,7 @@ export const EventIssueTypes = () => ( {/* Type changed */} - + @@ -863,7 +855,7 @@ export const EventIssueHierarchy = () => ( {/* Sub-issue added (single) */} - + @@ -888,7 +880,7 @@ export const EventIssueHierarchy = () => ( {/* Sub-issue added (multiple) — plural copy + N reference rows */} - + @@ -920,7 +912,7 @@ export const EventIssueHierarchy = () => ( {/* Sub-issue removed (single) */} - + @@ -945,7 +937,7 @@ export const EventIssueHierarchy = () => ( {/* Sub-issues removed (multiple) */} - + @@ -977,7 +969,7 @@ export const EventIssueHierarchy = () => ( {/* Parent issue added (single) */} - + @@ -1002,7 +994,7 @@ export const EventIssueHierarchy = () => ( {/* Parent issues added (multiple) */} - + @@ -1034,7 +1026,7 @@ export const EventIssueHierarchy = () => ( {/* Parent issue removed (single) */} - + @@ -1059,7 +1051,7 @@ export const EventIssueHierarchy = () => ( {/* Parent issues removed (multiple) */} - + @@ -1107,7 +1099,7 @@ export const EventDependencies = () => ( {/* Blocked by (single) */} - + @@ -1132,7 +1124,7 @@ export const EventDependencies = () => ( {/* Blocked by (multiple) — count in copy + N rows */} - + @@ -1164,7 +1156,7 @@ export const EventDependencies = () => ( {/* Blocked by removed (single) */} - + @@ -1189,7 +1181,7 @@ export const EventDependencies = () => ( {/* Blocked by removed (multiple) */} - + @@ -1221,7 +1213,7 @@ export const EventDependencies = () => ( {/* Blocking (single) */} - + @@ -1246,7 +1238,7 @@ export const EventDependencies = () => ( {/* Blocking (multiple) */} - + @@ -1278,7 +1270,7 @@ export const EventDependencies = () => ( {/* Blocking removed (single) */} - + @@ -1303,7 +1295,7 @@ export const EventDependencies = () => ( {/* Blocking removed (multiple) */} - + @@ -1355,7 +1347,7 @@ export const EventIssueFields = () => ( {/* Set text field */} - + @@ -1376,7 +1368,7 @@ export const EventIssueFields = () => ( {/* Set number field */} - + @@ -1397,7 +1389,7 @@ export const EventIssueFields = () => ( {/* Set date field */} - + @@ -1418,7 +1410,7 @@ export const EventIssueFields = () => ( {/* Set single-select field — value is a colored token */} - + @@ -1452,7 +1444,7 @@ export const EventIssueFields = () => ( {/* Changed text field */} - + @@ -1473,7 +1465,7 @@ export const EventIssueFields = () => ( {/* Changed number field */} - + @@ -1494,7 +1486,7 @@ export const EventIssueFields = () => ( {/* Changed date field */} - + @@ -1515,7 +1507,7 @@ export const EventIssueFields = () => ( {/* Changed single-select field */} - + @@ -1546,7 +1538,7 @@ export const EventIssueFields = () => ( {/* Cleared text field — no value */} - + @@ -1563,7 +1555,7 @@ export const EventIssueFields = () => ( {/* Cleared number field */} - + @@ -1580,7 +1572,7 @@ export const EventIssueFields = () => ( {/* Cleared date field */} - + @@ -1597,7 +1589,7 @@ export const EventIssueFields = () => ( {/* Cleared single-select field */} - + @@ -1614,7 +1606,7 @@ export const EventIssueFields = () => ( {/* Rollup: updated only — multiple field updates collapsed into one row */} - + @@ -1639,7 +1631,7 @@ export const EventIssueFields = () => ( {/* Rollup: removed only */} - + @@ -1658,7 +1650,7 @@ export const EventIssueFields = () => ( {/* Rollup: updated and also removed — combined row joined by "and also" */} - + @@ -1710,7 +1702,7 @@ export const EventProject = () => ( {/* Added to project */} - + @@ -1731,7 +1723,7 @@ export const EventProject = () => ( {/* Removed from project */} - + @@ -1755,7 +1747,7 @@ export const EventProject = () => ( strings are PLAIN TEXT (not bold). Both forms shown under one caption. */} - + @@ -1769,7 +1761,7 @@ export const EventProject = () => ( - + @@ -1809,7 +1801,7 @@ export const EventLabels = () => ( {/* Label added */} - + @@ -1838,7 +1830,7 @@ export const EventLabels = () => ( {/* Label removed */} - + @@ -1929,7 +1921,7 @@ export const EventTitle = () => ( {/* Title changed */} - + @@ -1964,7 +1956,7 @@ export const EventMilestones = () => ( {/* Added to milestone */} - + @@ -1984,7 +1976,7 @@ export const EventMilestones = () => ( {/* Removed from milestone */} - + @@ -2023,7 +2015,7 @@ export const EventAssignments = () => ( {/* Self-assigned */} - + @@ -2039,7 +2031,7 @@ export const EventAssignments = () => ( {/* Assigned someone else — assignee is a bold link, no avatar. */} - + @@ -2055,7 +2047,7 @@ export const EventAssignments = () => ( {/* Assigned multiple — joined with "and". */} - + @@ -2073,7 +2065,7 @@ export const EventAssignments = () => ( {/* Self-unassigned */} - + @@ -2089,7 +2081,7 @@ export const EventAssignments = () => ( {/* Unassigned someone else */} - + @@ -2105,7 +2097,7 @@ export const EventAssignments = () => ( {/* Unassigned multiple — joined with "and". */} - + @@ -2142,7 +2134,7 @@ export const EventLockUnlock = () => ( too heated). */} - + @@ -2152,7 +2144,7 @@ export const EventLockUnlock = () => ( - + @@ -2162,7 +2154,7 @@ export const EventLockUnlock = () => ( - + @@ -2172,7 +2164,7 @@ export const EventLockUnlock = () => ( - + @@ -2188,7 +2180,7 @@ export const EventLockUnlock = () => ( {/* Locked (no reason) */} - + @@ -2204,7 +2196,7 @@ export const EventLockUnlock = () => ( {/* Unlocked — UnlockIcon badge (not LockIcon). */} - + @@ -2235,7 +2227,7 @@ export const EventCommentDeleted = () => ( {/* Comment deleted */} - + @@ -2273,7 +2265,7 @@ export const EventCrossReferences = () => ( {/* Mentioned from an issue */} - + @@ -2298,7 +2290,7 @@ export const EventCrossReferences = () => ( {/* Mentioned from a pull request */} - + @@ -2323,7 +2315,7 @@ export const EventCrossReferences = () => ( {/* Linked a closing pull request */} - + diff --git a/packages/react/src/Timeline/Timeline.license-compliance.features.stories.tsx b/packages/react/src/Timeline/Timeline.license-compliance.features.stories.tsx index 20c4acca04b..38dff3326fe 100644 --- a/packages/react/src/Timeline/Timeline.license-compliance.features.stories.tsx +++ b/packages/react/src/Timeline/Timeline.license-compliance.features.stories.tsx @@ -19,13 +19,7 @@ import {Button} from '../Button' import Link from '../Link' import Octicon from '../Octicon' import {EventSubRow, Examples, MutedTime, UserActor, VariantSection} from './internal/timelineStoryHelpers' -import { - actorTypeForLogin, - LICENSE_COMPLIANCE_SCOPE, - LICENSE_COMPLIANCE_TAXONOMY, - toEventDataAttributes, - type LicenseComplianceEventType, -} from './taxonomy' +import {eventDataAttributesFor} from './taxonomy' import classes from './Timeline.license-compliance.features.stories.module.css' /** @@ -78,11 +72,10 @@ import classes from './Timeline.license-compliance.features.stories.module.css' * below now carries the event `data-*` attributes projected from the merged * taxonomy module (`./taxonomy`, primer/react#8180) — the single source of truth * for Timeline event categorization (github/primer#6664, docs github/primer#6888). - * Each row spreads the output of `toEventDataAttributes` via the local `lcAttrs` - * helper, which derives `category` / `visibility` FROM the catalog entry - * (`LICENSE_COMPLIANCE_TAXONOMY`) so the stories stay in sync with the catalog, - * and resolves `data-actor-type` at runtime from each row's rendered actor login - * (`actorTypeForLogin`). The contract per rendered `
  • `: `data-event-scope`, + * Each row spreads the output of the shared `eventDataAttributesFor` helper, + * which derives `category` / `visibility` FROM the catalog entry so the stories + * stay in sync with the catalog, and resolves `data-actor-type` at runtime from + * each row's rendered actor login. The contract per rendered `
  • `: `data-event-scope`, * `data-event-type` (the UNSCOPED leaf), `data-event-category`, * `data-event-visibility` (defaults `primary`), and `data-actor-type` (OMITTED * when the row renders no actor, e.g. the synthetic `appeared_in_branch`). @@ -160,22 +153,12 @@ const PolicyLink = ({href = '../../settings/security_analysis'}: {href?: string} ) /** - * Local projection of the taxonomy `data-*` contract for this surface. Given a - * License Compliance leaf `type` (and, when the row renders an actor, that - * actor's `login`), it returns the `data-*` attribute set to spread on the - * `Timeline.Item`. `category` and `visibility` come FROM the catalog entry so - * the stories track `LICENSE_COMPLIANCE_TAXONOMY`; `data-actor-type` is resolved - * at runtime from the login and omitted when no login is passed (actor-less - * rows). See github/primer#6664 and the taxonomy docs (github/primer#6888). + * The stories below project each row's `data-*` via the shared + * `eventDataAttributesFor('license-compliance', , login?)` helper (see + * `./taxonomy`). `category` and `visibility` come from the catalog entry; + * `data-actor-type` resolves from the login and is omitted for actor-less rows. + * See github/primer#6664 and the taxonomy docs (github/primer#6888). */ -const lcAttrs = (type: LicenseComplianceEventType, login?: string) => - toEventDataAttributes({ - scope: LICENSE_COMPLIANCE_SCOPE, - type, - category: LICENSE_COMPLIANCE_TAXONOMY[type].category, - visibility: LICENSE_COMPLIANCE_TAXONOMY[type].visibility, - actorType: login ? actorTypeForLogin(login) : undefined, - }) export default { title: 'Components/Timeline/Events/License Compliance', @@ -223,7 +206,7 @@ export const EventOpened = () => ( {/* Opened — license-compliance system bot, ShieldIcon on success (green) */} - + @@ -259,7 +242,7 @@ export const EventAppearedInBranch = () => ( badge, with a BranchName pill. PR sub-row is dormant (see group doc). */} - + @@ -299,7 +282,7 @@ export const EventReviewRequested = () => ( {/* Requested to close — no reason, no PR, no comment */} - + @@ -315,7 +298,7 @@ export const EventReviewRequested = () => ( {/* Requested to close as {reason}, with a requester comment sub-row */} - + @@ -333,7 +316,7 @@ export const EventReviewRequested = () => ( "Review request" button (latest request only) in Timeline.Actions. */} - + @@ -367,7 +350,7 @@ export const EventReviewApproved = () => ( {/* Approved closure request */} - + @@ -383,7 +366,7 @@ export const EventReviewApproved = () => ( {/* Approved closure request — with reviewer comment */} - + @@ -411,7 +394,7 @@ export const EventReviewDenied = () => ( {/* Denied closure request */} - + @@ -427,7 +410,7 @@ export const EventReviewDenied = () => ( {/* Denied closure request — with reviewer comment */} - + @@ -459,7 +442,9 @@ export const EventReviewExpired = () => ( {/* Request to close expired — license-compliance system bot, automatic expiry */} - + @@ -489,7 +474,7 @@ export const EventExceptionAdded = () => ( {/* Full shape — package + policy link + repo name */} - + @@ -507,7 +492,7 @@ export const EventExceptionAdded = () => ( {/* Fallback shape — body missing package info */} - + @@ -536,7 +521,7 @@ export const EventLicensesAdded = () => ( {/* Full shape — licenses list + policy link + repo name */} - + @@ -554,7 +539,7 @@ export const EventLicensesAdded = () => ( {/* Fallback shape — body missing licenses array */} - + @@ -587,7 +572,7 @@ export const EventClosed = () => ( {/* Closed as amendment — with a closing comment */} - + @@ -604,7 +589,7 @@ export const EventClosed = () => ( {/* Closed as private package */} - + @@ -620,7 +605,7 @@ export const EventClosed = () => ( {/* Closed as inaccurate license */} - + @@ -636,7 +621,7 @@ export const EventClosed = () => ( {/* Closed as policy edited */} - + @@ -652,7 +637,7 @@ export const EventClosed = () => ( {/* Closed as fixed */} - + @@ -668,7 +653,7 @@ export const EventClosed = () => ( {/* Closed as outdated — resolution Outdated */} - + @@ -684,7 +669,7 @@ export const EventClosed = () => ( {/* Closed this alert — default (no reason / resolution) */} - + diff --git a/packages/react/src/Timeline/Timeline.secret-scanning.features.stories.tsx b/packages/react/src/Timeline/Timeline.secret-scanning.features.stories.tsx index c7e48924ab0..435cd52139d 100644 --- a/packages/react/src/Timeline/Timeline.secret-scanning.features.stories.tsx +++ b/packages/react/src/Timeline/Timeline.secret-scanning.features.stories.tsx @@ -20,12 +20,7 @@ import { import {Button} from '../Button' import Octicon from '../Octicon' import {EventSubRow, MutedTime, RealisticTimeline, UserActor, VariantSection} from './internal/timelineStoryHelpers' -import { - actorTypeForLogin, - SECRET_SCANNING_TAXONOMY, - toEventDataAttributes, - type SecretScanningEventType, -} from './taxonomy' +import {eventDataAttributesFor} from './taxonomy' /** * Secret Scanning alert Timeline event examples (Phase 2 of github/primer#6663). @@ -63,7 +58,8 @@ import { * `Timeline.Item` below carries the `data-*` event contract * (`data-event-scope` / `data-event-type` / `data-event-category` / * `data-event-visibility` / `data-actor-type`), projected from the shared - * `SECRET_SCANNING_TAXONOMY` catalog via `toEventDataAttributes`. This mirrors + * `SECRET_SCANNING_TAXONOMY` catalog via the `eventDataAttributesFor` helper. + * This mirrors * the License Compliance pilot (primer/react#8216) and consumes the merged * taxonomy module (#8180) documented in github/primer#6888. * @@ -111,20 +107,11 @@ import { const SIX7_AVATAR = 'https://avatars.githubusercontent.com/u/4548309?v=4' const HUBOT_AVATAR = 'https://avatars.githubusercontent.com/u/480938?v=4' -// Projects a cataloged Secret Scanning leaf into the event `data-*` contract -// (github/primer#6664), same shape as the License Compliance pilot -// (primer/react#8216). `category` and `visibility` are read FROM the catalog so -// the story never re-declares the taxonomy; `login` is the actor rendered in the -// row, resolved to `data-actor-type` via `actorTypeForLogin`. Omit `login` for -// actor-less rows so no `data-actor-type` is emitted. -const secretScanningAttrs = (type: SecretScanningEventType, login?: string) => - toEventDataAttributes({ - scope: 'secret-scanning', - type, - category: SECRET_SCANNING_TAXONOMY[type].category, - visibility: SECRET_SCANNING_TAXONOMY[type].visibility, - actorType: login ? actorTypeForLogin(login) : undefined, - }) +// The stories below project each row's `data-*` via the shared +// `eventDataAttributesFor('secret-scanning', , login?)` helper: `category` +// and `visibility` are read FROM the catalog so the story never re-declares the +// taxonomy; `login` is the actor rendered in the row, resolved to +// `data-actor-type`. Omit `login` for actor-less rows so none is emitted. export default { title: 'Components/Timeline/Events/Secret Scanning', @@ -171,7 +158,7 @@ export const EventCreated = () => ( actorTypeForLogin('GitHub') classifies it as 'user' -> data-actor-type='user'. FLAGGED: whether a system identity should classify as user vs bot is worth confirming (see report). */} - + @@ -212,7 +199,7 @@ export const EventResolution = () => ( {/* `closed` leaf (Resolution, any non-reopened resolution.type); user actor. */} - + @@ -232,7 +219,7 @@ export const EventResolution = () => ( {/* `closed` leaf; user actor. */} - + @@ -249,7 +236,7 @@ export const EventResolution = () => ( {/* `closed` leaf; user actor. */} - + @@ -266,7 +253,7 @@ export const EventResolution = () => ( {/* `closed` leaf; user actor. */} - + @@ -283,7 +270,7 @@ export const EventResolution = () => ( {/* `closed` leaf; user actor. */} - + @@ -300,7 +287,7 @@ export const EventResolution = () => ( {/* `closed` leaf; user actor. */} - + @@ -317,7 +304,7 @@ export const EventResolution = () => ( {/* `closed` leaf; user actor. */} - + @@ -340,7 +327,7 @@ export const EventResolution = () => ( {/* Context row: a `closed` leaf preceding the reopen, tagged in place like the seven dedicated "Closed as …" variants; user actor. */} - + @@ -352,7 +339,7 @@ export const EventResolution = () => ( {/* `reopened` leaf (Resolution, resolution.type === 'reopened'); user actor. */} - + @@ -383,7 +370,7 @@ export const EventBypass = () => ( {/* `bypassed` leaf; user actor. */} - + @@ -454,7 +441,7 @@ export const EventValidityChange = () => ( {/* `validity_changed` leaf; automated -> GitHub system actor -> 'user'. */} - + @@ -471,7 +458,7 @@ export const EventValidityChange = () => ( {/* `validity_changed` leaf; manual -> user actor. */} - + @@ -488,7 +475,7 @@ export const EventValidityChange = () => ( {/* `validity_changed` leaf; automated -> GitHub system actor -> 'user'. */} - + @@ -505,7 +492,7 @@ export const EventValidityChange = () => ( {/* `validity_changed` leaf; manual -> user actor. */} - + @@ -522,7 +509,7 @@ export const EventValidityChange = () => ( {/* `validity_changed` leaf; automated -> GitHub system actor -> 'user'. */} - + @@ -539,7 +526,7 @@ export const EventValidityChange = () => ( {/* `validity_changed` leaf; manual -> user actor. */} - + @@ -671,7 +658,7 @@ export const EventClosureRequest = () => ( {/* `dismissal_requested` leaf (DelegatedClosureRequestOpened); user actor. */} - + @@ -696,7 +683,7 @@ export const EventClosureRequest = () => ( {/* `dismissal_requested` leaf; user actor. */} - + @@ -718,7 +705,7 @@ export const EventClosureRequest = () => ( {/* `dismissal_reviewed` leaf; reviewer user actor (six7). */} - + @@ -738,7 +725,7 @@ export const EventClosureRequest = () => ( {/* `dismissal_reviewed` leaf; reviewer user actor (six7). */} - + diff --git a/packages/react/src/Timeline/taxonomy/eventDataAttributes.test.ts b/packages/react/src/Timeline/taxonomy/eventDataAttributes.test.ts new file mode 100644 index 00000000000..4325a857260 --- /dev/null +++ b/packages/react/src/Timeline/taxonomy/eventDataAttributes.test.ts @@ -0,0 +1,49 @@ +/** + * Tests for the consolidated `data-*` projection `eventDataAttributesFor` — the + * shared helper the per-surface Storybook stories consume. Covers actor + * resolution (bot vs user), actor omission, `auditOnly` pass-through, the + * `hasActor: false` actor suppression, and catalog-derived category. + */ + +import {describe, it, expect} from 'vitest' +import {eventDataAttributesFor} from './eventDataAttributes' + +describe('eventDataAttributesFor', () => { + it('resolves a bot login to data-actor-type "bot"', () => { + const attrs = eventDataAttributesFor('dependabot', 'opened', 'dependabot[bot]') + expect(attrs['data-actor-type']).toBe('bot') + expect(attrs['data-event-scope']).toBe('dependabot') + expect(attrs['data-event-type']).toBe('opened') + }) + + it('resolves a plain user login to data-actor-type "user"', () => { + const attrs = eventDataAttributesFor('issue', 'closed', 'monalisa') + expect(attrs['data-actor-type']).toBe('user') + }) + + it('omits data-actor-type entirely when no login is passed', () => { + const attrs = eventDataAttributesFor('issue', 'closed') + expect('data-actor-type' in attrs).toBe(false) + }) + + it('preserves auditOnly visibility for a metadata leaf', () => { + const attrs = eventDataAttributesFor('issue', 'labeled', 'monalisa') + expect(attrs['data-event-visibility']).toBe('auditOnly') + expect(attrs['data-event-category']).toBe('metadata') + }) + + it('defaults visibility to primary for a non-metadata leaf', () => { + const attrs = eventDataAttributesFor('license-compliance', 'review_requested', 'monalisa') + expect(attrs['data-event-visibility']).toBe('primary') + }) + + it('never emits data-actor-type for a hasActor:false leaf, even with a login', () => { + const attrs = eventDataAttributesFor('license-compliance', 'appeared_in_branch', 'monalisa') + expect('data-actor-type' in attrs).toBe(false) + }) + + it('derives data-event-category from the catalog entry', () => { + const attrs = eventDataAttributesFor('license-compliance', 'review_requested') + expect(attrs['data-event-category']).toBe('reviews') + }) +}) diff --git a/packages/react/src/Timeline/taxonomy/eventDataAttributes.ts b/packages/react/src/Timeline/taxonomy/eventDataAttributes.ts new file mode 100644 index 00000000000..612f715fab6 --- /dev/null +++ b/packages/react/src/Timeline/taxonomy/eventDataAttributes.ts @@ -0,0 +1,44 @@ +/** + * Consolidated `data-*` projection for cataloged Timeline surfaces. + * + * One type-safe generic that replaces the per-surface inline wrappers the + * Storybook stories used to declare (`lcAttrs`, `codeScanningAttrs`, + * `dependabotAttrs`, `secretScanningAttrs`, `issueAttrs`). Given a cataloged + * `scope` and one of that scope's leaf `type`s, it reads the catalog entry from + * {@link SURFACE_TAXONOMIES} and serializes the event `data-*` contract via + * {@link toEventDataAttributes} (github/primer#6664). `category` and + * `visibility` come from the catalog, so a consumer never re-declares the + * taxonomy; `data-actor-type` resolves at runtime from the row's actor `login`. + * + * Not part of the public `@primer/react` export surface — this stays beside the + * Timeline component for stories and the planned playground to consume. + */ + +import {SURFACE_TAXONOMIES, toEventDataAttributes} from './eventTaxonomy' +import type {CatalogedScope, EventDataAttributes, EventTaxonomyEntry} from './eventTaxonomy' +import {actorTypeForLogin} from './actorType' + +/** + * Project a cataloged `(scope, type)` pair (plus the row's optional actor + * `login`) into the event `data-*` attribute set. + * + * `category` and `visibility` are read from the catalog entry, so `auditOnly` + * metadata leaves keep their visibility automatically. `data-actor-type` is + * emitted only when the leaf is actor-capable (`hasActor: true`) AND a `login` + * is supplied; a structurally actor-less leaf (`hasActor: false`) never emits + * `data-actor-type`, regardless of any login passed. + */ +export function eventDataAttributesFor( + scope: S, + type: keyof (typeof SURFACE_TAXONOMIES)[S] & string, + login?: string, +): EventDataAttributes { + const entry = SURFACE_TAXONOMIES[scope][type] as EventTaxonomyEntry + return toEventDataAttributes({ + scope, + type, + category: entry.category, + visibility: entry.visibility, + actorType: entry.hasActor && login ? actorTypeForLogin(login) : undefined, + }) +} diff --git a/packages/react/src/Timeline/taxonomy/index.ts b/packages/react/src/Timeline/taxonomy/index.ts index 560bdd7f50c..c72581fe623 100644 --- a/packages/react/src/Timeline/taxonomy/index.ts +++ b/packages/react/src/Timeline/taxonomy/index.ts @@ -18,3 +18,4 @@ export * from './surfaces' export * from './actorType' export * from './eventCategories' export * from './eventTaxonomy' +export * from './eventDataAttributes' From e5e13b9470304c9c8b43cfff4af4daf5997fd7e9 Mon Sep 17 00:00:00 2001 From: Jan Maarten <83665577+janmaarten-a11y@users.noreply.github.com> Date: Fri, 24 Jul 2026 12:55:10 -0700 Subject: [PATCH 4/4] feat(Timeline): add SS reported leaf, classify GitHub actor as bot, drop stray Issues draft row --- .../Timeline.issues.features.stories.tsx | 26 +++------------ ...eline.secret-scanning.features.stories.tsx | 32 ++++++++++--------- .../react/src/Timeline/taxonomy/actorType.ts | 5 +++ .../Timeline/taxonomy/eventTaxonomy.test.ts | 2 +- .../src/Timeline/taxonomy/eventTaxonomy.ts | 4 ++- 5 files changed, 30 insertions(+), 39 deletions(-) diff --git a/packages/react/src/Timeline/Timeline.issues.features.stories.tsx b/packages/react/src/Timeline/Timeline.issues.features.stories.tsx index 934b22def4f..94300951af4 100644 --- a/packages/react/src/Timeline/Timeline.issues.features.stories.tsx +++ b/packages/react/src/Timeline/Timeline.issues.features.stories.tsx @@ -14,7 +14,6 @@ import { GitCommitIcon, GitPullRequestIcon, IssueClosedIcon, - IssueDraftIcon, IssueOpenedIcon, IssueReopenedIcon, IssueTrackedByIcon, @@ -385,23 +384,6 @@ export const EventState = () => ( - - {/* Converted from draft */} - - - {/* Untagged: a metadata/auditOnly event that ISSUE_TAXONOMY does not yet enumerate as a distinct leaf (candidate leaf to add in a follow-up, not a defect). Emitting a data-event-type without a real catalog leaf would break the taxonomy single source of truth. */} - - - - - - - {'converted this from a draft issue '} - - - - - ) @@ -574,7 +556,7 @@ export const EventDuplicates = () => ( {/* Marked as a duplicate of this issue — no right controls. */} - {/* Untagged: a metadata/auditOnly event that ISSUE_TAXONOMY does not yet enumerate as a distinct leaf (the catalog has only 'marked_as_duplicate'); candidate leaf to add in a follow-up, not a defect. */} + {/* Untagged: verified against the redesign prototype (github/prototyping janmaarten-a11y/timeline-redesign-v1) as an intentional exclusion, this variant is not modeled as a distinct catalog leaf (the catalog has only 'marked_as_duplicate'); no leaf -> no data-* tag. */} @@ -597,7 +579,7 @@ export const EventDuplicates = () => ( {/* Unmarked this as a duplicate of . */} - {/* Untagged: a metadata/auditOnly event that ISSUE_TAXONOMY does not yet enumerate as a distinct leaf (the catalog has only 'marked_as_duplicate'); candidate leaf to add in a follow-up, not a defect. */} + {/* Untagged: verified against the redesign prototype (github/prototyping janmaarten-a11y/timeline-redesign-v1) as an intentional exclusion, this variant is not modeled as a distinct catalog leaf (the catalog has only 'marked_as_duplicate'); no leaf -> no data-* tag. */} @@ -619,7 +601,7 @@ export const EventDuplicates = () => ( {/* Unmarked as a duplicate of this issue. */} - {/* Untagged: a metadata/auditOnly event that ISSUE_TAXONOMY does not yet enumerate as a distinct leaf (the catalog has only 'marked_as_duplicate'); candidate leaf to add in a follow-up, not a defect. */} + {/* Untagged: verified against the redesign prototype (github/prototyping janmaarten-a11y/timeline-redesign-v1) as an intentional exclusion, this variant is not modeled as a distinct catalog leaf (the catalog has only 'marked_as_duplicate'); no leaf -> no data-* tag. */} @@ -1860,7 +1842,7 @@ export const EventLabels = () => ( with "and" between them. */} - {/* Untagged: a metadata/auditOnly rolled-up event (labeled + unlabeled in one row) that ISSUE_TAXONOMY does not yet enumerate as a distinct leaf; candidate leaf to add in a follow-up, not a defect. */} + {/* Untagged: a rolled-up event (labeled + unlabeled in one row) verified against the redesign prototype (github/prototyping janmaarten-a11y/timeline-redesign-v1) as an intentional exclusion, not modeled as a distinct catalog leaf; no leaf -> no data-* tag. */} diff --git a/packages/react/src/Timeline/Timeline.secret-scanning.features.stories.tsx b/packages/react/src/Timeline/Timeline.secret-scanning.features.stories.tsx index 435cd52139d..2013d1a86c9 100644 --- a/packages/react/src/Timeline/Timeline.secret-scanning.features.stories.tsx +++ b/packages/react/src/Timeline/Timeline.secret-scanning.features.stories.tsx @@ -154,10 +154,8 @@ export const EventCreated = () => ( {/* `detected` leaf. Renders the GitHub SYSTEM-IDENTITY actor - (``); GitHub is not in the bot set, so - actorTypeForLogin('GitHub') classifies it as 'user' -> data-actor-type='user'. - FLAGGED: whether a system identity should classify as user vs bot is - worth confirming (see report). */} + (``); `github` is in the bot set, so + actorTypeForLogin('GitHub') classifies it as 'bot' -> data-actor-type='bot'. */} @@ -389,7 +387,9 @@ export const EventBypass = () => ( {/* UNTAGGED: the delegated-BYPASS request/approve flow has no catalog leaf. The catalog's dismissal_requested / dismissal_reviewed cover the - delegated-CLOSURE flow, a different thing. No leaf -> no data-* tag. */} + delegated-CLOSURE flow, a different thing. Verified against the redesign + prototype (github/prototyping janmaarten-a11y/timeline-redesign-v1) as an + intentional exclusion. No leaf -> no data-* tag. */} @@ -407,7 +407,8 @@ export const EventBypass = () => ( {/* UNTAGGED: delegated-BYPASS flow, no catalog leaf (see the request - variant above). No leaf -> no data-* tag. */} + variant above), verified against the redesign prototype as an + intentional exclusion. No leaf -> no data-* tag. */} @@ -440,7 +441,7 @@ export const EventValidityChange = () => ( {/* Active — automated (GitHub), AlertIcon on danger (red) */} - {/* `validity_changed` leaf; automated -> GitHub system actor -> 'user'. */} + {/* `validity_changed` leaf; automated -> GitHub system actor -> 'bot'. */} @@ -474,7 +475,7 @@ export const EventValidityChange = () => ( {/* Inactive — automated (GitHub), SkipIcon on default (gray) */} - {/* `validity_changed` leaf; automated -> GitHub system actor -> 'user'. */} + {/* `validity_changed` leaf; automated -> GitHub system actor -> 'bot'. */} @@ -508,7 +509,7 @@ export const EventValidityChange = () => ( {/* Unknown — automated (GitHub), AlertIcon on attention (amber) */} - {/* `validity_changed` leaf; automated -> GitHub system actor -> 'user'. */} + {/* `validity_changed` leaf; automated -> GitHub system actor -> 'bot'. */} @@ -554,11 +555,10 @@ export const EventReport = () => ( {/* Reported — ShieldCheckIcon, default (gray) badge, user actor */} - {/* UNTAGGED: the catalog maps Report -> validity_changed, but - github/primer#6888 lists that mapping as UNCONFIRMED (open item #2). - Emitting an unconfirmed value would bake in a guess, so leave untagged - until the docs confirm the leaf. */} - + {/* `reported` leaf (findings). Live AlertTimeline.tsx `TimelineEventType.Report` + does NOT pass `isGitHubActor`, so the actor is the USER — resolves to + data-actor-type='user'. */} + @@ -745,7 +745,9 @@ export const EventClosureRequest = () => ( {/* UNTAGGED: Secret Scanning has NO dismissal_cancelled leaf (unlike - Dependabot). No leaf -> no data-* tag. */} + Dependabot); verified against the redesign prototype + (github/prototyping janmaarten-a11y/timeline-redesign-v1) as an + intentional exclusion. No leaf -> no data-* tag. */} diff --git a/packages/react/src/Timeline/taxonomy/actorType.ts b/packages/react/src/Timeline/taxonomy/actorType.ts index 521405f8dea..71b06131abd 100644 --- a/packages/react/src/Timeline/taxonomy/actorType.ts +++ b/packages/react/src/Timeline/taxonomy/actorType.ts @@ -24,6 +24,11 @@ const BOT_LOGINS: ReadonlySet = new Set([ 'dependabot-preview', 'github-actions', 'github-license-compliance', + // `github` is the platform system identity (e.g. Secret Scanning's automated + // detection/validity events). Classified as a bot so automated-vs-human + // filtering treats system activity like other automation; a distinct + // 'system' actor type is a possible future refinement. + 'github', 'copilot', 'hubot', ]) diff --git a/packages/react/src/Timeline/taxonomy/eventTaxonomy.test.ts b/packages/react/src/Timeline/taxonomy/eventTaxonomy.test.ts index d442a1aa502..fa9829608e4 100644 --- a/packages/react/src/Timeline/taxonomy/eventTaxonomy.test.ts +++ b/packages/react/src/Timeline/taxonomy/eventTaxonomy.test.ts @@ -270,7 +270,7 @@ describe('actorTypeForLogin', () => { }) it('classifies known first-party automation logins as bots', () => { - for (const login of ['dependabot', 'github-actions', 'github-license-compliance', 'copilot', 'hubot']) { + for (const login of ['dependabot', 'github-actions', 'github-license-compliance', 'github', 'copilot', 'hubot']) { expect(actorTypeForLogin(login)).toBe('bot') expect(actorTypeForLogin(login.toUpperCase())).toBe('bot') } diff --git a/packages/react/src/Timeline/taxonomy/eventTaxonomy.ts b/packages/react/src/Timeline/taxonomy/eventTaxonomy.ts index 348910b4c6c..2a5015d0150 100644 --- a/packages/react/src/Timeline/taxonomy/eventTaxonomy.ts +++ b/packages/react/src/Timeline/taxonomy/eventTaxonomy.ts @@ -151,6 +151,7 @@ export const LICENSE_COMPLIANCE_TAXONOMY: Record = { detected: {category: 'findings', hasActor: true}, // `Creation`; system GitHub actor (isGitHubActor), rendered - validity_changed: {category: 'findings', hasActor: true}, // `Report` path; user actor + validity_changed: {category: 'findings', hasActor: true}, // `ValidityChange` (token verification status); system 'GitHub' actor when automated, user when manual + reported: {category: 'findings', hasActor: true}, // live AlertTimeline.tsx `TimelineEventType.Report`: "reported this secret"; user actor bypassed: {category: 'status', hasActor: true}, dismissal_requested: {category: 'reviews', hasActor: true}, // `DelegatedClosureRequestOpened` dismissal_reviewed: {category: 'reviews', hasActor: true},