nucleus-core-ts 0.9.816 → 0.9.818

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.
@@ -1,2 +1,2 @@
1
1
  import type { IntegrationsPageProps } from '../types';
2
- export declare function IntegrationsPage({ title, subtitle, className, actions, labels: labelsOverride, focusTargetEntity, onSubscribeProgress, }: IntegrationsPageProps): import("react/jsx-runtime").JSX.Element;
2
+ export declare function IntegrationsPage({ title, subtitle, className, actions, labels: labelsOverride, focusTargetEntity, focusStep, onSubscribeProgress, }: IntegrationsPageProps): import("react/jsx-runtime").JSX.Element;
@@ -25,7 +25,7 @@ import { RunsTab } from './RunsTab';
25
25
  * page from the server rather than being handed a list to slice.
26
26
  */ gsap.registerPlugin(useGSAP);
27
27
  const theme = integrationsPageTheme;
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, onSubscribeProgress }) {
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 }) {
29
29
  const store = useIntegrationsStore();
30
30
  // Resolved here rather than read from the context, because this component is
31
31
  // the one that PROVIDES it — and passed down so a control four levels deep
@@ -92,12 +92,13 @@ export function IntegrationsPage({ title = 'Integrations', subtitle = 'Connect a
92
92
  * is chosen for them. With several, the choice is real and stays theirs.
93
93
  */ const takeFocus = useEffectEvent(()=>{
94
94
  if (!focusTargetEntity) return;
95
- store.setFocusTargetEntity(focusTargetEntity);
95
+ store.setFocusTargetEntity(focusTargetEntity, focusStep ?? 'mapping');
96
96
  });
97
97
  useEffect(()=>{
98
98
  takeFocus();
99
99
  }, [
100
- focusTargetEntity
100
+ focusTargetEntity,
101
+ focusStep
101
102
  ]);
102
103
  const followFocus = useEffectEvent(()=>{
103
104
  if (!store.focusTargetEntity) return;
@@ -26,5 +26,19 @@ export type MappingEditorProps = {
26
26
  onSave: (draft: MappingDraft) => void;
27
27
  onDelete: (id: string) => void;
28
28
  onCancel: () => void;
29
+ /**
30
+ * Go and import with this mapping.
31
+ *
32
+ * The import lives on its own step, and it should: it is the destructive one,
33
+ * and it wants the whole width for a preview. But somebody who has just
34
+ * finished describing a mapping wants to RUN it, and leaving them to find the
35
+ * step themselves is the panel forgetting what they were doing. Absent for an
36
+ * unsaved mapping, because there is nothing to run yet.
37
+ *
38
+ * The DRAFT is handed over, not the id: what is on screen is what the operator
39
+ * means to run, so edits they have not saved yet are saved on the way rather
40
+ * than quietly ignored by an import of the older version.
41
+ */
42
+ onRunImport?: (draft: MappingDraft) => void;
29
43
  };
30
- export declare function MappingEditor({ mapping, targets, targetsLoading, endpoints, freshSample, saving, deleting, onSave, onDelete, onCancel, }: MappingEditorProps): import("react/jsx-runtime").JSX.Element;
44
+ export declare function MappingEditor({ mapping, targets, targetsLoading, endpoints, freshSample, saving, deleting, onSave, onDelete, onCancel, onRunImport, }: MappingEditorProps): import("react/jsx-runtime").JSX.Element;
@@ -21,7 +21,7 @@ import { CardSkeleton } from './Skeletons';
21
21
  * Mount it with a `key` of the mapping id so switching rows starts a fresh
22
22
  * draft instead of pouring one mapping's edits into another.
23
23
  */ const theme = integrationsPageTheme;
24
- export function MappingEditor({ mapping, targets, targetsLoading, endpoints, freshSample, saving, deleting, onSave, onDelete, onCancel }) {
24
+ export function MappingEditor({ mapping, targets, targetsLoading, endpoints, freshSample, saving, deleting, onSave, onDelete, onCancel, onRunImport }) {
25
25
  const labels = useLabels();
26
26
  const uid = useId();
27
27
  const [draft, setDraft] = useState(mapping);
@@ -236,6 +236,13 @@ export function MappingEditor({ mapping, targets, targetsLoading, endpoints, fre
236
236
  type: "button",
237
237
  children: "Cancel"
238
238
  }),
239
+ draft.id && onRunImport ? /*#__PURE__*/ _jsx("button", {
240
+ className: theme.button.secondary,
241
+ disabled: busy,
242
+ onClick: ()=>onRunImport(draft),
243
+ type: "button",
244
+ children: "Preview and import"
245
+ }) : null,
239
246
  /*#__PURE__*/ _jsx("button", {
240
247
  className: theme.button.primary,
241
248
  disabled: busy || issues.length > 0,
@@ -95,6 +95,7 @@ export function MappingsTab({ actions }) {
95
95
  */ const followFocus = useEffectEvent(()=>{
96
96
  const entity = store.focusTargetEntity;
97
97
  if (!entity || sourceId === '') return;
98
+ const step = store.focusStep;
98
99
  store.setFocusTargetEntity(null);
99
100
  actions.listMappings(sourceId, {
100
101
  page: 1,
@@ -110,6 +111,9 @@ export function MappingsTab({ actions }) {
110
111
  const found = result.items[0];
111
112
  if (found) {
112
113
  openMapping(found);
114
+ // "Fill this table now" is a different errand from "describe how it is
115
+ // filled", and both are offered from the table itself.
116
+ if (step === 'import') store.setTab('runs');
113
117
  return;
114
118
  }
115
119
  setSelected(null);
@@ -132,7 +136,7 @@ export function MappingsTab({ actions }) {
132
136
  setSelected(null);
133
137
  store.selectMapping(null);
134
138
  };
135
- const save = (draft)=>{
139
+ const save = (draft, then)=>{
136
140
  setSaving(true);
137
141
  store.setError(null);
138
142
  actions.saveMapping(draft).then((saved)=>{
@@ -142,11 +146,31 @@ export function MappingsTab({ actions }) {
142
146
  setSelected(saved);
143
147
  store.selectMapping(saved.id);
144
148
  setReloadToken((token)=>token + 1);
149
+ then?.(saved);
145
150
  }).catch((error)=>{
146
151
  setSaving(false);
147
152
  store.setError(messageOf(error));
148
153
  });
149
154
  };
155
+ /**
156
+ * Take this mapping to the import step.
157
+ *
158
+ * Saved first when it differs from what is stored: the operator is looking at
159
+ * the version they mean to run, and importing the older one instead — with no
160
+ * sign that it happened — is the worst kind of quiet.
161
+ */ const runImport = (draft)=>{
162
+ const stored = selected ? toMappingDraft(selected) : null;
163
+ const dirty = !stored || JSON.stringify(stored) !== JSON.stringify(draft);
164
+ const go = (id)=>{
165
+ store.selectMapping(id);
166
+ store.setTab('runs');
167
+ };
168
+ if (!dirty && draft.id) {
169
+ go(draft.id);
170
+ return;
171
+ }
172
+ save(draft, (saved)=>go(saved.id));
173
+ };
150
174
  const remove = (id)=>{
151
175
  if (id === '') return;
152
176
  setDeleting(true);
@@ -213,6 +237,7 @@ export function MappingsTab({ actions }) {
213
237
  mapping: draft,
214
238
  onCancel: closeEditor,
215
239
  onDelete: remove,
240
+ onRunImport: runImport,
216
241
  onSave: save,
217
242
  saving: saving,
218
243
  targets: targets,
@@ -41,8 +41,9 @@ export const { useStore: useIntegrationsStore } = createStore(initial, {
41
41
  store.plan = null;
42
42
  store.error = null;
43
43
  }),
44
- setFocusTargetEntity: (store)=>(entity)=>{
44
+ setFocusTargetEntity: (store)=>(entity, step)=>{
45
45
  store.focusTargetEntity = entity;
46
+ if (step) store.focusStep = step;
46
47
  },
47
48
  setTab: (store)=>(tab)=>{
48
49
  store.tab = tab;
@@ -29,6 +29,8 @@ export type State = {
29
29
  * later click on another mapping is not dragged back.
30
30
  */
31
31
  focusTargetEntity: string | null;
32
+ /** Whether that caller wanted to DESCRIBE the import or RUN it. */
33
+ focusStep: 'mapping' | 'import';
32
34
  endpoints: IntegrationEndpoint[];
33
35
  endpointsTotal: number;
34
36
  endpointsPage: number;
@@ -65,7 +67,7 @@ export type Actions = {
65
67
  setSourcesLoading: (loading: boolean) => void;
66
68
  selectSource: (id: string | null) => void;
67
69
  setTab: (tab: ConnectionTab) => void;
68
- setFocusTargetEntity: (entity: string | null) => void;
70
+ setFocusTargetEntity: (entity: string | null, step?: 'mapping' | 'import') => void;
69
71
  setEndpoints: (items: IntegrationEndpoint[], total: number) => void;
70
72
  /**
71
73
  * Just the count, without the rows.
@@ -7,6 +7,7 @@ export const initial = {
7
7
  selectedSourceId: null,
8
8
  tab: 'connection',
9
9
  focusTargetEntity: null,
10
+ focusStep: 'mapping',
10
11
  endpoints: [],
11
12
  endpointsTotal: 0,
12
13
  endpointsPage: 1,
@@ -106,6 +106,14 @@ export type IntegrationsPageProps = {
106
106
  * it — instead of on a chooser the caller has already answered.
107
107
  */
108
108
  focusTargetEntity?: string;
109
+ /**
110
+ * What they came to do with it.
111
+ *
112
+ * "Describe how this table is filled" and "fill it now" are two different
113
+ * errands, and a management screen offers both. Landing on the wrong one
114
+ * means finding the right step by hand — which is what the button was for.
115
+ */
116
+ focusStep?: 'mapping' | 'import';
109
117
  /**
110
118
  * The panel's own words, in the app's language.
111
119
  *