@manta-eu/react 0.10.0 → 0.12.0

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.
Files changed (53) hide show
  1. package/dist/components/datastore-management/use-datastore-status.d.ts +15 -0
  2. package/dist/components/datastore-management/use-datastore-status.d.ts.map +1 -1
  3. package/dist/components/datastore-management/use-datastore-status.js +73 -2
  4. package/dist/components/datastore-management/use-datastore-status.js.map +1 -1
  5. package/dist/components/explain-fact/ExplainFact.d.ts +2 -0
  6. package/dist/components/explain-fact/ExplainFact.d.ts.map +1 -1
  7. package/dist/components/explain-fact/ExplainFact.js +6 -10
  8. package/dist/components/explain-fact/ExplainFact.js.map +1 -1
  9. package/dist/components/fact/Fact.d.ts +16 -0
  10. package/dist/components/fact/Fact.d.ts.map +1 -0
  11. package/dist/components/fact/Fact.js +27 -0
  12. package/dist/components/fact/Fact.js.map +1 -0
  13. package/dist/globals.css +15 -0
  14. package/dist/providers/KnowledgeGraphStateContext.d.ts +2 -2
  15. package/dist/providers/KnowledgeGraphStateContext.d.ts.map +1 -1
  16. package/dist/providers/KnowledgeGraphStateProvider.d.ts.map +1 -1
  17. package/dist/providers/KnowledgeGraphStateProvider.js +8 -49
  18. package/dist/providers/KnowledgeGraphStateProvider.js.map +1 -1
  19. package/dist/routes/datastores/components/LinkUnlinkedDatastoreDialog.d.ts.map +1 -1
  20. package/dist/routes/datastores/components/LinkUnlinkedDatastoreDialog.js +2 -0
  21. package/dist/routes/datastores/components/LinkUnlinkedDatastoreDialog.js.map +1 -1
  22. package/dist/routes/datastores/components/StatusBadge.d.ts +8 -6
  23. package/dist/routes/datastores/components/StatusBadge.d.ts.map +1 -1
  24. package/dist/routes/datastores/components/StatusBadge.js +59 -20
  25. package/dist/routes/datastores/components/StatusBadge.js.map +1 -1
  26. package/dist/routes/datastores/deletion-warning.d.ts +2 -0
  27. package/dist/routes/datastores/deletion-warning.d.ts.map +1 -0
  28. package/dist/routes/datastores/deletion-warning.js +8 -0
  29. package/dist/routes/datastores/deletion-warning.js.map +1 -0
  30. package/dist/routes/datastores/flatten.d.ts +8 -3
  31. package/dist/routes/datastores/flatten.d.ts.map +1 -1
  32. package/dist/routes/datastores/flatten.js +16 -11
  33. package/dist/routes/datastores/flatten.js.map +1 -1
  34. package/dist/routes/datastores/hooks/useDatastoreColumns.d.ts +12 -2
  35. package/dist/routes/datastores/hooks/useDatastoreColumns.d.ts.map +1 -1
  36. package/dist/routes/datastores/hooks/useDatastoreColumns.js +142 -95
  37. package/dist/routes/datastores/hooks/useDatastoreColumns.js.map +1 -1
  38. package/dist/routes/datastores/index.d.ts.map +1 -1
  39. package/dist/routes/datastores/index.js +48 -57
  40. package/dist/routes/datastores/index.js.map +1 -1
  41. package/package.json +12 -7
  42. package/src/app-subpaths.json +1 -0
  43. package/src/components/datastore-management/use-datastore-status.ts +137 -2
  44. package/src/components/explain-fact/ExplainFact.tsx +19 -17
  45. package/src/components/fact/Fact.tsx +103 -0
  46. package/src/providers/KnowledgeGraphStateContext.ts +2 -2
  47. package/src/providers/KnowledgeGraphStateProvider.tsx +13 -65
  48. package/src/routes/datastores/components/LinkUnlinkedDatastoreDialog.tsx +2 -0
  49. package/src/routes/datastores/components/StatusBadge.tsx +90 -27
  50. package/src/routes/datastores/deletion-warning.ts +7 -0
  51. package/src/routes/datastores/flatten.ts +27 -14
  52. package/src/routes/datastores/hooks/useDatastoreColumns.tsx +198 -117
  53. package/src/routes/datastores/index.tsx +67 -72
