nucleus-core-ts 0.10.147 → 0.10.151
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/LICENSE +48 -48
- package/dist/.build-ok +1 -1
- package/dist/fe/components/AuthorizationPage/labels.js +13 -13
- package/dist/fe/components/Captcha/labels.js +17 -17
- package/dist/fe/components/ChatPanel/helpers.js +17 -17
- package/dist/fe/components/ChatPanel/labels.js +16 -16
- package/dist/fe/components/DataTable/labels.js +16 -16
- package/dist/fe/components/DevicesPage/components/DeviceCard.js +15 -15
- package/dist/fe/components/DevicesPage/components/DevicesPage.js +9 -9
- package/dist/fe/components/DevicesPage/labels.js +12 -12
- package/dist/fe/components/ForgotPasswordPage/labels.js +8 -8
- package/dist/fe/components/FormBuilder/components/FormField.js +5 -5
- package/dist/fe/components/FormBuilder/utils/fieldMapping.js +9 -9
- package/dist/fe/components/IntegrationsPage/components/ConnectionAuthFields.js +8 -8
- package/dist/fe/components/IntegrationsPage/components/ConnectionBasicFields.js +3 -3
- package/dist/fe/components/IntegrationsPage/components/ConnectionExport.js +23 -23
- package/dist/fe/components/IntegrationsPage/components/ConnectionForm.js +22 -22
- package/dist/fe/components/IntegrationsPage/components/ConnectionList.js +7 -7
- package/dist/fe/components/IntegrationsPage/components/ConnectionOauthFields.js +6 -6
- package/dist/fe/components/IntegrationsPage/components/ConnectionSettingsTab.js +24 -24
- package/dist/fe/components/IntegrationsPage/components/ConnectionSwitcher.js +19 -19
- package/dist/fe/components/IntegrationsPage/components/ConnectionTabs.js +15 -15
- package/dist/fe/components/IntegrationsPage/components/ConnectionTest.js +21 -21
- package/dist/fe/components/IntegrationsPage/components/ConnectionTransfer.js +18 -18
- package/dist/fe/components/IntegrationsPage/components/CredentialFields.js +19 -19
- package/dist/fe/components/IntegrationsPage/components/CredentialSlotRow.js +8 -8
- package/dist/fe/components/IntegrationsPage/components/EndpointForm.js +7 -7
- package/dist/fe/components/IntegrationsPage/components/EndpointFormFields.js +12 -12
- package/dist/fe/components/IntegrationsPage/components/EndpointList.js +7 -7
- package/dist/fe/components/IntegrationsPage/components/EndpointSample.js +36 -36
- package/dist/fe/components/IntegrationsPage/components/EndpointsTab.js +29 -29
- package/dist/fe/components/IntegrationsPage/components/FieldMappingRow.js +7 -7
- package/dist/fe/components/IntegrationsPage/components/FieldMappingRows.js +6 -6
- package/dist/fe/components/IntegrationsPage/components/IntegrationsPage.js +60 -60
- package/dist/fe/components/IntegrationsPage/components/MappingAccountFields.js +15 -15
- package/dist/fe/components/IntegrationsPage/components/MappingEditor.js +9 -9
- package/dist/fe/components/IntegrationsPage/components/MappingList.js +23 -23
- package/dist/fe/components/IntegrationsPage/components/MappingPasswordField.js +12 -12
- package/dist/fe/components/IntegrationsPage/components/MappingScheduleFields.js +23 -23
- package/dist/fe/components/IntegrationsPage/components/MappingWriteModeFields.js +31 -31
- package/dist/fe/components/IntegrationsPage/components/MappingsTab.js +56 -56
- package/dist/fe/components/IntegrationsPage/components/PaginationFields.js +11 -11
- package/dist/fe/components/IntegrationsPage/components/ParamBindingRows.js +15 -15
- package/dist/fe/components/IntegrationsPage/components/Primitives.js +28 -28
- package/dist/fe/components/IntegrationsPage/components/ReferenceCheckRows.js +9 -9
- package/dist/fe/components/IntegrationsPage/components/RunConfirmStep.js +6 -6
- package/dist/fe/components/IntegrationsPage/components/RunHistory.js +6 -6
- package/dist/fe/components/IntegrationsPage/components/RunHistoryRow.js +9 -9
- package/dist/fe/components/IntegrationsPage/components/RunPanel.js +40 -40
- package/dist/fe/components/IntegrationsPage/components/RunPasswordField.js +18 -18
- package/dist/fe/components/IntegrationsPage/components/RunPlanIssues.js +6 -6
- package/dist/fe/components/IntegrationsPage/components/RunPlanReferences.js +12 -12
- package/dist/fe/components/IntegrationsPage/components/RunPlanSuggestions.js +11 -11
- package/dist/fe/components/IntegrationsPage/components/RunPlanSummary.js +6 -6
- package/dist/fe/components/IntegrationsPage/components/RunProgressBar.js +6 -6
- package/dist/fe/components/IntegrationsPage/components/RunRevertConflicts.js +6 -6
- package/dist/fe/components/IntegrationsPage/components/Skeletons.js +7 -7
- package/dist/fe/components/IntegrationsPage/components/authOptions.js +4 -4
- package/dist/fe/components/IntegrationsPage/components/connectionDraft.js +27 -27
- package/dist/fe/components/IntegrationsPage/components/credentialSlots.js +25 -25
- package/dist/fe/components/IntegrationsPage/components/deleteIsConfirmed.js +9 -9
- package/dist/fe/components/IntegrationsPage/components/fieldMappingOptions.js +4 -4
- package/dist/fe/components/IntegrationsPage/components/formSupport.js +34 -34
- package/dist/fe/components/IntegrationsPage/components/jsonArray.js +14 -14
- package/dist/fe/components/IntegrationsPage/components/jsonObjectField.js +15 -15
- package/dist/fe/components/IntegrationsPage/components/mappingDraft.js +10 -10
- package/dist/fe/components/IntegrationsPage/components/mappingStaleness.js +22 -22
- package/dist/fe/components/IntegrationsPage/components/nextRuns.js +8 -8
- package/dist/fe/components/IntegrationsPage/components/passwordIsNeeded.js +13 -13
- package/dist/fe/components/IntegrationsPage/components/pathParams.js +6 -6
- package/dist/fe/components/IntegrationsPage/components/runFormat.js +18 -18
- package/dist/fe/components/IntegrationsPage/components/settleOwnership.js +18 -18
- package/dist/fe/components/IntegrationsPage/components/useInfiniteList.js +8 -8
- package/dist/fe/components/IntegrationsPage/store/index.js +20 -20
- package/dist/fe/components/IntegrationsPage/store/upsertInto.js +12 -12
- package/dist/fe/components/IntegrationsPage/text/context.js +6 -6
- package/dist/fe/components/IntegrationsPage/text/index.js +18 -18
- package/dist/fe/components/IntegrationsPage/theme/index.js +29 -29
- package/dist/fe/components/IntegrationsPage/types/records.js +10 -10
- package/dist/fe/components/IntegrationsPage/types/results.js +3 -3
- package/dist/fe/components/NotificationCenter/labels.js +17 -17
- package/dist/fe/components/ProfilePage/labels.js +10 -10
- package/dist/fe/components/ResetPasswordPage/labels.js +4 -4
- package/dist/fe/components/SetPasswordPage/labels.js +11 -11
- package/dist/fe/components/UsersPage/labels.js +14 -14
- package/dist/fe/utils/columnUtils.js +24 -24
- package/dist/fe/utils/searchFold.js +36 -36
- package/dist/index.js +6 -6
- package/dist/server.js +6 -6
- package/dist/src/Client/Proxy/authz.js +4 -4
- package/dist/src/Client/Proxy/httpProxy.js +136 -136
- package/dist/src/Client/Proxy/largeBody.js +29 -29
- package/dist/src/Client/Proxy/utils.js +32 -32
- package/dist/src/Client/Proxy/wsProxy.js +59 -59
- package/dist/src/Managers/Redis/index.d.ts +14 -0
- package/dist/src/Nucleus/routes/auth/admin/impersonationStore.d.ts +3 -4
- package/dist/src/Nucleus/routes/auth/refresh/issuedPair.d.ts +67 -0
- package/dist/src/Nucleus/routes/auth/refresh/recordIssued.d.ts +15 -6
- package/dist/src/Nucleus/routes/auth/refresh/refreshChain.d.ts +56 -0
- package/dist/src/Services/Auth/SessionStore/utils.d.ts +2 -0
- package/dist/src/Shared/cron.js +71 -71
- package/dist/src/Shared/redactSecrets.js +156 -156
- package/dist/src/Shared/redaction/bare.js +61 -61
- package/dist/src/Shared/redaction/floor.js +48 -48
- package/dist/src/Shared/redaction/freeText.js +70 -70
- package/dist/src/Shared/redaction/json.js +30 -30
- package/dist/src/Shared/redaction/lists.js +23 -23
- package/dist/src/Shared/redaction/markers.js +28 -28
- package/dist/src/Shared/redaction/names.js +49 -49
- package/dist/src/Shared/redaction/pairs.js +54 -54
- package/dist/src/Shared/redaction/secretString.js +29 -29
- package/dist/src/Shared/redaction/structured.js +52 -52
- package/dist/src/Shared/redaction/walk.js +25 -25
- package/infra/scripts/generate-project.ts +766 -766
- package/infra/templates/README.md +57 -57
- package/infra/templates/backend/.dockerignore +9 -9
- package/infra/templates/backend/.env.example +15 -15
- package/infra/templates/backend/README.md +28 -28
- package/infra/templates/backend/azure-pipelines.yml +135 -135
- package/infra/templates/backend/k8s/Dockerfile +31 -31
- package/infra/templates/backend/k8s/configmap.yaml +26 -26
- package/infra/templates/backend/k8s/deployment.yaml +85 -85
- package/infra/templates/backend/k8s/pvc.yaml +12 -12
- package/infra/templates/backend/k8s/service.yaml +13 -13
- package/infra/templates/backend/tsconfig.json +22 -22
- package/infra/templates/frontend/.dockerignore +10 -10
- package/infra/templates/frontend/.env.example +8 -8
- package/infra/templates/frontend/.env.local.example +10 -10
- package/infra/templates/frontend/README.md +69 -69
- package/infra/templates/frontend/app/[tablename]/page.tsx +37 -37
- package/infra/templates/frontend/app/_components/AuthWrapper/index.tsx +63 -63
- package/infra/templates/frontend/app/authorization/page.tsx +43 -43
- package/infra/templates/frontend/app/change-password/page.tsx +23 -23
- package/infra/templates/frontend/app/devices/page.tsx +127 -127
- package/infra/templates/frontend/app/forgot-password/page.tsx +20 -20
- package/infra/templates/frontend/app/globals.css +28 -28
- package/infra/templates/frontend/app/layout.tsx +35 -35
- package/infra/templates/frontend/app/login/page.tsx +33 -33
- package/infra/templates/frontend/app/page.tsx +565 -565
- package/infra/templates/frontend/app/profile/page.tsx +92 -92
- package/infra/templates/frontend/app/register/page.tsx +27 -27
- package/infra/templates/frontend/app/reset-password/page.tsx +60 -60
- package/infra/templates/frontend/app/set-password/page.tsx +43 -43
- package/infra/templates/frontend/app/ui-docs/page.tsx +52 -52
- package/infra/templates/frontend/app/users/page.tsx +52 -52
- package/infra/templates/frontend/app/verify-email/page.tsx +41 -41
- package/infra/templates/frontend/app/verify-magic-link/page.tsx +46 -46
- package/infra/templates/frontend/azure-pipelines.yml +137 -137
- package/infra/templates/frontend/code-and-design-principles.md +438 -438
- package/infra/templates/frontend/k8s/Dockerfile +50 -50
- package/infra/templates/frontend/k8s/configmap.yaml +18 -18
- package/infra/templates/frontend/k8s/deployment.yaml +72 -72
- package/infra/templates/frontend/k8s/ingress.yaml +36 -36
- package/infra/templates/frontend/k8s/service.yaml +14 -14
- package/infra/templates/frontend/lib/api/endpoints.ts +37 -37
- package/infra/templates/frontend/lib/api/factory.ts +44 -44
- package/infra/templates/frontend/lib/api/hook.ts +10 -10
- package/infra/templates/frontend/next.config.ts +19 -19
- package/infra/templates/frontend/package.json +34 -34
- package/infra/templates/frontend/postcss.config.mjs +5 -5
- package/infra/templates/frontend/server.ts +114 -114
- package/infra/templates/frontend/tsconfig.json +23 -23
- package/package.json +1 -1
- package/public/components/CTA.tsx +25 -25
- package/public/components/Capabilities.tsx +67 -67
- package/public/components/Features.tsx +107 -107
- package/public/components/Footer.tsx +58 -58
- package/public/components/Hero.tsx +51 -51
- package/public/components/Managers.tsx +55 -55
- package/public/components/Nav.tsx +91 -91
- package/public/components/Stack.tsx +36 -36
- package/public/components/docs/Docs.tsx +60 -60
- package/public/components/docs/DocsContent.tsx +176 -176
- package/public/components/docs/DocsRightSidebar.tsx +53 -53
- package/public/components/docs/DocsSidebar.tsx +54 -54
- package/public/components/docs/content/authentication.ts +836 -836
- package/public/components/docs/content/authorization.ts +268 -268
- package/public/components/docs/content/backupRestore.ts +173 -173
- package/public/components/docs/content/clientModule.ts +287 -287
- package/public/components/docs/content/entityCrud.ts +682 -682
- package/public/components/docs/content/monitoring.ts +313 -313
- package/public/components/docs/content/multiTenant.ts +269 -269
- package/public/components/docs/content/oauthApiKeys.ts +325 -325
- package/public/components/docs/content/pubsub.ts +131 -131
- package/public/components/docs/content/storageCdn.ts +171 -171
- package/public/components/docs/content/verification.ts +289 -289
- package/public/components/docs/docsData.ts +113 -113
- package/public/components/docs/index.ts +6 -6
- package/public/components/index.ts +9 -9
- package/public/docs.css +629 -629
- package/public/index.html +81 -81
- package/public/index.tsx +42 -42
- package/public/store.ts +76 -76
- package/public/styles.css +858 -858
- package/schemas/config.nucleus.json +2651 -2651
- package/scripts/audit-purge-noise.ts +120 -120
- package/scripts/build.ts +582 -582
- package/scripts/generate-system-tables-types.ts +18 -18
- package/scripts/generate-types-schema.test.ts +73 -73
- package/scripts/generate-types-schema.ts +1284 -1284
- package/scripts/packagedScripts.test.ts +87 -87
- package/scripts/publish.ts +276 -276
- package/scripts/skillCitations.test.ts +107 -107
- package/scripts/verify-publishable.ts +40 -40
|
@@ -7,12 +7,12 @@ import { integrationsPageTheme } from '../theme';
|
|
|
7
7
|
import { FieldMappingRow } from './FieldMappingRow';
|
|
8
8
|
import { EMPTY_FIELD_MAPPING, FIELD_MAPPING_COLUMNS, FIELD_MAPPING_GRID, PARENT_PREFIX } from './fieldMappingOptions';
|
|
9
9
|
import { Notice, Section } from './Primitives';
|
|
10
|
-
/**
|
|
11
|
-
* The rows that say which incoming value becomes which column.
|
|
12
|
-
*
|
|
13
|
-
* The order is kept because the writer applies the rows in it: two rows aimed at
|
|
14
|
-
* the same column mean the later one wins, and that is only predictable if the
|
|
15
|
-
* operator can see the order and change it.
|
|
10
|
+
/**
|
|
11
|
+
* The rows that say which incoming value becomes which column.
|
|
12
|
+
*
|
|
13
|
+
* The order is kept because the writer applies the rows in it: two rows aimed at
|
|
14
|
+
* the same column mean the later one wins, and that is only predictable if the
|
|
15
|
+
* operator can see the order and change it.
|
|
16
16
|
*/ const theme = integrationsPageTheme;
|
|
17
17
|
export function FieldMappingRows({ rows, sourceFields, targetFields, disabled, onChange }) {
|
|
18
18
|
const labels = useLabels();
|
|
@@ -16,13 +16,13 @@ import { EndpointsTab } from './EndpointsTab';
|
|
|
16
16
|
import { MappingsTab } from './MappingsTab';
|
|
17
17
|
import { Notice } from './Primitives';
|
|
18
18
|
import { RunsTab } from './RunsTab';
|
|
19
|
-
/**
|
|
20
|
-
* The shell.
|
|
21
|
-
*
|
|
22
|
-
* It owns three things and nothing else: the selection rail, which section of the
|
|
23
|
-
* selected connection is on screen, and the one place an error is reported. Every
|
|
24
|
-
* call it needs arrives as an injected action, and each section fetches its own
|
|
25
|
-
* page from the server rather than being handed a list to slice.
|
|
19
|
+
/**
|
|
20
|
+
* The shell.
|
|
21
|
+
*
|
|
22
|
+
* It owns three things and nothing else: the selection rail, which section of the
|
|
23
|
+
* selected connection is on screen, and the one place an error is reported. Every
|
|
24
|
+
* call it needs arrives as an injected action, and each section fetches its own
|
|
25
|
+
* page from the server rather than being handed a list to slice.
|
|
26
26
|
*/ gsap.registerPlugin(useGSAP);
|
|
27
27
|
const theme = integrationsPageTheme;
|
|
28
28
|
export function IntegrationsPage({ title = 'Integrations', subtitle = 'Connect an external service, describe the data you want and import it on your terms.', className, actions, labels: labelsOverride, focusTargetEntity, focusStep, onSubscribeProgress }) {
|
|
@@ -33,15 +33,15 @@ export function IntegrationsPage({ title = 'Integrations', subtitle = 'Connect a
|
|
|
33
33
|
const labels = resolveIntegrationsLabels(labelsOverride);
|
|
34
34
|
const containerRef = useRef(null);
|
|
35
35
|
const contentRef = useRef(null);
|
|
36
|
-
/**
|
|
37
|
-
* Bumped after a connection is written, to pull the rail again.
|
|
38
|
-
*
|
|
39
|
-
* `ConnectionList` and `ConnectionSettingsTab` were both built to be told
|
|
40
|
-
* about a save and neither was ever told: the shell mounted them with only
|
|
41
|
-
* `actions`. Creating the first connection therefore selected a record the
|
|
42
|
-
* rail had never fetched, so the rail kept saying "0 connections" while the
|
|
43
|
-
* detail treated the missing record as still loading and sat on skeletons
|
|
44
|
-
* until the page was reloaded by hand. The write itself had succeeded.
|
|
36
|
+
/**
|
|
37
|
+
* Bumped after a connection is written, to pull the rail again.
|
|
38
|
+
*
|
|
39
|
+
* `ConnectionList` and `ConnectionSettingsTab` were both built to be told
|
|
40
|
+
* about a save and neither was ever told: the shell mounted them with only
|
|
41
|
+
* `actions`. Creating the first connection therefore selected a record the
|
|
42
|
+
* rail had never fetched, so the rail kept saying "0 connections" while the
|
|
43
|
+
* detail treated the missing record as still loading and sat on skeletons
|
|
44
|
+
* until the page was reloaded by hand. The write itself had succeeded.
|
|
45
45
|
*/ const [sourcesReloadToken, setSourcesReloadToken] = useState(0);
|
|
46
46
|
const handleSourceSaved = (saved)=>{
|
|
47
47
|
// Into the store FIRST. Bumping the token only tells the rail to refetch,
|
|
@@ -51,26 +51,26 @@ export function IntegrationsPage({ title = 'Integrations', subtitle = 'Connect a
|
|
|
51
51
|
store.upsertSource(saved);
|
|
52
52
|
setSourcesReloadToken((token)=>token + 1);
|
|
53
53
|
};
|
|
54
|
-
/**
|
|
55
|
-
* A connection was deleted.
|
|
56
|
-
*
|
|
57
|
-
* Nothing about it is worth keeping on screen — the tabs below would be
|
|
58
|
-
* showing the calls and mappings of a record that no longer exists — so the
|
|
59
|
-
* selection is dropped, the rail comes back, and the list is asked again.
|
|
60
|
-
* `forgetFocusOnLeave` matters here too: arriving from a table set a focus
|
|
61
|
-
* that would otherwise re-select the next connection instantly.
|
|
54
|
+
/**
|
|
55
|
+
* A connection was deleted.
|
|
56
|
+
*
|
|
57
|
+
* Nothing about it is worth keeping on screen — the tabs below would be
|
|
58
|
+
* showing the calls and mappings of a record that no longer exists — so the
|
|
59
|
+
* selection is dropped, the rail comes back, and the list is asked again.
|
|
60
|
+
* `forgetFocusOnLeave` matters here too: arriving from a table set a focus
|
|
61
|
+
* that would otherwise re-select the next connection instantly.
|
|
62
62
|
*/ const handleSourceDeleted = ()=>{
|
|
63
63
|
forgetFocusOnLeave();
|
|
64
64
|
setSourcesReloadToken((token)=>token + 1);
|
|
65
65
|
};
|
|
66
|
-
/**
|
|
67
|
-
* How far this connection has got, asked for directly.
|
|
68
|
-
*
|
|
69
|
-
* The step strip promises to say where you are, so it cannot depend on having
|
|
70
|
-
* VISITED the step that counts — on a fresh load every total was zero and the
|
|
71
|
-
* strip told an operator with five calls, three mappings and two finished
|
|
72
|
-
* imports to "finish the previous step first". Three counting requests, one
|
|
73
|
-
* row each, are the price of a strip that is telling the truth.
|
|
66
|
+
/**
|
|
67
|
+
* How far this connection has got, asked for directly.
|
|
68
|
+
*
|
|
69
|
+
* The step strip promises to say where you are, so it cannot depend on having
|
|
70
|
+
* VISITED the step that counts — on a fresh load every total was zero and the
|
|
71
|
+
* strip told an operator with five calls, three mappings and two finished
|
|
72
|
+
* imports to "finish the previous step first". Three counting requests, one
|
|
73
|
+
* row each, are the price of a strip that is telling the truth.
|
|
74
74
|
*/ const loadProgress = useEffectEvent((sourceId)=>{
|
|
75
75
|
const one = {
|
|
76
76
|
page: 1,
|
|
@@ -100,13 +100,13 @@ export function IntegrationsPage({ title = 'Integrations', subtitle = 'Connect a
|
|
|
100
100
|
store.selectedSourceId,
|
|
101
101
|
sourcesReloadToken
|
|
102
102
|
]);
|
|
103
|
-
/**
|
|
104
|
-
* Somebody arrived from the table they want filled.
|
|
105
|
-
*
|
|
106
|
-
* They have already said which table; being shown a connection chooser and
|
|
107
|
-
* four mappings is asking the same question again. So the entity is handed to
|
|
108
|
-
* the mappings step, and — when there is only one connection to be meant — it
|
|
109
|
-
* is chosen for them. With several, the choice is real and stays theirs.
|
|
103
|
+
/**
|
|
104
|
+
* Somebody arrived from the table they want filled.
|
|
105
|
+
*
|
|
106
|
+
* They have already said which table; being shown a connection chooser and
|
|
107
|
+
* four mappings is asking the same question again. So the entity is handed to
|
|
108
|
+
* the mappings step, and — when there is only one connection to be meant — it
|
|
109
|
+
* is chosen for them. With several, the choice is real and stays theirs.
|
|
110
110
|
*/ const takeFocus = useEffectEvent(()=>{
|
|
111
111
|
if (!focusTargetEntity) return;
|
|
112
112
|
store.setFocusTargetEntity(focusTargetEntity, focusStep ?? 'mapping');
|
|
@@ -117,13 +117,13 @@ export function IntegrationsPage({ title = 'Integrations', subtitle = 'Connect a
|
|
|
117
117
|
focusTargetEntity,
|
|
118
118
|
focusStep
|
|
119
119
|
]);
|
|
120
|
-
/**
|
|
121
|
-
* Chosen for them ONCE, and never again.
|
|
122
|
-
*
|
|
123
|
-
* This used to re-choose whenever nothing was selected — so pressing "Change"
|
|
124
|
-
* cleared the connection and the same effect immediately put it back. There
|
|
125
|
-
* was no way to reach the list again short of reloading the page, and the
|
|
126
|
-
* button looked broken rather than overruled.
|
|
120
|
+
/**
|
|
121
|
+
* Chosen for them ONCE, and never again.
|
|
122
|
+
*
|
|
123
|
+
* This used to re-choose whenever nothing was selected — so pressing "Change"
|
|
124
|
+
* cleared the connection and the same effect immediately put it back. There
|
|
125
|
+
* was no way to reach the list again short of reloading the page, and the
|
|
126
|
+
* button looked broken rather than overruled.
|
|
127
127
|
*/ const autoSelected = useRef(false);
|
|
128
128
|
const followFocus = useEffectEvent(()=>{
|
|
129
129
|
if (!store.focusTargetEntity) return;
|
|
@@ -136,13 +136,13 @@ export function IntegrationsPage({ title = 'Integrations', subtitle = 'Connect a
|
|
|
136
136
|
}
|
|
137
137
|
if (store.tab !== 'mappings') store.setTab('mappings');
|
|
138
138
|
});
|
|
139
|
-
/**
|
|
140
|
-
* Leaving a connection spends the arrival.
|
|
141
|
-
*
|
|
142
|
-
* Somebody who pressed "Change" is choosing a different connection; the table
|
|
143
|
-
* they arrived asking about is no longer what they are doing, and carrying it
|
|
144
|
-
* forward would jump them into a mapping of a connection they had not picked
|
|
145
|
-
* yet.
|
|
139
|
+
/**
|
|
140
|
+
* Leaving a connection spends the arrival.
|
|
141
|
+
*
|
|
142
|
+
* Somebody who pressed "Change" is choosing a different connection; the table
|
|
143
|
+
* they arrived asking about is no longer what they are doing, and carrying it
|
|
144
|
+
* forward would jump them into a mapping of a connection they had not picked
|
|
145
|
+
* yet.
|
|
146
146
|
*/ const forgetFocusOnLeave = useEffectEvent(()=>{
|
|
147
147
|
if (autoSelected.current && !store.selectedSourceId && store.focusTargetEntity) {
|
|
148
148
|
store.setFocusTargetEntity(null);
|
|
@@ -195,13 +195,13 @@ export function IntegrationsPage({ title = 'Integrations', subtitle = 'Connect a
|
|
|
195
195
|
};
|
|
196
196
|
const tab = store.tab;
|
|
197
197
|
const hasConnection = store.selectedSourceId !== null;
|
|
198
|
-
/**
|
|
199
|
-
* What each step can say about itself.
|
|
200
|
-
*
|
|
201
|
-
* Evidence rather than a bare count: "3 calls, all tried" answers a question a
|
|
202
|
-
* number does not — whether the thing was only declared or actually run. A
|
|
203
|
-
* step whose prerequisite is missing is marked locked rather than hidden,
|
|
204
|
-
* because a step you cannot see is one you cannot understand the order of.
|
|
198
|
+
/**
|
|
199
|
+
* What each step can say about itself.
|
|
200
|
+
*
|
|
201
|
+
* Evidence rather than a bare count: "3 calls, all tried" answers a question a
|
|
202
|
+
* number does not — whether the thing was only declared or actually run. A
|
|
203
|
+
* step whose prerequisite is missing is marked locked rather than hidden,
|
|
204
|
+
* because a step you cannot see is one you cannot understand the order of.
|
|
205
205
|
*/ const selectedSource = store.sources.find((s)=>s.id === store.selectedSourceId);
|
|
206
206
|
const sampled = store.endpoints.filter((e)=>(e.sampleResponse?.records?.length ?? 0) > 0).length;
|
|
207
207
|
const steps = {
|
|
@@ -4,21 +4,21 @@ import { useId } from 'react';
|
|
|
4
4
|
import { useLabels } from '../text/context';
|
|
5
5
|
import { integrationsPageTheme } from '../theme';
|
|
6
6
|
import { Field, Notice, Section } from './Primitives';
|
|
7
|
-
/**
|
|
8
|
-
* A login for every person this import brings in.
|
|
9
|
-
*
|
|
10
|
-
* A directory carries people, not accounts. Importing eight hundred employees
|
|
11
|
-
* and then creating eight hundred logins by hand — matching them up by e-mail,
|
|
12
|
-
* one at a time — is the job nobody gets to, so the import that was supposed to
|
|
13
|
-
* save the afternoon leaves a table of people who cannot sign in.
|
|
14
|
-
*
|
|
15
|
-
* Turned on here, it happens in the SAME import and the same transaction: the
|
|
16
|
-
* person and their login both exist, or neither does. A record whose address is
|
|
17
|
-
* already an account is linked to it rather than given a second one, so running
|
|
18
|
-
* the import again is safe.
|
|
19
|
-
*
|
|
20
|
-
* The password is not on this screen. It belongs to the run — typed once, used
|
|
21
|
-
* for every account that run creates, stored nowhere.
|
|
7
|
+
/**
|
|
8
|
+
* A login for every person this import brings in.
|
|
9
|
+
*
|
|
10
|
+
* A directory carries people, not accounts. Importing eight hundred employees
|
|
11
|
+
* and then creating eight hundred logins by hand — matching them up by e-mail,
|
|
12
|
+
* one at a time — is the job nobody gets to, so the import that was supposed to
|
|
13
|
+
* save the afternoon leaves a table of people who cannot sign in.
|
|
14
|
+
*
|
|
15
|
+
* Turned on here, it happens in the SAME import and the same transaction: the
|
|
16
|
+
* person and their login both exist, or neither does. A record whose address is
|
|
17
|
+
* already an account is linked to it rather than given a second one, so running
|
|
18
|
+
* the import again is safe.
|
|
19
|
+
*
|
|
20
|
+
* The password is not on this screen. It belongs to the run — typed once, used
|
|
21
|
+
* for every account that run creates, stored nowhere.
|
|
22
22
|
*/ const theme = integrationsPageTheme;
|
|
23
23
|
/** Columns that plausibly hold a password, so the guess is a good one. */ const PASSWORD_LIKE = /password|passwd|sifre|şifre|parola/i;
|
|
24
24
|
/** Columns that plausibly point at the account row. */ const LINK_LIKE = /^(user_?id|account_?id|login_?id)$/i;
|
|
@@ -13,15 +13,15 @@ import { mappingDraftIssues } from './mappingDraft';
|
|
|
13
13
|
import { Field, Section } from './Primitives';
|
|
14
14
|
import { ReferenceCheckRows } from './ReferenceCheckRows';
|
|
15
15
|
import { CardSkeleton } from './Skeletons';
|
|
16
|
-
/**
|
|
17
|
-
* Everything about one mapping, on one screen.
|
|
18
|
-
*
|
|
19
|
-
* The draft is local until Save: an import is not a text field where a stray
|
|
20
|
-
* keystroke is harmless, and half a mapping saved between two thoughts is a
|
|
21
|
-
* mapping that could be RUN in that state.
|
|
22
|
-
*
|
|
23
|
-
* Mount it with a `key` of the mapping id so switching rows starts a fresh
|
|
24
|
-
* draft instead of pouring one mapping's edits into another.
|
|
16
|
+
/**
|
|
17
|
+
* Everything about one mapping, on one screen.
|
|
18
|
+
*
|
|
19
|
+
* The draft is local until Save: an import is not a text field where a stray
|
|
20
|
+
* keystroke is harmless, and half a mapping saved between two thoughts is a
|
|
21
|
+
* mapping that could be RUN in that state.
|
|
22
|
+
*
|
|
23
|
+
* Mount it with a `key` of the mapping id so switching rows starts a fresh
|
|
24
|
+
* draft instead of pouring one mapping's edits into another.
|
|
25
25
|
*/ const theme = integrationsPageTheme;
|
|
26
26
|
export function MappingEditor({ mapping, targets, targetsLoading, endpoints, freshSample, saving, deleting, onSave, onDelete, onCancel, onRunImport }) {
|
|
27
27
|
const labels = useLabels();
|
|
@@ -10,12 +10,12 @@ import { Badge, Field, Notice } from './Primitives';
|
|
|
10
10
|
import { formatMoment } from './runFormat';
|
|
11
11
|
import { ListSkeleton } from './Skeletons';
|
|
12
12
|
import { useInfiniteList } from './useInfiniteList';
|
|
13
|
-
/**
|
|
14
|
-
* The mappings of the selected connection, one page at a time.
|
|
15
|
-
*
|
|
16
|
-
* Searching and paging are the server's job; this list only shows what came
|
|
17
|
-
* back. The write mode is on the row rather than only inside the editor because
|
|
18
|
-
* `replace` is the one setting worth spotting before opening anything.
|
|
13
|
+
/**
|
|
14
|
+
* The mappings of the selected connection, one page at a time.
|
|
15
|
+
*
|
|
16
|
+
* Searching and paging are the server's job; this list only shows what came
|
|
17
|
+
* back. The write mode is on the row rather than only inside the editor because
|
|
18
|
+
* `replace` is the one setting worth spotting before opening anything.
|
|
19
19
|
*/ const theme = integrationsPageTheme;
|
|
20
20
|
const PAGE_SIZE = 20;
|
|
21
21
|
const SEARCH_DEBOUNCE_MS = 300;
|
|
@@ -24,14 +24,14 @@ const WRITE_MODE_TONE = {
|
|
|
24
24
|
append: 'neutral',
|
|
25
25
|
replace: 'danger'
|
|
26
26
|
};
|
|
27
|
-
/**
|
|
28
|
-
* When this mapping last ran, in a form that cannot tear on hydration.
|
|
29
|
-
*
|
|
30
|
-
* It used to be `toLocaleString()`, which reads the SERVER's locale and zone on
|
|
31
|
-
* the server and the BROWSER's on the client — two different strings for the
|
|
32
|
-
* same row, and React replacing one with the other after mount. It also meant a
|
|
33
|
-
* Turkish installation was shown 8/2/2026, which in Turkey reads as the 8th of
|
|
34
|
-
* February. `formatMoment` is UTC, fixed-width and labelled as such.
|
|
27
|
+
/**
|
|
28
|
+
* When this mapping last ran, in a form that cannot tear on hydration.
|
|
29
|
+
*
|
|
30
|
+
* It used to be `toLocaleString()`, which reads the SERVER's locale and zone on
|
|
31
|
+
* the server and the BROWSER's on the client — two different strings for the
|
|
32
|
+
* same row, and React replacing one with the other after mount. It also meant a
|
|
33
|
+
* Turkish installation was shown 8/2/2026, which in Turkey reads as the 8th of
|
|
34
|
+
* February. `formatMoment` is UTC, fixed-width and labelled as such.
|
|
35
35
|
*/ function describeLastRun(value, labels) {
|
|
36
36
|
if (!value) return labels.neverRun;
|
|
37
37
|
const when = new Date(value);
|
|
@@ -40,15 +40,15 @@ const WRITE_MODE_TONE = {
|
|
|
40
40
|
export function MappingList({ actions, sourceId, selectedId, reloadToken, onSelect, onRun }) {
|
|
41
41
|
const t = useLabels().errors;
|
|
42
42
|
const labels = useLabels();
|
|
43
|
-
/*
|
|
44
|
-
* Read AFTER mount, never during render.
|
|
45
|
-
*
|
|
46
|
-
* "How long ago" is a function of the clock, and the clock on the server that
|
|
47
|
-
* renders this HTML is not the one in the browser that hydrates it. Computing
|
|
48
|
-
* it in render is what put two different strings in the same row and had React
|
|
49
|
-
* swap one for the other on mount — the same fault the last-run timestamp was
|
|
50
|
-
* already fixed for. Null until mounted means the first paint has no age
|
|
51
|
-
* badges at all, which both renders agree on.
|
|
43
|
+
/*
|
|
44
|
+
* Read AFTER mount, never during render.
|
|
45
|
+
*
|
|
46
|
+
* "How long ago" is a function of the clock, and the clock on the server that
|
|
47
|
+
* renders this HTML is not the one in the browser that hydrates it. Computing
|
|
48
|
+
* it in render is what put two different strings in the same row and had React
|
|
49
|
+
* swap one for the other on mount — the same fault the last-run timestamp was
|
|
50
|
+
* already fixed for. Null until mounted means the first paint has no age
|
|
51
|
+
* badges at all, which both renders agree on.
|
|
52
52
|
*/ const [now, setNow] = useState(null);
|
|
53
53
|
useEffect(()=>{
|
|
54
54
|
setNow(Date.now());
|
|
@@ -4,18 +4,18 @@ import { useId } from 'react';
|
|
|
4
4
|
import { useLabels } from '../text/context';
|
|
5
5
|
import { integrationsPageTheme } from '../theme';
|
|
6
6
|
import { Field } from './Primitives';
|
|
7
|
-
/**
|
|
8
|
-
* Where a shared opening password lands, if this import creates accounts.
|
|
9
|
-
*
|
|
10
|
-
* The mapping says WHICH COLUMN; the run says what the password is. Nothing
|
|
11
|
-
* about the password is stored here — an import that creates eight hundred
|
|
12
|
-
* accounts must not also leave the key to them sitting in a config table.
|
|
13
|
-
*
|
|
14
|
-
* Why a shared password at all: a source system carries people, not
|
|
15
|
-
* credentials. Eight hundred generated passwords is eight hundred things
|
|
16
|
-
* somebody has to deliver by hand. One opening password, typed at run time and
|
|
17
|
-
* changed by each person afterwards, is the only version of this that a person
|
|
18
|
-
* can actually carry out.
|
|
7
|
+
/**
|
|
8
|
+
* Where a shared opening password lands, if this import creates accounts.
|
|
9
|
+
*
|
|
10
|
+
* The mapping says WHICH COLUMN; the run says what the password is. Nothing
|
|
11
|
+
* about the password is stored here — an import that creates eight hundred
|
|
12
|
+
* accounts must not also leave the key to them sitting in a config table.
|
|
13
|
+
*
|
|
14
|
+
* Why a shared password at all: a source system carries people, not
|
|
15
|
+
* credentials. Eight hundred generated passwords is eight hundred things
|
|
16
|
+
* somebody has to deliver by hand. One opening password, typed at run time and
|
|
17
|
+
* changed by each person afterwards, is the only version of this that a person
|
|
18
|
+
* can actually carry out.
|
|
19
19
|
*/ const theme = integrationsPageTheme;
|
|
20
20
|
/** Column names that plausibly hold a password, offered first. */ const LIKELY = /password|passwd|sifre|şifre|parola/i;
|
|
21
21
|
export function MappingPasswordField({ value, targetFields, disabled, onChange }) {
|
|
@@ -6,24 +6,24 @@ import { useLabels } from '../text/context';
|
|
|
6
6
|
import { integrationsPageTheme } from '../theme';
|
|
7
7
|
import { describeNextRuns } from './nextRuns';
|
|
8
8
|
import { Field, Notice, Section } from './Primitives';
|
|
9
|
-
/**
|
|
10
|
-
* Running this import without anybody clicking anything.
|
|
11
|
-
*
|
|
12
|
-
* The mapping has carried a cron, a timezone and an enabled flag since it was
|
|
13
|
-
* first designed, and nothing read them — an operator could fill one in, save
|
|
14
|
-
* it, see it on the screen, and it would never run. This is the screen for the
|
|
15
|
-
* half that now does.
|
|
16
|
-
*
|
|
17
|
-
* The expression is checked as it is typed and read back in plain words. Cron is
|
|
18
|
-
* a language people write once a year, and "0 2 * * *" and "2 0 * * *" look
|
|
19
|
-
* alike until one of them has been importing at two minutes past midnight for a
|
|
20
|
-
* month.
|
|
9
|
+
/**
|
|
10
|
+
* Running this import without anybody clicking anything.
|
|
11
|
+
*
|
|
12
|
+
* The mapping has carried a cron, a timezone and an enabled flag since it was
|
|
13
|
+
* first designed, and nothing read them — an operator could fill one in, save
|
|
14
|
+
* it, see it on the screen, and it would never run. This is the screen for the
|
|
15
|
+
* half that now does.
|
|
16
|
+
*
|
|
17
|
+
* The expression is checked as it is typed and read back in plain words. Cron is
|
|
18
|
+
* a language people write once a year, and "0 2 * * *" and "2 0 * * *" look
|
|
19
|
+
* alike until one of them has been importing at two minutes past midnight for a
|
|
20
|
+
* month.
|
|
21
21
|
*/ const theme = integrationsPageTheme;
|
|
22
|
-
/**
|
|
23
|
-
* Offered because they are what people actually mean, spelled correctly.
|
|
24
|
-
*
|
|
25
|
-
* Built per render rather than held as a module constant: the cron strings are
|
|
26
|
-
* fixed, the words beside them are whatever language the portal runs in.
|
|
22
|
+
/**
|
|
23
|
+
* Offered because they are what people actually mean, spelled correctly.
|
|
24
|
+
*
|
|
25
|
+
* Built per render rather than held as a module constant: the cron strings are
|
|
26
|
+
* fixed, the words beside them are whatever language the portal runs in.
|
|
27
27
|
*/ const presetsOf = (t)=>[
|
|
28
28
|
{
|
|
29
29
|
label: t.nightly,
|
|
@@ -154,12 +154,12 @@ export function MappingScheduleFields({ enabled, cron, timezone, needsPassword,
|
|
|
154
154
|
}
|
|
155
155
|
/** The next few firings, listed. Its own component so it can hold the clock. */ function NextRunsList({ cron, timezone }) {
|
|
156
156
|
const labels = useLabels();
|
|
157
|
-
/*
|
|
158
|
-
* The clock is read after mount, never during render.
|
|
159
|
-
*
|
|
160
|
-
* "From now" on the server and "from now" in the browser are different
|
|
161
|
-
* instants, so rendering this straight away would produce two different
|
|
162
|
-
* lists for one page and tear on hydration.
|
|
157
|
+
/*
|
|
158
|
+
* The clock is read after mount, never during render.
|
|
159
|
+
*
|
|
160
|
+
* "From now" on the server and "from now" in the browser are different
|
|
161
|
+
* instants, so rendering this straight away would produce two different
|
|
162
|
+
* lists for one page and tear on hydration.
|
|
163
163
|
*/ const [from, setFrom] = useState(null);
|
|
164
164
|
useEffect(()=>setFrom(new Date()), [
|
|
165
165
|
cron,
|
|
@@ -3,17 +3,17 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { useLabels } from '../text/context';
|
|
4
4
|
import { integrationsPageTheme } from '../theme';
|
|
5
5
|
import { Field, Notice } from './Primitives';
|
|
6
|
-
/**
|
|
7
|
-
* The choices that decide what an import DOES to the table.
|
|
8
|
-
*
|
|
9
|
-
* The first two are the destructive pair: one decides how the fetched records
|
|
10
|
-
* land, the other decides what happens to the rows the source stopped sending.
|
|
11
|
-
* Every other field on a mapping only moves values around.
|
|
12
|
-
*
|
|
13
|
-
* The rest appear once that second choice can remove something, and they are
|
|
14
|
-
* the two things that separate a synchronisation from a data-loss incident: a
|
|
15
|
-
* mark saying which rows this mapping owns, and a ceiling on how much of the
|
|
16
|
-
* table one run may take away.
|
|
6
|
+
/**
|
|
7
|
+
* The choices that decide what an import DOES to the table.
|
|
8
|
+
*
|
|
9
|
+
* The first two are the destructive pair: one decides how the fetched records
|
|
10
|
+
* land, the other decides what happens to the rows the source stopped sending.
|
|
11
|
+
* Every other field on a mapping only moves values around.
|
|
12
|
+
*
|
|
13
|
+
* The rest appear once that second choice can remove something, and they are
|
|
14
|
+
* the two things that separate a synchronisation from a data-loss incident: a
|
|
15
|
+
* mark saying which rows this mapping owns, and a ceiling on how much of the
|
|
16
|
+
* table one run may take away.
|
|
17
17
|
*/ const theme = integrationsPageTheme;
|
|
18
18
|
const WRITE_MODES = [
|
|
19
19
|
'upsert',
|
|
@@ -213,27 +213,27 @@ export function MappingWriteModeFields({ idPrefix, writeMode, missingRecordPolic
|
|
|
213
213
|
]
|
|
214
214
|
});
|
|
215
215
|
}
|
|
216
|
-
/**
|
|
217
|
-
* Stored as a share, typed as a percentage.
|
|
218
|
-
*
|
|
219
|
-
* The engine compares `removing / existing` against this number, so the column
|
|
220
|
-
* holds 0.2 — but nobody sets a safety limit by thinking in fractions, and 0.2
|
|
221
|
-
* typed into a box labelled "percent" would mean a fifth of a percent. The
|
|
222
|
-
* conversion lives here so the two readings can never drift apart.
|
|
216
|
+
/**
|
|
217
|
+
* Stored as a share, typed as a percentage.
|
|
218
|
+
*
|
|
219
|
+
* The engine compares `removing / existing` against this number, so the column
|
|
220
|
+
* holds 0.2 — but nobody sets a safety limit by thinking in fractions, and 0.2
|
|
221
|
+
* typed into a box labelled "percent" would mean a fifth of a percent. The
|
|
222
|
+
* conversion lives here so the two readings can never drift apart.
|
|
223
223
|
*/ export function shareToPercent(share) {
|
|
224
|
-
/*
|
|
225
|
-
* `unknown`, not `number`, because that is what actually arrives.
|
|
226
|
-
*
|
|
227
|
-
* A `numeric` column comes back from the API as TEXT — Postgres returns
|
|
228
|
-
* arbitrary-precision numbers as strings — so this was handed "0.2" and
|
|
229
|
-
* `Number.isFinite("0.2")` is false. It answered '' and the box rendered
|
|
230
|
-
* empty, which reads as "no ceiling set" for a mapping that had one.
|
|
231
|
-
*
|
|
232
|
-
* That is worse than a cosmetic bug: saving the form from that state sends
|
|
233
|
-
* the empty box back through `percentToShare`, which means 0, and the guard
|
|
234
|
-
* the operator thought was in place is quietly removed by a save they made
|
|
235
|
-
* for some other reason. On the customer's org-chart import the ceiling was
|
|
236
|
-
* stored as 0.2 the whole time and reported as absent for two days.
|
|
224
|
+
/*
|
|
225
|
+
* `unknown`, not `number`, because that is what actually arrives.
|
|
226
|
+
*
|
|
227
|
+
* A `numeric` column comes back from the API as TEXT — Postgres returns
|
|
228
|
+
* arbitrary-precision numbers as strings — so this was handed "0.2" and
|
|
229
|
+
* `Number.isFinite("0.2")` is false. It answered '' and the box rendered
|
|
230
|
+
* empty, which reads as "no ceiling set" for a mapping that had one.
|
|
231
|
+
*
|
|
232
|
+
* That is worse than a cosmetic bug: saving the form from that state sends
|
|
233
|
+
* the empty box back through `percentToShare`, which means 0, and the guard
|
|
234
|
+
* the operator thought was in place is quietly removed by a save they made
|
|
235
|
+
* for some other reason. On the customer's org-chart import the ceiling was
|
|
236
|
+
* stored as 0.2 the whole time and reported as absent for two days.
|
|
237
237
|
*/ const value = typeof share === 'string' ? Number(share) : share;
|
|
238
238
|
if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) return '';
|
|
239
239
|
return String(Math.round(value * 1000) / 10);
|
|
@@ -8,13 +8,13 @@ import { MappingEditor } from './MappingEditor';
|
|
|
8
8
|
import { MappingList } from './MappingList';
|
|
9
9
|
import { blankMappingDraft, toMappingDraft } from './mappingDraft';
|
|
10
10
|
import { Notice } from './Primitives';
|
|
11
|
-
/**
|
|
12
|
-
* The mappings of the selected connection: the list on one side, the mapping
|
|
13
|
-
* being edited on the other.
|
|
14
|
-
*
|
|
15
|
-
* Only this component talks to the injected actions — the list renders what it
|
|
16
|
-
* is given and the editor edits a draft, so neither can save half a change on
|
|
17
|
-
* its own or refetch behind the other's back.
|
|
11
|
+
/**
|
|
12
|
+
* The mappings of the selected connection: the list on one side, the mapping
|
|
13
|
+
* being edited on the other.
|
|
14
|
+
*
|
|
15
|
+
* Only this component talks to the injected actions — the list renders what it
|
|
16
|
+
* is given and the editor edits a draft, so neither can save half a change on
|
|
17
|
+
* its own or refetch behind the other's back.
|
|
18
18
|
*/ const theme = integrationsPageTheme;
|
|
19
19
|
function messageOf(error, fallback) {
|
|
20
20
|
return error instanceof Error ? error.message : fallback;
|
|
@@ -46,25 +46,25 @@ export function MappingsTab({ actions }) {
|
|
|
46
46
|
useEffect(()=>{
|
|
47
47
|
loadTargets();
|
|
48
48
|
}, []);
|
|
49
|
-
/**
|
|
50
|
-
* The connection's endpoints, when nothing has fetched them yet.
|
|
51
|
-
*
|
|
52
|
-
* `store.endpoints` is filled by the Endpoints tab, so opening a connection
|
|
53
|
-
* and going straight to Mappings met "No endpoints on this connection yet"
|
|
54
|
-
* and a disabled New mapping — about a connection with three of them. The
|
|
55
|
-
* only way through was to visit the other tab first, which nothing said.
|
|
56
|
-
*
|
|
57
|
-
* Its OWN list, not the one the Endpoints step is showing.
|
|
58
|
-
*
|
|
59
|
-
* `store.endpoints` holds whatever that step last fetched — including what a
|
|
60
|
-
* SEARCH narrowed it to. A mapping reads one call, and the picker offering
|
|
61
|
-
* them was reading that same array: typing "employee" into the endpoints
|
|
62
|
-
* search left the mapping editor able to choose only the calls matching it,
|
|
63
|
-
* and `loadEndpointsIfMissing` then declined to fetch because the array was
|
|
64
|
-
* not empty. Nothing said the list was a filtered one.
|
|
65
|
-
*
|
|
66
|
-
* Read to exhaustion rather than one page: a picker that silently stops at
|
|
67
|
-
* fifty is the same defect one step down.
|
|
49
|
+
/**
|
|
50
|
+
* The connection's endpoints, when nothing has fetched them yet.
|
|
51
|
+
*
|
|
52
|
+
* `store.endpoints` is filled by the Endpoints tab, so opening a connection
|
|
53
|
+
* and going straight to Mappings met "No endpoints on this connection yet"
|
|
54
|
+
* and a disabled New mapping — about a connection with three of them. The
|
|
55
|
+
* only way through was to visit the other tab first, which nothing said.
|
|
56
|
+
*
|
|
57
|
+
* Its OWN list, not the one the Endpoints step is showing.
|
|
58
|
+
*
|
|
59
|
+
* `store.endpoints` holds whatever that step last fetched — including what a
|
|
60
|
+
* SEARCH narrowed it to. A mapping reads one call, and the picker offering
|
|
61
|
+
* them was reading that same array: typing "employee" into the endpoints
|
|
62
|
+
* search left the mapping editor able to choose only the calls matching it,
|
|
63
|
+
* and `loadEndpointsIfMissing` then declined to fetch because the array was
|
|
64
|
+
* not empty. Nothing said the list was a filtered one.
|
|
65
|
+
*
|
|
66
|
+
* Read to exhaustion rather than one page: a picker that silently stops at
|
|
67
|
+
* fifty is the same defect one step down.
|
|
68
68
|
*/ const loadPickableEndpoints = useEffectEvent(()=>{
|
|
69
69
|
if (sourceId === '') {
|
|
70
70
|
setPickableEndpoints([]);
|
|
@@ -106,16 +106,16 @@ export function MappingsTab({ actions }) {
|
|
|
106
106
|
setSelected(mapping);
|
|
107
107
|
store.selectMapping(mapping.id);
|
|
108
108
|
};
|
|
109
|
-
/**
|
|
110
|
-
* Opens the mapping for the table somebody arrived asking about.
|
|
111
|
-
*
|
|
112
|
-
* Asked of the SERVER by target entity rather than searched in the page this
|
|
113
|
-
* tab happens to be holding: the list is paged, so the mapping being looked
|
|
114
|
-
* for is frequently not in it. If there is none yet, a new one is started
|
|
115
|
-
* already aimed at that table — which is the other half of the answer.
|
|
116
|
-
*
|
|
117
|
-
* Consumed once. Left set, it would drag the selection back every time the
|
|
118
|
-
* operator clicked a different mapping.
|
|
109
|
+
/**
|
|
110
|
+
* Opens the mapping for the table somebody arrived asking about.
|
|
111
|
+
*
|
|
112
|
+
* Asked of the SERVER by target entity rather than searched in the page this
|
|
113
|
+
* tab happens to be holding: the list is paged, so the mapping being looked
|
|
114
|
+
* for is frequently not in it. If there is none yet, a new one is started
|
|
115
|
+
* already aimed at that table — which is the other half of the answer.
|
|
116
|
+
*
|
|
117
|
+
* Consumed once. Left set, it would drag the selection back every time the
|
|
118
|
+
* operator clicked a different mapping.
|
|
119
119
|
*/ const followFocus = useEffectEvent(()=>{
|
|
120
120
|
const entity = store.focusTargetEntity;
|
|
121
121
|
if (!entity || sourceId === '') return;
|
|
@@ -174,20 +174,20 @@ export function MappingsTab({ actions }) {
|
|
|
174
174
|
// second earlier landed on "choose a mapping".
|
|
175
175
|
store.upsertMapping(saved);
|
|
176
176
|
store.selectMapping(saved.id);
|
|
177
|
-
/**
|
|
178
|
-
* The plan described the mapping as it WAS.
|
|
179
|
-
*
|
|
180
|
-
* A preview is an answer about one particular mapping, and editing that
|
|
181
|
-
* mapping makes the answer wrong — silently, because the import step
|
|
182
|
-
* prints its own heading from the saved record and the counts from the
|
|
183
|
-
* plan. Switching the write mode to `replace` after previewing gave a
|
|
184
|
-
* screen that said `replace` above numbers computed for `upsert`, and
|
|
185
|
-
* the destructive confirmation then combined the new mode with the old
|
|
186
|
-
* count: "1240 rows will be deleted", from a plan that never considered
|
|
187
|
-
* deleting anything.
|
|
188
|
-
*
|
|
189
|
-
* `selectMapping` cannot do this — it returns early when the id has not
|
|
190
|
-
* changed, and on a save it never does.
|
|
177
|
+
/**
|
|
178
|
+
* The plan described the mapping as it WAS.
|
|
179
|
+
*
|
|
180
|
+
* A preview is an answer about one particular mapping, and editing that
|
|
181
|
+
* mapping makes the answer wrong — silently, because the import step
|
|
182
|
+
* prints its own heading from the saved record and the counts from the
|
|
183
|
+
* plan. Switching the write mode to `replace` after previewing gave a
|
|
184
|
+
* screen that said `replace` above numbers computed for `upsert`, and
|
|
185
|
+
* the destructive confirmation then combined the new mode with the old
|
|
186
|
+
* count: "1240 rows will be deleted", from a plan that never considered
|
|
187
|
+
* deleting anything.
|
|
188
|
+
*
|
|
189
|
+
* `selectMapping` cannot do this — it returns early when the id has not
|
|
190
|
+
* changed, and on a save it never does.
|
|
191
191
|
*/ store.setPlan(null, null);
|
|
192
192
|
setReloadToken((token)=>token + 1);
|
|
193
193
|
then?.(saved);
|
|
@@ -196,12 +196,12 @@ export function MappingsTab({ actions }) {
|
|
|
196
196
|
store.setError(messageOf(error, labels.errors.requestFailed));
|
|
197
197
|
});
|
|
198
198
|
};
|
|
199
|
-
/**
|
|
200
|
-
* Take this mapping to the import step.
|
|
201
|
-
*
|
|
202
|
-
* Saved first when it differs from what is stored: the operator is looking at
|
|
203
|
-
* the version they mean to run, and importing the older one instead — with no
|
|
204
|
-
* sign that it happened — is the worst kind of quiet.
|
|
199
|
+
/**
|
|
200
|
+
* Take this mapping to the import step.
|
|
201
|
+
*
|
|
202
|
+
* Saved first when it differs from what is stored: the operator is looking at
|
|
203
|
+
* the version they mean to run, and importing the older one instead — with no
|
|
204
|
+
* sign that it happened — is the worst kind of quiet.
|
|
205
205
|
*/ const runImport = (draft)=>{
|
|
206
206
|
const stored = selected ? toMappingDraft(selected) : null;
|
|
207
207
|
const dirty = !stored || JSON.stringify(stored) !== JSON.stringify(draft);
|