@manta-eu/react 0.2.0 → 0.2.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.
Files changed (235) hide show
  1. package/dist/components/channel-icon/index.d.ts +1 -0
  2. package/dist/components/channel-icon/index.d.ts.map +1 -1
  3. package/dist/components/channel-icon/index.js +1 -1
  4. package/dist/components/channel-icon/index.js.map +1 -1
  5. package/dist/components/planning/ActioningView.d.ts +1 -2
  6. package/dist/components/planning/ActioningView.d.ts.map +1 -1
  7. package/dist/components/planning/ActioningView.js +19 -44
  8. package/dist/components/planning/ActioningView.js.map +1 -1
  9. package/dist/components/planning/ManualActionCard.d.ts +4 -0
  10. package/dist/components/planning/ManualActionCard.d.ts.map +1 -1
  11. package/dist/components/planning/ManualActionCard.js +7 -5
  12. package/dist/components/planning/ManualActionCard.js.map +1 -1
  13. package/dist/components/planning/ManualActionList.d.ts +4 -0
  14. package/dist/components/planning/ManualActionList.d.ts.map +1 -1
  15. package/dist/components/planning/ManualActionList.js +8 -2
  16. package/dist/components/planning/ManualActionList.js.map +1 -1
  17. package/dist/components/planning/actioning-view-utils.d.ts +9 -0
  18. package/dist/components/planning/actioning-view-utils.d.ts.map +1 -1
  19. package/dist/components/planning/actioning-view-utils.js +19 -1
  20. package/dist/components/planning/actioning-view-utils.js.map +1 -1
  21. package/dist/components/tooltip/index.d.ts +7 -0
  22. package/dist/components/tooltip/index.d.ts.map +1 -1
  23. package/dist/components/tooltip/index.js +10 -1
  24. package/dist/components/tooltip/index.js.map +1 -1
  25. package/dist/globals.css +19 -6
  26. package/dist/graph/entity-link.d.ts +14 -0
  27. package/dist/graph/entity-link.d.ts.map +1 -0
  28. package/dist/graph/entity-link.js +12 -0
  29. package/dist/graph/entity-link.js.map +1 -0
  30. package/dist/graph/graph-list-utils.d.ts +1 -0
  31. package/dist/graph/graph-list-utils.d.ts.map +1 -1
  32. package/dist/graph/graph-list-utils.js +1 -0
  33. package/dist/graph/graph-list-utils.js.map +1 -1
  34. package/dist/graph/graph-model.d.ts.map +1 -1
  35. package/dist/graph/graph-model.js +14 -5
  36. package/dist/graph/graph-model.js.map +1 -1
  37. package/dist/graph/nodes/BrandAssetAnalysisView.d.ts.map +1 -1
  38. package/dist/graph/nodes/BrandAssetAnalysisView.js +2 -1
  39. package/dist/graph/nodes/BrandAssetAnalysisView.js.map +1 -1
  40. package/dist/graph/nodes/BrandFontSuggestionView.d.ts.map +1 -1
  41. package/dist/graph/nodes/BrandFontSuggestionView.js +2 -1
  42. package/dist/graph/nodes/BrandFontSuggestionView.js.map +1 -1
  43. package/dist/graph/nodes/BrandGuidelinesView.js +1 -1
  44. package/dist/graph/nodes/BrandGuidelinesView.js.map +1 -1
  45. package/dist/graph/nodes/NodePropertyTable.d.ts +4 -2
  46. package/dist/graph/nodes/NodePropertyTable.d.ts.map +1 -1
  47. package/dist/graph/nodes/NodePropertyTable.js +12 -59
  48. package/dist/graph/nodes/NodePropertyTable.js.map +1 -1
  49. package/dist/graph/nodes/node-property-row.d.ts +30 -0
  50. package/dist/graph/nodes/node-property-row.d.ts.map +1 -0
  51. package/dist/graph/nodes/node-property-row.js +35 -0
  52. package/dist/graph/nodes/node-property-row.js.map +1 -0
  53. package/dist/graph/property-formatters.d.ts +1 -0
  54. package/dist/graph/property-formatters.d.ts.map +1 -1
  55. package/dist/graph/property-formatters.js +4 -1
  56. package/dist/graph/property-formatters.js.map +1 -1
  57. package/dist/graph/special-nodes/AttributeList.d.ts +27 -0
  58. package/dist/graph/special-nodes/AttributeList.d.ts.map +1 -0
  59. package/dist/graph/special-nodes/AttributeList.js +57 -0
  60. package/dist/graph/special-nodes/AttributeList.js.map +1 -0
  61. package/dist/graph/special-nodes/EntityPreview.d.ts +5 -0
  62. package/dist/graph/special-nodes/EntityPreview.d.ts.map +1 -0
  63. package/dist/graph/special-nodes/EntityPreview.js +19 -0
  64. package/dist/graph/special-nodes/EntityPreview.js.map +1 -0
  65. package/dist/graph/special-nodes/EntityValueChip.d.ts +11 -0
  66. package/dist/graph/special-nodes/EntityValueChip.d.ts.map +1 -0
  67. package/dist/graph/special-nodes/EntityValueChip.js +40 -0
  68. package/dist/graph/special-nodes/EntityValueChip.js.map +1 -0
  69. package/dist/graph/special-nodes/GenericView.d.ts +2 -1
  70. package/dist/graph/special-nodes/GenericView.d.ts.map +1 -1
  71. package/dist/graph/special-nodes/GenericView.js +23 -69
  72. package/dist/graph/special-nodes/GenericView.js.map +1 -1
  73. package/dist/graph/special-nodes/card-facts.d.ts +52 -0
  74. package/dist/graph/special-nodes/card-facts.d.ts.map +1 -0
  75. package/dist/graph/special-nodes/card-facts.js +189 -0
  76. package/dist/graph/special-nodes/card-facts.js.map +1 -0
  77. package/dist/graph/special-nodes/property-icons.d.ts +9 -0
  78. package/dist/graph/special-nodes/property-icons.d.ts.map +1 -0
  79. package/dist/graph/special-nodes/property-icons.js +39 -0
  80. package/dist/graph/special-nodes/property-icons.js.map +1 -0
  81. package/dist/graph/status-utils.d.ts +16 -0
  82. package/dist/graph/status-utils.d.ts.map +1 -1
  83. package/dist/graph/status-utils.js +35 -0
  84. package/dist/graph/status-utils.js.map +1 -1
  85. package/dist/graph/types.d.ts +5 -0
  86. package/dist/graph/types.d.ts.map +1 -1
  87. package/dist/hooks/use-approve-anyway.d.ts +12 -0
  88. package/dist/hooks/use-approve-anyway.d.ts.map +1 -0
  89. package/dist/hooks/use-approve-anyway.js +81 -0
  90. package/dist/hooks/use-approve-anyway.js.map +1 -0
  91. package/dist/hooks/use-connection-problems.d.ts.map +1 -1
  92. package/dist/hooks/useValidateActionPlan.d.ts.map +1 -1
  93. package/dist/ldo-ui.d.ts +2 -1
  94. package/dist/ldo-ui.d.ts.map +1 -1
  95. package/dist/ldo-ui.js +1 -0
  96. package/dist/ldo-ui.js.map +1 -1
  97. package/dist/lib/manual-actions.d.ts +2 -1
  98. package/dist/lib/manual-actions.d.ts.map +1 -1
  99. package/dist/lib/manual-actions.js +3 -2
  100. package/dist/lib/manual-actions.js.map +1 -1
  101. package/dist/providers/ActiveChatProvider.js +3 -1
  102. package/dist/providers/ActiveChatProvider.js.map +1 -1
  103. package/dist/providers/ChatManagerProvider.d.ts.map +1 -1
  104. package/dist/providers/ChatManagerProvider.js +3 -1
  105. package/dist/providers/ChatManagerProvider.js.map +1 -1
  106. package/dist/providers/chat-socket-auth.d.ts +7 -2
  107. package/dist/providers/chat-socket-auth.d.ts.map +1 -1
  108. package/dist/providers/chat-socket-auth.js +87 -16
  109. package/dist/providers/chat-socket-auth.js.map +1 -1
  110. package/dist/providers/index.d.ts.map +1 -1
  111. package/dist/providers/index.js +8 -1
  112. package/dist/providers/index.js.map +1 -1
  113. package/dist/routes/activity/detail/ActivityChanges.d.ts +2 -0
  114. package/dist/routes/activity/detail/ActivityChanges.d.ts.map +1 -1
  115. package/dist/routes/activity/detail/ActivityChanges.js +8 -7
  116. package/dist/routes/activity/detail/ActivityChanges.js.map +1 -1
  117. package/dist/routes/activity/detail/ActivityDetail.d.ts +1 -0
  118. package/dist/routes/activity/detail/ActivityDetail.d.ts.map +1 -1
  119. package/dist/routes/activity/detail/ActivityDetail.js +5 -4
  120. package/dist/routes/activity/detail/ActivityDetail.js.map +1 -1
  121. package/dist/routes/activity/detail/ActivityStepsRail.d.ts +1 -0
  122. package/dist/routes/activity/detail/ActivityStepsRail.d.ts.map +1 -1
  123. package/dist/routes/activity/detail/ActivityStepsRail.js +3 -2
  124. package/dist/routes/activity/detail/ActivityStepsRail.js.map +1 -1
  125. package/dist/routes/activity/detail/ActivityTimeline.js +2 -2
  126. package/dist/routes/activity/detail/ActivityTimeline.js.map +1 -1
  127. package/dist/routes/activity/detail/NoChangesMessage.d.ts +6 -0
  128. package/dist/routes/activity/detail/NoChangesMessage.d.ts.map +1 -0
  129. package/dist/routes/activity/detail/NoChangesMessage.js +10 -0
  130. package/dist/routes/activity/detail/NoChangesMessage.js.map +1 -0
  131. package/dist/routes/data-explorer/components/RawTriples.js +1 -1
  132. package/dist/routes/data-explorer/components/RawTriples.js.map +1 -1
  133. package/dist/routes/data-explorer/components/RecentTypes.js +1 -1
  134. package/dist/routes/data-explorer/components/RecentTypes.js.map +1 -1
  135. package/dist/routes/data-explorer/components/class-detail/Instances.js +1 -1
  136. package/dist/routes/data-explorer/components/class-detail/Instances.js.map +1 -1
  137. package/dist/routes/data-explorer/components/namespace-detail/NamespaceClassRow.js +1 -1
  138. package/dist/routes/data-explorer/components/namespace-detail/NamespaceClassRow.js.map +1 -1
  139. package/dist/routes/data-explorer/components/ontology-coverage/CoverageTermColumns.js +1 -1
  140. package/dist/routes/data-explorer/components/ontology-coverage/CoverageTermColumns.js.map +1 -1
  141. package/dist/routes/data-explorer/components/ontology-coverage/UnobservedRuleVocabulary.js +1 -1
  142. package/dist/routes/data-explorer/components/ontology-coverage/UnobservedRuleVocabulary.js.map +1 -1
  143. package/dist/routes/data-explorer/components/record-detail/ChatPreview.js +2 -2
  144. package/dist/routes/data-explorer/components/record-detail/ChatPreview.js.map +1 -1
  145. package/dist/routes/integrations/components/connect-forms/DataConnectionConnectForm.d.ts.map +1 -1
  146. package/dist/routes/integrations/components/connect-forms/DataConnectionConnectForm.js +13 -6
  147. package/dist/routes/integrations/components/connect-forms/DataConnectionConnectForm.js.map +1 -1
  148. package/dist/routes/integrations/components/connect-forms/data-connection-fields.d.ts +3 -0
  149. package/dist/routes/integrations/components/connect-forms/data-connection-fields.d.ts.map +1 -1
  150. package/dist/routes/integrations/components/connect-forms/data-connection-fields.js +9 -2
  151. package/dist/routes/integrations/components/connect-forms/data-connection-fields.js.map +1 -1
  152. package/dist/routes/plans/components/ActionPlanPage.d.ts.map +1 -1
  153. package/dist/routes/plans/components/ActionPlanPage.js +4 -7
  154. package/dist/routes/plans/components/ActionPlanPage.js.map +1 -1
  155. package/dist/routes/plans/components/PlanDetailRouteContent.d.ts.map +1 -1
  156. package/dist/routes/plans/components/PlanDetailRouteContent.js +2 -1
  157. package/dist/routes/plans/components/PlanDetailRouteContent.js.map +1 -1
  158. package/package.json +12 -7
  159. package/src/app-subpaths.json +1 -0
  160. package/src/components/channel-icon/index.tsx +1 -1
  161. package/src/components/planning/ActioningView.tsx +62 -70
  162. package/src/components/planning/ManualActionCard.tsx +66 -14
  163. package/src/components/planning/ManualActionList.tsx +30 -11
  164. package/src/components/planning/actioning-view-utils.ts +27 -1
  165. package/src/components/tooltip/index.tsx +35 -2
  166. package/src/graph/entity-link.tsx +20 -0
  167. package/src/graph/graph-list-utils.ts +2 -0
  168. package/src/graph/graph-model.ts +15 -6
  169. package/src/graph/nodes/BrandAssetAnalysisView.tsx +3 -1
  170. package/src/graph/nodes/BrandFontSuggestionView.tsx +7 -1
  171. package/src/graph/nodes/BrandGuidelinesView.tsx +1 -1
  172. package/src/graph/nodes/NodePropertyTable.tsx +32 -143
  173. package/src/graph/nodes/node-property-row.tsx +154 -0
  174. package/src/graph/property-formatters.ts +5 -1
  175. package/src/graph/special-nodes/AttributeList.tsx +177 -0
  176. package/src/graph/special-nodes/EntityPreview.tsx +27 -0
  177. package/src/graph/special-nodes/EntityValueChip.tsx +85 -0
  178. package/src/graph/special-nodes/GenericView.tsx +49 -139
  179. package/src/graph/special-nodes/card-facts.ts +278 -0
  180. package/src/graph/special-nodes/property-icons.ts +59 -0
  181. package/src/graph/status-utils.ts +40 -0
  182. package/src/graph/types.ts +4 -0
  183. package/src/hooks/use-approve-anyway.ts +100 -0
  184. package/src/ldo-ui.ts +8 -0
  185. package/src/lib/manual-actions.ts +3 -2
  186. package/src/providers/ActiveChatProvider.tsx +3 -1
  187. package/src/providers/ChatManagerProvider.tsx +3 -1
  188. package/src/providers/chat-socket-auth.ts +95 -16
  189. package/src/providers/index.tsx +16 -1
  190. package/src/routes/activity/detail/ActivityChanges.tsx +21 -5
  191. package/src/routes/activity/detail/ActivityDetail.tsx +24 -5
  192. package/src/routes/activity/detail/ActivityStepsRail.tsx +4 -2
  193. package/src/routes/activity/detail/ActivityTimeline.tsx +2 -2
  194. package/src/routes/activity/detail/NoChangesMessage.tsx +20 -0
  195. package/src/routes/data-explorer/components/RawTriples.tsx +1 -1
  196. package/src/routes/data-explorer/components/RecentTypes.tsx +1 -1
  197. package/src/routes/data-explorer/components/class-detail/Instances.tsx +1 -1
  198. package/src/routes/data-explorer/components/namespace-detail/NamespaceClassRow.tsx +1 -1
  199. package/src/routes/data-explorer/components/ontology-coverage/CoverageTermColumns.tsx +1 -1
  200. package/src/routes/data-explorer/components/ontology-coverage/UnobservedRuleVocabulary.tsx +1 -1
  201. package/src/routes/data-explorer/components/record-detail/ChatPreview.tsx +2 -2
  202. package/src/routes/integrations/components/connect-forms/DataConnectionConnectForm.tsx +38 -12
  203. package/src/routes/integrations/components/connect-forms/data-connection-fields.ts +11 -2
  204. package/src/routes/plans/components/ActionPlanPage.tsx +9 -8
  205. package/src/routes/plans/components/PlanDetailRouteContent.tsx +4 -3
  206. package/dist/components/planning/ExecutionActivityRow.d.ts +0 -9
  207. package/dist/components/planning/ExecutionActivityRow.d.ts.map +0 -1
  208. package/dist/components/planning/ExecutionActivityRow.js +0 -53
  209. package/dist/components/planning/ExecutionActivityRow.js.map +0 -1
  210. package/dist/components/planning/ExecutionSectionItem.d.ts +0 -20
  211. package/dist/components/planning/ExecutionSectionItem.d.ts.map +0 -1
  212. package/dist/components/planning/ExecutionSectionItem.js +0 -66
  213. package/dist/components/planning/ExecutionSectionItem.js.map +0 -1
  214. package/dist/components/planning/ExecutionSections.d.ts +0 -16
  215. package/dist/components/planning/ExecutionSections.d.ts.map +0 -1
  216. package/dist/components/planning/ExecutionSections.js +0 -32
  217. package/dist/components/planning/ExecutionSections.js.map +0 -1
  218. package/dist/components/planning/action-plan-icons.d.ts +0 -3
  219. package/dist/components/planning/action-plan-icons.d.ts.map +0 -1
  220. package/dist/components/planning/action-plan-icons.js +0 -25
  221. package/dist/components/planning/action-plan-icons.js.map +0 -1
  222. package/dist/components/planning/execution-section-model.d.ts +0 -24
  223. package/dist/components/planning/execution-section-model.d.ts.map +0 -1
  224. package/dist/components/planning/execution-section-model.js +0 -150
  225. package/dist/components/planning/execution-section-model.js.map +0 -1
  226. package/dist/routes/data-explorer/components/TruncateTooltip.d.ts +0 -7
  227. package/dist/routes/data-explorer/components/TruncateTooltip.d.ts.map +0 -1
  228. package/dist/routes/data-explorer/components/TruncateTooltip.js +0 -9
  229. package/dist/routes/data-explorer/components/TruncateTooltip.js.map +0 -1
  230. package/src/components/planning/ExecutionActivityRow.tsx +0 -89
  231. package/src/components/planning/ExecutionSectionItem.tsx +0 -184
  232. package/src/components/planning/ExecutionSections.tsx +0 -97
  233. package/src/components/planning/action-plan-icons.tsx +0 -27
  234. package/src/components/planning/execution-section-model.ts +0 -190
  235. package/src/routes/data-explorer/components/TruncateTooltip.tsx +0 -25