@@ -1,4 +1,3 @@
1
- import { enableDatastoreRemoval } from "@manta-eu/client/shared/constants";
2
1
  import { isPresent } from "@manta-eu/lib/present";
3
2
  import type { ColumnDef } from "@tanstack/react-table";
4
3
  import { formatDistanceToNow } from "date-fns";
@@ -13,9 +12,8 @@ import {
13
12
  Plus,
14
13
  Power,
15
14
  RefreshCw,
16
- RotateCcw,
15
+ RotateCw,
17
16
  Trash,
18
- Trash2,
19
17
  TriangleAlert,
20
18
  Users,
21
19
  } from "lucide-react";
@@ -24,12 +22,14 @@ import { match } from "ts-pattern";
24
22
  import {
25
23
  DatastoreStatusBadge,
26
24
  datastoreStatusFilterValue,
25
+ type DatastoreStatusBadgeProps,
27
26
  type DatastoreStatusFilterValue,
28
27
  } from "../components/StatusBadge";
29
28
  import { isUnmirroredOrganization, type DatastoreRow } from "../flatten";
30
29
  import { Badge } from "#components/badge/index";
31
30
  import { Button } from "#components/button/index";
32
31
  import { datastoreStatusCategory, isMaterializing, type DatastoreStatus } from "#components/datastore-management/tree";
32
+ import type { DatastoreDeletion } from "#components/datastore-management/use-datastore-status";
33
33
  import { DisabledActionTooltip } from "#components/disabled-action-tooltip/index";
