nucleus-core-ts 0.9.781 → 0.9.783
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/client.js +1 -1
- package/dist/fe/components/IntegrationsPage/components/ConnectionAuthFields.d.ts +2 -0
- package/dist/fe/components/IntegrationsPage/components/ConnectionAuthFields.js +85 -0
- package/dist/fe/components/IntegrationsPage/components/ConnectionBasicFields.d.ts +2 -0
- package/dist/fe/components/IntegrationsPage/components/ConnectionBasicFields.js +165 -0
- package/dist/fe/components/IntegrationsPage/components/ConnectionForm.d.ts +10 -0
- package/dist/fe/components/IntegrationsPage/components/ConnectionForm.js +112 -0
- package/dist/fe/components/IntegrationsPage/components/ConnectionList.d.ts +9 -0
- package/dist/fe/components/IntegrationsPage/components/ConnectionList.js +184 -0
- package/dist/fe/components/IntegrationsPage/components/ConnectionOauthFields.d.ts +2 -0
- package/dist/fe/components/IntegrationsPage/components/ConnectionOauthFields.js +131 -0
- package/dist/fe/components/IntegrationsPage/components/ConnectionSettingsTab.d.ts +21 -0
- package/dist/fe/components/IntegrationsPage/components/ConnectionSettingsTab.js +49 -0
- package/dist/fe/components/IntegrationsPage/components/ConnectionTabs.d.ts +12 -0
- package/dist/fe/components/IntegrationsPage/components/ConnectionTabs.js +95 -0
- package/dist/fe/components/IntegrationsPage/components/ConnectionTest.d.ts +9 -0
- package/dist/fe/components/IntegrationsPage/components/ConnectionTest.js +116 -0
- package/dist/fe/components/IntegrationsPage/components/CredentialFields.d.ts +9 -0
- package/dist/fe/components/IntegrationsPage/components/CredentialFields.js +171 -0
- package/dist/fe/components/IntegrationsPage/components/CredentialSlotRow.d.ts +13 -0
- package/dist/fe/components/IntegrationsPage/components/CredentialSlotRow.js +63 -0
- package/dist/fe/components/IntegrationsPage/components/EndpointForm.d.ts +11 -0
- package/dist/fe/components/IntegrationsPage/components/EndpointForm.js +166 -0
- package/dist/fe/components/IntegrationsPage/components/EndpointFormFields.d.ts +19 -0
- package/dist/fe/components/IntegrationsPage/components/EndpointFormFields.js +162 -0
- package/dist/fe/components/IntegrationsPage/components/EndpointList.d.ts +12 -0
- package/dist/fe/components/IntegrationsPage/components/EndpointList.js +205 -0
- package/dist/fe/components/IntegrationsPage/components/EndpointSample.d.ts +6 -0
- package/dist/fe/components/IntegrationsPage/components/EndpointSample.js +204 -0
- package/dist/fe/components/IntegrationsPage/components/EndpointsTab.d.ts +5 -0
- package/dist/fe/components/IntegrationsPage/components/EndpointsTab.js +101 -0
- package/dist/fe/components/IntegrationsPage/components/FieldMappingRow.d.ts +17 -0
- package/dist/fe/components/IntegrationsPage/components/FieldMappingRow.js +179 -0
- package/dist/fe/components/IntegrationsPage/components/FieldMappingRows.d.ts +11 -0
- package/dist/fe/components/IntegrationsPage/components/FieldMappingRows.js +115 -0
- package/dist/fe/components/IntegrationsPage/components/IntegrationsPage.d.ts +2 -0
- package/dist/fe/components/IntegrationsPage/components/IntegrationsPage.js +175 -0
- package/dist/fe/components/IntegrationsPage/components/MappingEditor.d.ts +15 -0
- package/dist/fe/components/IntegrationsPage/components/MappingEditor.js +193 -0
- package/dist/fe/components/IntegrationsPage/components/MappingList.d.ts +11 -0
- package/dist/fe/components/IntegrationsPage/components/MappingList.js +191 -0
- package/dist/fe/components/IntegrationsPage/components/MappingWriteModeFields.d.ts +19 -0
- package/dist/fe/components/IntegrationsPage/components/MappingWriteModeFields.js +150 -0
- package/dist/fe/components/IntegrationsPage/components/MappingsTab.d.ts +5 -0
- package/dist/fe/components/IntegrationsPage/components/MappingsTab.js +154 -0
- package/dist/fe/components/IntegrationsPage/components/PaginationFields.d.ts +9 -0
- package/dist/fe/components/IntegrationsPage/components/PaginationFields.js +218 -0
- package/dist/fe/components/IntegrationsPage/components/ParamBindingRow.d.ts +24 -0
- package/dist/fe/components/IntegrationsPage/components/ParamBindingRow.js +180 -0
- package/dist/fe/components/IntegrationsPage/components/ParamBindingRows.d.ts +33 -0
- package/dist/fe/components/IntegrationsPage/components/ParamBindingRows.js +100 -0
- package/dist/fe/components/IntegrationsPage/components/Primitives.d.ts +65 -0
- package/dist/fe/components/IntegrationsPage/components/Primitives.js +94 -0
- package/dist/fe/components/IntegrationsPage/components/RunConfirmStep.d.ts +10 -0
- package/dist/fe/components/IntegrationsPage/components/RunConfirmStep.js +48 -0
- package/dist/fe/components/IntegrationsPage/components/RunHistory.d.ts +10 -0
- package/dist/fe/components/IntegrationsPage/components/RunHistory.js +124 -0
- package/dist/fe/components/IntegrationsPage/components/RunHistoryRow.d.ts +7 -0
- package/dist/fe/components/IntegrationsPage/components/RunHistoryRow.js +129 -0
- package/dist/fe/components/IntegrationsPage/components/RunPanel.d.ts +9 -0
- package/dist/fe/components/IntegrationsPage/components/RunPanel.js +160 -0
- package/dist/fe/components/IntegrationsPage/components/RunPlanIssues.d.ts +5 -0
- package/dist/fe/components/IntegrationsPage/components/RunPlanIssues.js +43 -0
- package/dist/fe/components/IntegrationsPage/components/RunPlanSummary.d.ts +5 -0
- package/dist/fe/components/IntegrationsPage/components/RunPlanSummary.js +92 -0
- package/dist/fe/components/IntegrationsPage/components/RunProgressBar.d.ts +8 -0
- package/dist/fe/components/IntegrationsPage/components/RunProgressBar.js +77 -0
- package/dist/fe/components/IntegrationsPage/components/RunRevertConflicts.d.ts +13 -0
- package/dist/fe/components/IntegrationsPage/components/RunRevertConflicts.js +65 -0
- package/dist/fe/components/IntegrationsPage/components/RunsTab.d.ts +13 -0
- package/dist/fe/components/IntegrationsPage/components/RunsTab.js +39 -0
- package/dist/fe/components/IntegrationsPage/components/Skeletons.d.ts +9 -0
- package/dist/fe/components/IntegrationsPage/components/Skeletons.js +139 -0
- package/dist/fe/components/IntegrationsPage/components/authOptions.d.ts +29 -0
- package/dist/fe/components/IntegrationsPage/components/authOptions.js +71 -0
- package/dist/fe/components/IntegrationsPage/components/connectionDraft.d.ts +64 -0
- package/dist/fe/components/IntegrationsPage/components/connectionDraft.js +112 -0
- package/dist/fe/components/IntegrationsPage/components/credentialSlots.d.ts +25 -0
- package/dist/fe/components/IntegrationsPage/components/credentialSlots.js +67 -0
- package/dist/fe/components/IntegrationsPage/components/fieldMappingOptions.d.ts +16 -0
- package/dist/fe/components/IntegrationsPage/components/fieldMappingOptions.js +41 -0
- package/dist/fe/components/IntegrationsPage/components/formSupport.d.ts +19 -0
- package/dist/fe/components/IntegrationsPage/components/formSupport.js +25 -0
- package/dist/fe/components/IntegrationsPage/components/mappingDraft.d.ts +40 -0
- package/dist/fe/components/IntegrationsPage/components/mappingDraft.js +44 -0
- package/dist/fe/components/IntegrationsPage/components/pathParams.d.ts +9 -0
- package/dist/fe/components/IntegrationsPage/components/pathParams.js +15 -0
- package/dist/fe/components/IntegrationsPage/components/runFormat.d.ts +33 -0
- package/dist/fe/components/IntegrationsPage/components/runFormat.js +85 -0
- package/dist/fe/components/IntegrationsPage/components/useInfiniteList.d.ts +40 -0
- package/dist/fe/components/IntegrationsPage/components/useInfiniteList.js +110 -0
- package/dist/fe/components/IntegrationsPage/index.d.ts +18 -0
- package/dist/fe/components/IntegrationsPage/index.js +11 -0
- package/dist/fe/components/IntegrationsPage/store/index.d.ts +2 -0
- package/dist/fe/components/IntegrationsPage/store/index.js +124 -0
- package/dist/fe/components/IntegrationsPage/store/state.d.ts +80 -0
- package/dist/fe/components/IntegrationsPage/store/state.js +32 -0
- package/dist/fe/components/IntegrationsPage/theme/index.d.ts +103 -0
- package/dist/fe/components/IntegrationsPage/theme/index.js +113 -0
- package/dist/fe/components/IntegrationsPage/types/actions.d.ts +91 -0
- package/dist/fe/components/IntegrationsPage/types/actions.js +1 -0
- package/dist/fe/components/IntegrationsPage/types/index.d.ts +3 -0
- package/dist/fe/components/IntegrationsPage/types/index.js +3 -0
- package/dist/fe/components/IntegrationsPage/types/records.d.ts +139 -0
- package/dist/fe/components/IntegrationsPage/types/records.js +11 -0
- package/dist/fe/components/IntegrationsPage/types/results.d.ts +52 -0
- package/dist/fe/components/IntegrationsPage/types/results.js +4 -0
- package/dist/fe/index.d.ts +2 -0
- package/dist/fe/index.js +1 -0
- package/dist/index.js +14 -14
- package/dist/src/Client/ApiCaller/system-tables.d.ts +505 -0
- package/dist/src/Client/Proxy/wsProxy.js +25 -1
- package/dist/src/ElysiaPlugin/routes/backup/types.d.ts +3 -1
- package/dist/src/ElysiaPlugin/routes/integrations/index.d.ts +49 -0
- package/dist/src/ElysiaPlugin/routes/integrations/inspect.d.ts +87 -0
- package/dist/src/ElysiaPlugin/routes/integrations/inspectRoutes.d.ts +41 -0
- package/dist/src/ElysiaPlugin/routes/integrations/repository.d.ts +88 -0
- package/dist/src/ElysiaPlugin/routes/integrations/types.d.ts +22 -0
- package/dist/src/ElysiaPlugin/routes/pubsub/frame.d.ts +37 -0
- package/dist/src/ElysiaPlugin/routes/pubsub/frame.test.d.ts +1 -0
- package/dist/src/Services/Backup/BackupService.d.ts +16 -8
- package/dist/src/Services/Backup/types.d.ts +25 -0
- package/dist/src/Services/Integrations/bindings.d.ts +116 -0
- package/dist/src/Services/Integrations/bindings.test.d.ts +1 -0
- package/dist/src/Services/Integrations/collect.d.ts +68 -0
- package/dist/src/Services/Integrations/collect.test.d.ts +1 -0
- package/dist/src/Services/Integrations/fetcher.d.ts +100 -0
- package/dist/src/Services/Integrations/fetcher.test.d.ts +1 -0
- package/dist/src/Services/Integrations/lock.d.ts +77 -0
- package/dist/src/Services/Integrations/lock.test.d.ts +1 -0
- package/dist/src/Services/Integrations/mapping.d.ts +63 -0
- package/dist/src/Services/Integrations/mapping.test.d.ts +1 -0
- package/dist/src/Services/Integrations/oauth.d.ts +89 -0
- package/dist/src/Services/Integrations/oauth.test.d.ts +1 -0
- package/dist/src/Services/Integrations/pagination.d.ts +97 -0
- package/dist/src/Services/Integrations/pagination.test.d.ts +1 -0
- package/dist/src/Services/Integrations/plan.d.ts +74 -0
- package/dist/src/Services/Integrations/plan.test.d.ts +1 -0
- package/dist/src/Services/Integrations/revert.d.ts +78 -0
- package/dist/src/Services/Integrations/revert.test.d.ts +1 -0
- package/dist/src/Services/Integrations/runner.d.ts +126 -0
- package/dist/src/Services/Integrations/runner.test.d.ts +1 -0
- package/dist/src/Services/Integrations/types.d.ts +208 -0
- package/dist/src/Services/Integrations/writer.d.ts +105 -0
- package/dist/src/Services/Integrations/writer.test.d.ts +1 -0
- package/dist/src/Utils/cron.d.ts +65 -0
- package/dist/src/Utils/cron.test.d.ts +1 -0
- package/dist/src/types.d.ts +67 -1
- package/package.json +1 -1
- package/schemas/config.nucleus.json +43 -0
- package/schemas/table.schema.json +2 -1
- package/scripts/generate-schema.test.ts +78 -0
- package/scripts/generate-schema.ts +43 -5
- package/src/system.tables.json +362 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { integrationsPageTheme } from '../theme';
|
|
4
|
+
import { Field, Notice } from './Primitives';
|
|
5
|
+
/**
|
|
6
|
+
* The two choices that decide what an import DOES to the table.
|
|
7
|
+
*
|
|
8
|
+
* They sit together because they are the destructive pair: one decides how the
|
|
9
|
+
* fetched records land, the other decides what happens to the rows the source
|
|
10
|
+
* stopped sending. Every other field on a mapping only moves values around.
|
|
11
|
+
*/ const theme = integrationsPageTheme;
|
|
12
|
+
const WRITE_MODES = [
|
|
13
|
+
'upsert',
|
|
14
|
+
'append',
|
|
15
|
+
'replace'
|
|
16
|
+
];
|
|
17
|
+
const WRITE_MODE_LABEL = {
|
|
18
|
+
upsert: 'Upsert — update what matches, insert the rest',
|
|
19
|
+
append: 'Append — insert every fetched record',
|
|
20
|
+
replace: 'Replace — rebuild the table from this response'
|
|
21
|
+
};
|
|
22
|
+
const WRITE_MODE_HINT = {
|
|
23
|
+
upsert: 'Rows keep their identity, so whatever points at them keeps working.',
|
|
24
|
+
append: 'Nothing is matched, so running this import twice stores every record twice.',
|
|
25
|
+
replace: 'The table is emptied and refilled on every run.'
|
|
26
|
+
};
|
|
27
|
+
const MISSING_POLICIES = [
|
|
28
|
+
'ignore',
|
|
29
|
+
'deactivate',
|
|
30
|
+
'delete'
|
|
31
|
+
];
|
|
32
|
+
const MISSING_POLICY_LABEL = {
|
|
33
|
+
ignore: 'Ignore — leave them exactly as they are',
|
|
34
|
+
deactivate: 'Deactivate — mark them with a column value',
|
|
35
|
+
delete: 'Delete — remove them from the table'
|
|
36
|
+
};
|
|
37
|
+
const MISSING_POLICY_HINT = {
|
|
38
|
+
ignore: 'Safest. Records removed at the source stay in the table until someone acts.',
|
|
39
|
+
deactivate: 'Reversible. The row survives and history that refers to it stays readable.',
|
|
40
|
+
delete: 'Permanent. A source that returns a short page for any reason takes rows with it.'
|
|
41
|
+
};
|
|
42
|
+
function toWriteMode(value) {
|
|
43
|
+
return WRITE_MODES.find((mode)=>mode === value) ?? 'upsert';
|
|
44
|
+
}
|
|
45
|
+
function toMissingPolicy(value) {
|
|
46
|
+
return MISSING_POLICIES.find((policy)=>policy === value) ?? 'ignore';
|
|
47
|
+
}
|
|
48
|
+
/** Deactivate has nowhere to write its flag without both of these. */ export function missingRecordFieldsIncomplete(policy, column, value) {
|
|
49
|
+
return policy === 'deactivate' && (column.trim() === '' || value.trim() === '');
|
|
50
|
+
}
|
|
51
|
+
export function MappingWriteModeFields({ idPrefix, writeMode, missingRecordPolicy, missingRecordColumn, missingRecordValue, dedupConfigured, disabled, onWriteModeChange, onMissingRecordPolicyChange, onMissingRecordColumnChange, onMissingRecordValueChange }) {
|
|
52
|
+
const modeId = `${idPrefix}-write-mode`;
|
|
53
|
+
const policyId = `${idPrefix}-missing-policy`;
|
|
54
|
+
const columnId = `${idPrefix}-missing-column`;
|
|
55
|
+
const valueId = `${idPrefix}-missing-value`;
|
|
56
|
+
const columnMissing = missingRecordPolicy === 'deactivate' && missingRecordColumn.trim() === '';
|
|
57
|
+
const valueMissing = missingRecordPolicy === 'deactivate' && missingRecordValue.trim() === '';
|
|
58
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
59
|
+
className: "flex flex-col gap-3",
|
|
60
|
+
children: [
|
|
61
|
+
/*#__PURE__*/ _jsx(Field, {
|
|
62
|
+
htmlFor: modeId,
|
|
63
|
+
hint: WRITE_MODE_HINT[writeMode],
|
|
64
|
+
label: "Write mode",
|
|
65
|
+
children: /*#__PURE__*/ _jsx("select", {
|
|
66
|
+
"aria-describedby": `${modeId}-hint`,
|
|
67
|
+
className: theme.field.input,
|
|
68
|
+
disabled: disabled,
|
|
69
|
+
id: modeId,
|
|
70
|
+
onChange: (event)=>onWriteModeChange(toWriteMode(event.target.value)),
|
|
71
|
+
value: writeMode,
|
|
72
|
+
children: WRITE_MODES.map((mode)=>/*#__PURE__*/ _jsx("option", {
|
|
73
|
+
value: mode,
|
|
74
|
+
children: WRITE_MODE_LABEL[mode]
|
|
75
|
+
}, mode))
|
|
76
|
+
})
|
|
77
|
+
}),
|
|
78
|
+
writeMode === 'replace' ? /*#__PURE__*/ _jsxs(Notice, {
|
|
79
|
+
title: "Replace rebuilds the table on every run",
|
|
80
|
+
tone: "danger",
|
|
81
|
+
children: [
|
|
82
|
+
/*#__PURE__*/ _jsx("p", {
|
|
83
|
+
children: "Every existing row is dropped and written again, so row ids change. Foreign keys, saved links, exports, report filters and anything else holding one of those ids breaks, and no amount of re-running repairs it."
|
|
84
|
+
}),
|
|
85
|
+
/*#__PURE__*/ _jsx("p", {
|
|
86
|
+
className: "mt-1",
|
|
87
|
+
children: dedupConfigured ? 'This mapping already has a deduplication field, so Upsert does the same job in place and the ids survive.' : 'Set a deduplication field and choose Upsert instead: it matches each record and updates it in place, leaving the ids alone.'
|
|
88
|
+
})
|
|
89
|
+
]
|
|
90
|
+
}) : null,
|
|
91
|
+
/*#__PURE__*/ _jsx(Field, {
|
|
92
|
+
htmlFor: policyId,
|
|
93
|
+
hint: MISSING_POLICY_HINT[missingRecordPolicy],
|
|
94
|
+
label: "Records the source no longer returns",
|
|
95
|
+
children: /*#__PURE__*/ _jsx("select", {
|
|
96
|
+
"aria-describedby": `${policyId}-hint`,
|
|
97
|
+
className: theme.field.input,
|
|
98
|
+
disabled: disabled,
|
|
99
|
+
id: policyId,
|
|
100
|
+
onChange: (event)=>onMissingRecordPolicyChange(toMissingPolicy(event.target.value)),
|
|
101
|
+
value: missingRecordPolicy,
|
|
102
|
+
children: MISSING_POLICIES.map((policy)=>/*#__PURE__*/ _jsx("option", {
|
|
103
|
+
value: policy,
|
|
104
|
+
children: MISSING_POLICY_LABEL[policy]
|
|
105
|
+
}, policy))
|
|
106
|
+
})
|
|
107
|
+
}),
|
|
108
|
+
missingRecordPolicy === 'delete' ? /*#__PURE__*/ _jsx(Notice, {
|
|
109
|
+
title: "Deleting is not reversible",
|
|
110
|
+
tone: "warning",
|
|
111
|
+
children: "A source that answers with a short page — a filter left on, a timeout, an expired token honoured with an empty list — looks exactly like records that went away."
|
|
112
|
+
}) : null,
|
|
113
|
+
missingRecordPolicy === 'deactivate' ? /*#__PURE__*/ _jsxs("div", {
|
|
114
|
+
className: "grid grid-cols-1 gap-3 sm:grid-cols-2",
|
|
115
|
+
children: [
|
|
116
|
+
/*#__PURE__*/ _jsx(Field, {
|
|
117
|
+
error: columnMissing ? 'Required while Deactivate is selected.' : null,
|
|
118
|
+
hint: "A column on the target entity.",
|
|
119
|
+
htmlFor: columnId,
|
|
120
|
+
label: "Deactivation column",
|
|
121
|
+
children: /*#__PURE__*/ _jsx("input", {
|
|
122
|
+
"aria-describedby": `${columnId}-hint`,
|
|
123
|
+
className: theme.field.input,
|
|
124
|
+
disabled: disabled,
|
|
125
|
+
id: columnId,
|
|
126
|
+
onChange: (event)=>onMissingRecordColumnChange(event.target.value),
|
|
127
|
+
placeholder: "isActive",
|
|
128
|
+
value: missingRecordColumn
|
|
129
|
+
})
|
|
130
|
+
}),
|
|
131
|
+
/*#__PURE__*/ _jsx(Field, {
|
|
132
|
+
error: valueMissing ? 'Required while Deactivate is selected.' : null,
|
|
133
|
+
hint: "Written into that column.",
|
|
134
|
+
htmlFor: valueId,
|
|
135
|
+
label: "Deactivation value",
|
|
136
|
+
children: /*#__PURE__*/ _jsx("input", {
|
|
137
|
+
"aria-describedby": `${valueId}-hint`,
|
|
138
|
+
className: theme.field.input,
|
|
139
|
+
disabled: disabled,
|
|
140
|
+
id: valueId,
|
|
141
|
+
onChange: (event)=>onMissingRecordValueChange(event.target.value),
|
|
142
|
+
placeholder: "false",
|
|
143
|
+
value: missingRecordValue
|
|
144
|
+
})
|
|
145
|
+
})
|
|
146
|
+
]
|
|
147
|
+
}) : null
|
|
148
|
+
]
|
|
149
|
+
});
|
|
150
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useEffectEvent, useState } from 'react';
|
|
4
|
+
import { useIntegrationsStore } from '../store';
|
|
5
|
+
import { integrationsPageTheme } from '../theme';
|
|
6
|
+
import { MappingEditor } from './MappingEditor';
|
|
7
|
+
import { MappingList } from './MappingList';
|
|
8
|
+
import { blankMappingDraft, toMappingDraft } from './mappingDraft';
|
|
9
|
+
import { Notice } from './Primitives';
|
|
10
|
+
/**
|
|
11
|
+
* The mappings of the selected connection: the list on one side, the mapping
|
|
12
|
+
* being edited on the other.
|
|
13
|
+
*
|
|
14
|
+
* Only this component talks to the injected actions — the list renders what it
|
|
15
|
+
* is given and the editor edits a draft, so neither can save half a change on
|
|
16
|
+
* its own or refetch behind the other's back.
|
|
17
|
+
*/ const theme = integrationsPageTheme;
|
|
18
|
+
function messageOf(error) {
|
|
19
|
+
return error instanceof Error ? error.message : 'The request failed.';
|
|
20
|
+
}
|
|
21
|
+
export function MappingsTab({ actions }) {
|
|
22
|
+
const store = useIntegrationsStore();
|
|
23
|
+
const [targets, setTargets] = useState([]);
|
|
24
|
+
const [targetsLoading, setTargetsLoading] = useState(true);
|
|
25
|
+
const [selected, setSelected] = useState(null);
|
|
26
|
+
const [creating, setCreating] = useState(false);
|
|
27
|
+
const [saving, setSaving] = useState(false);
|
|
28
|
+
const [deleting, setDeleting] = useState(false);
|
|
29
|
+
const [reloadToken, setReloadToken] = useState(0);
|
|
30
|
+
const sourceId = store.selectedSourceId ?? '';
|
|
31
|
+
const newMappingEndpointId = store.selectedEndpointId ?? store.endpoints[0]?.id ?? '';
|
|
32
|
+
const loadTargets = useEffectEvent(()=>{
|
|
33
|
+
setTargetsLoading(true);
|
|
34
|
+
actions.listTargets().then((result)=>{
|
|
35
|
+
setTargets(result);
|
|
36
|
+
setTargetsLoading(false);
|
|
37
|
+
}).catch((error)=>{
|
|
38
|
+
setTargetsLoading(false);
|
|
39
|
+
store.setError(messageOf(error));
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
useEffect(()=>{
|
|
43
|
+
loadTargets();
|
|
44
|
+
}, []);
|
|
45
|
+
const startNew = ()=>{
|
|
46
|
+
setSelected(null);
|
|
47
|
+
setCreating(true);
|
|
48
|
+
store.selectMapping(null);
|
|
49
|
+
};
|
|
50
|
+
const openMapping = (mapping)=>{
|
|
51
|
+
setCreating(false);
|
|
52
|
+
setSelected(mapping);
|
|
53
|
+
store.selectMapping(mapping.id);
|
|
54
|
+
};
|
|
55
|
+
const closeEditor = ()=>{
|
|
56
|
+
setCreating(false);
|
|
57
|
+
setSelected(null);
|
|
58
|
+
store.selectMapping(null);
|
|
59
|
+
};
|
|
60
|
+
const save = (draft)=>{
|
|
61
|
+
setSaving(true);
|
|
62
|
+
store.setError(null);
|
|
63
|
+
actions.saveMapping(draft).then((saved)=>{
|
|
64
|
+
setSaving(false);
|
|
65
|
+
if (!saved) return store.setError('The mapping was not saved.');
|
|
66
|
+
setCreating(false);
|
|
67
|
+
setSelected(saved);
|
|
68
|
+
store.selectMapping(saved.id);
|
|
69
|
+
setReloadToken((token)=>token + 1);
|
|
70
|
+
}).catch((error)=>{
|
|
71
|
+
setSaving(false);
|
|
72
|
+
store.setError(messageOf(error));
|
|
73
|
+
});
|
|
74
|
+
};
|
|
75
|
+
const remove = (id)=>{
|
|
76
|
+
if (id === '') return;
|
|
77
|
+
setDeleting(true);
|
|
78
|
+
store.setError(null);
|
|
79
|
+
actions.deleteMapping(id).then((removed)=>{
|
|
80
|
+
setDeleting(false);
|
|
81
|
+
if (!removed) return store.setError('The mapping was not deleted.');
|
|
82
|
+
closeEditor();
|
|
83
|
+
setReloadToken((token)=>token + 1);
|
|
84
|
+
}).catch((error)=>{
|
|
85
|
+
setDeleting(false);
|
|
86
|
+
store.setError(messageOf(error));
|
|
87
|
+
});
|
|
88
|
+
};
|
|
89
|
+
const draft = creating ? blankMappingDraft(newMappingEndpointId) : selected ? toMappingDraft(selected) : null;
|
|
90
|
+
// A sample describes ONE endpoint. Offering its field names against a mapping
|
|
91
|
+
// that reads from a different one would suggest columns that never arrive.
|
|
92
|
+
const sourceFields = draft && store.selectedEndpointId === draft.endpointId ? store.sample?.fields ?? [] : [];
|
|
93
|
+
return /*#__PURE__*/ _jsxs("section", {
|
|
94
|
+
className: "flex flex-col gap-4",
|
|
95
|
+
children: [
|
|
96
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
97
|
+
className: theme.header.wrapper,
|
|
98
|
+
children: [
|
|
99
|
+
/*#__PURE__*/ _jsx("p", {
|
|
100
|
+
className: theme.header.subtitle,
|
|
101
|
+
children: "A mapping turns one endpoint's response into rows of one entity."
|
|
102
|
+
}),
|
|
103
|
+
/*#__PURE__*/ _jsx("div", {
|
|
104
|
+
className: theme.header.actions,
|
|
105
|
+
children: /*#__PURE__*/ _jsx("button", {
|
|
106
|
+
className: theme.button.primary,
|
|
107
|
+
disabled: newMappingEndpointId === '',
|
|
108
|
+
onClick: startNew,
|
|
109
|
+
type: "button",
|
|
110
|
+
children: "New mapping"
|
|
111
|
+
})
|
|
112
|
+
})
|
|
113
|
+
]
|
|
114
|
+
}),
|
|
115
|
+
newMappingEndpointId === '' ? /*#__PURE__*/ _jsx(Notice, {
|
|
116
|
+
title: "Select an endpoint first",
|
|
117
|
+
tone: "info",
|
|
118
|
+
children: "Every mapping reads from one endpoint. Choose one on the Endpoints tab and it becomes the source of the next mapping created here."
|
|
119
|
+
}) : null,
|
|
120
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
121
|
+
className: theme.layout.split,
|
|
122
|
+
children: [
|
|
123
|
+
/*#__PURE__*/ _jsx("div", {
|
|
124
|
+
className: theme.layout.aside,
|
|
125
|
+
children: /*#__PURE__*/ _jsx(MappingList, {
|
|
126
|
+
actions: actions,
|
|
127
|
+
sourceId: sourceId,
|
|
128
|
+
onSelect: openMapping,
|
|
129
|
+
reloadToken: reloadToken,
|
|
130
|
+
selectedId: selected?.id ?? null
|
|
131
|
+
})
|
|
132
|
+
}),
|
|
133
|
+
/*#__PURE__*/ _jsx("div", {
|
|
134
|
+
className: theme.layout.main,
|
|
135
|
+
children: draft ? /*#__PURE__*/ _jsx(MappingEditor, {
|
|
136
|
+
deleting: deleting,
|
|
137
|
+
mapping: draft,
|
|
138
|
+
onCancel: closeEditor,
|
|
139
|
+
onDelete: remove,
|
|
140
|
+
onSave: save,
|
|
141
|
+
saving: saving,
|
|
142
|
+
sourceFields: sourceFields,
|
|
143
|
+
targets: targets,
|
|
144
|
+
targetsLoading: targetsLoading
|
|
145
|
+
}, draft.id ?? 'new-mapping') : /*#__PURE__*/ _jsx("p", {
|
|
146
|
+
className: theme.list.empty,
|
|
147
|
+
children: "Pick a mapping to edit it, or create one to describe what an import writes."
|
|
148
|
+
})
|
|
149
|
+
})
|
|
150
|
+
]
|
|
151
|
+
})
|
|
152
|
+
]
|
|
153
|
+
});
|
|
154
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { PaginationProfileValue } from '../types';
|
|
2
|
+
export type PaginationFieldsProps = {
|
|
3
|
+
/** Prefixes every control id so two of these can share one page. */
|
|
4
|
+
idPrefix: string;
|
|
5
|
+
value: PaginationProfileValue;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
onChange: (next: PaginationProfileValue) => void;
|
|
8
|
+
};
|
|
9
|
+
export declare function PaginationFields({ idPrefix, value, disabled, onChange }: PaginationFieldsProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { integrationsPageTheme } from '../theme';
|
|
4
|
+
import { Field, Notice } from './Primitives';
|
|
5
|
+
/**
|
|
6
|
+
* How the panel walks a list endpoint to its end.
|
|
7
|
+
*
|
|
8
|
+
* Nothing here is detected. The profile is a statement about the API being read,
|
|
9
|
+
* and the warning above the picker is why it has to be one: the wrong choice
|
|
10
|
+
* does not fail — it succeeds on page one and reports a total that looks fine.
|
|
11
|
+
*/ const theme = integrationsPageTheme;
|
|
12
|
+
const KINDS = [
|
|
13
|
+
{
|
|
14
|
+
value: 'none',
|
|
15
|
+
label: 'None — one response holds every record'
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
value: 'page_number',
|
|
19
|
+
label: 'Page number — ?page=1&size=100'
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
value: 'offset',
|
|
23
|
+
label: 'Offset — ?offset=0&limit=100'
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
value: 'cursor',
|
|
27
|
+
label: 'Cursor — the body carries a token for the next page'
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
value: 'link_header',
|
|
31
|
+
label: 'Link header — RFC 5988 rel="next"'
|
|
32
|
+
}
|
|
33
|
+
];
|
|
34
|
+
const TOTAL_HINT = 'Dotted path to the record count, when the body reports one.';
|
|
35
|
+
const SIZE_LABEL = 'Records per request';
|
|
36
|
+
/** Only the parameters the chosen style actually sends. */ const FIELDS = {
|
|
37
|
+
none: [],
|
|
38
|
+
page_number: [
|
|
39
|
+
{
|
|
40
|
+
as: 'text',
|
|
41
|
+
key: 'pageParam',
|
|
42
|
+
label: 'Page query parameter',
|
|
43
|
+
placeholder: 'page'
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
as: 'text',
|
|
47
|
+
key: 'sizeParam',
|
|
48
|
+
label: 'Page size query parameter',
|
|
49
|
+
placeholder: 'size'
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
as: 'number',
|
|
53
|
+
key: 'firstPage',
|
|
54
|
+
label: 'First page number',
|
|
55
|
+
placeholder: '1',
|
|
56
|
+
hint: 'Some APIs count from 0, most from 1.'
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
as: 'number',
|
|
60
|
+
key: 'pageSize',
|
|
61
|
+
label: SIZE_LABEL,
|
|
62
|
+
placeholder: '100'
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
as: 'text',
|
|
66
|
+
key: 'totalPath',
|
|
67
|
+
label: 'Total count path',
|
|
68
|
+
placeholder: 'meta.totalItems',
|
|
69
|
+
hint: TOTAL_HINT
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
as: 'text',
|
|
73
|
+
key: 'lastPagePath',
|
|
74
|
+
label: 'Last page path',
|
|
75
|
+
placeholder: 'meta.totalPages',
|
|
76
|
+
hint: 'Stops the walk when there is a last page number but no total.'
|
|
77
|
+
}
|
|
78
|
+
],
|
|
79
|
+
offset: [
|
|
80
|
+
{
|
|
81
|
+
as: 'text',
|
|
82
|
+
key: 'offsetParam',
|
|
83
|
+
label: 'Offset query parameter',
|
|
84
|
+
placeholder: 'offset'
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
as: 'text',
|
|
88
|
+
key: 'limitParam',
|
|
89
|
+
label: 'Limit query parameter',
|
|
90
|
+
placeholder: 'limit'
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
as: 'number',
|
|
94
|
+
key: 'pageSize',
|
|
95
|
+
label: SIZE_LABEL,
|
|
96
|
+
placeholder: '100'
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
as: 'text',
|
|
100
|
+
key: 'totalPath',
|
|
101
|
+
label: 'Total count path',
|
|
102
|
+
placeholder: 'meta.totalItems',
|
|
103
|
+
hint: TOTAL_HINT
|
|
104
|
+
}
|
|
105
|
+
],
|
|
106
|
+
cursor: [
|
|
107
|
+
{
|
|
108
|
+
as: 'text',
|
|
109
|
+
key: 'cursorParam',
|
|
110
|
+
label: 'Cursor query parameter',
|
|
111
|
+
placeholder: 'cursor'
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
as: 'text',
|
|
115
|
+
key: 'nextCursorPath',
|
|
116
|
+
label: 'Next cursor path',
|
|
117
|
+
placeholder: 'meta.nextCursor',
|
|
118
|
+
hint: 'Dotted path to the next token. Empty or absent ends the walk.'
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
as: 'number',
|
|
122
|
+
key: 'pageSize',
|
|
123
|
+
label: SIZE_LABEL,
|
|
124
|
+
placeholder: '100'
|
|
125
|
+
}
|
|
126
|
+
],
|
|
127
|
+
link_header: [
|
|
128
|
+
{
|
|
129
|
+
as: 'text',
|
|
130
|
+
key: 'sizeParam',
|
|
131
|
+
label: 'Page size query parameter',
|
|
132
|
+
placeholder: 'per_page',
|
|
133
|
+
hint: 'Only if the API accepts one; the next URL comes from the header.'
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
as: 'number',
|
|
137
|
+
key: 'pageSize',
|
|
138
|
+
label: SIZE_LABEL,
|
|
139
|
+
placeholder: '100'
|
|
140
|
+
}
|
|
141
|
+
]
|
|
142
|
+
};
|
|
143
|
+
export function PaginationFields({ idPrefix, value, disabled, onChange }) {
|
|
144
|
+
const write = (spec, entered)=>{
|
|
145
|
+
const next = {
|
|
146
|
+
...value
|
|
147
|
+
};
|
|
148
|
+
if (spec.as === 'number') {
|
|
149
|
+
const parsed = Number.parseInt(entered, 10);
|
|
150
|
+
next[spec.key] = Number.isFinite(parsed) ? parsed : undefined;
|
|
151
|
+
} else {
|
|
152
|
+
next[spec.key] = entered;
|
|
153
|
+
}
|
|
154
|
+
onChange(next);
|
|
155
|
+
};
|
|
156
|
+
const read = (spec)=>{
|
|
157
|
+
const current = value[spec.key];
|
|
158
|
+
return current === undefined || current === null ? '' : String(current);
|
|
159
|
+
};
|
|
160
|
+
return /*#__PURE__*/ _jsxs("section", {
|
|
161
|
+
className: "flex flex-col gap-3",
|
|
162
|
+
children: [
|
|
163
|
+
/*#__PURE__*/ _jsx(Notice, {
|
|
164
|
+
title: "The pagination profile is chosen, not guessed",
|
|
165
|
+
tone: "warning",
|
|
166
|
+
children: "Nothing here is detected for you. Pick the wrong style and the import still succeeds — it reads the first page and never asks for the rest. In a destructive write mode the records it never saw count as missing, so the run deletes or deactivates them."
|
|
167
|
+
}),
|
|
168
|
+
/*#__PURE__*/ _jsx(Field, {
|
|
169
|
+
hint: "Match this to how the API documents its own list endpoints.",
|
|
170
|
+
htmlFor: `${idPrefix}-kind`,
|
|
171
|
+
label: "Pagination style",
|
|
172
|
+
children: /*#__PURE__*/ _jsx("select", {
|
|
173
|
+
"aria-describedby": `${idPrefix}-kind-hint`,
|
|
174
|
+
className: theme.field.input,
|
|
175
|
+
disabled: disabled,
|
|
176
|
+
id: `${idPrefix}-kind`,
|
|
177
|
+
onChange: (event)=>onChange({
|
|
178
|
+
...value,
|
|
179
|
+
kind: toKind(event.target.value)
|
|
180
|
+
}),
|
|
181
|
+
value: value.kind,
|
|
182
|
+
children: KINDS.map((option)=>/*#__PURE__*/ _jsx("option", {
|
|
183
|
+
value: option.value,
|
|
184
|
+
children: option.label
|
|
185
|
+
}, option.value))
|
|
186
|
+
})
|
|
187
|
+
}),
|
|
188
|
+
/*#__PURE__*/ _jsx("div", {
|
|
189
|
+
className: "grid grid-cols-1 gap-3 sm:grid-cols-2",
|
|
190
|
+
children: FIELDS[value.kind].map((spec)=>{
|
|
191
|
+
const id = `${idPrefix}-${spec.key}`;
|
|
192
|
+
return /*#__PURE__*/ _jsx(Field, {
|
|
193
|
+
hint: spec.hint,
|
|
194
|
+
htmlFor: id,
|
|
195
|
+
label: spec.label,
|
|
196
|
+
children: /*#__PURE__*/ _jsx("input", {
|
|
197
|
+
"aria-describedby": spec.hint ? `${id}-hint` : undefined,
|
|
198
|
+
className: theme.field.input,
|
|
199
|
+
disabled: disabled,
|
|
200
|
+
id: id,
|
|
201
|
+
inputMode: spec.as === 'number' ? 'numeric' : 'text',
|
|
202
|
+
onChange: (event)=>write(spec, event.target.value),
|
|
203
|
+
placeholder: spec.placeholder,
|
|
204
|
+
type: "text",
|
|
205
|
+
value: read(spec)
|
|
206
|
+
})
|
|
207
|
+
}, spec.key);
|
|
208
|
+
})
|
|
209
|
+
})
|
|
210
|
+
]
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
function toKind(candidate) {
|
|
214
|
+
for (const option of KINDS){
|
|
215
|
+
if (option.value === candidate) return option.value;
|
|
216
|
+
}
|
|
217
|
+
return 'none';
|
|
218
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { IntegrationEndpoint, ParamBindingValue } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* One parameter and where its value comes from.
|
|
4
|
+
*
|
|
5
|
+
* Split out of the list because the three sources each need different controls,
|
|
6
|
+
* and keeping them together put the whole screen over the size a single file is
|
|
7
|
+
* allowed to be.
|
|
8
|
+
*/
|
|
9
|
+
export type ParamBindingRowProps = {
|
|
10
|
+
binding: ParamBindingValue;
|
|
11
|
+
onChange: (next: ParamBindingValue) => void;
|
|
12
|
+
onRemove: () => void;
|
|
13
|
+
siblings: IntegrationEndpoint[];
|
|
14
|
+
entities: {
|
|
15
|
+
entity: string;
|
|
16
|
+
label: string;
|
|
17
|
+
fields: string[];
|
|
18
|
+
}[];
|
|
19
|
+
disabled: boolean;
|
|
20
|
+
};
|
|
21
|
+
export declare function ParamBindingRow({ binding, onChange, onRemove, siblings, entities, disabled, }: ParamBindingRowProps): import("react/jsx-runtime").JSX.Element;
|
|
22
|
+
export type ParamBindingValueFieldsProps = ParamBindingRowProps & {
|
|
23
|
+
rowId: string;
|
|
24
|
+
};
|