@shipfox/client-triggers 12.0.2 → 14.0.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/preview.tsx +3 -3
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +37 -0
- package/dist/components/events-list-states.d.ts +2 -2
- package/dist/components/events-list-states.d.ts.map +1 -1
- package/dist/components/events-list-states.js +5 -5
- package/dist/components/events-list-states.js.map +1 -1
- package/dist/components/events-list.d.ts +1 -1
- package/dist/components/events-list.d.ts.map +1 -1
- package/dist/components/events-list.js +2 -2
- package/dist/components/events-list.js.map +1 -1
- package/dist/components/events-list.stories.d.ts +1 -1
- package/dist/components/events-list.stories.js +1 -1
- package/dist/components/events-list.stories.js.map +1 -1
- package/dist/components/trigger-event-detail.d.ts +6 -4
- package/dist/components/trigger-event-detail.d.ts.map +1 -1
- package/dist/components/trigger-event-detail.js +80 -35
- package/dist/components/trigger-event-detail.js.map +1 -1
- package/dist/components/trigger-event-detail.stories.d.ts +1 -0
- package/dist/components/trigger-event-detail.stories.d.ts.map +1 -1
- package/dist/components/trigger-event-detail.stories.js +40 -6
- package/dist/components/trigger-event-detail.stories.js.map +1 -1
- package/dist/components/types.d.ts +1 -1
- package/dist/components/types.d.ts.map +1 -1
- package/dist/components/types.js.map +1 -1
- package/dist/feature.d.ts +1 -1
- package/dist/feature.js +1 -1
- package/dist/feature.js.map +1 -1
- package/dist/pages/events-page.d.ts +2 -1
- package/dist/pages/events-page.d.ts.map +1 -1
- package/dist/pages/events-page.js +3 -2
- package/dist/pages/events-page.js.map +1 -1
- package/dist/routes/events-settings.d.ts.map +1 -1
- package/dist/routes/events-settings.js +2 -1
- package/dist/routes/events-settings.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +6 -5
- package/src/components/events-list-states.tsx +8 -6
- package/src/components/events-list.stories.tsx +1 -1
- package/src/components/events-list.test.tsx +0 -1
- package/src/components/events-list.tsx +2 -2
- package/src/components/trigger-event-detail.stories.tsx +25 -7
- package/src/components/trigger-event-detail.test.tsx +42 -10
- package/src/components/trigger-event-detail.tsx +137 -28
- package/src/components/types.ts +1 -1
- package/src/feature.ts +1 -1
- package/src/pages/events-page.tsx +9 -2
- package/src/routes/events-settings.tsx +7 -2
- package/tsconfig.build.tsbuildinfo +1 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shipfox/client-triggers",
|
|
3
3
|
"license": "MIT",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "14.0.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/ShipfoxHQ/shipfox.git",
|
|
@@ -28,10 +28,11 @@
|
|
|
28
28
|
"@swc/helpers": "^0.5.17",
|
|
29
29
|
"@shipfox/api-triggers-dto": "9.0.2",
|
|
30
30
|
"@shipfox/client-api": "6.0.1",
|
|
31
|
-
"@shipfox/client-
|
|
32
|
-
"@shipfox/client-
|
|
33
|
-
"@shipfox/
|
|
34
|
-
"@shipfox/
|
|
31
|
+
"@shipfox/client-projects": "14.0.0",
|
|
32
|
+
"@shipfox/client-shell": "14.0.0",
|
|
33
|
+
"@shipfox/client-ui": "14.0.0",
|
|
34
|
+
"@shipfox/integration-icons": "0.3.0",
|
|
35
|
+
"@shipfox/react-ui": "0.5.0"
|
|
35
36
|
},
|
|
36
37
|
"peerDependencies": {
|
|
37
38
|
"@tanstack/react-query": "^5.101.0",
|
|
@@ -25,7 +25,7 @@ export function EventsListSkeleton() {
|
|
|
25
25
|
);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
export function EventsListEmpty({
|
|
28
|
+
export function EventsListEmpty({workspaceSlug}: {workspaceSlug?: string | undefined}) {
|
|
29
29
|
return (
|
|
30
30
|
<div className="p-16">
|
|
31
31
|
<EmptyState
|
|
@@ -33,11 +33,13 @@ export function EventsListEmpty({workspaceId}: {workspaceId: string}) {
|
|
|
33
33
|
title="No events yet"
|
|
34
34
|
description="Events appear here once a connected integration delivers one or you fire a trigger."
|
|
35
35
|
action={
|
|
36
|
-
|
|
37
|
-
<
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
workspaceSlug ? (
|
|
37
|
+
<Button asChild size="sm" variant="secondary">
|
|
38
|
+
<Link to="/w/$workspaceSlug/settings/integrations" params={{workspaceSlug}}>
|
|
39
|
+
Configure integrations
|
|
40
|
+
</Link>
|
|
41
|
+
</Button>
|
|
42
|
+
) : undefined
|
|
41
43
|
}
|
|
42
44
|
/>
|
|
43
45
|
</div>
|
|
@@ -90,12 +90,12 @@ const meta = {
|
|
|
90
90
|
parameters: {layout: 'padded'},
|
|
91
91
|
decorators: [withWidth],
|
|
92
92
|
args: {
|
|
93
|
+
workspaceSlug: 'acme',
|
|
93
94
|
events: SAMPLE_EVENTS,
|
|
94
95
|
query: makeQuery(),
|
|
95
96
|
facets: SAMPLE_FACETS,
|
|
96
97
|
filters: {},
|
|
97
98
|
onFiltersChange: () => undefined,
|
|
98
|
-
workspaceId: 'ws-demo',
|
|
99
99
|
hasNextPage: false,
|
|
100
100
|
isFetchingNextPage: false,
|
|
101
101
|
onLoadMore: () => undefined,
|
|
@@ -40,7 +40,6 @@ function makeProps(overrides: Partial<EventsListProps> = {}): EventsListProps {
|
|
|
40
40
|
facets: {sources: [{value: 'github_acme', count: 1}], events: [{value: 'push', count: 1}]},
|
|
41
41
|
filters: {},
|
|
42
42
|
onFiltersChange: vi.fn(),
|
|
43
|
-
workspaceId: 'ws-1',
|
|
44
43
|
hasNextPage: false,
|
|
45
44
|
isFetchingNextPage: false,
|
|
46
45
|
onLoadMore: vi.fn(),
|
|
@@ -18,7 +18,7 @@ export function EventsList({
|
|
|
18
18
|
facets,
|
|
19
19
|
filters,
|
|
20
20
|
onFiltersChange,
|
|
21
|
-
|
|
21
|
+
workspaceSlug,
|
|
22
22
|
hasNextPage,
|
|
23
23
|
isFetchingNextPage,
|
|
24
24
|
onLoadMore,
|
|
@@ -53,7 +53,7 @@ export function EventsList({
|
|
|
53
53
|
{query.isPending ? <EventsListSkeleton /> : null}
|
|
54
54
|
{!query.isPending ? <QueryLoadError query={query} subject="events" icon="pulseLine" /> : null}
|
|
55
55
|
{!query.isPending && refreshFailed ? <EventsListStaleError query={query} /> : null}
|
|
56
|
-
{showEmptyState && !activeFilters ? <EventsListEmpty
|
|
56
|
+
{showEmptyState && !activeFilters ? <EventsListEmpty workspaceSlug={workspaceSlug} /> : null}
|
|
57
57
|
{showEmptyState && activeFilters ? <EventsListNoMatches onClear={clearFilters} /> : null}
|
|
58
58
|
|
|
59
59
|
{events.length > 0 ? (
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import {argosScreenshot} from '@argos-ci/storybook/vitest';
|
|
2
2
|
import type {TriggerEventDetailResponseDto} from '@shipfox/api-triggers-dto';
|
|
3
|
+
import {projectsQueryKeys} from '@shipfox/client-projects';
|
|
3
4
|
import {RelativeTimeProvider} from '@shipfox/react-ui/relative-time';
|
|
4
5
|
import type {Decorator, Meta, StoryObj} from '@storybook/react';
|
|
6
|
+
import {QueryClient, QueryClientProvider} from '@tanstack/react-query';
|
|
5
7
|
import {
|
|
6
8
|
createMemoryHistory,
|
|
7
9
|
createRootRoute,
|
|
@@ -22,16 +24,31 @@ function minutesAgo(minutes: number): string {
|
|
|
22
24
|
return new Date(Date.now() - minutes * 60_000).toISOString();
|
|
23
25
|
}
|
|
24
26
|
|
|
27
|
+
function createStoryQueryClient() {
|
|
28
|
+
const queryClient = new QueryClient({
|
|
29
|
+
defaultOptions: {queries: {staleTime: Number.POSITIVE_INFINITY, retry: false}},
|
|
30
|
+
});
|
|
31
|
+
queryClient.setQueryData(projectsQueryKeys.list(WORKSPACE_ID), {
|
|
32
|
+
pages: [{projects: [{id: PROJECT_ID, slug: 'checkout-api'}], nextCursor: null}],
|
|
33
|
+
pageParams: [undefined],
|
|
34
|
+
});
|
|
35
|
+
return queryClient;
|
|
36
|
+
}
|
|
37
|
+
|
|
25
38
|
const withRouter: Decorator = (Story) => {
|
|
39
|
+
const queryClient = createStoryQueryClient();
|
|
40
|
+
|
|
26
41
|
function StoryRoute() {
|
|
27
42
|
return (
|
|
28
|
-
<
|
|
29
|
-
<
|
|
30
|
-
<div className="
|
|
31
|
-
<
|
|
43
|
+
<QueryClientProvider client={queryClient}>
|
|
44
|
+
<RelativeTimeProvider>
|
|
45
|
+
<div className="min-h-screen bg-background-subtle-base p-24 [--app-content-h:calc(100dvh_-_96px)]">
|
|
46
|
+
<div className="@container ml-auto w-[860px]">
|
|
47
|
+
<Story />
|
|
48
|
+
</div>
|
|
32
49
|
</div>
|
|
33
|
-
</
|
|
34
|
-
</
|
|
50
|
+
</RelativeTimeProvider>
|
|
51
|
+
</QueryClientProvider>
|
|
35
52
|
);
|
|
36
53
|
}
|
|
37
54
|
|
|
@@ -43,7 +60,7 @@ const withRouter: Decorator = (Story) => {
|
|
|
43
60
|
});
|
|
44
61
|
const runRoute = createRoute({
|
|
45
62
|
getParentRoute: () => rootRoute,
|
|
46
|
-
path: '/
|
|
63
|
+
path: '/w/$workspaceSlug/p/$projectSlug/runs/$workflowRunId',
|
|
47
64
|
component: StoryRoute,
|
|
48
65
|
});
|
|
49
66
|
const router = createRouter({
|
|
@@ -146,6 +163,7 @@ const meta = {
|
|
|
146
163
|
decorators: [withRouter],
|
|
147
164
|
args: {
|
|
148
165
|
workspaceId: WORKSPACE_ID,
|
|
166
|
+
workspaceSlug: 'acme',
|
|
149
167
|
event: routedEvent,
|
|
150
168
|
onBack: () => undefined,
|
|
151
169
|
},
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type {TriggerEventDetailResponseDto} from '@shipfox/api-triggers-dto';
|
|
2
|
+
import {projectsQueryKeys} from '@shipfox/client-projects';
|
|
2
3
|
import {RelativeTimeProvider} from '@shipfox/react-ui/relative-time';
|
|
4
|
+
import {QueryClient, QueryClientProvider} from '@tanstack/react-query';
|
|
3
5
|
import {fireEvent, render, screen} from '@testing-library/react';
|
|
4
6
|
import userEvent from '@testing-library/user-event';
|
|
5
7
|
import type {ReactElement} from 'react';
|
|
@@ -60,17 +62,39 @@ function makeEvent(
|
|
|
60
62
|
};
|
|
61
63
|
}
|
|
62
64
|
|
|
63
|
-
function renderWithProviders(
|
|
64
|
-
|
|
65
|
+
function renderWithProviders(
|
|
66
|
+
ui: ReactElement,
|
|
67
|
+
{includeListProject = true, includeDetailProject = false} = {},
|
|
68
|
+
) {
|
|
69
|
+
const queryClient = new QueryClient({
|
|
70
|
+
defaultOptions: {queries: {retry: false, staleTime: Infinity}},
|
|
71
|
+
});
|
|
72
|
+
const project = {id: PROJECT_ID, slug: 'checkout-api'};
|
|
73
|
+
queryClient.setQueryData(projectsQueryKeys.list(WORKSPACE_ID), {
|
|
74
|
+
pages: [{projects: includeListProject ? [project] : [], nextCursor: null}],
|
|
75
|
+
pageParams: [undefined],
|
|
76
|
+
});
|
|
77
|
+
if (includeDetailProject) queryClient.setQueryData(projectsQueryKeys.detail(PROJECT_ID), project);
|
|
78
|
+
return render(
|
|
79
|
+
<QueryClientProvider client={queryClient}>
|
|
80
|
+
<RelativeTimeProvider>{ui}</RelativeTimeProvider>
|
|
81
|
+
</QueryClientProvider>,
|
|
82
|
+
);
|
|
65
83
|
}
|
|
66
84
|
|
|
67
|
-
function renderDetailView(
|
|
85
|
+
function renderDetailView(
|
|
86
|
+
event: TriggerEventDetailResponseDto,
|
|
87
|
+
onBack = vi.fn(),
|
|
88
|
+
options?: {includeListProject?: boolean; includeDetailProject?: boolean},
|
|
89
|
+
) {
|
|
68
90
|
return renderWithProviders(
|
|
69
91
|
<TriggerEventDetailView
|
|
70
92
|
workspaceId={WORKSPACE_ID}
|
|
93
|
+
workspaceSlug="acme"
|
|
71
94
|
event={toTriggerEventDetail(event)}
|
|
72
95
|
onBack={onBack}
|
|
73
96
|
/>,
|
|
97
|
+
options,
|
|
74
98
|
);
|
|
75
99
|
}
|
|
76
100
|
|
|
@@ -82,7 +106,7 @@ describe('TriggerEventDetailView', () => {
|
|
|
82
106
|
expect(screen.getByText('Deploy production')).toBeInTheDocument();
|
|
83
107
|
expect(screen.getByRole('link', {name: DEPLOY_RUN_LINK_NAME})).toHaveAttribute(
|
|
84
108
|
'href',
|
|
85
|
-
`/
|
|
109
|
+
`/w/acme/p/checkout-api/runs/${RUN_ID}`,
|
|
86
110
|
);
|
|
87
111
|
expect(screen.getByText(PAYLOAD_REF_LINE)).toBeInTheDocument();
|
|
88
112
|
});
|
|
@@ -157,6 +181,18 @@ describe('TriggerEventDetailView', () => {
|
|
|
157
181
|
expect(screen.queryByRole('link')).not.toBeInTheDocument();
|
|
158
182
|
});
|
|
159
183
|
|
|
184
|
+
test('resolves a matched project from its detail query when it is not in the list page', async () => {
|
|
185
|
+
renderDetailView(makeEvent(), vi.fn(), {
|
|
186
|
+
includeListProject: false,
|
|
187
|
+
includeDetailProject: true,
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
expect(await screen.findByRole('link', {name: DEPLOY_RUN_LINK_NAME})).toHaveAttribute(
|
|
191
|
+
'href',
|
|
192
|
+
`/w/acme/p/checkout-api/runs/${RUN_ID}`,
|
|
193
|
+
);
|
|
194
|
+
});
|
|
195
|
+
|
|
160
196
|
test('calls onBack from the focused detail panel control', async () => {
|
|
161
197
|
const onBack = vi.fn();
|
|
162
198
|
renderDetailView(makeEvent(), onBack);
|
|
@@ -179,9 +215,7 @@ describe('TriggerEventDetail', () => {
|
|
|
179
215
|
refetch: vi.fn(),
|
|
180
216
|
} as unknown as ReturnType<typeof useTriggerEventQuery>);
|
|
181
217
|
|
|
182
|
-
renderWithProviders(
|
|
183
|
-
<TriggerEventDetail workspaceId={WORKSPACE_ID} eventId={EVENT_ID} onBack={vi.fn()} />,
|
|
184
|
-
);
|
|
218
|
+
renderWithProviders(<TriggerEventDetail eventId={EVENT_ID} onBack={vi.fn()} />);
|
|
185
219
|
|
|
186
220
|
expect(await screen.findByRole('button', {name: 'Back to events'})).toBeInTheDocument();
|
|
187
221
|
});
|
|
@@ -194,9 +228,7 @@ describe('TriggerEventDetail', () => {
|
|
|
194
228
|
refetch,
|
|
195
229
|
} as unknown as ReturnType<typeof useTriggerEventQuery>);
|
|
196
230
|
|
|
197
|
-
renderWithProviders(
|
|
198
|
-
<TriggerEventDetail workspaceId={WORKSPACE_ID} eventId={EVENT_ID} onBack={vi.fn()} />,
|
|
199
|
-
);
|
|
231
|
+
renderWithProviders(<TriggerEventDetail eventId={EVENT_ID} onBack={vi.fn()} />);
|
|
200
232
|
|
|
201
233
|
fireEvent.click(await screen.findByRole('button', {name: 'Retry'}));
|
|
202
234
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {useProjectQuery, useProjectsInfiniteQuery} from '@shipfox/client-projects';
|
|
1
2
|
import {Badge} from '@shipfox/react-ui/badge';
|
|
2
3
|
import {Button} from '@shipfox/react-ui/button';
|
|
3
4
|
import {Callout} from '@shipfox/react-ui/callout';
|
|
@@ -34,17 +35,30 @@ const DETAIL_RAIL_CLASS =
|
|
|
34
35
|
'@min-[820px]:sticky @min-[820px]:top-16 @min-[820px]:max-h-[calc(var(--app-content-h,100dvh_-_96px)_-_32px)] @min-[820px]:min-h-[min(320px,calc(var(--app-content-h,100dvh_-_96px)_-_32px))]';
|
|
35
36
|
|
|
36
37
|
export interface TriggerEventDetailProps {
|
|
37
|
-
workspaceId
|
|
38
|
+
workspaceId?: string | undefined;
|
|
39
|
+
workspaceSlug?: string | undefined;
|
|
38
40
|
eventId?: string | undefined;
|
|
39
41
|
onBack: () => void;
|
|
40
42
|
}
|
|
41
43
|
|
|
42
|
-
export function TriggerEventDetail({
|
|
44
|
+
export function TriggerEventDetail({
|
|
45
|
+
workspaceId,
|
|
46
|
+
workspaceSlug,
|
|
47
|
+
eventId,
|
|
48
|
+
onBack,
|
|
49
|
+
}: TriggerEventDetailProps) {
|
|
43
50
|
const query = useTriggerEventQuery(eventId);
|
|
44
51
|
|
|
45
52
|
if (!eventId) return <TriggerEventDetailPlaceholder />;
|
|
46
53
|
if (query.data) {
|
|
47
|
-
return
|
|
54
|
+
return (
|
|
55
|
+
<TriggerEventDetailView
|
|
56
|
+
workspaceId={workspaceId}
|
|
57
|
+
workspaceSlug={workspaceSlug}
|
|
58
|
+
event={query.data}
|
|
59
|
+
onBack={onBack}
|
|
60
|
+
/>
|
|
61
|
+
);
|
|
48
62
|
}
|
|
49
63
|
if (query.isError)
|
|
50
64
|
return <TriggerEventDetailError onBack={onBack} onRetry={() => query.refetch()} />;
|
|
@@ -53,10 +67,12 @@ export function TriggerEventDetail({workspaceId, eventId, onBack}: TriggerEventD
|
|
|
53
67
|
|
|
54
68
|
export function TriggerEventDetailView({
|
|
55
69
|
workspaceId,
|
|
70
|
+
workspaceSlug,
|
|
56
71
|
event,
|
|
57
72
|
onBack,
|
|
58
73
|
}: {
|
|
59
|
-
workspaceId
|
|
74
|
+
workspaceId?: string | undefined;
|
|
75
|
+
workspaceSlug?: string | undefined;
|
|
60
76
|
event: TriggerEventDetailModel;
|
|
61
77
|
onBack: () => void;
|
|
62
78
|
}) {
|
|
@@ -124,7 +140,7 @@ export function TriggerEventDetailView({
|
|
|
124
140
|
key={event.id}
|
|
125
141
|
className="flex min-h-0 flex-1 flex-col gap-20 overflow-y-auto p-16 scrollbar"
|
|
126
142
|
>
|
|
127
|
-
<EventRuns workspaceId={workspaceId} event={event} />
|
|
143
|
+
<EventRuns workspaceId={workspaceId} workspaceSlug={workspaceSlug} event={event} />
|
|
128
144
|
<EventPayload payload={formattedPayload} />
|
|
129
145
|
</div>
|
|
130
146
|
</aside>
|
|
@@ -207,7 +223,15 @@ function TriggerEventDetailError({onBack, onRetry}: {onBack: () => void; onRetry
|
|
|
207
223
|
);
|
|
208
224
|
}
|
|
209
225
|
|
|
210
|
-
function EventRuns({
|
|
226
|
+
function EventRuns({
|
|
227
|
+
workspaceId,
|
|
228
|
+
workspaceSlug,
|
|
229
|
+
event,
|
|
230
|
+
}: {
|
|
231
|
+
workspaceId?: string | undefined;
|
|
232
|
+
workspaceSlug?: string | undefined;
|
|
233
|
+
event: TriggerEventDetailModel;
|
|
234
|
+
}) {
|
|
211
235
|
if (event.decisions.length === 0) {
|
|
212
236
|
if (event.outcome === 'discarded') {
|
|
213
237
|
return (
|
|
@@ -219,6 +243,55 @@ function EventRuns({workspaceId, event}: {workspaceId: string; event: TriggerEve
|
|
|
219
243
|
return null;
|
|
220
244
|
}
|
|
221
245
|
|
|
246
|
+
if (!workspaceId) {
|
|
247
|
+
return <EventRunsList workspaceSlug={workspaceSlug} projectSlugs={new Map()} event={event} />;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return (
|
|
251
|
+
<EventRunsWithProjects workspaceId={workspaceId} workspaceSlug={workspaceSlug} event={event} />
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function EventRunsWithProjects({
|
|
256
|
+
workspaceId,
|
|
257
|
+
workspaceSlug,
|
|
258
|
+
event,
|
|
259
|
+
}: {
|
|
260
|
+
workspaceId: string;
|
|
261
|
+
workspaceSlug?: string | undefined;
|
|
262
|
+
event: TriggerEventDetailModel;
|
|
263
|
+
}) {
|
|
264
|
+
const projectsQuery = useProjectsInfiniteQuery(workspaceId);
|
|
265
|
+
const projectSlugs = useMemo(
|
|
266
|
+
() =>
|
|
267
|
+
new Map(
|
|
268
|
+
projectsQuery.data?.pages
|
|
269
|
+
.flatMap((page) => page.projects)
|
|
270
|
+
.map((project) => [project.id, project.slug] as const),
|
|
271
|
+
),
|
|
272
|
+
[projectsQuery.data],
|
|
273
|
+
);
|
|
274
|
+
return (
|
|
275
|
+
<EventRunsList
|
|
276
|
+
workspaceSlug={workspaceSlug}
|
|
277
|
+
projectSlugs={projectSlugs}
|
|
278
|
+
projectDetailLookupEnabled={!projectsQuery.isPending}
|
|
279
|
+
event={event}
|
|
280
|
+
/>
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function EventRunsList({
|
|
285
|
+
workspaceSlug,
|
|
286
|
+
projectSlugs,
|
|
287
|
+
projectDetailLookupEnabled = false,
|
|
288
|
+
event,
|
|
289
|
+
}: {
|
|
290
|
+
workspaceSlug?: string | undefined;
|
|
291
|
+
projectSlugs: ReadonlyMap<string, string>;
|
|
292
|
+
projectDetailLookupEnabled?: boolean;
|
|
293
|
+
event: TriggerEventDetailModel;
|
|
294
|
+
}) {
|
|
222
295
|
return (
|
|
223
296
|
<section aria-labelledby="trigger-event-runs-heading" className="flex flex-col gap-6">
|
|
224
297
|
<Text id="trigger-event-runs-heading" size="sm" bold>
|
|
@@ -226,7 +299,14 @@ function EventRuns({workspaceId, event}: {workspaceId: string; event: TriggerEve
|
|
|
226
299
|
</Text>
|
|
227
300
|
<ul className="-mx-8 flex flex-col gap-1">
|
|
228
301
|
{event.decisions.map((decision) => (
|
|
229
|
-
<DecisionRow
|
|
302
|
+
<DecisionRow
|
|
303
|
+
key={decision.id}
|
|
304
|
+
workspaceSlug={workspaceSlug}
|
|
305
|
+
projectId={decision.projectId ?? undefined}
|
|
306
|
+
projectSlug={decision.projectId ? projectSlugs.get(decision.projectId) : undefined}
|
|
307
|
+
projectDetailLookupEnabled={projectDetailLookupEnabled}
|
|
308
|
+
decision={decision}
|
|
309
|
+
/>
|
|
230
310
|
))}
|
|
231
311
|
</ul>
|
|
232
312
|
</section>
|
|
@@ -234,12 +314,25 @@ function EventRuns({workspaceId, event}: {workspaceId: string; event: TriggerEve
|
|
|
234
314
|
}
|
|
235
315
|
|
|
236
316
|
function DecisionRow({
|
|
237
|
-
|
|
317
|
+
projectId,
|
|
318
|
+
projectSlug,
|
|
319
|
+
workspaceSlug,
|
|
320
|
+
projectDetailLookupEnabled,
|
|
238
321
|
decision,
|
|
239
322
|
}: {
|
|
240
|
-
|
|
323
|
+
projectId?: string | undefined;
|
|
324
|
+
projectSlug?: string | undefined;
|
|
325
|
+
workspaceSlug?: string | undefined;
|
|
326
|
+
projectDetailLookupEnabled: boolean;
|
|
241
327
|
decision: TriggerEventMatchedWorkflowResult;
|
|
242
328
|
}) {
|
|
329
|
+
const canRenderRunLink =
|
|
330
|
+
decision.decision === 'triggered' && Boolean(decision.runId && decision.runName);
|
|
331
|
+
const projectQuery = useProjectQuery(
|
|
332
|
+
canRenderRunLink && projectDetailLookupEnabled && !projectSlug ? projectId : undefined,
|
|
333
|
+
);
|
|
334
|
+
const resolvedProjectSlug = projectSlug ?? projectQuery.data?.slug;
|
|
335
|
+
|
|
243
336
|
if (decision.decision !== 'triggered' || !decision.runId || !decision.runName) {
|
|
244
337
|
return (
|
|
245
338
|
<li className="flex min-w-0 items-start gap-8 rounded-6 px-8 py-6">
|
|
@@ -266,27 +359,43 @@ function DecisionRow({
|
|
|
266
359
|
);
|
|
267
360
|
}
|
|
268
361
|
|
|
362
|
+
const row = (
|
|
363
|
+
<>
|
|
364
|
+
<Icon
|
|
365
|
+
name="cornerDownRightLine"
|
|
366
|
+
className="mt-3 size-14 shrink-0 text-foreground-neutral-muted"
|
|
367
|
+
aria-hidden="true"
|
|
368
|
+
/>
|
|
369
|
+
<span className="flex min-w-0 flex-col gap-1">
|
|
370
|
+
<Text as="span" size="sm" className="min-w-0 truncate text-foreground-neutral-base">
|
|
371
|
+
{decision.subscriptionName}
|
|
372
|
+
</Text>
|
|
373
|
+
<Code as="span" variant="label" className="truncate text-foreground-neutral-muted">
|
|
374
|
+
{decision.runName}
|
|
375
|
+
</Code>
|
|
376
|
+
</span>
|
|
377
|
+
</>
|
|
378
|
+
);
|
|
379
|
+
const rowClassName =
|
|
380
|
+
'flex min-w-0 items-start gap-8 rounded-6 px-8 py-6 hover:bg-background-components-hover focus-visible:outline-none focus-visible:shadow-button-neutral-focus';
|
|
381
|
+
|
|
269
382
|
return (
|
|
270
383
|
<li>
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
{decision.runName}
|
|
287
|
-
</Code>
|
|
288
|
-
</span>
|
|
289
|
-
</Link>
|
|
384
|
+
{workspaceSlug && resolvedProjectSlug ? (
|
|
385
|
+
<Link
|
|
386
|
+
to="/w/$workspaceSlug/p/$projectSlug/runs/$workflowRunId"
|
|
387
|
+
params={{
|
|
388
|
+
workspaceSlug,
|
|
389
|
+
projectSlug: resolvedProjectSlug,
|
|
390
|
+
workflowRunId: decision.runId,
|
|
391
|
+
}}
|
|
392
|
+
className={rowClassName}
|
|
393
|
+
>
|
|
394
|
+
{row}
|
|
395
|
+
</Link>
|
|
396
|
+
) : (
|
|
397
|
+
<div className={rowClassName}>{row}</div>
|
|
398
|
+
)}
|
|
290
399
|
</li>
|
|
291
400
|
);
|
|
292
401
|
}
|
package/src/components/types.ts
CHANGED
|
@@ -13,7 +13,7 @@ export interface EventsListProps {
|
|
|
13
13
|
facets: TriggerEventFacets | undefined;
|
|
14
14
|
filters: TriggerEventFilters;
|
|
15
15
|
onFiltersChange: (patch: Partial<TriggerEventFilters>) => void;
|
|
16
|
-
|
|
16
|
+
workspaceSlug?: string | undefined;
|
|
17
17
|
hasNextPage: boolean;
|
|
18
18
|
isFetchingNextPage: boolean;
|
|
19
19
|
onLoadMore: () => void;
|
package/src/feature.ts
CHANGED
|
@@ -8,7 +8,7 @@ export const triggersFeature = defineClientFeature({
|
|
|
8
8
|
id: 'shipfox.triggers',
|
|
9
9
|
routes: [
|
|
10
10
|
{
|
|
11
|
-
path: '/
|
|
11
|
+
path: '/w/$workspaceSlug/settings/events',
|
|
12
12
|
parent: 'workspaceSettings',
|
|
13
13
|
impl: '@shipfox/client-triggers/routes/events-settings',
|
|
14
14
|
},
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
|
|
13
13
|
export interface EventsPageProps {
|
|
14
14
|
workspaceId: string;
|
|
15
|
+
workspaceSlug?: string | undefined;
|
|
15
16
|
filters: TriggerEventFilters;
|
|
16
17
|
onFiltersChange: (patch: Partial<TriggerEventFilters>) => void;
|
|
17
18
|
}
|
|
@@ -20,7 +21,12 @@ export interface EventsPageProps {
|
|
|
20
21
|
* Workspace-scoped Events list. Router-agnostic: filters and their setter come in as props
|
|
21
22
|
* (the settings wrapper binds them to the URL), so a story can drive it with local state.
|
|
22
23
|
*/
|
|
23
|
-
export function EventsPage({
|
|
24
|
+
export function EventsPage({
|
|
25
|
+
workspaceId,
|
|
26
|
+
workspaceSlug,
|
|
27
|
+
filters,
|
|
28
|
+
onFiltersChange,
|
|
29
|
+
}: EventsPageProps) {
|
|
24
30
|
const query = useTriggerEventsInfiniteQuery(workspaceId, filters);
|
|
25
31
|
const facetsQuery = useTriggerEventFacetsQuery(workspaceId);
|
|
26
32
|
const [selectedEventId, setSelectedEventId] = useState<string | undefined>();
|
|
@@ -59,7 +65,7 @@ export function EventsPage({workspaceId, filters, onFiltersChange}: EventsPagePr
|
|
|
59
65
|
facets={facetsQuery.data}
|
|
60
66
|
filters={filters}
|
|
61
67
|
onFiltersChange={onFiltersChange}
|
|
62
|
-
|
|
68
|
+
workspaceSlug={workspaceSlug}
|
|
63
69
|
hasNextPage={query.hasNextPage}
|
|
64
70
|
isFetchingNextPage={query.isFetchingNextPage}
|
|
65
71
|
onLoadMore={() => {
|
|
@@ -71,6 +77,7 @@ export function EventsPage({workspaceId, filters, onFiltersChange}: EventsPagePr
|
|
|
71
77
|
</div>
|
|
72
78
|
<TriggerEventDetail
|
|
73
79
|
workspaceId={workspaceId}
|
|
80
|
+
workspaceSlug={workspaceSlug}
|
|
74
81
|
eventId={selectedEventId}
|
|
75
82
|
onBack={() => setSelectedEventId(undefined)}
|
|
76
83
|
/>
|
|
@@ -4,7 +4,7 @@ import type {TriggerEventFilters} from '#core/trigger-event.js';
|
|
|
4
4
|
import {EventsPage} from '#pages/events-page.js';
|
|
5
5
|
import {type TriggerEventsSearch, validateTriggerEventsSearch} from '#search.js';
|
|
6
6
|
|
|
7
|
-
const routeApi = getRouteApi('/
|
|
7
|
+
const routeApi = getRouteApi('/w/$workspaceSlug/settings/events');
|
|
8
8
|
|
|
9
9
|
// Wrapped (not just re-typed) so the search validator's own type is portable outside this
|
|
10
10
|
// package: TanStack Router's `const` generics capture the exact declared function, and a
|
|
@@ -24,7 +24,12 @@ export default defineRoute({
|
|
|
24
24
|
void navigate({search: {...search, ...patch}, replace: true});
|
|
25
25
|
};
|
|
26
26
|
return (
|
|
27
|
-
<EventsPage
|
|
27
|
+
<EventsPage
|
|
28
|
+
workspaceId={workspace.id}
|
|
29
|
+
workspaceSlug={workspace.slug}
|
|
30
|
+
filters={search}
|
|
31
|
+
onFiltersChange={onFiltersChange}
|
|
32
|
+
/>
|
|
28
33
|
);
|
|
29
34
|
},
|
|
30
35
|
});
|