From 04f26fcbfb0a460a5bbb9382df008fe95073b90e 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/2] 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 620253e5b56a8656f1cc6c530cd21cac94978483 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/2] 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 */}