@shipfox/client-integrations 12.0.1 → 13.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +41 -0
  3. package/dist/components/callback-status-shell.d.ts +4 -3
  4. package/dist/components/callback-status-shell.d.ts.map +1 -1
  5. package/dist/components/callback-status-shell.js +11 -11
  6. package/dist/components/callback-status-shell.js.map +1 -1
  7. package/dist/components/installed-integrations-section.d.ts +2 -1
  8. package/dist/components/installed-integrations-section.d.ts.map +1 -1
  9. package/dist/components/installed-integrations-section.js +7 -6
  10. package/dist/components/installed-integrations-section.js.map +1 -1
  11. package/dist/components/integration-gallery-for-workspace.d.ts.map +1 -1
  12. package/dist/components/integration-gallery-for-workspace.js +11 -2
  13. package/dist/components/integration-gallery-for-workspace.js.map +1 -1
  14. package/dist/components/integration-gallery.stories.js +10 -9
  15. package/dist/components/integration-gallery.stories.js.map +1 -1
  16. package/dist/components/provider-grid.d.ts +2 -2
  17. package/dist/components/provider-grid.d.ts.map +1 -1
  18. package/dist/components/provider-grid.js +4 -4
  19. package/dist/components/provider-grid.js.map +1 -1
  20. package/dist/components/redirect-install-page.d.ts +1 -1
  21. package/dist/components/redirect-install-page.js +2 -2
  22. package/dist/components/redirect-install-page.js.map +1 -1
  23. package/dist/components/webhook/webhook-modals.stories.js +2 -2
  24. package/dist/components/webhook/webhook-modals.stories.js.map +1 -1
  25. package/dist/feature.d.ts +7 -7
  26. package/dist/feature.js +7 -7
  27. package/dist/feature.js.map +1 -1
  28. package/dist/hooks/api/integrations.js +1 -1
  29. package/dist/hooks/api/integrations.js.map +1 -1
  30. package/dist/pages/gitea-install-page.js +4 -4
  31. package/dist/pages/gitea-install-page.js.map +1 -1
  32. package/dist/pages/linear-callback-page.d.ts.map +1 -1
  33. package/dist/pages/linear-callback-page.js +58 -12
  34. package/dist/pages/linear-callback-page.js.map +1 -1
  35. package/dist/pages/sentry-callback-page.d.ts.map +1 -1
  36. package/dist/pages/sentry-callback-page.js +22 -9
  37. package/dist/pages/sentry-callback-page.js.map +1 -1
  38. package/dist/pages/slack-callback-page.d.ts.map +1 -1
  39. package/dist/pages/slack-callback-page.js +50 -18
  40. package/dist/pages/slack-callback-page.js.map +1 -1
  41. package/dist/pages/source-control-onboarding-page.js +1 -1
  42. package/dist/pages/source-control-onboarding-page.js.map +1 -1
  43. package/dist/provider-catalog.d.ts +1 -1
  44. package/dist/provider-catalog.d.ts.map +1 -1
  45. package/dist/provider-catalog.js +5 -5
  46. package/dist/provider-catalog.js.map +1 -1
  47. package/dist/sentry-callback.js +2 -2
  48. package/dist/sentry-callback.js.map +1 -1
  49. package/dist/tsconfig.test.tsbuildinfo +1 -1
  50. package/dist/workspace-navigation.d.ts +13 -0
  51. package/dist/workspace-navigation.d.ts.map +1 -0
  52. package/dist/workspace-navigation.js +17 -0
  53. package/dist/workspace-navigation.js.map +1 -0
  54. package/package.json +13 -13
  55. package/src/components/callback-status-shell.tsx +14 -10
  56. package/src/components/installed-integrations-section.tsx +16 -9
  57. package/src/components/integration-gallery-for-workspace.tsx +20 -10
  58. package/src/components/integration-gallery.stories.tsx +10 -10
  59. package/src/components/integration-gallery.test.tsx +30 -15
  60. package/src/components/provider-grid.tsx +6 -6
  61. package/src/components/redirect-install-page.test.tsx +3 -3
  62. package/src/components/redirect-install-page.tsx +2 -2
  63. package/src/components/webhook/webhook-modals.stories.tsx +2 -2
  64. package/src/feature.ts +7 -7
  65. package/src/hooks/api/integrations.ts +1 -1
  66. package/src/pages/gitea-install-page.tsx +2 -2
  67. package/src/pages/linear-callback-page.test.tsx +25 -12
  68. package/src/pages/linear-callback-page.tsx +73 -12
  69. package/src/pages/sentry-callback-page.test.tsx +8 -5
  70. package/src/pages/sentry-callback-page.tsx +32 -8
  71. package/src/pages/slack-callback-page.test.tsx +78 -0
  72. package/src/pages/slack-callback-page.tsx +56 -18
  73. package/src/pages/source-control-onboarding-page.test.tsx +4 -4
  74. package/src/pages/source-control-onboarding-page.tsx +1 -1
  75. package/src/provider-catalog.test.ts +4 -4
  76. package/src/provider-catalog.ts +10 -10
  77. package/src/sentry-callback.ts +2 -2
  78. package/src/workspace-navigation.test.ts +43 -0
  79. package/src/workspace-navigation.ts +38 -0
  80. package/test/render.tsx +2 -1
  81. package/tsconfig.build.tsbuildinfo +1 -1
