@anchrd/intel-ui 0.8.3 → 0.8.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anchrd/intel-ui",
3
- "version": "0.8.3",
3
+ "version": "0.8.4",
4
4
  "type": "module",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  AppendKnowledgeTableRowsInput,
3
3
  AppendKnowledgeTableRowsResult,
4
+ ArchiveFlowInput,
4
5
  ArchiveKnowledgeNodeInput,
5
6
  CompleteFlowRunStepInput,
6
7
  CreateFlowInput,
@@ -159,6 +160,9 @@ export function createIntelDataProvider(
159
160
  const params = new URLSearchParams(
160
161
  query.parentId === undefined ? {} : { parentId: query.parentId ?? "" },
161
162
  );
163
+ // Same rule as `parentId`: only the asked-for case is sent. The default is the server's, and a
164
+ // provider that spells it out anyway would have to be changed whenever the server's is.
165
+ if (query.includeArchived) params.set("includeArchived", "true");
162
166
  const search = params.toString();
163
167
  return await request(`/flows${search ? `?${search}` : ""}`, FlowList);
164
168
  }
@@ -366,6 +370,13 @@ export function createIntelDataProvider(
366
370
  body: JSON.stringify(parsed),
367
371
  });
368
372
  },
373
+ async archiveFlow(input) {
374
+ const parsed = ArchiveFlowInput.parse(input);
375
+ return await request(`/flows/${encodeURIComponent(parsed.flowId)}/archive`, Flow, {
376
+ method: "POST",
377
+ body: JSON.stringify(parsed),
378
+ });
379
+ },
369
380
  async saveFlow(input) {
370
381
  const parsed = SaveFlowVersionInput.parse(input);
371
382
  return await request(`/flows/${encodeURIComponent(parsed.flowId)}/versions`, FlowDocument, {
@@ -1,6 +1,7 @@
1
1
  import type {
2
2
  AppendKnowledgeTableRowsInput,
3
3
  AppendKnowledgeTableRowsResult,
4
+ ArchiveFlowInput,
4
5
  ArchiveKnowledgeNodeInput,
5
6
  CompleteFlowRunStepInput,
6
7
  CreateFlowInput,
@@ -117,6 +118,9 @@ export interface IntelDataProvider {
117
118
  getFlowRequirements(flowId: string): Promise<FlowRequirements>;
118
119
  createFlow(input: CreateFlowInput): Promise<Flow>;
119
120
  updateFlow(input: UpdateFlowInput): Promise<Flow>;
121
+ // Archive a flow or take it back out. An archived flow leaves the tree, stops being startable and
122
+ // stops resolving as another flow's callee; its versions stay untouched.
123
+ archiveFlow(input: ArchiveFlowInput): Promise<Flow>;
120
124
  saveFlow(input: SaveFlowVersionInput): Promise<FlowDocument>;
121
125
  // Which version each sub-flow call will take once published, and which of them publishing
122
126
  // freezes. Read before publishing, so the author agrees to the pins rather than discovering them.
package/src/i18n/en.json CHANGED
@@ -72,6 +72,7 @@
72
72
  "resource.menu": "More actions for {title}",
73
73
  "resource.rename": "Rename",
74
74
  "resource.renameTitle": "Rename {title}",
75
+ "resource.archive": "Archive",
75
76
  "resource.conflict": "It was not changed: somebody else changed this entry first. Reload it and try again.",
76
77
  "resource.forbidden": "It was not changed: you may not change this entry.",
77
78
  "resource.failed": "The change was not saved. Reload the latest version and try again.",
@@ -114,7 +115,6 @@
114
115
  "knowledge.shareUnreadableHint": "Nothing is blocked. A run of those flows will simply stop at that step for them.",
115
116
  "knowledge.versions": "Version history",
116
117
  "knowledge.version": "Version {sequence}",
117
- "knowledge.archive": "Archive",
118
118
  "view.showGraph": "Show this level as a graph",
119
119
  "view.showEditor": "Back to the editor",
120
120
  "view.showRuns": "Show this flow's runs",
@@ -125,6 +125,7 @@ export function ResourceMenu({
125
125
  const [sharing, setSharing] = useState(false);
126
126
  const [linksOpen, setLinksOpen] = useState(false);
127
127
  const selected = useRouterState({ select: (state) => selectedFrom(state.location.search) });
128
+ const pathname = useRouterState({ select: (state) => state.location.pathname });
128
129
  // ⚠️ Dragging is a pointer gesture and nothing else: no keyboard, no screen reader, no touch worth
129
130
  // the name. #27 gave moving a second, equal route through a folder picker, and that route lived in
130
131
  // the tree row's menu. With the row menu gone (#58) it lives here, or it does not exist.
@@ -189,9 +190,20 @@ export function ResourceMenu({
189
190
  },
190
191
  });
191
192
 
193
+ // Both records take the same conflict route as a rename: `baseUpdatedAt` from the row that was on
194
+ // screen, so archiving something somebody else has just changed is refused rather than silently
195
+ // applied to a different state than the one that was read.
192
196
  const archive = useMutation({
193
197
  mutationFn: async () => {
194
- if (target.type === "flow") throw new Error("A flow cannot be archived");
198
+ if (target.type === "flow") {
199
+ await data.archiveFlow({
200
+ flowId: target.flow.id,
201
+ baseUpdatedAt: target.flow.updatedAt,
202
+ archived: true,
203
+ idempotencyKey: crypto.randomUUID(),
204
+ });
205
+ return;
206
+ }
195
207
  await data.archiveKnowledge({
196
208
  nodeId: target.node.id,
197
209
  baseUpdatedAt: target.node.updatedAt,
@@ -203,7 +215,14 @@ export function ResourceMenu({
203
215
  await refresh();
204
216
  // The row is gone from the tree. If it was also what the screen was showing, the screen has
205
217
  // nothing left to show either.
206
- if (selected === id) await navigate({ to: "/knowledge", search: {} });
218
+ //
219
+ // ⚠️ It drops the selection where it stands rather than always landing on /knowledge: a flow
220
+ // archived from the flow screen would otherwise throw the reader onto another screen for no
221
+ // reason they could see. A flow reached through the shared tree stays on /knowledge, which is
222
+ // where it was being read.
223
+ if (selected === id) {
224
+ await navigate({ to: pathname === "/flows" ? "/flows" : "/knowledge", search: {} });
225
+ }
207
226
  },
208
227
  });
209
228
 
@@ -260,18 +279,17 @@ export function ResourceMenu({
260
279
  {i18n.t("knowledge.share")}
261
280
  </DropdownMenuItem>
262
281
  ) : null}
263
- {node ? (
264
- <>
265
- <DropdownMenuSeparator />
266
- <DropdownMenuItem
267
- onSelect={() => archive.mutate()}
268
- className="text-destructive focus:text-destructive"
269
- >
270
- <Archive aria-hidden="true" />
271
- {i18n.t("knowledge.archive")}
272
- </DropdownMenuItem>
273
- </>
274
- ) : null}
282
+ {/* A flow is archived like a document (issue 109). It keeps its versions and its runs; what it
283
+ loses is reach — it leaves the tree, cannot be started, and stops resolving as another
284
+ flow's callee. */}
285
+ <DropdownMenuSeparator />
286
+ <DropdownMenuItem
287
+ onSelect={() => archive.mutate()}
288
+ className="text-destructive focus:text-destructive"
289
+ >
290
+ <Archive aria-hidden="true" />
291
+ {i18n.t("resource.archive")}
292
+ </DropdownMenuItem>
275
293
  </DropdownMenuContent>
276
294
  </DropdownMenu>
277
295
  {/* The menu closes on selection, so a refusal has nowhere to live inside it. It stands over