34
34
  import {
35
35
  Dropdown,
@@ -38,7 +38,7 @@ import {
38
38
  DropdownSeparator,
39
39
  DropdownTrigger,
40
40
  } from "#components/dropdown/index";
41
- import { Tooltip, TooltipContent, TooltipTrigger } from "#components/tooltip/index";
41
+ import { Tooltip, TooltipContent, TooltipTrigger, TruncateTooltip } from "#components/tooltip/index";
42
42
  import { NestedMarker, nestedIndent } from "#ldo-ui";
43
43
  import { cn } from "#lib/utils";
44
44
 
@@ -97,6 +97,7 @@ const columnMinWidths: Record<string, string> = {
97
97
 
98
98
  type UseDatastoreColumnsArgs = {
99
99
  statusByDatastoreId: Record<string, DatastoreStatus>;
100
+ deletionByDatastoreId: Record<string, DatastoreDeletion>;
100
101
  currentDatastoreId?: string;
101
102
  isSwitching?: boolean;
102
103
  // Indentation only means something while rows are in hierarchy order; a sort makes it a lie.
@@ -109,9 +110,9 @@ type UseDatastoreColumnsArgs = {
109
110
  onLink: (row: DatastoreRow) => void;
110
111
  onProvision: (row: DatastoreRow) => void;
111
112
  onLinkExisting: (row: DatastoreRow) => void;
112
- onRemove: (row: DatastoreRow) => void;
113
- onRestore: (row: DatastoreRow) => void;
114
113
  onDelete: (row: DatastoreRow) => void;
114
+ onContinueDeletion: (row: DatastoreRow) => void;
115
+ isContinuingDeletion?: boolean;
115
116
  onRename: (row: DatastoreRow) => void;
116
117
  onRegister: (row: DatastoreRow) => void;
117
118
  registeringDatastoreIds: ReadonlySet<string>;
@@ -125,9 +126,68 @@ type UseDatastoreColumnsArgs = {
125
126
  providerLinkEnabled?: boolean;
126
127
  };
127
128
 
129
+ const unregisteredReason = "Its data is already deleted - continue the deletion to finish.";
130
+
131
+ const unreadableReason = "Couldn't check how far its deletion got - delete it again to restart it.";
132
+
133
+ const lastDeletionStep = "keycloak";
134
+
135
+ function linkBlockedReason(deletion: DatastoreDeletion | undefined): string | undefined {
136
+ if (!deletion) return undefined;
137
+ if (deletion.unreadable === true) return unreadableReason;
138
+ if (deletion.unregistered === true) return unregisteredReason;
139
+ return !isPresent(deletion.error) && !deletion.retrying ? "Being deleted - it can't be linked." : undefined;
140
+ }
141
+
142
+ function deleteBlockedReason(
143
+ deletion: DatastoreDeletion | undefined,
144
+ nestedReason: string | undefined,
145
+ isUnlinked: boolean,
146
+ ): string | undefined {
147
+ if (!deletion) return nestedReason;
148
+ const stepRunning = !isPresent(deletion.error) && !deletion.retrying && deletion.unreadable !== true;
149
+ if (stepRunning) return "Already being deleted.";
150
+ if (deletion.unregistered === true && isUnlinked) return unregisteredReason;
151
+ return nestedReason;
152
+ }
153
+
154
+ function continueLabel(deletion: DatastoreDeletion | undefined): string | undefined {
155
+ if (!deletion) return undefined;
156
+ if (isPresent(deletion.error)) return "Continue deletion";
157
+ if (deletion.retrying) return "Retry now";
158
+ return undefined;
159
+ }
160
+
161
+ export type DeletionActionState = {
162
+ deleteBlockedReason: string | undefined;
163
+ linkBlockedReason: string | undefined;
164
+ addChildBlockedReason: string | undefined;
165
+ // Undefined while no Continue/Retry button applies.
166
+ continueLabel: string | undefined;
167
+ continueBlockedReason: string | undefined;
168
+ };
169
+
170
+ export function deletionActionState(
171
+ deletion: DatastoreDeletion | undefined,
172
+ nestedDatastoreNames: string[] | undefined,
173
+ isUnlinked: boolean,
174
+ ): DeletionActionState {
175
+ const nestedReason = isPresent(nestedDatastoreNames)
176
+ ? `Delete the datastores under it first: ${nestedDatastoreNames.join(", ")}`
177
+ : undefined;
178
+ return {
179
+ deleteBlockedReason: deleteBlockedReason(deletion, nestedReason, isUnlinked),
180
+ linkBlockedReason: linkBlockedReason(deletion),
181
+ addChildBlockedReason: deletion ? "Being deleted." : undefined,
182
+ continueLabel: continueLabel(deletion),
183
+ continueBlockedReason: deletion?.step === lastDeletionStep ? nestedReason : undefined,
184
+ };
185
+ }
186
+
128
187
  export function useDatastoreColumns(args: UseDatastoreColumnsArgs): ColumnDef<DatastoreRow>[] {
129
188
  const {
130
189
  statusByDatastoreId,
190
+ deletionByDatastoreId,
131
191
  currentDatastoreId,
132
192
  isSwitching,
133
193
  indented,
@@ -139,9 +199,9 @@ export function useDatastoreColumns(args: UseDatastoreColumnsArgs): ColumnDef<Da
139
199
  onLink,
140
200
  onProvision,
141
201
  onLinkExisting,
142
- onRemove,
143
- onRestore,
144
202
  onDelete,
203
+ onContinueDeletion,
204
+ isContinuingDeletion,
145
205
  onRename,
146
206
  onRegister,
147
207
  registeringDatastoreIds,
@@ -155,8 +215,14 @@ export function useDatastoreColumns(args: UseDatastoreColumnsArgs): ColumnDef<Da
155
215
  providerLinkEnabled,
156
216
  } = args;
157
217
 
158
- return useMemo(
159
- () => [
218
+ return useMemo<ColumnDef<DatastoreRow>[]>(() => {
219
+ const statusBadgeProps = (row: DatastoreRow): DatastoreStatusBadgeProps => ({
220
+ node: row,
221
+ status: statusByDatastoreId[row.datastoreId ?? ""],
222
+ deletion: deletionByDatastoreId[row.datastoreId ?? ""],
223
+ });
224
+
225
+ return [
160
226
  {
161
227
  id: datastoreColumnIds.name,
162
228
  accessorKey: "name",
@@ -188,20 +254,31 @@ export function useDatastoreColumns(args: UseDatastoreColumnsArgs): ColumnDef<Da
188
254
  </Tooltip>
189
255
  )}
190
256
  {isRealmDefault && (
191
- <Badge variant="copy" size="sm" className="shrink-0">
192
- Default
193
- </Badge>
257
+ <Tooltip>
258
+ <TooltipTrigger asChild>
259
+ <Badge variant="copy" size="sm" className="shrink-0 cursor-default" tabIndex={0}>
260
+ Default
261
+ </Badge>
262
+ </TooltipTrigger>
263
+ <TooltipContent>The realm's own datastore. It can't be deleted.</TooltipContent>
264
+ </Tooltip>
194
265
  )}
195
266
  {current && (
196
- <Badge
197
- data-testid={`datastore-selected-badge-${row.original.id}`}
198
- variant="copy"
199
- size="sm"
200
- className="shrink-0"
201
- >
202
- <Check className="size-3" />
203
- Selected
204
- </Badge>
267
+ <Tooltip>
268
+ <TooltipTrigger asChild>
269
+ <Badge
270
+ data-testid={`datastore-selected-badge-${row.original.id}`}
271
+ variant="copy"
272
+ size="sm"
273
+ className="shrink-0 cursor-default"
274
+ tabIndex={0}
275
+ >
276
+ <Check className="size-3" />
277
+ Selected
278
+ </Badge>
279
+ </TooltipTrigger>
280
+ <TooltipContent>The datastore you're working in.</TooltipContent>
281
+ </Tooltip>
205
282
  )}
206
283
  </div>
207
284
  </div>
@@ -245,13 +322,13 @@ export function useDatastoreColumns(args: UseDatastoreColumnsArgs): ColumnDef<Da
245
322
  if (!match) return <div className={cn(minWidthClassName, "typography-small text-muted-foreground")}>—</div>;
246
323
 
247
324
  return (
248
- <div
249
- className={cn(minWidthClassName, "truncate typography-small")}
250
- title={`${match.organizationName}${match.isOrganization ? "" : match.path}`}
325
+ <TruncateTooltip
326
+ text={`${match.organizationName}${match.isOrganization ? "" : match.path}`}
327
+ className={cn(minWidthClassName, "block truncate typography-small")}
251
328
  >
252
329
  <span className="text-muted-foreground">{match.organizationName}</span>
253
330
  {!match.isOrganization && <span className="text-default-foreground">{match.path}</span>}
254
- </div>
331
+ </TruncateTooltip>
255
332
  );
256
333
  },
257
334
  },
@@ -261,10 +338,10 @@ export function useDatastoreColumns(args: UseDatastoreColumnsArgs): ColumnDef<Da
261
338
  header: datastoreColumnLabels[datastoreColumnIds.status],
262
339
  enableSorting: false,
263
340
  filterFn: (row, _columnId, filterValue: DatastoreStatusFilterValue) =>
264
- datastoreStatusFilterValue(row.original, statusByDatastoreId[row.original.datastoreId ?? ""]) === filterValue,
341
+ datastoreStatusFilterValue(statusBadgeProps(row.original)) === filterValue,
265
342
  cell: ({ row }) => (
266
- <div className={columnMinWidths[datastoreColumnIds.status]}>
267
- <DatastoreStatusBadge node={row.original} status={statusByDatastoreId[row.original.datastoreId ?? ""]} />
343
+ <div className={cn(columnMinWidths[datastoreColumnIds.status], "flex flex-col items-start gap-1")}>
344
+ <DatastoreStatusBadge {...statusBadgeProps(row.original)} />
268
345
  </div>
269
346
  ),
270
347
  },
@@ -295,12 +372,14 @@ export function useDatastoreColumns(args: UseDatastoreColumnsArgs): ColumnDef<Da
295
372
  return <div className={cn(minWidthClassName, "typography-small text-muted-foreground")}>—</div>;
296
373
  }
297
374
  return (
298
- <div
299
- className={cn(minWidthClassName, "truncate typography-small text-muted-foreground")}
300
- title={at.toLocaleString()}
301
- >
302
- {formatDistanceToNow(at, { addSuffix: true })}
303
- </div>
375
+ <Tooltip>
376
+ <TooltipTrigger asChild>
377
+ <div className={cn(minWidthClassName, "truncate typography-small text-muted-foreground")}>
378
+ {formatDistanceToNow(at, { addSuffix: true })}
379
+ </div>
380
+ </TooltipTrigger>
381
+ <TooltipContent>{at.toLocaleString()}</TooltipContent>
382
+ </Tooltip>
304
383
  );
305
384
  },
306
385
  },
@@ -324,10 +403,11 @@ export function useDatastoreColumns(args: UseDatastoreColumnsArgs): ColumnDef<Da
324
403
  .otherwise(() => undefined);
325
404
  const canSelect = hasDatastore && !reason && !current && !isSwitching;
326
405
  const canDelete = hasDatastore && !current;
327
- const nestedDatastoreNames = nestedDatastoreNamesByRowId.get(datastoreRow.id);
328
- const deleteBlockedReason = isPresent(nestedDatastoreNames)
329
- ? `Delete the datastores under it first: ${nestedDatastoreNames.join(", ")}`
330
- : undefined;
406
+ const deletionState = deletionActionState(
407
+ hasDatastore ? deletionByDatastoreId[datastoreId] : undefined,
408
+ nestedDatastoreNamesByRowId.get(datastoreRow.id),
409
+ isUnlinked === true,
410
+ );
331
411
 
332
412
  return (
333
413
  <div className={cn(columnMinWidths[datastoreColumnIds.actions], "flex items-center justify-end gap-1.5")}>
@@ -343,16 +423,33 @@ export function useDatastoreColumns(args: UseDatastoreColumnsArgs): ColumnDef<Da
343
423
  Link to identity provider
344
424
  </Button>
345
425
  )}
426
+ {isPresent(deletionState.continueLabel) && (
427
+ <DisabledActionTooltip reason={deletionState.continueBlockedReason}>
428
+ <Button
429
+ data-testid={`datastore-continue-deletion-${datastoreRow.id}`}
430
+ size="sm"
431
+ variant="secondary"
432
+ icon={RotateCw}
433
+ disabled={isContinuingDeletion === true || isPresent(deletionState.continueBlockedReason)}
434
+ onClick={() => onContinueDeletion(datastoreRow)}
435
+ >
436
+ {deletionState.continueLabel}
437
+ </Button>
438
+ </DisabledActionTooltip>
439
+ )}
346
440
  {isUnlinked && (
347
- <Button
348
- data-testid={`datastore-link-${datastoreRow.id}`}
349
- size="sm"
350
- variant="secondary"
351
- icon={Link2}
352
- onClick={() => onLink(datastoreRow)}
353
- >
354
- Link
355
- </Button>
441
+ <DisabledActionTooltip reason={deletionState.linkBlockedReason}>
442
+ <Button
443
+ data-testid={`datastore-link-${datastoreRow.id}`}
444
+ size="sm"
445
+ variant="secondary"
446
+ icon={Link2}
447
+ disabled={isPresent(deletionState.linkBlockedReason)}
448
+ onClick={() => onLink(datastoreRow)}
449
+ >
450
+ Link
451
+ </Button>
452
+ </DisabledActionTooltip>
356
453
  )}
357
454
  {hasDatastore && !removed && category === "offline" && (
358
455
  <Button
@@ -384,16 +481,17 @@ export function useDatastoreColumns(args: UseDatastoreColumnsArgs): ColumnDef<Da
384
481
  </Button>
385
482
  )}
386
483
  {!isUnlinked && hasDatastore && !current && !removed && (
387
- <Button
388
- data-testid={`datastore-select-${datastoreRow.id}`}
389
- size="sm"
390
- variant="secondary"
391
- disabled={!canSelect}
392
- title={reason}
393
- onClick={() => onSelect(datastoreRow)}
394
- >
395
- Select
396
- </Button>
484
+ <DisabledActionTooltip reason={reason}>
485
+ <Button
486
+ data-testid={`datastore-select-${datastoreRow.id}`}
487
+ size="sm"
488
+ variant="secondary"
489
+ disabled={!canSelect}
490
+ onClick={() => onSelect(datastoreRow)}
491
+ >
492
+ Select
493
+ </Button>
494
+ </DisabledActionTooltip>
397
495
  )}
398
496
  {isRealmDefault || (isUnlinked && !canDelete) ? (
399
497
  // Same-sized spacer keeps Select/Selected aligned with rows that have the ellipsis.
@@ -457,41 +555,24 @@ export function useDatastoreColumns(args: UseDatastoreColumnsArgs): ColumnDef<Da
457
555
  Build datastore now
458
556
  </DropdownItem>
459
557
  )}
460
- <DropdownItem icon={<Plus className="size-4" />} onSelect={() => onAddChild(datastoreRow)}>
461
- Add datastore
462
- </DropdownItem>
463
- {enableDatastoreRemoval && hasDatastore && !removed && (
464
- <>
465
- <DropdownSeparator />
466
- <DropdownItem
467
- className="text-error-foreground"
468
- icon={<Trash2 className="size-4" />}
469
- onSelect={() => onRemove(datastoreRow)}
470
- >
471
- Remove datastore
472
- </DropdownItem>
473
- </>
474
- )}
475
- {enableDatastoreRemoval && hasDatastore && removed && (
476
- <>
477
- <DropdownSeparator />
478
- <DropdownItem
479
- icon={<RotateCcw className="size-4" />}
480
- onSelect={() => onRestore(datastoreRow)}
481
- >
482
- Restore datastore
483
- </DropdownItem>
484
- </>
485
- )}
486
- {!enableDatastoreRemoval && canDelete && <DropdownSeparator />}
558
+ <DisabledActionTooltip reason={deletionState.addChildBlockedReason}>
559
+ <DropdownItem
560
+ icon={<Plus className="size-4" />}
561
+ disabled={isPresent(deletionState.addChildBlockedReason)}
562
+ onSelect={() => onAddChild(datastoreRow)}
563
+ >
564
+ Add datastore
565
+ </DropdownItem>
566
+ </DisabledActionTooltip>
567
+ {canDelete && <DropdownSeparator />}
487
568
  </>
488
569
  )}