@@ -4,20 +4,20 @@ describe('PROVIDER_CATALOG', () => {
4
4
  test('contains route entries for github, sentry, and gitea', () => {
5
5
  expect(PROVIDER_CATALOG.github).toMatchObject({
6
6
  kind: 'redirect-install',
7
- setupPath: '/workspaces/$wid/integrations/github',
7
+ setupPath: '/w/$workspaceSlug/integrations/github',
8
8
  });
9
9
  expect(PROVIDER_CATALOG.sentry).toMatchObject({
10
10
  kind: 'redirect-install',
11
- setupPath: '/workspaces/$wid/integrations/sentry',
11
+ setupPath: '/w/$workspaceSlug/integrations/sentry',
12
12
  });
13
13
  expect(PROVIDER_CATALOG.linear).toMatchObject({
14
14
  kind: 'redirect-install',
15
15
  iconName: 'linear',
16
- setupPath: '/workspaces/$wid/integrations/linear',
16
+ setupPath: '/w/$workspaceSlug/integrations/linear',
17
17
  });
18
18
  expect(PROVIDER_CATALOG.gitea).toMatchObject({
19
19
  kind: 'direct-connect',
20
- setupPath: '/workspaces/$wid/integrations/gitea',
20
+ setupPath: '/w/$workspaceSlug/integrations/gitea',
21
21
  });
22
22
  });
23
23
 
@@ -4,11 +4,11 @@ import type {IconName} from '@shipfox/react-ui/icon';
4
4
  // Literal union (not string) so `<Link to={catalog.setupPath}>` stays typed
5
5
  // against TanStack Router's route tree.
6
6
  export type ProviderSetupPath =
7
- | '/workspaces/$wid/integrations/github'
8
- | '/workspaces/$wid/integrations/gitea'
9
- | '/workspaces/$wid/integrations/sentry'
10
- | '/workspaces/$wid/integrations/linear'
11
- | '/workspaces/$wid/integrations/slack';
7
+ | '/w/$workspaceSlug/integrations/github'
8
+ | '/w/$workspaceSlug/integrations/gitea'
9
+ | '/w/$workspaceSlug/integrations/sentry'
10
+ | '/w/$workspaceSlug/integrations/linear'
11
+ | '/w/$workspaceSlug/integrations/slack';
12
12
 
13
13
  interface RouteProviderCatalogEntry {
14
14
  kind: 'redirect-install' | 'direct-connect';
@@ -27,27 +27,27 @@ export const PROVIDER_CATALOG: Record<string, ProviderCatalogEntry> = {
27
27
  github: {
28
28
  kind: 'redirect-install',
29
29
  iconName: PROVIDER_ICONS.github,
30
- setupPath: '/workspaces/$wid/integrations/github',
30
+ setupPath: '/w/$workspaceSlug/integrations/github',
31
31
  },
32
32
  sentry: {
33
33
  kind: 'redirect-install',
34
34
  iconName: PROVIDER_ICONS.sentry,
35
- setupPath: '/workspaces/$wid/integrations/sentry',
35
+ setupPath: '/w/$workspaceSlug/integrations/sentry',
36
36
  },
37
37
  linear: {
38
38
  kind: 'redirect-install',
39
39
  iconName: PROVIDER_ICONS.linear,
40
- setupPath: '/workspaces/$wid/integrations/linear',
40
+ setupPath: '/w/$workspaceSlug/integrations/linear',
41
41
  },
42
42
  slack: {
43
43
  kind: 'redirect-install',
44
44
  iconName: PROVIDER_ICONS.slack,
45
- setupPath: '/workspaces/$wid/integrations/slack',
45
+ setupPath: '/w/$workspaceSlug/integrations/slack',
46
46
  },
47
47
  gitea: {
48
48
  kind: 'direct-connect',
49
49
  iconName: PROVIDER_ICONS.gitea,
50
- setupPath: '/workspaces/$wid/integrations/gitea',
50
+ setupPath: '/w/$workspaceSlug/integrations/gitea',
51
51
  },
52
52
  webhook: {
53
53
  kind: 'modal-connect',
@@ -20,7 +20,7 @@ const sentryInstallWorkspaceStorageKey = {
20
20
  } satisfies BrowserStorageKey<string>;
21
21
 
22
22
  // Storage helpers swallow failures (quota, private mode, disabled storage):
23
- // the handoff is an optimization, never a requirement the callback always
23
+ // the handoff is an optimization, never a requirement: the callback always
24
24
  // asks for explicit confirmation and only uses the stored id to pre-select.
25
25
  export function saveSentryInstallWorkspace(storage: WorkspaceStorage, workspaceId: string): void {
26
26
  installWorkspaceStorage(storage).write(workspaceId);
@@ -67,7 +67,7 @@ export type SentryWorkspacePreselection =
67
67
  | {kind: 'none'};
68
68
 
69
69
  // Sentry's redirect carries no state token, so the callback can never prove it
70
- // belongs to an install the user started installation always requires an
70
+ // belongs to an install the user started. Installation always requires an
71
71
  // explicit click. The stored id (and a sole workspace) only pre-select.
72
72
  export function preselectSentryWorkspace(
73
73
  storedId: string | undefined,
@@ -0,0 +1,43 @@
1
+ import {configureApiClient} from '@shipfox/client-api';
2
+ import {userWorkspacesQueryKey} from '@shipfox/client-shell/runtime';
3
+ import {QueryClient} from '@tanstack/react-query';
4
+ import {resolveWorkspaceSlug} from './workspace-navigation.js';
5
+
6
+ function jsonResponse(body: unknown, init: ResponseInit = {}): Response {
7
+ return new Response(JSON.stringify(body), {
8
+ headers: {'content-type': 'application/json'},
9
+ status: 200,
10
+ ...init,
11
+ });
12
+ }
13
+
14
+ describe('resolveWorkspaceSlug', () => {
15
+ beforeEach(() => {
16
+ configureApiClient({baseUrl: 'https://api.example.test', fetchImpl: undefined});
17
+ });
18
+
19
+ test('uses refreshed cached memberships when the follow-up list request fails', async () => {
20
+ configureApiClient({
21
+ fetchImpl: vi
22
+ .fn()
23
+ .mockResolvedValue(
24
+ jsonResponse(
25
+ {code: 'server-error', message: 'Workspace list unavailable'},
26
+ {status: 500},
27
+ ),
28
+ ),
29
+ });
30
+ const queryClient = new QueryClient();
31
+ queryClient.setQueryData(userWorkspacesQueryKey, {
32
+ memberships: [{id: 'response-workspace', slug: 'response-workspace'}],
33
+ });
34
+
35
+ await expect(
36
+ resolveWorkspaceSlug({
37
+ workspaceId: 'response-workspace',
38
+ fallbackWorkspaces: [{id: 'response-workspace', slug: 'old-slug'}],
39
+ queryClient,
40
+ }),
41
+ ).resolves.toBe('response-workspace');
42
+ });
43
+ });
@@ -0,0 +1,38 @@
1
+ import {listUserWorkspaces, userWorkspacesQueryKey} from '@shipfox/client-shell/runtime';
2
+ import type {QueryClient} from '@tanstack/react-query';
3
+
4
+ interface WorkspaceSlugCandidate {
5
+ id: string;
6
+ slug: string;
7
+ }
8
+
9
+ interface UserWorkspacesQueryData {
10
+ memberships: WorkspaceSlugCandidate[];
11
+ }
12
+
13
+ export function rememberCallbackKey(keys: Set<string>, key: string, maxSize = 32): void {
14
+ keys.delete(key);
15
+ keys.add(key);
16
+ if (keys.size <= maxSize) return;
17
+ const oldest = keys.values().next().value;
18
+ if (typeof oldest === 'string') keys.delete(oldest);
19
+ }
20
+
21
+ export async function resolveWorkspaceSlug({
22
+ workspaceId,
23
+ fallbackWorkspaces,
24
+ queryClient,
25
+ }: {
26
+ workspaceId: string;
27
+ fallbackWorkspaces: readonly WorkspaceSlugCandidate[];
28
+ queryClient: QueryClient;
29
+ }): Promise<string | undefined> {
30
+ const currentWorkspaces = await listUserWorkspaces()
31
+ .then(({memberships}) => memberships)
32
+ .catch(() => {
33
+ const refreshedWorkspaces =
34
+ queryClient.getQueryData<UserWorkspacesQueryData>(userWorkspacesQueryKey);
35
+ return refreshedWorkspaces?.memberships ?? fallbackWorkspaces;
36
+ });
37
+ return currentWorkspaces.find(({id}) => id === workspaceId)?.slug;
38
+ }
package/test/render.tsx CHANGED
@@ -19,11 +19,12 @@ export const INTEGRATIONS_TEST_WID = '11111111-1111-4111-8111-111111111111';
19
19
  export interface TestWorkspace {
20
20
  id: string;
21
21
  name: string;
22
+ slug: string;
22
23
  membershipId: string;
23
24
  }
24
25
 
25
26
  export function testWorkspace(overrides: Partial<TestWorkspace> = {}): TestWorkspace {
26
- return {id: INTEGRATIONS_TEST_WID, name: 'Acme', membershipId: 'm-1', ...overrides};
27
+ return {id: INTEGRATIONS_TEST_WID, name: 'Acme', slug: 'acme', membershipId: 'm-1', ...overrides};
27
28
  }
28
29
 
29
30
  export function jsonResponse(body: unknown, init: ResponseInit = {}) {