@shipfox/client-integrations 5.0.0 → 6.0.2
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 +84 -0
- package/dist/application/complete-integration-callback.d.ts +13 -0
- package/dist/application/complete-integration-callback.d.ts.map +1 -0
- package/dist/application/complete-integration-callback.js +26 -0
- package/dist/application/complete-integration-callback.js.map +1 -0
- package/dist/components/connection-picker.d.ts +2 -2
- package/dist/components/connection-picker.d.ts.map +1 -1
- package/dist/components/connection-picker.js +4 -4
- package/dist/components/connection-picker.js.map +1 -1
- package/dist/components/installed-integrations-section.d.ts +8 -6
- package/dist/components/installed-integrations-section.d.ts.map +1 -1
- package/dist/components/installed-integrations-section.js +22 -15
- package/dist/components/installed-integrations-section.js.map +1 -1
- package/dist/components/integration-gallery-for-workspace.d.ts +2 -2
- package/dist/components/integration-gallery-for-workspace.d.ts.map +1 -1
- package/dist/components/integration-gallery-for-workspace.js +14 -7
- package/dist/components/integration-gallery-for-workspace.js.map +1 -1
- package/dist/components/integration-gallery.d.ts +2 -2
- package/dist/components/integration-gallery.d.ts.map +1 -1
- package/dist/components/integration-gallery.js.map +1 -1
- package/dist/components/integration-usage-events.d.ts +1 -3
- package/dist/components/integration-usage-events.d.ts.map +1 -1
- package/dist/components/integration-usage-events.js +1 -125
- package/dist/components/integration-usage-events.js.map +1 -1
- package/dist/components/integration-usage-modal.d.ts +2 -2
- package/dist/components/integration-usage-modal.d.ts.map +1 -1
- package/dist/components/integration-usage-modal.js +2 -2
- package/dist/components/integration-usage-modal.js.map +1 -1
- package/dist/components/provider-grid.d.ts +7 -4
- package/dist/components/provider-grid.d.ts.map +1 -1
- package/dist/components/provider-grid.js +15 -10
- package/dist/components/provider-grid.js.map +1 -1
- package/dist/components/redirect-install-page.d.ts +2 -3
- package/dist/components/redirect-install-page.d.ts.map +1 -1
- package/dist/components/redirect-install-page.js +1 -1
- package/dist/components/redirect-install-page.js.map +1 -1
- package/dist/components/repository-picker.d.ts +2 -2
- package/dist/components/repository-picker.d.ts.map +1 -1
- package/dist/components/repository-picker.js +4 -4
- package/dist/components/repository-picker.js.map +1 -1
- package/dist/components/webhook/webhook-create-modal.d.ts +2 -2
- package/dist/components/webhook/webhook-create-modal.d.ts.map +1 -1
- package/dist/components/webhook/webhook-create-modal.js +8 -8
- package/dist/components/webhook/webhook-create-modal.js.map +1 -1
- package/dist/components/webhook/webhook-modals.stories.d.ts.map +1 -1
- package/dist/components/webhook/webhook-modals.stories.js +7 -7
- package/dist/components/webhook/webhook-modals.stories.js.map +1 -1
- package/dist/components/webhook/webhook-usage-details.d.ts.map +1 -1
- package/dist/components/webhook/webhook-usage-details.js +2 -2
- package/dist/components/webhook/webhook-usage-details.js.map +1 -1
- package/dist/connection-status-badge.d.ts +2 -2
- package/dist/connection-status-badge.d.ts.map +1 -1
- package/dist/connection-status-badge.js +11 -15
- package/dist/connection-status-badge.js.map +1 -1
- package/dist/core/models.d.ts +66 -0
- package/dist/core/models.d.ts.map +1 -0
- package/dist/core/models.js +156 -0
- package/dist/core/models.js.map +1 -0
- package/dist/feature.d.ts +7 -0
- package/dist/feature.d.ts.map +1 -1
- package/dist/feature.js +10 -9
- package/dist/feature.js.map +1 -1
- package/dist/hooks/api/integration-mapper.d.ts +11 -0
- package/dist/hooks/api/integration-mapper.d.ts.map +1 -0
- package/dist/hooks/api/integration-mapper.js +54 -0
- package/dist/hooks/api/integration-mapper.js.map +1 -0
- package/dist/hooks/api/integrations.d.ts +45 -240
- package/dist/hooks/api/integrations.d.ts.map +1 -1
- package/dist/hooks/api/integrations.js +78 -31
- package/dist/hooks/api/integrations.js.map +1 -1
- package/dist/hooks/api/webhook-connections.d.ts +12 -64
- package/dist/hooks/api/webhook-connections.d.ts.map +1 -1
- package/dist/hooks/api/webhook-connections.js +17 -9
- package/dist/hooks/api/webhook-connections.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/linear-callback.d.ts +2 -1
- package/dist/linear-callback.d.ts.map +1 -1
- package/dist/linear-callback.js +17 -15
- package/dist/linear-callback.js.map +1 -1
- package/dist/pages/gitea-install-page.d.ts.map +1 -1
- package/dist/pages/gitea-install-page.js +1 -10
- package/dist/pages/gitea-install-page.js.map +1 -1
- package/dist/pages/linear-callback-page.d.ts.map +1 -1
- package/dist/pages/linear-callback-page.js +19 -32
- package/dist/pages/linear-callback-page.js.map +1 -1
- package/dist/pages/linear-install-page.d.ts.map +1 -1
- package/dist/pages/linear-install-page.js +2 -1
- package/dist/pages/linear-install-page.js.map +1 -1
- package/dist/pages/sentry-callback-page.d.ts.map +1 -1
- package/dist/pages/sentry-callback-page.js +22 -25
- package/dist/pages/sentry-callback-page.js.map +1 -1
- package/dist/pages/sentry-install-page.d.ts.map +1 -1
- package/dist/pages/sentry-install-page.js +2 -1
- package/dist/pages/sentry-install-page.js.map +1 -1
- package/dist/pages/slack-callback-page.d.ts.map +1 -1
- package/dist/pages/slack-callback-page.js +21 -28
- package/dist/pages/slack-callback-page.js.map +1 -1
- package/dist/pages/slack-install-page.d.ts.map +1 -1
- package/dist/pages/slack-install-page.js +2 -1
- package/dist/pages/slack-install-page.js.map +1 -1
- package/dist/pages/source-control-onboarding-page.d.ts.map +1 -1
- package/dist/pages/source-control-onboarding-page.js +5 -1
- package/dist/pages/source-control-onboarding-page.js.map +1 -1
- package/dist/provider-catalog.d.ts.map +1 -1
- package/dist/provider-catalog.js +7 -6
- package/dist/provider-catalog.js.map +1 -1
- package/dist/routes/github-callback.d.ts +16 -0
- package/dist/routes/github-callback.d.ts.map +1 -1
- package/dist/routes/github-callback.js +43 -21
- package/dist/routes/github-callback.js.map +1 -1
- package/dist/routes/linear-callback.d.ts +2 -0
- package/dist/routes/linear-callback.d.ts.map +1 -1
- package/dist/routes/linear-callback.js +2 -0
- package/dist/routes/linear-callback.js.map +1 -1
- package/dist/routes/sentry-callback.d.ts +2 -0
- package/dist/routes/sentry-callback.d.ts.map +1 -1
- package/dist/routes/sentry-callback.js +2 -0
- package/dist/routes/sentry-callback.js.map +1 -1
- package/dist/routes/slack-callback.d.ts +2 -0
- package/dist/routes/slack-callback.d.ts.map +1 -1
- package/dist/routes/slack-callback.js +2 -0
- package/dist/routes/slack-callback.js.map +1 -1
- package/dist/sentry-callback.d.ts +2 -1
- package/dist/sentry-callback.d.ts.map +1 -1
- package/dist/sentry-callback.js +18 -19
- package/dist/sentry-callback.js.map +1 -1
- package/dist/slack-callback.d.ts +2 -1
- package/dist/slack-callback.d.ts.map +1 -1
- package/dist/slack-callback.js +17 -15
- package/dist/slack-callback.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +17 -17
- package/src/application/complete-integration-callback.test.ts +41 -0
- package/src/application/complete-integration-callback.ts +39 -0
- package/src/components/connection-picker.tsx +7 -7
- package/src/components/installed-integrations-section.tsx +29 -24
- package/src/components/integration-gallery-for-workspace.tsx +23 -16
- package/src/components/integration-gallery.tsx +3 -3
- package/src/components/integration-usage-events.ts +1 -116
- package/src/components/integration-usage-modal.tsx +4 -4
- package/src/components/provider-grid.tsx +30 -18
- package/src/components/redirect-install-page.test.tsx +2 -6
- package/src/components/redirect-install-page.tsx +3 -2
- package/src/components/repository-picker.tsx +6 -6
- package/src/components/webhook/webhook-create-modal.tsx +12 -11
- package/src/components/webhook/webhook-modals.stories.tsx +10 -10
- package/src/components/webhook/webhook-usage-details.tsx +2 -4
- package/src/connection-status-badge.tsx +8 -14
- package/src/core/models.ts +200 -0
- package/src/feature.ts +12 -10
- package/src/hooks/api/integration-mapper.ts +64 -0
- package/src/hooks/api/integrations.test.ts +44 -22
- package/src/hooks/api/integrations.ts +229 -84
- package/src/hooks/api/webhook-connections.ts +58 -17
- package/src/index.ts +2 -1
- package/src/linear-callback.ts +24 -16
- package/src/pages/gitea-install-page.tsx +1 -10
- package/src/pages/linear-callback-page.tsx +18 -30
- package/src/pages/linear-install-page.tsx +2 -1
- package/src/pages/sentry-callback-page.test.tsx +5 -1
- package/src/pages/sentry-callback-page.tsx +28 -25
- package/src/pages/sentry-install-page.tsx +2 -1
- package/src/pages/slack-callback-page.tsx +20 -26
- package/src/pages/slack-install-page.tsx +2 -1
- package/src/pages/source-control-onboarding-page.tsx +5 -1
- package/src/provider-catalog.ts +7 -6
- package/src/routes/github-callback.test.ts +35 -1
- package/src/routes/github-callback.tsx +47 -21
- package/src/routes/linear-callback.tsx +5 -1
- package/src/routes/sentry-callback.tsx +5 -1
- package/src/routes/slack-callback.tsx +6 -1
- package/src/sentry-callback.test.ts +2 -1
- package/src/sentry-callback.ts +30 -20
- package/src/slack-callback.ts +24 -16
- package/tsconfig.build.tsbuildinfo +1 -1
- package/vercel.json +5 -0
- package/dist/integration-icon.d.ts +0 -17
- package/dist/integration-icon.d.ts.map +0 -1
- package/dist/integration-icon.js +0 -22
- package/dist/integration-icon.js.map +0 -1
- package/src/integration-icon.react.test.tsx +0 -40
- package/src/integration-icon.test.ts +0 -19
- package/src/integration-icon.tsx +0 -26
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shipfox/client-integrations",
|
|
3
3
|
"license": "MIT",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "6.0.2",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/ShipfoxHQ/shipfox.git",
|
|
@@ -10,10 +10,6 @@
|
|
|
10
10
|
"type": "module",
|
|
11
11
|
"main": "dist/index.js",
|
|
12
12
|
"types": "dist/index.d.ts",
|
|
13
|
-
"imports": {
|
|
14
|
-
"#*": "./dist/*",
|
|
15
|
-
"#test/*": "./test/*"
|
|
16
|
-
},
|
|
17
13
|
"exports": {
|
|
18
14
|
".": {
|
|
19
15
|
"types": "./dist/index.d.ts",
|
|
@@ -31,18 +27,19 @@
|
|
|
31
27
|
"dependencies": {
|
|
32
28
|
"@swc/helpers": "^0.5.17",
|
|
33
29
|
"@tanstack/react-form": "^1.32.0",
|
|
34
|
-
"@shipfox/api-integration-
|
|
35
|
-
"@shipfox/api-integration-
|
|
36
|
-
"@shipfox/api-integration-
|
|
37
|
-
"@shipfox/api-integration-
|
|
38
|
-
"@shipfox/api-integration-
|
|
39
|
-
"@shipfox/api-integration-sentry-dto": "
|
|
40
|
-
"@shipfox/api
|
|
41
|
-
"@shipfox/client-
|
|
42
|
-
"@shipfox/
|
|
43
|
-
"@shipfox/client-
|
|
44
|
-
"@shipfox/client-
|
|
45
|
-
"@shipfox/
|
|
30
|
+
"@shipfox/api-integration-gitea-dto": "9.0.2",
|
|
31
|
+
"@shipfox/api-integration-github-dto": "9.0.2",
|
|
32
|
+
"@shipfox/api-integration-slack-dto": "9.0.2",
|
|
33
|
+
"@shipfox/api-integration-core-dto": "9.0.2",
|
|
34
|
+
"@shipfox/api-integration-linear-dto": "9.0.2",
|
|
35
|
+
"@shipfox/api-integration-sentry-dto": "9.0.2",
|
|
36
|
+
"@shipfox/client-api": "6.0.1",
|
|
37
|
+
"@shipfox/client-ui": "6.0.2",
|
|
38
|
+
"@shipfox/api-integration-webhook-dto": "9.0.2",
|
|
39
|
+
"@shipfox/client-auth": "6.0.2",
|
|
40
|
+
"@shipfox/client-shell": "6.0.2",
|
|
41
|
+
"@shipfox/integration-icons": "0.2.2",
|
|
42
|
+
"@shipfox/react-ui": "0.3.7"
|
|
46
43
|
},
|
|
47
44
|
"peerDependencies": {
|
|
48
45
|
"@tanstack/react-query": "^5.101.0",
|
|
@@ -51,6 +48,9 @@
|
|
|
51
48
|
"react": "^19.0.0",
|
|
52
49
|
"react-dom": "^19.0.0"
|
|
53
50
|
},
|
|
51
|
+
"imports": {
|
|
52
|
+
"#*": "./dist/*"
|
|
53
|
+
},
|
|
54
54
|
"scripts": {
|
|
55
55
|
"build": "shipfox-swc",
|
|
56
56
|
"check": "shipfox-biome-check",
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import {QueryClient} from '@tanstack/react-query';
|
|
2
|
+
import type {IntegrationConnection} from '#core/models.js';
|
|
3
|
+
import {integrationsQueryKeys} from '#hooks/api/integrations.js';
|
|
4
|
+
import {completeIntegrationCallback} from './complete-integration-callback.js';
|
|
5
|
+
|
|
6
|
+
const WORKSPACE_ID = '11111111-1111-4111-8111-111111111111';
|
|
7
|
+
|
|
8
|
+
const connection: IntegrationConnection = {
|
|
9
|
+
id: '22222222-2222-4222-8222-222222222222',
|
|
10
|
+
workspaceId: WORKSPACE_ID,
|
|
11
|
+
provider: 'github',
|
|
12
|
+
externalAccountId: 'account',
|
|
13
|
+
slug: 'github-account',
|
|
14
|
+
displayName: 'GitHub',
|
|
15
|
+
lifecycleStatus: 'active',
|
|
16
|
+
capabilities: ['source_control'],
|
|
17
|
+
createdAt: '2026-07-23T00:00:00.000Z',
|
|
18
|
+
updatedAt: '2026-07-23T00:00:00.000Z',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
describe('completeIntegrationCallback', () => {
|
|
22
|
+
test('invalidates the workspace connection views after callback completion', async () => {
|
|
23
|
+
const queryClient = new QueryClient();
|
|
24
|
+
const invalidateQueries = vi.spyOn(queryClient, 'invalidateQueries');
|
|
25
|
+
const refreshAuth = vi.fn().mockResolvedValue({accessToken: 'access-token'});
|
|
26
|
+
const complete = vi.fn().mockResolvedValue(connection);
|
|
27
|
+
|
|
28
|
+
const result = await completeIntegrationCallback({
|
|
29
|
+
input: {code: 'grant-code'},
|
|
30
|
+
refreshAuth,
|
|
31
|
+
complete,
|
|
32
|
+
queryClient,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
expect(result).toBe(connection);
|
|
36
|
+
expect(complete).toHaveBeenCalledWith({code: 'grant-code'}, 'access-token');
|
|
37
|
+
expect(invalidateQueries).toHaveBeenCalledWith({
|
|
38
|
+
queryKey: integrationsQueryKeys.connectionsByWorkspace(WORKSPACE_ID),
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {type QueryClient, useQueryClient} from '@tanstack/react-query';
|
|
2
|
+
import {useCallback} from 'react';
|
|
3
|
+
import type {IntegrationConnection} from '#core/models.js';
|
|
4
|
+
import {integrationsQueryKeys} from '#hooks/api/integrations.js';
|
|
5
|
+
|
|
6
|
+
export interface CompleteIntegrationCallbackOptions<TInput> {
|
|
7
|
+
input: TInput;
|
|
8
|
+
refreshAuth: () => Promise<{accessToken: string}>;
|
|
9
|
+
complete: (input: TInput, accessToken: string) => Promise<IntegrationConnection>;
|
|
10
|
+
queryClient: QueryClient;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function completeIntegrationCallback<TInput>({
|
|
14
|
+
input,
|
|
15
|
+
refreshAuth,
|
|
16
|
+
complete,
|
|
17
|
+
queryClient,
|
|
18
|
+
}: CompleteIntegrationCallbackOptions<TInput>): Promise<IntegrationConnection> {
|
|
19
|
+
const session = await refreshAuth();
|
|
20
|
+
const connection = await complete(input, session.accessToken);
|
|
21
|
+
try {
|
|
22
|
+
await queryClient.invalidateQueries({
|
|
23
|
+
queryKey: integrationsQueryKeys.connectionsByWorkspace(connection.workspaceId),
|
|
24
|
+
});
|
|
25
|
+
} catch {
|
|
26
|
+
// Cache refresh is best effort: the successful callback is already committed server-side.
|
|
27
|
+
}
|
|
28
|
+
return connection;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function useCompleteIntegrationCallback() {
|
|
32
|
+
const queryClient = useQueryClient();
|
|
33
|
+
return useCallback(
|
|
34
|
+
async <TInput>(
|
|
35
|
+
options: Omit<CompleteIntegrationCallbackOptions<TInput>, 'queryClient'>,
|
|
36
|
+
): Promise<IntegrationConnection> => completeIntegrationCallback({...options, queryClient}),
|
|
37
|
+
[queryClient],
|
|
38
|
+
);
|
|
39
|
+
}
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {IntegrationIcon} from '@shipfox/integration-icons';
|
|
2
2
|
import {useIsTextTruncated} from '@shipfox/react-ui/hooks';
|
|
3
3
|
import {Label} from '@shipfox/react-ui/label';
|
|
4
4
|
import {RadioGroup, RadioGroupItem} from '@shipfox/react-ui/radio-group';
|
|
5
5
|
import {Tooltip, TooltipContent, TooltipTrigger} from '@shipfox/react-ui/tooltip';
|
|
6
6
|
import {Text} from '@shipfox/react-ui/typography';
|
|
7
7
|
import {useId} from 'react';
|
|
8
|
-
import {
|
|
8
|
+
import type {IntegrationConnection} from '#core/models.js';
|
|
9
9
|
|
|
10
10
|
export function ConnectionPicker({
|
|
11
11
|
connections,
|
|
12
12
|
selectedConnectionId,
|
|
13
13
|
onSelect,
|
|
14
14
|
}: {
|
|
15
|
-
connections:
|
|
15
|
+
connections: IntegrationConnection[];
|
|
16
16
|
selectedConnectionId: string | undefined;
|
|
17
17
|
onSelect: (connectionId: string) => void;
|
|
18
18
|
}) {
|
|
@@ -39,8 +39,8 @@ export function ConnectionPicker({
|
|
|
39
39
|
);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
function ConnectionOption({connection}: {connection:
|
|
43
|
-
const {ref: nameRef, isTruncated} = useIsTextTruncated<HTMLSpanElement>(connection.
|
|
42
|
+
function ConnectionOption({connection}: {connection: IntegrationConnection}) {
|
|
43
|
+
const {ref: nameRef, isTruncated} = useIsTextTruncated<HTMLSpanElement>(connection.displayName);
|
|
44
44
|
|
|
45
45
|
return (
|
|
46
46
|
<span className="flex min-w-0 items-center gap-10">
|
|
@@ -61,11 +61,11 @@ function ConnectionOption({connection}: {connection: IntegrationConnectionDto})
|
|
|
61
61
|
<TooltipTrigger asChild>
|
|
62
62
|
<span ref={nameRef} className="min-w-0 truncate">
|
|
63
63
|
<Text as="span" size="sm" bold>
|
|
64
|
-
{connection.
|
|
64
|
+
{connection.displayName}
|
|
65
65
|
</Text>
|
|
66
66
|
</span>
|
|
67
67
|
</TooltipTrigger>
|
|
68
|
-
{isTruncated ? <TooltipContent>{connection.
|
|
68
|
+
{isTruncated ? <TooltipContent>{connection.displayName}</TooltipContent> : null}
|
|
69
69
|
</Tooltip>
|
|
70
70
|
</span>
|
|
71
71
|
);
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
IntegrationConnectionDto,
|
|
3
|
-
ListIntegrationConnectionsResponseDto,
|
|
4
|
-
} from '@shipfox/api-integration-core-dto';
|
|
5
1
|
import {QueryLoadError} from '@shipfox/client-ui';
|
|
2
|
+
import {IntegrationIcon} from '@shipfox/integration-icons';
|
|
6
3
|
import {IconButton} from '@shipfox/react-ui/button';
|
|
7
4
|
import {
|
|
8
5
|
DropdownMenu,
|
|
@@ -15,18 +12,20 @@ import {EmptyState} from '@shipfox/react-ui/empty-state';
|
|
|
15
12
|
import {Skeleton} from '@shipfox/react-ui/skeleton';
|
|
16
13
|
import {Header, Text} from '@shipfox/react-ui/typography';
|
|
17
14
|
import {cn, formatDate} from '@shipfox/react-ui/utils';
|
|
18
|
-
import type {UseQueryResult} from '@tanstack/react-query';
|
|
19
15
|
import {Link} from '@tanstack/react-router';
|
|
20
16
|
import {ConnectionStatusBadge} from '#connection-status-badge.js';
|
|
21
|
-
import {
|
|
17
|
+
import {type IntegrationConnection, isUsableConnection} from '#core/models.js';
|
|
22
18
|
import {usageEventsForConnection} from './integration-usage-events.js';
|
|
23
19
|
|
|
24
20
|
interface InstalledIntegrationsSectionProps {
|
|
25
|
-
|
|
26
|
-
|
|
21
|
+
connections: IntegrationConnection[];
|
|
22
|
+
isPending: boolean;
|
|
23
|
+
isFetching: boolean;
|
|
24
|
+
error?: Error | null | undefined;
|
|
25
|
+
onRetry: () => void;
|
|
27
26
|
isMutating: boolean;
|
|
28
27
|
onUse: (connectionId: string) => void;
|
|
29
|
-
onSetActive: (connection:
|
|
28
|
+
onSetActive: (connection: IntegrationConnection, active: boolean) => void;
|
|
30
29
|
onDelete: (connectionId: string) => void;
|
|
31
30
|
providerDisplayName: (provider: string) => string | undefined;
|
|
32
31
|
}
|
|
@@ -35,8 +34,11 @@ const INSTALLED_SURFACE_CLASS =
|
|
|
35
34
|
'overflow-hidden rounded-8 border border-border-neutral-base bg-background-neutral-base';
|
|
36
35
|
|
|
37
36
|
export function InstalledIntegrationsSection({
|
|
38
|
-
connectionsQuery,
|
|
39
37
|
connections,
|
|
38
|
+
isPending,
|
|
39
|
+
isFetching,
|
|
40
|
+
error,
|
|
41
|
+
onRetry,
|
|
40
42
|
isMutating,
|
|
41
43
|
onUse,
|
|
42
44
|
onSetActive,
|
|
@@ -52,15 +54,18 @@ export function InstalledIntegrationsSection({
|
|
|
52
54
|
</Text>
|
|
53
55
|
</div>
|
|
54
56
|
|
|
55
|
-
{
|
|
57
|
+
{isPending ? <InstalledSkeleton label="Loading integrations" /> : null}
|
|
56
58
|
|
|
57
|
-
{
|
|
59
|
+
{error ? (
|
|
58
60
|
<div className={cn(INSTALLED_SURFACE_CLASS, 'px-16')}>
|
|
59
|
-
<QueryLoadError
|
|
61
|
+
<QueryLoadError
|
|
62
|
+
query={{isError: true, isFetching, data: undefined, error, refetch: onRetry}}
|
|
63
|
+
subject="integrations"
|
|
64
|
+
/>
|
|
60
65
|
</div>
|
|
61
66
|
) : null}
|
|
62
67
|
|
|
63
|
-
{
|
|
68
|
+
{!isPending && !error && connections.length === 0 ? (
|
|
64
69
|
<div className={cn(INSTALLED_SURFACE_CLASS, 'px-16')}>
|
|
65
70
|
<EmptyState
|
|
66
71
|
icon="componentLine"
|
|
@@ -97,15 +102,15 @@ function InstalledRow({
|
|
|
97
102
|
onDelete,
|
|
98
103
|
providerDisplayName,
|
|
99
104
|
}: {
|
|
100
|
-
connection:
|
|
105
|
+
connection: IntegrationConnection;
|
|
101
106
|
isMutating: boolean;
|
|
102
107
|
onUse: (connectionId: string) => void;
|
|
103
108
|
onSetActive: (active: boolean) => void;
|
|
104
109
|
onDelete: (connectionId: string) => void;
|
|
105
110
|
providerDisplayName: (provider: string) => string | undefined;
|
|
106
111
|
}) {
|
|
107
|
-
const muted = connection.
|
|
108
|
-
const active = connection
|
|
112
|
+
const muted = connection.lifecycleStatus === 'disabled';
|
|
113
|
+
const active = isUsableConnection(connection);
|
|
109
114
|
const recentEventsEvent = usageEventsForConnection(connection)[0]?.value ?? 'received';
|
|
110
115
|
const providerName = providerDisplayName(connection.provider);
|
|
111
116
|
|
|
@@ -126,12 +131,12 @@ function InstalledRow({
|
|
|
126
131
|
bold
|
|
127
132
|
className={cn('truncate', muted ? 'text-foreground-neutral-disabled' : undefined)}
|
|
128
133
|
>
|
|
129
|
-
{connection.
|
|
134
|
+
{connection.displayName}
|
|
130
135
|
</Text>
|
|
131
|
-
<ConnectionStatusBadge status={connection.
|
|
136
|
+
<ConnectionStatusBadge status={connection.lifecycleStatus} className="shrink-0" />
|
|
132
137
|
</div>
|
|
133
138
|
<Text size="sm" className="truncate text-foreground-neutral-muted">
|
|
134
|
-
Added {formatDate(connection.
|
|
139
|
+
Added {formatDate(connection.createdAt)}
|
|
135
140
|
</Text>
|
|
136
141
|
</div>
|
|
137
142
|
<DropdownMenu>
|
|
@@ -140,13 +145,13 @@ function InstalledRow({
|
|
|
140
145
|
size="sm"
|
|
141
146
|
variant="transparent"
|
|
142
147
|
icon="more2Line"
|
|
143
|
-
aria-label={`Open ${connection.
|
|
148
|
+
aria-label={`Open ${connection.displayName} integration actions`}
|
|
144
149
|
/>
|
|
145
150
|
</DropdownMenuTrigger>
|
|
146
151
|
<DropdownMenuContent align="end">
|
|
147
|
-
{connection.
|
|
152
|
+
{connection.externalUrl ? (
|
|
148
153
|
<DropdownMenuItem asChild>
|
|
149
|
-
<a href={connection.
|
|
154
|
+
<a href={connection.externalUrl} target="_blank" rel="noreferrer noopener">
|
|
150
155
|
{providerName ? `Open in ${providerName}` : 'Open provider settings'}
|
|
151
156
|
</a>
|
|
152
157
|
</DropdownMenuItem>
|
|
@@ -157,7 +162,7 @@ function InstalledRow({
|
|
|
157
162
|
<DropdownMenuItem asChild>
|
|
158
163
|
<Link
|
|
159
164
|
to="/workspaces/$wid/settings/events"
|
|
160
|
-
params={{wid: connection.
|
|
165
|
+
params={{wid: connection.workspaceId}}
|
|
161
166
|
search={{source: [connection.slug], event: [recentEventsEvent]}}
|
|
162
167
|
>
|
|
163
168
|
View recent events
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
IntegrationCapabilityDto,
|
|
3
|
-
IntegrationConnectionDto,
|
|
4
|
-
IntegrationProviderDto,
|
|
5
|
-
} from '@shipfox/api-integration-core-dto';
|
|
6
1
|
import {toast} from '@shipfox/react-ui/toast';
|
|
7
2
|
import {Header, Text} from '@shipfox/react-ui/typography';
|
|
8
3
|
import {useState} from 'react';
|
|
4
|
+
import type {
|
|
5
|
+
IntegrationCapability,
|
|
6
|
+
IntegrationConnection,
|
|
7
|
+
IntegrationProvider,
|
|
8
|
+
} from '#core/models.js';
|
|
9
9
|
import {
|
|
10
10
|
useDeleteIntegrationConnectionMutation,
|
|
11
11
|
useIntegrationConnectionsQuery,
|
|
@@ -25,7 +25,7 @@ import {WebhookCreateModal} from './webhook/webhook-create-modal.js';
|
|
|
25
25
|
import {WebhookUsageDetails} from './webhook/webhook-usage-details.js';
|
|
26
26
|
|
|
27
27
|
interface IntegrationGalleryForWorkspaceProps {
|
|
28
|
-
capability:
|
|
28
|
+
capability: IntegrationCapability | undefined;
|
|
29
29
|
emptyProvidersMessage: string;
|
|
30
30
|
workspaceId: string;
|
|
31
31
|
}
|
|
@@ -38,7 +38,7 @@ export function IntegrationGalleryForWorkspace({
|
|
|
38
38
|
const [createProvider, setCreateProvider] = useState<string | undefined>();
|
|
39
39
|
const [usageConnectionId, setUsageConnectionId] = useState<string | undefined>();
|
|
40
40
|
const [createdUsageConnection, setCreatedUsageConnection] = useState<
|
|
41
|
-
|
|
41
|
+
IntegrationConnection | undefined
|
|
42
42
|
>();
|
|
43
43
|
const [deleteConnectionId, setDeleteConnectionId] = useState<string | undefined>();
|
|
44
44
|
const providersQuery = useIntegrationProvidersQuery(capability ? {capability} : undefined);
|
|
@@ -48,12 +48,12 @@ export function IntegrationGalleryForWorkspace({
|
|
|
48
48
|
const updateWebhookConnection = useUpdateWebhookConnectionMutation();
|
|
49
49
|
const deleteWebhookConnection = useDeleteWebhookConnectionMutation();
|
|
50
50
|
|
|
51
|
-
const providers = providersQuery.data
|
|
52
|
-
const providersMap = new Map<string,
|
|
51
|
+
const providers = providersQuery.data ?? [];
|
|
52
|
+
const providersMap = new Map<string, IntegrationProvider>(
|
|
53
53
|
providers.map((provider) => [provider.provider, provider]),
|
|
54
54
|
);
|
|
55
55
|
|
|
56
|
-
const allConnections = connectionsQuery.data
|
|
56
|
+
const allConnections = connectionsQuery.data ?? [];
|
|
57
57
|
// Filter in memory so the all-status cache key stays shared; passing capability
|
|
58
58
|
// into the hook would collide with the active-only `source_control` key used
|
|
59
59
|
// elsewhere.
|
|
@@ -61,13 +61,13 @@ export function IntegrationGalleryForWorkspace({
|
|
|
61
61
|
? allConnections.filter((connection) => connection.capabilities.includes(capability))
|
|
62
62
|
: allConnections;
|
|
63
63
|
|
|
64
|
-
const providerDisplayName = (provider: string) => providersMap.get(provider)?.
|
|
64
|
+
const providerDisplayName = (provider: string) => providersMap.get(provider)?.displayName;
|
|
65
65
|
const providerLabel = (provider: string) => providerDisplayName(provider) ?? provider;
|
|
66
66
|
|
|
67
67
|
const sortedConnections = [...connections].sort((a, b) => {
|
|
68
68
|
const byProvider = providerLabel(a.provider).localeCompare(providerLabel(b.provider));
|
|
69
69
|
if (byProvider !== 0) return byProvider;
|
|
70
|
-
return a.
|
|
70
|
+
return a.createdAt.localeCompare(b.createdAt);
|
|
71
71
|
});
|
|
72
72
|
const usageConnection =
|
|
73
73
|
sortedConnections.find((connection) => connection.id === usageConnectionId) ??
|
|
@@ -77,7 +77,7 @@ export function IntegrationGalleryForWorkspace({
|
|
|
77
77
|
(connection) => connection.id === deleteConnectionId,
|
|
78
78
|
);
|
|
79
79
|
|
|
80
|
-
async function setConnectionActive(connection:
|
|
80
|
+
async function setConnectionActive(connection: IntegrationConnection, active: boolean) {
|
|
81
81
|
try {
|
|
82
82
|
const body = {lifecycle_status: active ? 'active' : 'disabled'} as const;
|
|
83
83
|
if (connection.provider === 'webhook') {
|
|
@@ -122,8 +122,11 @@ export function IntegrationGalleryForWorkspace({
|
|
|
122
122
|
return (
|
|
123
123
|
<div className="flex flex-col gap-24">
|
|
124
124
|
<InstalledIntegrationsSection
|
|
125
|
-
connectionsQuery={connectionsQuery}
|
|
126
125
|
connections={sortedConnections}
|
|
126
|
+
isPending={connectionsQuery.isPending}
|
|
127
|
+
isFetching={connectionsQuery.isFetching}
|
|
128
|
+
error={connectionsQuery.isError ? connectionsQuery.error : undefined}
|
|
129
|
+
onRetry={() => void connectionsQuery.refetch()}
|
|
127
130
|
isMutating={
|
|
128
131
|
updateConnection.isPending ||
|
|
129
132
|
deleteConnection.isPending ||
|
|
@@ -147,7 +150,11 @@ export function IntegrationGalleryForWorkspace({
|
|
|
147
150
|
</div>
|
|
148
151
|
|
|
149
152
|
<ProviderGrid
|
|
150
|
-
|
|
153
|
+
providers={providers}
|
|
154
|
+
isPending={providersQuery.isPending}
|
|
155
|
+
isFetching={providersQuery.isFetching}
|
|
156
|
+
error={providersQuery.isError ? providersQuery.error : undefined}
|
|
157
|
+
onRetry={() => void providersQuery.refetch()}
|
|
151
158
|
workspaceId={workspaceId}
|
|
152
159
|
emptyMessage={emptyProvidersMessage}
|
|
153
160
|
onOpenProvider={setCreateProvider}
|
|
@@ -178,7 +185,7 @@ export function IntegrationGalleryForWorkspace({
|
|
|
178
185
|
) : null}
|
|
179
186
|
</IntegrationUsageModal>
|
|
180
187
|
<IntegrationDeleteConfirmModal
|
|
181
|
-
connectionName={deleteConnectionTarget?.
|
|
188
|
+
connectionName={deleteConnectionTarget?.displayName}
|
|
182
189
|
open={deleteConnectionId !== undefined}
|
|
183
190
|
isPending={deleteConnection.isPending || deleteWebhookConnection.isPending}
|
|
184
191
|
onOpenChange={(open) => {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type {IntegrationCapabilityDto} from '@shipfox/api-integration-core-dto';
|
|
2
1
|
import {useActiveWorkspace} from '@shipfox/client-auth';
|
|
2
|
+
import type {IntegrationCapability} from '#core/models.js';
|
|
3
3
|
import {IntegrationGalleryForWorkspace} from './integration-gallery-for-workspace.js';
|
|
4
4
|
|
|
5
5
|
export interface IntegrationGalleryProps {
|
|
6
|
-
capability?:
|
|
6
|
+
capability?: IntegrationCapability;
|
|
7
7
|
emptyProvidersMessage?: string;
|
|
8
8
|
workspaceId?: string;
|
|
9
9
|
}
|
|
@@ -35,7 +35,7 @@ function RoutedIntegrationGallery({
|
|
|
35
35
|
capability,
|
|
36
36
|
emptyProvidersMessage,
|
|
37
37
|
}: {
|
|
38
|
-
capability:
|
|
38
|
+
capability: IntegrationCapability | undefined;
|
|
39
39
|
emptyProvidersMessage: string;
|
|
40
40
|
}) {
|
|
41
41
|
const workspace = useActiveWorkspace();
|
|
@@ -1,116 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import {SENTRY_ISSUE_ACTIONS} from '@shipfox/api-integration-core-dto';
|
|
3
|
-
import {linearWebhookEventNames} from '@shipfox/api-integration-linear-dto';
|
|
4
|
-
import {WEBHOOK_RECEIVED_EVENT} from '@shipfox/api-integration-webhook-dto';
|
|
5
|
-
import type {IntegrationUsageEvent} from './integration-usage-modal.js';
|
|
6
|
-
|
|
7
|
-
const GITHUB_EVENTS = [
|
|
8
|
-
'push',
|
|
9
|
-
'branch_protection_configuration',
|
|
10
|
-
'branch_protection_rule',
|
|
11
|
-
'check_run',
|
|
12
|
-
'check_suite',
|
|
13
|
-
'code_scanning_alert',
|
|
14
|
-
'commit_comment',
|
|
15
|
-
'create',
|
|
16
|
-
'custom_property',
|
|
17
|
-
'custom_property_values',
|
|
18
|
-
'delete',
|
|
19
|
-
'dependabot_alert',
|
|
20
|
-
'deploy_key',
|
|
21
|
-
'deployment',
|
|
22
|
-
'deployment_protection_rule',
|
|
23
|
-
'deployment_review',
|
|
24
|
-
'deployment_status',
|
|
25
|
-
'discussion',
|
|
26
|
-
'discussion_comment',
|
|
27
|
-
'fork',
|
|
28
|
-
'github_app_authorization',
|
|
29
|
-
'gollum',
|
|
30
|
-
'installation',
|
|
31
|
-
'installation_repositories',
|
|
32
|
-
'installation_target',
|
|
33
|
-
'issue_comment',
|
|
34
|
-
'issue_dependencies',
|
|
35
|
-
'issues',
|
|
36
|
-
'label',
|
|
37
|
-
'marketplace_purchase',
|
|
38
|
-
'member',
|
|
39
|
-
'membership',
|
|
40
|
-
'merge_group',
|
|
41
|
-
'meta',
|
|
42
|
-
'milestone',
|
|
43
|
-
'org_block',
|
|
44
|
-
'organization',
|
|
45
|
-
'package',
|
|
46
|
-
'page_build',
|
|
47
|
-
'personal_access_token_request',
|
|
48
|
-
'ping',
|
|
49
|
-
'project',
|
|
50
|
-
'project_card',
|
|
51
|
-
'project_column',
|
|
52
|
-
'projects_v2',
|
|
53
|
-
'projects_v2_item',
|
|
54
|
-
'projects_v2_status_update',
|
|
55
|
-
'public',
|
|
56
|
-
'pull_request',
|
|
57
|
-
'pull_request_review',
|
|
58
|
-
'pull_request_review_comment',
|
|
59
|
-
'pull_request_review_thread',
|
|
60
|
-
'registry_package',
|
|
61
|
-
'release',
|
|
62
|
-
'repository',
|
|
63
|
-
'repository_advisory',
|
|
64
|
-
'repository_dispatch',
|
|
65
|
-
'repository_import',
|
|
66
|
-
'repository_ruleset',
|
|
67
|
-
'repository_vulnerability_alert',
|
|
68
|
-
'secret_scanning_alert',
|
|
69
|
-
'secret_scanning_alert_location',
|
|
70
|
-
'secret_scanning_scan',
|
|
71
|
-
'security_advisory',
|
|
72
|
-
'security_and_analysis',
|
|
73
|
-
'sponsorship',
|
|
74
|
-
'star',
|
|
75
|
-
'status',
|
|
76
|
-
'sub_issues',
|
|
77
|
-
'team',
|
|
78
|
-
'team_add',
|
|
79
|
-
'watch',
|
|
80
|
-
'workflow_dispatch',
|
|
81
|
-
'workflow_job',
|
|
82
|
-
'workflow_run',
|
|
83
|
-
] as const;
|
|
84
|
-
|
|
85
|
-
export function usageEventsForConnection(
|
|
86
|
-
connection: IntegrationConnectionDto,
|
|
87
|
-
): IntegrationUsageEvent[] {
|
|
88
|
-
if (connection.provider === 'webhook') {
|
|
89
|
-
return [{value: WEBHOOK_RECEIVED_EVENT, label: WEBHOOK_RECEIVED_EVENT}];
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
if (connection.provider === 'github') {
|
|
93
|
-
return GITHUB_EVENTS.map((event) => ({value: event, label: event}));
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (connection.provider === 'sentry') {
|
|
97
|
-
return SENTRY_ISSUE_ACTIONS.map((action) => ({
|
|
98
|
-
value: `issue.${action}`,
|
|
99
|
-
label: sentryIssueEventLabel(action),
|
|
100
|
-
}));
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (connection.provider === 'linear') {
|
|
104
|
-
return linearWebhookEventNames.map((event) => ({value: event, label: event}));
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if (connection.capabilities.includes('source_control')) {
|
|
108
|
-
return [{value: 'push', label: 'push'}];
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
return [{value: 'received', label: 'received'}];
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function sentryIssueEventLabel(action: SentryIssueAction): string {
|
|
115
|
-
return `issue.${action}`;
|
|
116
|
-
}
|
|
1
|
+
export {usageEventsForConnection} from '#core/models.js';
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type {IntegrationConnectionDto} from '@shipfox/api-integration-core-dto';
|
|
2
1
|
import {Button} from '@shipfox/react-ui/button';
|
|
3
2
|
import {
|
|
4
3
|
CodeBlock,
|
|
@@ -29,6 +28,7 @@ import {Text} from '@shipfox/react-ui/typography';
|
|
|
29
28
|
import type {ReactNode} from 'react';
|
|
30
29
|
import {useEffect, useMemo, useState} from 'react';
|
|
31
30
|
import {ConnectionStatusBadge} from '#connection-status-badge.js';
|
|
31
|
+
import type {IntegrationConnection} from '#core/models.js';
|
|
32
32
|
|
|
33
33
|
export interface IntegrationUsageEvent {
|
|
34
34
|
value: string;
|
|
@@ -36,7 +36,7 @@ export interface IntegrationUsageEvent {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
interface IntegrationUsageModalProps {
|
|
39
|
-
connection:
|
|
39
|
+
connection: IntegrationConnection | null;
|
|
40
40
|
events: IntegrationUsageEvent[];
|
|
41
41
|
open: boolean;
|
|
42
42
|
onOpenChange: (open: boolean) => void;
|
|
@@ -69,7 +69,7 @@ export function IntegrationUsageModal({
|
|
|
69
69
|
() => [{language: 'yaml', filename: WORKFLOW_FILENAME, code: workflowExample}],
|
|
70
70
|
[workflowExample],
|
|
71
71
|
);
|
|
72
|
-
const title = connection ? `Use ${connection.
|
|
72
|
+
const title = connection ? `Use ${connection.displayName}` : 'Use integration';
|
|
73
73
|
|
|
74
74
|
return (
|
|
75
75
|
<Modal open={open && connection !== null} onOpenChange={onOpenChange}>
|
|
@@ -82,7 +82,7 @@ export function IntegrationUsageModal({
|
|
|
82
82
|
{title}
|
|
83
83
|
</Text>
|
|
84
84
|
{connection ? (
|
|
85
|
-
<ConnectionStatusBadge status={connection.
|
|
85
|
+
<ConnectionStatusBadge status={connection.lifecycleStatus} className="shrink-0" />
|
|
86
86
|
) : null}
|
|
87
87
|
</div>
|
|
88
88
|
<Text size="sm" className="text-foreground-neutral-muted">
|