@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.
- package/dist/components/channel-icon/index.d.ts +1 -0
- package/dist/components/channel-icon/index.d.ts.map +1 -1
- package/dist/components/channel-icon/index.js +1 -1
- package/dist/components/channel-icon/index.js.map +1 -1
- package/dist/components/planning/ActioningView.d.ts +1 -2
- package/dist/components/planning/ActioningView.d.ts.map +1 -1
- package/dist/components/planning/ActioningView.js +19 -44
- package/dist/components/planning/ActioningView.js.map +1 -1
- package/dist/components/planning/ManualActionCard.d.ts +4 -0
- package/dist/components/planning/ManualActionCard.d.ts.map +1 -1
- package/dist/components/planning/ManualActionCard.js +7 -5
- package/dist/components/planning/ManualActionCard.js.map +1 -1
- package/dist/components/planning/ManualActionList.d.ts +4 -0
- package/dist/components/planning/ManualActionList.d.ts.map +1 -1
- package/dist/components/planning/ManualActionList.js +8 -2
- package/dist/components/planning/ManualActionList.js.map +1 -1
- package/dist/components/planning/actioning-view-utils.d.ts +9 -0
- package/dist/components/planning/actioning-view-utils.d.ts.map +1 -1
- package/dist/components/planning/actioning-view-utils.js +19 -1
- package/dist/components/planning/actioning-view-utils.js.map +1 -1
- package/dist/components/tooltip/index.d.ts +7 -0
- package/dist/components/tooltip/index.d.ts.map +1 -1
- package/dist/components/tooltip/index.js +10 -1
- package/dist/components/tooltip/index.js.map +1 -1
- package/dist/globals.css +19 -6
- package/dist/graph/entity-link.d.ts +14 -0
- package/dist/graph/entity-link.d.ts.map +1 -0
- package/dist/graph/entity-link.js +12 -0
- package/dist/graph/entity-link.js.map +1 -0
- package/dist/graph/graph-list-utils.d.ts +1 -0
- package/dist/graph/graph-list-utils.d.ts.map +1 -1
- package/dist/graph/graph-list-utils.js +1 -0
- package/dist/graph/graph-list-utils.js.map +1 -1
- package/dist/graph/graph-model.d.ts.map +1 -1
- package/dist/graph/graph-model.js +14 -5
- package/dist/graph/graph-model.js.map +1 -1
- package/dist/graph/nodes/BrandAssetAnalysisView.d.ts.map +1 -1
- package/dist/graph/nodes/BrandAssetAnalysisView.js +2 -1
- package/dist/graph/nodes/BrandAssetAnalysisView.js.map +1 -1
- package/dist/graph/nodes/BrandFontSuggestionView.d.ts.map +1 -1
- package/dist/graph/nodes/BrandFontSuggestionView.js +2 -1
- package/dist/graph/nodes/BrandFontSuggestionView.js.map +1 -1
- package/dist/graph/nodes/BrandGuidelinesView.js +1 -1
- package/dist/graph/nodes/BrandGuidelinesView.js.map +1 -1
- package/dist/graph/nodes/NodePropertyTable.d.ts +4 -2
- package/dist/graph/nodes/NodePropertyTable.d.ts.map +1 -1
- package/dist/graph/nodes/NodePropertyTable.js +12 -59
- package/dist/graph/nodes/NodePropertyTable.js.map +1 -1
- package/dist/graph/nodes/node-property-row.d.ts +30 -0
- package/dist/graph/nodes/node-property-row.d.ts.map +1 -0
- package/dist/graph/nodes/node-property-row.js +35 -0
- package/dist/graph/nodes/node-property-row.js.map +1 -0
- package/dist/graph/property-formatters.d.ts +1 -0
- package/dist/graph/property-formatters.d.ts.map +1 -1
- package/dist/graph/property-formatters.js +4 -1
- package/dist/graph/property-formatters.js.map +1 -1
- package/dist/graph/special-nodes/AttributeList.d.ts +27 -0
- package/dist/graph/special-nodes/AttributeList.d.ts.map +1 -0
- package/dist/graph/special-nodes/AttributeList.js +57 -0
- package/dist/graph/special-nodes/AttributeList.js.map +1 -0
- package/dist/graph/special-nodes/EntityPreview.d.ts +5 -0
- package/dist/graph/special-nodes/EntityPreview.d.ts.map +1 -0
- package/dist/graph/special-nodes/EntityPreview.js +19 -0
- package/dist/graph/special-nodes/EntityPreview.js.map +1 -0
- package/dist/graph/special-nodes/EntityValueChip.d.ts +11 -0
- package/dist/graph/special-nodes/EntityValueChip.d.ts.map +1 -0
- package/dist/graph/special-nodes/EntityValueChip.js +40 -0
- package/dist/graph/special-nodes/EntityValueChip.js.map +1 -0
- package/dist/graph/special-nodes/GenericView.d.ts +2 -1
- package/dist/graph/special-nodes/GenericView.d.ts.map +1 -1
- package/dist/graph/special-nodes/GenericView.js +23 -69
- package/dist/graph/special-nodes/GenericView.js.map +1 -1
- package/dist/graph/special-nodes/card-facts.d.ts +52 -0
- package/dist/graph/special-nodes/card-facts.d.ts.map +1 -0
- package/dist/graph/special-nodes/card-facts.js +189 -0
- package/dist/graph/special-nodes/card-facts.js.map +1 -0
- package/dist/graph/special-nodes/property-icons.d.ts +9 -0
- package/dist/graph/special-nodes/property-icons.d.ts.map +1 -0
- package/dist/graph/special-nodes/property-icons.js +39 -0
- package/dist/graph/special-nodes/property-icons.js.map +1 -0
- package/dist/graph/status-utils.d.ts +16 -0
- package/dist/graph/status-utils.d.ts.map +1 -1
- package/dist/graph/status-utils.js +35 -0
- package/dist/graph/status-utils.js.map +1 -1
- package/dist/graph/types.d.ts +5 -0
- package/dist/graph/types.d.ts.map +1 -1
- package/dist/hooks/use-approve-anyway.d.ts +12 -0
- package/dist/hooks/use-approve-anyway.d.ts.map +1 -0
- package/dist/hooks/use-approve-anyway.js +81 -0
- package/dist/hooks/use-approve-anyway.js.map +1 -0
- package/dist/hooks/use-connection-problems.d.ts.map +1 -1
- package/dist/hooks/useValidateActionPlan.d.ts.map +1 -1
- package/dist/ldo-ui.d.ts +2 -1
- package/dist/ldo-ui.d.ts.map +1 -1
- package/dist/ldo-ui.js +1 -0
- package/dist/ldo-ui.js.map +1 -1
- package/dist/lib/manual-actions.d.ts +2 -1
- package/dist/lib/manual-actions.d.ts.map +1 -1
- package/dist/lib/manual-actions.js +3 -2
- package/dist/lib/manual-actions.js.map +1 -1
- package/dist/providers/ActiveChatProvider.js +3 -1
- package/dist/providers/ActiveChatProvider.js.map +1 -1
- package/dist/providers/ChatManagerProvider.d.ts.map +1 -1
- package/dist/providers/ChatManagerProvider.js +3 -1
- package/dist/providers/ChatManagerProvider.js.map +1 -1
- package/dist/providers/chat-socket-auth.d.ts +7 -2
- package/dist/providers/chat-socket-auth.d.ts.map +1 -1
- package/dist/providers/chat-socket-auth.js +87 -16
- package/dist/providers/chat-socket-auth.js.map +1 -1
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js +8 -1
- package/dist/providers/index.js.map +1 -1
- package/dist/routes/activity/detail/ActivityChanges.d.ts +2 -0
- package/dist/routes/activity/detail/ActivityChanges.d.ts.map +1 -1
- package/dist/routes/activity/detail/ActivityChanges.js +8 -7
- package/dist/routes/activity/detail/ActivityChanges.js.map +1 -1
- package/dist/routes/activity/detail/ActivityDetail.d.ts +1 -0
- package/dist/routes/activity/detail/ActivityDetail.d.ts.map +1 -1
- package/dist/routes/activity/detail/ActivityDetail.js +5 -4
- package/dist/routes/activity/detail/ActivityDetail.js.map +1 -1
- package/dist/routes/activity/detail/ActivityStepsRail.d.ts +1 -0
- package/dist/routes/activity/detail/ActivityStepsRail.d.ts.map +1 -1
- package/dist/routes/activity/detail/ActivityStepsRail.js +3 -2
- package/dist/routes/activity/detail/ActivityStepsRail.js.map +1 -1
- package/dist/routes/activity/detail/ActivityTimeline.js +2 -2
- package/dist/routes/activity/detail/ActivityTimeline.js.map +1 -1
- package/dist/routes/activity/detail/NoChangesMessage.d.ts +6 -0
- package/dist/routes/activity/detail/NoChangesMessage.d.ts.map +1 -0
- package/dist/routes/activity/detail/NoChangesMessage.js +10 -0
- package/dist/routes/activity/detail/NoChangesMessage.js.map +1 -0
- package/dist/routes/data-explorer/components/RawTriples.js +1 -1
- package/dist/routes/data-explorer/components/RawTriples.js.map +1 -1
- package/dist/routes/data-explorer/components/RecentTypes.js +1 -1
- package/dist/routes/data-explorer/components/RecentTypes.js.map +1 -1
- package/dist/routes/data-explorer/components/class-detail/Instances.js +1 -1
- package/dist/routes/data-explorer/components/class-detail/Instances.js.map +1 -1
- package/dist/routes/data-explorer/components/namespace-detail/NamespaceClassRow.js +1 -1
- package/dist/routes/data-explorer/components/namespace-detail/NamespaceClassRow.js.map +1 -1
- package/dist/routes/data-explorer/components/ontology-coverage/CoverageTermColumns.js +1 -1
- package/dist/routes/data-explorer/components/ontology-coverage/CoverageTermColumns.js.map +1 -1
- package/dist/routes/data-explorer/components/ontology-coverage/UnobservedRuleVocabulary.js +1 -1
- package/dist/routes/data-explorer/components/ontology-coverage/UnobservedRuleVocabulary.js.map +1 -1
- package/dist/routes/data-explorer/components/record-detail/ChatPreview.js +2 -2
- package/dist/routes/data-explorer/components/record-detail/ChatPreview.js.map +1 -1
- package/dist/routes/integrations/components/connect-forms/DataConnectionConnectForm.d.ts.map +1 -1
- package/dist/routes/integrations/components/connect-forms/DataConnectionConnectForm.js +13 -6
- package/dist/routes/integrations/components/connect-forms/DataConnectionConnectForm.js.map +1 -1
- package/dist/routes/integrations/components/connect-forms/data-connection-fields.d.ts +3 -0
- package/dist/routes/integrations/components/connect-forms/data-connection-fields.d.ts.map +1 -1
- package/dist/routes/integrations/components/connect-forms/data-connection-fields.js +9 -2
- package/dist/routes/integrations/components/connect-forms/data-connection-fields.js.map +1 -1
- package/dist/routes/plans/components/ActionPlanPage.d.ts.map +1 -1
- package/dist/routes/plans/components/ActionPlanPage.js +4 -7
- package/dist/routes/plans/components/ActionPlanPage.js.map +1 -1
- package/dist/routes/plans/components/PlanDetailRouteContent.d.ts.map +1 -1
- package/dist/routes/plans/components/PlanDetailRouteContent.js +2 -1
- package/dist/routes/plans/components/PlanDetailRouteContent.js.map +1 -1
- package/package.json +12 -7
- package/src/app-subpaths.json +1 -0
- package/src/components/channel-icon/index.tsx +1 -1
- package/src/components/planning/ActioningView.tsx +62 -70
- package/src/components/planning/ManualActionCard.tsx +66 -14
- package/src/components/planning/ManualActionList.tsx +30 -11
- package/src/components/planning/actioning-view-utils.ts +27 -1
- package/src/components/tooltip/index.tsx +35 -2
- package/src/graph/entity-link.tsx +20 -0
- package/src/graph/graph-list-utils.ts +2 -0
- package/src/graph/graph-model.ts +15 -6
- package/src/graph/nodes/BrandAssetAnalysisView.tsx +3 -1
- package/src/graph/nodes/BrandFontSuggestionView.tsx +7 -1
- package/src/graph/nodes/BrandGuidelinesView.tsx +1 -1
- package/src/graph/nodes/NodePropertyTable.tsx +32 -143
- package/src/graph/nodes/node-property-row.tsx +154 -0
- package/src/graph/property-formatters.ts +5 -1
- package/src/graph/special-nodes/AttributeList.tsx +177 -0
- package/src/graph/special-nodes/EntityPreview.tsx +27 -0
- package/src/graph/special-nodes/EntityValueChip.tsx +85 -0
- package/src/graph/special-nodes/GenericView.tsx +49 -139
- package/src/graph/special-nodes/card-facts.ts +278 -0
- package/src/graph/special-nodes/property-icons.ts +59 -0
- package/src/graph/status-utils.ts +40 -0
- package/src/graph/types.ts +4 -0
- package/src/hooks/use-approve-anyway.ts +100 -0
- package/src/ldo-ui.ts +8 -0
- package/src/lib/manual-actions.ts +3 -2
- package/src/providers/ActiveChatProvider.tsx +3 -1
- package/src/providers/ChatManagerProvider.tsx +3 -1
- package/src/providers/chat-socket-auth.ts +95 -16
- package/src/providers/index.tsx +16 -1
- package/src/routes/activity/detail/ActivityChanges.tsx +21 -5
- package/src/routes/activity/detail/ActivityDetail.tsx +24 -5
- package/src/routes/activity/detail/ActivityStepsRail.tsx +4 -2
- package/src/routes/activity/detail/ActivityTimeline.tsx +2 -2
- package/src/routes/activity/detail/NoChangesMessage.tsx +20 -0
- package/src/routes/data-explorer/components/RawTriples.tsx +1 -1
- package/src/routes/data-explorer/components/RecentTypes.tsx +1 -1
- package/src/routes/data-explorer/components/class-detail/Instances.tsx +1 -1
- package/src/routes/data-explorer/components/namespace-detail/NamespaceClassRow.tsx +1 -1
- package/src/routes/data-explorer/components/ontology-coverage/CoverageTermColumns.tsx +1 -1
- package/src/routes/data-explorer/components/ontology-coverage/UnobservedRuleVocabulary.tsx +1 -1
- package/src/routes/data-explorer/components/record-detail/ChatPreview.tsx +2 -2
- package/src/routes/integrations/components/connect-forms/DataConnectionConnectForm.tsx +38 -12
- package/src/routes/integrations/components/connect-forms/data-connection-fields.ts +11 -2
- package/src/routes/plans/components/ActionPlanPage.tsx +9 -8
- package/src/routes/plans/components/PlanDetailRouteContent.tsx +4 -3
- package/dist/components/planning/ExecutionActivityRow.d.ts +0 -9
- package/dist/components/planning/ExecutionActivityRow.d.ts.map +0 -1
- package/dist/components/planning/ExecutionActivityRow.js +0 -53
- package/dist/components/planning/ExecutionActivityRow.js.map +0 -1
- package/dist/components/planning/ExecutionSectionItem.d.ts +0 -20
- package/dist/components/planning/ExecutionSectionItem.d.ts.map +0 -1
- package/dist/components/planning/ExecutionSectionItem.js +0 -66
- package/dist/components/planning/ExecutionSectionItem.js.map +0 -1
- package/dist/components/planning/ExecutionSections.d.ts +0 -16
- package/dist/components/planning/ExecutionSections.d.ts.map +0 -1
- package/dist/components/planning/ExecutionSections.js +0 -32
- package/dist/components/planning/ExecutionSections.js.map +0 -1
- package/dist/components/planning/action-plan-icons.d.ts +0 -3
- package/dist/components/planning/action-plan-icons.d.ts.map +0 -1
- package/dist/components/planning/action-plan-icons.js +0 -25
- package/dist/components/planning/action-plan-icons.js.map +0 -1
- package/dist/components/planning/execution-section-model.d.ts +0 -24
- package/dist/components/planning/execution-section-model.d.ts.map +0 -1
- package/dist/components/planning/execution-section-model.js +0 -150
- package/dist/components/planning/execution-section-model.js.map +0 -1
- package/dist/routes/data-explorer/components/TruncateTooltip.d.ts +0 -7
- package/dist/routes/data-explorer/components/TruncateTooltip.d.ts.map +0 -1
- package/dist/routes/data-explorer/components/TruncateTooltip.js +0 -9
- package/dist/routes/data-explorer/components/TruncateTooltip.js.map +0 -1
- package/src/components/planning/ExecutionActivityRow.tsx +0 -89
- package/src/components/planning/ExecutionSectionItem.tsx +0 -184
- package/src/components/planning/ExecutionSections.tsx +0 -97
- package/src/components/planning/action-plan-icons.tsx +0 -27
- package/src/components/planning/execution-section-model.ts +0 -190
- 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 {
|
|
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
|
-
| {
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
39
|
-
? {
|
|
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
|
-
[
|
|
120
|
+
[hasAccessToken, ticketQuery, datastoreQuery, shouldReconnectOnClose],
|
|
42
121
|
);
|
|
43
122
|
}
|
|
44
123
|
|
package/src/providers/index.tsx
CHANGED
|
@@ -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>
|
|
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 {
|
|
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
|
-
<
|
|
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 {
|
|
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
|
|
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
|
-
<
|
|
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 "
|
|
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 "
|
|
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)
|
|
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
|
-
|
|
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
|
-
<
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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: {
|
|
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
|
|