nucleus-core-ts 0.9.845 → 0.9.847
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/ConnectionDelete.js +14 -21
- package/dist/fe/components/IntegrationsPage/components/ConnectionTest.js +2 -6
- package/dist/fe/components/IntegrationsPage/components/IntegrationsPage.js +6 -1
- package/dist/fe/components/IntegrationsPage/components/MappingEditor.js +2 -2
- package/dist/fe/components/IntegrationsPage/components/ParamBindingRows.js +2 -5
- package/dist/fe/components/IntegrationsPage/components/RunConfirmStep.js +3 -1
- package/dist/fe/components/IntegrationsPage/components/RunProgressBar.js +3 -1
- package/dist/fe/components/IntegrationsPage/store/index.js +16 -18
- package/dist/fe/components/IntegrationsPage/store/state.d.ts +12 -10
- package/dist/fe/components/IntegrationsPage/store/state.js +0 -4
- package/dist/fe/components/IntegrationsPage/store/upsertInto.d.ts +20 -0
- package/dist/fe/components/IntegrationsPage/store/upsertInto.js +21 -0
- package/dist/fe/components/IntegrationsPage/store/upsertInto.test.d.ts +1 -0
- package/dist/fe/components/IntegrationsPage/store/upsertInto.test.js +67 -0
- package/dist/fe/components/IntegrationsPage/text/index.d.ts +9 -0
- package/dist/fe/components/IntegrationsPage/text/index.js +10 -1
- package/package.json +1 -1
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useState } from 'react';
|
|
4
4
|
import { useIntegrationsStore } from '../store';
|
|
5
|
+
import { useLabels } from '../text/context';
|
|
5
6
|
import { integrationsPageTheme as theme } from '../theme';
|
|
6
7
|
import { deleteIsConfirmed } from './deleteIsConfirmed';
|
|
7
8
|
import { Notice } from './Primitives';
|
|
8
9
|
export function ConnectionDelete({ actions, source, onDeleted }) {
|
|
10
|
+
const labels = useLabels();
|
|
9
11
|
const store = useIntegrationsStore();
|
|
10
12
|
const [asking, setAsking] = useState(false);
|
|
11
13
|
const [typed, setTyped] = useState('');
|
|
@@ -18,11 +20,12 @@ export function ConnectionDelete({ actions, source, onDeleted }) {
|
|
|
18
20
|
actions.deleteSource(source.id).then((done)=>{
|
|
19
21
|
setBusy(false);
|
|
20
22
|
if (!done) {
|
|
21
|
-
store.setError(
|
|
23
|
+
store.setError(labels.connections.deleteFailed);
|
|
22
24
|
return;
|
|
23
25
|
}
|
|
24
26
|
setAsking(false);
|
|
25
27
|
setTyped('');
|
|
28
|
+
store.removeSource(source.id);
|
|
26
29
|
store.selectSource(null);
|
|
27
30
|
onDeleted?.();
|
|
28
31
|
}).catch((error)=>{
|
|
@@ -38,13 +41,13 @@ export function ConnectionDelete({ actions, source, onDeleted }) {
|
|
|
38
41
|
children: [
|
|
39
42
|
/*#__PURE__*/ _jsx("h3", {
|
|
40
43
|
className: theme.card.title,
|
|
41
|
-
children:
|
|
44
|
+
children: labels.connections.deleteHeading
|
|
42
45
|
}),
|
|
43
46
|
asking ? null : /*#__PURE__*/ _jsx("button", {
|
|
44
47
|
className: theme.button.danger,
|
|
45
48
|
onClick: ()=>setAsking(true),
|
|
46
49
|
type: "button",
|
|
47
|
-
children:
|
|
50
|
+
children: labels.connections.deleteStart
|
|
48
51
|
})
|
|
49
52
|
]
|
|
50
53
|
}),
|
|
@@ -52,27 +55,17 @@ export function ConnectionDelete({ actions, source, onDeleted }) {
|
|
|
52
55
|
className: theme.card.body,
|
|
53
56
|
children: asking ? /*#__PURE__*/ _jsxs(_Fragment, {
|
|
54
57
|
children: [
|
|
55
|
-
/*#__PURE__*/
|
|
56
|
-
title:
|
|
58
|
+
/*#__PURE__*/ _jsx(Notice, {
|
|
59
|
+
title: labels.connections.deleteWarnTitle,
|
|
57
60
|
tone: "danger",
|
|
58
|
-
children:
|
|
59
|
-
"Everything configured under “",
|
|
60
|
-
source.name,
|
|
61
|
-
"” goes: the calls, the mappings and the record of what they imported. The rows already written into your tables stay where they are — but nothing will be able to undo the imports that put them there."
|
|
62
|
-
]
|
|
61
|
+
children: labels.connections.deleteWarnBody(source.name)
|
|
63
62
|
}),
|
|
64
63
|
/*#__PURE__*/ _jsxs("label", {
|
|
65
64
|
className: theme.field.wrapper,
|
|
66
65
|
children: [
|
|
67
|
-
/*#__PURE__*/
|
|
66
|
+
/*#__PURE__*/ _jsx("span", {
|
|
68
67
|
className: theme.field.label,
|
|
69
|
-
children:
|
|
70
|
-
"Type ",
|
|
71
|
-
/*#__PURE__*/ _jsx("strong", {
|
|
72
|
-
children: source.name
|
|
73
|
-
}),
|
|
74
|
-
" to confirm"
|
|
75
|
-
]
|
|
68
|
+
children: labels.connections.deleteTypeToConfirm
|
|
76
69
|
}),
|
|
77
70
|
/*#__PURE__*/ _jsx("input", {
|
|
78
71
|
className: theme.field.input,
|
|
@@ -94,21 +87,21 @@ export function ConnectionDelete({ actions, source, onDeleted }) {
|
|
|
94
87
|
setTyped('');
|
|
95
88
|
},
|
|
96
89
|
type: "button",
|
|
97
|
-
children:
|
|
90
|
+
children: labels.common.cancel
|
|
98
91
|
}),
|
|
99
92
|
/*#__PURE__*/ _jsx("button", {
|
|
100
93
|
className: theme.button.danger,
|
|
101
94
|
disabled: busy || !confirmed,
|
|
102
95
|
onClick: remove,
|
|
103
96
|
type: "button",
|
|
104
|
-
children: busy ?
|
|
97
|
+
children: busy ? labels.common.deleting : labels.connections.deleteConfirm
|
|
105
98
|
})
|
|
106
99
|
]
|
|
107
100
|
})
|
|
108
101
|
]
|
|
109
102
|
}) : /*#__PURE__*/ _jsx("p", {
|
|
110
103
|
className: theme.field.hint,
|
|
111
|
-
children:
|
|
104
|
+
children: labels.connections.deleteHint
|
|
112
105
|
})
|
|
113
106
|
})
|
|
114
107
|
]
|
|
@@ -66,13 +66,9 @@ export function ConnectionTest({ actions, source, loading = false }) {
|
|
|
66
66
|
/*#__PURE__*/ _jsxs("div", {
|
|
67
67
|
className: theme.card.body,
|
|
68
68
|
children: [
|
|
69
|
-
source ? /*#__PURE__*/
|
|
69
|
+
source ? /*#__PURE__*/ _jsx("p", {
|
|
70
70
|
className: theme.field.hint,
|
|
71
|
-
children:
|
|
72
|
-
"Calls ",
|
|
73
|
-
source.baseUrl,
|
|
74
|
-
" with the settings and credentials as they are stored right now."
|
|
75
|
-
]
|
|
71
|
+
children: labels.auth.testCalls(source.baseUrl)
|
|
76
72
|
}) : /*#__PURE__*/ _jsx("p", {
|
|
77
73
|
className: theme.field.hint,
|
|
78
74
|
children: labels.auth.testSaveFirst
|
|
@@ -43,7 +43,12 @@ export function IntegrationsPage({ title = 'Integrations', subtitle = 'Connect a
|
|
|
43
43
|
* detail treated the missing record as still loading and sat on skeletons
|
|
44
44
|
* until the page was reloaded by hand. The write itself had succeeded.
|
|
45
45
|
*/ const [sourcesReloadToken, setSourcesReloadToken] = useState(0);
|
|
46
|
-
const handleSourceSaved = ()=>{
|
|
46
|
+
const handleSourceSaved = (saved)=>{
|
|
47
|
+
// Into the store FIRST. Bumping the token only tells the rail to refetch,
|
|
48
|
+
// and choosing a connection unmounts the rail — so on a NEW connection the
|
|
49
|
+
// token reached nobody and the detail below could not find the record it
|
|
50
|
+
// had just been switched to.
|
|
51
|
+
store.upsertSource(saved);
|
|
47
52
|
setSourcesReloadToken((token)=>token + 1);
|
|
48
53
|
};
|
|
49
54
|
/**
|
|
@@ -261,7 +261,7 @@ export function MappingEditor({ mapping, targets, targetsLoading, endpoints, fre
|
|
|
261
261
|
disabled: busy,
|
|
262
262
|
onClick: ()=>onDelete(draft.id ?? ''),
|
|
263
263
|
type: "button",
|
|
264
|
-
children: deleting ?
|
|
264
|
+
children: deleting ? labels.common.deleting : labels.common.delete
|
|
265
265
|
}) : null,
|
|
266
266
|
/*#__PURE__*/ _jsx("button", {
|
|
267
267
|
className: theme.button.secondary,
|
|
@@ -282,7 +282,7 @@ export function MappingEditor({ mapping, targets, targetsLoading, endpoints, fre
|
|
|
282
282
|
disabled: busy || issues.length > 0,
|
|
283
283
|
onClick: ()=>onSave(draft),
|
|
284
284
|
type: "button",
|
|
285
|
-
children: saving ?
|
|
285
|
+
children: saving ? labels.common.saving : labels.mappings.save
|
|
286
286
|
})
|
|
287
287
|
]
|
|
288
288
|
})
|
|
@@ -60,13 +60,10 @@ import { Notice } from './Primitives';
|
|
|
60
60
|
className: theme.field.hint,
|
|
61
61
|
children: labels.endpoints.bindingsNone
|
|
62
62
|
}) : null,
|
|
63
|
-
unbound.length > 0 ? /*#__PURE__*/
|
|
63
|
+
unbound.length > 0 ? /*#__PURE__*/ _jsx(Notice, {
|
|
64
64
|
title: labels.endpoints.bindingsTitle,
|
|
65
65
|
tone: "warning",
|
|
66
|
-
children:
|
|
67
|
-
unbound.join(', '),
|
|
68
|
-
" — an import cannot run until every one of them is bound. Add a binding for each below."
|
|
69
|
-
]
|
|
66
|
+
children: labels.endpoints.bindingsUnbound(unbound.join(', '))
|
|
70
67
|
}) : null,
|
|
71
68
|
value.map((binding, index)=>/*#__PURE__*/ _jsx(ParamBindingRow, {
|
|
72
69
|
binding: binding,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useLabels } from '../text/context';
|
|
3
4
|
import { integrationsPageTheme } from '../theme';
|
|
4
5
|
import { Notice } from './Primitives';
|
|
5
6
|
/**
|
|
@@ -10,6 +11,7 @@ import { Notice } from './Primitives';
|
|
|
10
11
|
* sure?" transfers no information, so this one refuses to be that.
|
|
11
12
|
*/ const theme = integrationsPageTheme;
|
|
12
13
|
export function RunConfirmStep({ lines, counted, working, onConfirm, onDismiss }) {
|
|
14
|
+
const labels = useLabels();
|
|
13
15
|
return /*#__PURE__*/ _jsxs(Notice, {
|
|
14
16
|
tone: "danger",
|
|
15
17
|
title: "This import destroys data. Read what it removes, then confirm.",
|
|
@@ -32,7 +34,7 @@ export function RunConfirmStep({ lines, counted, working, onConfirm, onDismiss }
|
|
|
32
34
|
disabled: working,
|
|
33
35
|
onClick: onConfirm,
|
|
34
36
|
type: "button",
|
|
35
|
-
children: working ?
|
|
37
|
+
children: working ? labels.runs.starting : 'Run it and remove those rows'
|
|
36
38
|
}),
|
|
37
39
|
/*#__PURE__*/ _jsx("button", {
|
|
38
40
|
className: theme.button.secondary,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { cn } from '../../../utils/cn';
|
|
4
|
+
import { useLabels } from '../text/context';
|
|
4
5
|
import { integrationsPageTheme } from '../theme';
|
|
5
6
|
/**
|
|
6
7
|
* How far the run this panel started has got.
|
|
@@ -17,6 +18,7 @@ const PHASE_LABEL = {
|
|
|
17
18
|
done: 'Finished'
|
|
18
19
|
};
|
|
19
20
|
export function RunProgressBar({ runId, progress, cancelling, onCancel }) {
|
|
21
|
+
const labels = useLabels();
|
|
20
22
|
const total = progress?.total ?? 0;
|
|
21
23
|
const processed = progress?.processed ?? 0;
|
|
22
24
|
const determinate = total > 0;
|
|
@@ -68,7 +70,7 @@ export function RunProgressBar({ runId, progress, cancelling, onCancel }) {
|
|
|
68
70
|
disabled: cancelling,
|
|
69
71
|
onClick: onCancel,
|
|
70
72
|
type: "button",
|
|
71
|
-
children: cancelling ?
|
|
73
|
+
children: cancelling ? labels.runs.cancelling : labels.runs.cancel
|
|
72
74
|
})
|
|
73
75
|
]
|
|
74
76
|
})
|
|
@@ -1,15 +1,28 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { batch, createStore } from 'h-state';
|
|
3
3
|
import { initial } from './state';
|
|
4
|
+
import { isNewTo, upsertInto } from './upsertInto';
|
|
4
5
|
export const { useStore: useIntegrationsStore } = createStore(initial, {
|
|
5
6
|
setSources: (store)=>(items, total)=>batch(()=>{
|
|
6
7
|
store.sources = items;
|
|
7
8
|
store.sourcesTotal = total;
|
|
8
9
|
store.sourcesLoading = false;
|
|
9
10
|
}),
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
upsertSource: (store)=>(source)=>batch(()=>{
|
|
12
|
+
// The count moves only for an addition — an edit that bumped the total
|
|
13
|
+
// would make the rail claim a connection nobody can find.
|
|
14
|
+
if (isNewTo(store.sources, source)) store.sourcesTotal = store.sourcesTotal + 1;
|
|
15
|
+
store.sources = upsertInto(store.sources, source);
|
|
16
|
+
}),
|
|
17
|
+
removeSource: (store)=>(id)=>batch(()=>{
|
|
18
|
+
const kept = store.sources.filter((candidate)=>candidate.id !== id);
|
|
19
|
+
// Only when it was actually on this page — the count must not fall for a
|
|
20
|
+
// record the list never held.
|
|
21
|
+
if (kept.length !== store.sources.length) {
|
|
22
|
+
store.sourcesTotal = Math.max(0, store.sourcesTotal - 1);
|
|
23
|
+
store.sources = kept;
|
|
24
|
+
}
|
|
25
|
+
}),
|
|
13
26
|
setSourcesSearch: (store)=>(search)=>batch(()=>{
|
|
14
27
|
store.sourcesSearch = search;
|
|
15
28
|
// A new search is a new result set; staying on page 4 would show an empty
|
|
@@ -53,12 +66,6 @@ export const { useStore: useIntegrationsStore } = createStore(initial, {
|
|
|
53
66
|
store.endpointsTotal = total;
|
|
54
67
|
store.endpointsLoading = false;
|
|
55
68
|
}),
|
|
56
|
-
setEndpointsTotal: (store)=>(total)=>{
|
|
57
|
-
store.endpointsTotal = total;
|
|
58
|
-
},
|
|
59
|
-
setEndpointsPage: (store)=>(page)=>{
|
|
60
|
-
store.endpointsPage = page;
|
|
61
|
-
},
|
|
62
69
|
setEndpointsSearch: (store)=>(search)=>batch(()=>{
|
|
63
70
|
store.endpointsSearch = search;
|
|
64
71
|
store.endpointsPage = 1;
|
|
@@ -80,9 +87,6 @@ export const { useStore: useIntegrationsStore } = createStore(initial, {
|
|
|
80
87
|
setMappingsTotal: (store)=>(total)=>{
|
|
81
88
|
store.mappingsTotal = total;
|
|
82
89
|
},
|
|
83
|
-
setMappingsPage: (store)=>(page)=>{
|
|
84
|
-
store.mappingsPage = page;
|
|
85
|
-
},
|
|
86
90
|
setMappingsSearch: (store)=>(search)=>batch(()=>{
|
|
87
91
|
store.mappingsSearch = search;
|
|
88
92
|
store.mappingsPage = 1;
|
|
@@ -107,12 +111,6 @@ export const { useStore: useIntegrationsStore } = createStore(initial, {
|
|
|
107
111
|
store.runsTotal = total;
|
|
108
112
|
store.runsLoading = false;
|
|
109
113
|
}),
|
|
110
|
-
setRunsTotal: (store)=>(total)=>{
|
|
111
|
-
store.runsTotal = total;
|
|
112
|
-
},
|
|
113
|
-
setRunsPage: (store)=>(page)=>{
|
|
114
|
-
store.runsPage = page;
|
|
115
|
-
},
|
|
116
114
|
setRunsLoading: (store)=>(loading)=>{
|
|
117
115
|
store.runsLoading = loading;
|
|
118
116
|
},
|
|
@@ -15,7 +15,6 @@ export type State = {
|
|
|
15
15
|
[key: string]: unknown;
|
|
16
16
|
sources: IntegrationSource[];
|
|
17
17
|
sourcesTotal: number;
|
|
18
|
-
sourcesPage: number;
|
|
19
18
|
sourcesSearch: string;
|
|
20
19
|
sourcesLoading: boolean;
|
|
21
20
|
selectedSourceId: string | null;
|
|
@@ -33,19 +32,16 @@ export type State = {
|
|
|
33
32
|
focusStep: 'mapping' | 'import';
|
|
34
33
|
endpoints: IntegrationEndpoint[];
|
|
35
34
|
endpointsTotal: number;
|
|
36
|
-
endpointsPage: number;
|
|
37
35
|
endpointsSearch: string;
|
|
38
36
|
endpointsLoading: boolean;
|
|
39
37
|
selectedEndpointId: string | null;
|
|
40
38
|
mappings: IntegrationMapping[];
|
|
41
39
|
mappingsTotal: number;
|
|
42
|
-
mappingsPage: number;
|
|
43
40
|
mappingsSearch: string;
|
|
44
41
|
mappingsLoading: boolean;
|
|
45
42
|
selectedMappingId: string | null;
|
|
46
43
|
runs: IntegrationRun[];
|
|
47
44
|
runsTotal: number;
|
|
48
|
-
runsPage: number;
|
|
49
45
|
runsLoading: boolean;
|
|
50
46
|
/** The real shape of the selected endpoint's response, once sampled. */
|
|
51
47
|
sample: EndpointSampleValue | null;
|
|
@@ -62,7 +58,18 @@ export type State = {
|
|
|
62
58
|
export type Actions = {
|
|
63
59
|
[key: string]: unknown;
|
|
64
60
|
setSources: (items: IntegrationSource[], total: number) => void;
|
|
65
|
-
|
|
61
|
+
/**
|
|
62
|
+
* Put one connection into the list, whether or not it is already there.
|
|
63
|
+
*
|
|
64
|
+
* The rail is what fetches connections, and choosing one UNMOUNTS the rail —
|
|
65
|
+
* so a connection created from the form was selected into a list that had
|
|
66
|
+
* never heard of it, and every panel below sat on skeletons until the page
|
|
67
|
+
* was reloaded by hand. The save already returns the record; this is where it
|
|
68
|
+
* goes so the detail can find it.
|
|
69
|
+
*/
|
|
70
|
+
upsertSource: (source: IntegrationSource) => void;
|
|
71
|
+
/** Drop one connection from the list, for when it has just been deleted. */
|
|
72
|
+
removeSource: (id: string) => void;
|
|
66
73
|
setSourcesSearch: (search: string) => void;
|
|
67
74
|
setSourcesLoading: (loading: boolean) => void;
|
|
68
75
|
selectSource: (id: string | null) => void;
|
|
@@ -75,20 +82,15 @@ export type Actions = {
|
|
|
75
82
|
* The step strip reports how far a connection has got, and it cannot wait for
|
|
76
83
|
* the tab that lists the rows to have been opened — so it counts directly.
|
|
77
84
|
*/
|
|
78
|
-
setEndpointsTotal: (total: number) => void;
|
|
79
|
-
setEndpointsPage: (page: number) => void;
|
|
80
85
|
setEndpointsSearch: (search: string) => void;
|
|
81
86
|
setEndpointsLoading: (loading: boolean) => void;
|
|
82
87
|
selectEndpoint: (id: string | null) => void;
|
|
83
88
|
setMappings: (items: IntegrationMapping[], total: number) => void;
|
|
84
89
|
setMappingsTotal: (total: number) => void;
|
|
85
|
-
setMappingsPage: (page: number) => void;
|
|
86
90
|
setMappingsSearch: (search: string) => void;
|
|
87
91
|
setMappingsLoading: (loading: boolean) => void;
|
|
88
92
|
selectMapping: (id: string | null) => void;
|
|
89
93
|
setRuns: (items: IntegrationRun[], total: number) => void;
|
|
90
|
-
setRunsTotal: (total: number) => void;
|
|
91
|
-
setRunsPage: (page: number) => void;
|
|
92
94
|
setRunsLoading: (loading: boolean) => void;
|
|
93
95
|
setSample: (sample: EndpointSampleValue | null) => void;
|
|
94
96
|
setSampling: (sampling: boolean) => void;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
export const initial = {
|
|
2
2
|
sources: [],
|
|
3
3
|
sourcesTotal: 0,
|
|
4
|
-
sourcesPage: 1,
|
|
5
4
|
sourcesSearch: '',
|
|
6
5
|
sourcesLoading: false,
|
|
7
6
|
selectedSourceId: null,
|
|
@@ -10,19 +9,16 @@ export const initial = {
|
|
|
10
9
|
focusStep: 'mapping',
|
|
11
10
|
endpoints: [],
|
|
12
11
|
endpointsTotal: 0,
|
|
13
|
-
endpointsPage: 1,
|
|
14
12
|
endpointsSearch: '',
|
|
15
13
|
endpointsLoading: false,
|
|
16
14
|
selectedEndpointId: null,
|
|
17
15
|
mappings: [],
|
|
18
16
|
mappingsTotal: 0,
|
|
19
|
-
mappingsPage: 1,
|
|
20
17
|
mappingsSearch: '',
|
|
21
18
|
mappingsLoading: false,
|
|
22
19
|
selectedMappingId: null,
|
|
23
20
|
runs: [],
|
|
24
21
|
runsTotal: 0,
|
|
25
|
-
runsPage: 1,
|
|
26
22
|
runsLoading: false,
|
|
27
23
|
sample: null,
|
|
28
24
|
sampling: false,
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a saved record goes in a list that may or may not already hold it.
|
|
3
|
+
*
|
|
4
|
+
* Kept as a function so the rule can be tested apart from the store: the panel
|
|
5
|
+
* unmounts the connection rail the moment a connection is chosen, so a newly
|
|
6
|
+
* created connection is selected into a list that never fetched it. Putting the
|
|
7
|
+
* returned record in by hand is what stops the detail below from waiting for a
|
|
8
|
+
* fetch that no mounted component will make.
|
|
9
|
+
*
|
|
10
|
+
* A new record goes to the FRONT — it is the one just made, and the operator is
|
|
11
|
+
* about to be switched to it; an existing one is replaced where it already sits,
|
|
12
|
+
* so an edit never reorders a list somebody is reading.
|
|
13
|
+
*/
|
|
14
|
+
export declare function upsertInto<T extends {
|
|
15
|
+
id: string;
|
|
16
|
+
}>(items: T[], record: T): T[];
|
|
17
|
+
/** Whether `record` would be an addition rather than a replacement. */
|
|
18
|
+
export declare function isNewTo<T extends {
|
|
19
|
+
id: string;
|
|
20
|
+
}>(items: T[], record: T): boolean;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a saved record goes in a list that may or may not already hold it.
|
|
3
|
+
*
|
|
4
|
+
* Kept as a function so the rule can be tested apart from the store: the panel
|
|
5
|
+
* unmounts the connection rail the moment a connection is chosen, so a newly
|
|
6
|
+
* created connection is selected into a list that never fetched it. Putting the
|
|
7
|
+
* returned record in by hand is what stops the detail below from waiting for a
|
|
8
|
+
* fetch that no mounted component will make.
|
|
9
|
+
*
|
|
10
|
+
* A new record goes to the FRONT — it is the one just made, and the operator is
|
|
11
|
+
* about to be switched to it; an existing one is replaced where it already sits,
|
|
12
|
+
* so an edit never reorders a list somebody is reading.
|
|
13
|
+
*/ export function upsertInto(items, record) {
|
|
14
|
+
return items.some((candidate)=>candidate.id === record.id) ? items.map((candidate)=>candidate.id === record.id ? record : candidate) : [
|
|
15
|
+
record,
|
|
16
|
+
...items
|
|
17
|
+
];
|
|
18
|
+
}
|
|
19
|
+
/** Whether `record` would be an addition rather than a replacement. */ export function isNewTo(items, record) {
|
|
20
|
+
return !items.some((candidate)=>candidate.id === record.id);
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test';
|
|
2
|
+
import { isNewTo, upsertInto } from './upsertInto';
|
|
3
|
+
const a = {
|
|
4
|
+
id: 'a',
|
|
5
|
+
name: 'First'
|
|
6
|
+
};
|
|
7
|
+
const b = {
|
|
8
|
+
id: 'b',
|
|
9
|
+
name: 'Second'
|
|
10
|
+
};
|
|
11
|
+
describe('upsertInto', ()=>{
|
|
12
|
+
it('puts a new record at the front', ()=>{
|
|
13
|
+
expect(upsertInto([
|
|
14
|
+
a
|
|
15
|
+
], b)).toEqual([
|
|
16
|
+
b,
|
|
17
|
+
a
|
|
18
|
+
]);
|
|
19
|
+
});
|
|
20
|
+
it('adds to an empty list', ()=>{
|
|
21
|
+
expect(upsertInto([], a)).toEqual([
|
|
22
|
+
a
|
|
23
|
+
]);
|
|
24
|
+
});
|
|
25
|
+
it('replaces an existing record where it already sits', ()=>{
|
|
26
|
+
const renamed = {
|
|
27
|
+
id: 'a',
|
|
28
|
+
name: 'Renamed'
|
|
29
|
+
};
|
|
30
|
+
expect(upsertInto([
|
|
31
|
+
b,
|
|
32
|
+
a
|
|
33
|
+
], renamed)).toEqual([
|
|
34
|
+
b,
|
|
35
|
+
renamed
|
|
36
|
+
]);
|
|
37
|
+
});
|
|
38
|
+
it('never duplicates — saving the same record twice leaves one', ()=>{
|
|
39
|
+
const once = upsertInto([], a);
|
|
40
|
+
expect(upsertInto(once, {
|
|
41
|
+
id: 'a',
|
|
42
|
+
name: 'Again'
|
|
43
|
+
})).toHaveLength(1);
|
|
44
|
+
});
|
|
45
|
+
it('leaves the original array untouched', ()=>{
|
|
46
|
+
const items = [
|
|
47
|
+
a
|
|
48
|
+
];
|
|
49
|
+
upsertInto(items, b);
|
|
50
|
+
expect(items).toEqual([
|
|
51
|
+
a
|
|
52
|
+
]);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
describe('isNewTo', ()=>{
|
|
56
|
+
it('is true only for a record the list does not hold', ()=>{
|
|
57
|
+
expect(isNewTo([
|
|
58
|
+
a
|
|
59
|
+
], b)).toBe(true);
|
|
60
|
+
expect(isNewTo([
|
|
61
|
+
a
|
|
62
|
+
], {
|
|
63
|
+
id: 'a',
|
|
64
|
+
name: 'Renamed'
|
|
65
|
+
})).toBe(false);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -63,6 +63,14 @@ export type IntegrationsLabels = {
|
|
|
63
63
|
disabledBadge: string;
|
|
64
64
|
saveChanges: string;
|
|
65
65
|
createSubmit: string;
|
|
66
|
+
deleteHeading: string;
|
|
67
|
+
deleteStart: string;
|
|
68
|
+
deleteWarnTitle: string;
|
|
69
|
+
deleteWarnBody: (name: string) => string;
|
|
70
|
+
deleteTypeToConfirm: string;
|
|
71
|
+
deleteConfirm: string;
|
|
72
|
+
deleteHint: string;
|
|
73
|
+
deleteFailed: string;
|
|
66
74
|
};
|
|
67
75
|
runs: {
|
|
68
76
|
/** The one-line summary above the buttons. */
|
|
@@ -72,6 +80,7 @@ export type IntegrationsLabels = {
|
|
|
72
80
|
start: string;
|
|
73
81
|
starting: string;
|
|
74
82
|
cancel: string;
|
|
83
|
+
cancelling: string;
|
|
75
84
|
previewExplainTitle: string;
|
|
76
85
|
previewExplainBody: string;
|
|
77
86
|
historyHeading: string;
|
|
@@ -56,7 +56,15 @@
|
|
|
56
56
|
enabledBadge: 'Enabled',
|
|
57
57
|
disabledBadge: 'Off',
|
|
58
58
|
saveChanges: 'Save changes',
|
|
59
|
-
createSubmit: 'Create connection'
|
|
59
|
+
createSubmit: 'Create connection',
|
|
60
|
+
deleteHeading: 'Remove this connection',
|
|
61
|
+
deleteStart: 'Delete',
|
|
62
|
+
deleteWarnTitle: 'This takes its calls and mappings with it',
|
|
63
|
+
deleteWarnBody: (name)=>`Everything configured under “${name}” goes: the calls, the mappings and the record of what they imported. The rows already written into your tables stay where they are — but nothing will be able to undo the imports that put them there.`,
|
|
64
|
+
deleteTypeToConfirm: 'Type the connection name to confirm',
|
|
65
|
+
deleteConfirm: 'Delete this connection',
|
|
66
|
+
deleteHint: 'A connection can be turned off instead — that stops every call and import through it and keeps the settings. Deleting is for one that should not have existed.',
|
|
67
|
+
deleteFailed: 'The connection was not deleted.'
|
|
60
68
|
},
|
|
61
69
|
runs: {
|
|
62
70
|
summary: (target, writeMode, missing)=>`Writes into ${target} · ${writeMode} · missing rows: ${missing}`,
|
|
@@ -65,6 +73,7 @@
|
|
|
65
73
|
start: 'Run import',
|
|
66
74
|
starting: 'Starting…',
|
|
67
75
|
cancel: 'Cancel',
|
|
76
|
+
cancelling: 'Cancelling…',
|
|
68
77
|
previewExplainTitle: 'Preview before you run',
|
|
69
78
|
previewExplainBody: 'A preview reads the source without writing anything, and reports how many rows would be created, updated, skipped, and how many rows in the target the source no longer returns.',
|
|
70
79
|
historyHeading: 'History',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nucleus-core-ts",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.847",
|
|
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",
|