489
570
  {canDelete && (
490
- <DisabledActionTooltip reason={deleteBlockedReason}>
571
+ <DisabledActionTooltip reason={deletionState.deleteBlockedReason}>
491
572
  <DropdownItem
492
573
  className="text-error-foreground"
493
574
  icon={<Trash className="size-4" />}
494
- disabled={isPresent(deleteBlockedReason)}
575
+ disabled={isPresent(deletionState.deleteBlockedReason)}
495
576
  onSelect={() => onDelete(datastoreRow)}
496
577
  >
497
578
  Delete permanently
@@ -505,34 +586,34 @@ export function useDatastoreColumns(args: UseDatastoreColumnsArgs): ColumnDef<Da
505
586
  );
506
587
  },
507
588
  },
508
- ],
509
- [
510
- statusByDatastoreId,
511
- currentDatastoreId,
512
- isSwitching,
513
- indented,
514
- matchableRowIds,
515
- nestedDatastoreNamesByRowId,
516
- suggestedMatchByDatastoreId,
517
- onSelect,
518
- onAddChild,
519
- onLink,
520
- onProvision,
521
- onLinkExisting,
522
- onRemove,
523
- onRestore,
524
- onDelete,
525
- onRegister,
526
- registeringDatastoreIds,
527
- removedSubtreeRowIds,
528
- onRename,
529
- onManageUsers,
530
- onManageCustomApps,
531
- onBringOnline,
532
- isBringingOnline,
533
- onLinkProvider,
534
- isLinkingProvider,
535
- providerLinkEnabled,
536
- ],
537
- );
589
+ ];
590
+ }, [
591
+ statusByDatastoreId,
592
+ deletionByDatastoreId,
593
+ currentDatastoreId,
594
+ isSwitching,
595
+ indented,
596
+ matchableRowIds,
597
+ nestedDatastoreNamesByRowId,
598
+ suggestedMatchByDatastoreId,
599
+ onSelect,
600
+ onAddChild,
601
+ onLink,
602
+ onProvision,
603
+ onLinkExisting,
604
+ onDelete,
605
+ onContinueDeletion,
606
+ isContinuingDeletion,
607
+ onRegister,
608
+ registeringDatastoreIds,
609
+ removedSubtreeRowIds,
610
+ onRename,
611
+ onManageUsers,
612
+ onManageCustomApps,
613
+ onBringOnline,
614
+ isBringingOnline,
615
+ onLinkProvider,
616
+ isLinkingProvider,
617
+ providerLinkEnabled,
618
+ ]);
538
619
  }