nucleus-core-ts 0.9.843 → 0.9.845
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.d.ts +20 -0
- package/dist/fe/components/IntegrationsPage/components/ConnectionDelete.js +116 -0
- package/dist/fe/components/IntegrationsPage/components/ConnectionSettingsTab.d.ts +3 -1
- package/dist/fe/components/IntegrationsPage/components/ConnectionSettingsTab.js +7 -1
- package/dist/fe/components/IntegrationsPage/components/ConnectionSwitcher.js +16 -9
- package/dist/fe/components/IntegrationsPage/components/ConnectionTest.js +3 -3
- package/dist/fe/components/IntegrationsPage/components/CredentialSlotRow.js +2 -2
- package/dist/fe/components/IntegrationsPage/components/FieldMappingRows.js +1 -1
- package/dist/fe/components/IntegrationsPage/components/IntegrationsPage.js +14 -0
- package/dist/fe/components/IntegrationsPage/components/MappingList.js +6 -1
- package/dist/fe/components/IntegrationsPage/components/ParamBindingRows.js +2 -8
- package/dist/fe/components/IntegrationsPage/components/RunPanel.js +2 -2
- package/dist/fe/components/IntegrationsPage/components/deleteIsConfirmed.d.ts +11 -0
- package/dist/fe/components/IntegrationsPage/components/deleteIsConfirmed.js +14 -0
- package/dist/fe/components/IntegrationsPage/components/deleteIsConfirmed.test.d.ts +1 -0
- package/dist/fe/components/IntegrationsPage/components/deleteIsConfirmed.test.js +29 -0
- package/dist/fe/components/IntegrationsPage/text/index.d.ts +3 -13
- package/dist/fe/components/IntegrationsPage/text/index.js +4 -14
- package/package.json +1 -1
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { IntegrationSource, IntegrationsActions } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Removing a connection.
|
|
4
|
+
*
|
|
5
|
+
* `deleteSource` existed in the contract from the beginning and nothing ever
|
|
6
|
+
* called it: a connection could be created and edited but never removed, so a
|
|
7
|
+
* mistyped one stayed on the rail forever. The one operation that cannot be
|
|
8
|
+
* undone was the one with no button.
|
|
9
|
+
*
|
|
10
|
+
* Asked twice, and the second time by name. A connection is the root of its
|
|
11
|
+
* calls, its mappings and their history; "are you sure" is not a warning
|
|
12
|
+
* anybody reads, but typing the name is a sentence you cannot press by accident.
|
|
13
|
+
*/
|
|
14
|
+
export type ConnectionDeleteProps = {
|
|
15
|
+
actions: IntegrationsActions;
|
|
16
|
+
source: IntegrationSource | null;
|
|
17
|
+
/** Called once it is gone, so the rail can come back. */
|
|
18
|
+
onDeleted?: () => void;
|
|
19
|
+
};
|
|
20
|
+
export declare function ConnectionDelete({ actions, source, onDeleted }: ConnectionDeleteProps): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { useIntegrationsStore } from '../store';
|
|
5
|
+
import { integrationsPageTheme as theme } from '../theme';
|
|
6
|
+
import { deleteIsConfirmed } from './deleteIsConfirmed';
|
|
7
|
+
import { Notice } from './Primitives';
|
|
8
|
+
export function ConnectionDelete({ actions, source, onDeleted }) {
|
|
9
|
+
const store = useIntegrationsStore();
|
|
10
|
+
const [asking, setAsking] = useState(false);
|
|
11
|
+
const [typed, setTyped] = useState('');
|
|
12
|
+
const [busy, setBusy] = useState(false);
|
|
13
|
+
if (!source) return null;
|
|
14
|
+
const confirmed = deleteIsConfirmed(source.name, typed);
|
|
15
|
+
const remove = ()=>{
|
|
16
|
+
setBusy(true);
|
|
17
|
+
store.setError(null);
|
|
18
|
+
actions.deleteSource(source.id).then((done)=>{
|
|
19
|
+
setBusy(false);
|
|
20
|
+
if (!done) {
|
|
21
|
+
store.setError('The connection was not deleted.');
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
setAsking(false);
|
|
25
|
+
setTyped('');
|
|
26
|
+
store.selectSource(null);
|
|
27
|
+
onDeleted?.();
|
|
28
|
+
}).catch((error)=>{
|
|
29
|
+
setBusy(false);
|
|
30
|
+
store.setError(error instanceof Error ? error.message : 'The request failed.');
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
return /*#__PURE__*/ _jsxs("section", {
|
|
34
|
+
className: theme.card.wrapper,
|
|
35
|
+
children: [
|
|
36
|
+
/*#__PURE__*/ _jsxs("header", {
|
|
37
|
+
className: theme.card.header,
|
|
38
|
+
children: [
|
|
39
|
+
/*#__PURE__*/ _jsx("h3", {
|
|
40
|
+
className: theme.card.title,
|
|
41
|
+
children: "Remove this connection"
|
|
42
|
+
}),
|
|
43
|
+
asking ? null : /*#__PURE__*/ _jsx("button", {
|
|
44
|
+
className: theme.button.danger,
|
|
45
|
+
onClick: ()=>setAsking(true),
|
|
46
|
+
type: "button",
|
|
47
|
+
children: "Delete"
|
|
48
|
+
})
|
|
49
|
+
]
|
|
50
|
+
}),
|
|
51
|
+
/*#__PURE__*/ _jsx("div", {
|
|
52
|
+
className: theme.card.body,
|
|
53
|
+
children: asking ? /*#__PURE__*/ _jsxs(_Fragment, {
|
|
54
|
+
children: [
|
|
55
|
+
/*#__PURE__*/ _jsxs(Notice, {
|
|
56
|
+
title: "This takes its calls and mappings with it",
|
|
57
|
+
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
|
+
]
|
|
63
|
+
}),
|
|
64
|
+
/*#__PURE__*/ _jsxs("label", {
|
|
65
|
+
className: theme.field.wrapper,
|
|
66
|
+
children: [
|
|
67
|
+
/*#__PURE__*/ _jsxs("span", {
|
|
68
|
+
className: theme.field.label,
|
|
69
|
+
children: [
|
|
70
|
+
"Type ",
|
|
71
|
+
/*#__PURE__*/ _jsx("strong", {
|
|
72
|
+
children: source.name
|
|
73
|
+
}),
|
|
74
|
+
" to confirm"
|
|
75
|
+
]
|
|
76
|
+
}),
|
|
77
|
+
/*#__PURE__*/ _jsx("input", {
|
|
78
|
+
className: theme.field.input,
|
|
79
|
+
disabled: busy,
|
|
80
|
+
onChange: (event)=>setTyped(event.target.value),
|
|
81
|
+
placeholder: source.name,
|
|
82
|
+
value: typed
|
|
83
|
+
})
|
|
84
|
+
]
|
|
85
|
+
}),
|
|
86
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
87
|
+
className: "flex flex-wrap justify-end gap-2",
|
|
88
|
+
children: [
|
|
89
|
+
/*#__PURE__*/ _jsx("button", {
|
|
90
|
+
className: theme.button.secondary,
|
|
91
|
+
disabled: busy,
|
|
92
|
+
onClick: ()=>{
|
|
93
|
+
setAsking(false);
|
|
94
|
+
setTyped('');
|
|
95
|
+
},
|
|
96
|
+
type: "button",
|
|
97
|
+
children: "Cancel"
|
|
98
|
+
}),
|
|
99
|
+
/*#__PURE__*/ _jsx("button", {
|
|
100
|
+
className: theme.button.danger,
|
|
101
|
+
disabled: busy || !confirmed,
|
|
102
|
+
onClick: remove,
|
|
103
|
+
type: "button",
|
|
104
|
+
children: busy ? 'Deleting…' : 'Delete this connection'
|
|
105
|
+
})
|
|
106
|
+
]
|
|
107
|
+
})
|
|
108
|
+
]
|
|
109
|
+
}) : /*#__PURE__*/ _jsx("p", {
|
|
110
|
+
className: theme.field.hint,
|
|
111
|
+
children: "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."
|
|
112
|
+
})
|
|
113
|
+
})
|
|
114
|
+
]
|
|
115
|
+
});
|
|
116
|
+
}
|
|
@@ -3,6 +3,8 @@ export type ConnectionSettingsTabProps = {
|
|
|
3
3
|
actions: IntegrationsActions;
|
|
4
4
|
/** Called after a NEW connection is saved, so the list can pick it up. */
|
|
5
5
|
onSaved?: (source: IntegrationSource) => void;
|
|
6
|
+
/** Called after this connection is deleted, so the rail can come back. */
|
|
7
|
+
onDeleted?: () => void;
|
|
6
8
|
};
|
|
7
9
|
/**
|
|
8
10
|
* Everything about the connection itself: its address and authentication, its
|
|
@@ -18,4 +20,4 @@ export type ConnectionSettingsTabProps = {
|
|
|
18
20
|
* credential boxes invites pressing it before there is anything to authenticate
|
|
19
21
|
* with, and reading the failure as "the address is wrong".
|
|
20
22
|
*/
|
|
21
|
-
export declare function ConnectionSettingsTab({ actions, onSaved }: ConnectionSettingsTabProps): import("react/jsx-runtime").JSX.Element;
|
|
23
|
+
export declare function ConnectionSettingsTab({ actions, onSaved, onDeleted }: ConnectionSettingsTabProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useIntegrationsStore } from '../store';
|
|
4
4
|
import { integrationsPageTheme as theme } from '../theme';
|
|
5
|
+
import { ConnectionDelete } from './ConnectionDelete';
|
|
5
6
|
import { ConnectionForm } from './ConnectionForm';
|
|
6
7
|
import { ConnectionTest } from './ConnectionTest';
|
|
7
8
|
import { CredentialFields } from './CredentialFields';
|
|
@@ -18,7 +19,7 @@ import { CredentialFields } from './CredentialFields';
|
|
|
18
19
|
* the address, enters the secret, then presses test. Putting the test above the
|
|
19
20
|
* credential boxes invites pressing it before there is anything to authenticate
|
|
20
21
|
* with, and reading the failure as "the address is wrong".
|
|
21
|
-
*/ export function ConnectionSettingsTab({ actions, onSaved }) {
|
|
22
|
+
*/ export function ConnectionSettingsTab({ actions, onSaved, onDeleted }) {
|
|
22
23
|
const store = useIntegrationsStore();
|
|
23
24
|
const source = store.sources.find((candidate)=>candidate.id === store.selectedSourceId) ?? null;
|
|
24
25
|
// The list holds only the current page, so a connection selected before a
|
|
@@ -43,6 +44,11 @@ import { CredentialFields } from './CredentialFields';
|
|
|
43
44
|
actions: actions,
|
|
44
45
|
loading: store.sourcesLoading || missingFromPage,
|
|
45
46
|
source: source
|
|
47
|
+
}),
|
|
48
|
+
/*#__PURE__*/ _jsx(ConnectionDelete, {
|
|
49
|
+
actions: actions,
|
|
50
|
+
onDeleted: onDeleted,
|
|
51
|
+
source: source
|
|
46
52
|
})
|
|
47
53
|
]
|
|
48
54
|
});
|
|
@@ -31,28 +31,35 @@ import { integrationsPageTheme as theme } from '../theme';
|
|
|
31
31
|
return /*#__PURE__*/ _jsxs("div", {
|
|
32
32
|
className: cn('flex flex-wrap items-center justify-between gap-3 rounded-2xl border border-slate-200/80 bg-white px-4 py-3 dark:border-slate-800 dark:bg-slate-900'),
|
|
33
33
|
children: [
|
|
34
|
+
/*#__PURE__*/ _jsxs("button", {
|
|
35
|
+
"aria-label": labels.connections.change,
|
|
36
|
+
className: cn(theme.button.secondary, 'shrink-0 gap-1.5'),
|
|
37
|
+
onClick: ()=>store.selectSource(null),
|
|
38
|
+
type: "button",
|
|
39
|
+
children: [
|
|
40
|
+
/*#__PURE__*/ _jsx("span", {
|
|
41
|
+
"aria-hidden": "true",
|
|
42
|
+
children: "←"
|
|
43
|
+
}),
|
|
44
|
+
labels.connections.change
|
|
45
|
+
]
|
|
46
|
+
}),
|
|
34
47
|
/*#__PURE__*/ _jsxs("div", {
|
|
35
|
-
className: "flex min-w-0 flex-col",
|
|
48
|
+
className: "flex min-w-0 flex-1 flex-col items-end text-right",
|
|
36
49
|
children: [
|
|
37
50
|
/*#__PURE__*/ _jsx("span", {
|
|
38
51
|
className: cn(theme.field.label, 'text-slate-400 dark:text-slate-500'),
|
|
39
52
|
children: labels.connections.heading
|
|
40
53
|
}),
|
|
41
54
|
/*#__PURE__*/ _jsx("span", {
|
|
42
|
-
className: "truncate font-semibold text-base text-slate-900 dark:text-slate-100",
|
|
55
|
+
className: "w-full truncate font-semibold text-base text-slate-900 dark:text-slate-100",
|
|
43
56
|
children: source?.name ?? labels.connections.heading
|
|
44
57
|
}),
|
|
45
58
|
source?.baseUrl ? /*#__PURE__*/ _jsx("span", {
|
|
46
|
-
className: cn(theme.list.itemMeta, '
|
|
59
|
+
className: cn(theme.list.itemMeta, 'w-full truncate'),
|
|
47
60
|
children: source.baseUrl
|
|
48
61
|
}) : null
|
|
49
62
|
]
|
|
50
|
-
}),
|
|
51
|
-
/*#__PURE__*/ _jsx("button", {
|
|
52
|
-
className: theme.button.secondary,
|
|
53
|
-
onClick: ()=>store.selectSource(null),
|
|
54
|
-
type: "button",
|
|
55
|
-
children: labels.connections.change
|
|
56
63
|
})
|
|
57
64
|
]
|
|
58
65
|
});
|
|
@@ -59,7 +59,7 @@ export function ConnectionTest({ actions, source, loading = false }) {
|
|
|
59
59
|
}),
|
|
60
60
|
result ? /*#__PURE__*/ _jsx(Badge, {
|
|
61
61
|
tone: result.ok ? 'success' : 'danger',
|
|
62
|
-
children: result.ok ?
|
|
62
|
+
children: result.ok ? labels.auth.testOkBadge : labels.auth.testNotReachable
|
|
63
63
|
}) : null
|
|
64
64
|
]
|
|
65
65
|
}),
|
|
@@ -75,7 +75,7 @@ export function ConnectionTest({ actions, source, loading = false }) {
|
|
|
75
75
|
]
|
|
76
76
|
}) : /*#__PURE__*/ _jsx("p", {
|
|
77
77
|
className: theme.field.hint,
|
|
78
|
-
children:
|
|
78
|
+
children: labels.auth.testSaveFirst
|
|
79
79
|
}),
|
|
80
80
|
result?.ok ? /*#__PURE__*/ _jsxs(Notice, {
|
|
81
81
|
title: labels.auth.testOkTitle,
|
|
@@ -110,7 +110,7 @@ export function ConnectionTest({ actions, source, loading = false }) {
|
|
|
110
110
|
disabled: !source || testing,
|
|
111
111
|
onClick: handleTest,
|
|
112
112
|
type: "button",
|
|
113
|
-
children: testing ?
|
|
113
|
+
children: testing ? labels.auth.testing : labels.auth.testRun
|
|
114
114
|
})
|
|
115
115
|
})
|
|
116
116
|
]
|
|
@@ -47,7 +47,7 @@ export function CredentialSlotRow({ slot, source, value, maskedHint, disabled, o
|
|
|
47
47
|
children: stored ? labels.auth.stored : labels.auth.notSet
|
|
48
48
|
}),
|
|
49
49
|
stored ? /*#__PURE__*/ _jsx("span", {
|
|
50
|
-
className: cn(theme.field.hint, 'truncate font-mono'),
|
|
50
|
+
className: cn(theme.field.hint, 'min-w-0 truncate font-mono'),
|
|
51
51
|
children: maskedHint
|
|
52
52
|
}) : null,
|
|
53
53
|
/*#__PURE__*/ _jsx("button", {
|
|
@@ -56,7 +56,7 @@ export function CredentialSlotRow({ slot, source, value, maskedHint, disabled, o
|
|
|
56
56
|
disabled: disabled || !stored,
|
|
57
57
|
onClick: onClear,
|
|
58
58
|
type: "button",
|
|
59
|
-
children:
|
|
59
|
+
children: labels.auth.clear
|
|
60
60
|
})
|
|
61
61
|
]
|
|
62
62
|
})
|
|
@@ -42,7 +42,7 @@ export function FieldMappingRows({ rows, sourceFields, targetFields, disabled, o
|
|
|
42
42
|
EMPTY_FIELD_MAPPING
|
|
43
43
|
]),
|
|
44
44
|
type: "button",
|
|
45
|
-
children:
|
|
45
|
+
children: labels.mappings.addField
|
|
46
46
|
}),
|
|
47
47
|
purpose: "Which incoming value becomes which column. The order is the order they are applied in.",
|
|
48
48
|
title: labels.mappings.fieldsHeading,
|
|
@@ -47,6 +47,18 @@ export function IntegrationsPage({ title = 'Integrations', subtitle = 'Connect a
|
|
|
47
47
|
setSourcesReloadToken((token)=>token + 1);
|
|
48
48
|
};
|
|
49
49
|
/**
|
|
50
|
+
* A connection was deleted.
|
|
51
|
+
*
|
|
52
|
+
* Nothing about it is worth keeping on screen — the tabs below would be
|
|
53
|
+
* showing the calls and mappings of a record that no longer exists — so the
|
|
54
|
+
* selection is dropped, the rail comes back, and the list is asked again.
|
|
55
|
+
* `forgetFocusOnLeave` matters here too: arriving from a table set a focus
|
|
56
|
+
* that would otherwise re-select the next connection instantly.
|
|
57
|
+
*/ const handleSourceDeleted = ()=>{
|
|
58
|
+
forgetFocusOnLeave();
|
|
59
|
+
setSourcesReloadToken((token)=>token + 1);
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
50
62
|
* How far this connection has got, asked for directly.
|
|
51
63
|
*
|
|
52
64
|
* The step strip promises to say where you are, so it cannot depend on having
|
|
@@ -273,6 +285,7 @@ export function IntegrationsPage({ title = 'Integrations', subtitle = 'Connect a
|
|
|
273
285
|
children: [
|
|
274
286
|
tab === 'connection' ? /*#__PURE__*/ _jsx(ConnectionSettingsTab, {
|
|
275
287
|
actions: actions,
|
|
288
|
+
onDeleted: handleSourceDeleted,
|
|
276
289
|
onSaved: handleSourceSaved
|
|
277
290
|
}) : null,
|
|
278
291
|
tab === 'endpoints' ? /*#__PURE__*/ _jsx(EndpointsTab, {
|
|
@@ -305,6 +318,7 @@ export function IntegrationsPage({ title = 'Integrations', subtitle = 'Connect a
|
|
|
305
318
|
className: theme.layout.main,
|
|
306
319
|
children: /*#__PURE__*/ _jsx(ConnectionSettingsTab, {
|
|
307
320
|
actions: actions,
|
|
321
|
+
onDeleted: handleSourceDeleted,
|
|
308
322
|
onSaved: handleSourceSaved
|
|
309
323
|
})
|
|
310
324
|
})
|
|
@@ -147,7 +147,12 @@ export function MappingList({ actions, sourceId, selectedId, reloadToken, onSele
|
|
|
147
147
|
children: [
|
|
148
148
|
/*#__PURE__*/ _jsxs("button", {
|
|
149
149
|
"aria-current": mapping.id === selectedId ? 'true' : undefined,
|
|
150
|
-
className: cn(theme.list.item,
|
|
150
|
+
className: cn(theme.list.item, // `flex-1` alone leaves min-width at auto, and the title
|
|
151
|
+
// inside truncates — which means it never wraps, so its
|
|
152
|
+
// min-content width is the WHOLE name. One long mapping
|
|
153
|
+
// name then widened this row past the panel and scrolled
|
|
154
|
+
// the page sideways. min-w-0 is what lets truncate truncate.
|
|
155
|
+
'min-w-0 flex-1', mapping.id === selectedId ? theme.list.itemSelected : undefined),
|
|
151
156
|
onClick: ()=>onSelect(mapping),
|
|
152
157
|
type: "button",
|
|
153
158
|
children: [
|
|
@@ -56,15 +56,9 @@ import { Notice } from './Primitives';
|
|
|
56
56
|
/*#__PURE__*/ _jsxs("div", {
|
|
57
57
|
className: theme.card.body,
|
|
58
58
|
children: [
|
|
59
|
-
required.length === 0 ? /*#__PURE__*/
|
|
59
|
+
required.length === 0 ? /*#__PURE__*/ _jsx("p", {
|
|
60
60
|
className: theme.field.hint,
|
|
61
|
-
children:
|
|
62
|
-
"This endpoint takes no parameters. Add a ",
|
|
63
|
-
/*#__PURE__*/ _jsx("code", {
|
|
64
|
-
children: '{name}'
|
|
65
|
-
}),
|
|
66
|
-
" token to its path if it needs one."
|
|
67
|
-
]
|
|
61
|
+
children: labels.endpoints.bindingsNone
|
|
68
62
|
}) : null,
|
|
69
63
|
unbound.length > 0 ? /*#__PURE__*/ _jsxs(Notice, {
|
|
70
64
|
title: labels.endpoints.bindingsTitle,
|
|
@@ -178,14 +178,14 @@ export function RunPanel({ mapping, actions, onSubscribeProgress, onRunSettled }
|
|
|
178
178
|
disabled: store.planning || activeRunId !== null,
|
|
179
179
|
onClick: preview,
|
|
180
180
|
type: "button",
|
|
181
|
-
children: store.planning ?
|
|
181
|
+
children: store.planning ? labels.runs.previewing : labels.runs.preview
|
|
182
182
|
}),
|
|
183
183
|
/*#__PURE__*/ _jsx("button", {
|
|
184
184
|
className: theme.button.primary,
|
|
185
185
|
disabled: starting || confirming || activeRunId !== null || !passwordReady,
|
|
186
186
|
onClick: ()=>start(false),
|
|
187
187
|
type: "button",
|
|
188
|
-
children: starting ?
|
|
188
|
+
children: starting ? labels.runs.starting : labels.runs.start
|
|
189
189
|
})
|
|
190
190
|
]
|
|
191
191
|
})
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether what the operator typed matches the connection they are deleting.
|
|
3
|
+
*
|
|
4
|
+
* Kept out of the component so the rule can be tested: it is the only thing
|
|
5
|
+
* standing between a stray click and a connection's calls, mappings and run
|
|
6
|
+
* history. Trimmed on both sides because a name pasted out of the list carries
|
|
7
|
+
* a trailing space often enough that refusing it reads as a bug, and compared
|
|
8
|
+
* exactly otherwise — two connections called "HR" and "HR 2" must not confirm
|
|
9
|
+
* for each other.
|
|
10
|
+
*/
|
|
11
|
+
export declare function deleteIsConfirmed(name: string, typed: string): boolean;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether what the operator typed matches the connection they are deleting.
|
|
3
|
+
*
|
|
4
|
+
* Kept out of the component so the rule can be tested: it is the only thing
|
|
5
|
+
* standing between a stray click and a connection's calls, mappings and run
|
|
6
|
+
* history. Trimmed on both sides because a name pasted out of the list carries
|
|
7
|
+
* a trailing space often enough that refusing it reads as a bug, and compared
|
|
8
|
+
* exactly otherwise — two connections called "HR" and "HR 2" must not confirm
|
|
9
|
+
* for each other.
|
|
10
|
+
*/ export function deleteIsConfirmed(name, typed) {
|
|
11
|
+
const target = name.trim();
|
|
12
|
+
if (target.length === 0) return false;
|
|
13
|
+
return typed.trim() === target;
|
|
14
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test';
|
|
2
|
+
import { deleteIsConfirmed } from './deleteIsConfirmed';
|
|
3
|
+
describe('deleteIsConfirmed', ()=>{
|
|
4
|
+
it('confirms the exact name', ()=>{
|
|
5
|
+
expect(deleteIsConfirmed('TatMetal HR', 'TatMetal HR')).toBe(true);
|
|
6
|
+
});
|
|
7
|
+
it('forgives surrounding whitespace on either side', ()=>{
|
|
8
|
+
expect(deleteIsConfirmed('TatMetal HR', ' TatMetal HR ')).toBe(true);
|
|
9
|
+
expect(deleteIsConfirmed(' TatMetal HR', 'TatMetal HR')).toBe(true);
|
|
10
|
+
});
|
|
11
|
+
it('refuses a different connection with a similar name', ()=>{
|
|
12
|
+
expect(deleteIsConfirmed('HR', 'HR 2')).toBe(false);
|
|
13
|
+
expect(deleteIsConfirmed('HR 2', 'HR')).toBe(false);
|
|
14
|
+
});
|
|
15
|
+
it('is case sensitive — the name is copied, not remembered', ()=>{
|
|
16
|
+
expect(deleteIsConfirmed('TatMetal HR', 'tatmetal hr')).toBe(false);
|
|
17
|
+
});
|
|
18
|
+
it('refuses an empty box', ()=>{
|
|
19
|
+
expect(deleteIsConfirmed('TatMetal HR', '')).toBe(false);
|
|
20
|
+
expect(deleteIsConfirmed('TatMetal HR', ' ')).toBe(false);
|
|
21
|
+
});
|
|
22
|
+
/**
|
|
23
|
+
* A connection saved with a blank name would otherwise confirm on an empty
|
|
24
|
+
* box — the one case where pressing nothing deletes something.
|
|
25
|
+
*/ it('never confirms when the connection itself has no name', ()=>{
|
|
26
|
+
expect(deleteIsConfirmed('', '')).toBe(false);
|
|
27
|
+
expect(deleteIsConfirmed(' ', '')).toBe(false);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
@@ -35,7 +35,6 @@ export type IntegrationsLabels = {
|
|
|
35
35
|
};
|
|
36
36
|
/** Marks a step whose prerequisite is not done yet. */
|
|
37
37
|
lockedHint: string;
|
|
38
|
-
doneHint: string;
|
|
39
38
|
/** One short phrase per step proving it is done. */
|
|
40
39
|
evidence: {
|
|
41
40
|
connection: (name: string) => string;
|
|
@@ -69,7 +68,9 @@ export type IntegrationsLabels = {
|
|
|
69
68
|
/** The one-line summary above the buttons. */
|
|
70
69
|
summary: (target: string, writeMode: string, missing: string) => string;
|
|
71
70
|
preview: string;
|
|
71
|
+
previewing: string;
|
|
72
72
|
start: string;
|
|
73
|
+
starting: string;
|
|
73
74
|
cancel: string;
|
|
74
75
|
previewExplainTitle: string;
|
|
75
76
|
previewExplainBody: string;
|
|
@@ -131,7 +132,6 @@ export type IntegrationsLabels = {
|
|
|
131
132
|
bindingsTitle: string;
|
|
132
133
|
bindingsUnbound: (names: string) => string;
|
|
133
134
|
bindingsNone: string;
|
|
134
|
-
bindFrom: string;
|
|
135
135
|
bindStatic: string;
|
|
136
136
|
bindEndpoint: string;
|
|
137
137
|
bindEntity: string;
|
|
@@ -215,7 +215,6 @@ export type IntegrationsLabels = {
|
|
|
215
215
|
sampleFirstTitle: string;
|
|
216
216
|
sampleFirstBody: string;
|
|
217
217
|
nothingMapped: string;
|
|
218
|
-
parentPrefixHint: string;
|
|
219
218
|
templateHint: string;
|
|
220
219
|
source: string;
|
|
221
220
|
sourcePlaceholder: string;
|
|
@@ -262,6 +261,7 @@ export type IntegrationsLabels = {
|
|
|
262
261
|
clear: string;
|
|
263
262
|
testHeading: string;
|
|
264
263
|
testRun: string;
|
|
264
|
+
testing: string;
|
|
265
265
|
testOkTitle: string;
|
|
266
266
|
testOkBadge: string;
|
|
267
267
|
testFailedTitle: string;
|
|
@@ -290,16 +290,6 @@ export type IntegrationsLabels = {
|
|
|
290
290
|
deactivate: string;
|
|
291
291
|
delete: string;
|
|
292
292
|
};
|
|
293
|
-
writeMode: {
|
|
294
|
-
upsert: string;
|
|
295
|
-
append: string;
|
|
296
|
-
replace: string;
|
|
297
|
-
};
|
|
298
|
-
missingPolicy: {
|
|
299
|
-
ignore: string;
|
|
300
|
-
deactivate: string;
|
|
301
|
-
delete: string;
|
|
302
|
-
};
|
|
303
293
|
common: {
|
|
304
294
|
save: string;
|
|
305
295
|
cancel: string;
|
|
@@ -31,7 +31,6 @@
|
|
|
31
31
|
purpose: 'See what would happen, then let it happen.'
|
|
32
32
|
},
|
|
33
33
|
lockedHint: 'Finish the step before this one first.',
|
|
34
|
-
doneHint: 'Done',
|
|
35
34
|
evidence: {
|
|
36
35
|
connection: (name)=>`Connected to ${name}`,
|
|
37
36
|
endpoints: (total, sampled)=>sampled === total ? `${total} call(s), all tried` : `${total} call(s), ${sampled} tried`,
|
|
@@ -51,7 +50,7 @@
|
|
|
51
50
|
loadFailed: 'This list could not be loaded',
|
|
52
51
|
retry: 'Try again',
|
|
53
52
|
off: 'Off',
|
|
54
|
-
change: '
|
|
53
|
+
change: 'Back to connections',
|
|
55
54
|
editTitle: 'Connection',
|
|
56
55
|
newTitle: 'New connection',
|
|
57
56
|
enabledBadge: 'Enabled',
|
|
@@ -62,7 +61,9 @@
|
|
|
62
61
|
runs: {
|
|
63
62
|
summary: (target, writeMode, missing)=>`Writes into ${target} · ${writeMode} · missing rows: ${missing}`,
|
|
64
63
|
preview: 'Preview',
|
|
64
|
+
previewing: 'Previewing…',
|
|
65
65
|
start: 'Run import',
|
|
66
|
+
starting: 'Starting…',
|
|
66
67
|
cancel: 'Cancel',
|
|
67
68
|
previewExplainTitle: 'Preview before you run',
|
|
68
69
|
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.',
|
|
@@ -123,7 +124,6 @@
|
|
|
123
124
|
bindingsTitle: 'Parameter values',
|
|
124
125
|
bindingsUnbound: (names)=>`${names} — an import cannot run until every one of them is bound. Add a binding for each below.`,
|
|
125
126
|
bindingsNone: 'This call takes no parameters. Add a {name} token to its path if it needs one.',
|
|
126
|
-
bindFrom: 'Value from',
|
|
127
127
|
bindStatic: 'A fixed value',
|
|
128
128
|
bindEndpoint: 'A field of another call',
|
|
129
129
|
bindEntity: 'A column of one of our tables',
|
|
@@ -205,7 +205,6 @@
|
|
|
205
205
|
sampleFirstTitle: 'Try the call first',
|
|
206
206
|
sampleFirstBody: 'No field names are known yet. Trying the selected call lists what the response really returns; until then a source has to be typed from memory, and a typo shows up only as an empty column once the import has already run.',
|
|
207
207
|
nothingMapped: 'Nothing is mapped yet, so a run would write empty records. Add a field to begin.',
|
|
208
|
-
parentPrefixHint: 'to take the value from the parent call’s record instead of this one.',
|
|
209
208
|
templateHint: 'to build one value from several — use it when no single field is unique on its own, which is what the matching column needs.',
|
|
210
209
|
source: 'Source',
|
|
211
210
|
sourcePlaceholder: 'employeeNo',
|
|
@@ -251,6 +250,7 @@
|
|
|
251
250
|
clear: 'Clear',
|
|
252
251
|
testHeading: 'Does it answer?',
|
|
253
252
|
testRun: 'Test the connection',
|
|
253
|
+
testing: 'Testing…',
|
|
254
254
|
testOkTitle: 'Connection succeeded',
|
|
255
255
|
testOkBadge: 'Reachable',
|
|
256
256
|
testFailedTitle: 'Connection failed',
|
|
@@ -278,16 +278,6 @@
|
|
|
278
278
|
deactivate: 'Reversible. The row survives and history that refers to it stays readable.',
|
|
279
279
|
delete: 'Permanent. A source that returns a short page for any reason takes rows with it.'
|
|
280
280
|
},
|
|
281
|
-
writeMode: {
|
|
282
|
-
upsert: 'update what matches, insert the rest',
|
|
283
|
-
append: 'always insert, never match',
|
|
284
|
-
replace: 'empty the table, then insert'
|
|
285
|
-
},
|
|
286
|
-
missingPolicy: {
|
|
287
|
-
ignore: 'leave them exactly as they are',
|
|
288
|
-
deactivate: 'mark them inactive',
|
|
289
|
-
delete: 'delete them'
|
|
290
|
-
},
|
|
291
281
|
common: {
|
|
292
282
|
save: 'Save',
|
|
293
283
|
cancel: 'Cancel',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nucleus-core-ts",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.845",
|
|
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",
|