nucleus-core-ts 0.9.818 → 0.9.820

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.
@@ -4,8 +4,16 @@ export type MappingListProps = {
4
4
  /** The connection whose mappings these are. */
5
5
  sourceId: string;
6
6
  selectedId: string | null;
7
+ /**
8
+ * Run THIS one.
9
+ *
10
+ * The import step imports whichever mapping happens to be selected, which is
11
+ * an answer to "run something" and not to "run this". Somebody looking at a
12
+ * list of four wants the fourth, and the row is where they are looking.
13
+ */
14
+ onRun?: (mapping: IntegrationMapping) => void;
7
15
  /** Bumped by the tab after a save or a delete so page one is fetched again. */
8
16
  reloadToken: number;
9
17
  onSelect: (mapping: IntegrationMapping) => void;
10
18
  };
11
- export declare function MappingList({ actions, sourceId, selectedId, reloadToken, onSelect, }: MappingListProps): import("react/jsx-runtime").JSX.Element;
19
+ export declare function MappingList({ actions, sourceId, selectedId, reloadToken, onSelect, onRun, }: MappingListProps): import("react/jsx-runtime").JSX.Element;
@@ -27,7 +27,7 @@ function describeLastRun(value) {
27
27
  const when = new Date(value);
28
28
  return Number.isNaN(when.getTime()) ? 'Never run' : `Last run ${when.toLocaleString()}`;
29
29
  }
