@shipfox/client-triggers 4.0.0 → 6.0.1
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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +49 -0
- package/dist/components/events-filter-bar.d.ts +3 -4
- package/dist/components/events-filter-bar.d.ts.map +1 -1
- package/dist/components/events-filter-bar.js +8 -16
- package/dist/components/events-filter-bar.js.map +1 -1
- package/dist/components/events-list.d.ts.map +1 -1
- package/dist/components/events-list.js +2 -4
- package/dist/components/events-list.js.map +1 -1
- package/dist/components/events-list.stories.d.ts +1 -16
- package/dist/components/events-list.stories.d.ts.map +1 -1
- package/dist/components/events-list.stories.js +2 -1
- package/dist/components/events-list.stories.js.map +1 -1
- package/dist/components/trigger-event-detail.d.ts +2 -2
- package/dist/components/trigger-event-detail.d.ts.map +1 -1
- package/dist/components/trigger-event-detail.js +7 -7
- package/dist/components/trigger-event-detail.js.map +1 -1
- package/dist/components/trigger-event-detail.stories.d.ts +1 -36
- package/dist/components/trigger-event-detail.stories.d.ts.map +1 -1
- package/dist/components/trigger-event-detail.stories.js +6 -3
- package/dist/components/trigger-event-detail.stories.js.map +1 -1
- package/dist/components/trigger-event-result.d.ts +2 -2
- package/dist/components/trigger-event-result.d.ts.map +1 -1
- package/dist/components/trigger-event-result.js +8 -16
- package/dist/components/trigger-event-result.js.map +1 -1
- package/dist/components/trigger-event-row.d.ts +2 -2
- package/dist/components/trigger-event-row.d.ts.map +1 -1
- package/dist/components/trigger-event-row.js +1 -1
- package/dist/components/trigger-event-row.js.map +1 -1
- package/dist/components/trigger-source-icon.d.ts +8 -6
- package/dist/components/trigger-source-icon.d.ts.map +1 -1
- package/dist/components/trigger-source-icon.js +4 -3
- package/dist/components/trigger-source-icon.js.map +1 -1
- package/dist/components/types.d.ts +3 -4
- package/dist/components/types.d.ts.map +1 -1
- package/dist/components/types.js.map +1 -1
- package/dist/core/trigger-event.d.ts +78 -0
- package/dist/core/trigger-event.d.ts.map +1 -0
- package/dist/core/trigger-event.js +71 -0
- package/dist/core/trigger-event.js.map +1 -0
- package/dist/feature.d.ts +23 -0
- package/dist/feature.d.ts.map +1 -0
- package/dist/feature.js +23 -0
- package/dist/feature.js.map +1 -0
- package/dist/hooks/api/trigger-event-mapper.d.ts +7 -0
- package/dist/hooks/api/trigger-event-mapper.d.ts.map +1 -0
- package/dist/hooks/api/trigger-event-mapper.js +57 -0
- package/dist/hooks/api/trigger-event-mapper.js.map +1 -0
- package/dist/hooks/api/trigger-events.d.ts +15 -138
- package/dist/hooks/api/trigger-events.d.ts.map +1 -1
- package/dist/hooks/api/trigger-events.js +44 -47
- package/dist/hooks/api/trigger-events.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/pages/events-page.d.ts +1 -1
- package/dist/pages/events-page.d.ts.map +1 -1
- package/dist/pages/events-page.js +1 -1
- package/dist/pages/events-page.js.map +1 -1
- package/dist/routes/events-settings.d.ts +8 -0
- package/dist/routes/events-settings.d.ts.map +1 -0
- package/dist/routes/events-settings.js +37 -0
- package/dist/routes/events-settings.js.map +1 -0
- package/dist/search.d.ts +3 -3
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +3 -4
- package/dist/search.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +18 -9
- package/src/components/events-filter-bar.tsx +16 -11
- package/src/components/events-list.stories.tsx +3 -2
- package/src/components/events-list.test.tsx +4 -3
- package/src/components/events-list.tsx +2 -12
- package/src/components/trigger-event-detail.stories.tsx +7 -3
- package/src/components/trigger-event-detail.test.tsx +6 -1
- package/src/components/trigger-event-detail.tsx +21 -18
- package/src/components/trigger-event-result.test.ts +6 -6
- package/src/components/trigger-event-result.ts +9 -13
- package/src/components/trigger-event-row.tsx +5 -5
- package/src/components/trigger-source-icon.tsx +9 -7
- package/src/components/types.ts +7 -7
- package/src/core/trigger-event.test.ts +40 -0
- package/src/core/trigger-event.ts +126 -0
- package/src/feature.ts +17 -0
- package/src/hooks/api/trigger-event-mapper.test.ts +88 -0
- package/src/hooks/api/trigger-event-mapper.ts +76 -0
- package/src/hooks/api/trigger-events.test.ts +49 -12
- package/src/hooks/api/trigger-events.ts +96 -56
- package/src/index.ts +1 -0
- package/src/pages/events-page.test.tsx +4 -4
- package/src/pages/events-page.tsx +3 -4
- package/src/routes/events-settings.tsx +30 -0
- package/src/search.ts +4 -5
- package/tsconfig.build.tsbuildinfo +1 -1
- package/vercel.json +5 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
export type TriggerEventOrigin = 'integration' | 'manual' | 'cron';
|
|
2
|
+
|
|
3
|
+
export type TriggerEventOutcome = 'received' | 'routed' | 'discarded' | 'failed' | 'errored';
|
|
4
|
+
|
|
5
|
+
export const triggerEventOutcomes = [
|
|
6
|
+
'received',
|
|
7
|
+
'routed',
|
|
8
|
+
'discarded',
|
|
9
|
+
'failed',
|
|
10
|
+
'errored',
|
|
11
|
+
] as const satisfies readonly TriggerEventOutcome[];
|
|
12
|
+
|
|
13
|
+
export type TriggerEventDecisionOutcome = 'triggered' | 'filter-error' | 'dispatch-error';
|
|
14
|
+
|
|
15
|
+
export interface TriggerEventSource {
|
|
16
|
+
provider: string | null;
|
|
17
|
+
source: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface TriggerEventSummary extends TriggerEventSource {
|
|
21
|
+
id: string;
|
|
22
|
+
eventRef: string;
|
|
23
|
+
origin: TriggerEventOrigin;
|
|
24
|
+
workspaceId: string;
|
|
25
|
+
event: string;
|
|
26
|
+
deliveryId: string | null;
|
|
27
|
+
connectionId: string | null;
|
|
28
|
+
outcome: TriggerEventOutcome;
|
|
29
|
+
matchedCount: number;
|
|
30
|
+
receivedAt: string;
|
|
31
|
+
processedAt: string | null;
|
|
32
|
+
createdAt: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface TriggerEventMatchedWorkflowResult {
|
|
36
|
+
id: string;
|
|
37
|
+
subscriptionName: string;
|
|
38
|
+
decision: TriggerEventDecisionOutcome;
|
|
39
|
+
projectId: string | null;
|
|
40
|
+
runId: string | null;
|
|
41
|
+
runName: string | null;
|
|
42
|
+
reason: string | null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface TriggerEventDetail extends TriggerEventSummary {
|
|
46
|
+
connectionName: string | null;
|
|
47
|
+
payload: Record<string, unknown> | null;
|
|
48
|
+
decisions: TriggerEventMatchedWorkflowResult[];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface TriggerEventFacetItem {
|
|
52
|
+
value: string;
|
|
53
|
+
count: number;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface TriggerEventFacets {
|
|
57
|
+
sources: TriggerEventFacetItem[];
|
|
58
|
+
events: TriggerEventFacetItem[];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface TriggerEventListPage {
|
|
62
|
+
events: TriggerEventSummary[];
|
|
63
|
+
nextCursor: string | null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface TriggerEventFilters {
|
|
67
|
+
source?: string[] | undefined;
|
|
68
|
+
event?: string[] | undefined;
|
|
69
|
+
outcome?: TriggerEventOutcome[] | undefined;
|
|
70
|
+
from?: string | undefined;
|
|
71
|
+
to?: string | undefined;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function normalizeTriggerEventFilterValues(
|
|
75
|
+
values: readonly string[] | undefined,
|
|
76
|
+
): string[] | null {
|
|
77
|
+
return values && values.length > 0 ? [...new Set(values)].sort() : null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function normalizeTriggerEventFilters(filters: TriggerEventFilters) {
|
|
81
|
+
return {
|
|
82
|
+
source: normalizeTriggerEventFilterValues(filters.source),
|
|
83
|
+
event: normalizeTriggerEventFilterValues(filters.event),
|
|
84
|
+
outcome: normalizeTriggerEventFilterValues(filters.outcome),
|
|
85
|
+
from: filters.from ?? null,
|
|
86
|
+
to: filters.to ?? null,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function hasTriggerEventFilters(filters: TriggerEventFilters): boolean {
|
|
91
|
+
const normalized = normalizeTriggerEventFilters(filters);
|
|
92
|
+
return Boolean(
|
|
93
|
+
normalized.source || normalized.event || normalized.outcome || normalized.from || normalized.to,
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export type TriggerEventResultKind = 'triggered' | 'no-match' | 'failed' | 'evaluating';
|
|
98
|
+
|
|
99
|
+
export interface TriggerEventResult {
|
|
100
|
+
kind: TriggerEventResultKind;
|
|
101
|
+
matchedWorkflowCount: number;
|
|
102
|
+
isFailure: boolean;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function getTriggerEventResult(
|
|
106
|
+
event: Pick<TriggerEventSummary, 'outcome' | 'matchedCount'>,
|
|
107
|
+
): TriggerEventResult {
|
|
108
|
+
switch (event.outcome) {
|
|
109
|
+
case 'routed':
|
|
110
|
+
return {kind: 'triggered', matchedWorkflowCount: event.matchedCount, isFailure: false};
|
|
111
|
+
case 'discarded':
|
|
112
|
+
return {kind: 'no-match', matchedWorkflowCount: 0, isFailure: false};
|
|
113
|
+
case 'failed':
|
|
114
|
+
case 'errored':
|
|
115
|
+
return {kind: 'failed', matchedWorkflowCount: event.matchedCount, isFailure: true};
|
|
116
|
+
case 'received':
|
|
117
|
+
return {kind: 'evaluating', matchedWorkflowCount: 0, isFailure: false};
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export const triggerEventResultFilterOutcomes = {
|
|
122
|
+
triggered: ['routed'],
|
|
123
|
+
'no-match': ['discarded'],
|
|
124
|
+
failed: ['failed', 'errored'],
|
|
125
|
+
evaluating: ['received'],
|
|
126
|
+
} as const satisfies Record<TriggerEventResultKind, readonly TriggerEventOutcome[]>;
|
package/src/feature.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import {defineClientFeature, type SettingsSectionEntry} from '@shipfox/client-shell';
|
|
2
|
+
|
|
3
|
+
export const triggersSettingsSections = [
|
|
4
|
+
{id: 'settings.events', pathSegment: 'events', label: 'Events', icon: 'pulseLine', order: 800},
|
|
5
|
+
] as const satisfies readonly SettingsSectionEntry[];
|
|
6
|
+
|
|
7
|
+
export const triggersFeature = defineClientFeature({
|
|
8
|
+
id: 'shipfox.triggers',
|
|
9
|
+
routes: [
|
|
10
|
+
{
|
|
11
|
+
path: '/workspaces/$wid/settings/events',
|
|
12
|
+
parent: 'workspaceSettings',
|
|
13
|
+
impl: '@shipfox/client-triggers/routes/events-settings',
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
settingsSections: triggersSettingsSections,
|
|
17
|
+
});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
TriggerEventDetailResponseDto,
|
|
3
|
+
TriggerEventListItemDto,
|
|
4
|
+
} from '@shipfox/api-triggers-dto';
|
|
5
|
+
import {toTriggerEventDetail, toTriggerEventListPage} from './trigger-event-mapper.js';
|
|
6
|
+
|
|
7
|
+
const EVENT_ID = '22222222-2222-4222-8222-222222222222';
|
|
8
|
+
const WORKSPACE_ID = '11111111-1111-4111-8111-111111111111';
|
|
9
|
+
|
|
10
|
+
function triggerEventDto(
|
|
11
|
+
overrides: Partial<TriggerEventListItemDto> = {},
|
|
12
|
+
): TriggerEventListItemDto {
|
|
13
|
+
return {
|
|
14
|
+
id: EVENT_ID,
|
|
15
|
+
event_ref: 'github:delivery-1:push',
|
|
16
|
+
origin: 'integration' as const,
|
|
17
|
+
workspace_id: WORKSPACE_ID,
|
|
18
|
+
provider: 'github',
|
|
19
|
+
source: 'github',
|
|
20
|
+
event: 'push',
|
|
21
|
+
delivery_id: 'delivery-1',
|
|
22
|
+
connection_id: '33333333-3333-4333-8333-333333333333',
|
|
23
|
+
outcome: 'routed' as const,
|
|
24
|
+
matched_count: 1,
|
|
25
|
+
received_at: '2026-06-01T00:00:00.000Z',
|
|
26
|
+
processed_at: '2026-06-01T00:00:01.000Z',
|
|
27
|
+
created_at: '2026-06-01T00:00:00.000Z',
|
|
28
|
+
...overrides,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
describe('trigger event mapper', () => {
|
|
33
|
+
test('maps a transport list page before it enters the cache', () => {
|
|
34
|
+
const page = toTriggerEventListPage({trigger_events: [triggerEventDto()], next_cursor: 'next'});
|
|
35
|
+
|
|
36
|
+
expect(page).toEqual({
|
|
37
|
+
events: [
|
|
38
|
+
expect.objectContaining({
|
|
39
|
+
eventRef: 'github:delivery-1:push',
|
|
40
|
+
workspaceId: WORKSPACE_ID,
|
|
41
|
+
matchedCount: 1,
|
|
42
|
+
receivedAt: '2026-06-01T00:00:00.000Z',
|
|
43
|
+
}),
|
|
44
|
+
],
|
|
45
|
+
nextCursor: 'next',
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('maps matched workflow results into their domain contract', () => {
|
|
50
|
+
const detail = toTriggerEventDetail({
|
|
51
|
+
...triggerEventDto(),
|
|
52
|
+
connection_name: 'Shipfox',
|
|
53
|
+
payload: {ref: 'main'},
|
|
54
|
+
decisions: [
|
|
55
|
+
{
|
|
56
|
+
id: '44444444-4444-4444-8444-444444444444',
|
|
57
|
+
received_event_id: EVENT_ID,
|
|
58
|
+
subscription_kind: 'trigger',
|
|
59
|
+
subscription_id: '55555555-5555-4555-8555-555555555555',
|
|
60
|
+
subscription_name: 'Deploy',
|
|
61
|
+
workflow_definition_id: '66666666-6666-4666-8666-666666666666',
|
|
62
|
+
project_id: '77777777-7777-4777-8777-777777777777',
|
|
63
|
+
workflow_run_id: null,
|
|
64
|
+
job_id: null,
|
|
65
|
+
matcher_kind: null,
|
|
66
|
+
matcher_ordinal: null,
|
|
67
|
+
decision: 'triggered',
|
|
68
|
+
run_id: '88888888-8888-4888-8888-888888888888',
|
|
69
|
+
run_name: 'deploy #1',
|
|
70
|
+
reason: null,
|
|
71
|
+
created_at: '2026-06-01T00:00:01.000Z',
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
} satisfies TriggerEventDetailResponseDto);
|
|
75
|
+
|
|
76
|
+
expect(detail.decisions).toEqual([
|
|
77
|
+
{
|
|
78
|
+
id: '44444444-4444-4444-8444-444444444444',
|
|
79
|
+
subscriptionName: 'Deploy',
|
|
80
|
+
decision: 'triggered',
|
|
81
|
+
projectId: '77777777-7777-4777-8777-777777777777',
|
|
82
|
+
runId: '88888888-8888-4888-8888-888888888888',
|
|
83
|
+
runName: 'deploy #1',
|
|
84
|
+
reason: null,
|
|
85
|
+
},
|
|
86
|
+
]);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
TriggerDecisionDto,
|
|
3
|
+
TriggerEventDetailResponseDto,
|
|
4
|
+
TriggerEventFacetItemDto,
|
|
5
|
+
TriggerEventFacetsResponseDto,
|
|
6
|
+
TriggerEventListItemDto,
|
|
7
|
+
TriggerEventListResponseDto,
|
|
8
|
+
} from '@shipfox/api-triggers-dto';
|
|
9
|
+
import type {
|
|
10
|
+
TriggerEventDetail,
|
|
11
|
+
TriggerEventFacetItem,
|
|
12
|
+
TriggerEventFacets,
|
|
13
|
+
TriggerEventListPage,
|
|
14
|
+
TriggerEventMatchedWorkflowResult,
|
|
15
|
+
TriggerEventSummary,
|
|
16
|
+
} from '#core/trigger-event.js';
|
|
17
|
+
|
|
18
|
+
export function toTriggerEventSummary(event: TriggerEventListItemDto): TriggerEventSummary {
|
|
19
|
+
return {
|
|
20
|
+
id: event.id,
|
|
21
|
+
eventRef: event.event_ref,
|
|
22
|
+
origin: event.origin,
|
|
23
|
+
workspaceId: event.workspace_id,
|
|
24
|
+
provider: event.provider,
|
|
25
|
+
source: event.source,
|
|
26
|
+
event: event.event,
|
|
27
|
+
deliveryId: event.delivery_id,
|
|
28
|
+
connectionId: event.connection_id,
|
|
29
|
+
outcome: event.outcome,
|
|
30
|
+
matchedCount: event.matched_count,
|
|
31
|
+
receivedAt: event.received_at,
|
|
32
|
+
processedAt: event.processed_at,
|
|
33
|
+
createdAt: event.created_at,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function toTriggerEventListPage(
|
|
38
|
+
response: TriggerEventListResponseDto,
|
|
39
|
+
): TriggerEventListPage {
|
|
40
|
+
return {
|
|
41
|
+
events: response.trigger_events.map(toTriggerEventSummary),
|
|
42
|
+
nextCursor: response.next_cursor,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function toMatchedWorkflowResult(decision: TriggerDecisionDto): TriggerEventMatchedWorkflowResult {
|
|
47
|
+
return {
|
|
48
|
+
id: decision.id,
|
|
49
|
+
subscriptionName: decision.subscription_name,
|
|
50
|
+
decision: decision.decision,
|
|
51
|
+
projectId: decision.project_id,
|
|
52
|
+
runId: decision.run_id,
|
|
53
|
+
runName: decision.run_name,
|
|
54
|
+
reason: decision.reason,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function toTriggerEventDetail(response: TriggerEventDetailResponseDto): TriggerEventDetail {
|
|
59
|
+
return {
|
|
60
|
+
...toTriggerEventSummary(response),
|
|
61
|
+
connectionName: response.connection_name,
|
|
62
|
+
payload: response.payload,
|
|
63
|
+
decisions: response.decisions.map(toMatchedWorkflowResult),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function toTriggerEventFacetItem(item: TriggerEventFacetItemDto): TriggerEventFacetItem {
|
|
68
|
+
return {value: item.value, count: item.count};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function toTriggerEventFacets(response: TriggerEventFacetsResponseDto): TriggerEventFacets {
|
|
72
|
+
return {
|
|
73
|
+
sources: response.sources.map(toTriggerEventFacetItem),
|
|
74
|
+
events: response.events.map(toTriggerEventFacetItem),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -18,9 +18,32 @@ function requestFrom(fetchImpl: ReturnType<typeof vi.fn>): Request {
|
|
|
18
18
|
return fetchImpl.mock.calls[0]?.[0] as Request;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
const EVENT_ID = '22222222-2222-4222-8222-222222222222';
|
|
22
|
+
const WORKSPACE_ID = '11111111-1111-4111-8111-111111111111';
|
|
23
|
+
|
|
24
|
+
function triggerEventDto(overrides: Record<string, unknown> = {}) {
|
|
25
|
+
return {
|
|
26
|
+
id: EVENT_ID,
|
|
27
|
+
event_ref: 'github:delivery-1:push',
|
|
28
|
+
origin: 'integration',
|
|
29
|
+
workspace_id: WORKSPACE_ID,
|
|
30
|
+
provider: 'github',
|
|
31
|
+
source: 'github',
|
|
32
|
+
event: 'push',
|
|
33
|
+
delivery_id: 'delivery-1',
|
|
34
|
+
connection_id: '33333333-3333-4333-8333-333333333333',
|
|
35
|
+
outcome: 'routed',
|
|
36
|
+
matched_count: 1,
|
|
37
|
+
received_at: '2026-06-01T00:00:00.000Z',
|
|
38
|
+
processed_at: '2026-06-01T00:00:01.000Z',
|
|
39
|
+
created_at: '2026-06-01T00:00:00.000Z',
|
|
40
|
+
...overrides,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
21
44
|
describe('triggerEventsQueryKeys', () => {
|
|
22
45
|
test('list key nests under the workspace lists key', () => {
|
|
23
|
-
const workspaceId =
|
|
46
|
+
const workspaceId = WORKSPACE_ID;
|
|
24
47
|
|
|
25
48
|
const key = triggerEventsQueryKeys.list(workspaceId, {});
|
|
26
49
|
|
|
@@ -35,7 +58,7 @@ describe('triggerEventsQueryKeys', () => {
|
|
|
35
58
|
});
|
|
36
59
|
|
|
37
60
|
test('keeps distinct page limits on distinct keys', () => {
|
|
38
|
-
const workspaceId =
|
|
61
|
+
const workspaceId = WORKSPACE_ID;
|
|
39
62
|
|
|
40
63
|
const defaultLimit = triggerEventsQueryKeys.list(workspaceId, {});
|
|
41
64
|
const compactLimit = triggerEventsQueryKeys.list(workspaceId, {}, 25);
|
|
@@ -46,7 +69,7 @@ describe('triggerEventsQueryKeys', () => {
|
|
|
46
69
|
});
|
|
47
70
|
|
|
48
71
|
test('normalizes absent filter fields to null so empty variants share a key', () => {
|
|
49
|
-
const workspaceId =
|
|
72
|
+
const workspaceId = WORKSPACE_ID;
|
|
50
73
|
|
|
51
74
|
const empty = triggerEventsQueryKeys.list(workspaceId, {});
|
|
52
75
|
const allUndefined = triggerEventsQueryKeys.list(workspaceId, {
|
|
@@ -61,7 +84,7 @@ describe('triggerEventsQueryKeys', () => {
|
|
|
61
84
|
});
|
|
62
85
|
|
|
63
86
|
test('treats an empty outcome array as no outcome filter', () => {
|
|
64
|
-
const workspaceId =
|
|
87
|
+
const workspaceId = WORKSPACE_ID;
|
|
65
88
|
|
|
66
89
|
const key = triggerEventsQueryKeys.list(workspaceId, {outcome: []});
|
|
67
90
|
|
|
@@ -69,7 +92,7 @@ describe('triggerEventsQueryKeys', () => {
|
|
|
69
92
|
});
|
|
70
93
|
|
|
71
94
|
test('treats empty source and event arrays as no filter', () => {
|
|
72
|
-
const workspaceId =
|
|
95
|
+
const workspaceId = WORKSPACE_ID;
|
|
73
96
|
|
|
74
97
|
const key = triggerEventsQueryKeys.list(workspaceId, {source: [], event: []});
|
|
75
98
|
|
|
@@ -77,7 +100,7 @@ describe('triggerEventsQueryKeys', () => {
|
|
|
77
100
|
});
|
|
78
101
|
|
|
79
102
|
test('sorts and de-duplicates outcome so filter order does not split the cache', () => {
|
|
80
|
-
const workspaceId =
|
|
103
|
+
const workspaceId = WORKSPACE_ID;
|
|
81
104
|
|
|
82
105
|
const a = triggerEventsQueryKeys.list(workspaceId, {outcome: ['routed', 'failed']});
|
|
83
106
|
const b = triggerEventsQueryKeys.list(workspaceId, {outcome: ['failed', 'routed', 'failed']});
|
|
@@ -87,7 +110,7 @@ describe('triggerEventsQueryKeys', () => {
|
|
|
87
110
|
});
|
|
88
111
|
|
|
89
112
|
test('sorts and de-duplicates source and event filters so order does not split the cache', () => {
|
|
90
|
-
const workspaceId =
|
|
113
|
+
const workspaceId = WORKSPACE_ID;
|
|
91
114
|
|
|
92
115
|
const a = triggerEventsQueryKeys.list(workspaceId, {
|
|
93
116
|
source: ['github', 'gitea'],
|
|
@@ -203,14 +226,23 @@ describe('listTriggerEvents', () => {
|
|
|
203
226
|
|
|
204
227
|
test('returns the parsed list response', async () => {
|
|
205
228
|
const page = {
|
|
206
|
-
trigger_events: [],
|
|
229
|
+
trigger_events: [triggerEventDto()],
|
|
207
230
|
next_cursor: 'next-page',
|
|
208
231
|
};
|
|
209
232
|
configureApiClient({fetchImpl: vi.fn(async () => jsonResponse(page))});
|
|
210
233
|
|
|
211
234
|
const result = await listTriggerEvents({workspaceId});
|
|
212
235
|
|
|
213
|
-
expect(result).toEqual(
|
|
236
|
+
expect(result).toEqual({
|
|
237
|
+
events: [
|
|
238
|
+
expect.objectContaining({
|
|
239
|
+
id: EVENT_ID,
|
|
240
|
+
eventRef: 'github:delivery-1:push',
|
|
241
|
+
matchedCount: 1,
|
|
242
|
+
}),
|
|
243
|
+
],
|
|
244
|
+
nextCursor: 'next-page',
|
|
245
|
+
});
|
|
214
246
|
});
|
|
215
247
|
});
|
|
216
248
|
|
|
@@ -220,8 +252,13 @@ describe('getTriggerEvent', () => {
|
|
|
220
252
|
});
|
|
221
253
|
|
|
222
254
|
test('requests the event detail by id', async () => {
|
|
223
|
-
const id =
|
|
224
|
-
const fetchImpl = vi.fn(async () =>
|
|
255
|
+
const id = EVENT_ID;
|
|
256
|
+
const fetchImpl = vi.fn(async () =>
|
|
257
|
+
jsonResponse({
|
|
258
|
+
...triggerEventDto({id, connection_name: null, payload: null}),
|
|
259
|
+
decisions: [],
|
|
260
|
+
}),
|
|
261
|
+
);
|
|
225
262
|
configureApiClient({fetchImpl});
|
|
226
263
|
|
|
227
264
|
await getTriggerEvent({id});
|
|
@@ -238,7 +275,7 @@ describe('getTriggerEventFacets', () => {
|
|
|
238
275
|
});
|
|
239
276
|
|
|
240
277
|
test('requests the workspace facets', async () => {
|
|
241
|
-
const workspaceId =
|
|
278
|
+
const workspaceId = WORKSPACE_ID;
|
|
242
279
|
const fetchImpl = vi.fn(async () => jsonResponse({sources: [], events: []}));
|
|
243
280
|
configureApiClient({fetchImpl});
|
|
244
281
|
|
|
@@ -1,40 +1,60 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
TriggerEventOutcomeDto,
|
|
1
|
+
import {
|
|
2
|
+
triggerEventDetailResponseSchema,
|
|
3
|
+
triggerEventFacetsResponseSchema,
|
|
4
|
+
triggerEventListResponseSchema,
|
|
6
5
|
} from '@shipfox/api-triggers-dto';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
6
|
+
import {checkedApiRequest} from '@shipfox/client-api';
|
|
7
|
+
import {
|
|
8
|
+
type FetchQueryOptions,
|
|
9
|
+
type InfiniteData,
|
|
10
|
+
infiniteQueryOptions,
|
|
11
|
+
keepPreviousData,
|
|
12
|
+
queryOptions,
|
|
13
|
+
type UseInfiniteQueryOptions,
|
|
14
|
+
useInfiniteQuery,
|
|
15
|
+
useQuery,
|
|
16
|
+
} from '@tanstack/react-query';
|
|
17
|
+
import {
|
|
18
|
+
normalizeTriggerEventFilters,
|
|
19
|
+
normalizeTriggerEventFilterValues,
|
|
20
|
+
type TriggerEventFilters,
|
|
21
|
+
} from '#core/trigger-event.js';
|
|
22
|
+
import {
|
|
23
|
+
toTriggerEventDetail,
|
|
24
|
+
toTriggerEventFacets,
|
|
25
|
+
toTriggerEventListPage,
|
|
26
|
+
} from './trigger-event-mapper.js';
|
|
17
27
|
|
|
18
|
-
|
|
19
|
-
return values && values.length > 0 ? [...new Set(values)].sort() : null;
|
|
20
|
-
}
|
|
28
|
+
export type {TriggerEventFilters} from '#core/trigger-event.js';
|
|
21
29
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
type TriggerEventsInfiniteQueryOptions = UseInfiniteQueryOptions<
|
|
31
|
+
import('#core/trigger-event.js').TriggerEventListPage,
|
|
32
|
+
Error,
|
|
33
|
+
InfiniteData<import('#core/trigger-event.js').TriggerEventListPage, string | undefined>,
|
|
34
|
+
ReturnType<typeof triggerEventsQueryKeys.list>,
|
|
35
|
+
string | undefined
|
|
36
|
+
>;
|
|
37
|
+
|
|
38
|
+
type TriggerEventQueryOptions = FetchQueryOptions<
|
|
39
|
+
import('#core/trigger-event.js').TriggerEventDetail,
|
|
40
|
+
Error,
|
|
41
|
+
import('#core/trigger-event.js').TriggerEventDetail,
|
|
42
|
+
ReturnType<typeof triggerEventsQueryKeys.detail>
|
|
43
|
+
>;
|
|
44
|
+
|
|
45
|
+
type TriggerEventFacetsQueryOptions = FetchQueryOptions<
|
|
46
|
+
import('#core/trigger-event.js').TriggerEventFacets,
|
|
47
|
+
Error,
|
|
48
|
+
import('#core/trigger-event.js').TriggerEventFacets,
|
|
49
|
+
ReturnType<typeof triggerEventsQueryKeys.facets>
|
|
50
|
+
>;
|
|
31
51
|
|
|
32
52
|
function setListParam(
|
|
33
53
|
params: URLSearchParams,
|
|
34
54
|
name: string,
|
|
35
55
|
values: readonly string[] | undefined,
|
|
36
56
|
) {
|
|
37
|
-
const normalized =
|
|
57
|
+
const normalized = normalizeTriggerEventFilterValues(values);
|
|
38
58
|
if (normalized) params.set(name, normalized.join(','));
|
|
39
59
|
}
|
|
40
60
|
|
|
@@ -45,7 +65,7 @@ export const triggerEventsQueryKeys = {
|
|
|
45
65
|
[
|
|
46
66
|
...triggerEventsQueryKeys.lists(workspaceId),
|
|
47
67
|
limit,
|
|
48
|
-
|
|
68
|
+
normalizeTriggerEventFilters(filters),
|
|
49
69
|
] as const,
|
|
50
70
|
detail: (id: string) => [...triggerEventsQueryKeys.all, 'detail', id] as const,
|
|
51
71
|
facets: (workspaceId: string) => [...triggerEventsQueryKeys.all, 'facets', workspaceId] as const,
|
|
@@ -72,16 +92,36 @@ export async function listTriggerEvents({
|
|
|
72
92
|
if (filters.to) params.set('to', filters.to);
|
|
73
93
|
setListParam(params, 'outcome', filters.outcome);
|
|
74
94
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
95
|
+
const response = await checkedApiRequest(
|
|
96
|
+
triggerEventListResponseSchema,
|
|
97
|
+
`/trigger-events?${params.toString()}`,
|
|
98
|
+
{signal},
|
|
99
|
+
);
|
|
100
|
+
return toTriggerEventListPage(response);
|
|
78
101
|
}
|
|
79
102
|
|
|
80
103
|
export async function getTriggerEvent({id, signal}: {id: string; signal?: AbortSignal}) {
|
|
81
|
-
|
|
104
|
+
const response = await checkedApiRequest(
|
|
105
|
+
triggerEventDetailResponseSchema,
|
|
82
106
|
`/trigger-events/${encodeURIComponent(id)}`,
|
|
83
107
|
{signal},
|
|
84
108
|
);
|
|
109
|
+
return toTriggerEventDetail(response);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function triggerEventsInfiniteQueryOptions(
|
|
113
|
+
workspaceId: string,
|
|
114
|
+
filters: TriggerEventFilters = {},
|
|
115
|
+
limit = 50,
|
|
116
|
+
): TriggerEventsInfiniteQueryOptions {
|
|
117
|
+
return infiniteQueryOptions({
|
|
118
|
+
queryKey: triggerEventsQueryKeys.list(workspaceId, filters, limit),
|
|
119
|
+
initialPageParam: undefined as string | undefined,
|
|
120
|
+
queryFn: ({pageParam, signal}) =>
|
|
121
|
+
listTriggerEvents({workspaceId, filters, limit, cursor: pageParam, signal}),
|
|
122
|
+
getNextPageParam: (lastPage) => lastPage.nextCursor ?? undefined,
|
|
123
|
+
placeholderData: keepPreviousData,
|
|
124
|
+
});
|
|
85
125
|
}
|
|
86
126
|
|
|
87
127
|
export function useTriggerEventsInfiniteQuery(
|
|
@@ -90,29 +130,22 @@ export function useTriggerEventsInfiniteQuery(
|
|
|
90
130
|
limit = 50,
|
|
91
131
|
) {
|
|
92
132
|
return useInfiniteQuery({
|
|
93
|
-
|
|
94
|
-
? triggerEventsQueryKeys.list(workspaceId, filters, limit)
|
|
95
|
-
: [...triggerEventsQueryKeys.all, 'list'],
|
|
133
|
+
...triggerEventsInfiniteQueryOptions(workspaceId ?? '', filters, limit),
|
|
96
134
|
enabled: Boolean(workspaceId),
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
signal,
|
|
105
|
-
}),
|
|
106
|
-
getNextPageParam: (lastPage) => lastPage.next_cursor ?? undefined,
|
|
107
|
-
placeholderData: keepPreviousData,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function triggerEventQueryOptions(id: string): TriggerEventQueryOptions {
|
|
139
|
+
return queryOptions({
|
|
140
|
+
queryKey: triggerEventsQueryKeys.detail(id),
|
|
141
|
+
queryFn: ({signal}) => getTriggerEvent({id, signal}),
|
|
108
142
|
});
|
|
109
143
|
}
|
|
110
144
|
|
|
111
145
|
export function useTriggerEventQuery(id: string | undefined) {
|
|
112
146
|
return useQuery({
|
|
113
|
-
|
|
147
|
+
...triggerEventQueryOptions(id ?? ''),
|
|
114
148
|
enabled: Boolean(id),
|
|
115
|
-
queryFn: ({signal}) => getTriggerEvent({id: id ?? '', signal}),
|
|
116
149
|
});
|
|
117
150
|
}
|
|
118
151
|
|
|
@@ -124,20 +157,27 @@ export async function getTriggerEventFacets({
|
|
|
124
157
|
signal?: AbortSignal;
|
|
125
158
|
}) {
|
|
126
159
|
const params = new URLSearchParams({workspace_id: workspaceId});
|
|
127
|
-
|
|
160
|
+
const response = await checkedApiRequest(
|
|
161
|
+
triggerEventFacetsResponseSchema,
|
|
128
162
|
`/trigger-events/facets?${params.toString()}`,
|
|
129
163
|
{signal},
|
|
130
164
|
);
|
|
165
|
+
return toTriggerEventFacets(response);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function triggerEventFacetsQueryOptions(
|
|
169
|
+
workspaceId: string,
|
|
170
|
+
): TriggerEventFacetsQueryOptions {
|
|
171
|
+
return queryOptions({
|
|
172
|
+
queryKey: triggerEventsQueryKeys.facets(workspaceId),
|
|
173
|
+
queryFn: ({signal}) => getTriggerEventFacets({workspaceId, signal}),
|
|
174
|
+
staleTime: 5 * 60 * 1000,
|
|
175
|
+
});
|
|
131
176
|
}
|
|
132
177
|
|
|
133
178
|
export function useTriggerEventFacetsQuery(workspaceId: string | undefined) {
|
|
134
179
|
return useQuery({
|
|
135
|
-
|
|
136
|
-
? triggerEventsQueryKeys.facets(workspaceId)
|
|
137
|
-
: [...triggerEventsQueryKeys.all, 'facets'],
|
|
180
|
+
...triggerEventFacetsQueryOptions(workspaceId ?? ''),
|
|
138
181
|
enabled: Boolean(workspaceId),
|
|
139
|
-
queryFn: ({signal}) => getTriggerEventFacets({workspaceId: workspaceId ?? '', signal}),
|
|
140
|
-
// Distinct values change slowly; avoid refetching on every page mount.
|
|
141
|
-
staleTime: 5 * 60 * 1000,
|
|
142
182
|
});
|
|
143
183
|
}
|
package/src/index.ts
CHANGED