@vendure/dashboard 3.2.4 → 3.3.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/plugin/utils/ast-utils.spec.js +2 -2
  2. package/dist/plugin/utils/schema-generator.js +1 -1
  3. package/dist/plugin/utils/ui-config.js +2 -3
  4. package/dist/plugin/vite-plugin-config.js +4 -6
  5. package/package.json +13 -9
  6. package/src/app/app-providers.tsx +1 -1
  7. package/src/app/routes/_authenticated/_orders/orders.graphql.ts +0 -1
  8. package/src/app/routes/_authenticated/_product-variants/product-variants_.$id.tsx +8 -2
  9. package/src/app/routes/_authenticated/_promotions/promotions_.$id.tsx +6 -0
  10. package/src/app/routes/_authenticated/_system/job-queue.tsx +7 -8
  11. package/src/app/routes/_authenticated/_system/scheduled-tasks.tsx +241 -0
  12. package/src/app/styles.css +15 -0
  13. package/src/lib/components/data-table/data-table-view-options.tsx +1 -1
  14. package/src/lib/components/data-table/data-table.tsx +32 -26
  15. package/src/lib/components/data-table/refresh-button.tsx +25 -0
  16. package/src/lib/components/layout/nav-user.tsx +16 -11
  17. package/src/lib/components/layout/prerelease-popup.tsx +1 -5
  18. package/src/lib/components/shared/alerts.tsx +19 -1
  19. package/src/lib/components/shared/error-page.tsx +2 -2
  20. package/src/lib/components/shared/navigation-confirmation.tsx +20 -10
  21. package/src/lib/components/shared/paginated-list-data-table.tsx +1 -0
  22. package/src/lib/framework/alert/alert-extensions.tsx +31 -0
  23. package/src/lib/framework/alert/alert-item.tsx +47 -0
  24. package/src/lib/framework/alert/alerts-indicator.tsx +23 -0
  25. package/src/lib/framework/alert/types.ts +13 -0
  26. package/src/lib/framework/dashboard-widget/base-widget.tsx +1 -0
  27. package/src/lib/framework/defaults.ts +34 -0
  28. package/src/lib/framework/document-introspection/get-document-structure.ts +1 -2
  29. package/src/lib/framework/extension-api/define-dashboard-extension.ts +15 -5
  30. package/src/lib/framework/extension-api/extension-api-types.ts +81 -12
  31. package/src/lib/framework/layout-engine/layout-extensions.ts +3 -3
  32. package/src/lib/framework/layout-engine/page-layout.tsx +192 -35
  33. package/src/lib/framework/layout-engine/page-provider.tsx +10 -0
  34. package/src/lib/framework/page/detail-page.tsx +62 -9
  35. package/src/lib/framework/page/list-page.tsx +19 -0
  36. package/src/lib/framework/page/page-api.ts +1 -1
  37. package/src/lib/framework/page/use-detail-page.ts +81 -0
  38. package/src/lib/framework/registry/registry-types.ts +6 -2
  39. package/src/lib/graphql/graphql-env.d.ts +25 -9
  40. package/src/lib/hooks/use-auth.tsx +13 -1
  41. package/src/lib/hooks/use-channel.ts +13 -0
  42. package/src/lib/hooks/use-local-format.ts +28 -1
  43. package/src/lib/hooks/use-page.tsx +2 -3
  44. package/src/lib/hooks/use-permissions.ts +13 -0
  45. package/src/lib/index.ts +3 -4
  46. package/src/lib/providers/auth.tsx +11 -1
  47. package/src/lib/providers/channel-provider.tsx +8 -1
  48. package/vite/utils/ast-utils.spec.ts +2 -2
  49. package/vite/utils/schema-generator.ts +4 -5
  50. package/vite/utils/ui-config.ts +7 -3
  51. package/vite/vite-plugin-admin-api-schema.ts +0 -10
  52. package/vite/vite-plugin-config.ts +1 -0
  53. package/src/lib/components/ui/avatar.tsx +0 -38
