@agent-native/core-corpus 0.210.2 → 0.211.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 (192) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/actions/references/action-fields.md +5 -3
  3. package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +41 -2
  4. package/corpus/templates/analytics/.agents/skills/data-querying/SKILL.md +4 -2
  5. package/corpus/templates/analytics/CHANGELOG.md +18 -0
  6. package/corpus/templates/analytics/actions/build-data-index.ts +11 -2
  7. package/corpus/templates/analytics/actions/configure-content-collection.ts +29 -4
  8. package/corpus/templates/analytics/actions/configure-first-party-events-model.ts +9 -7
  9. package/corpus/templates/analytics/actions/create-analytics-public-key.ts +4 -0
  10. package/corpus/templates/analytics/actions/estimate-content-signups-cost.ts +72 -0
  11. package/corpus/templates/analytics/actions/estimate-query-cost.ts +16 -134
  12. package/corpus/templates/analytics/actions/list-sigma-workbooks.ts +123 -42
  13. package/corpus/templates/analytics/actions/migrate-sigma-workbook.ts +20 -17
  14. package/corpus/templates/analytics/actions/revoke-analytics-public-key.ts +4 -0
  15. package/corpus/templates/analytics/app/components/dashboard/SigmaMigrationDialog.tsx +246 -680
  16. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +1 -1
  17. package/corpus/templates/analytics/app/i18n/en-US.ts +54 -55
  18. package/corpus/templates/analytics/app/pages/DashboardOverview.tsx +0 -1
  19. package/corpus/templates/analytics/app/pages/DataSources.tsx +56 -0
  20. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/filter-vars.ts +4 -3
  21. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +26 -8
  22. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/interpolate.ts +3 -1
  23. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/panel-cost-notice.ts +172 -0
  24. package/corpus/templates/analytics/app/pages/data-sources/SearchConsoleDetail.tsx +441 -0
  25. package/corpus/templates/analytics/changelog/2026-10-15-a-content-dashboard-lists-every-blog-article-with-its-author.md +6 -0
  26. package/corpus/templates/analytics/changelog/2026-10-15-data-sources-has-a-google-search-console-card-that-shows-the.md +6 -0
  27. package/corpus/templates/analytics/changelog/2026-10-16-the-content-dashboard-can-show-signups-per-blog-article.md +6 -0
  28. package/corpus/templates/analytics/changelog/2026-10-19-creating-or-revoking-a-public-analytics-key-now-asks-you-to-.md +6 -0
  29. package/corpus/templates/analytics/changelog/2026-10-19-search-sigma-workbooks-by-name-and-open-ones-a-teammate-alre.md +6 -0
  30. package/corpus/templates/analytics/changelog/2026-10-20-blog-signup-attribution-excludes-signups-before-the-first-vi.md +6 -0
  31. package/corpus/templates/analytics/changelog/2026-10-20-collapsed-analytics-navigation-starts-at-the-top.md +6 -0
  32. package/corpus/templates/analytics/changelog/2026-10-20-content-keeps-successful-coverage-dates-and-missing-day-gaps.md +6 -0
  33. package/corpus/templates/analytics/changelog/2026-10-20-private-github-repositories-can-be-indexed-with-your-connect.md +6 -0
  34. package/corpus/templates/analytics/changelog/2026-10-20-search-console-preserves-backfill-and-paused-collection.md +6 -0
  35. package/corpus/templates/analytics/changelog/2026-10-20-search-console-refreshes-historical-coverage-when-properties.md +6 -0
  36. package/corpus/templates/analytics/changelog/2026-10-20-search-console-setup-keeps-saved-properties-readable-and-sup.md +6 -0
  37. package/corpus/templates/analytics/seeds/dashboards/content-view.json +182 -0
  38. package/corpus/templates/analytics/server/db/schema.ts +1 -0
  39. package/corpus/templates/analytics/server/lib/bi-migration/external-dashboards-store.ts +56 -18
  40. package/corpus/templates/analytics/server/lib/bi-migration/unfinished-import.ts +64 -0
  41. package/corpus/templates/analytics/server/lib/builtin-dashboard-pass.ts +17 -2
  42. package/corpus/templates/analytics/server/lib/content-metrics/collect.ts +2 -0
  43. package/corpus/templates/analytics/server/lib/content-metrics/config.ts +92 -3
  44. package/corpus/templates/analytics/server/lib/content-metrics/legacy-content-view.json +172 -0
  45. package/corpus/templates/analytics/server/lib/content-metrics/migration.ts +2 -0
  46. package/corpus/templates/analytics/server/lib/content-metrics/steps/daily-metrics.ts +8 -3
  47. package/corpus/templates/analytics/server/lib/content-metrics/steps/search-console.ts +16 -3
  48. package/corpus/templates/analytics/server/lib/content-metrics/steps/signups.ts +240 -0
  49. package/corpus/templates/analytics/server/lib/content-metrics/store.ts +5 -0
  50. package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +21 -0
  51. package/corpus/templates/analytics/server/lib/dashboard-panel-verification.ts +300 -5
  52. package/corpus/templates/analytics/server/lib/dashboard-seeds.ts +2 -0
  53. package/corpus/templates/analytics/server/lib/dashboards-store.ts +28 -0
  54. package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +128 -24
  55. package/corpus/templates/analytics/server/lib/google-analytics.ts +56 -16
  56. package/corpus/templates/analytics/server/lib/index-build-github.ts +5 -0
  57. package/corpus/templates/analytics/server/lib/provider-credentials.ts +47 -2
  58. package/corpus/templates/analytics/server/lib/query-cost-estimate.ts +184 -5
  59. package/corpus/templates/analytics/server/plugins/db.ts +9 -1
  60. package/corpus/templates/analytics/shared/builtin-dashboard-ids.ts +2 -0
  61. package/corpus/templates/analytics/shared/search-console-property.ts +78 -0
  62. package/corpus/templates/assets/.agents/skills/actions/references/action-fields.md +5 -3
  63. package/corpus/templates/brain/.agents/skills/actions/references/action-fields.md +5 -3
  64. package/corpus/templates/calendar/.agents/skills/actions/references/action-fields.md +5 -3
  65. package/corpus/templates/calendar/actions/delete-event.ts +1 -3
  66. package/corpus/templates/calendar/actions/delete-events.ts +1 -2
  67. package/corpus/templates/calendar/actions/event-action-helpers.ts +0 -4
  68. package/corpus/templates/calendar/actions/update-event.ts +2 -6
  69. package/corpus/templates/chat/.agents/skills/actions/references/action-fields.md +5 -3
  70. package/corpus/templates/clips/.agents/skills/actions/references/action-fields.md +5 -3
  71. package/corpus/templates/clips/CHANGELOG.md +11 -24
  72. package/corpus/templates/clips/actions/create-recording-upload.ts +5 -1
  73. package/corpus/templates/clips/app/components/player/chapter-list.tsx +7 -6
  74. package/corpus/templates/clips/app/components/player/comment-composer.tsx +5 -1
  75. package/corpus/templates/clips/app/components/player/player-controls.tsx +1 -0
  76. package/corpus/templates/clips/app/components/player/scrubber.tsx +4 -2
  77. package/corpus/templates/clips/app/i18n/en-US.ts +3 -3
  78. package/corpus/templates/clips/app/lib/extension-auth.ts +70 -0
  79. package/corpus/templates/clips/app/root.tsx +22 -10
  80. package/corpus/templates/clips/app/routes/_app.notifications.tsx +3 -0
  81. package/corpus/templates/clips/app/routes/import.tsx +115 -110
  82. package/corpus/templates/clips/changelog/2026-10-16-agent-uploads-can-use-free-clips-storage.md +6 -0
  83. package/corpus/templates/clips/changelog/2026-10-19-agents-see-a-clip-s-frames-as-soon-as-it-finishes-processing.md +6 -0
  84. package/corpus/templates/clips/changelog/2026-10-19-chapter-list-times-now-match-the-player-s-clock-and-each-cha.md +6 -0
  85. package/corpus/templates/clips/changelog/2026-10-19-editing-a-comment-puts-the-cursor-after-its-text-and-reply-o.md +6 -0
  86. package/corpus/templates/clips/changelog/2026-10-19-youtube-import-followups.md +6 -0
  87. package/corpus/templates/clips/changelog/2026-10-20-locally-saved-desktop-recordings-now-appear-in-the-recovery-.md +6 -0
  88. package/corpus/templates/clips/desktop/src/app.tsx +29 -186
  89. package/corpus/templates/clips/desktop/src/components/RecordingRecovery.tsx +171 -16
  90. package/corpus/templates/clips/desktop/src/lib/record-first.ts +9 -32
  91. package/corpus/templates/clips/desktop/src/lib/recording-recovery.ts +30 -1
  92. package/corpus/templates/clips/server/lib/agent-frame-request.ts +5 -2
  93. package/corpus/templates/clips/server/lib/recording-upload-ticket.ts +8 -0
  94. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +2 -0
  95. package/corpus/templates/content/.agents/skills/actions/references/action-fields.md +5 -3
  96. package/corpus/templates/content/CHANGELOG.md +12 -0
  97. package/corpus/templates/content/actions/_content-database-personal-view.ts +5 -15
  98. package/corpus/templates/content/actions/_database-navigation.ts +3 -3
  99. package/corpus/templates/content/actions/_database-utils.ts +10 -22
  100. package/corpus/templates/content/actions/_database-view-setup.ts +16 -76
  101. package/corpus/templates/content/actions/create-comment-ai-suggestion.ts +93 -44
  102. package/corpus/templates/content/actions/export-document.ts +2 -14
  103. package/corpus/templates/content/actions/suggest-document-edit.ts +5 -7
  104. package/corpus/templates/content/actions/update-content-database-view.ts +2 -14
  105. package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +86 -30
  106. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +64 -28
  107. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +6 -46
  108. package/corpus/templates/content/app/components/editor/PageDraftRecovery.tsx +72 -7
  109. package/corpus/templates/content/app/components/editor/TableHoverControls.tsx +292 -103
  110. package/corpus/templates/content/app/components/editor/ThreadSuggestion.tsx +147 -40
  111. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +108 -11
  112. package/corpus/templates/content/app/components/editor/database/ContentTable.tsx +2 -1
  113. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +150 -166
  114. package/corpus/templates/content/app/components/editor/database/database-cell-comments.tsx +515 -0
  115. package/corpus/templates/content/app/components/editor/database/database-item-comments.tsx +5 -43
  116. package/corpus/templates/content/app/components/editor/database/filter-sort.ts +8 -783
  117. package/corpus/templates/content/app/components/editor/live-editor-session.ts +5 -1
  118. package/corpus/templates/content/app/components/editor/page-draft-journal.ts +61 -1
  119. package/corpus/templates/content/app/components/editor/property-comments.tsx +35 -4
  120. package/corpus/templates/content/app/components/editor/property-date-format.ts +45 -0
  121. package/corpus/templates/content/app/components/editor/thread-suggestions.ts +30 -0
  122. package/corpus/templates/content/app/components/editor/useDocumentReconcileRecovery.ts +23 -4
  123. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +36 -10
  124. package/corpus/templates/content/app/global.css +23 -0
  125. package/corpus/templates/content/app/i18n-data.ts +1 -0
  126. package/corpus/templates/content/changelog/2026-10-16-opening-a-page-in-a-new-tab-while-another-tab-is-still-savin.md +6 -0
  127. package/corpus/templates/content/changelog/2026-10-16-typing-steadily-in-one-tab-no-longer-makes-another-open-tab-.md +6 -0
  128. package/corpus/templates/content/changelog/2026-10-19-database-views-can-filter-the-same-property-more-than-once-n.md +6 -0
  129. package/corpus/templates/content/changelog/2026-10-19-edits-made-from-a-block-s-menu-or-a-table-s-row-and-column-m.md +6 -0
  130. package/corpus/templates/content/changelog/2026-10-19-starting-a-comment-puts-the-cursor-in-the-comment-box-ready-.md +6 -0
  131. package/corpus/templates/content/changelog/2026-10-19-table-row-and-column-options-now-appear-for-the-row-or-colum.md +6 -0
  132. package/corpus/templates/content/docs/product/capabilities/content.comment.page-owned.md +5 -1
  133. package/corpus/templates/content/docs/product/capabilities/content.revision.suggestions.md +3 -1
  134. package/corpus/templates/content/e2e/helpers.ts +10 -0
  135. package/corpus/templates/content/shared/api.ts +18 -12
  136. package/corpus/templates/content/shared/database-query.ts +107 -13
  137. package/corpus/templates/content/shared/suggestion-diff.ts +35 -0
  138. package/corpus/templates/crm/.agents/skills/actions/references/action-fields.md +5 -3
  139. package/corpus/templates/design/.agents/skills/actions/references/action-fields.md +5 -3
  140. package/corpus/templates/design/CHANGELOG.md +2 -0
  141. package/corpus/templates/design/actions/delete-file.ts +1 -4
  142. package/corpus/templates/design/app/components/design/LayersPanel.tsx +10 -1
  143. package/corpus/templates/design/app/components/visual-editor/ReviewCanvasPins.tsx +24 -4
  144. package/corpus/templates/design/app/pages/design-editor/commands/mode-change.ts +14 -9
  145. package/corpus/templates/design/app/pages/design-editor/domains/use-editor-modes.ts +23 -2
  146. package/corpus/templates/design/app/pages/design-editor/tool-state.ts +29 -0
  147. package/corpus/templates/design/changelog/2026-10-19-reply-and-edit-on-a-comment-in-the-sidebar-or-on-a-canvas-pi.md +6 -0
  148. package/corpus/templates/design/changelog/2026-10-20-comments-return-to-the-mode-they-came-from-and-layers-search.md +6 -0
  149. package/corpus/templates/design/e2e/global-setup.ts +185 -85
  150. package/corpus/templates/dispatch/.agents/skills/actions/references/action-fields.md +5 -3
  151. package/corpus/templates/dispatch/CHANGELOG.md +4 -0
  152. package/corpus/templates/dispatch/actions/apply-workspace-connection-setup.ts +4 -0
  153. package/corpus/templates/dispatch/actions/delete-workspace-connection.ts +4 -0
  154. package/corpus/templates/dispatch/actions/set-workspace-connection-grant.ts +4 -0
  155. package/corpus/templates/dispatch/actions/upsert-workspace-connection.ts +4 -0
  156. package/corpus/templates/dispatch/changelog/2026-10-19-vault-secrets-grants-link-tokens-app-access-and-workspace-co.md +6 -0
  157. package/corpus/templates/factory/.agents/skills/actions/references/action-fields.md +5 -3
  158. package/corpus/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +105 -33
  159. package/corpus/templates/factory/.agents/skills/review-latest-feedback/references/channel-inventory.md +128 -74
  160. package/corpus/templates/factory/.agents/skills/review-latest-feedback/references/slack-reaction-replies.md +39 -11
  161. package/corpus/templates/forms/.agents/skills/actions/references/action-fields.md +5 -3
  162. package/corpus/templates/mail/.agents/skills/actions/references/action-fields.md +5 -3
  163. package/corpus/templates/mail/.agents/skills/contacts-and-crm/SKILL.md +22 -17
  164. package/corpus/templates/mail/AGENTS.md +1 -1
  165. package/corpus/templates/mail/CHANGELOG.md +14 -0
  166. package/corpus/templates/mail/actions/provider-api-request.ts +3 -3
  167. package/corpus/templates/mail/app/components/email/IntegrationsSidebar.tsx +211 -54
  168. package/corpus/templates/mail/app/hooks/use-inbox-threads.ts +26 -2
  169. package/corpus/templates/mail/app/i18n/en-US.ts +5 -1
  170. package/corpus/templates/mail/app/lib/gong-connection.ts +27 -0
  171. package/corpus/templates/mail/app/lib/thread-cache.ts +110 -9
  172. package/corpus/templates/mail/app/root.tsx +10 -2
  173. package/corpus/templates/mail/changelog/2026-10-20-connected-gong-settings-now-open-the-shared-integration-mana.md +6 -0
  174. package/corpus/templates/mail/changelog/2026-10-20-gong-connection-details-now-match-the-call-data-available-in.md +6 -0
  175. package/corpus/templates/mail/changelog/2026-10-20-improve-gong-setup-with-a-click-to-connect-flow-and-permissi.md +6 -0
  176. package/corpus/templates/mail/changelog/2026-10-20-mail-lets-you-retry-when-it-cannot-check-your-gong-access.md +6 -0
  177. package/corpus/templates/mail/changelog/2026-10-20-mail-thread-cache-stays-isolated-when-mailbox-scope-changes.md +6 -0
  178. package/corpus/templates/mail/changelog/2026-10-20-the-inbox-and-recently-opened-threads-now-open-instantly-fro.md +6 -0
  179. package/corpus/templates/mail/server/handlers/emails.ts +9 -0
  180. package/corpus/templates/plan/.agents/skills/actions/references/action-fields.md +5 -3
  181. package/corpus/templates/plan/CHANGELOG.md +4 -0
  182. package/corpus/templates/plan/app/pages/PlansPage.tsx +173 -33
  183. package/corpus/templates/plan/changelog/2026-10-19-opening-a-comment-thread-puts-the-cursor-in-the-reply-box-an.md +6 -0
  184. package/corpus/templates/slides/.agents/skills/actions/references/action-fields.md +5 -3
  185. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +6 -42
  186. package/corpus/templates/slides/app/components/editor/FloatingToolbar.tsx +3 -7
  187. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +5 -14
  188. package/corpus/templates/slides/app/i18n/en-US.ts +0 -2
  189. package/corpus/templates/slides/app/pages/DeckEditor.tsx +27 -33
  190. package/corpus/templates/slides/scripts/edit-fidelity/run.ts +1 -3
  191. package/corpus/templates/tasks/.agents/skills/actions/references/action-fields.md +5 -3
  192. package/package.json +1 -1
