@shipfox/client-integrations 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 +86 -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 +18 -0
- package/dist/feature.d.ts.map +1 -1
- package/dist/feature.js +16 -1
- 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 +47 -237
- package/dist/hooks/api/integrations.d.ts.map +1 -1
- package/dist/hooks/api/integrations.js +92 -42
- 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 +14 -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/integrations-settings.d.ts +6 -0
- package/dist/routes/integrations-settings.d.ts.map +1 -0
- package/dist/routes/integrations-settings.js +7 -0
- package/dist/routes/integrations-settings.js.map +1 -0
- 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 +16 -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 +14 -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 +17 -1
- package/src/hooks/api/integration-mapper.ts +64 -0
- package/src/hooks/api/integrations.test.ts +44 -22
- package/src/hooks/api/integrations.ts +258 -89
- package/src/hooks/api/webhook-connections.ts +58 -17
- package/src/index.ts +2 -1
- package/src/linear-callback.ts +21 -16
- package/src/pages/gitea-install-page.tsx +1 -10
- package/src/pages/linear-callback-page.test.tsx +1 -1
- package/src/pages/linear-callback-page.tsx +18 -29
- package/src/pages/linear-install-page.tsx +2 -1
- package/src/pages/sentry-callback-page.test.tsx +6 -2
- 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 -25
- 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/integrations-settings.tsx +4 -0
- 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 +28 -20
- package/src/slack-callback.ts +21 -16
- package/test/render.tsx +1 -5
- 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
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
IntegrationProviderDto,
|
|
3
|
-
ListIntegrationProvidersResponseDto,
|
|
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 {Card} from '@shipfox/react-ui/card';
|
|
7
4
|
import {EmptyState} from '@shipfox/react-ui/empty-state';
|
|
8
5
|
import {Icon} from '@shipfox/react-ui/icon';
|
|
9
6
|
import {Skeleton} from '@shipfox/react-ui/skeleton';
|
|
10
7
|
import {Text} from '@shipfox/react-ui/typography';
|
|
11
8
|
import {cn} from '@shipfox/react-ui/utils';
|
|
12
|
-
import type {UseQueryResult} from '@tanstack/react-query';
|
|
13
9
|
import {Link} from '@tanstack/react-router';
|
|
14
|
-
import {
|
|
10
|
+
import type {IntegrationProvider} from '#core/models.js';
|
|
15
11
|
import {PROVIDER_CATALOG} from '#provider-catalog.js';
|
|
16
12
|
|
|
17
13
|
export interface ProviderGridProps {
|
|
18
|
-
|
|
14
|
+
providers: IntegrationProvider[];
|
|
15
|
+
isPending: boolean;
|
|
16
|
+
isFetching?: boolean;
|
|
17
|
+
error?: Error | null | undefined;
|
|
18
|
+
onRetry?: () => void;
|
|
19
19
|
workspaceId: string;
|
|
20
20
|
emptyMessage: string;
|
|
21
21
|
loadingLabel?: string;
|
|
@@ -29,27 +29,39 @@ export const PROVIDER_SURFACE_CLASS =
|
|
|
29
29
|
'overflow-hidden rounded-8 border border-border-neutral-base bg-background-neutral-base';
|
|
30
30
|
|
|
31
31
|
export function ProviderGrid({
|
|
32
|
-
|
|
32
|
+
providers,
|
|
33
|
+
isPending,
|
|
34
|
+
isFetching = false,
|
|
35
|
+
error,
|
|
36
|
+
onRetry,
|
|
33
37
|
workspaceId,
|
|
34
38
|
emptyMessage,
|
|
35
39
|
loadingLabel = 'Loading providers',
|
|
36
40
|
errorSubject = 'available integrations',
|
|
37
41
|
onOpenProvider,
|
|
38
42
|
}: ProviderGridProps) {
|
|
39
|
-
const providers = providersQuery.data?.providers ?? [];
|
|
40
43
|
const installableProviders = providers.filter((provider) => PROVIDER_CATALOG[provider.provider]);
|
|
41
44
|
|
|
42
|
-
if (
|
|
45
|
+
if (isPending) return <ProviderGridSkeleton label={loadingLabel} />;
|
|
43
46
|
|
|
44
|
-
if (
|
|
47
|
+
if (error) {
|
|
45
48
|
return (
|
|
46
49
|
<div className={cn(PROVIDER_SURFACE_CLASS, 'px-16')}>
|
|
47
|
-
<QueryLoadError
|
|
50
|
+
<QueryLoadError
|
|
51
|
+
query={{
|
|
52
|
+
isError: true,
|
|
53
|
+
isFetching,
|
|
54
|
+
data: undefined,
|
|
55
|
+
error,
|
|
56
|
+
refetch: onRetry ?? (() => undefined),
|
|
57
|
+
}}
|
|
58
|
+
subject={errorSubject}
|
|
59
|
+
/>
|
|
48
60
|
</div>
|
|
49
61
|
);
|
|
50
62
|
}
|
|
51
63
|
|
|
52
|
-
if (
|
|
64
|
+
if (installableProviders.length === 0) {
|
|
53
65
|
return (
|
|
54
66
|
<div className={cn(PROVIDER_SURFACE_CLASS, 'px-16')}>
|
|
55
67
|
<EmptyState
|
|
@@ -81,7 +93,7 @@ function ProviderCard({
|
|
|
81
93
|
workspaceId,
|
|
82
94
|
onOpenProvider,
|
|
83
95
|
}: {
|
|
84
|
-
provider:
|
|
96
|
+
provider: IntegrationProvider;
|
|
85
97
|
workspaceId: string;
|
|
86
98
|
onOpenProvider?: ((provider: string) => void) | undefined;
|
|
87
99
|
}) {
|
|
@@ -92,7 +104,7 @@ function ProviderCard({
|
|
|
92
104
|
return (
|
|
93
105
|
<button
|
|
94
106
|
type="button"
|
|
95
|
-
aria-label={`Add ${provider.
|
|
107
|
+
aria-label={`Add ${provider.displayName}`}
|
|
96
108
|
onClick={() => onOpenProvider?.(provider.provider)}
|
|
97
109
|
className="group flex h-full w-full min-w-0 items-center justify-between gap-12 rounded-8 border border-border-neutral-base bg-background-neutral-base p-16 text-left transition-colors hover:bg-background-components-hover focus-visible:shadow-button-neutral-focus focus-visible:outline-none"
|
|
98
110
|
>
|
|
@@ -105,7 +117,7 @@ function ProviderCard({
|
|
|
105
117
|
<Link
|
|
106
118
|
to={catalog.setupPath}
|
|
107
119
|
params={{wid: workspaceId}}
|
|
108
|
-
aria-label={`Install ${provider.
|
|
120
|
+
aria-label={`Install ${provider.displayName}`}
|
|
109
121
|
className="group flex h-full min-w-0 items-center justify-between gap-12 rounded-8 border border-border-neutral-base bg-background-neutral-base p-16 transition-colors hover:bg-background-components-hover focus-visible:shadow-button-neutral-focus focus-visible:outline-none"
|
|
110
122
|
>
|
|
111
123
|
<ProviderCardContent provider={provider} action="Install" />
|
|
@@ -117,7 +129,7 @@ function ProviderCardContent({
|
|
|
117
129
|
provider,
|
|
118
130
|
action,
|
|
119
131
|
}: {
|
|
120
|
-
provider:
|
|
132
|
+
provider: IntegrationProvider;
|
|
121
133
|
action: 'Add' | 'Install';
|
|
122
134
|
}) {
|
|
123
135
|
return (
|
|
@@ -129,7 +141,7 @@ function ProviderCardContent({
|
|
|
129
141
|
className="size-24 shrink-0 text-foreground-neutral-base"
|
|
130
142
|
/>
|
|
131
143
|
<Text as="span" size="md" bold className="truncate">
|
|
132
|
-
{provider.
|
|
144
|
+
{provider.displayName}
|
|
133
145
|
</Text>
|
|
134
146
|
</span>
|
|
135
147
|
<span className="flex shrink-0 items-center gap-4 text-foreground-neutral-muted transition-colors group-hover:text-foreground-highlight-interactive">
|
|
@@ -26,9 +26,7 @@ function renderInstallPage(
|
|
|
26
26
|
|
|
27
27
|
describe('RedirectInstallPage', () => {
|
|
28
28
|
test('requests the install URL and leaves the app', async () => {
|
|
29
|
-
const installRequest = vi
|
|
30
|
-
.fn()
|
|
31
|
-
.mockResolvedValue({install_url: 'https://provider.test/install'});
|
|
29
|
+
const installRequest = vi.fn().mockResolvedValue({installUrl: 'https://provider.test/install'});
|
|
32
30
|
const assignLocation = vi.fn();
|
|
33
31
|
const beforeRedirect = vi.fn();
|
|
34
32
|
|
|
@@ -68,9 +66,7 @@ describe('RedirectInstallPage', () => {
|
|
|
68
66
|
});
|
|
69
67
|
|
|
70
68
|
test('requests the install URL exactly once in Strict Mode', async () => {
|
|
71
|
-
const installRequest = vi
|
|
72
|
-
.fn()
|
|
73
|
-
.mockResolvedValue({install_url: 'https://provider.test/install'});
|
|
69
|
+
const installRequest = vi.fn().mockResolvedValue({installUrl: 'https://provider.test/install'});
|
|
74
70
|
|
|
75
71
|
renderInstallPage(
|
|
76
72
|
{
|
|
@@ -6,9 +6,10 @@ import {FullPageLoader} from '@shipfox/react-ui/loader';
|
|
|
6
6
|
import {Text} from '@shipfox/react-ui/typography';
|
|
7
7
|
import {Link} from '@tanstack/react-router';
|
|
8
8
|
import {useEffect, useRef, useState} from 'react';
|
|
9
|
+
import type {InstallRedirect} from '#core/models.js';
|
|
9
10
|
|
|
10
11
|
interface RedirectInstallPageProps {
|
|
11
|
-
installRequest: (body: {workspace_id: string}) => Promise<
|
|
12
|
+
installRequest: (body: {workspace_id: string}) => Promise<InstallRedirect>;
|
|
12
13
|
errorFallbackMessage: string;
|
|
13
14
|
loadingLabel?: string;
|
|
14
15
|
/**
|
|
@@ -44,7 +45,7 @@ export function RedirectInstallPage({
|
|
|
44
45
|
}
|
|
45
46
|
installRequest({workspace_id: workspace.id})
|
|
46
47
|
.then((response) => {
|
|
47
|
-
assignLocation(response.
|
|
48
|
+
assignLocation(response.installUrl);
|
|
48
49
|
})
|
|
49
50
|
.catch((error: unknown) => {
|
|
50
51
|
setErrorMessage(error instanceof ApiError ? error.message : errorFallbackMessage);
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type {RepositoryDto} from '@shipfox/api-integration-core-dto';
|
|
2
1
|
import {Button} from '@shipfox/react-ui/button';
|
|
3
2
|
import {Input} from '@shipfox/react-ui/input';
|
|
4
3
|
import {Label} from '@shipfox/react-ui/label';
|
|
@@ -6,6 +5,7 @@ import {RadioGroup, RadioGroupItem} from '@shipfox/react-ui/radio-group';
|
|
|
6
5
|
import {Skeleton} from '@shipfox/react-ui/skeleton';
|
|
7
6
|
import {Text} from '@shipfox/react-ui/typography';
|
|
8
7
|
import {useId} from 'react';
|
|
8
|
+
import type {Repository} from '#core/models.js';
|
|
9
9
|
|
|
10
10
|
export function RepositoryPicker({
|
|
11
11
|
repositories,
|
|
@@ -20,7 +20,7 @@ export function RepositoryPicker({
|
|
|
20
20
|
onSearchChange,
|
|
21
21
|
searchDisabled,
|
|
22
22
|
}: {
|
|
23
|
-
repositories:
|
|
23
|
+
repositories: Repository[];
|
|
24
24
|
selectedRepositoryId: string | undefined;
|
|
25
25
|
onSelect: (repositoryId: string) => void;
|
|
26
26
|
isLoading: boolean;
|
|
@@ -69,16 +69,16 @@ export function RepositoryPicker({
|
|
|
69
69
|
>
|
|
70
70
|
{repositories.map((repository) => (
|
|
71
71
|
<RadioGroupItem
|
|
72
|
-
key={repository.
|
|
73
|
-
value={repository.
|
|
72
|
+
key={repository.externalRepositoryId}
|
|
73
|
+
value={repository.externalRepositoryId}
|
|
74
74
|
className="p-12"
|
|
75
75
|
>
|
|
76
76
|
<span className="flex min-w-0 items-center justify-between gap-10">
|
|
77
77
|
<Text as="span" size="sm" bold className="truncate">
|
|
78
|
-
{repository.
|
|
78
|
+
{repository.fullName}
|
|
79
79
|
</Text>
|
|
80
80
|
<Text as="span" size="xs" className="shrink-0 text-foreground-neutral-muted">
|
|
81
|
-
{repository.
|
|
81
|
+
{repository.defaultBranch}
|
|
82
82
|
</Text>
|
|
83
83
|
</span>
|
|
84
84
|
</RadioGroupItem>
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import type {IntegrationConnectionDto} from '@shipfox/api-integration-core-dto';
|
|
2
1
|
import {
|
|
3
2
|
createWebhookConnectionBodySchema,
|
|
4
3
|
WEBHOOK_RESERVED_SLUGS,
|
|
5
|
-
type WebhookConnectionDto,
|
|
6
4
|
webhookSlugSchema,
|
|
7
5
|
} from '@shipfox/api-integration-webhook-dto';
|
|
8
6
|
import {displayNameFieldError} from '@shipfox/client-ui';
|
|
@@ -21,6 +19,7 @@ import {toast} from '@shipfox/react-ui/toast';
|
|
|
21
19
|
import {Code, Text} from '@shipfox/react-ui/typography';
|
|
22
20
|
import {useForm} from '@tanstack/react-form';
|
|
23
21
|
import {useEffect, useRef, useState} from 'react';
|
|
22
|
+
import type {IntegrationConnection, WebhookConnection} from '#core/models.js';
|
|
24
23
|
import {useCreateWebhookConnectionMutation} from '#hooks/api/webhook-connections.js';
|
|
25
24
|
import {webhookCreateErrorToFormError} from './webhook-form-errors.js';
|
|
26
25
|
|
|
@@ -28,7 +27,7 @@ interface WebhookCreateModalProps {
|
|
|
28
27
|
workspaceId: string;
|
|
29
28
|
open: boolean;
|
|
30
29
|
onOpenChange: (open: boolean) => void;
|
|
31
|
-
onCreated: (connection:
|
|
30
|
+
onCreated: (connection: IntegrationConnection) => void;
|
|
32
31
|
}
|
|
33
32
|
|
|
34
33
|
export function WebhookCreateModal({
|
|
@@ -53,7 +52,7 @@ export function WebhookCreateModal({
|
|
|
53
52
|
slug: value.slug.trim(),
|
|
54
53
|
});
|
|
55
54
|
const connection = await createWebhook.mutateAsync(body);
|
|
56
|
-
onCreated(
|
|
55
|
+
onCreated(webhookConnectionToIntegrationConnection(connection));
|
|
57
56
|
toast.success('Webhook created.');
|
|
58
57
|
onOpenChange(false);
|
|
59
58
|
} catch (error) {
|
|
@@ -231,17 +230,19 @@ function suggestWebhookSlug(name: string): string {
|
|
|
231
230
|
return suffix ? `${prefix}-${suffix}` : prefix;
|
|
232
231
|
}
|
|
233
232
|
|
|
234
|
-
function
|
|
233
|
+
function webhookConnectionToIntegrationConnection(
|
|
234
|
+
connection: WebhookConnection,
|
|
235
|
+
): IntegrationConnection {
|
|
235
236
|
return {
|
|
236
237
|
id: connection.id,
|
|
237
|
-
|
|
238
|
+
workspaceId: connection.workspaceId,
|
|
238
239
|
provider: 'webhook',
|
|
239
|
-
|
|
240
|
+
externalAccountId: connection.slug,
|
|
240
241
|
slug: connection.slug,
|
|
241
|
-
|
|
242
|
-
|
|
242
|
+
displayName: connection.name,
|
|
243
|
+
lifecycleStatus: connection.lifecycleStatus,
|
|
243
244
|
capabilities: [],
|
|
244
|
-
|
|
245
|
-
|
|
245
|
+
createdAt: connection.createdAt,
|
|
246
|
+
updatedAt: connection.updatedAt,
|
|
246
247
|
};
|
|
247
248
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type {IntegrationConnectionDto} from '@shipfox/api-integration-core-dto';
|
|
2
1
|
import type {WebhookConnectionDto} from '@shipfox/api-integration-webhook-dto';
|
|
3
2
|
import {WEBHOOK_RECEIVED_EVENT} from '@shipfox/api-integration-webhook-dto';
|
|
4
3
|
import {configureApiClient} from '@shipfox/client-api';
|
|
@@ -14,6 +13,7 @@ import {
|
|
|
14
13
|
RouterProvider,
|
|
15
14
|
} from '@tanstack/react-router';
|
|
16
15
|
import {useMemo} from 'react';
|
|
16
|
+
import type {IntegrationConnection} from '#core/models.js';
|
|
17
17
|
import {IntegrationDeleteConfirmModal} from '../integration-delete-confirm-modal.js';
|
|
18
18
|
import {IntegrationUsageModal} from '../integration-usage-modal.js';
|
|
19
19
|
import {CopyableValue} from './copyable-value.js';
|
|
@@ -52,22 +52,22 @@ const disabledConnection: WebhookConnectionDto = {
|
|
|
52
52
|
lifecycle_status: 'disabled',
|
|
53
53
|
};
|
|
54
54
|
|
|
55
|
-
const activeIntegrationConnection:
|
|
55
|
+
const activeIntegrationConnection: IntegrationConnection = {
|
|
56
56
|
id: CONNECTION_ID,
|
|
57
|
-
|
|
57
|
+
workspaceId: WORKSPACE_ID,
|
|
58
58
|
provider: 'webhook',
|
|
59
|
-
|
|
59
|
+
externalAccountId: activeConnection.slug,
|
|
60
60
|
slug: activeConnection.slug,
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
displayName: activeConnection.name,
|
|
62
|
+
lifecycleStatus: activeConnection.lifecycle_status,
|
|
63
63
|
capabilities: [],
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
createdAt: activeConnection.created_at,
|
|
65
|
+
updatedAt: activeConnection.updated_at,
|
|
66
66
|
};
|
|
67
67
|
|
|
68
|
-
const disabledIntegrationConnection:
|
|
68
|
+
const disabledIntegrationConnection: IntegrationConnection = {
|
|
69
69
|
...activeIntegrationConnection,
|
|
70
|
-
|
|
70
|
+
lifecycleStatus: 'disabled',
|
|
71
71
|
};
|
|
72
72
|
|
|
73
73
|
function WebhookModalStory({scenario}: WebhookModalStoryProps) {
|
|
@@ -12,9 +12,7 @@ interface WebhookUsageDetailsProps {
|
|
|
12
12
|
|
|
13
13
|
export function WebhookUsageDetails({workspaceId, connectionId}: WebhookUsageDetailsProps) {
|
|
14
14
|
const connectionsQuery = useWebhookConnectionsQuery(workspaceId);
|
|
15
|
-
const connection = connectionsQuery.data?.
|
|
16
|
-
(candidate) => candidate.id === connectionId,
|
|
17
|
-
);
|
|
15
|
+
const connection = connectionsQuery.data?.find((candidate) => candidate.id === connectionId);
|
|
18
16
|
|
|
19
17
|
if (connectionsQuery.isPending) {
|
|
20
18
|
return (
|
|
@@ -36,7 +34,7 @@ export function WebhookUsageDetails({workspaceId, connectionId}: WebhookUsageDet
|
|
|
36
34
|
<Text size="sm" bold>
|
|
37
35
|
Inbound URL
|
|
38
36
|
</Text>
|
|
39
|
-
<CopyableValue label="inbound URL" value={connection.
|
|
37
|
+
<CopyableValue label="inbound URL" value={connection.inboundUrl} />
|
|
40
38
|
<WebhookPublicEndpointAlert />
|
|
41
39
|
</div>
|
|
42
40
|
</div>
|
|
@@ -1,19 +1,9 @@
|
|
|
1
|
-
import type {IntegrationConnectionLifecycleStatusDto} from '@shipfox/api-integration-core-dto';
|
|
2
1
|
import {Badge} from '@shipfox/react-ui/badge';
|
|
3
2
|
import type {IconName} from '@shipfox/react-ui/icon';
|
|
4
|
-
|
|
5
|
-
const lifecyclePills: Record<
|
|
6
|
-
IntegrationConnectionLifecycleStatusDto,
|
|
7
|
-
{variant: 'warning' | 'error'; label: string; iconLeft?: IconName} | undefined
|
|
8
|
-
> = {
|
|
9
|
-
// `active` is the expected state and carries no badge.
|
|
10
|
-
active: undefined,
|
|
11
|
-
disabled: {variant: 'warning', label: 'Disabled', iconLeft: 'errorWarningLine'},
|
|
12
|
-
error: {variant: 'error', label: 'Error'},
|
|
13
|
-
};
|
|
3
|
+
import {type ConnectionLifecycleStatus, connectionLifecyclePresentation} from '#core/models.js';
|
|
14
4
|
|
|
15
5
|
export interface ConnectionStatusBadgeProps {
|
|
16
|
-
status:
|
|
6
|
+
status: ConnectionLifecycleStatus;
|
|
17
7
|
className?: string;
|
|
18
8
|
}
|
|
19
9
|
|
|
@@ -23,8 +13,12 @@ export interface ConnectionStatusBadgeProps {
|
|
|
23
13
|
* nothing for the expected `active` state.
|
|
24
14
|
*/
|
|
25
15
|
export function ConnectionStatusBadge({status, className}: ConnectionStatusBadgeProps) {
|
|
26
|
-
const
|
|
27
|
-
if (
|
|
16
|
+
const presentation = connectionLifecyclePresentation(status);
|
|
17
|
+
if (presentation.kind === 'active') return null;
|
|
18
|
+
const pill: {variant: 'warning' | 'error'; label: string; iconLeft?: IconName} =
|
|
19
|
+
presentation.kind === 'disabled'
|
|
20
|
+
? {variant: 'warning', label: presentation.label, iconLeft: 'errorWarningLine'}
|
|
21
|
+
: {variant: 'error', label: presentation.label};
|
|
28
22
|
return (
|
|
29
23
|
<Badge
|
|
30
24
|
variant={pill.variant}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
export type IntegrationCapability = 'source_control' | 'agent_tools';
|
|
2
|
+
export type ConnectionLifecycleStatus = 'active' | 'disabled' | 'error';
|
|
3
|
+
|
|
4
|
+
export interface IntegrationProvider {
|
|
5
|
+
provider: string;
|
|
6
|
+
displayName: string;
|
|
7
|
+
capabilities: IntegrationCapability[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface IntegrationConnection {
|
|
11
|
+
id: string;
|
|
12
|
+
workspaceId: string;
|
|
13
|
+
provider: string;
|
|
14
|
+
externalAccountId: string;
|
|
15
|
+
slug: string;
|
|
16
|
+
displayName: string;
|
|
17
|
+
lifecycleStatus: ConnectionLifecycleStatus;
|
|
18
|
+
capabilities: IntegrationCapability[];
|
|
19
|
+
externalUrl?: string | undefined;
|
|
20
|
+
createdAt: string;
|
|
21
|
+
updatedAt: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface Repository {
|
|
25
|
+
connectionId: string;
|
|
26
|
+
externalRepositoryId: string;
|
|
27
|
+
owner: string;
|
|
28
|
+
name: string;
|
|
29
|
+
fullName: string;
|
|
30
|
+
defaultBranch: string;
|
|
31
|
+
visibility: 'public' | 'private' | 'internal' | 'unknown';
|
|
32
|
+
cloneUrl: string;
|
|
33
|
+
htmlUrl: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface RepositoryPage {
|
|
37
|
+
repositories: Repository[];
|
|
38
|
+
nextCursor?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface WebhookConnection {
|
|
42
|
+
id: string;
|
|
43
|
+
workspaceId: string;
|
|
44
|
+
name: string;
|
|
45
|
+
slug: string;
|
|
46
|
+
lifecycleStatus: ConnectionLifecycleStatus;
|
|
47
|
+
inboundUrl: string;
|
|
48
|
+
createdAt: string;
|
|
49
|
+
updatedAt: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface InstallRedirect {
|
|
53
|
+
installUrl: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export type IntegrationUsageEvent = {
|
|
57
|
+
value: string;
|
|
58
|
+
label: string;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export type ConnectionLifecyclePresentation =
|
|
62
|
+
| {kind: 'active'}
|
|
63
|
+
| {kind: 'disabled'; label: 'Disabled'}
|
|
64
|
+
| {kind: 'error'; label: 'Error'};
|
|
65
|
+
|
|
66
|
+
const githubUsageEvents = [
|
|
67
|
+
'push',
|
|
68
|
+
'branch_protection_configuration',
|
|
69
|
+
'branch_protection_rule',
|
|
70
|
+
'check_run',
|
|
71
|
+
'check_suite',
|
|
72
|
+
'code_scanning_alert',
|
|
73
|
+
'commit_comment',
|
|
74
|
+
'create',
|
|
75
|
+
'custom_property',
|
|
76
|
+
'custom_property_values',
|
|
77
|
+
'delete',
|
|
78
|
+
'dependabot_alert',
|
|
79
|
+
'deploy_key',
|
|
80
|
+
'deployment',
|
|
81
|
+
'deployment_protection_rule',
|
|
82
|
+
'deployment_review',
|
|
83
|
+
'deployment_status',
|
|
84
|
+
'discussion',
|
|
85
|
+
'discussion_comment',
|
|
86
|
+
'fork',
|
|
87
|
+
'github_app_authorization',
|
|
88
|
+
'gollum',
|
|
89
|
+
'installation',
|
|
90
|
+
'installation_repositories',
|
|
91
|
+
'installation_target',
|
|
92
|
+
'issue_comment',
|
|
93
|
+
'issue_dependencies',
|
|
94
|
+
'issues',
|
|
95
|
+
'label',
|
|
96
|
+
'marketplace_purchase',
|
|
97
|
+
'member',
|
|
98
|
+
'membership',
|
|
99
|
+
'merge_group',
|
|
100
|
+
'meta',
|
|
101
|
+
'milestone',
|
|
102
|
+
'org_block',
|
|
103
|
+
'organization',
|
|
104
|
+
'package',
|
|
105
|
+
'page_build',
|
|
106
|
+
'personal_access_token_request',
|
|
107
|
+
'ping',
|
|
108
|
+
'project',
|
|
109
|
+
'project_card',
|
|
110
|
+
'project_column',
|
|
111
|
+
'projects_v2',
|
|
112
|
+
'projects_v2_item',
|
|
113
|
+
'projects_v2_status_update',
|
|
114
|
+
'public',
|
|
115
|
+
'pull_request',
|
|
116
|
+
'pull_request_review',
|
|
117
|
+
'pull_request_review_comment',
|
|
118
|
+
'pull_request_review_thread',
|
|
119
|
+
'registry_package',
|
|
120
|
+
'release',
|
|
121
|
+
'repository',
|
|
122
|
+
'repository_advisory',
|
|
123
|
+
'repository_dispatch',
|
|
124
|
+
'repository_import',
|
|
125
|
+
'repository_ruleset',
|
|
126
|
+
'repository_vulnerability_alert',
|
|
127
|
+
'secret_scanning_alert',
|
|
128
|
+
'secret_scanning_alert_location',
|
|
129
|
+
'secret_scanning_scan',
|
|
130
|
+
'security_advisory',
|
|
131
|
+
'security_and_analysis',
|
|
132
|
+
'sponsorship',
|
|
133
|
+
'star',
|
|
134
|
+
'status',
|
|
135
|
+
'sub_issues',
|
|
136
|
+
'team',
|
|
137
|
+
'team_add',
|
|
138
|
+
'watch',
|
|
139
|
+
'workflow_dispatch',
|
|
140
|
+
'workflow_job',
|
|
141
|
+
'workflow_run',
|
|
142
|
+
] as const;
|
|
143
|
+
|
|
144
|
+
const linearUsageEvents = [
|
|
145
|
+
'Issue.create',
|
|
146
|
+
'Issue.update',
|
|
147
|
+
'Issue.remove',
|
|
148
|
+
'Comment.create',
|
|
149
|
+
'Comment.update',
|
|
150
|
+
'Comment.remove',
|
|
151
|
+
'IssueLabel.create',
|
|
152
|
+
'IssueLabel.update',
|
|
153
|
+
'IssueLabel.remove',
|
|
154
|
+
'Project.create',
|
|
155
|
+
'Project.update',
|
|
156
|
+
'Project.remove',
|
|
157
|
+
'Cycle.create',
|
|
158
|
+
'Cycle.update',
|
|
159
|
+
'Cycle.remove',
|
|
160
|
+
'agentSession.created',
|
|
161
|
+
'agentSession.prompted',
|
|
162
|
+
] as const;
|
|
163
|
+
|
|
164
|
+
const sentryIssueActions = ['created', 'resolved', 'assigned', 'archived', 'unresolved'] as const;
|
|
165
|
+
|
|
166
|
+
export function usageEventsForConnection(
|
|
167
|
+
connection: Pick<IntegrationConnection, 'provider' | 'capabilities'>,
|
|
168
|
+
): IntegrationUsageEvent[] {
|
|
169
|
+
if (connection.provider === 'webhook') return [{value: 'received', label: 'received'}];
|
|
170
|
+
if (connection.provider === 'github')
|
|
171
|
+
return githubUsageEvents.map((value) => ({value, label: value}));
|
|
172
|
+
if (connection.provider === 'sentry')
|
|
173
|
+
return sentryIssueActions.map((action) => ({
|
|
174
|
+
value: `issue.${action}`,
|
|
175
|
+
label: `issue.${action}`,
|
|
176
|
+
}));
|
|
177
|
+
if (connection.provider === 'linear')
|
|
178
|
+
return linearUsageEvents.map((value) => ({value, label: value}));
|
|
179
|
+
if (connection.capabilities.includes('source_control')) return [{value: 'push', label: 'push'}];
|
|
180
|
+
return [{value: 'received', label: 'received'}];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function connectionLifecyclePresentation(
|
|
184
|
+
status: ConnectionLifecycleStatus,
|
|
185
|
+
): ConnectionLifecyclePresentation {
|
|
186
|
+
if (status === 'disabled') return {kind: 'disabled', label: 'Disabled'};
|
|
187
|
+
if (status === 'error') return {kind: 'error', label: 'Error'};
|
|
188
|
+
return {kind: 'active'};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function hasCapability(
|
|
192
|
+
connection: IntegrationConnection,
|
|
193
|
+
capability: IntegrationCapability,
|
|
194
|
+
) {
|
|
195
|
+
return connection.capabilities.includes(capability);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function isUsableConnection(connection: IntegrationConnection) {
|
|
199
|
+
return connection.lifecycleStatus === 'active';
|
|
200
|
+
}
|
package/src/feature.ts
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
|
-
import {defineClientFeature} from '@shipfox/client-shell';
|
|
1
|
+
import {defineClientFeature, type SettingsSectionEntry} from '@shipfox/client-shell';
|
|
2
|
+
|
|
3
|
+
export const integrationsSettingsSections = [
|
|
4
|
+
{
|
|
5
|
+
id: 'settings.integrations',
|
|
6
|
+
pathSegment: 'integrations',
|
|
7
|
+
label: 'Integrations',
|
|
8
|
+
icon: 'plugLine',
|
|
9
|
+
order: 700,
|
|
10
|
+
},
|
|
11
|
+
] as const satisfies readonly SettingsSectionEntry[];
|
|
2
12
|
|
|
3
13
|
export const integrationsFeature = defineClientFeature({
|
|
4
14
|
id: 'shipfox.integrations',
|
|
@@ -53,5 +63,11 @@ export const integrationsFeature = defineClientFeature({
|
|
|
53
63
|
parent: 'workspaceLayout',
|
|
54
64
|
impl: '@shipfox/client-integrations/routes/slack',
|
|
55
65
|
},
|
|
66
|
+
{
|
|
67
|
+
path: '/workspaces/$wid/settings/integrations',
|
|
68
|
+
parent: 'workspaceSettings',
|
|
69
|
+
impl: '@shipfox/client-integrations/routes/integrations-settings',
|
|
70
|
+
},
|
|
56
71
|
],
|
|
72
|
+
settingsSections: integrationsSettingsSections,
|
|
57
73
|
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
IntegrationConnectionDto,
|
|
3
|
+
IntegrationProviderDto,
|
|
4
|
+
RepositoryDto,
|
|
5
|
+
} from '@shipfox/api-integration-core-dto';
|
|
6
|
+
import type {WebhookConnectionDto} from '@shipfox/api-integration-webhook-dto';
|
|
7
|
+
import type {
|
|
8
|
+
InstallRedirect,
|
|
9
|
+
IntegrationConnection,
|
|
10
|
+
IntegrationProvider,
|
|
11
|
+
Repository,
|
|
12
|
+
WebhookConnection,
|
|
13
|
+
} from '#core/models.js';
|
|
14
|
+
|
|
15
|
+
export function toIntegrationProvider(dto: IntegrationProviderDto): IntegrationProvider {
|
|
16
|
+
return {provider: dto.provider, displayName: dto.display_name, capabilities: dto.capabilities};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function toInstallRedirect(dto: {install_url: string}): InstallRedirect {
|
|
20
|
+
return {installUrl: dto.install_url};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function toIntegrationConnection(dto: IntegrationConnectionDto): IntegrationConnection {
|
|
24
|
+
return {
|
|
25
|
+
id: dto.id,
|
|
26
|
+
workspaceId: dto.workspace_id,
|
|
27
|
+
provider: dto.provider,
|
|
28
|
+
externalAccountId: dto.external_account_id,
|
|
29
|
+
slug: dto.slug,
|
|
30
|
+
displayName: dto.display_name,
|
|
31
|
+
lifecycleStatus: dto.lifecycle_status,
|
|
32
|
+
capabilities: dto.capabilities,
|
|
33
|
+
externalUrl: dto.external_url,
|
|
34
|
+
createdAt: dto.created_at,
|
|
35
|
+
updatedAt: dto.updated_at,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function toRepository(dto: RepositoryDto): Repository {
|
|
40
|
+
return {
|
|
41
|
+
connectionId: dto.connection_id,
|
|
42
|
+
externalRepositoryId: dto.external_repository_id,
|
|
43
|
+
owner: dto.owner,
|
|
44
|
+
name: dto.name,
|
|
45
|
+
fullName: dto.full_name,
|
|
46
|
+
defaultBranch: dto.default_branch,
|
|
47
|
+
visibility: dto.visibility,
|
|
48
|
+
cloneUrl: dto.clone_url,
|
|
49
|
+
htmlUrl: dto.html_url,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function toWebhookConnection(dto: WebhookConnectionDto): WebhookConnection {
|
|
54
|
+
return {
|
|
55
|
+
id: dto.id,
|
|
56
|
+
workspaceId: dto.workspace_id,
|
|
57
|
+
name: dto.name,
|
|
58
|
+
slug: dto.slug,
|
|
59
|
+
lifecycleStatus: dto.lifecycle_status,
|
|
60
|
+
inboundUrl: dto.inbound_url,
|
|
61
|
+
createdAt: dto.created_at,
|
|
62
|
+
updatedAt: dto.updated_at,
|
|
63
|
+
};
|
|
64
|
+
}
|