@@ -1,44 +1,123 @@
1
1
  import { chatTicketPath, chatTicketQueryParam } from "@manta-eu/client/shared/chat-manager";
2
2
  import { datastoreHeader, datastoreQueryParam } from "@manta-eu/client/shared/datastore-path";
3
3
  import { isPresent } from "@manta-eu/lib/present";
4
- import { useCallback, useEffect, useMemo } from "react";
4
+ import { isTerminalCloseEvent } from "agents/client";
5
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
5
6
  import { providerFetch } from "./manta-client-context";
6
7
  import { useMantaClient } from "./MantaClientProvider";
7
8
 
8
- // A reconnect always fetches a new ticket, and an expiring cache entry would replace a live socket.
9
- const ticketCacheTtlMs = 24 * 60 * 60 * 1000;
10
9
  const tokenRefreshCheckMs = 30_000;
10
+ const reconnectDelaysMs = [0, 1_000, 2_000, 5_000, 10_000];
11
+ const dropCountResetMs = 60_000;
12
+ const stableConnectionMs = 5_000;
13
+ // Any other 4xx is Manta refusing this person or datastore for good, so asking again cannot help.
14
+ const retriedRefusals = new Set([401, 408, 429]);
11
15
 
12
16
  export type ChatSocketAuth =
