nucleus-core-ts 0.9.825 → 0.9.826

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.
@@ -7,6 +7,7 @@ import { FieldMappingRows } from './FieldMappingRows';
7
7
  import { endpointLabel, fieldsOfStoredSample } from './formSupport';
8
8
  import { MappingAccountFields } from './MappingAccountFields';
9
9
  import { MappingPasswordField } from './MappingPasswordField';
10
+ import { MappingScheduleFields } from './MappingScheduleFields';
10
11
  import { MappingWriteModeFields } from './MappingWriteModeFields';
11
12
  import { mappingDraftIssues } from './mappingDraft';
12
13
  import { Field } from './Primitives';
@@ -213,6 +214,18 @@ export function MappingEditor({ mapping, targets, targetsLoading, endpoints, fre
213
214
  targets: targets,
214
215
  value: draft.accountRule ?? null
215
216
  }),
217
+ /*#__PURE__*/ _jsx(MappingScheduleFields, {
218
+ cron: draft.scheduleCron ?? '',
219
+ disabled: busy,
220
+ enabled: draft.scheduleEnabled ?? false,
221
+ needsPassword: Boolean(draft.accountRule || draft.passwordTargetColumn),
222
+ onChange: (next)=>patch({
223
+ scheduleEnabled: next.enabled,
224
+ scheduleCron: next.cron,
225
+ scheduleTimezone: next.timezone
226
+ }),
227
+ timezone: draft.scheduleTimezone ?? ''
228
+ }),
216
229
  /*#__PURE__*/ _jsx(MappingPasswordField, {
217
230
  disabled: busy,
218
231
  onChange: (value)=>patch({
@@ -0,0 +1,14 @@
1
+ export type MappingScheduleFieldsProps = {
2
+ enabled: boolean;
3
+ cron: string;
4
+ timezone: string;
5
+ /** A mapping that creates logins cannot run unattended — there is no password. */
6
+ needsPassword?: boolean;
7
+ disabled?: boolean;
8
+ onChange: (next: {
9
+ enabled: boolean;
10
+ cron: string;
11
+ timezone: string;
12
+ }) => void;
13
+ };
14
+ export declare function MappingScheduleFields({ enabled, cron, timezone, needsPassword, disabled, onChange, }: MappingScheduleFieldsProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,150 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useId } from 'react';
4
+ import { describeCron, parseCron } from '../../../../src/Services/Integrations/cron';
5
+ import { integrationsPageTheme } from '../theme';
6
+ import { Field, Notice } from './Primitives';
7
+ /**
8
+ * Running this import without anybody clicking anything.
9
+ *
10
+ * The mapping has carried a cron, a timezone and an enabled flag since it was
11
+ * first designed, and nothing read them — an operator could fill one in, save
12
+ * it, see it on the screen, and it would never run. This is the screen for the
13
+ * half that now does.
14
+ *
15
+ * The expression is checked as it is typed and read back in plain words. Cron is
16
+ * a language people write once a year, and "0 2 * * *" and "2 0 * * *" look
17
+ * alike until one of them has been importing at two minutes past midnight for a
18
+ * month.
19
+ */ const theme = integrationsPageTheme;
20
+ /** Offered because they are what people actually mean, spelled correctly. */ const PRESETS = [
21
+ {
22
+ label: 'Every night at 02:00',
23
+ cron: '0 2 * * *'
24
+ },
25
+ {
26
+ label: 'Every weekday at 07:00',
27
+ cron: '0 7 * * 1-5'
28
+ },
29
+ {
30
+ label: 'Every Monday at 06:00',
31
+ cron: '0 6 * * 1'
32
+ },
33
+ {
34
+ label: 'Every hour',
35
+ cron: '0 * * * *'
36
+ },
37
+ {
38
+ label: 'Every 15 minutes',
39
+ cron: '*/15 * * * *'
40
+ }
41
+ ];
42
+ export function MappingScheduleFields({ enabled, cron, timezone, needsPassword, disabled, onChange }) {
43
+ const uid = useId();
44
+ const parsed = cron.trim() === '' ? null : parseCron(cron);
45
+ const invalid = parsed !== null && !parsed.ok;
46
+ const patch = (next)=>{
47
+ onChange({
48
+ enabled,
49
+ cron,
50
+ timezone,
51
+ ...next
52
+ });
53
+ };
54
+ return /*#__PURE__*/ _jsxs("section", {
55
+ className: "flex flex-col gap-3",
56
+ children: [
57
+ /*#__PURE__*/ _jsxs("div", {
58
+ className: "flex flex-wrap items-center justify-between gap-2",
59
+ children: [
60
+ /*#__PURE__*/ _jsx("h3", {
61
+ className: theme.card.title,
62
+ children: "Run this import on a schedule"
63
+ }),
64
+ /*#__PURE__*/ _jsxs("label", {
65
+ className: "flex items-center gap-2 text-sm",
66
+ children: [
67
+ /*#__PURE__*/ _jsx("input", {
68
+ checked: enabled,
69
+ disabled: disabled,
70
+ id: `${uid}-enabled`,
71
+ onChange: (event)=>patch({
72
+ enabled: event.target.checked
73
+ }),
74
+ type: "checkbox"
75
+ }),
76
+ /*#__PURE__*/ _jsx("span", {
77
+ className: theme.field.label,
78
+ children: "Enabled"
79
+ })
80
+ ]
81
+ })
82
+ ]
83
+ }),
84
+ enabled ? /*#__PURE__*/ _jsxs(_Fragment, {
85
+ children: [
86
+ /*#__PURE__*/ _jsxs("div", {
87
+ className: "grid grid-cols-1 gap-3 sm:grid-cols-2",
88
+ children: [
89
+ /*#__PURE__*/ _jsx(Field, {
90
+ error: invalid && !parsed.ok ? parsed.error : null,
91
+ hint: cron.trim() === '' ? 'Five parts: minute hour day-of-month month day-of-week.' : describeCron(cron, timezone),
92
+ htmlFor: `${uid}-cron`,
93
+ label: "When",
94
+ children: /*#__PURE__*/ _jsx("input", {
95
+ className: theme.field.input,
96
+ disabled: disabled,
97
+ id: `${uid}-cron`,
98
+ onChange: (event)=>patch({
99
+ cron: event.target.value
100
+ }),
101
+ placeholder: "0 2 * * *",
102
+ value: cron
103
+ })
104
+ }),
105
+ /*#__PURE__*/ _jsx(Field, {
106
+ hint: "The company's own clock, so 02:00 stays 02:00 across a daylight-saving change.",
107
+ htmlFor: `${uid}-timezone`,
108
+ label: "In this timezone",
109
+ children: /*#__PURE__*/ _jsx("input", {
110
+ className: theme.field.input,
111
+ disabled: disabled,
112
+ id: `${uid}-timezone`,
113
+ onChange: (event)=>patch({
114
+ timezone: event.target.value
115
+ }),
116
+ placeholder: "Europe/Istanbul",
117
+ value: timezone
118
+ })
119
+ })
120
+ ]
121
+ }),
122
+ /*#__PURE__*/ _jsx("div", {
123
+ className: "flex flex-wrap gap-2",
124
+ children: PRESETS.map((preset)=>/*#__PURE__*/ _jsx("button", {
125
+ className: theme.button.ghost,
126
+ disabled: disabled,
127
+ onClick: ()=>patch({
128
+ cron: preset.cron
129
+ }),
130
+ type: "button",
131
+ children: preset.label
132
+ }, preset.cron))
133
+ }),
134
+ needsPassword ? /*#__PURE__*/ _jsx(Notice, {
135
+ title: "This import cannot run on its own",
136
+ tone: "warning",
137
+ children: "It creates a login for every new record, and a login needs a password — which there is nobody to type at that hour. Either start this import by hand, or turn the login off and give people their accounts another way."
138
+ }) : null,
139
+ /*#__PURE__*/ _jsx("p", {
140
+ className: theme.field.hint,
141
+ children: "Every server running this app checks the schedule, and exactly one of them takes the import — the same lock that stops two people running it at once."
142
+ })
143
+ ]
144
+ }) : /*#__PURE__*/ _jsx("p", {
145
+ className: theme.list.empty,
146
+ children: "Off. This import runs when somebody asks for it, and at no other time."
147
+ })
148
+ ]
149
+ });
150
+ }
@@ -31,6 +31,10 @@ export type MappingDraft = {
31
31
  passwordTargetColumn?: string | null;
32
32
  /** Creates a login for each new record, in the same import. */
33
33
  accountRule?: AccountRuleValue | null;
34
+ /** Runs on its own, when the expression names this minute. */
35
+ scheduleEnabled?: boolean;
36
+ scheduleCron?: string | null;
37
+ scheduleTimezone?: string | null;
34
38
  enabled?: boolean;
35
39
  };
36
40
  /**
@@ -32,6 +32,9 @@ import { missingRecordFieldsIncomplete } from './MappingWriteModeFields';
32
32
  referenceChecks: asArray(mapping.referenceChecks),
33
33
  passwordTargetColumn: mapping.passwordTargetColumn ?? null,
34
34
  accountRule: mapping.accountRule ?? null,
35
+ scheduleEnabled: mapping.scheduleEnabled ?? false,
36
+ scheduleCron: mapping.scheduleCron ?? '',
37
+ scheduleTimezone: mapping.scheduleTimezone ?? '',
35
38
  enabled: mapping.enabled
36
39
  };
37
40
  }
@@ -47,6 +50,9 @@ import { missingRecordFieldsIncomplete } from './MappingWriteModeFields';
47
50
  if (draft.endpointId.trim() === '') issues.push('Select the endpoint this mapping reads from.');
48
51
  const usable = asArray(draft.fieldMappings).some((row)=>row.target.trim() !== '' && (row.source.trim() !== '' || (row.constant ?? '').trim() !== ''));
49
52
  if (!usable) issues.push('Map at least one field to a target column.');
53
+ if (draft.scheduleEnabled && (draft.scheduleCron ?? '').trim() === '') {
54
+ issues.push('A schedule that is on needs to say when.');
55
+ }
50
56
  const account = draft.accountRule;
51
57
  if (account) {
52
58
  const missing = [
@@ -5,6 +5,7 @@ export { ConnectionTransfer } from './components/ConnectionTransfer';
5
5
  export { EndpointsTab } from './components/EndpointsTab';
6
6
  export { IntegrationsPage } from './components/IntegrationsPage';
7
7
  export { MappingAccountFields } from './components/MappingAccountFields';
8
+ export { MappingScheduleFields } from './components/MappingScheduleFields';
8
9
  export { MappingsTab } from './components/MappingsTab';
9
10
  export type { BadgeProps, BadgeTone, CodeBlockProps, FieldProps, NoticeProps, NoticeTone, StatProps, } from './components/Primitives';
10
11
  export { Badge, CodeBlock, Field, Notice, Stat } from './components/Primitives';
@@ -4,6 +4,7 @@ export { ConnectionTransfer } from './components/ConnectionTransfer';
4
4
  export { EndpointsTab } from './components/EndpointsTab';
5
5
  export { IntegrationsPage } from './components/IntegrationsPage';
6
6
  export { MappingAccountFields } from './components/MappingAccountFields';
7
+ export { MappingScheduleFields } from './components/MappingScheduleFields';
7
8
  export { MappingsTab } from './components/MappingsTab';
8
9
  export { Badge, CodeBlock, Field, Notice, Stat } from './components/Primitives';
9
10
  export { RunPlanSuggestions } from './components/RunPlanSuggestions';
@@ -152,6 +152,10 @@ export type IntegrationMapping = {
152
152
  passwordTargetColumn?: string | null;
153
153
  /** Creates a login for each new record, in the same import. */
154
154
  accountRule?: AccountRuleValue | null;
155
+ /** Runs on its own, without anybody clicking anything. */
156
+ scheduleEnabled?: boolean | null;
157
+ scheduleCron?: string | null;
158
+ scheduleTimezone?: string | null;
155
159
  lastRunAt?: string | null;
156
160
  enabled?: boolean;
157
161
  };