nucleus-core-ts 0.9.849 → 0.9.851
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/fe/components/IntegrationsPage/components/ConnectionForm.js +4 -1
- package/dist/fe/components/IntegrationsPage/components/ConnectionList.js +16 -2
- package/dist/fe/components/IntegrationsPage/components/ConnectionSettingsTab.js +15 -7
- package/dist/fe/components/IntegrationsPage/components/ConnectionTest.js +13 -2
- package/dist/fe/components/IntegrationsPage/components/EndpointList.js +16 -2
- package/dist/fe/components/IntegrationsPage/components/EndpointSample.js +11 -3
- package/dist/fe/components/IntegrationsPage/components/MappingList.js +16 -2
- package/dist/fe/components/IntegrationsPage/components/MappingsTab.js +4 -1
- package/dist/fe/components/IntegrationsPage/components/RunHistory.js +15 -2
- package/dist/fe/components/IntegrationsPage/components/RunPanel.js +19 -2
- package/dist/fe/components/IntegrationsPage/components/useInfiniteList.d.ts +13 -0
- package/dist/fe/components/IntegrationsPage/components/useInfiniteList.js +7 -0
- package/dist/fe/components/IntegrationsPage/store/index.js +21 -3
- package/dist/fe/components/IntegrationsPage/store/state.d.ts +14 -1
- package/dist/fe/components/IntegrationsPage/store/state.js +1 -0
- package/package.json +1 -1
|
@@ -54,11 +54,14 @@ export function ConnectionForm({ actions, source, loading = false, onSaved }) {
|
|
|
54
54
|
store.setError('The connection was not saved. The server did not return the record.');
|
|
55
55
|
return;
|
|
56
56
|
}
|
|
57
|
+
// Told about the record BEFORE it is selected. The caller is what puts
|
|
58
|
+
// it into the list, and selecting an id the list does not hold is the
|
|
59
|
+
// state every card below renders a skeleton for.
|
|
60
|
+
onSaved?.(saved);
|
|
57
61
|
// Selecting resets everything hanging off a connection, so it is only
|
|
58
62
|
// right for a NEW one — doing it on an edit would empty the endpoint,
|
|
59
63
|
// mapping and run lists the operator is working in.
|
|
60
64
|
if (!source) store.selectSource(saved.id);
|
|
61
|
-
onSaved?.(saved);
|
|
62
65
|
}).catch((reason)=>{
|
|
63
66
|
setSaving(false);
|
|
64
67
|
store.setError(errorText(reason, 'The connection could not be saved.'));
|
|
@@ -59,17 +59,31 @@ export function ConnectionList({ actions, onNewConnection, reloadToken = 0 }) {
|
|
|
59
59
|
const publishLoading = useEffectEvent(()=>{
|
|
60
60
|
store.setSourcesLoading(list.loading);
|
|
61
61
|
});
|
|
62
|
+
// Only what the SERVER said. A reset empties the hook's items before the
|
|
63
|
+
// first page is asked for, and publishing that emptied the shared store —
|
|
64
|
+
// which is where the editor looks for the record it is editing. Saving
|
|
65
|
+
// triggers a reset, so saving is when the record vanished.
|
|
62
66
|
useEffect(()=>{
|
|
63
|
-
publishItems();
|
|
67
|
+
if (list.settled) publishItems();
|
|
64
68
|
}, [
|
|
65
69
|
list.items,
|
|
66
|
-
list.total
|
|
70
|
+
list.total,
|
|
71
|
+
list.settled
|
|
67
72
|
]);
|
|
68
73
|
useEffect(()=>{
|
|
69
74
|
publishLoading();
|
|
70
75
|
}, [
|
|
71
76
|
list.loading
|
|
72
77
|
]);
|
|
78
|
+
// Handed back on the way out. This flag has exactly one writer — this
|
|
79
|
+
// component — and unmounting mid-request left it true with nobody able to
|
|
80
|
+
// clear it, so whatever else reads it showed a skeleton for a request that
|
|
81
|
+
// had already finished.
|
|
82
|
+
useEffect(()=>{
|
|
83
|
+
return ()=>{
|
|
84
|
+
store.setSourcesLoading(false);
|
|
85
|
+
};
|
|
86
|
+
}, []);
|
|
73
87
|
const handleNew = ()=>{
|
|
74
88
|
store.selectSource(null);
|
|
75
89
|
onNewConnection?.();
|
|
@@ -22,27 +22,35 @@ import { CredentialFields } from './CredentialFields';
|
|
|
22
22
|
*/ export function ConnectionSettingsTab({ actions, onSaved, onDeleted }) {
|
|
23
23
|
const store = useIntegrationsStore();
|
|
24
24
|
const source = store.sources.find((candidate)=>candidate.id === store.selectedSourceId) ?? null;
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Waiting for THIS record, and nothing else.
|
|
27
|
+
*
|
|
28
|
+
* It used to be `store.sourcesLoading || missingFromPage`, and sourcesLoading
|
|
29
|
+
* is true whenever the rail is fetching ANY page — including a page fetched
|
|
30
|
+
* because somebody typed a letter into the search box. With no connection
|
|
31
|
+
* chosen this tab is the blank "new connection" form, and every keystroke in
|
|
32
|
+
* the rail beside it replaced that form with skeletons.
|
|
33
|
+
*
|
|
34
|
+
* A form that is not editing a record is never loading: there is nothing to
|
|
35
|
+
* wait for. A form that IS editing one waits only until that record is here.
|
|
36
|
+
*/ const waitingForRecord = store.selectedSourceId !== null && source === null;
|
|
29
37
|
return /*#__PURE__*/ _jsxs("div", {
|
|
30
38
|
className: theme.layout.main,
|
|
31
39
|
children: [
|
|
32
40
|
/*#__PURE__*/ _jsx(ConnectionForm, {
|
|
33
41
|
actions: actions,
|
|
34
|
-
loading:
|
|
42
|
+
loading: waitingForRecord,
|
|
35
43
|
onSaved: onSaved,
|
|
36
44
|
source: source
|
|
37
45
|
}),
|
|
38
46
|
/*#__PURE__*/ _jsx(CredentialFields, {
|
|
39
47
|
actions: actions,
|
|
40
|
-
loading:
|
|
48
|
+
loading: waitingForRecord,
|
|
41
49
|
source: source
|
|
42
50
|
}),
|
|
43
51
|
/*#__PURE__*/ _jsx(ConnectionTest, {
|
|
44
52
|
actions: actions,
|
|
45
|
-
loading:
|
|
53
|
+
loading: waitingForRecord,
|
|
46
54
|
source: source
|
|
47
55
|
}),
|
|
48
56
|
/*#__PURE__*/ _jsx(ConnectionDelete, {
|
|
@@ -25,10 +25,21 @@ export function ConnectionTest({ actions, source, loading = false }) {
|
|
|
25
25
|
setResult(null);
|
|
26
26
|
setTesting(false);
|
|
27
27
|
});
|
|
28
|
-
|
|
28
|
+
/**
|
|
29
|
+
* A verdict describes the settings it was given, not the connection's name.
|
|
30
|
+
*
|
|
31
|
+
* Keyed on the id alone, "Reachable" survived editing the base URL and saving:
|
|
32
|
+
* the record object is replaced but its id is not, so this never re-ran and a
|
|
33
|
+
* green badge sat under an address that had never been called. Everything the
|
|
34
|
+
* test actually depends on is in the key.
|
|
35
|
+
*/ useEffect(()=>{
|
|
29
36
|
clear();
|
|
30
37
|
}, [
|
|
31
|
-
source?.id
|
|
38
|
+
source?.id,
|
|
39
|
+
source?.baseUrl,
|
|
40
|
+
source?.authType,
|
|
41
|
+
source?.tokenUrl,
|
|
42
|
+
source?.tlsVerify
|
|
32
43
|
]);
|
|
33
44
|
const handleTest = ()=>{
|
|
34
45
|
if (!source) return;
|
|
@@ -72,17 +72,31 @@ export function EndpointList({ sourceId, actions, onNewEndpoint, onSelect, reloa
|
|
|
72
72
|
const publishLoading = useEffectEvent(()=>{
|
|
73
73
|
store.setEndpointsLoading(list.loading);
|
|
74
74
|
});
|
|
75
|
+
// Only what the SERVER said. A reset empties the hook's items before the
|
|
76
|
+
// first page is asked for, and publishing that emptied the shared store —
|
|
77
|
+
// which is where the editor looks for the record it is editing. Saving
|
|
78
|
+
// triggers a reset, so saving is when the record vanished.
|
|
75
79
|
useEffect(()=>{
|
|
76
|
-
publishItems();
|
|
80
|
+
if (list.settled) publishItems();
|
|
77
81
|
}, [
|
|
78
82
|
list.items,
|
|
79
|
-
list.total
|
|
83
|
+
list.total,
|
|
84
|
+
list.settled
|
|
80
85
|
]);
|
|
81
86
|
useEffect(()=>{
|
|
82
87
|
publishLoading();
|
|
83
88
|
}, [
|
|
84
89
|
list.loading
|
|
85
90
|
]);
|
|
91
|
+
// Handed back on the way out. This flag has exactly one writer — this
|
|
92
|
+
// component — and unmounting mid-request left it true with nobody able to
|
|
93
|
+
// clear it, so whatever else reads it showed a skeleton for a request that
|
|
94
|
+
// had already finished.
|
|
95
|
+
useEffect(()=>{
|
|
96
|
+
return ()=>{
|
|
97
|
+
store.setEndpointsLoading(false);
|
|
98
|
+
};
|
|
99
|
+
}, []);
|
|
86
100
|
const handleNew = ()=>{
|
|
87
101
|
store.selectEndpoint(null);
|
|
88
102
|
onNewEndpoint?.();
|
|
@@ -26,7 +26,15 @@ export function EndpointSample({ endpoint, actions }) {
|
|
|
26
26
|
const selectedRef = useRef(store.selectedEndpointId);
|
|
27
27
|
selectedRef.current = store.selectedEndpointId;
|
|
28
28
|
const params = pathParamNames(endpoint.path);
|
|
29
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Only a sample taken of THIS call.
|
|
31
|
+
*
|
|
32
|
+
* The card is keyed per selection, so switching calls removes it and a request
|
|
33
|
+
* still in flight settles with nowhere to report to — except the shared slot,
|
|
34
|
+
* which it writes to regardless. Before the tag, that answer appeared under
|
|
35
|
+
* whichever call was open when it landed, and the mapping built next took its
|
|
36
|
+
* field list from a call that had never returned those fields.
|
|
37
|
+
*/ const sample = store.sampleEndpointId === endpoint.id ? store.sample : null;
|
|
30
38
|
let unfilled = 0;
|
|
31
39
|
for (const name of params){
|
|
32
40
|
if (!(values[name] ?? '').trim()) unfilled += 1;
|
|
@@ -60,11 +68,11 @@ export function EndpointSample({ endpoint, actions }) {
|
|
|
60
68
|
store.setError(null);
|
|
61
69
|
actions.sampleEndpoint(requestedFor, params.length > 0 ? values : undefined).then((result)=>{
|
|
62
70
|
if (settleElsewhere(mine, requestedFor)) return;
|
|
63
|
-
store.setSample(result);
|
|
71
|
+
store.setSample(result, requestedFor);
|
|
64
72
|
if (!result.ok && result.error) store.setError(result.error);
|
|
65
73
|
}).catch((error)=>{
|
|
66
74
|
if (settleElsewhere(mine, requestedFor)) return;
|
|
67
|
-
store.setSample(null);
|
|
75
|
+
store.setSample(null, requestedFor);
|
|
68
76
|
store.setError(error instanceof Error ? error.message : 'Sampling the endpoint failed.');
|
|
69
77
|
});
|
|
70
78
|
};
|
|
@@ -83,17 +83,31 @@ export function MappingList({ actions, sourceId, selectedId, reloadToken, onSele
|
|
|
83
83
|
const publishLoading = useEffectEvent(()=>{
|
|
84
84
|
store.setMappingsLoading(list.loading);
|
|
85
85
|
});
|
|
86
|
+
// Only what the SERVER said. A reset empties the hook's items before the
|
|
87
|
+
// first page is asked for, and publishing that emptied the shared store —
|
|
88
|
+
// which is where the editor looks for the record it is editing. Saving
|
|
89
|
+
// triggers a reset, so saving is when the record vanished.
|
|
86
90
|
useEffect(()=>{
|
|
87
|
-
publishItems();
|
|
91
|
+
if (list.settled) publishItems();
|
|
88
92
|
}, [
|
|
89
93
|
list.items,
|
|
90
|
-
list.total
|
|
94
|
+
list.total,
|
|
95
|
+
list.settled
|
|
91
96
|
]);
|
|
92
97
|
useEffect(()=>{
|
|
93
98
|
publishLoading();
|
|
94
99
|
}, [
|
|
95
100
|
list.loading
|
|
96
101
|
]);
|
|
102
|
+
// Handed back on the way out. This flag has exactly one writer — this
|
|
103
|
+
// component — and unmounting mid-request left it true with nobody able to
|
|
104
|
+
// clear it, so whatever else reads it showed a skeleton for a request that
|
|
105
|
+
// had already finished.
|
|
106
|
+
useEffect(()=>{
|
|
107
|
+
return ()=>{
|
|
108
|
+
store.setMappingsLoading(false);
|
|
109
|
+
};
|
|
110
|
+
}, []);
|
|
97
111
|
const showSkeleton = list.loading && list.items.length === 0;
|
|
98
112
|
return /*#__PURE__*/ _jsxs("section", {
|
|
99
113
|
className: "flex flex-col gap-3",
|
|
@@ -196,7 +196,10 @@ export function MappingsTab({ actions }) {
|
|
|
196
196
|
// to and the editor matches it against the mapping's own choice. The editor
|
|
197
197
|
// falls back to the endpoint's STORED sample — which is why the sample is
|
|
198
198
|
// persisted at all, and what a connection revisited tomorrow has.
|
|
199
|
-
const freshSample =
|
|
199
|
+
const freshSample = // The tag must match: a field list offered here becomes the mapping, and a
|
|
200
|
+
// sample left over from another call would name fields the chosen one does
|
|
201
|
+
// not return.
|
|
202
|
+
store.selectedEndpointId && store.sample && store.sampleEndpointId === store.selectedEndpointId ? {
|
|
200
203
|
endpointId: store.selectedEndpointId,
|
|
201
204
|
fields: store.sample.fields ?? []
|
|
202
205
|
} : null;
|
|
@@ -51,17 +51,30 @@ export function RunHistory({ actions, mappingId, refreshKey, onRefresh }) {
|
|
|
51
51
|
const publishLoading = useEffectEvent(()=>{
|
|
52
52
|
store.setRunsLoading(list.loading);
|
|
53
53
|
});
|
|
54
|
+
// Only what the SERVER said — see the same guard on the other three lists: a
|
|
55
|
+
// reset empties the hook's items before the first page is asked for, and
|
|
56
|
+
// publishing that emptied the shared store.
|
|
54
57
|
useEffect(()=>{
|
|
55
|
-
publishRuns();
|
|
58
|
+
if (list.settled) publishRuns();
|
|
56
59
|
}, [
|
|
57
60
|
list.items,
|
|
58
|
-
list.total
|
|
61
|
+
list.total,
|
|
62
|
+
list.settled
|
|
59
63
|
]);
|
|
60
64
|
useEffect(()=>{
|
|
61
65
|
publishLoading();
|
|
62
66
|
}, [
|
|
63
67
|
list.loading
|
|
64
68
|
]);
|
|
69
|
+
// Handed back on the way out. This flag has exactly one writer — this
|
|
70
|
+
// component — and unmounting mid-request left it true with nobody able to
|
|
71
|
+
// clear it, so whatever else reads it showed a skeleton for a request that
|
|
72
|
+
// had already finished.
|
|
73
|
+
useEffect(()=>{
|
|
74
|
+
return ()=>{
|
|
75
|
+
store.setRunsLoading(false);
|
|
76
|
+
};
|
|
77
|
+
}, []);
|
|
65
78
|
const firstPagePending = list.loading && list.items.length === 0;
|
|
66
79
|
return /*#__PURE__*/ _jsxs("section", {
|
|
67
80
|
className: theme.card.wrapper,
|
|
@@ -66,11 +66,16 @@ export function RunPanel({ mapping, actions, onSubscribeProgress, onRunSettled }
|
|
|
66
66
|
setConfirming(true);
|
|
67
67
|
return;
|
|
68
68
|
}
|
|
69
|
+
// A refusal ENDS the confirmation. Left standing, the panel kept asking
|
|
70
|
+
// "are you sure?" about an import the server had already turned down,
|
|
71
|
+
// and pressing the confirm button again just collected the same refusal.
|
|
69
72
|
if (result.error) {
|
|
73
|
+
setConfirming(false);
|
|
70
74
|
store.setError(result.error);
|
|
71
75
|
return;
|
|
72
76
|
}
|
|
73
77
|
if (!result.runId) {
|
|
78
|
+
setConfirming(false);
|
|
74
79
|
store.setError('The import was accepted but no run id came back, so it cannot be tracked.');
|
|
75
80
|
return;
|
|
76
81
|
}
|
|
@@ -80,6 +85,7 @@ export function RunPanel({ mapping, actions, onSubscribeProgress, onRunSettled }
|
|
|
80
85
|
store.setActiveRun(result.runId);
|
|
81
86
|
}).catch((error)=>{
|
|
82
87
|
setStarting(false);
|
|
88
|
+
setConfirming(false);
|
|
83
89
|
store.setError(errorText(error, 'The import could not be started.'));
|
|
84
90
|
});
|
|
85
91
|
};
|
|
@@ -120,10 +126,21 @@ export function RunPanel({ mapping, actions, onSubscribeProgress, onRunSettled }
|
|
|
120
126
|
*/ const pollActiveRun = useEffectEvent(()=>{
|
|
121
127
|
const runId = store.activeRunId;
|
|
122
128
|
if (!runId) return;
|
|
123
|
-
actions.
|
|
129
|
+
actions// NEWEST first, explicitly. Without a sort the server answers in its own
|
|
130
|
+
// order — which is the order the rows were written — so from the sixth
|
|
131
|
+
// import of a mapping onwards the run being watched was not among the
|
|
132
|
+
// five that came back. The tick below then found nothing, returned, and
|
|
133
|
+
// left the progress bar running for an import that had already finished.
|
|
134
|
+
.listRuns({
|
|
124
135
|
mappingId: mapping.id,
|
|
125
136
|
page: 1,
|
|
126
|
-
limit: 5
|
|
137
|
+
limit: 5,
|
|
138
|
+
sort: [
|
|
139
|
+
{
|
|
140
|
+
field: 'createdAt',
|
|
141
|
+
direction: 'desc'
|
|
142
|
+
}
|
|
143
|
+
]
|
|
127
144
|
}).then((result)=>{
|
|
128
145
|
if (store.activeRunId !== runId) return;
|
|
129
146
|
const run = result.items.find((item)=>item.id === runId);
|
|
@@ -33,6 +33,19 @@ export type UseInfiniteListResult<T> = {
|
|
|
33
33
|
* hook reports the failure and the caller must show it.
|
|
34
34
|
*/
|
|
35
35
|
error: string | null;
|
|
36
|
+
/**
|
|
37
|
+
* Whether what `items` holds has actually come back from the server.
|
|
38
|
+
*
|
|
39
|
+
* A reset empties `items` BEFORE the first page is asked for, and callers
|
|
40
|
+
* mirror `items` into the shared store — so every refetch published an empty
|
|
41
|
+
* page, and whatever was reading that store lost the record it was showing.
|
|
42
|
+
* Saving is what triggers a refetch, which is why saving is when it was seen:
|
|
43
|
+
* the editor asked the store for the record it had just saved and the store
|
|
44
|
+
* had been emptied a moment earlier.
|
|
45
|
+
*
|
|
46
|
+
* False from a reset until the first page settles, success or failure.
|
|
47
|
+
*/
|
|
48
|
+
settled: boolean;
|
|
36
49
|
/** Put this on an element after the last row; seeing it asks for the next page. */
|
|
37
50
|
sentinelRef: RefObject<HTMLDivElement | null>;
|
|
38
51
|
reload: () => void;
|
|
@@ -17,6 +17,7 @@ export function useInfiniteList(options) {
|
|
|
17
17
|
const [loading, setLoading] = useState(true);
|
|
18
18
|
const [error, setError] = useState(null);
|
|
19
19
|
const [reloadToken, setReloadToken] = useState(0);
|
|
20
|
+
const [settled, setSettled] = useState(false);
|
|
20
21
|
const sentinelRef = useRef(null);
|
|
21
22
|
const nextPageRef = useRef(1);
|
|
22
23
|
const loadedCountRef = useRef(0);
|
|
@@ -32,6 +33,7 @@ export function useInfiniteList(options) {
|
|
|
32
33
|
if (generation !== generationRef.current) return;
|
|
33
34
|
inFlightRef.current = false;
|
|
34
35
|
setLoading(false);
|
|
36
|
+
setSettled(true);
|
|
35
37
|
setTotal(result.total);
|
|
36
38
|
setItems((previous)=>page === 1 ? result.items : [
|
|
37
39
|
...previous,
|
|
@@ -47,6 +49,9 @@ export function useInfiniteList(options) {
|
|
|
47
49
|
if (generation !== generationRef.current) return;
|
|
48
50
|
inFlightRef.current = false;
|
|
49
51
|
setLoading(false);
|
|
52
|
+
// Settled, even though it failed: the caller must be free to show the
|
|
53
|
+
// error instead of waiting on a page that is never coming.
|
|
54
|
+
setSettled(true);
|
|
50
55
|
setError(reason instanceof Error ? reason.message : String(reason));
|
|
51
56
|
// Stop asking. The sentinel stays on screen, so retrying on failure
|
|
52
57
|
// would hammer a broken endpoint for as long as the panel is open;
|
|
@@ -67,6 +72,7 @@ export function useInfiniteList(options) {
|
|
|
67
72
|
setItems([]);
|
|
68
73
|
setTotal(0);
|
|
69
74
|
setLoading(true);
|
|
75
|
+
setSettled(false);
|
|
70
76
|
setError(null);
|
|
71
77
|
fetchPage(1);
|
|
72
78
|
return ()=>{
|
|
@@ -103,6 +109,7 @@ export function useInfiniteList(options) {
|
|
|
103
109
|
items,
|
|
104
110
|
total,
|
|
105
111
|
loading,
|
|
112
|
+
settled,
|
|
106
113
|
error,
|
|
107
114
|
sentinelRef,
|
|
108
115
|
reload
|
|
@@ -39,13 +39,25 @@ export const { useStore: useIntegrationsStore } = createStore(initial, {
|
|
|
39
39
|
store.tab = 'connection';
|
|
40
40
|
store.endpoints = [];
|
|
41
41
|
store.endpointsTotal = 0;
|
|
42
|
-
store.endpointsPage = 1;
|
|
43
42
|
store.endpointsSearch = '';
|
|
44
43
|
store.selectedEndpointId = null;
|
|
45
44
|
store.mappings = [];
|
|
46
45
|
store.mappingsTotal = 0;
|
|
47
|
-
store.mappingsPage = 1;
|
|
48
46
|
store.mappingsSearch = '';
|
|
47
|
+
// Every WAIT below a connection ends with it too. These are the flags the
|
|
48
|
+
// cards read to decide between a skeleton and their content, and their
|
|
49
|
+
// only writers live under the connection being left — so leaving one set
|
|
50
|
+
// meant the next connection opened onto a skeleton with nothing running.
|
|
51
|
+
store.endpointsLoading = false;
|
|
52
|
+
store.mappingsLoading = false;
|
|
53
|
+
store.runsLoading = false;
|
|
54
|
+
store.sample = null;
|
|
55
|
+
store.sampleEndpointId = null;
|
|
56
|
+
store.sampling = false;
|
|
57
|
+
store.plan = null;
|
|
58
|
+
store.planning = false;
|
|
59
|
+
store.activeRunId = null;
|
|
60
|
+
store.progress = null;
|
|
49
61
|
store.selectedMappingId = null;
|
|
50
62
|
store.runs = [];
|
|
51
63
|
store.runsTotal = 0;
|
|
@@ -89,6 +101,7 @@ export const { useStore: useIntegrationsStore } = createStore(initial, {
|
|
|
89
101
|
// The sample describes ONE endpoint; carrying it over would offer the
|
|
90
102
|
// wrong field list to the next mapping.
|
|
91
103
|
store.sample = null;
|
|
104
|
+
store.sampleEndpointId = null;
|
|
92
105
|
// And the WAIT for a sample belongs to the endpoint that was left, not to
|
|
93
106
|
// the one arrived at. Clearing the record without clearing the flag left
|
|
94
107
|
// the next endpoint showing a skeleton for a request that was never going
|
|
@@ -127,6 +140,10 @@ export const { useStore: useIntegrationsStore } = createStore(initial, {
|
|
|
127
140
|
// A plan belongs to the mapping it was computed for. Showing it against a
|
|
128
141
|
// different one would have an operator approve counts from another import.
|
|
129
142
|
store.plan = null;
|
|
143
|
+
// And the WAIT for a plan goes with it. Discarding the plan while leaving
|
|
144
|
+
// this set put the next mapping's panel on a counting skeleton for a
|
|
145
|
+
// preview that had been computed for a different mapping and thrown away.
|
|
146
|
+
store.planning = false;
|
|
130
147
|
// And so does a RUN, which says something stronger than a plan does: that
|
|
131
148
|
// this import happened. Seen live — the Ünvanlar run stayed on screen
|
|
132
149
|
// under a header reading "Writes into locations", for a mapping that had
|
|
@@ -142,8 +159,9 @@ export const { useStore: useIntegrationsStore } = createStore(initial, {
|
|
|
142
159
|
setRunsLoading: (store)=>(loading)=>{
|
|
143
160
|
store.runsLoading = loading;
|
|
144
161
|
},
|
|
145
|
-
setSample: (store)=>(sample)=>batch(()=>{
|
|
162
|
+
setSample: (store)=>(sample, endpointId)=>batch(()=>{
|
|
146
163
|
store.sample = sample;
|
|
164
|
+
store.sampleEndpointId = sample ? endpointId : null;
|
|
147
165
|
store.sampling = false;
|
|
148
166
|
}),
|
|
149
167
|
setSampling: (store)=>(sampling)=>{
|
|
@@ -45,6 +45,19 @@ export type State = {
|
|
|
45
45
|
runsLoading: boolean;
|
|
46
46
|
/** The real shape of the selected endpoint's response, once sampled. */
|
|
47
47
|
sample: EndpointSampleValue | null;
|
|
48
|
+
/**
|
|
49
|
+
* The call the sample in hand describes.
|
|
50
|
+
*
|
|
51
|
+
* A sample is a field list, and a field list is what the next mapping is built
|
|
52
|
+
* from — so one written against the wrong call produces a mapping of fields
|
|
53
|
+
* that call never returns. The card is keyed per selection and therefore
|
|
54
|
+
* REMOVED when the operator clicks another call, which means a request still
|
|
55
|
+
* in flight settles into a component that no longer exists and writes to this
|
|
56
|
+
* shared slot anyway. Tagging is what makes that write harmless: it lands
|
|
57
|
+
* under the call it was asked for, and whoever is showing a different one
|
|
58
|
+
* simply does not read it.
|
|
59
|
+
*/
|
|
60
|
+
sampleEndpointId: string | null;
|
|
48
61
|
sampling: boolean;
|
|
49
62
|
/** What a run would do. Cleared whenever the mapping changes underneath it. */
|
|
50
63
|
plan: RunPlanValue | null;
|
|
@@ -114,7 +127,7 @@ export type Actions = {
|
|
|
114
127
|
selectMapping: (id: string | null) => void;
|
|
115
128
|
setRuns: (items: IntegrationRun[], total: number) => void;
|
|
116
129
|
setRunsLoading: (loading: boolean) => void;
|
|
117
|
-
setSample: (sample: EndpointSampleValue | null) => void;
|
|
130
|
+
setSample: (sample: EndpointSampleValue | null, endpointId: string | null) => void;
|
|
118
131
|
setSampling: (sampling: boolean) => void;
|
|
119
132
|
setPlan: (plan: RunPlanValue | null) => void;
|
|
120
133
|
setPlanning: (planning: boolean) => void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nucleus-core-ts",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.851",
|
|
4
4
|
"description": "Production-ready, enterprise-grade TypeScript framework for building multi-tenant APIs",
|
|
5
5
|
"author": "Hidayet Can Özcan <hidayetcan@gmail.com>",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|