30
- export function MappingList({ actions, sourceId, selectedId, reloadToken, onSelect }) {
30
+ export function MappingList({ actions, sourceId, selectedId, reloadToken, onSelect, onRun }) {
31
31
  const labels = useLabels();
32
32
  const store = useIntegrationsStore();
33
33
  const uid = useId();
@@ -142,40 +142,51 @@ export function MappingList({ actions, sourceId, selectedId, reloadToken, onSele
142
142
  }),
143
143
  /*#__PURE__*/ _jsx("ul", {
144
144
  className: theme.list.wrapper,
145
- children: list.items.map((mapping)=>/*#__PURE__*/ _jsx("li", {
146
- children: /*#__PURE__*/ _jsxs("button", {
147
- "aria-current": mapping.id === selectedId ? 'true' : undefined,
148
- className: cn(theme.list.item, mapping.id === selectedId ? theme.list.itemSelected : undefined),
149
- onClick: ()=>onSelect(mapping),
150
- type: "button",
151
- children: [
152
- /*#__PURE__*/ _jsx("span", {
153
- className: theme.list.itemTitle,
154
- children: mapping.name
155
- }),
156
- /*#__PURE__*/ _jsx("span", {
157
- className: theme.list.itemMeta,
158
- children: mapping.targetEntity || 'No target entity'
159
- }),
160
- /*#__PURE__*/ _jsxs("span", {
161
- className: "mt-1 flex flex-wrap items-center gap-1.5",
162
- children: [
163
- /*#__PURE__*/ _jsx(Badge, {
164
- tone: WRITE_MODE_TONE[mapping.writeMode],
165
- children: mapping.writeMode
166
- }),
167
- mapping.enabled === false ? /*#__PURE__*/ _jsx(Badge, {
168
- tone: "neutral",
169
- children: "disabled"
170
- }) : null,
171
- /*#__PURE__*/ _jsx("span", {
172
- className: theme.list.itemMeta,
173
- children: describeLastRun(mapping.lastRunAt)
174
- })
175
- ]
176
- })
177
- ]
178
- })
145
+ children: list.items.map((mapping)=>/*#__PURE__*/ _jsxs("li", {
146
+ className: "flex items-stretch gap-1",
147
+ children: [
148
+ /*#__PURE__*/ _jsxs("button", {
149
+ "aria-current": mapping.id === selectedId ? 'true' : undefined,
150
+ className: cn(theme.list.item, 'flex-1', mapping.id === selectedId ? theme.list.itemSelected : undefined),
151
+ onClick: ()=>onSelect(mapping),
152
+ type: "button",
153
+ children: [
154
+ /*#__PURE__*/ _jsx("span", {
155
+ className: theme.list.itemTitle,
156
+ children: mapping.name
157
+ }),
158
+ /*#__PURE__*/ _jsx("span", {
159
+ className: theme.list.itemMeta,
160
+ children: mapping.targetEntity || 'No target entity'
161
+ }),
162
+ /*#__PURE__*/ _jsxs("span", {
163
+ className: "mt-1 flex flex-wrap items-center gap-1.5",
164
+ children: [
165
+ /*#__PURE__*/ _jsx(Badge, {
166
+ tone: WRITE_MODE_TONE[mapping.writeMode],
167
+ children: mapping.writeMode
168
+ }),
169
+ mapping.enabled === false ? /*#__PURE__*/ _jsx(Badge, {
170
+ tone: "neutral",
171
+ children: "disabled"
172
+ }) : null,
173
+ /*#__PURE__*/ _jsx("span", {
174
+ className: theme.list.itemMeta,
175
+ children: describeLastRun(mapping.lastRunAt)
176
+ })
177
+ ]
178
+ })
179
+ ]
180
+ }),
181
+ onRun ? /*#__PURE__*/ _jsx("button", {
182
+ "aria-label": `Import ${mapping.name}`,
183
+ className: cn(theme.button.secondary, 'self-center whitespace-nowrap'),
184
+ onClick: ()=>onRun(mapping),
185
+ title: `Preview and import ${mapping.name}`,
186
+ type: "button",
187
+ children: "Import"
188
+ }) : null
189
+ ]
179
190
  }, mapping.id))
180
191
  })
181
192
  ]
@@ -223,6 +223,10 @@ export function MappingsTab({ actions }) {
223
223
  children: /*#__PURE__*/ _jsx(MappingList, {
224
224
  actions: actions,
225
225
  sourceId: sourceId,
226
+ onRun: (mapping)=>{
227
+ openMapping(mapping);
228
+ store.setTab('runs');
229
+ },
226
230
  onSelect: openMapping,
227
231
  reloadToken: reloadToken,
228
232
  selectedId: selected?.id ?? null
@@ -93,6 +93,46 @@ export function RunPanel({ mapping, actions, onSubscribeProgress, onRunSettled }
93
93
  onRunSettled?.();
94
94
  }
95
95
  });
96
+ /**
97
+ * Ask the server whether the run has finished.
98
+ *
99
+ * Progress frames are PUSHED — over whatever realtime channel the app wires
100
+ * up — and an app that wires none, or a socket that drops, leaves this panel
101
+ * saying "waiting for the first update" over an import that finished minutes
102
+ * ago, with a Cancel button for a run that is not running. The bar was the
103
+ * only thing on screen and it was lying.
104
+ *
105
+ * So the run's own row is the authority: it is polled while one is active, it
106
+ * settles the panel when the status is terminal, and it says what happened.
107
+ * Realtime stays the fast path and is no longer the only one.
108
+ */ const pollActiveRun = useEffectEvent(()=>{
109
+ const runId = store.activeRunId;
110
+ if (!runId) return;
111
+ actions.listRuns({
112
+ mappingId: mapping.id,
113
+ page: 1,
114
+ limit: 5
115
+ }).then((result)=>{
116
+ if (store.activeRunId !== runId) return;
117
+ const run = result.items.find((item)=>item.id === runId);
118
+ if (!run || run.status === 'running' || run.status === 'queued') return;
119
+ store.setActiveRun(null);
120
+ store.setProgress(null);
121
+ if (run.status === 'failed') {
122
+ store.setError(run.errors?.[0] ?? 'The import failed. Its history entry has the detail.');
123
+ }
124
+ onRunSettled?.();
125
+ }).catch(()=>{
126
+ // A failed poll is not a failed run. The next tick asks again.
127
+ });
128
+ });
129
+ useEffect(()=>{
130
+ if (!activeRunId) return;
131
+ const timer = setInterval(pollActiveRun, 2000);
132
+ return ()=>clearInterval(timer);
133
+ }, [
134
+ activeRunId
135
+ ]);
96
136
  const openProgressStream = useEffectEvent(()=>onSubscribeProgress?.(applyProgress));
97
137
  useEffect(()=>{
98
138
  // The app hands back its own unsubscribe; returning it is the cleanup.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nucleus-core-ts",
3
- "version": "0.9.818",
3
+ "version": "0.9.820",
4
4
  "description": "Production-ready, enterprise-grade TypeScript framework for building multi-tenant APIs",
5
5
  "author": "Hidayet Can Özcan <hidayetcan@gmail.com>",
6
6
  "license": "SEE LICENSE IN LICENSE",