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..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,6 +26,7 @@ import { UserActor, VariantSection, } from './internal/timelineStoryHelpers' +import {eventDataAttributesFor} from './taxonomy' import classes from './Timeline.code-scanning.features.stories.module.css' /** @@ -172,6 +173,17 @@ 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). Each `Timeline.Item` below projects its Code Scanning leaf event + * type through the shared `eventDataAttributesFor('code-scanning', , + * 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. + */ + export default { title: 'Components/Timeline/Events/Code Scanning', component: Timeline, @@ -219,7 +231,7 @@ export const EventDetected = () => ( sub-row, and a right-aligned tool-version Label (Timeline.Actions). */} - + @@ -253,7 +265,7 @@ export const EventDetected = () => ( card — `show_timeline_commit?` is false for this event). */} - + @@ -283,7 +295,7 @@ export const EventDetected = () => ( {category}" pill (rendered when the alert has more than one category). */} - + @@ -314,13 +326,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 +354,7 @@ export const EventFixed = () => ( {/* Fixed — non-selected ref → bare default check */} - + @@ -348,9 +367,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 +388,7 @@ export const EventFixed = () => ( {/* Config deleted — non-selected ref → bare default check */} - + @@ -398,7 +420,7 @@ export const EventClosedByUser = () => ( {/* Closed as false positive — with a resolution-note sub-row */} - + @@ -415,7 +437,7 @@ export const EventClosedByUser = () => ( {/* Closed as used in tests */} - + @@ -431,7 +453,7 @@ export const EventClosedByUser = () => ( {/* Closed as won't fix */} - + @@ -448,7 +470,7 @@ export const EventClosedByUser = () => ( `resolution == :NO_RESOLUTION`. */} - + @@ -475,7 +497,7 @@ export const EventReopened = () => ( - + @@ -510,7 +532,7 @@ export const EventDismissalRequested = () => ( - + @@ -550,7 +572,7 @@ export const EventDismissalReviewed = () => ( {/* Approved — check icon + reviewer-comment sub-row */} - + @@ -567,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 7768c168dff..791cbde6fa0 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 {eventDataAttributesFor} 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 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 + * 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,15 @@ const PushPill = ({sha}: {sha: string}) => ( ) +/** + * 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). + */ + export default { title: 'Components/Timeline/Events/Dependabot', component: Timeline, @@ -160,7 +186,7 @@ export const EventOpened = () => ( {/* Opened — no source */} - + @@ -176,7 +202,7 @@ export const EventOpened = () => ( {/* OpenedFromPR — bold `#123` pull-request link (scheme: primary, bold) */} - + @@ -192,7 +218,7 @@ export const EventOpened = () => ( {/* OpenedFromPush — blue push-pill with the 7-char `after` SHA */} - + @@ -225,7 +251,7 @@ export const EventFixed = () => ( {/* Fixed — no source */} - + @@ -241,7 +267,7 @@ export const EventFixed = () => ( {/* FixedViaPR — bold `#123` pull-request link */} - + @@ -257,7 +283,7 @@ export const EventFixed = () => ( {/* FixedViaPush — blue push-pill */} - + @@ -290,7 +316,7 @@ export const EventDismissed = () => ( {/* Manual — risk is tolerable (with an optional dismissal note) */} - + @@ -309,7 +335,7 @@ export const EventDismissed = () => ( {/* Manual — fix started */} - + @@ -325,7 +351,7 @@ export const EventDismissed = () => ( {/* Manual — no bandwidth to fix this */} - + @@ -341,7 +367,7 @@ export const EventDismissed = () => ( {/* Manual — vulnerable code is not actually used */} - + @@ -357,7 +383,7 @@ export const EventDismissed = () => ( {/* Manual — inaccurate */} - + @@ -373,7 +399,7 @@ export const EventDismissed = () => ( {/* Auto — rule-based, no source (with optional rule comment) */} - + @@ -396,7 +422,7 @@ export const EventDismissed = () => ( {/* Auto — from a pull request */} - + @@ -412,7 +438,7 @@ export const EventDismissed = () => ( {/* Auto — from a push */} - + @@ -441,7 +467,7 @@ export const EventReopened = () => ( {/* Manual reopen — user actor */} - + @@ -457,7 +483,7 @@ export const EventReopened = () => ( {/* Reintroduced — no source */} - + @@ -473,7 +499,7 @@ export const EventReopened = () => ( {/* Reintroduced — from a pull request */} - + @@ -489,7 +515,7 @@ export const EventReopened = () => ( {/* Reintroduced — from a push */} - + @@ -505,7 +531,7 @@ export const EventReopened = () => ( {/* Auto-reopened — rule change (with optional rule comment) */} - + @@ -560,7 +586,7 @@ export const EventDismissalRequest = () => ( place that right-aligned control in the `Timeline.Actions` slot. */} - + @@ -584,7 +610,7 @@ export const EventDismissalRequest = () => ( {/* Dismissal approved — circle user actor, success/check badge */} - + @@ -601,7 +627,7 @@ export const EventDismissalRequest = () => ( {/* Dismissal denied — circle user actor, danger/x badge */} - + @@ -619,7 +645,7 @@ export const EventDismissalRequest = () => ( "x")` (no bg) → a bare default badge, not a danger/emphasis one. */} - + @@ -651,6 +677,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 +790,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..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, @@ -40,6 +39,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 {eventDataAttributesFor} from './taxonomy' /** * Issue Timeline event examples (Phase 2 of github/primer#6663). @@ -104,6 +104,16 @@ export default { ], } as Meta> +/** + * 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). + */ + /** * The Closed event group — `IssueTimeline.eventClosed` (audit § 2). * @@ -118,7 +128,7 @@ export const EventClosed = () => ( {/* Closed as completed */} - + @@ -139,7 +149,7 @@ export const EventClosed = () => ( (portable for docs copy-paste; matches production `TimelineRow`). */} - + ( {/* Closed via pull request */} - + @@ -185,7 +195,7 @@ export const EventClosed = () => ( {/* Closed via commit */} - + @@ -210,7 +220,7 @@ export const EventClosed = () => ( equivalent for github-ui's `ProjectV2` closer link. */} - + @@ -233,7 +243,7 @@ export const EventClosed = () => ( composed here from Primer primitives. */} - + ( {/* Closed with no state reason */} - + @@ -293,7 +303,7 @@ export const EventState = () => ( {/* Reopened — open (green) badge via useIssueState (ReopenedEvent.tsx) */} - + @@ -310,7 +320,7 @@ export const EventState = () => ( plain inline Link (the audit shows it bold; live code is canonical). */} - + @@ -329,7 +339,7 @@ export const EventState = () => ( {/* Pinned */} - + @@ -345,7 +355,7 @@ export const EventState = () => ( {/* Unpinned */} - + @@ -362,7 +372,7 @@ export const EventState = () => ( the resulting discussion by number. */} - + @@ -374,22 +384,6 @@ export const EventState = () => ( - - {/* Converted from draft */} - - - - - - - - - {'converted this from a draft issue '} - - - - - ) @@ -407,7 +401,7 @@ export const EventReferences = () => ( open PR state icon inline before the PR title). */} - + @@ -431,7 +425,7 @@ export const EventReferences = () => ( icon is behind the live code here. */} - + @@ -451,7 +445,7 @@ export const EventReferences = () => ( inline (showAgoTimestamp={false}) and the commit card is sub-content. */} - + @@ -482,7 +476,7 @@ export const EventReferences = () => ( {/* Multiple commit references — same event, pluralized copy + N cards. */} - + @@ -538,7 +532,7 @@ export const EventDuplicates = () => ( IssueLink uses the open (green) state icon. */} - + @@ -562,6 +556,7 @@ export const EventDuplicates = () => ( {/* Marked as a duplicate of this issue — no right controls. */} + {/* 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. */} @@ -584,6 +579,7 @@ export const EventDuplicates = () => ( {/* Unmarked this as a duplicate of . */} + {/* 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. */} @@ -605,6 +601,7 @@ export const EventDuplicates = () => ( {/* Unmarked as a duplicate of this issue. */} + {/* 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. */} @@ -638,7 +635,7 @@ export const EventModeration = () => ( {/* User blocked (permanent) */} - + @@ -654,7 +651,7 @@ export const EventModeration = () => ( {/* User temporarily blocked */} - + @@ -670,7 +667,7 @@ export const EventModeration = () => ( {/* Comment pinned — IssueCommentPinnedEvent.tsx links the pinned comment. */} - + @@ -689,7 +686,7 @@ export const EventModeration = () => ( {/* Comment unpinned */} - + @@ -720,7 +717,7 @@ export const EventIssueTypes = () => ( {/* Type added */} - + @@ -750,7 +747,7 @@ export const EventIssueTypes = () => ( {/* Type removed */} - + @@ -780,7 +777,7 @@ export const EventIssueTypes = () => ( {/* Type changed */} - + @@ -840,7 +837,7 @@ export const EventIssueHierarchy = () => ( {/* Sub-issue added (single) */} - + @@ -865,7 +862,7 @@ export const EventIssueHierarchy = () => ( {/* Sub-issue added (multiple) — plural copy + N reference rows */} - + @@ -897,7 +894,7 @@ export const EventIssueHierarchy = () => ( {/* Sub-issue removed (single) */} - + @@ -922,7 +919,7 @@ export const EventIssueHierarchy = () => ( {/* Sub-issues removed (multiple) */} - + @@ -954,7 +951,7 @@ export const EventIssueHierarchy = () => ( {/* Parent issue added (single) */} - + @@ -979,7 +976,7 @@ export const EventIssueHierarchy = () => ( {/* Parent issues added (multiple) */} - + @@ -1011,7 +1008,7 @@ export const EventIssueHierarchy = () => ( {/* Parent issue removed (single) */} - + @@ -1036,7 +1033,7 @@ export const EventIssueHierarchy = () => ( {/* Parent issues removed (multiple) */} - + @@ -1084,7 +1081,7 @@ export const EventDependencies = () => ( {/* Blocked by (single) */} - + @@ -1109,7 +1106,7 @@ export const EventDependencies = () => ( {/* Blocked by (multiple) — count in copy + N rows */} - + @@ -1141,7 +1138,7 @@ export const EventDependencies = () => ( {/* Blocked by removed (single) */} - + @@ -1166,7 +1163,7 @@ export const EventDependencies = () => ( {/* Blocked by removed (multiple) */} - + @@ -1198,7 +1195,7 @@ export const EventDependencies = () => ( {/* Blocking (single) */} - + @@ -1223,7 +1220,7 @@ export const EventDependencies = () => ( {/* Blocking (multiple) */} - + @@ -1255,7 +1252,7 @@ export const EventDependencies = () => ( {/* Blocking removed (single) */} - + @@ -1280,7 +1277,7 @@ export const EventDependencies = () => ( {/* Blocking removed (multiple) */} - + @@ -1332,7 +1329,7 @@ export const EventIssueFields = () => ( {/* Set text field */} - + @@ -1353,7 +1350,7 @@ export const EventIssueFields = () => ( {/* Set number field */} - + @@ -1374,7 +1371,7 @@ export const EventIssueFields = () => ( {/* Set date field */} - + @@ -1395,7 +1392,7 @@ export const EventIssueFields = () => ( {/* Set single-select field — value is a colored token */} - + @@ -1429,7 +1426,7 @@ export const EventIssueFields = () => ( {/* Changed text field */} - + @@ -1450,7 +1447,7 @@ export const EventIssueFields = () => ( {/* Changed number field */} - + @@ -1471,7 +1468,7 @@ export const EventIssueFields = () => ( {/* Changed date field */} - + @@ -1492,7 +1489,7 @@ export const EventIssueFields = () => ( {/* Changed single-select field */} - + @@ -1523,7 +1520,7 @@ export const EventIssueFields = () => ( {/* Cleared text field — no value */} - + @@ -1540,7 +1537,7 @@ export const EventIssueFields = () => ( {/* Cleared number field */} - + @@ -1557,7 +1554,7 @@ export const EventIssueFields = () => ( {/* Cleared date field */} - + @@ -1574,7 +1571,7 @@ export const EventIssueFields = () => ( {/* Cleared single-select field */} - + @@ -1591,7 +1588,7 @@ export const EventIssueFields = () => ( {/* Rollup: updated only — multiple field updates collapsed into one row */} - + @@ -1616,7 +1613,7 @@ export const EventIssueFields = () => ( {/* Rollup: removed only */} - + @@ -1635,7 +1632,7 @@ export const EventIssueFields = () => ( {/* Rollup: updated and also removed — combined row joined by "and also" */} - + @@ -1687,7 +1684,7 @@ export const EventProject = () => ( {/* Added to project */} - + @@ -1708,7 +1705,7 @@ export const EventProject = () => ( {/* Removed from project */} - + @@ -1732,7 +1729,7 @@ export const EventProject = () => ( strings are PLAIN TEXT (not bold). Both forms shown under one caption. */} - + @@ -1746,7 +1743,7 @@ export const EventProject = () => ( - + @@ -1786,7 +1783,7 @@ export const EventLabels = () => ( {/* Label added */} - + @@ -1815,7 +1812,7 @@ export const EventLabels = () => ( {/* Label removed */} - + @@ -1845,6 +1842,7 @@ export const EventLabels = () => ( with "and" between them. */} + {/* 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. */} @@ -1905,7 +1903,7 @@ export const EventTitle = () => ( {/* Title changed */} - + @@ -1940,7 +1938,7 @@ export const EventMilestones = () => ( {/* Added to milestone */} - + @@ -1960,7 +1958,7 @@ export const EventMilestones = () => ( {/* Removed from milestone */} - + @@ -1999,7 +1997,7 @@ export const EventAssignments = () => ( {/* Self-assigned */} - + @@ -2015,7 +2013,7 @@ export const EventAssignments = () => ( {/* Assigned someone else — assignee is a bold link, no avatar. */} - + @@ -2031,7 +2029,7 @@ export const EventAssignments = () => ( {/* Assigned multiple — joined with "and". */} - + @@ -2049,7 +2047,7 @@ export const EventAssignments = () => ( {/* Self-unassigned */} - + @@ -2065,7 +2063,7 @@ export const EventAssignments = () => ( {/* Unassigned someone else */} - + @@ -2081,7 +2079,7 @@ export const EventAssignments = () => ( {/* Unassigned multiple — joined with "and". */} - + @@ -2118,7 +2116,7 @@ export const EventLockUnlock = () => ( too heated). */} - + @@ -2128,7 +2126,7 @@ export const EventLockUnlock = () => ( - + @@ -2138,7 +2136,7 @@ export const EventLockUnlock = () => ( - + @@ -2148,7 +2146,7 @@ export const EventLockUnlock = () => ( - + @@ -2164,7 +2162,7 @@ export const EventLockUnlock = () => ( {/* Locked (no reason) */} - + @@ -2180,7 +2178,7 @@ export const EventLockUnlock = () => ( {/* Unlocked — UnlockIcon badge (not LockIcon). */} - + @@ -2211,7 +2209,7 @@ export const EventCommentDeleted = () => ( {/* Comment deleted */} - + @@ -2249,7 +2247,7 @@ export const EventCrossReferences = () => ( {/* Mentioned from an issue */} - + @@ -2274,7 +2272,7 @@ export const EventCrossReferences = () => ( {/* Mentioned from a pull request */} - + @@ -2299,7 +2297,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 36d8dda8015..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,6 +19,7 @@ import {Button} from '../Button' import Link from '../Link' import Octicon from '../Octicon' import {EventSubRow, Examples, MutedTime, UserActor, VariantSection} from './internal/timelineStoryHelpers' +import {eventDataAttributesFor} from './taxonomy' import classes from './Timeline.license-compliance.features.stories.module.css' /** @@ -67,10 +68,21 @@ 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 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`). + * 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 +152,14 @@ const PolicyLink = ({href = '../../settings/security_analysis'}: {href?: string} ) +/** + * 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). + */ + export default { title: 'Components/Timeline/Events/License Compliance', component: Timeline, @@ -186,7 +206,7 @@ export const EventOpened = () => ( {/* Opened — license-compliance system bot, ShieldIcon on success (green) */} - + @@ -222,7 +242,7 @@ export const EventAppearedInBranch = () => ( badge, with a BranchName pill. PR sub-row is dormant (see group doc). */} - + @@ -262,7 +282,7 @@ export const EventReviewRequested = () => ( {/* Requested to close — no reason, no PR, no comment */} - + @@ -278,7 +298,7 @@ export const EventReviewRequested = () => ( {/* Requested to close as {reason}, with a requester comment sub-row */} - + @@ -296,7 +316,7 @@ export const EventReviewRequested = () => ( "Review request" button (latest request only) in Timeline.Actions. */} - + @@ -330,7 +350,7 @@ export const EventReviewApproved = () => ( {/* Approved closure request */} - + @@ -346,7 +366,7 @@ export const EventReviewApproved = () => ( {/* Approved closure request — with reviewer comment */} - + @@ -374,7 +394,7 @@ export const EventReviewDenied = () => ( {/* Denied closure request */} - + @@ -390,7 +410,7 @@ export const EventReviewDenied = () => ( {/* Denied closure request — with reviewer comment */} - + @@ -422,7 +442,9 @@ export const EventReviewExpired = () => ( {/* Request to close expired — license-compliance system bot, automatic expiry */} - + @@ -452,7 +474,7 @@ export const EventExceptionAdded = () => ( {/* Full shape — package + policy link + repo name */} - + @@ -470,7 +492,7 @@ export const EventExceptionAdded = () => ( {/* Fallback shape — body missing package info */} - + @@ -499,7 +521,7 @@ export const EventLicensesAdded = () => ( {/* Full shape — licenses list + policy link + repo name */} - + @@ -517,7 +539,7 @@ export const EventLicensesAdded = () => ( {/* Fallback shape — body missing licenses array */} - + @@ -550,7 +572,7 @@ export const EventClosed = () => ( {/* Closed as amendment — with a closing comment */} - + @@ -567,7 +589,7 @@ export const EventClosed = () => ( {/* Closed as private package */} - + @@ -583,7 +605,7 @@ export const EventClosed = () => ( {/* Closed as inaccurate license */} - + @@ -599,7 +621,7 @@ export const EventClosed = () => ( {/* Closed as policy edited */} - + @@ -615,7 +637,7 @@ export const EventClosed = () => ( {/* Closed as fixed */} - + @@ -631,7 +653,7 @@ export const EventClosed = () => ( {/* Closed as outdated — resolution Outdated */} - + @@ -647,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 d9aa3afd1c8..2013d1a86c9 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,7 @@ import { import {Button} from '../Button' import Octicon from '../Octicon' import {EventSubRow, MutedTime, RealisticTimeline, UserActor, VariantSection} from './internal/timelineStoryHelpers' +import {eventDataAttributesFor} from './taxonomy' /** * Secret Scanning alert Timeline event examples (Phase 2 of github/primer#6663). @@ -53,10 +54,19 @@ 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 the `eventDataAttributesFor` helper. + * 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 +107,12 @@ 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' +// 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', component: Timeline, @@ -137,7 +153,10 @@ export const EventCreated = () => ( {/* Created — GitHub system actor, ShieldIcon on success (green) */} - + {/* `detected` leaf. Renders the GitHub SYSTEM-IDENTITY actor + (``); `github` is in the bot set, so + actorTypeForLogin('GitHub') classifies it as 'bot' -> data-actor-type='bot'. */} + @@ -177,7 +196,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 +216,8 @@ export const EventResolution = () => ( {/* Closed as false positive — gray (default) ShieldSlash badge. */} - + {/* `closed` leaf; user actor. */} + @@ -212,7 +233,8 @@ export const EventResolution = () => ( {/* Closed as won't fix */} - + {/* `closed` leaf; user actor. */} + @@ -228,7 +250,8 @@ export const EventResolution = () => ( {/* Closed as used in tests */} - + {/* `closed` leaf; user actor. */} + @@ -244,7 +267,8 @@ export const EventResolution = () => ( {/* Closed as pattern deleted */} - + {/* `closed` leaf; user actor. */} + @@ -260,7 +284,8 @@ export const EventResolution = () => ( {/* Closed as pattern edited */} - + {/* `closed` leaf; user actor. */} + @@ -276,7 +301,8 @@ export const EventResolution = () => ( {/* Closed as ignored by configuration (resolution type `hidden_by_config`) */} - + {/* `closed` leaf; user actor. */} + @@ -297,7 +323,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 +336,8 @@ export const EventResolution = () => ( - + {/* `reopened` leaf (Resolution, resolution.type === 'reopened'); user actor. */} + @@ -338,7 +367,8 @@ export const EventBypass = () => ( {/* Bypassed — AlertIcon, default (gray) badge */} - + {/* `bypassed` leaf; user actor. */} + @@ -355,6 +385,11 @@ 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. Verified against the redesign + prototype (github/prototyping janmaarten-a11y/timeline-redesign-v1) as an + intentional exclusion. No leaf -> no data-* tag. */} @@ -371,6 +406,9 @@ export const EventBypass = () => ( {/* Bypass approved — CheckCircleIcon. Delegated bypass: gated as above. */} + {/* UNTAGGED: delegated-BYPASS flow, no catalog leaf (see the request + variant above), verified against the redesign prototype as an + intentional exclusion. No leaf -> no data-* tag. */} @@ -403,7 +441,8 @@ export const EventValidityChange = () => ( {/* Active — automated (GitHub), AlertIcon on danger (red) */} - + {/* `validity_changed` leaf; automated -> GitHub system actor -> 'bot'. */} + @@ -419,7 +458,8 @@ export const EventValidityChange = () => ( {/* Active — manual (user), same danger badge */} - + {/* `validity_changed` leaf; manual -> user actor. */} + @@ -435,7 +475,8 @@ export const EventValidityChange = () => ( {/* Inactive — automated (GitHub), SkipIcon on default (gray) */} - + {/* `validity_changed` leaf; automated -> GitHub system actor -> 'bot'. */} + @@ -451,7 +492,8 @@ export const EventValidityChange = () => ( {/* Inactive — manual (user) */} - + {/* `validity_changed` leaf; manual -> user actor. */} + @@ -467,7 +509,8 @@ export const EventValidityChange = () => ( {/* Unknown — automated (GitHub), AlertIcon on attention (amber) */} - + {/* `validity_changed` leaf; automated -> GitHub system actor -> 'bot'. */} + @@ -483,7 +526,8 @@ export const EventValidityChange = () => ( {/* Unknown — manual (user) */} - + {/* `validity_changed` leaf; manual -> user actor. */} + @@ -511,7 +555,10 @@ export const EventReport = () => ( {/* Reported — ShieldCheckIcon, default (gray) badge, user actor */} - + {/* `reported` leaf (findings). Live AlertTimeline.tsx `TimelineEventType.Report` + does NOT pass `isGitHubActor`, so the actor is the USER — resolves to + data-actor-type='user'. */} + @@ -545,6 +592,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 +610,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 +657,8 @@ export const EventClosureRequest = () => ( un-bolded `resolutionText(exemption_request.reason)`. */} - + {/* `dismissal_requested` leaf (DelegatedClosureRequestOpened); user actor. */} + @@ -630,7 +682,8 @@ export const EventClosureRequest = () => ( {/* Requested — requester view: the invisible "Cancel request" button. */} - + {/* `dismissal_requested` leaf; user actor. */} + @@ -651,7 +704,8 @@ export const EventClosureRequest = () => ( {/* Approved — CheckCircleIcon, with a reviewer comment sub-row. */} - + {/* `dismissal_reviewed` leaf; reviewer user actor (six7). */} + @@ -670,7 +724,8 @@ export const EventClosureRequest = () => ( {/* Denied — XIcon */} - + {/* `dismissal_reviewed` leaf; reviewer user actor (six7). */} + @@ -689,6 +744,10 @@ export const EventClosureRequest = () => ( {/* Cancelled — SkipIcon */} + {/* UNTAGGED: Secret Scanning has NO dismissal_cancelled leaf (unlike + Dependabot); verified against the redesign prototype + (github/prototyping janmaarten-a11y/timeline-redesign-v1) as an + intentional exclusion. No leaf -> no data-* tag. */} @@ -717,6 +776,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 */} 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/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/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}, 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'