nucleus-core-ts 0.9.935 → 0.9.936
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/.build-ok +1 -1
- package/dist/fe/components/IntegrationsPage/components/MappingEditor.js +4 -0
- package/dist/fe/components/IntegrationsPage/components/MappingList.js +35 -1
- package/dist/fe/components/IntegrationsPage/components/MappingWriteModeFields.d.ts +9 -1
- package/dist/fe/components/IntegrationsPage/components/MappingWriteModeFields.js +16 -1
- package/dist/fe/components/IntegrationsPage/components/mappingDraft.d.ts +1 -0
- package/dist/fe/components/IntegrationsPage/components/mappingDraft.js +1 -0
- package/dist/fe/components/IntegrationsPage/components/mappingStaleness.d.ts +45 -0
- package/dist/fe/components/IntegrationsPage/components/mappingStaleness.js +46 -0
- package/dist/fe/components/IntegrationsPage/text/index.d.ts +6 -0
- package/dist/fe/components/IntegrationsPage/text/index.js +4 -0
- package/dist/fe/components/IntegrationsPage/types/records.d.ts +7 -0
- package/dist/index.js +4 -4
- package/dist/src/ElysiaPlugin/routes/auth/login/notifyDecision.d.ts +32 -0
- package/dist/src/ElysiaPlugin/routes/auth/passwordReset/index.d.ts +12 -1
- package/dist/src/ElysiaPlugin/routes/auth/passwordReset/outcome.d.ts +54 -0
- package/dist/src/Services/Integrations/keyDrift.d.ts +40 -0
- package/package.json +1 -1
package/dist/.build-ok
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.9.
|
|
1
|
+
0.9.936
|
|
@@ -182,6 +182,7 @@ export function MappingEditor({ mapping, targets, targetsLoading, endpoints, fre
|
|
|
182
182
|
missingRecordColumn: draft.missingRecordColumn ?? '',
|
|
183
183
|
missingRecordPolicy: draft.missingRecordPolicy,
|
|
184
184
|
missingRecordValue: draft.missingRecordValue ?? '',
|
|
185
|
+
removalScopeColumn: draft.removalScopeColumn ?? '',
|
|
185
186
|
originColumn: draft.originColumn ?? '',
|
|
186
187
|
originValue: draft.originValue ?? '',
|
|
187
188
|
maxRemovalShare: draft.maxRemovalShare ?? 0,
|
|
@@ -194,6 +195,9 @@ export function MappingEditor({ mapping, targets, targetsLoading, endpoints, fre
|
|
|
194
195
|
onMissingRecordValueChange: (value)=>patch({
|
|
195
196
|
missingRecordValue: value
|
|
196
197
|
}),
|
|
198
|
+
onRemovalScopeColumnChange: (value)=>patch({
|
|
199
|
+
removalScopeColumn: value
|
|
200
|
+
}),
|
|
197
201
|
onOriginColumnChange: (value)=>patch({
|
|
198
202
|
originColumn: value
|
|
199
203
|
}),
|
|
@@ -5,6 +5,7 @@ import { cn } from '../../../utils/cn';
|
|
|
5
5
|
import { useIntegrationsStore } from '../store';
|
|
6
6
|
import { useLabels } from '../text/context';
|
|
7
7
|
import { integrationsPageTheme } from '../theme';
|
|
8
|
+
import { mappingStaleness } from './mappingStaleness';
|
|
8
9
|
import { Badge, Field, Notice } from './Primitives';
|
|
9
10
|
import { formatMoment } from './runFormat';
|
|
10
11
|
import { ListSkeleton } from './Skeletons';
|
|
@@ -39,6 +40,19 @@ const WRITE_MODE_TONE = {
|
|
|
39
40
|
export function MappingList({ actions, sourceId, selectedId, reloadToken, onSelect, onRun }) {
|
|
40
41
|
const t = useLabels().errors;
|
|
41
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.
|
|
52
|
+
*/ const [now, setNow] = useState(null);
|
|
53
|
+
useEffect(()=>{
|
|
54
|
+
setNow(Date.now());
|
|
55
|
+
}, []);
|
|
42
56
|
const store = useIntegrationsStore();
|
|
43
57
|
const uid = useId();
|
|
44
58
|
const search = store.mappingsSearch;
|
|
@@ -201,7 +215,27 @@ export function MappingList({ actions, sourceId, selectedId, reloadToken, onSele
|
|
|
201
215
|
/*#__PURE__*/ _jsx("span", {
|
|
202
216
|
className: theme.list.itemMeta,
|
|
203
217
|
children: describeLastRun(mapping.lastRunAt, labels.ui)
|
|
204
|
-
})
|
|
218
|
+
}),
|
|
219
|
+
(()=>{
|
|
220
|
+
if (now === null) return null;
|
|
221
|
+
const health = mappingStaleness({
|
|
222
|
+
lastRunAt: mapping.lastRunAt,
|
|
223
|
+
scheduleEnabled: mapping.scheduleEnabled,
|
|
224
|
+
now
|
|
225
|
+
});
|
|
226
|
+
return /*#__PURE__*/ _jsxs(_Fragment, {
|
|
227
|
+
children: [
|
|
228
|
+
health.unscheduled ? /*#__PURE__*/ _jsx(Badge, {
|
|
229
|
+
tone: "warning",
|
|
230
|
+
children: labels.ui.unscheduled
|
|
231
|
+
}) : null,
|
|
232
|
+
health.tone === 'warning' && health.ageDays !== null ? /*#__PURE__*/ _jsx(Badge, {
|
|
233
|
+
tone: "warning",
|
|
234
|
+
children: labels.ui.behindByDays(health.ageDays)
|
|
235
|
+
}) : null
|
|
236
|
+
]
|
|
237
|
+
});
|
|
238
|
+
})()
|
|
205
239
|
]
|
|
206
240
|
})
|
|
207
241
|
]
|
|
@@ -8,6 +8,13 @@ export type MappingWriteModeFieldsProps = {
|
|
|
8
8
|
missingRecordPolicy: MissingRecordPolicyValue;
|
|
9
9
|
missingRecordColumn: string;
|
|
10
10
|
missingRecordValue: string;
|
|
11
|
+
/**
|
|
12
|
+
* Target column carrying whichever value this mapping's calls vary.
|
|
13
|
+
*
|
|
14
|
+
* Set, removal is confined to the values THIS run read, so one call answering
|
|
15
|
+
* with an error costs that slice its cleanup rather than refusing the run.
|
|
16
|
+
*/
|
|
17
|
+
removalScopeColumn: string;
|
|
11
18
|
/** The mark this mapping puts on its rows — and the only ones it may remove. */
|
|
12
19
|
originColumn: string;
|
|
13
20
|
originValue: string;
|
|
@@ -20,11 +27,12 @@ export type MappingWriteModeFieldsProps = {
|
|
|
20
27
|
onMissingRecordPolicyChange: (policy: MissingRecordPolicyValue) => void;
|
|
21
28
|
onMissingRecordColumnChange: (value: string) => void;
|
|
22
29
|
onMissingRecordValueChange: (value: string) => void;
|
|
30
|
+
onRemovalScopeColumnChange: (value: string) => void;
|
|
23
31
|
onOriginColumnChange: (value: string) => void;
|
|
24
32
|
onOriginValueChange: (value: string) => void;
|
|
25
33
|
onMaxRemovalShareChange: (value: number) => void;
|
|
26
34
|
};
|
|
27
|
-
export declare function MappingWriteModeFields({ idPrefix, writeMode, missingRecordPolicy, missingRecordColumn, missingRecordValue, originColumn, originValue, maxRemovalShare, dedupConfigured, disabled, onWriteModeChange, onMissingRecordPolicyChange, onMissingRecordColumnChange, onMissingRecordValueChange, onOriginColumnChange, onOriginValueChange, onMaxRemovalShareChange, }: MappingWriteModeFieldsProps): import("react/jsx-runtime").JSX.Element;
|
|
35
|
+
export declare function MappingWriteModeFields({ idPrefix, writeMode, missingRecordPolicy, missingRecordColumn, missingRecordValue, originColumn, originValue, maxRemovalShare, dedupConfigured, disabled, onWriteModeChange, onMissingRecordPolicyChange, onMissingRecordColumnChange, onMissingRecordValueChange, removalScopeColumn, onRemovalScopeColumnChange, onOriginColumnChange, onOriginValueChange, onMaxRemovalShareChange, }: MappingWriteModeFieldsProps): import("react/jsx-runtime").JSX.Element;
|
|
28
36
|
/**
|
|
29
37
|
* Stored as a share, typed as a percentage.
|
|
30
38
|
*
|
|
@@ -34,13 +34,14 @@ function toMissingPolicy(value) {
|
|
|
34
34
|
/** Deactivate has nowhere to write its flag without both of these. */ export function missingRecordFieldsIncomplete(policy, column, value) {
|
|
35
35
|
return policy === 'deactivate' && (column.trim() === '' || value.trim() === '');
|
|
36
36
|
}
|
|
37
|
-
export function MappingWriteModeFields({ idPrefix, writeMode, missingRecordPolicy, missingRecordColumn, missingRecordValue, originColumn, originValue, maxRemovalShare, dedupConfigured, disabled, onWriteModeChange, onMissingRecordPolicyChange, onMissingRecordColumnChange, onMissingRecordValueChange, onOriginColumnChange, onOriginValueChange, onMaxRemovalShareChange }) {
|
|
37
|
+
export function MappingWriteModeFields({ idPrefix, writeMode, missingRecordPolicy, missingRecordColumn, missingRecordValue, originColumn, originValue, maxRemovalShare, dedupConfigured, disabled, onWriteModeChange, onMissingRecordPolicyChange, onMissingRecordColumnChange, onMissingRecordValueChange, removalScopeColumn, onRemovalScopeColumnChange, onOriginColumnChange, onOriginValueChange, onMaxRemovalShareChange }) {
|
|
38
38
|
const labels = useLabels();
|
|
39
39
|
const modeId = `${idPrefix}-write-mode`;
|
|
40
40
|
const policyId = `${idPrefix}-missing-policy`;
|
|
41
41
|
const columnId = `${idPrefix}-missing-column`;
|
|
42
42
|
const valueId = `${idPrefix}-missing-value`;
|
|
43
43
|
const originColumnId = `${idPrefix}-origin-column`;
|
|
44
|
+
const removalScopeColumnId = `${idPrefix}-removal-scope-column`;
|
|
44
45
|
const originValueId = `${idPrefix}-origin-value`;
|
|
45
46
|
const shareId = `${idPrefix}-max-removal-share`;
|
|
46
47
|
const columnMissing = missingRecordPolicy === 'deactivate' && missingRecordColumn.trim() === '';
|
|
@@ -171,6 +172,20 @@ export function MappingWriteModeFields({ idPrefix, writeMode, missingRecordPolic
|
|
|
171
172
|
})
|
|
172
173
|
]
|
|
173
174
|
}),
|
|
175
|
+
/*#__PURE__*/ _jsx(Field, {
|
|
176
|
+
hint: labels.mappings.removalScopeColumnHint,
|
|
177
|
+
htmlFor: removalScopeColumnId,
|
|
178
|
+
label: labels.mappings.removalScopeColumn,
|
|
179
|
+
children: /*#__PURE__*/ _jsx("input", {
|
|
180
|
+
"aria-describedby": `${removalScopeColumnId}-hint`,
|
|
181
|
+
className: theme.field.input,
|
|
182
|
+
disabled: disabled,
|
|
183
|
+
id: removalScopeColumnId,
|
|
184
|
+
onChange: (event)=>onRemovalScopeColumnChange(event.target.value),
|
|
185
|
+
placeholder: "company_code",
|
|
186
|
+
value: removalScopeColumn
|
|
187
|
+
})
|
|
188
|
+
}),
|
|
174
189
|
originColumn.trim() === '' ? /*#__PURE__*/ _jsx(Notice, {
|
|
175
190
|
title: labels.mappings.originUnsetTitle,
|
|
176
191
|
tone: "warning",
|
|
@@ -26,6 +26,7 @@ export type MappingDraft = {
|
|
|
26
26
|
missingRecordColumn?: string | null;
|
|
27
27
|
missingRecordValue?: string | null;
|
|
28
28
|
/** The mark this mapping puts on its rows, and the only ones it may remove. */
|
|
29
|
+
removalScopeColumn?: string | null;
|
|
29
30
|
originColumn?: string | null;
|
|
30
31
|
originValue?: string | null;
|
|
31
32
|
/** The largest share of existing rows one run may remove; 0 is no ceiling. */
|
|
@@ -29,6 +29,7 @@ import { missingRecordFieldsIncomplete } from './MappingWriteModeFields';
|
|
|
29
29
|
missingRecordPolicy: mapping.missingRecordPolicy,
|
|
30
30
|
missingRecordColumn: mapping.missingRecordColumn,
|
|
31
31
|
missingRecordValue: mapping.missingRecordValue,
|
|
32
|
+
removalScopeColumn: mapping.removalScopeColumn ?? '',
|
|
32
33
|
originColumn: mapping.originColumn ?? '',
|
|
33
34
|
originValue: mapping.originValue ?? '',
|
|
34
35
|
maxRemovalShare: mapping.maxRemovalShare ?? 0,
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a mapping is actually keeping its table up to date.
|
|
3
|
+
*
|
|
4
|
+
* The list already said "Last run: 2026-08-02 12:51". Next to four siblings that
|
|
5
|
+
* all ran the previous night, that line is the difference between a working
|
|
6
|
+
* integration and a table two days out of date — and it takes arithmetic to see
|
|
7
|
+
* it, so nobody did. What happened instead: a chief executive was missing from
|
|
8
|
+
* the directory, a director's title was the one he had held in July, and people
|
|
9
|
+
* who had left were still listed. All four were the same fact, and the screen
|
|
10
|
+
* that knew it showed a timestamp.
|
|
11
|
+
*
|
|
12
|
+
* The cause underneath was smaller still: that mapping had no schedule. Its
|
|
13
|
+
* siblings ran nightly; it only ever ran when somebody pressed a button, and
|
|
14
|
+
* nobody had pressed it since Sunday. Nothing on the screen said so.
|
|
15
|
+
*
|
|
16
|
+
* So this answers two questions the timestamp cannot: does anything run this on
|
|
17
|
+
* its own, and is what it wrote still recent. Both are properties of the row, so
|
|
18
|
+
* they belong on the row.
|
|
19
|
+
*/
|
|
20
|
+
export type MappingStaleness = {
|
|
21
|
+
/** Nothing runs this unless a person does. */
|
|
22
|
+
unscheduled: boolean;
|
|
23
|
+
/** It has never run at all. */
|
|
24
|
+
neverRun: boolean;
|
|
25
|
+
/** Whole days since the last run, null when it has never run. */
|
|
26
|
+
ageDays: number | null;
|
|
27
|
+
/** Worth an operator's attention. */
|
|
28
|
+
tone: 'ok' | 'warning';
|
|
29
|
+
};
|
|
30
|
+
export type StalenessQuestion = {
|
|
31
|
+
lastRunAt?: string | null;
|
|
32
|
+
scheduleEnabled?: boolean | null;
|
|
33
|
+
/** Passed in rather than read, so this is a function of its arguments. */
|
|
34
|
+
now: number;
|
|
35
|
+
/**
|
|
36
|
+
* How old is too old.
|
|
37
|
+
*
|
|
38
|
+
* A day, because every schedule in a normal install runs at least nightly, so
|
|
39
|
+
* a mapping older than that has missed one — and a threshold shorter than the
|
|
40
|
+
* cadence would flag every mapping every morning, which is how a warning
|
|
41
|
+
* stops being read.
|
|
42
|
+
*/
|
|
43
|
+
staleAfterHours?: number;
|
|
44
|
+
};
|
|
45
|
+
export declare function mappingStaleness(question: StalenessQuestion): MappingStaleness;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a mapping is actually keeping its table up to date.
|
|
3
|
+
*
|
|
4
|
+
* The list already said "Last run: 2026-08-02 12:51". Next to four siblings that
|
|
5
|
+
* all ran the previous night, that line is the difference between a working
|
|
6
|
+
* integration and a table two days out of date — and it takes arithmetic to see
|
|
7
|
+
* it, so nobody did. What happened instead: a chief executive was missing from
|
|
8
|
+
* the directory, a director's title was the one he had held in July, and people
|
|
9
|
+
* who had left were still listed. All four were the same fact, and the screen
|
|
10
|
+
* that knew it showed a timestamp.
|
|
11
|
+
*
|
|
12
|
+
* The cause underneath was smaller still: that mapping had no schedule. Its
|
|
13
|
+
* siblings ran nightly; it only ever ran when somebody pressed a button, and
|
|
14
|
+
* nobody had pressed it since Sunday. Nothing on the screen said so.
|
|
15
|
+
*
|
|
16
|
+
* So this answers two questions the timestamp cannot: does anything run this on
|
|
17
|
+
* its own, and is what it wrote still recent. Both are properties of the row, so
|
|
18
|
+
* they belong on the row.
|
|
19
|
+
*/ const HOUR = 60 * 60 * 1000;
|
|
20
|
+
export function mappingStaleness(question) {
|
|
21
|
+
const unscheduled = question.scheduleEnabled !== true;
|
|
22
|
+
const staleAfter = (question.staleAfterHours ?? 24) * HOUR;
|
|
23
|
+
const parsed = question.lastRunAt ? Date.parse(question.lastRunAt) : Number.NaN;
|
|
24
|
+
if (!Number.isFinite(parsed)) {
|
|
25
|
+
/*
|
|
26
|
+
* Never run is only worth a warning when nothing is going to run it.
|
|
27
|
+
* A mapping created five minutes ago with a schedule on it is not a
|
|
28
|
+
* problem — it is a mapping waiting for tonight.
|
|
29
|
+
*/ return {
|
|
30
|
+
unscheduled,
|
|
31
|
+
neverRun: true,
|
|
32
|
+
ageDays: null,
|
|
33
|
+
tone: unscheduled ? 'warning' : 'ok'
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
// A clock skewed forward would otherwise report a negative age and read as
|
|
37
|
+
// fresh; the floor at zero keeps the answer honest either way.
|
|
38
|
+
const age = Math.max(0, question.now - parsed);
|
|
39
|
+
const ageDays = Math.floor(age / (24 * HOUR));
|
|
40
|
+
return {
|
|
41
|
+
unscheduled,
|
|
42
|
+
neverRun: false,
|
|
43
|
+
ageDays,
|
|
44
|
+
tone: age > staleAfter ? 'warning' : 'ok'
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -237,6 +237,8 @@ export type IntegrationsLabels = {
|
|
|
237
237
|
deactivateColumnHint: string;
|
|
238
238
|
deactivateValue: string;
|
|
239
239
|
deactivateValueHint: string;
|
|
240
|
+
removalScopeColumn: string;
|
|
241
|
+
removalScopeColumnHint: string;
|
|
240
242
|
originColumn: string;
|
|
241
243
|
originColumnHint: string;
|
|
242
244
|
originValue: string;
|
|
@@ -595,6 +597,10 @@ export type IntegrationsLabels = {
|
|
|
595
597
|
ui: {
|
|
596
598
|
tryAgain: string;
|
|
597
599
|
neverRun: string;
|
|
600
|
+
/** Nothing runs this mapping unless somebody presses the button. */
|
|
601
|
+
unscheduled: string;
|
|
602
|
+
/** How far behind a mapping's last run is, in whole days. */
|
|
603
|
+
behindByDays: (days: number) => string;
|
|
598
604
|
lastRun: (moment: string) => string;
|
|
599
605
|
shownOfTotal: (shown: string | number, total: string | number) => string;
|
|
600
606
|
runImport: string;
|
|
@@ -217,6 +217,8 @@
|
|
|
217
217
|
deactivateColumnHint: 'A column on the target table.',
|
|
218
218
|
deactivateValue: 'Deactivation value',
|
|
219
219
|
deactivateValueHint: 'Written into that column.',
|
|
220
|
+
removalScopeColumn: 'Removal scope column',
|
|
221
|
+
removalScopeColumnHint: 'For a source read through several calls — one per company, branch or tenant. Name the target column carrying whichever value the calls vary, and removal is limited to the values this run actually read: a call that answers with an error costs its own slice its cleanup instead of stopping the whole run. Blank means every row is a candidate, and one failing call refuses every removal.',
|
|
220
222
|
originColumn: 'Ownership column',
|
|
221
223
|
originColumnHint: 'A column on the target table that this mapping stamps on every row it writes.',
|
|
222
224
|
originValue: 'Ownership mark',
|
|
@@ -522,6 +524,8 @@
|
|
|
522
524
|
ui: {
|
|
523
525
|
tryAgain: 'Try again',
|
|
524
526
|
neverRun: 'Never run',
|
|
527
|
+
unscheduled: 'not scheduled',
|
|
528
|
+
behindByDays: (days)=>days <= 1 ? '1 day behind' : `${days} days behind`,
|
|
525
529
|
lastRun: (moment)=>`Last run ${moment}`,
|
|
526
530
|
shownOfTotal: (shown, total)=>`${shown} of ${total} shown`,
|
|
527
531
|
runImport: 'Import',
|
|
@@ -188,6 +188,13 @@ export type IntegrationMapping = {
|
|
|
188
188
|
missingRecordPolicy: MissingRecordPolicyValue;
|
|
189
189
|
missingRecordColumn?: string | null;
|
|
190
190
|
missingRecordValue?: string | null;
|
|
191
|
+
/**
|
|
192
|
+
* Target column carrying whichever value this mapping's calls vary — a
|
|
193
|
+
* company code, a branch, a tenant. Set, removal is confined to the values
|
|
194
|
+
* this run actually read, so one call answering with an error costs that
|
|
195
|
+
* slice its cleanup instead of refusing the whole run.
|
|
196
|
+
*/
|
|
197
|
+
removalScopeColumn?: string | null;
|
|
191
198
|
/**
|
|
192
199
|
* The mark this mapping puts on every row it writes, and the only rows it is
|
|
193
200
|
* then allowed to take away. Without it a run that removes missing records
|