13
17
  | { query: Record<string, string>; credentials: "include" }
14
- | { query: () => Promise<Record<string, string>>; cacheTtl: number; credentials: "omit" };
18
+ | {
19
+ query: Record<string, string>;
20
+ enabled: boolean;
21
+ shouldReconnectOnClose: (event: CloseEvent) => boolean;
22
+ credentials: "omit";
23
+ };
24
+
25
+ type Ticketed = { datastoreKey: string; query: Record<string, string> };
15
26
 
16
27
  function ticketFrom(body: unknown): string | undefined {
17
28
  if (typeof body !== "object" || body === null || !("ticket" in body)) return undefined;
18
29
  return typeof body.ticket === "string" ? body.ticket : undefined;
19
30
  }
20
31
 
32
+ // partysocket clears shouldReconnect before a close it was asked for: an unmount, or a socket being replaced.
33
+ function isRequestedClose(event: CloseEvent): boolean {
34
+ const target: unknown = event.target;
35
+ return typeof target === "object" && target !== null && "shouldReconnect" in target && !target.shouldReconnect;
36
+ }
37
+
38
+ /**
39
+ * A ticket is spent on its first connect, and partysocket would reconnect to the same URL, so the socket never
40
+ * reconnects by itself: each connect waits for a new ticket, and a dropped socket asks for one.
41
+ */
21
42
  export function useChatSocketAuth(datastoreQuery: Record<string, string>): ChatSocketAuth {
22
43
  const client = useMantaClient();
23
44
  const { getAccessToken, mantaBaseUrl } = client;
24
45
  const authorizedFetch = providerFetch(client);
46
+ const authorizedFetchRef = useRef(authorizedFetch);
47
+ authorizedFetchRef.current = authorizedFetch;
48
+ const hasAccessToken = getAccessToken !== undefined;
49
+ const datastoreKey = JSON.stringify(datastoreQuery);
50
+ const datastoreQueryRef = useRef(datastoreQuery);
51
+ datastoreQueryRef.current = datastoreQuery;
52
+ const [ticketed, setTicketed] = useState<Ticketed>();
53
+ const [connectRequest, setConnectRequest] = useState(0);
54
+ const drops = useRef({ count: 0, lastAt: 0 });
55
+ const ticketedAt = useRef<number>(undefined);
25
56
 
26
- const ticketQuery = useCallback(async () => {
27
- const response = await authorizedFetch(new URL(`/${chatTicketPath}`, mantaBaseUrl), {
28
- method: "POST",
29
- headers: datastoreHeader(datastoreQuery[datastoreQueryParam]),
30
- });
31
- const ticket = response.ok ? ticketFrom(await response.json()) : undefined;
32
- if (!isPresent(ticket)) throw new Error(`Manta refused a chat ticket (${response.status})`);
33
- return { ...datastoreQuery, [chatTicketQueryParam]: ticket };
34
- }, [authorizedFetch, mantaBaseUrl, datastoreQuery]);
57
+ // A socket that stayed up starts the backoff over; one dropped at once, as a server refusing it would, does not.
58
+ const recordDrop = useCallback((socketClosed = false) => {
59
+ const now = Date.now();
60
+ const stayedUp = socketClosed && ticketedAt.current !== undefined && now - ticketedAt.current >= stableConnectionMs;
61
+ const fresh = stayedUp || now - drops.current.lastAt > dropCountResetMs;
62
+ drops.current = { count: fresh ? 1 : drops.current.count + 1, lastAt: now };
63
+ setConnectRequest((request) => request + 1);
64
+ }, []);
65
+
66
+ useEffect(() => {
67
+ if (!hasAccessToken) return;
68
+ setTicketed((held) => (held?.datastoreKey === datastoreKey ? held : undefined));
69
+ let cancelled = false;
70
+ const delay = reconnectDelaysMs[Math.min(Math.max(drops.current.count - 1, 0), reconnectDelaysMs.length - 1)];
71
+ const timer = setTimeout(() => {
72
+ const query = datastoreQueryRef.current;
73
+ authorizedFetchRef
74
+ .current(new URL(`/${chatTicketPath}`, mantaBaseUrl), {
75
+ method: "POST",
76
+ headers: datastoreHeader(query[datastoreQueryParam]),
77
+ })
78
+ .then(async (response) => {
79
+ if (response.status >= 400 && response.status < 500 && !retriedRefusals.has(response.status)) {
80
+ if (!cancelled) console.warn(`Manta refused a chat ticket (${response.status}); not asking again`);
81
+ return;
82
+ }
83
+ const ticket = response.ok ? ticketFrom(await response.json()) : undefined;
84
+ if (!isPresent(ticket)) throw new Error(`Manta refused a chat ticket (${response.status})`);
85
+ if (cancelled) return;
86
+ ticketedAt.current = Date.now();
87
+ setTicketed({ datastoreKey, query: { ...query, [chatTicketQueryParam]: ticket } });
88
+ })
89
+ .catch((error: unknown) => {
90
+ if (cancelled) return;
91
+ console.warn("Could not get a chat ticket", error);
92
+ recordDrop();
93
+ });
94
+ }, delay);
95
+ return () => {
96
+ cancelled = true;
97
+ clearTimeout(timer);
98
+ };
99
+ }, [hasAccessToken, mantaBaseUrl, datastoreKey, connectRequest, recordDrop]);
100
+
101
+ const shouldReconnectOnClose = useCallback(
102
+ (event: CloseEvent) => {
103
+ if (!isRequestedClose(event) && !isTerminalCloseEvent(event)) recordDrop(true);
104
+ return false;
105
+ },
106
+ [recordDrop],
107
+ );
35
108
 
109
+ const ticketQuery = ticketed?.datastoreKey === datastoreKey ? ticketed.query : undefined;
36
110
  return useMemo(
37
111
  () =>
38
- getAccessToken
39
- ? { query: ticketQuery, cacheTtl: ticketCacheTtlMs, credentials: "omit" as const }
112
+ hasAccessToken
113
+ ? {
114
+ query: ticketQuery ?? datastoreQuery,
115
+ enabled: ticketQuery !== undefined,
116
+ shouldReconnectOnClose,
117
+ credentials: "omit" as const,
118
+ }
40
119
  : { query: datastoreQuery, credentials: "include" as const },
41
- [getAccessToken, ticketQuery, datastoreQuery],
120
+ [hasAccessToken, ticketQuery, datastoreQuery, shouldReconnectOnClose],
42
121
  );
43
122
  }
