@shipfox/client-triggers 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.storybook/main.ts +1 -0
- package/.storybook/preview.css +8 -0
- package/.storybook/preview.tsx +89 -0
- package/.turbo/turbo-build.log +2 -0
- package/.turbo/turbo-type$colon$emit.log +1 -0
- package/.turbo/turbo-type.log +1 -0
- package/CHANGELOG.md +120 -0
- package/LICENSE +21 -0
- package/dist/components/events-filter-bar.d.ts +13 -0
- package/dist/components/events-filter-bar.d.ts.map +1 -0
- package/dist/components/events-filter-bar.js +198 -0
- package/dist/components/events-filter-bar.js.map +1 -0
- package/dist/components/events-list-states.d.ts +16 -0
- package/dist/components/events-list-states.d.ts.map +1 -0
- package/dist/components/events-list-states.js +105 -0
- package/dist/components/events-list-states.js.map +1 -0
- package/dist/components/events-list.d.ts +3 -0
- package/dist/components/events-list.d.ts.map +1 -0
- package/dist/components/events-list.js +97 -0
- package/dist/components/events-list.js.map +1 -0
- package/dist/components/events-list.stories.d.ts +53 -0
- package/dist/components/events-list.stories.d.ts.map +1 -0
- package/dist/components/events-list.stories.js +180 -0
- package/dist/components/events-list.stories.js.map +1 -0
- package/dist/components/trigger-event-detail.d.ts +13 -0
- package/dist/components/trigger-event-detail.d.ts.map +1 -0
- package/dist/components/trigger-event-detail.js +376 -0
- package/dist/components/trigger-event-detail.js.map +1 -0
- package/dist/components/trigger-event-detail.stories.d.ts +65 -0
- package/dist/components/trigger-event-detail.stories.d.ts.map +1 -0
- package/dist/components/trigger-event-detail.stories.js +181 -0
- package/dist/components/trigger-event-detail.stories.js.map +1 -0
- package/dist/components/trigger-event-result.d.ts +15 -0
- package/dist/components/trigger-event-result.d.ts.map +1 -0
- package/dist/components/trigger-event-result.js +48 -0
- package/dist/components/trigger-event-result.js.map +1 -0
- package/dist/components/trigger-event-row.d.ts +9 -0
- package/dist/components/trigger-event-row.d.ts.map +1 -0
- package/dist/components/trigger-event-row.js +91 -0
- package/dist/components/trigger-event-row.js.map +1 -0
- package/dist/components/trigger-source-icon.d.ts +20 -0
- package/dist/components/trigger-source-icon.d.ts.map +1 -0
- package/dist/components/trigger-source-icon.js +28 -0
- package/dist/components/trigger-source-icon.js.map +1 -0
- package/dist/components/types.d.ts +20 -0
- package/dist/components/types.d.ts.map +1 -0
- package/dist/components/types.js +3 -0
- package/dist/components/types.js.map +1 -0
- package/dist/hooks/api/trigger-events.d.ts +164 -0
- package/dist/hooks/api/trigger-events.d.ts.map +1 -0
- package/dist/hooks/api/trigger-events.js +122 -0
- package/dist/hooks/api/trigger-events.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/pages/events-page.d.ts +12 -0
- package/dist/pages/events-page.d.ts.map +1 -0
- package/dist/pages/events-page.js +82 -0
- package/dist/pages/events-page.js.map +1 -0
- package/dist/search.d.ts +23 -0
- package/dist/search.d.ts.map +1 -0
- package/dist/search.js +28 -0
- package/dist/search.js.map +1 -0
- package/dist/tsconfig.test.tsbuildinfo +1 -0
- package/package.json +92 -0
- package/src/components/events-filter-bar.tsx +191 -0
- package/src/components/events-list-states.tsx +89 -0
- package/src/components/events-list.stories.tsx +141 -0
- package/src/components/events-list.test.tsx +173 -0
- package/src/components/events-list.tsx +107 -0
- package/src/components/trigger-event-detail.stories.tsx +169 -0
- package/src/components/trigger-event-detail.test.tsx +201 -0
- package/src/components/trigger-event-detail.tsx +317 -0
- package/src/components/trigger-event-result.test.ts +40 -0
- package/src/components/trigger-event-result.ts +40 -0
- package/src/components/trigger-event-row.tsx +86 -0
- package/src/components/trigger-source-icon.react.test.tsx +34 -0
- package/src/components/trigger-source-icon.test.ts +23 -0
- package/src/components/trigger-source-icon.tsx +36 -0
- package/src/components/types.ts +22 -0
- package/src/hooks/api/trigger-events.test.ts +252 -0
- package/src/hooks/api/trigger-events.ts +143 -0
- package/src/index.ts +4 -0
- package/src/pages/events-page.test.tsx +100 -0
- package/src/pages/events-page.tsx +79 -0
- package/src/search.test.ts +66 -0
- package/src/search.ts +38 -0
- package/test/setup.ts +35 -0
- package/tsconfig.build.json +9 -0
- package/tsconfig.build.tsbuildinfo +1 -0
- package/tsconfig.json +3 -0
- package/tsconfig.test.json +8 -0
- package/vercel.json +8 -0
- package/vitest.config.ts +68 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import {QueryLoadError} from '@shipfox/client-ui';
|
|
2
|
+
import {Button} from '@shipfox/react-ui/button';
|
|
3
|
+
import {Table, TableBody, TableHead, TableHeader, TableRow} from '@shipfox/react-ui/table';
|
|
4
|
+
import type {TriggerEventFilters} from '#hooks/api/trigger-events.js';
|
|
5
|
+
import {EventsFilterBar} from './events-filter-bar.js';
|
|
6
|
+
import {
|
|
7
|
+
EventsListEmpty,
|
|
8
|
+
EventsListNoMatches,
|
|
9
|
+
EventsListSkeleton,
|
|
10
|
+
EventsListStaleError,
|
|
11
|
+
} from './events-list-states.js';
|
|
12
|
+
import {TriggerEventRow} from './trigger-event-row.js';
|
|
13
|
+
import type {EventsListProps} from './types.js';
|
|
14
|
+
|
|
15
|
+
function hasAnyFilter(filters: TriggerEventFilters): boolean {
|
|
16
|
+
return Boolean(
|
|
17
|
+
(filters.source && filters.source.length > 0) ||
|
|
18
|
+
(filters.event && filters.event.length > 0) ||
|
|
19
|
+
filters.from ||
|
|
20
|
+
filters.to ||
|
|
21
|
+
(filters.outcome && filters.outcome.length > 0),
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function EventsList({
|
|
26
|
+
events,
|
|
27
|
+
query,
|
|
28
|
+
facets,
|
|
29
|
+
filters,
|
|
30
|
+
onFiltersChange,
|
|
31
|
+
workspaceId,
|
|
32
|
+
hasNextPage,
|
|
33
|
+
isFetchingNextPage,
|
|
34
|
+
onLoadMore,
|
|
35
|
+
selectedEventId,
|
|
36
|
+
onSelectEvent,
|
|
37
|
+
}: EventsListProps) {
|
|
38
|
+
const activeFilters = hasAnyFilter(filters);
|
|
39
|
+
const refreshFailed = query.isError && query.data !== undefined;
|
|
40
|
+
const showEmptyState = !query.isPending && !query.isError && events.length === 0;
|
|
41
|
+
|
|
42
|
+
function clearFilters() {
|
|
43
|
+
onFiltersChange({
|
|
44
|
+
source: undefined,
|
|
45
|
+
event: undefined,
|
|
46
|
+
outcome: undefined,
|
|
47
|
+
from: undefined,
|
|
48
|
+
to: undefined,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<div className="flex min-h-0 flex-col overflow-hidden rounded-8 border border-border-neutral-base bg-background-neutral-base">
|
|
54
|
+
<EventsFilterBar
|
|
55
|
+
filters={filters}
|
|
56
|
+
onFiltersChange={onFiltersChange}
|
|
57
|
+
sources={facets?.sources}
|
|
58
|
+
events={facets?.events}
|
|
59
|
+
hasActiveFilters={activeFilters}
|
|
60
|
+
onClear={clearFilters}
|
|
61
|
+
/>
|
|
62
|
+
|
|
63
|
+
{query.isPending ? <EventsListSkeleton /> : null}
|
|
64
|
+
{!query.isPending ? <QueryLoadError query={query} subject="events" icon="pulseLine" /> : null}
|
|
65
|
+
{!query.isPending && refreshFailed ? <EventsListStaleError query={query} /> : null}
|
|
66
|
+
{showEmptyState && !activeFilters ? <EventsListEmpty workspaceId={workspaceId} /> : null}
|
|
67
|
+
{showEmptyState && activeFilters ? <EventsListNoMatches onClear={clearFilters} /> : null}
|
|
68
|
+
|
|
69
|
+
{events.length > 0 ? (
|
|
70
|
+
<>
|
|
71
|
+
<Table>
|
|
72
|
+
<TableHeader className="sticky top-0 z-10">
|
|
73
|
+
<TableRow>
|
|
74
|
+
<TableHead>Event</TableHead>
|
|
75
|
+
<TableHead>Result</TableHead>
|
|
76
|
+
<TableHead className="text-right">Received</TableHead>
|
|
77
|
+
</TableRow>
|
|
78
|
+
</TableHeader>
|
|
79
|
+
<TableBody>
|
|
80
|
+
{events.map((event) => (
|
|
81
|
+
<TriggerEventRow
|
|
82
|
+
key={event.id}
|
|
83
|
+
event={event}
|
|
84
|
+
selected={event.id === selectedEventId}
|
|
85
|
+
onSelect={onSelectEvent}
|
|
86
|
+
/>
|
|
87
|
+
))}
|
|
88
|
+
</TableBody>
|
|
89
|
+
</Table>
|
|
90
|
+
{hasNextPage ? (
|
|
91
|
+
<div className="flex justify-center p-16">
|
|
92
|
+
<Button
|
|
93
|
+
type="button"
|
|
94
|
+
variant="secondary"
|
|
95
|
+
size="sm"
|
|
96
|
+
isLoading={isFetchingNextPage}
|
|
97
|
+
onClick={onLoadMore}
|
|
98
|
+
>
|
|
99
|
+
Load more
|
|
100
|
+
</Button>
|
|
101
|
+
</div>
|
|
102
|
+
) : null}
|
|
103
|
+
</>
|
|
104
|
+
) : null}
|
|
105
|
+
</div>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import {argosScreenshot} from '@argos-ci/storybook/vitest';
|
|
2
|
+
import type {TriggerEventDetailResponseDto} from '@shipfox/api-triggers-dto';
|
|
3
|
+
import {RelativeTimeProvider} from '@shipfox/react-ui/relative-time';
|
|
4
|
+
import type {Decorator, Meta, StoryObj} from '@storybook/react';
|
|
5
|
+
import {
|
|
6
|
+
createMemoryHistory,
|
|
7
|
+
createRootRoute,
|
|
8
|
+
createRoute,
|
|
9
|
+
createRouter,
|
|
10
|
+
Outlet,
|
|
11
|
+
RouterProvider,
|
|
12
|
+
} from '@tanstack/react-router';
|
|
13
|
+
import {screen} from 'storybook/test';
|
|
14
|
+
import {TriggerEventDetailView} from './trigger-event-detail.js';
|
|
15
|
+
|
|
16
|
+
const WORKSPACE_ID = '11111111-1111-4111-8111-111111111111';
|
|
17
|
+
const PROJECT_ID = '22222222-2222-4222-8222-222222222222';
|
|
18
|
+
const RUN_ID = '33333333-3333-4333-8333-333333333333';
|
|
19
|
+
|
|
20
|
+
function minutesAgo(minutes: number): string {
|
|
21
|
+
return new Date(Date.now() - minutes * 60_000).toISOString();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const withRouter: Decorator = (Story) => {
|
|
25
|
+
function StoryRoute() {
|
|
26
|
+
return (
|
|
27
|
+
<RelativeTimeProvider>
|
|
28
|
+
<div className="min-h-screen bg-background-subtle-base p-24">
|
|
29
|
+
<div className="ml-auto w-[400px]">
|
|
30
|
+
<Story />
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
</RelativeTimeProvider>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const rootRoute = createRootRoute({component: Outlet});
|
|
38
|
+
const storyRoute = createRoute({
|
|
39
|
+
getParentRoute: () => rootRoute,
|
|
40
|
+
path: '/',
|
|
41
|
+
component: StoryRoute,
|
|
42
|
+
});
|
|
43
|
+
const runRoute = createRoute({
|
|
44
|
+
getParentRoute: () => rootRoute,
|
|
45
|
+
path: '/workspaces/$wid/projects/$pid/runs/$workflowRunId',
|
|
46
|
+
component: StoryRoute,
|
|
47
|
+
});
|
|
48
|
+
const router = createRouter({
|
|
49
|
+
history: createMemoryHistory({initialEntries: ['/']}),
|
|
50
|
+
routeTree: rootRoute.addChildren([storyRoute, runRoute]),
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
return <RouterProvider router={router} />;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const routedEvent: TriggerEventDetailResponseDto = {
|
|
57
|
+
id: '44444444-4444-4444-8444-444444444444',
|
|
58
|
+
event_ref: 'github:delivery-179:push',
|
|
59
|
+
origin: 'integration',
|
|
60
|
+
workspace_id: WORKSPACE_ID,
|
|
61
|
+
provider: 'github',
|
|
62
|
+
source: 'github_acme',
|
|
63
|
+
event: 'push',
|
|
64
|
+
delivery_id: 'delivery-179',
|
|
65
|
+
connection_id: '55555555-5555-4555-8555-555555555555',
|
|
66
|
+
connection_name: 'ShipfoxHQ Production',
|
|
67
|
+
outcome: 'routed',
|
|
68
|
+
matched_count: 2,
|
|
69
|
+
payload: {
|
|
70
|
+
ref: 'refs/heads/main',
|
|
71
|
+
repository: {full_name: 'ShipfoxHQ/platform'},
|
|
72
|
+
head_commit: {id: '9f1a0f2c7a1b', message: 'Deploy event detail'},
|
|
73
|
+
},
|
|
74
|
+
received_at: minutesAgo(15),
|
|
75
|
+
processed_at: '2026-06-25T19:30:02.000Z',
|
|
76
|
+
created_at: '2026-06-25T19:30:00.000Z',
|
|
77
|
+
decisions: [
|
|
78
|
+
{
|
|
79
|
+
id: '66666666-6666-4666-8666-666666666666',
|
|
80
|
+
received_event_id: '44444444-4444-4444-8444-444444444444',
|
|
81
|
+
subscription_kind: 'trigger',
|
|
82
|
+
subscription_id: '77777777-7777-4777-8777-777777777777',
|
|
83
|
+
subscription_name: 'Deploy production',
|
|
84
|
+
workflow_definition_id: '88888888-8888-4888-8888-888888888888',
|
|
85
|
+
project_id: PROJECT_ID,
|
|
86
|
+
workflow_run_id: null,
|
|
87
|
+
job_id: null,
|
|
88
|
+
matcher_kind: null,
|
|
89
|
+
matcher_ordinal: null,
|
|
90
|
+
decision: 'triggered',
|
|
91
|
+
run_id: RUN_ID,
|
|
92
|
+
run_name: 'deploy-web #184',
|
|
93
|
+
reason: null,
|
|
94
|
+
created_at: '2026-06-25T19:30:02.000Z',
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
id: '99999999-9999-4999-8999-999999999999',
|
|
98
|
+
received_event_id: '44444444-4444-4444-8444-444444444444',
|
|
99
|
+
subscription_kind: 'trigger',
|
|
100
|
+
subscription_id: 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa',
|
|
101
|
+
subscription_name: 'Mirror staging',
|
|
102
|
+
workflow_definition_id: 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb',
|
|
103
|
+
project_id: PROJECT_ID,
|
|
104
|
+
workflow_run_id: null,
|
|
105
|
+
job_id: null,
|
|
106
|
+
matcher_kind: null,
|
|
107
|
+
matcher_ordinal: null,
|
|
108
|
+
decision: 'triggered',
|
|
109
|
+
run_id: 'cccccccc-cccc-4ccc-8ccc-cccccccccccc',
|
|
110
|
+
run_name: 'mirror-staging #51',
|
|
111
|
+
reason: null,
|
|
112
|
+
created_at: '2026-06-25T19:30:02.000Z',
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const discardedEvent: TriggerEventDetailResponseDto = {
|
|
118
|
+
...routedEvent,
|
|
119
|
+
id: 'dddddddd-dddd-4ddd-8ddd-dddddddddddd',
|
|
120
|
+
event_ref: 'github:delivery-180:issue_comment',
|
|
121
|
+
event: 'issue_comment',
|
|
122
|
+
delivery_id: 'delivery-180',
|
|
123
|
+
outcome: 'discarded',
|
|
124
|
+
matched_count: 0,
|
|
125
|
+
payload: {action: 'created', comment: {body: '/preview'}},
|
|
126
|
+
processed_at: '2026-06-25T19:33:01.000Z',
|
|
127
|
+
decisions: [],
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const meta = {
|
|
131
|
+
title: 'Triggers/EventDetail',
|
|
132
|
+
component: TriggerEventDetailView,
|
|
133
|
+
parameters: {
|
|
134
|
+
layout: 'fullscreen',
|
|
135
|
+
argos: {
|
|
136
|
+
modes: {
|
|
137
|
+
light: {theme: 'light'},
|
|
138
|
+
dark: {theme: 'dark'},
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
decorators: [withRouter],
|
|
143
|
+
args: {
|
|
144
|
+
workspaceId: WORKSPACE_ID,
|
|
145
|
+
event: routedEvent,
|
|
146
|
+
onBack: () => undefined,
|
|
147
|
+
},
|
|
148
|
+
} satisfies Meta<typeof TriggerEventDetailView>;
|
|
149
|
+
|
|
150
|
+
export default meta;
|
|
151
|
+
type Story = StoryObj<typeof meta>;
|
|
152
|
+
|
|
153
|
+
async function captureDetail(ctx: Parameters<NonNullable<Story['play']>>[0], name: string) {
|
|
154
|
+
await screen.findByRole('complementary', {name: 'Event details'});
|
|
155
|
+
await argosScreenshot(ctx, name);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export const Playground: Story = {
|
|
159
|
+
play: async (ctx) => {
|
|
160
|
+
await captureDetail(ctx, 'Trigger Event Detail Playground');
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
export const Discarded: Story = {
|
|
165
|
+
args: {event: discardedEvent},
|
|
166
|
+
play: async (ctx) => {
|
|
167
|
+
await captureDetail(ctx, 'Trigger Event Detail Discarded');
|
|
168
|
+
},
|
|
169
|
+
};
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import type {TriggerEventDetailResponseDto} from '@shipfox/api-triggers-dto';
|
|
2
|
+
import {RelativeTimeProvider} from '@shipfox/react-ui/relative-time';
|
|
3
|
+
import {fireEvent, render, screen} from '@testing-library/react';
|
|
4
|
+
import userEvent from '@testing-library/user-event';
|
|
5
|
+
import type {ReactElement} from 'react';
|
|
6
|
+
import {useTriggerEventQuery} from '#hooks/api/trigger-events.js';
|
|
7
|
+
import {TriggerEventDetail, TriggerEventDetailView} from './trigger-event-detail.js';
|
|
8
|
+
|
|
9
|
+
const WORKSPACE_ID = '11111111-1111-4111-8111-111111111111';
|
|
10
|
+
const PROJECT_ID = '22222222-2222-4222-8222-222222222222';
|
|
11
|
+
const RUN_ID = '33333333-3333-4333-8333-333333333333';
|
|
12
|
+
const EVENT_ID = '44444444-4444-4444-8444-444444444444';
|
|
13
|
+
const DEPLOY_RUN_LINK_NAME = /deploy-web #184/u;
|
|
14
|
+
const PAYLOAD_REF_LINE = /"ref": "refs\/heads\/main"/u;
|
|
15
|
+
|
|
16
|
+
const useTriggerEventQueryMock = vi.mocked(useTriggerEventQuery);
|
|
17
|
+
|
|
18
|
+
function makeEvent(
|
|
19
|
+
overrides: Partial<TriggerEventDetailResponseDto> = {},
|
|
20
|
+
): TriggerEventDetailResponseDto {
|
|
21
|
+
return {
|
|
22
|
+
id: EVENT_ID,
|
|
23
|
+
event_ref: 'github:delivery-179:push',
|
|
24
|
+
origin: 'integration',
|
|
25
|
+
workspace_id: WORKSPACE_ID,
|
|
26
|
+
provider: 'github',
|
|
27
|
+
source: 'github',
|
|
28
|
+
event: 'push',
|
|
29
|
+
delivery_id: 'delivery-179',
|
|
30
|
+
connection_id: '55555555-5555-4555-8555-555555555555',
|
|
31
|
+
connection_name: 'ShipfoxHQ Production',
|
|
32
|
+
outcome: 'routed',
|
|
33
|
+
matched_count: 1,
|
|
34
|
+
payload: {ref: 'refs/heads/main', action: 'push'},
|
|
35
|
+
received_at: '2026-06-25T19:30:00.000Z',
|
|
36
|
+
processed_at: '2026-06-25T19:30:02.000Z',
|
|
37
|
+
created_at: '2026-06-25T19:30:00.000Z',
|
|
38
|
+
decisions: [
|
|
39
|
+
{
|
|
40
|
+
id: '66666666-6666-4666-8666-666666666666',
|
|
41
|
+
received_event_id: EVENT_ID,
|
|
42
|
+
subscription_kind: 'trigger',
|
|
43
|
+
subscription_id: '77777777-7777-4777-8777-777777777777',
|
|
44
|
+
subscription_name: 'Deploy production',
|
|
45
|
+
workflow_definition_id: '88888888-8888-4888-8888-888888888888',
|
|
46
|
+
project_id: PROJECT_ID,
|
|
47
|
+
workflow_run_id: null,
|
|
48
|
+
job_id: null,
|
|
49
|
+
matcher_kind: null,
|
|
50
|
+
matcher_ordinal: null,
|
|
51
|
+
decision: 'triggered',
|
|
52
|
+
run_id: RUN_ID,
|
|
53
|
+
run_name: 'deploy-web #184',
|
|
54
|
+
reason: null,
|
|
55
|
+
created_at: '2026-06-25T19:30:02.000Z',
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
...overrides,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function renderWithProviders(ui: ReactElement) {
|
|
63
|
+
return render(<RelativeTimeProvider>{ui}</RelativeTimeProvider>);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function renderDetailView(event: TriggerEventDetailResponseDto, onBack = vi.fn()) {
|
|
67
|
+
return renderWithProviders(
|
|
68
|
+
<TriggerEventDetailView workspaceId={WORKSPACE_ID} event={event} onBack={onBack} />,
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
describe('TriggerEventDetailView', () => {
|
|
73
|
+
test('renders the result badge, run links, and payload', async () => {
|
|
74
|
+
renderDetailView(makeEvent());
|
|
75
|
+
|
|
76
|
+
expect(await screen.findByText('Triggered 1 workflow')).toBeInTheDocument();
|
|
77
|
+
expect(screen.getByText('Deploy production')).toBeInTheDocument();
|
|
78
|
+
expect(screen.getByRole('link', {name: DEPLOY_RUN_LINK_NAME})).toHaveAttribute(
|
|
79
|
+
'href',
|
|
80
|
+
`/workspaces/${WORKSPACE_ID}/projects/${PROJECT_ID}/runs/${RUN_ID}`,
|
|
81
|
+
);
|
|
82
|
+
expect(screen.getByText(PAYLOAD_REF_LINE)).toBeInTheDocument();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('renders a no-subscriptions note for a discarded event', async () => {
|
|
86
|
+
renderDetailView(makeEvent({outcome: 'discarded', matched_count: 0, decisions: []}));
|
|
87
|
+
|
|
88
|
+
expect(await screen.findByText('No workflows triggered')).toBeInTheDocument();
|
|
89
|
+
expect(screen.getByText('No workflows are subscribed to this event.')).toBeInTheDocument();
|
|
90
|
+
expect(screen.queryByText('Matched workflows')).not.toBeInTheDocument();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('renders failed decisions inline with their reason', async () => {
|
|
94
|
+
renderDetailView(
|
|
95
|
+
makeEvent({
|
|
96
|
+
outcome: 'errored',
|
|
97
|
+
decisions: [
|
|
98
|
+
{
|
|
99
|
+
id: '66666666-6666-4666-8666-666666666666',
|
|
100
|
+
received_event_id: EVENT_ID,
|
|
101
|
+
subscription_kind: 'trigger',
|
|
102
|
+
subscription_id: '77777777-7777-4777-8777-777777777777',
|
|
103
|
+
subscription_name: 'Deploy production',
|
|
104
|
+
workflow_definition_id: '88888888-8888-4888-8888-888888888888',
|
|
105
|
+
project_id: PROJECT_ID,
|
|
106
|
+
workflow_run_id: null,
|
|
107
|
+
job_id: null,
|
|
108
|
+
matcher_kind: null,
|
|
109
|
+
matcher_ordinal: null,
|
|
110
|
+
decision: 'dispatch-error',
|
|
111
|
+
run_id: null,
|
|
112
|
+
run_name: null,
|
|
113
|
+
reason: 'workflow definition deleted',
|
|
114
|
+
created_at: '2026-06-25T19:30:02.000Z',
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
}),
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
expect(await screen.findByText('Failed')).toBeInTheDocument();
|
|
121
|
+
expect(screen.getByText('Deploy production')).toBeInTheDocument();
|
|
122
|
+
expect(screen.getByText('workflow definition deleted')).toBeInTheDocument();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test('renders null run fields without a run link', async () => {
|
|
126
|
+
renderDetailView(
|
|
127
|
+
makeEvent({
|
|
128
|
+
decisions: [
|
|
129
|
+
{
|
|
130
|
+
id: '66666666-6666-4666-8666-666666666666',
|
|
131
|
+
received_event_id: EVENT_ID,
|
|
132
|
+
subscription_kind: 'trigger',
|
|
133
|
+
subscription_id: '77777777-7777-4777-8777-777777777777',
|
|
134
|
+
subscription_name: 'Deploy production',
|
|
135
|
+
workflow_definition_id: '88888888-8888-4888-8888-888888888888',
|
|
136
|
+
project_id: PROJECT_ID,
|
|
137
|
+
workflow_run_id: null,
|
|
138
|
+
job_id: null,
|
|
139
|
+
matcher_kind: null,
|
|
140
|
+
matcher_ordinal: null,
|
|
141
|
+
decision: 'filter-error',
|
|
142
|
+
run_id: null,
|
|
143
|
+
run_name: null,
|
|
144
|
+
reason: null,
|
|
145
|
+
created_at: '2026-06-25T19:30:02.000Z',
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
}),
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
expect(await screen.findByText('No run created')).toBeInTheDocument();
|
|
152
|
+
expect(screen.queryByRole('link')).not.toBeInTheDocument();
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test('calls onBack from the focused detail panel control', async () => {
|
|
156
|
+
const onBack = vi.fn();
|
|
157
|
+
renderDetailView(makeEvent(), onBack);
|
|
158
|
+
|
|
159
|
+
await userEvent.click(await screen.findByRole('button', {name: 'Back to events'}));
|
|
160
|
+
|
|
161
|
+
expect(onBack).toHaveBeenCalledTimes(1);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
describe('TriggerEventDetail', () => {
|
|
166
|
+
beforeEach(() => {
|
|
167
|
+
useTriggerEventQueryMock.mockReset();
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test('renders a loading panel while the detail query is pending', async () => {
|
|
171
|
+
useTriggerEventQueryMock.mockReturnValue({
|
|
172
|
+
data: undefined,
|
|
173
|
+
isError: false,
|
|
174
|
+
refetch: vi.fn(),
|
|
175
|
+
} as unknown as ReturnType<typeof useTriggerEventQuery>);
|
|
176
|
+
|
|
177
|
+
renderWithProviders(
|
|
178
|
+
<TriggerEventDetail workspaceId={WORKSPACE_ID} eventId={EVENT_ID} onBack={vi.fn()} />,
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
expect(await screen.findByRole('button', {name: 'Back to events'})).toBeInTheDocument();
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test('renders a retry action when the detail query fails', async () => {
|
|
185
|
+
const refetch = vi.fn();
|
|
186
|
+
useTriggerEventQueryMock.mockReturnValue({
|
|
187
|
+
data: undefined,
|
|
188
|
+
isError: true,
|
|
189
|
+
refetch,
|
|
190
|
+
} as unknown as ReturnType<typeof useTriggerEventQuery>);
|
|
191
|
+
|
|
192
|
+
renderWithProviders(
|
|
193
|
+
<TriggerEventDetail workspaceId={WORKSPACE_ID} eventId={EVENT_ID} onBack={vi.fn()} />,
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
fireEvent.click(await screen.findByRole('button', {name: 'Retry'}));
|
|
197
|
+
|
|
198
|
+
expect(screen.getByText('Event detail could not be loaded.')).toBeInTheDocument();
|
|
199
|
+
expect(refetch).toHaveBeenCalledTimes(1);
|
|
200
|
+
});
|
|
201
|
+
});
|