@@ -11,17 +11,13 @@ export function PrereleasePopup() {
11
11
  description: (
12
12
  <div className="space-y-2">
13
13
  <p>
14
- This is an <span className="font-bold">alpha</span> version of our new Vendure
14
+ This is a <span className="font-bold">beta</span> version of our new Vendure
15
15
  Dashboard!
16
16
  </p>
17
17
  <p>
18
18
  This release allows you to explore the new interface and functionality, but it's not
19
19
  yet ready for production use.
20
20
  </p>
21
- <p>
22
- If you find missing or broken functionality, you don't need to report it on GitHub at
23
- this point - we're already working on it!
24
- </p>
25
21
  </div>
26
22
  ),
27
23
  duration: 1000 * 60,
@@ -1,19 +1,37 @@
1
1
  import { BellIcon } from 'lucide-react';
2
2
  import { Button } from '../ui/button.js';
3
3
  import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '../ui/dialog.js';
4
+ import { useAlerts } from '../../framework/alert/alert-extensions.js';
5
+ import { AlertItem } from '../../framework/alert/alert-item.js';
6
+ import { ScrollArea } from '../ui/scroll-area.js';
7
+ import { AlertsIndicator } from '../../framework/alert/alerts-indicator.js';
4
8
 
5
9
  export function Alerts() {
10
+ const { alerts } = useAlerts();
11
+
12
+ if (alerts.length === 0) {
13
+ return null;
14
+ }
15
+
6
16
  return (
7
17
  <Dialog>
8
18
  <DialogTrigger asChild>
9
- <Button size="icon" variant="ghost">
19
+ <Button size="icon" variant="ghost" className="relative">
10
20
  <BellIcon />
21
+ <AlertsIndicator />
11
22
  </Button>
12
23
  </DialogTrigger>
13
24
  <DialogContent>
14
25
  <DialogHeader>
15
26
  <DialogTitle>Alerts</DialogTitle>
16
27
  </DialogHeader>
28
+ <ScrollArea className="max-h-[500px]">
29
+ <div className="flex flex-col divide-y divide-border">
30
+ {alerts.map(alert => (
31
+ <AlertItem className="py-2" key={alert.id} alert={alert} />
32
+ ))}
33
+ </div>
34
+ </ScrollArea>
17
35
  </DialogContent>
18
36
  </Dialog>
19
37
  );
@@ -13,12 +13,12 @@ export interface ErrorPageProps {
13
13
  */
14
14
  export function ErrorPage({ message }: ErrorPageProps) {
15
15
  return (
16
- <Page>
16
+ <Page pageId='error-page'>
17
17
  <PageTitle>
18
18
  <Trans>Error</Trans>
19
19
  </PageTitle>
20
20
  <PageLayout>
21
- <PageBlock column="main">
21
+ <PageBlock column="main" blockId='error-message'>
22
22
  <Alert variant="destructive">
23
23
  <AlertCircle className="h-4 w-4" />
24
24
  <AlertTitle>Error</AlertTitle>
@@ -1,9 +1,9 @@
1
- import { Trans } from "@/lib/trans.js";
2
- import { useBlocker } from "@tanstack/react-router";
3
- import { UseFormReturn } from "react-hook-form";
1
+ import { Trans } from '@/lib/trans.js';
2
+ import { useBlocker } from '@tanstack/react-router';
3
+ import { UseFormReturn } from 'react-hook-form';
4
4
 
5
- import { Button } from "../ui/button.js";
6
- import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "../ui/dialog.js";
5
+ import { Button } from '../ui/button.js';
6
+ import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '../ui/dialog.js';
7
7
 
8
8
  export interface NavigationConfirmationProps {
9
9
  form: UseFormReturn<any>;
@@ -12,19 +12,29 @@ export interface NavigationConfirmationProps {
12
12
  /**
13
13
  * Navigation confirmation dialog that blocks navigation when the form is dirty.
14
14
  */
15
- export function NavigationConfirmation(props: NavigationConfirmationProps) {
15
+ export function NavigationConfirmation(props: Readonly<NavigationConfirmationProps>) {
16
16
  const { proceed, reset, status } = useBlocker({
17
- shouldBlockFn: () => props.form.formState.isDirty,
17
+ shouldBlockFn: (args) => {
18
+ // When a new entity is being created, we don't want to block navigation
19
+ // to the newly-created entity page.
20
+ const isNavigatingToNewlyCreatedEntity = args.current.fullPath === args.next.fullPath
21
+ && args.current.params.id === 'new' && args.next.params.id !== 'new'
22
+ if (isNavigatingToNewlyCreatedEntity) {
23
+ return false;
24
+ }
25
+ return props.form.formState.isDirty;
26
+ },
18
27
  withResolver: true,
19
28
  enableBeforeUnload: true,
20
- })
29
+ });
21
30
  return (
22
31
  <Dialog open={status === 'blocked'} onOpenChange={reset}>
23
32
  <DialogContent className="sm:max-w-[425px]">
24
33
  <DialogHeader>
25
34
  <DialogTitle><Trans>Confirm navigation</Trans></DialogTitle>
26
35
  <DialogDescription>
27
- <Trans>Are you sure you want to navigate away from this page? Any unsaved changes will be lost.</Trans>
36
+ <Trans>Are you sure you want to navigate away from this page? Any unsaved changes will be
37
+ lost.</Trans>
28
38
  </DialogDescription>
29
39
  </DialogHeader>
30
40
  <DialogFooter>
@@ -35,5 +45,5 @@ export function NavigationConfirmation(props: NavigationConfirmationProps) {
35
45
  </DialogFooter>
36
46
  </DialogContent>
37
47
  </Dialog>
38
- )
48
+ );
39
49
  }
@@ -428,6 +428,7 @@ export function PaginatedListDataTable<
428
428
  facetedFilters={facetedFilters}
429
429
  disableViewOptions={disableViewOptions}
430
430
  setTableOptions={setTableOptions}
431
+ onRefresh={refetchPaginatedList}
431
432
  />
432
433
  </PaginatedListContext.Provider>
433
434
  );
@@ -0,0 +1,31 @@
1
+ import { useEffect } from 'react';
2
+ import { useState } from 'react';
3
+ import { globalRegistry } from '../registry/global-registry.js';
4
+ import { DashboardAlertDefinition } from './types.js';
5
+
6
+ globalRegistry.register('dashboardAlertRegistry', new Map<string, DashboardAlertDefinition>());
7
+
8
+ export function registerAlert<TResponse>(alert: DashboardAlertDefinition<TResponse>) {
9
+ globalRegistry.set('dashboardAlertRegistry', map => {
10
+ map.set(alert.id, alert);
11
+ return map;
12
+ });
13
+ }
14
+
15
+ export function getAlertRegistry() {
16
+ return globalRegistry.get('dashboardAlertRegistry');
17
+ }
18
+
19
+ export function getAlert(id: string) {
20
+ return getAlertRegistry().get(id);
21
+ }
22
+
23
+ export function useAlerts() {
24
+ const [alerts, setAlerts] = useState<DashboardAlertDefinition[]>([]);
25
+
26
+ useEffect(() => {
27
+ setAlerts(Array.from(getAlertRegistry().values()));
28
+ }, []);
29
+
30
+ return { alerts };
31
+ }
@@ -0,0 +1,47 @@
1
+ import { Button } from '@/components/ui/button.js';
2
+ import { useQuery } from '@tanstack/react-query';
3
+ import { ComponentProps } from 'react';
4
+ import { DashboardAlertDefinition } from './types.js';
5
+ import { cn } from '@/lib/utils.js';
6
+ interface AlertItemProps extends ComponentProps<'div'> {
7
+ alert: DashboardAlertDefinition;
8
+ }
9
+
10
+ export function AlertItem({ alert, className, ...props }: Readonly<AlertItemProps>) {
11
+ const { data } = useQuery({
12
+ queryKey: ['alert', alert.id],
13
+ queryFn: () => alert.check(),
14
+ refetchInterval: alert.recheckInterval,
15
+ });
16
+
17
+ const isAlertActive = alert.shouldShow?.(data);
18
+
19
+ if (!isAlertActive) {
20
+ return null;
21
+ }
22
+
23
+ return (
24
+ <div className={cn('flex items-center justify-between gap-1', className)} {...props}>
25
+ <div className="flex flex-col">
26
+ <span className="font-semibold">
27
+ {typeof alert.title === 'string' ? alert.title : alert.title(data)}
28
+ </span>
29
+ <span className="text-sm text-muted-foreground">
30
+ {typeof alert.description === 'string' ? alert.description : alert.description?.(data)}
31
+ </span>
32
+ </div>
33
+ <div className="flex items-center gap-1">
34
+ {alert.actions?.map(action => (
35
+ <Button
36
+ key={action.label}
37
+ variant="secondary"
38
+ size="sm"
39
+ onClick={() => action.onClick(data)}
40
+ >
41
+ {action.label}
42
+ </Button>
43
+ ))}
44
+ </div>
45
+ </div>
46
+ );
47
+ }
@@ -0,0 +1,23 @@
1
+ import { useQueries } from '@tanstack/react-query';
2
+ import { useAlerts } from './alert-extensions.js';
3
+
4
+ export function AlertsIndicator() {
5
+ const { alerts } = useAlerts();
6
+
7
+ const alertsCount = useQueries({
8
+ queries: alerts.map(alert => ({
9
+ queryKey: ['alert', alert.id],
10
+ queryFn: () => alert.check(),
11
+ })),
12
+ combine: results => {
13
+ return results.filter((result, idx) => result.data && alerts[idx].shouldShow?.(result.data))
14
+ .length;
15
+ },
16
+ });
17
+
18
+ return (
19
+ <div className="absolute -right-1 -top-1 rounded-full bg-red-500 text-xs w-4 h-4 flex items-center justify-center">
20
+ {alertsCount}
21
+ </div>
22
+ );
23
+ }
@@ -0,0 +1,13 @@
1
+ export interface DashboardAlertDefinition<TResponse = any> {
2
+ id: string;
3
+ title: string | ((data: TResponse) => string);
4
+ description?: string | ((data: TResponse) => string);
5
+ severity: 'info' | 'warning' | 'error';
6
+ check: () => Promise<TResponse> | TResponse;
7
+ recheckInterval?: number;
8
+ shouldShow?: (data: TResponse) => boolean;
9
+ actions?: Array<{
10
+ label: string;
11
+ onClick: (data: TResponse) => void;
12
+ }>;
13
+ }
@@ -2,6 +2,7 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/com
2
2
  import { cn } from '@/lib/utils.js';
3
3
  import { Trans } from '@/lib/trans.js';
4
4
  import { PropsWithChildren, useRef, useEffect, useState, createContext, useContext } from 'react';
5
+ import type React from 'react';
5
6
 
6
7
  type WidgetDimensions = {
7
8
  width: number;
@@ -122,6 +122,11 @@ export function registerDefaults() {
122
122
  title: 'Healthchecks',
123
123
  url: '/healthchecks',
124
124
  },
125
+ {
126
+ id: 'scheduled-tasks',
127
+ title: 'Scheduled Tasks',
128
+ url: '/scheduled-tasks',
129
+ },
125
130
  ],
126
131
  },
127
132
  {
@@ -216,4 +221,33 @@ export function registerDefaults() {
216
221
  component: OrdersSummaryWidget,
217
222
  defaultSize: { w: 6, h: 3, x: 6, y: 0 },
218
223
  });
224
+
225
+ // registerAlert<boolean>({
226
+ // id: 'test-alert',
227
+ // title: data => `Test Alert ${String(data)}`,
228
+ // description: 'This is a test alert',
229
+ // severity: 'info',
230
+ // check: () => Promise.resolve(true),
231
+ // actions: [
232
+ // {
233
+ // label: 'Test Action',
234
+ // onClick: () => console.log('Test Action'),
235
+ // },
236
+ // ],
237
+ // });
238
+
239
+ // registerAlert<boolean>({
240
+ // id: 'test-alert-2',
241
+ // title: 'Test Alert 2',
242
+ // description: 'This is a test alert 2',
243
+ // severity: 'info',
244
+ // check: () => Promise.resolve(true),
245
+ // shouldShow: data => data === true,
246
+ // actions: [
247
+ // {
248
+ // label: 'Test Action',
249
+ // onClick: () => console.log('Test Action'),
250
+ // },
251
+ // ],
252
+ // });
219
253
  }
@@ -2,11 +2,10 @@ import type { TypedDocumentNode } from '@graphql-typed-document-node/core';
2
2
  import { VariablesOf } from 'gql.tada';
3
3
  import {
4
4
  DocumentNode,
5
- OperationDefinitionNode,
6
5
  FieldNode,
7
6
  FragmentDefinitionNode,
8
7
  FragmentSpreadNode,
9
- VariableDefinitionNode,
8
+ OperationDefinitionNode,
10
9
  } from 'graphql';
11
10
  import { DefinitionNode, NamedTypeNode, SelectionSetNode, TypeNode } from 'graphql/language/ast.js';
12
11
  import { schemaInfo } from 'virtual:admin-api-schema';
@@ -1,14 +1,14 @@
1
- import { registerDashboardWidget } from '@/framework/dashboard-widget/widget-extensions.js';
2
- import { DashboardExtension } from '@/framework/extension-api/extension-api-types.js';
3
- import { addNavMenuItem, NavMenuItem } from '@/framework/nav-menu/nav-menu-extensions.js';
4
- import { registerRoute } from '@/framework/page/page-api.js';
5
-
1
+ import { registerDashboardWidget } from '../dashboard-widget/widget-extensions.js';
6
2
  import {
7
3
  registerDashboardActionBarItem,
8
4
  registerDashboardPageBlock,
9
5
  } from '../layout-engine/layout-extensions.js';
6
+ import { addNavMenuItem, NavMenuItem } from '../nav-menu/nav-menu-extensions.js';
7
+ import { registerRoute } from '../page/page-api.js';
10
8
  import { globalRegistry } from '../registry/global-registry.js';
11
9
 
10
+ import { DashboardExtension } from './extension-api-types.js';
11
+
12
12
  globalRegistry.register('extensionSourceChangeCallbacks', new Set<() => void>());
13
13
  globalRegistry.register('registerDashboardExtensionCallbacks', new Set<() => void>());
14
14
 
@@ -22,6 +22,16 @@ export function executeDashboardExtensionCallbacks() {
22
22
  }
23
23
  }
24
24
 
25
+ /**
26
+ * @description
27
+ * **Status: Developer Preview**
28
+ *
29
+ * The main entry point for extensions to the React-based dashboard.
30
+ *
31
+ *
32
+ * @docsCategory extensions
33
+ * @since 3.3.0
34
+ */
25
35
  export function defineDashboardExtension(extension: DashboardExtension) {
26
36
  globalRegistry.get('registerDashboardExtensionCallbacks').add(() => {
27
37
  if (extension.routes) {
@@ -1,9 +1,10 @@
1
- import { NavMenuItem } from '@/framework/nav-menu/nav-menu-extensions.js';
1
+ import { PageContextValue } from '@/framework/layout-engine/page-provider.js';
2
2
  import { AnyRoute, RouteOptions } from '@tanstack/react-router';
3
- import React from 'react';
3
+ import type React from 'react';
4
4
 
5
+ import { DashboardAlertDefinition } from '../alert/types.js';
5
6
  import { DashboardWidgetDefinition } from '../dashboard-widget/types.js';
6
- import { PageContext } from '../layout-engine/page-layout.js';
7
+ import { NavMenuItem } from '../nav-menu/nav-menu-extensions.js';
7
8
 
8
9
  export interface DashboardRouteDefinition {
9
10
  component: (route: AnyRoute) => React.ReactNode;
@@ -17,42 +18,110 @@ export interface ActionBarButtonState {
17
18
  visible: boolean;
18
19
  }
19
20
 
21
+ /**
22
+ * @description
23
+ * **Status: Developer Preview**
24
+ *
25
+ * Allows you to define custom action bar items for any page in the dashboard.
26
+ *
27
+ * @docsCategory extensions
28
+ * @since 3.3.0
29
+ */
20
30
  export interface DashboardActionBarItem {
21
- locationId: string;
22
- component: React.FunctionComponent<{ context: PageContext }>;
31
+ /**
32
+ * @description
33
+ * The ID of the page where the action bar item should be displayed.
34
+ */
35
+ pageId: string;
36
+ /**
37
+ * @description
38
+ * A React component that will be rendered in the action bar.
39
+ */
40
+ component: React.FunctionComponent<{ context: PageContextValue }>;
41
+ /**
42
+ * @description
43
+ * Any permissions that are required to display this action bar item.
44
+ */
23
45
  requiresPermission?: string | string[];
24
46
  }
25
47
 
26
48
  export interface DashboardActionBarDropdownMenuItem {
27
49
  locationId: string;
28
- component: React.FunctionComponent<{ context: PageContext }>;
50
+ component: React.FunctionComponent<{ context: PageContextValue }>;
29
51
  requiresPermission?: string | string[];
30
52
  }
31
53
 
32
54
  export type PageBlockPosition = { blockId: string; order: 'before' | 'after' | 'replace' };
33
55
 
56
+ /**
57
+ * @description
58
+ * **Status: Developer Preview**
59
+ *
60
+ * The location of a page block in the dashboard. The location can be found by turning on
61
+ * "developer mode" in the dashboard user menu (bottom left corner) and then
62
+ * clicking the `< />` icon when hovering over a page block.
63
+ *
64
+ * @docsCategory extensions
65
+ * @since 3.3.0
66
+ */
34
67
  export type PageBlockLocation = {
35
68
  pageId: string;
36
69
  position: PageBlockPosition;
37
70
  column: 'main' | 'side';
38
71
  };
39
72
 
73
+ /**
74
+ * @description
75
+ * **Status: Developer Preview**
76
+ *
77
+ * This allows you to insert a custom component into a specific location
78
+ * on any page in the dashboard.
79
+ *
80
+ * @docsCategory extensions
81
+ * @since 3.3.0
82
+ */
40
83
  export interface DashboardPageBlockDefinition {
41
84
  id: string;
42
85
  title?: React.ReactNode;
43
86
  location: PageBlockLocation;
44
- component: React.FunctionComponent<{ context: PageContext }>;
87
+ component: React.FunctionComponent<{ context: PageContextValue }>;
45
88
  requiresPermission?: string | string[];
46
89
  }
47
90
 
48
91
  /**
49
92
  * @description
50
- * The main entry point for a dashboard extension.
93
+ * **Status: Developer Preview**
94
+ *
51
95
  * This is used to define the routes, widgets, etc. that will be displayed in the dashboard.
96
+ *
97
+ * @docsCategory extensions
98
+ * @since 3.3.0
52
99
  */
53
100
  export interface DashboardExtension {
54
- routes: DashboardRouteDefinition[];
55
- widgets: DashboardWidgetDefinition[];
56
- actionBarItems: DashboardActionBarItem[];
57
- pageBlocks: DashboardPageBlockDefinition[];
101
+ /**
102
+ * @description
103
+ * Allows you to define custom routes such as list or detail views.
104
+ */
105
+ routes?: DashboardRouteDefinition[];
106
+ /**
107
+ * @description
108
+ * Allows you to define custom page blocks for any page in the dashboard.
109
+ */
110
+ pageBlocks?: DashboardPageBlockDefinition[];
111
+ /**
112
+ * @description
113
+ * Allows you to define custom action bar items for any page in the dashboard.
114
+ */
115
+ actionBarItems?: DashboardActionBarItem[];
116
+ /**
117
+ * @description
118
+ * Not yet implemented
119
+ */
120
+ alerts?: DashboardAlertDefinition[];
121
+ /**
122
+ * @description
123
+ * Allows you to define custom routes for the dashboard, which will render the
124
+ * given components and optionally also add a nav menu item.
125
+ */
126
+ widgets?: DashboardWidgetDefinition[];
58
127
  }
@@ -9,13 +9,13 @@ globalRegistry.register('dashboardPageBlockRegistry', new Map<string, DashboardP
9
9
 
10
10
  export function registerDashboardActionBarItem(item: DashboardActionBarItem) {
11
11
  globalRegistry.set('dashboardActionBarItemRegistry', map => {
12
- map.set(item.locationId, [...(map.get(item.locationId) ?? []), item]);
12
+ map.set(item.pageId, [...(map.get(item.pageId) ?? []), item]);
13
13
  return map;
14
14
  });
15
15
  }
16
16
 
17
- export function getDashboardActionBarItems(locationId: string) {
18
- return globalRegistry.get('dashboardActionBarItemRegistry').get(locationId) ?? [];
17
+ export function getDashboardActionBarItems(pageId: string) {
18
+ return globalRegistry.get('dashboardActionBarItemRegistry').get(pageId) ?? [];
19
19
  }
20
20
 
21
21
  export function registerDashboardPageBlock(block: DashboardPageBlockDefinition) {