44
123
 
@@ -1,9 +1,12 @@
1
1
  import { defaultDeployment } from "@manta-eu/auth/default-deployment";
2
2
  import type { AccessTokenSource } from "@manta-eu/client";
3
+ import { entityPath } from "@manta-eu/client/shared/explorer-paths";
3
4
  import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
4
5
  import { NuqsAdapter } from "nuqs/adapters/react-router/v7";
5
6
  import type { ReactNode } from "react";
6
7
  import { useState } from "react";
8
+ import { Link } from "react-router";
9
+ import { EntityLinkProvider, type EntityLinkProps } from "../graph/entity-link";
7
10
  import { ActiveDatastoreProvider, ActiveDatastoreRouter, useActiveDatastore } from "./ActiveDatastoreProvider";
8
11
  import { GenerateStructuredDataProvider } from "./GenerateStructuredDataProvider";
9
12
  import { KnowledgeGraphPollingProvider } from "./KnowledgeGraphPollingProvider";
@@ -89,6 +92,16 @@ function CoreProviders(props: ProvidersProps) {
89
92
  );
90
93
  }
91
94
 
95
+ function RecordEntityLink(props: EntityLinkProps) {
96
+ const { iri, className, children } = props;
97
+
98
+ return (
99
+ <Link to={entityPath(iri)} className={className} onClick={(event) => event.stopPropagation()}>
100
+ {children}
101
+ </Link>
102
+ );
103
+ }
104
+
92
105
  export function Providers(props: ProvidersProps) {
93
106
  const { children, ...coreProps } = props;
94
107
 
@@ -97,7 +110,9 @@ export function Providers(props: ProvidersProps) {
97
110
  <ThemeProvider>
98
111
  <CoreProviders {...coreProps}>
99
112
  <ActiveDatastoreRouter>
100
- <NuqsAdapter>{children}</NuqsAdapter>
113
+ <NuqsAdapter>
114
+ <EntityLinkProvider component={RecordEntityLink}>{children}</EntityLinkProvider>
115
+ </NuqsAdapter>
101
116
  </ActiveDatastoreRouter>
102
117
  </CoreProviders>
103
118
  </ThemeProvider>
@@ -8,6 +8,7 @@ import { useIntersectionObserver } from "usehooks-ts";
8
8
  import { DiffRow } from "../change-display";
9
9
  import { EntityNodeView, subsumesChangeRows } from "./EntityNodeView";
10
10
  import { LazyMount, viewportOverscan } from "./LazyMount";
11
+ import { NoChangesMessage } from "./NoChangesMessage";
11
12
  import { useEntityNodes, type ActivityChangeGroup, type EntityNode } from "#ldo-ui";
12
13
  import { cn } from "#lib/utils";
13
14
  import { entityPath } from "#routes/data-explorer/namespaces";
@@ -52,10 +53,11 @@ type ChangeGroupProps = {
52
53
  node?: EntityNode;
53
54
  nodesLoading: boolean;
54
55
  scrollRoot: HTMLElement | null;
56
+ showLinks: boolean;
55
57
  };
56
58
 
57
59
  function ChangeGroup(props: ChangeGroupProps) {
58
- const { group, selected, onJumpTo, node, nodesLoading, scrollRoot } = props;
60
+ const { group, selected, onJumpTo, node, nodesLoading, scrollRoot, showLinks } = props;
59
61
 
60
62
  // Answered from the change's own triples until the fetch lands, so the rows do not show then vanish.
61
63
  const typedCard = useMemo(
@@ -87,7 +89,7 @@ function ChangeGroup(props: ChangeGroupProps) {
87
89
  >
88
90
  {group.entityLabel}
89
91
  </button>
90
- {isFullIri(group.entityIri) && (
92
+ {showLinks && isFullIri(group.entityIri) && (
91
93
  <Link
92
94
  to={entityPath(group.entityIri)}
93
95
  aria-label={`Open ${group.entityLabel} in the data explorer`}
@@ -145,10 +147,11 @@ type ChangeBlockProps = {
145
147
  activeIri: string | null;
146
148
  onJumpTo: (entityIri: string) => void;
147
149
  scrollRoot: HTMLElement | null;
150
+ showLinks: boolean;
148
151
  };
149
152
 
150
153
  function ChangeBlock(props: ChangeBlockProps) {
151
- const { groups, activeIri, onJumpTo, scrollRoot } = props;
154
+ const { groups, activeIri, onJumpTo, scrollRoot, showLinks } = props;
152
155
 
153
156
  const { ref, isIntersecting } = useIntersectionObserver({
154
157
  root: scrollRoot,
@@ -173,6 +176,7 @@ function ChangeBlock(props: ChangeBlockProps) {
173
176
  .find(isPresent)}
174
177
  nodesLoading={isLoading}
175
178
  scrollRoot={scrollRoot}
179
+ showLinks={showLinks}
176
180
  />
177
181
  ))}
178
182
  </div>
@@ -188,10 +192,21 @@ export type ActivityChangesProps = {
188
192
  scrollTarget: { iri: string; nonce: number } | null;
189
193
  // Embedded runs in a short box, where the full-page scroll-room spacer would over-scroll.
190
194
  inline?: boolean;
195
+ running?: boolean;
196
+ showLinks?: boolean;
191
197
  };
192
198
 
193
199
  export function ActivityChanges(props: ActivityChangesProps) {
194
- const { groups, activeIri, onActivate, onJumpTo, scrollTarget, inline = false } = props;
200
+ const {
201
+ groups,
202
+ activeIri,
203
+ onActivate,
204
+ onJumpTo,
205
+ scrollTarget,
206
+ inline = false,
207
+ running = false,
208
+ showLinks = true,
209
+ } = props;
195
210
 
196
211
  const blocks = useMemo(
197
212
  () =>
@@ -260,7 +275,7 @@ export function ActivityChanges(props: ActivityChangesProps) {
260
275
  if (!isPresent(groups)) {
261
276
  return (
262
277
  <div className="rounded-xl border border-muted-border bg-card p-5">
263
- <p className="typography-small text-muted-foreground">No recorded changes.</p>
278
+ <NoChangesMessage running={running} />
264
279
  </div>
265
280
  );
266
281
  }
@@ -291,6 +306,7 @@ export function ActivityChanges(props: ActivityChangesProps) {
291
306
  activeIri={activeIri}
292
307
  onJumpTo={onJumpTo}
293
308
  scrollRoot={scrollRoot}
309
+ showLinks={showLinks}
294
310
  />
295
311
  ))}
296
312
 
@@ -41,10 +41,20 @@ export type ActivityDetailProps = {
41
41
  variant?: "page" | "embedded";
42
42
  // Embedded only — lead-in above the run, rendered after it loads.
43
43
  caption?: ReactNode;
44
+ // Off when the host has no route for the run page or the data explorer.
45
+ showLinks?: boolean;
44
46
  };
45
47
 
46
48
  export function ActivityDetail(props: ActivityDetailProps) {
47
- const { activityIri, leading, layout: controlledLayout, onLayoutChange, variant = "page", caption } = props;
49
+ const {
50
+ activityIri,
51
+ leading,
52
+ layout: controlledLayout,
53
+ onLayoutChange,
54
+ variant = "page",
55
+ caption,
56
+ showLinks = true,
57
+ } = props;
48
58
  const embedded = variant === "embedded";
49
59
 
50
60
  const [internalLayout, setInternalLayout] = useState<ActivityLayout>("timeline");
@@ -107,9 +117,13 @@ export function ActivityDetail(props: ActivityDetailProps) {
107
117
  />
108
118
  ) : null;
109
119
 
120
+ const isRunning = activity?.status === "running";
121
+
110
122
  const changes = (
111
123
  <ActivityChanges
112
124
  inline={embedded}
125
+ running={isRunning}
126
+ showLinks={showLinks}
113
127
  groups={groups}
114
128
  activeIri={activeIri}
115
129
  onActivate={activate}
@@ -140,7 +154,7 @@ export function ActivityDetail(props: ActivityDetailProps) {
140
154
  <div className="flex h-full min-h-0 flex-col gap-3">
141
155
  {isPresent(caption) && <p className="typography-small text-muted-foreground shrink-0">{caption}</p>}
142
156
  <div className="flex shrink-0 flex-wrap items-center gap-2">
143
- {isPresent(activityIri) ? (
157
+ {showLinks && isPresent(activityIri) ? (
144
158
  <Link
145
159
  to={buildActivityHref(activityIri)}
146
160
  title={`Open ${activity.planLabel} run detail`}
@@ -160,7 +174,7 @@ export function ActivityDetail(props: ActivityDetailProps) {
160
174
  <Banner label={activity.errorMessage} className="max-h-40 shrink-0 overflow-y-auto" />
161
175
  )}
162
176
 
163
- {isChangeSetLoading ? (
177
+ {isChangeSetLoading && !isRunning ? (
164
178
  <div className="flex items-center justify-center py-10">
165
179
  <Loader className="size-5" />
166
180
  </div>
@@ -234,7 +248,7 @@ export function ActivityDetail(props: ActivityDetailProps) {
234
248
  ) : layout === "steps" ? (
235
249
  <>
236
250
  <div className="min-h-0 flex-1 overflow-y-auto sm:hidden">
237
- <ActivityStepsRail groups={groups} activeIri={activeIri} onSelect={activate} />
251
+ <ActivityStepsRail groups={groups} activeIri={activeIri} onSelect={activate} running={isRunning} />
238
252
  <div className="border-t border-muted-border">
239
253
  <ActivityStepDetail group={activeGroup} />
240
254
  </div>
@@ -243,7 +257,12 @@ export function ActivityDetail(props: ActivityDetailProps) {
243
257
  <ResizablePanelGroup direction="horizontal" className="min-h-0 flex-1">
244
258
  <ResizablePanel defaultSize={26} minSize={18} maxSize={50} className="min-h-0">
245
259
  <div className="h-full overflow-y-auto">
246
- <ActivityStepsRail groups={groups} activeIri={activeIri} onSelect={activate} />
260
+ <ActivityStepsRail
261
+ groups={groups}
262
+ activeIri={activeIri}
263
+ onSelect={activate}
264
+ running={isRunning}
265
+ />
247
266
  </div>
248
267
  </ResizablePanel>
249
268
 
@@ -1,6 +1,7 @@
1
1
  import { humanCount } from "@manta-eu/lib/humanize";
2
2
  import { isPresent } from "@manta-eu/lib/present";
3
3
  import { formatStepTime } from "../format";
4
+ import { NoChangesMessage } from "./NoChangesMessage";
4
5
  import type { ActivityChangeGroup } from "#ldo-ui";
5
6
  import { cn } from "#lib/utils";
6
7
 
@@ -8,15 +9,16 @@ export type ActivityStepsRailProps = {
8
9
  groups: ActivityChangeGroup[];
9
10
  activeIri: string | null;
10
11
  onSelect: (entityIri: string) => void;
12
+ running?: boolean;
11
13
  };
12
14
 
13
15
  export function ActivityStepsRail(props: ActivityStepsRailProps) {
14
- const { groups, activeIri, onSelect } = props;
16
+ const { groups, activeIri, onSelect, running = false } = props;
15
17
 
16
18
  if (!isPresent(groups)) {
17
19
  return (
18
20
  <div className="p-4">
19
- <p className="typography-small text-muted-foreground">No recorded changes.</p>
21
+ <NoChangesMessage running={running} />
20
22
  </div>
21
23
  );
22
24
  }
@@ -42,7 +42,7 @@ export function ActivityTimeline(props: ActivityTimelineProps) {
42
42
 
43
43
  if (!isPresent(bars)) {
44
44
  return (
45
- <div className="rounded-xl border border-muted-border bg-card px-5 py-4">
45
+ <div className="shrink-0 rounded-xl border border-muted-border bg-card px-5 py-4">
46
46
  <p className="typography-small text-muted-foreground">No timestamps to plot.</p>
47
47
  </div>
48
48
  );
@@ -57,7 +57,7 @@ export function ActivityTimeline(props: ActivityTimelineProps) {
57
57
  const trackHeightPx = Math.max(laneCount, 1) * rowHeightPx;
58
58
 
59
59
  return (
60
- <div className="overflow-auto rounded-xl border border-muted-border bg-card">
60
+ <div className="shrink-0 overflow-auto rounded-xl border border-muted-border bg-card">
61
61
  <div className="px-5 pb-3 pt-3" style={{ minWidth: `${trackMinWidthPx}px` }}>
62
62
  <div className="relative h-4">
63
63
  {ticks.map((tick, index) => (
@@ -0,0 +1,20 @@
1
+ import { Loader } from "#components/loader/index";
2
+
3
+ type NoChangesMessageProps = {
4
+ running?: boolean;
5
+ };
6
+
7
+ export function NoChangesMessage(props: NoChangesMessageProps) {
8
+ const { running = false } = props;
9
+
10
+ if (!running) {
11
+ return <p className="typography-small text-muted-foreground">No recorded changes.</p>;
12
+ }
13
+
14
+ return (
15
+ <p role="status" className="flex items-center gap-2 typography-small text-muted-foreground">
16
+ <Loader className="size-4 shrink-0" />
17
+ Running. Changes appear as each step completes.
18
+ </p>
19
+ );
20
+ }
@@ -7,8 +7,8 @@ import type { ReactNode } from "react";
7
7
  import { Link } from "react-router";
8
8
  import { useTriples } from "../triples";
9
9
  import { turtleForSubject } from "../turtle";
10
- import { TruncateTooltip } from "./TruncateTooltip";
11
10
  import { Skeleton } from "#components/skeleton/index";
11
+ import { TruncateTooltip } from "#components/tooltip/index";
12
12
  import { decodeSkolem, isBlankNode, useInstanceLabel } from "#ldo-ui";
13
13
  import { cn } from "#lib/utils";
14
14
 
@@ -16,8 +16,8 @@ import { Breadcrumbs } from "./Breadcrumbs";
16
16
  import { DetailHeader, TypeBadge } from "./DetailHeader";
17
17
  import { ModifiedAgo } from "./ModifiedAgo";
18
18
  import { EntityName, LinkList, LinkRow, LoadingRows, StatusText } from "./RawTriples";
19
- import { TruncateTooltip } from "./TruncateTooltip";
20
19
  import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "#components/collapsible/index";
20
+ import { TruncateTooltip } from "#components/tooltip/index";
21
21
  import { cn } from "#lib/utils";
22
22
 
23
23
  type RecentInstanceRowsProps = { recentType: RecentType };
@@ -7,7 +7,7 @@ import { LoadMoreButton } from "../LoadMoreButton";
7
7
  import { ModifiedAgo } from "../ModifiedAgo";
8
8
  import { entityNameClass, EntityName, LinkList, LinkRow, LoadingRows, StatusText } from "../RawTriples";
9
9
  import { SegmentBuilder } from "../SegmentBuilder";
10
- import { TruncateTooltip } from "../TruncateTooltip";
10
+ import { TruncateTooltip } from "#components/tooltip/index";
11
11
  import { encodeBlankKey, useBlankInstances } from "#routes/data-explorer/blank-record";
12
12
  import { useFilteredCount, useFilteredInstances } from "#routes/data-explorer/filter-hooks";
13
13
  import { classAnchor, hasActiveConditions, type SegmentFilter } from "#routes/data-explorer/filter-query";
@@ -1,7 +1,7 @@
1
1
  import { humanCount } from "@manta-eu/lib/humanize";
2
2
  import { classPath } from "../../namespaces";
3
3
  import { entityNameClass, LinkRow } from "../RawTriples";
4
- import { TruncateTooltip } from "../TruncateTooltip";
4
+ import { TruncateTooltip } from "#components/tooltip/index";
5
5
  import type { ExplorerClass } from "#routes/data-explorer/classes";
6
6
 
7
7
  type NamespaceClassRowProps = { entry: ExplorerClass; count?: number };
@@ -4,10 +4,10 @@ import { useMemo } from "react";
4
4
  import { Link } from "react-router";
5
5
  import { classRef, namespaceLabel } from "../../namespaces";
6
6
  import { NamespaceSwatch } from "../NamespaceSwatch";
7
- import { TruncateTooltip } from "../TruncateTooltip";
8
7
  import { MissingValue } from "./MissingValue";
9
8
  import { RuleReferencesCell } from "./RuleReferencesCell";
10
9
  import { Badge } from "#components/badge/index";
10
+ import { TruncateTooltip } from "#components/tooltip/index";
11
11
  import { sectionLabelClass } from "#ldo-ui";
12
12
  import { cn } from "#lib/utils";
13
13
  import type { CoverageTerm } from "#routes/data-explorer/coverage";
@@ -6,13 +6,13 @@ import { useEffect, useMemo, useState } from "react";
6
6
  import { Link } from "react-router";
7
7
  import { classRef, namespaceLabel } from "../../namespaces";
8
8
  import { NamespaceSwatch } from "../NamespaceSwatch";
9
- import { TruncateTooltip } from "../TruncateTooltip";
10
9
  import { RuleReferencesCell } from "./RuleReferencesCell";
11
10
  import { Badge } from "#components/badge/index";
12
11
  import { EmptyState } from "#components/empty-state/index";
13
12
  import { SearchInput } from "#components/input/index";
14
13
  import { PaginationBlock } from "#components/pagination/index";
15
14
  import { createTableConfig, DataTable } from "#components/table/index";
15
+ import { TruncateTooltip } from "#components/tooltip/index";
16
16
  import { shouldClampPageIndex, useFittedPagination } from "#hooks/useFittedPagination";
17
17
  import { sectionLabelClass } from "#ldo-ui";
18
18
  import { cn } from "#lib/utils";
@@ -90,14 +90,14 @@ export function ChatPreview(props: ChatPreviewProps) {
90
90
 
91
91
  if (!node) return <StatusText>Nothing to preview.</StatusText>;
92
92
 
93
- const EntityView = getEntityView(node.data.type, node.data.actionShape) ?? GenericView;
93
+ const EntityView = getEntityView(node.data.type, node.data.actionShape);
94
94
 
95
95
  return (
96
96
  <div className="flex flex-col gap-6">
97
97
  <section>
98
98
  <h3 className={cn(sectionLabelClass, "mb-1.5 text-muted-foreground")}>Chat rendering</h3>
99
99
  <div className="max-w-md">
100
- <EntityView data={node.data} />
100
+ {EntityView ? <EntityView data={node.data} /> : <GenericView data={node.data} defaultExpanded />}
101
101
  </div>
102
102
  </section>
103
103
 
@@ -23,6 +23,8 @@ import {
23
23
  nameSchema,
24
24
  pickSettings,
25
25
  settingsSchema,
26
+ sourceKey,
27
+ sourceSchema,
26
28
  storedValues,
27
29
  type ConnectorField,
28
30
  type DataConnectionValues,
@@ -39,22 +41,41 @@ export type StoredConnection = StoredDataConnection & { id: string };
39
41
 
40
42
  type Step = StepConfig<DataConnectionValues>;
41
43
 
42
- function nameStep(providerName: string): Step {
44
+ const newSourceHint =
45
+ "Optional. Lowercase letters and digits. Left blank, Manta reuses the name of an earlier connection to this database, or takes one from the connection name. It can't change later.";
46
+ const editingSourceHint = "Set when the connection was created. It can't change.";
47
+
48
+ function nameStep(providerName: string, editing: boolean): Step {
43
49
  return {
44
50
  id: "name",
45
51
  title: "Name this connection",
46
52
  description: `Used to identify this ${providerName} database in Manta.`,
47
- schema: z.object({ [nameKey]: nameSchema }),
53
+ schema: z.object({ [nameKey]: nameSchema, [sourceKey]: sourceSchema }),
48
54
  continueLabel: "Continue",
49
55
  render: (form) => (
50
- <Input
51
- id={inputId(nameKey)}
52
- label="Name"
53
- placeholder="Warehouse"
54
- autoComplete="off"
55
- error={form.formState.errors[nameKey]?.message?.toString()}
56
- {...form.register(nameKey)}
57
- />
56
+ <div className="grid gap-4">
57
+ <Input
58
+ id={inputId(nameKey)}
59
+ label="Name"
60
+ placeholder="Warehouse"
61
+ autoComplete="off"
62
+ error={form.formState.errors[nameKey]?.message?.toString()}
63
+ {...form.register(nameKey)}
64
+ />
65
+
66
+ {(!editing || isPresent(form.getValues(sourceKey))) && (
67
+ <Input
68
+ id={inputId(sourceKey)}
69
+ label="Source name"
70
+ placeholder="warehouse"
71
+ autoComplete="off"
72
+ disabled={editing}
73
+ helperText={editing ? editingSourceHint : newSourceHint}
74
+ error={form.formState.errors[sourceKey]?.message?.toString()}
75
+ {...form.register(sourceKey)}
76
+ />
77
+ )}
78
+ </div>
58
79
  ),
59
80
  };
60
81
  }
@@ -117,7 +138,7 @@ export function createSteps(
117
138
  const required = fields.filter((field) => field.required);
118
139
  const optional = fields.filter((field) => !field.required);
119
140
  const steps = [
120
- nameStep(providerName),
141
+ nameStep(providerName, editing),
121
142
  connectionStep({ providerName, connectorType, stepFields: required, allFields: fields, editing }),
122
143
  ...(isPresent(optional) ? [optionalStep(optional, editing)] : []),
123
144
  ];
@@ -160,7 +181,12 @@ function DataConnectionWizard(props: DataConnectionWizardProps) {
160
181
 
161
182
  async function create(values: DataConnectionValues) {
162
183
  await createMutation.mutateAsync({
163
- input: { name: values[nameKey], connectorType, settings: pickSettings(values, fields) },
184
+ input: {
185
+ name: values[nameKey],
186
+ connectorType,
187
+ sourceName: values[sourceKey] || undefined,
188
+ settings: pickSettings(values, fields),
189
+ },
164
190
  });
165
191
  toast.success(`${provider.name} added`, { description: "Manta is testing the connection." });
166
192
  }
@@ -5,12 +5,19 @@ import { z } from "zod";
5
5
  export type ConnectorField = GQLDataConnectorTypesQuery["dataConnectorTypes"][number]["fields"][number];
6
6
 
7
7
  export const nameKey = "connectionName";
8
+ export const sourceKey = "sourceName";
8
9
 
9
10
  // The setting keys come from the connector descriptor at runtime, so the value type is an index signature.
10
11
  export type DataConnectionValues = { [nameKey]: string; [setting: string]: string };
11
12
 
12
13
  export const nameSchema = z.string().trim().min(1, "Name is required").max(64, "64 characters or fewer");
13
14
 
15
+ export const sourceSchema = z
16
+ .string()
17
+ .trim()
18
+ .regex(/^([a-z][a-z0-9]*)?$/, "Use lowercase letters and digits, starting with a letter")
19
+ .optional();
20
+
14
21
  const valuePatterns: Partial<Record<GQLDataConnectorFieldType, { pattern: RegExp; message: string }>> = {
15
22
  [GQLDataConnectorFieldType.Integer]: { pattern: /^-?\d+$/, message: "Enter a whole number" },
16
23
  [GQLDataConnectorFieldType.Decimal]: { pattern: /^-?\d+(\.\d+)?$/, message: "Enter a number" },
@@ -40,7 +47,7 @@ export function settingsSchema(fields: readonly ConnectorField[]) {
40
47
 
41
48
  // The fields come at runtime, so the built schema has no static output type to infer.
42
49
  export function formSchema(fields: readonly ConnectorField[]): z.ZodType<DataConnectionValues, DataConnectionValues> {
43
- return settingsSchema(fields).extend({ [nameKey]: nameSchema }) as unknown as z.ZodType<
50
+ return settingsSchema(fields).extend({ [nameKey]: nameSchema, [sourceKey]: sourceSchema }) as unknown as z.ZodType<
44
51
  DataConnectionValues,
45
52
  DataConnectionValues
46
53
  >;
@@ -55,13 +62,14 @@ export function pickSettings(values: DataConnectionValues, fields: readonly Conn
55
62
  }
56
63
 
57
64
  export function blankValues(fields: readonly ConnectorField[]): DataConnectionValues {
58
- return { ...Object.fromEntries(fields.map((field) => [field.key, ""])), [nameKey]: "" };
65
+ return { ...Object.fromEntries(fields.map((field) => [field.key, ""])), [nameKey]: "", [sourceKey]: "" };
59
66
  }
60
67
 
61
68
  export type DataConnectionSetting = { key: string; value: string };
62
69
 
63
70
  export type StoredDataConnection = {
64
71
  name: string;
72
+ sourceName?: string | null;
65
73
  settings: readonly DataConnectionSetting[];
66
74
  candidateSettings?: readonly DataConnectionSetting[] | null;
67
75
  error?: string | null;
@@ -83,6 +91,7 @@ export function storedValues(stored: StoredDataConnection, fields: readonly Conn
83
91
  return {
84
92
  ...Object.fromEntries(fields.map((field) => [field.key, field.secret ? "" : (storedByKey.get(field.key) ?? "")])),
85
93
  [nameKey]: stored.name,
94
+ [sourceKey]: stored.sourceName ?? "",
86
95
  };
87
96
  }
88
97