@@ -1,7 +1,8 @@
1
- import { agentNativePath } from "@agent-native/core/client/api-path";
1
+ import { agentNativePath, appPath } from "@agent-native/core/client/api-path";
2
2
  import {
3
3
  useActionMutation,
4
4
  useActionQuery,
5
+ useAvatarUrl,
5
6
  } from "@agent-native/core/client/hooks";
6
7
  import { useT } from "@agent-native/core/client/i18n";
7
8
  import { useOrgRole } from "@agent-native/core/client/org";
@@ -9,7 +10,6 @@ import {
9
10
  SIGMA_API_BASE_URLS,
10
11
  SIGMA_DEFAULT_API_BASE_URL,
11
12
  } from "@agent-native/core/connections/sigma-api";
12
- import { useSendToAgentChat } from "@agent-native/toolkit/app/chat";
13
13
  import { useOrgSwitcherAppLinks } from "@agent-native/toolkit/app/org/workspace-app-links";
14
14
  import { saveApiKeyValue } from "@agent-native/toolkit/app/settings/api-keys/api-keys-client";
15
15
  import {
@@ -17,18 +17,17 @@ import {
17
17
  IconCheck,
18
18
  IconExternalLink,
19
19
  IconLoader2,
20
- IconSparkles,
20
+ IconSearch,
21
21
  } from "@tabler/icons-react";
22
22
  import { useEffect, useMemo, useRef, useState } from "react";
23
23
  import type { FormEvent, ReactNode } from "react";
24
- import { Link } from "react-router";
25
24
 
25
+ import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
26
26
  import { Badge } from "@/components/ui/badge";
27
27
  import { Button } from "@/components/ui/button";
28
28
  import {
29
29
  Dialog,
30
30
  DialogContent,
31
- DialogFooter,
32
31
  DialogHeader,
33
32
  DialogTitle,
34
33
  } from "@/components/ui/dialog";
@@ -42,6 +41,13 @@ import {
42
41
  SelectValue,
43
42
  } from "@/components/ui/select";
44
43
  import { Skeleton } from "@/components/ui/skeleton";
44
+ import { Table, TableBody, TableCell, TableRow } from "@/components/ui/table";
45
+ import {
46
+ Tooltip,
47
+ TooltipContent,
48
+ TooltipProvider,
49
+ TooltipTrigger,
50
+ } from "@/components/ui/tooltip";
45
51
 
46
52
  type Connection = {
47
53
  id: string;
@@ -59,11 +65,16 @@ type SigmaWorkbook = {
59
65
  dashboardId?: string | null;
60
66
  dashboardName?: string;
61
67
  dashboardUrl?: string;
68
+ canOpen?: boolean;
69
+ migratedOwnerEmail?: string;
70
+ migratedOwnerName?: string | null;
71
+ migratingByEmail?: string;
72
+ migratingByName?: string | null;
62
73
  canRecoverMissingDashboard?: boolean;
63
74
  canRetryStaleClaim?: boolean;
75
+ canStartImport: boolean;
64
76
  flaggedCount?: number;
65
77
  migratedByTeammate?: boolean;
66
- retryableByCurrentUser: boolean;
67
78
  };
68
79
 
69
80
  type SigmaWorkbookListResult = {
@@ -78,59 +89,6 @@ type SigmaWorkbookListResult = {
78
89
  total?: number;
79
90
  };
80
91
 
81
- type SigmaElementReviewReason =
82
- | "unsupported_element"
83
- | "unsupported_source"
84
- | "source_unavailable"
85
- | "source_mapping_required"
86
- | "query_unavailable"
87
- | "unsafe_sql"
88
- | "visualization_review";
89
-
90
- type SigmaWorkbookElement = {
91
- id: string;
92
- name: string;
93
- type: string;
94
- importable: boolean;
95
- visualizationPreserved: boolean;
96
- reason?: SigmaElementReviewReason;
97
- };
98
-
99
- type SigmaWorkbookInspection = {
100
- workbook: { id: string; name: string; updatedAt?: string };
101
- importableCount: number;
102
- unsupportedCount: number;
103
- reviewRequiredCount: number;
104
- sourceMappingRequiredCount: number;
105
- elements: SigmaWorkbookElement[];
106
- betaRepresentationWarning: boolean;
107
- };
108
-
109
- type SigmaMigrationResult =
110
- | {
111
- status: "completed";
112
- dashboardId: string;
113
- dashboardName: string;
114
- dashboardUrl: string;
115
- importedCount: number;
116
- unsupportedCount: number;
117
- reviewRequiredCount: number;
118
- flaggedCount: number;
119
- }
120
- | {
121
- status: "already_migrated";
122
- dashboardId?: string;
123
- dashboardName?: string;
124
- dashboardUrl?: string;
125
- }
126
- | { status: "in_progress" };
127
-
128
- type MigrationArgs = {
129
- workbookId: string;
130
- connectionId?: string;
131
- recoverMissingDashboard?: boolean;
132
- };
133
-
134
92
  type LoadedWorkbookPage = { cursor?: string; workbooks: SigmaWorkbook[] };
135
93
  type SigmaConnectionSaveResult = {
136
94
  connectionId: string;
@@ -150,29 +108,25 @@ function sigmaSetupKey(
150
108
 
151
109
  const EMPTY_CONNECTIONS: Connection[] = [];
152
110
  const EMPTY_WORKBOOKS: SigmaWorkbook[] = [];
153
- const SIGMA_PREVIEW_ELEMENT_LIMIT = 100;
154
111
 
155
112
  export function SigmaMigrationDialog({
156
113
  open,
157
114
  onOpenChange,
158
- onImported,
159
115
  preferredConnectionId,
160
116
  }: {
161
117
  open: boolean;
162
118
  onOpenChange: (open: boolean) => void;
163
- onImported?: () => void;
164
119
  preferredConnectionId?: string | null;
165
120
  }) {
166
121
  const t = useT();
167
122
  const { canManageOrg } = useOrgRole();
168
123
  const { dispatchHref, dispatchVaultHref } = useOrgSwitcherAppLinks(open);
169
- const { send, isGenerating, codeRequiredDialog } = useSendToAgentChat();
170
124
  const [connectionId, setConnectionId] = useState<string | null>(null);
171
125
  const [sigmaApiBaseUrl, setSigmaApiBaseUrl] = useState(
172
126
  SIGMA_DEFAULT_API_BASE_URL,
173
127
  );
174
128
  const [showSigmaOAuthSetup, setShowSigmaOAuthSetup] = useState(false);
175
- const [workbookId, setWorkbookId] = useState<string | null>(null);
129
+ const [search, setSearch] = useState("");
176
130
  const [pageCursor, setPageCursor] = useState<string | undefined>();
177
131
  const [refreshVersion, setRefreshVersion] = useState(0);
178
132
  const [loadedPages, setLoadedPages] = useState<{
@@ -183,12 +137,6 @@ export function SigmaMigrationDialog({
183
137
  connectionId: string | null;
184
138
  result: SigmaWorkbookListResult;
185
139
  } | null>(null);
186
- const [migrationResult, setMigrationResult] =
187
- useState<SigmaMigrationResult | null>(null);
188
- const [migrationError, setMigrationError] = useState<string | null>(null);
189
- const [migrationInProgress, setMigrationInProgress] = useState(false);
190
- const [awaitingListConfirmation, setAwaitingListConfirmation] =
191
- useState(false);
192
140
  const activePageCursor =
193
141
  loadedPages.connectionId === connectionId ? pageCursor : undefined;
194
142
 
@@ -288,11 +236,7 @@ export function SigmaMigrationDialog({
288
236
 
289
237
  useEffect(() => {
290
238
  setPageCursor(undefined);
291
- setWorkbookId(null);
292
- setMigrationResult(null);
293
- setMigrationError(null);
294
- setMigrationInProgress(false);
295
- setAwaitingListConfirmation(false);
239
+ setSearch("");
296
240
  setLoadedPages({ connectionId, pages: [] });
297
241
  }, [connectionId]);
298
242
 
@@ -347,37 +291,13 @@ export function SigmaMigrationDialog({
347
291
  }
348
292
  return [...merged.values()];
349
293
  }, [connectionId, loadedPages]);
350
- useEffect(() => {
351
- if (workbooks.some((workbook) => workbook.id === workbookId)) return;
352
- setWorkbookId(workbooks[0]?.id ?? null);
353
- }, [workbooks, workbookId]);
354
-
355
- const selectedWorkbook =
356
- workbooks.find((workbook) => workbook.id === workbookId) ?? null;
357
- const selectedWorkbookPage =
358
- loadedPages.connectionId === connectionId
359
- ? loadedPages.pages.find((page) =>
360
- page.workbooks.some((workbook) => workbook.id === workbookId),
361
- )?.cursor
362
- : undefined;
363
- const inspection = useActionQuery<
364
- SigmaWorkbookInspection,
365
- "inspect-sigma-workbook"
366
- >(
367
- "inspect-sigma-workbook",
368
- selectedWorkbook && connectionId
369
- ? { workbookId: selectedWorkbook.id, connectionId }
370
- : undefined,
371
- {
372
- enabled: open && Boolean(selectedWorkbook && connectionId),
373
- staleTime: 15_000,
374
- },
375
- );
376
- const migrateWorkbook = useActionMutation<
377
- SigmaMigrationResult,
378
- MigrationArgs,
379
- "migrate-sigma-workbook"
380
- >("migrate-sigma-workbook");
294
+ const visibleWorkbooks = useMemo(() => {
295
+ const query = search.trim().toLowerCase();
296
+ if (!query) return workbooks;
297
+ return workbooks.filter((workbook) =>
298
+ workbook.name.toLowerCase().includes(query),
299
+ );
300
+ }, [search, workbooks]);
381
301
  const saveSigmaConnection = useActionMutation<
382
302
  SigmaConnectionSaveResult,
383
303
  SigmaConnectionSaveArgs,
@@ -403,53 +323,9 @@ export function SigmaMigrationDialog({
403
323
  );
404
324
  const showConnectionPicker = connectedConnections.length > 1;
405
325
  const needsConnectionSelection = showConnectionPicker && !connectionId;
406
- const inspectionIsValid = inspection.data
407
- ? isWorkbookInspection(inspection.data)
408
- : false;
409
- const canImport =
410
- Boolean(selectedWorkbook && connectionId && inspectionIsValid) &&
411
- (inspection.data?.importableCount ?? 0) > 0 &&
412
- !migrationInProgress &&
413
- selectedWorkbook?.retryableByCurrentUser !== false &&
414
- (selectedWorkbook?.status !== "migrated" ||
415
- selectedWorkbook?.canRecoverMissingDashboard === true) &&
416
- (selectedWorkbook?.status !== "migrating" ||
417
- selectedWorkbook?.canRetryStaleClaim === true);
418
- const isImported =
419
- migrationResult?.status === "completed" ||
420
- migrationResult?.status === "already_migrated" ||
421
- selectedWorkbook?.status === "migrated";
422
- const accessibleDashboardId =
423
- dashboardIdFromMigrationResult(migrationResult) ??
424
- (!awaitingListConfirmation && selectedWorkbook?.status === "migrated"
425
- ? (selectedWorkbook.dashboardId ?? null)
426
- : null);
427
- const accessibleDashboardUrl = safeDashboardPath(accessibleDashboardId);
428
- const reviewTarget =
429
- selectedWorkbook &&
430
- isImported &&
431
- accessibleDashboardId &&
432
- accessibleDashboardUrl
433
- ? {
434
- dashboardId: accessibleDashboardId,
435
- dashboardName:
436
- (migrationResult?.status === "completed" ||
437
- migrationResult?.status === "already_migrated"
438
- ? migrationResult.dashboardName
439
- : undefined) ??
440
- selectedWorkbook.dashboardName ??
441
- selectedWorkbook.name,
442
- }
443
- : null;
444
-
445
326
  const handleConnectionChange = (value: string) => {
446
327
  setConnectionId(value || null);
447
328
  setPageCursor(undefined);
448
- setWorkbookId(null);
449
- setMigrationResult(null);
450
- setMigrationError(null);
451
- setMigrationInProgress(false);
452
- setAwaitingListConfirmation(false);
453
329
  };
454
330
 
455
331
  const startPersonalSigmaOAuth = () => {
@@ -465,48 +341,6 @@ export function SigmaMigrationDialog({
465
341
  );
466
342
  };
467
343
 
468
- const handleWorkbookChange = (value: string) => {
469
- setWorkbookId(value);
470
- setMigrationResult(null);
471
- setMigrationError(null);
472
- setMigrationInProgress(false);
473
- setAwaitingListConfirmation(false);
474
- };
475
-
476
- const handleImport = async () => {
477
- if (!selectedWorkbook || !connectionId || !canImport) return;
478
- setMigrationError(null);
479
- setMigrationResult(null);
480
- setMigrationInProgress(false);
481
- setAwaitingListConfirmation(false);
482
- try {
483
- const result = await migrateWorkbook.mutateAsync({
484
- workbookId: selectedWorkbook.id,
485
- connectionId,
486
- ...(selectedWorkbook.canRecoverMissingDashboard
487
- ? { recoverMissingDashboard: true }
488
- : {}),
489
- });
490
- if (!isMigrationResult(result)) {
491
- setMigrationError(t("dashboardOverview.sigmaMigrationResultInvalid"));
492
- return;
493
- }
494
- if (result.status === "in_progress") {
495
- setMigrationInProgress(true);
496
- setPageCursor(selectedWorkbookPage);
497
- setRefreshVersion((current) => current + 1);
498
- return;
499
- }
500
- setMigrationResult(result);
501
- setAwaitingListConfirmation(true);
502
- setPageCursor(selectedWorkbookPage);
503
- setRefreshVersion((current) => current + 1);
504
- onImported?.();
505
- } catch {
506
- setMigrationError(t("dashboardOverview.sigmaMigrationFailed"));
507
- }
508
- };
509
-
510
344
  const handleSigmaConnect = async (event: FormEvent<HTMLFormElement>) => {
511
345
  event.preventDefault();
512
346
  setSigmaSetupError(null);
@@ -649,76 +483,6 @@ export function SigmaMigrationDialog({
649
483
  setSigmaCleanupError(null);
650
484
  }, [open]);
651
485
 
652
- useEffect(() => {
653
- if (
654
- !awaitingListConfirmation ||
655
- !selectedWorkbook ||
656
- !connectionId ||
657
- workbookList.isPending ||
658
- workbookList.isFetching ||
659
- listData?.connectionId !== connectionId ||
660
- !listData.workbooks.some(
661
- (workbook) => workbook.id === selectedWorkbook.id,
662
- )
663
- ) {
664
- return;
665
- }
666
- setAwaitingListConfirmation(false);
667
- }, [
668
- awaitingListConfirmation,
669
- connectionId,
670
- listData,
671
- selectedWorkbook,
672
- workbookList.isFetching,
673
- workbookList.isPending,
674
- ]);
675
-
676
- useEffect(() => {
677
- if (
678
- !migrationInProgress ||
679
- !selectedWorkbook ||
680
- workbookList.isFetching ||
681
- listData?.connectionId !== connectionId
682
- ) {
683
- return;
684
- }
685
- const listedWorkbook = listData.workbooks.find(
686
- (workbook) => workbook.id === selectedWorkbook.id,
687
- );
688
- if (
689
- listedWorkbook &&
690
- (listedWorkbook.status !== "migrating" ||
691
- listedWorkbook.canRetryStaleClaim === true)
692
- ) {
693
- setMigrationInProgress(false);
694
- }
695
- }, [
696
- connectionId,
697
- listData,
698
- migrationInProgress,
699
- selectedWorkbook,
700
- workbookList.isFetching,
701
- ]);
702
-
703
- const handleReview = () => {
704
- if (!reviewTarget || !selectedWorkbook) return;
705
- send({
706
- message: t("dashboardOverview.sigmaReviewPrompt", {
707
- name: reviewTarget.dashboardName,
708
- }),
709
- context: JSON.stringify({
710
- dashboardId: reviewTarget.dashboardId,
711
- workbookId: selectedWorkbook.id,
712
- source: "sigma",
713
- }),
714
- contextLabel: t("dashboardOverview.sigmaReviewContext"),
715
- submit: true,
716
- openSidebar: true,
717
- });
718
- onOpenChange(false);
719
- };
720
-
721
- const dashboardPath = reviewTarget ? accessibleDashboardUrl : null;
722
486
  const hasMoreWorkbooks =
723
487
  !listHasError &&
724
488
  listData?.connectionId === connectionId &&
@@ -862,12 +626,12 @@ export function SigmaMigrationDialog({
862
626
  open={open}
863
627
  onOpenChange={(nextOpen) => void handleDialogOpenChange(nextOpen)}
864
628
  >
865
- <DialogContent className="flex max-h-[90vh] w-[calc(100vw-2rem)] flex-col overflow-hidden p-0 sm:max-w-xl">
629
+ <DialogContent className="flex max-h-[90vh] w-[calc(100vw-2rem)] flex-col overflow-hidden p-0 sm:max-w-2xl">
866
630
  <DialogHeader className="px-6 pt-6">
867
631
  <DialogTitle>{t("dashboardOverview.importFromSigma")}</DialogTitle>
868
632
  </DialogHeader>
869
633
 
870
- <div className="min-h-0 flex-1 space-y-5 overflow-y-auto px-6 pb-2">
634
+ <div className="min-h-0 flex-1 space-y-5 overflow-y-auto px-6 pb-6">
871
635
  {sigmaCleanupPending ? (
872
636
  <div
873
637
  className="flex flex-wrap items-center justify-between gap-3 rounded-md border border-border p-3 text-sm"
@@ -1012,45 +776,31 @@ export function SigmaMigrationDialog({
1012
776
  {loadMoreButton}
1013
777
  </div>
1014
778
  ) : workbooks.length > 0 ? (
1015
- <div className="space-y-5">
1016
- <div className="space-y-2">
1017
- <Label htmlFor="sigma-workbook">
1018
- {t("dashboardOverview.sigmaWorkbook")}
1019
- </Label>
1020
- <Select
1021
- value={selectedWorkbook?.id ?? ""}
1022
- onValueChange={handleWorkbookChange}
1023
- >
1024
- <SelectTrigger id="sigma-workbook">
1025
- <SelectValue
1026
- placeholder={t(
1027
- "dashboardOverview.sigmaChooseWorkbook",
1028
- )}
1029
- />
1030
- </SelectTrigger>
1031
- <SelectContent>
1032
- {workbooks.map((workbook) => (
1033
- <SelectItem key={workbook.id} value={workbook.id}>
1034
- {workbook.name}
1035
- </SelectItem>
1036
- ))}
1037
- </SelectContent>
1038
- </Select>
779
+ <div className="space-y-3">
780
+ <div className="relative">
781
+ <IconSearch
782
+ aria-hidden="true"
783
+ className="pointer-events-none absolute start-2.5 top-1/2 size-4 -translate-y-1/2 text-muted-foreground"
784
+ />
785
+ <Input
786
+ type="search"
787
+ className="ps-8"
788
+ value={search}
789
+ onChange={(event) => setSearch(event.currentTarget.value)}
790
+ placeholder={t("dashboardOverview.sigmaSearchWorkbooks")}
791
+ aria-label={t("dashboardOverview.sigmaSearchWorkbooks")}
792
+ />
1039
793
  </div>
1040
-
1041
- {selectedWorkbook?.migratedByTeammate ? (
1042
- <Badge variant="outline">
1043
- {t("dashboardOverview.sigmaImportedByTeammate")}
1044
- </Badge>
1045
- ) : null}
1046
- {typeof selectedWorkbook?.flaggedCount === "number" &&
1047
- selectedWorkbook.flaggedCount > 0 ? (
1048
- <Badge variant="outline">
1049
- {t("dashboardOverview.sigmaFlaggedCount", {
1050
- count: selectedWorkbook.flaggedCount,
1051
- })}
1052
- </Badge>
1053
- ) : null}
794
+ <WorkbookTable
795
+ workbooks={visibleWorkbooks}
796
+ emptyMessage={
797
+ hasMoreWorkbooks
798
+ ? t(
799
+ "dashboardOverview.sigmaNoMatchingWorkbooksLoadMore",
800
+ )
801
+ : t("dashboardOverview.sigmaNoMatchingWorkbooks")
802
+ }
803
+ />
1054
804
 
1055
805
  {listHasError ? (
1056
806
  <StateMessage
@@ -1076,300 +826,16 @@ export function SigmaMigrationDialog({
1076
826
  ) : null}
1077
827
 
1078
828
  {loadMoreButton}
1079
-
1080
- {selectedWorkbook ? (
1081
- <section className="space-y-3" aria-live="polite">
1082
- <div className="flex flex-wrap items-center gap-2">
1083
- <h3 className="min-w-0 flex-1 truncate text-sm font-medium">
1084
- {selectedWorkbook.name}
1085
- </h3>
1086
- <WorkbookStatusBadge status={selectedWorkbook.status} />
1087
- {selectedWorkbook.isArchived ? (
1088
- <Badge variant="outline">
1089
- {t("dashboardOverview.sigmaArchived")}
1090
- </Badge>
1091
- ) : null}
1092
- </div>
1093
- {selectedWorkbook.status === "failed" &&
1094
- selectedWorkbook.retryableByCurrentUser === false ? (
1095
- <p className="text-sm text-muted-foreground">
1096
- {t("dashboardOverview.sigmaRetryOwnerOnly")}
1097
- </p>
1098
- ) : null}
1099
-
1100
- {inspection.isPending ? (
1101
- <PreviewSkeleton />
1102
- ) : inspection.isError ||
1103
- (inspection.data && !inspectionIsValid) ? (
1104
- <StateMessage
1105
- icon={<IconAlertCircle className="size-4" />}
1106
- title={t("dashboardOverview.sigmaPreviewFailed")}
1107
- detail={t("dashboardOverview.sigmaRetryPreview")}
1108
- action={
1109
- <div className="flex flex-wrap gap-2">
1110
- {chooseConnectionButton}
1111
- <Button
1112
- type="button"
1113
- variant="outline"
1114
- size="sm"
1115
- onClick={() => void inspection.refetch()}
1116
- >
1117
- {t("dashboardOverview.sigmaRetry")}
1118
- </Button>
1119
- </div>
1120
- }
1121
- />
1122
- ) : inspection.data ? (
1123
- <>
1124
- <WorkbookPreview inspection={inspection.data} t={t} />
1125
- </>
1126
- ) : null}
1127
-
1128
- {(selectedWorkbook.status === "migrating" &&
1129
- selectedWorkbook.canRetryStaleClaim !== true) ||
1130
- migrationInProgress ? (
1131
- <p
1132
- className="text-sm text-muted-foreground"
1133
- role="status"
1134
- >
1135
- {t("dashboardOverview.sigmaImportInProgress")}
1136
- </p>
1137
- ) : null}
1138
-
1139
- {migrationError ? (
1140
- <div className="flex flex-wrap items-center gap-2">
1141
- <p className="text-sm text-destructive" role="alert">
1142
- {migrationError}
1143
- </p>
1144
- {chooseConnectionButton}
1145
- </div>
1146
- ) : null}
1147
-
1148
- {isImported ? (
1149
- <div className="space-y-3 rounded-md border border-border/60 bg-muted/20 p-3">
1150
- <div className="flex items-start gap-2">
1151
- <IconCheck className="mt-0.5 size-4 shrink-0 text-primary" />
1152
- <div className="min-w-0 space-y-1">
1153
- <p className="text-sm font-medium">
1154
- {t(
1155
- migrationResult?.status === "completed"
1156
- ? "dashboardOverview.sigmaImportComplete"
1157
- : "dashboardOverview.sigmaAlreadyImported",
1158
- )}
1159
- </p>
1160
- {migrationResult?.status === "completed" ? (
1161
- <p className="text-sm text-muted-foreground">
1162
- {t("dashboardOverview.sigmaImportSummary", {
1163
- imported: migrationResult.importedCount,
1164
- unsupported:
1165
- migrationResult.unsupportedCount,
1166
- })}
1167
- </p>
1168
- ) : null}
1169
- </div>
1170
- </div>
1171
- {migrationResult?.status === "completed" &&
1172
- migrationResult.flaggedCount > 0 ? (
1173
- <Badge variant="outline">
1174
- {t("dashboardOverview.sigmaFlaggedCount", {
1175
- count: migrationResult.flaggedCount,
1176
- })}
1177
- </Badge>
1178
- ) : null}
1179
- {reviewTarget && dashboardPath ? (
1180
- <div className="flex flex-wrap gap-2">
1181
- <Button
1182
- asChild
1183
- type="button"
1184
- variant="outline"
1185
- size="sm"
1186
- >
1187
- <Link to={dashboardPath}>
1188
- {t("dashboardOverview.sigmaOpenDashboard")}
1189
- <IconExternalLink />
1190
- </Link>
1191
- </Button>
1192
- <Button
1193
- type="button"
1194
- size="sm"
1195
- onClick={handleReview}
1196
- disabled={isGenerating}
1197
- >
1198
- <IconSparkles />
1199
- {t("dashboardOverview.sigmaReviewWithAgent")}
1200
- </Button>
1201
- </div>
1202
- ) : (
1203
- <p className="text-sm text-muted-foreground">
1204
- {t("dashboardOverview.sigmaImportedNoDashboard")}
1205
- </p>
1206
- )}
1207
- </div>
1208
- ) : null}
1209
- </section>
1210
- ) : null}
1211
829
  </div>
1212
830
  ) : null
1213
831
  ) : null}
1214
832
  </div>
1215
-
1216
- <DialogFooter className="border-t border-border/60 px-6 py-4">
1217
- <Button
1218
- type="button"
1219
- variant="outline"
1220
- onClick={() => onOpenChange(false)}
1221
- >
1222
- {t("sidebar.cancel")}
1223
- </Button>
1224
- {connected &&
1225
- !needsConnectionSelection &&
1226
- selectedWorkbook &&
1227
- (canImport || migrateWorkbook.isPending) ? (
1228
- <Button
1229
- type="button"
1230
- onClick={() => void handleImport()}
1231
- disabled={
1232
- !canImport || migrationInProgress || migrateWorkbook.isPending
1233
- }
1234
- >
1235
- {migrateWorkbook.isPending ? (
1236
- <IconLoader2 className="animate-spin" />
1237
- ) : null}
1238
- {migrateWorkbook.isPending
1239
- ? t("dashboardOverview.sigmaImporting")
1240
- : selectedWorkbook.canRetryStaleClaim
1241
- ? t("dashboardOverview.sigmaRetry")
1242
- : t("dashboardOverview.sigmaImportWorkbook")}
1243
- </Button>
1244
- ) : null}
1245
- </DialogFooter>
1246
833
  </DialogContent>
1247
834
  </Dialog>
1248
- {codeRequiredDialog}
1249
835
  </>
1250
836
  );
1251
837
  }
1252
838
 
1253
- function WorkbookPreview({
1254
- inspection,
1255
- t,
1256
- }: {
1257
- inspection: SigmaWorkbookInspection;
1258
- t: ReturnType<typeof useT>;
1259
- }) {
1260
- const visibleElements = inspection.elements.slice(
1261
- 0,
1262
- SIGMA_PREVIEW_ELEMENT_LIMIT,
1263
- );
1264
- return (
1265
- <div className="space-y-3">
1266
- <div className="flex flex-wrap items-center justify-between gap-2">
1267
- <h4 className="text-sm font-medium">
1268
- {t("dashboardOverview.sigmaPreviewTitle")}
1269
- </h4>
1270
- <div className="flex flex-wrap gap-1.5">
1271
- <Badge variant="secondary">
1272
- {t("dashboardOverview.sigmaImportableCount", {
1273
- count: inspection.importableCount,
1274
- })}
1275
- </Badge>
1276
- <Badge variant="outline">
1277
- {t("dashboardOverview.sigmaUnsupportedCount", {
1278
- count: inspection.unsupportedCount,
1279
- })}
1280
- </Badge>
1281
- {inspection.reviewRequiredCount > 0 ? (
1282
- <Badge variant="outline">
1283
- {t("dashboardOverview.sigmaReviewRequiredCount", {
1284
- count: inspection.reviewRequiredCount,
1285
- })}
1286
- </Badge>
1287
- ) : null}
1288
- </div>
1289
- </div>
1290
- {inspection.betaRepresentationWarning ? (
1291
- <p className="text-sm text-muted-foreground">
1292
- {t("dashboardOverview.sigmaBetaRepresentationWarning")}
1293
- </p>
1294
- ) : null}
1295
- {inspection.sourceMappingRequiredCount > 0 ? (
1296
- <p className="text-sm text-muted-foreground">
1297
- {t("dashboardOverview.sigmaPreviewOnly")}
1298
- </p>
1299
- ) : null}
1300
- {inspection.elements.length === 0 ? (
1301
- <p className="text-sm text-muted-foreground">
1302
- {t("dashboardOverview.sigmaNoElements")}
1303
- </p>
1304
- ) : (
1305
- <>
1306
- <ul className="max-h-56 divide-y overflow-y-auto rounded-md border border-border/60">
1307
- {visibleElements.map((element) => (
1308
- <li
1309
- key={element.id}
1310
- className="flex min-w-0 items-start justify-between gap-3 px-3 py-2.5"
1311
- >
1312
- <div className="min-w-0">
1313
- <p className="truncate text-sm font-medium">{element.name}</p>
1314
- <p className="truncate text-xs text-muted-foreground">
1315
- {element.type}
1316
- </p>
1317
- {element.reason ? (
1318
- <p className="mt-1 text-xs text-muted-foreground">
1319
- {t(reasonTranslationKey(element.reason))}
1320
- </p>
1321
- ) : null}
1322
- </div>
1323
- <Badge
1324
- variant={element.importable ? "secondary" : "outline"}
1325
- className="shrink-0"
1326
- >
1327
- {element.importable
1328
- ? t("dashboardOverview.sigmaImportable")
1329
- : t("dashboardOverview.sigmaUnsupported")}
1330
- </Badge>
1331
- </li>
1332
- ))}
1333
- </ul>
1334
- {visibleElements.length < inspection.elements.length ? (
1335
- <p className="text-sm text-muted-foreground">
1336
- {t("dashboardOverview.sigmaPreviewTruncated", {
1337
- shown: visibleElements.length,
1338
- total: inspection.elements.length,
1339
- })}
1340
- </p>
1341
- ) : null}
1342
- </>
1343
- )}
1344
- </div>
1345
- );
1346
- }
1347
-
1348
- function reasonTranslationKey(reason: SigmaElementReviewReason) {
1349
- return {
1350
- unsupported_element: "dashboardOverview.sigmaReasonUnsupportedElement",
1351
- unsupported_source: "dashboardOverview.sigmaReasonUnsupportedSource",
1352
- source_unavailable: "dashboardOverview.sigmaReasonSourceUnavailable",
1353
- source_mapping_required:
1354
- "dashboardOverview.sigmaReasonSourceMappingRequired",
1355
- query_unavailable: "dashboardOverview.sigmaReasonQueryUnavailable",
1356
- unsafe_sql: "dashboardOverview.sigmaReasonUnsafeSql",
1357
- visualization_review: "dashboardOverview.sigmaReasonVisualizationReview",
1358
- }[reason];
1359
- }
1360
-
1361
- function WorkbookStatusBadge({ status }: { status: SigmaWorkbook["status"] }) {
1362
- const t = useT();
1363
- const labelKey = {
1364
- not_started: "dashboardOverview.sigmaStatusNotImported",
1365
- migrating: "dashboardOverview.sigmaStatusImporting",
1366
- migrated: "dashboardOverview.sigmaStatusImported",
1367
- failed: "dashboardOverview.sigmaStatusFailed",
1368
- }[status];
1369
- const variant = status === "failed" ? "destructive" : "outline";
1370
- return <Badge variant={variant}>{t(labelKey)}</Badge>;
1371
- }
1372
-
1373
839
  function StateMessage({
1374
840
  icon,
1375
841
  title,
@@ -1397,31 +863,15 @@ function StateMessage({
1397
863
 
1398
864
  function WorkbookListSkeleton() {
1399
865
  return (
1400
- <div className="space-y-4" aria-busy="true">
1401
- <Skeleton className="h-4 w-40" />
1402
- <Skeleton className="h-10 w-full" />
1403
- <Skeleton className="h-4 w-36" />
1404
- <Skeleton className="h-10 w-full" />
1405
- <div className="space-y-2 rounded-md border p-3">
1406
- <Skeleton className="h-4 w-44" />
1407
- <Skeleton className="h-8 w-full" />
1408
- <Skeleton className="h-8 w-full" />
1409
- </div>
1410
- </div>
1411
- );
1412
- }
1413
-
1414
- function PreviewSkeleton() {
1415
- return (
1416
- <div className="space-y-2" aria-busy="true">
1417
- <div className="flex items-center justify-between gap-2">
1418
- <Skeleton className="h-4 w-32" />
1419
- <Skeleton className="h-6 w-40" />
1420
- </div>
1421
- <div className="space-y-3 rounded-md border border-border/60 p-3">
1422
- <Skeleton className="h-8 w-full" />
1423
- <Skeleton className="h-8 w-full" />
1424
- <Skeleton className="h-8 w-full" />
866
+ <div className="space-y-3" aria-busy="true">
867
+ <Skeleton className="h-9 w-full" />
868
+ <div className="space-y-1 rounded-md border border-border/60 p-2">
869
+ {[0, 1, 2, 3, 4].map((row) => (
870
+ <div key={row} className="flex items-center justify-between gap-3">
871
+ <Skeleton className="h-5 w-2/3" />
872
+ <Skeleton className="h-8 w-20" />
873
+ </div>
874
+ ))}
1425
875
  </div>
1426
876
  </div>
1427
877
  );
@@ -1467,7 +917,15 @@ function isWorkbookListResult(
1467
917
  workbook.flaggedCount >= 0)) &&
1468
918
  (workbook.migratedByTeammate === undefined ||
1469
919
  typeof workbook.migratedByTeammate === "boolean") &&
1470
- typeof workbook.retryableByCurrentUser === "boolean",
920
+ (workbook.canOpen === undefined ||
921
+ typeof workbook.canOpen === "boolean") &&
922
+ (workbook.migratedOwnerEmail === undefined ||
923
+ typeof workbook.migratedOwnerEmail === "string") &&
924
+ (workbook.migratingByEmail === undefined ||
925
+ typeof workbook.migratingByEmail === "string") &&
926
+ isOptionalName(workbook.migratedOwnerName) &&
927
+ isOptionalName(workbook.migratingByName) &&
928
+ typeof workbook.canStartImport === "boolean",
1471
929
  ) &&
1472
930
  (result.nextPage === undefined ||
1473
931
  result.nextPage === null ||
@@ -1493,82 +951,190 @@ function actionErrorCode(error: unknown): string | undefined {
1493
951
  return typeof errorCode === "string" ? errorCode : undefined;
1494
952
  }
1495
953
 
1496
- function isWorkbookInspection(
1497
- value: unknown,
1498
- ): value is SigmaWorkbookInspection {
1499
- if (!value || typeof value !== "object") return false;
1500
- const result = value as SigmaWorkbookInspection;
954
+ function safeDashboardPath(id: string | null) {
955
+ if (!id || !/^[A-Za-z0-9_-]{1,200}$/.test(id)) return null;
956
+ return `/dashboards/${encodeURIComponent(id)}`;
957
+ }
958
+
959
+ function isOptionalName(value: unknown): boolean {
960
+ return value === undefined || value === null || typeof value === "string";
961
+ }
962
+
963
+ function WorkbookTable({
964
+ workbooks,
965
+ emptyMessage,
966
+ }: {
967
+ workbooks: SigmaWorkbook[];
968
+ emptyMessage: string;
969
+ }) {
970
+ const t = useT();
1501
971
  return (
1502
- Boolean(result.workbook && typeof result.workbook === "object") &&
1503
- typeof result.workbook.id === "string" &&
1504
- typeof result.workbook.name === "string" &&
1505
- typeof result.importableCount === "number" &&
1506
- result.importableCount >= 0 &&
1507
- typeof result.unsupportedCount === "number" &&
1508
- result.unsupportedCount >= 0 &&
1509
- typeof result.reviewRequiredCount === "number" &&
1510
- result.reviewRequiredCount >= 0 &&
1511
- typeof result.sourceMappingRequiredCount === "number" &&
1512
- result.sourceMappingRequiredCount >= 0 &&
1513
- typeof result.betaRepresentationWarning === "boolean" &&
1514
- Array.isArray(result.elements) &&
1515
- result.elements.every(
1516
- (element) =>
1517
- typeof element.id === "string" &&
1518
- typeof element.name === "string" &&
1519
- typeof element.type === "string" &&
1520
- typeof element.importable === "boolean" &&
1521
- typeof element.visualizationPreserved === "boolean" &&
1522
- (element.reason === undefined ||
1523
- [
1524
- "unsupported_element",
1525
- "unsupported_source",
1526
- "source_unavailable",
1527
- "source_mapping_required",
1528
- "query_unavailable",
1529
- "unsafe_sql",
1530
- "visualization_review",
1531
- ].includes(element.reason)),
1532
- )
972
+ <TooltipProvider delayDuration={200}>
973
+ {/* A fixed height keeps the dialog still while the search narrows it. */}
974
+ <div className="h-96 overflow-y-auto rounded-md border border-border/60">
975
+ {workbooks.length === 0 ? (
976
+ <p className="p-4 text-sm text-muted-foreground">{emptyMessage}</p>
977
+ ) : null}
978
+ <Table>
979
+ <TableBody>
980
+ {workbooks.map((workbook) => (
981
+ <WorkbookRow key={workbook.id} workbook={workbook} t={t} />
982
+ ))}
983
+ </TableBody>
984
+ </Table>
985
+ </div>
986
+ </TooltipProvider>
1533
987
  );
1534
988
  }
1535
989
 
1536
- function isMigrationResult(value: unknown): value is SigmaMigrationResult {
1537
- if (!value || typeof value !== "object") return false;
1538
- const result = value as SigmaMigrationResult;
1539
- if (result.status === "in_progress") return true;
1540
- if (result.status === "already_migrated") {
1541
- return (
1542
- (result.dashboardId === undefined ||
1543
- typeof result.dashboardId === "string") &&
1544
- (result.dashboardName === undefined ||
1545
- typeof result.dashboardName === "string") &&
1546
- (result.dashboardUrl === undefined ||
1547
- typeof result.dashboardUrl === "string")
1548
- );
1549
- }
990
+ function WorkbookRow({
991
+ workbook,
992
+ t,
993
+ }: {
994
+ workbook: SigmaWorkbook;
995
+ t: ReturnType<typeof useT>;
996
+ }) {
997
+ // Migrate shows only where the server would let this user start an import,
998
+ // or recover their own import whose dashboard is gone.
999
+ const canMigrate =
1000
+ workbook.canStartImport || Boolean(workbook.canRecoverMissingDashboard);
1001
+ const dashboardPath =
1002
+ workbook.status === "migrated" && !canMigrate
1003
+ ? safeDashboardPath(workbook.dashboardId ?? null)
1004
+ : null;
1005
+ const migrating = workbook.status === "migrating" && !canMigrate;
1006
+ const migratingBy = migrating ? (workbook.migratingByEmail ?? null) : null;
1007
+ const ownerEmail =
1008
+ workbook.status === "migrated" && !canMigrate
1009
+ ? (workbook.migratedOwnerEmail ?? null)
1010
+ : null;
1011
+ const muted = !canMigrate;
1550
1012
  return (
1551
- result.status === "completed" &&
1552
- typeof result.dashboardId === "string" &&
1553
- result.dashboardId.length > 0 &&
1554
- typeof result.dashboardName === "string" &&
1555
- typeof result.importedCount === "number" &&
1556
- typeof result.unsupportedCount === "number" &&
1557
- typeof result.reviewRequiredCount === "number" &&
1558
- typeof result.flaggedCount === "number" &&
1559
- typeof result.dashboardUrl === "string"
1013
+ <TableRow
1014
+ data-workbook-id={workbook.id}
1015
+ data-state={muted ? "taken" : undefined}
1016
+ >
1017
+ <TableCell>
1018
+ <div
1019
+ className={
1020
+ muted
1021
+ ? "flex min-w-0 items-center gap-2 text-muted-foreground"
1022
+ : "flex min-w-0 items-center gap-2"
1023
+ }
1024
+ >
1025
+ {dashboardPath ? (
1026
+ // A real link, so middle-click and copy work. Opening an imported
1027
+ // workbook never starts a second import; the dashboard's own
1028
+ // access check decides what the new tab shows.
1029
+ <a
1030
+ href={appPath(dashboardPath)}
1031
+ target="_blank"
1032
+ rel="noopener"
1033
+ className="inline-flex min-w-0 items-center gap-1.5 hover:underline"
1034
+ aria-label={`${workbook.name} — ${t("dashboardOverview.sigmaOpenMigratedDashboard")}`}
1035
+ >
1036
+ <span className="truncate">{workbook.name}</span>
1037
+ <IconExternalLink
1038
+ aria-hidden="true"
1039
+ className="size-4 shrink-0"
1040
+ />
1041
+ </a>
1042
+ ) : (
1043
+ <span className="truncate">{workbook.name}</span>
1044
+ )}
1045
+ {workbook.isArchived ? (
1046
+ <Badge variant="outline">
1047
+ {t("dashboardOverview.sigmaArchived")}
1048
+ </Badge>
1049
+ ) : null}
1050
+ </div>
1051
+ </TableCell>
1052
+ <TableCell className="w-px whitespace-nowrap text-end">
1053
+ {ownerEmail ? (
1054
+ <span className="inline-flex items-center gap-2 text-sm text-muted-foreground">
1055
+ {t("dashboardOverview.sigmaOwnedBy")}
1056
+ <OwnerAvatar
1057
+ email={ownerEmail}
1058
+ name={workbook.migratedOwnerName ?? null}
1059
+ label={ownerLabel(ownerEmail, workbook.migratedOwnerName)}
1060
+ />
1061
+ </span>
1062
+ ) : migrating ? (
1063
+ <span className="inline-flex items-center gap-2 text-sm text-muted-foreground">
1064
+ <IconLoader2 aria-hidden="true" className="size-4 animate-spin" />
1065
+ {migratingBy ? (
1066
+ <OwnerAvatar
1067
+ email={migratingBy}
1068
+ name={workbook.migratingByName ?? null}
1069
+ label={t("dashboardOverview.sigmaMigratingTooltip", {
1070
+ name: ownerLabel(migratingBy, workbook.migratingByName),
1071
+ })}
1072
+ />
1073
+ ) : null}
1074
+ </span>
1075
+ ) : canMigrate ? (
1076
+ <Tooltip>
1077
+ <TooltipTrigger asChild>
1078
+ {/* A disabled button gets no pointer events, so the span carries the hover. */}
1079
+ <span className="inline-flex" tabIndex={0}>
1080
+ <Button type="button" size="sm" disabled>
1081
+ {t("dashboardOverview.sigmaMigrate")}
1082
+ </Button>
1083
+ </span>
1084
+ </TooltipTrigger>
1085
+ <TooltipContent>
1086
+ {t("dashboardOverview.sigmaMigrateComingSoon")}
1087
+ </TooltipContent>
1088
+ </Tooltip>
1089
+ ) : null}
1090
+ </TableCell>
1091
+ </TableRow>
1560
1092
  );
1561
1093
  }
1562
1094
 
1563
- function safeDashboardPath(id: string | null) {
1564
- if (!id || !/^[A-Za-z0-9_-]{1,200}$/.test(id)) return null;
1565
- return `/dashboards/${encodeURIComponent(id)}`;
1095
+ function ownerLabel(email: string, name: string | null | undefined): string {
1096
+ return name ? `${name} · ${email}` : email;
1566
1097
  }
1567
1098
 
1568
- function dashboardIdFromMigrationResult(
1569
- result: SigmaMigrationResult | null,
1570
- ): string | null {
1571
- if (result?.status === "completed") return result.dashboardId;
1572
- if (result?.status === "already_migrated") return result.dashboardId ?? null;
1573
- return null;
1099
+ /** The account-menu avatar: the person's photo, else two initials. */
1100
+ function OwnerAvatar({
1101
+ email,
1102
+ name,
1103
+ label,
1104
+ }: {
1105
+ email: string;
1106
+ name: string | null;
1107
+ label: string;
1108
+ }) {
1109
+ const avatarUrl = useAvatarUrl(email);
1110
+ return (
1111
+ <Tooltip>
1112
+ <TooltipTrigger asChild>
1113
+ {/* role="img" makes the label the avatar's accessible name. */}
1114
+ <span
1115
+ role="img"
1116
+ tabIndex={0}
1117
+ aria-label={label}
1118
+ className="inline-flex rounded-full border border-border text-xs font-semibold text-muted-foreground"
1119
+ >
1120
+ <Avatar className="size-6">
1121
+ {avatarUrl ? <AvatarImage src={avatarUrl} alt="" /> : null}
1122
+ <AvatarFallback>{initials(name ?? email)}</AvatarFallback>
1123
+ </Avatar>
1124
+ </span>
1125
+ </TooltipTrigger>
1126
+ <TooltipContent>{label}</TooltipContent>
1127
+ </Tooltip>
1128
+ );
1129
+ }
1130
+
1131
+ function initials(value: string): string {
1132
+ return (
1133
+ value
1134
+ .split(/[ @._-]+/)
1135
+ .filter(Boolean)
1136
+ .slice(0, 2)
1137
+ .map((part) => part[0]?.toUpperCase())
1138
+ .join("") || "?"
1139
+ );
1574
1140
  }