diff --git a/cypress/component/CreateModal.cy.tsx b/cypress/component/CreateModal.cy.tsx
index 5d6e2f5a..0273e198 100644
--- a/cypress/component/CreateModal.cy.tsx
+++ b/cypress/component/CreateModal.cy.tsx
@@ -3,6 +3,7 @@ import { CreateModal } from '../../src/Components/CreateModal/CreateModal';
import Portal from '@redhat-cloud-services/frontend-components-notifications/Portal';
import { useAtomValue } from 'jotai';
import { notificationsAtom, useRemoveNotification } from '../../src/state/notificationsAtom';
+import { backendFlagAtom, store } from '../../src/state/store';
const NotificationPortal = () => {
const notifications = useAtomValue(notificationsAtom);
@@ -23,6 +24,10 @@ const mockDashboardResponse = {
};
describe('CreateModal', () => {
+ beforeEach(() => {
+ store.set(backendFlagAtom, true);
+ });
+
it('renders modal with title when isOpen=true', () => {
cy.mount();
cy.contains('Create new blank dashboard').should('be.visible');
diff --git a/cypress/component/DuplicateModal.cy.tsx b/cypress/component/DuplicateModal.cy.tsx
index bd38d0d3..478b9696 100644
--- a/cypress/component/DuplicateModal.cy.tsx
+++ b/cypress/component/DuplicateModal.cy.tsx
@@ -5,6 +5,7 @@ import { useAtomValue, useSetAtom } from 'jotai';
import { notificationsAtom, useRemoveNotification } from '../../src/state/notificationsAtom';
import { dashboardsAtom } from '../../src/state/dashboardsAtom';
import { DashboardTemplate } from '../../src/api/dashboard-templates';
+import { backendFlagAtom, store } from '../../src/state/store';
const NotificationPortal = () => {
const notifications = useAtomValue(notificationsAtom);
@@ -58,6 +59,9 @@ const mockCopyResponse = {
};
describe('DuplicateModal', () => {
+ beforeEach(() => {
+ store.set(backendFlagAtom, true);
+ });
it('renders modal with title when isOpen=true', () => {
cy.mount();
diff --git a/src/App.tsx b/src/App.tsx
index 628c7b63..2f8e9610 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -2,7 +2,7 @@ import React from 'react';
import DefaultRoute from './Routes/Default/Default';
const App = () => {
- return ;
+ return ;
};
export default App;
diff --git a/src/Components/CreateModal/CreateModal.tsx b/src/Components/CreateModal/CreateModal.tsx
index 867f84ff..fec9180d 100644
--- a/src/Components/CreateModal/CreateModal.tsx
+++ b/src/Components/CreateModal/CreateModal.tsx
@@ -8,10 +8,12 @@ interface CreateModalProps {
isOpen: boolean;
onClose: () => void;
onSuccess?: () => void;
+ layoutType?: string;
}
-export const CreateModal: React.FunctionComponent = ({ isOpen, onClose, onSuccess }) => {
- const { name, setName, setAsHomepage, setSetAsHomepage, isLoading, error, isFormValid, createDashboard, reset } = useCreateBlankDashboard();
+export const CreateModal: React.FunctionComponent = ({ isOpen, onClose, onSuccess, layoutType }) => {
+ const { name, setName, setAsHomepage, setSetAsHomepage, isLoading, error, isFormValid, createDashboard, reset } =
+ useCreateBlankDashboard(layoutType);
const addNotification = useAddNotification();
const handleNameChange = (_event: React.FormEvent, value: string) => {
diff --git a/src/Components/Header/Header.tsx b/src/Components/Header/Header.tsx
index e22c72cc..dc604227 100644
--- a/src/Components/Header/Header.tsx
+++ b/src/Components/Header/Header.tsx
@@ -27,7 +27,7 @@ import { CodeIcon, CopyIcon, EditAltIcon, EllipsisVIcon, PlusCircleIcon, PlusIco
import { useAtom, useSetAtom } from 'jotai';
import { drawerExpandedAtom } from '../../state/drawerExpandedAtom';
import { templateIdAtom } from '../../state/templateAtom';
-import { resetDashboardTemplate } from '../../api/dashboard-templates';
+import { useApi } from '../../hooks/useApi';
import useCurrentUser from '../../hooks/useCurrentUser';
import { WarningModal } from '@patternfly/react-component-groups';
import { Link } from 'react-router-dom';
@@ -37,7 +37,7 @@ import { CreateModal } from '../CreateModal/CreateModal';
import { ImportModal } from '../DashboardHub/ImportModal/ImportModal';
import { DuplicateModal } from '../DuplicateModal/DuplicateModal';
-export const KebabDropdown = () => {
+export const KebabDropdown = ({ layoutType }: { layoutType?: string }) => {
const { dashboards } = useGetDashboards();
const [isOpen, setIsOpen] = useState(false);
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
@@ -198,7 +198,7 @@ export const KebabDropdown = () => {
toggleRef={toggleRef}
popperProps={{ position: 'end' }}
/>
- setIsCreateModalOpen(false)} />
+ setIsCreateModalOpen(false)} layoutType={layoutType} />
setIsImportModalOpen(false)} />
setIsDuplicateModalOpen(false)} />
>
@@ -209,6 +209,7 @@ const Controls = () => {
const [isOpen, setIsOpen] = useState(false);
const toggleOpen = useSetAtom(drawerExpandedAtom);
const [templateId, setTemplateId] = useAtom(templateIdAtom);
+ const api = useApi();
return (
<>
@@ -223,7 +224,7 @@ const Controls = () => {
onConfirm={() => {
setIsOpen(false);
if (templateId > 0) {
- resetDashboardTemplate(templateId).then(() => {
+ api.resetDashboardTemplate(templateId).then(() => {
setTemplateId(NaN);
});
}
@@ -260,7 +261,7 @@ const Controls = () => {
);
};
-const Header = () => {
+const Header = ({ layoutType }: { layoutType?: string }) => {
const { currentUser } = useCurrentUser();
const userName = currentUser?.first_name && currentUser?.last_name ? ` ${currentUser.first_name} ${currentUser.last_name}` : currentUser?.username;
const isDashboardHub = useFlag('platform.widget-layout.dashboard-dropdown');
@@ -281,7 +282,7 @@ const Header = () => {
{isDashboardHub && (
-
+
)}
diff --git a/src/Modules/DashboardHub.tsx b/src/Modules/DashboardHub.tsx
index b0c6add8..5f35f5e9 100644
--- a/src/Modules/DashboardHub.tsx
+++ b/src/Modules/DashboardHub.tsx
@@ -1,18 +1,26 @@
-import React from 'react';
+import React, { useEffect } from 'react';
import Header from '../Components/DashboardHub/Header/Header';
import DashboardTable from '../Components/DashboardHub/DashboardTable/DashboardTable';
import { PageSection } from '@patternfly/react-core';
import useGetDashboards from '../hooks/useGetDashboards';
import Portal from '@redhat-cloud-services/frontend-components-notifications/Portal';
-import { useAtomValue } from 'jotai';
+import { Provider, useAtomValue, useSetAtom } from 'jotai';
+import { useFlag } from '@unleash/proxy-client-react';
import { notificationsAtom, useRemoveNotification } from '../state/notificationsAtom';
import { Route, Routes } from 'react-router-dom';
import GenericDashboardPage from './GenericDashboardPage';
+import { backendFlagAtom, store } from '../state/store';
-const DashboardHub = () => {
+const DashboardHubInner = () => {
const notifications = useAtomValue(notificationsAtom);
const removeNotification = useRemoveNotification();
const { dashboards, fetchDashboards } = useGetDashboards();
+ const setBackendFlag = useSetAtom(backendFlagAtom);
+ const isNewBackend = useFlag('platform.widget-layout.new-backend');
+
+ useEffect(() => {
+ setBackendFlag(isNewBackend);
+ }, [isNewBackend]);
return (
@@ -34,4 +42,10 @@ const DashboardHub = () => {
);
};
+const DashboardHub = () => (
+
+
+
+);
+
export default DashboardHub;
diff --git a/src/Routes/Default/Default.tsx b/src/Routes/Default/Default.tsx
index aa9cfb2f..f306d548 100644
--- a/src/Routes/Default/Default.tsx
+++ b/src/Routes/Default/Default.tsx
@@ -1,7 +1,9 @@
import { PageSection } from '@patternfly/react-core';
import AddWidgetDrawer from '../../Components/WidgetDrawer/WidgetDrawer';
import GridLayout from '../../Components/DnDLayout/GridLayout';
-import { useAtomValue, useSetAtom } from 'jotai';
+import { Provider, useAtomValue, useSetAtom } from 'jotai';
+import { useFlag } from '@unleash/proxy-client-react';
+import { backendFlagAtom, store } from '../../state/store';
import { lockedLayoutAtom } from '../../state/lockedLayoutAtom';
import { notificationsAtom, useRemoveNotification } from '../../state/notificationsAtom';
import Header from '../../Components/Header/Header';
@@ -13,13 +15,19 @@ import Portal from '@redhat-cloud-services/frontend-components-notifications/Por
import useChrome from '@redhat-cloud-services/frontend-components/useChrome';
import { resolvedWidgetMappingAtom } from '../../state/widgetMappingAtom';
-const DefaultRoute = (props: { layoutType?: LayoutTypes }) => {
+const DefaultRouteInner = (props: { layoutType: LayoutTypes }) => {
const isLayoutLocked = useAtomValue(lockedLayoutAtom);
const notifications = useAtomValue(notificationsAtom);
const removeNotification = useRemoveNotification();
const { template, saveTemplate, isLoaded, layoutRef } = useDashboardConfig(props.layoutType);
const resolveWidgetMapping = useSetAtom(resolvedWidgetMappingAtom);
const { visibilityFunctions } = useChrome();
+ const setBackendFlag = useSetAtom(backendFlagAtom);
+ const isNewBackend = useFlag('platform.widget-layout.new-backend');
+
+ useEffect(() => {
+ setBackendFlag(isNewBackend);
+ }, [isNewBackend]);
useEffect(() => {
if (visibilityFunctions) {
@@ -30,7 +38,7 @@ const DefaultRoute = (props: { layoutType?: LayoutTypes }) => {
return (
-
+
@@ -40,4 +48,10 @@ const DefaultRoute = (props: { layoutType?: LayoutTypes }) => {
);
};
+const DefaultRoute = (props: { layoutType: LayoutTypes }) => (
+
+
+
+);
+
export default DefaultRoute;
diff --git a/src/api/dashboard-templates-new.ts b/src/api/dashboard-templates-new.ts
new file mode 100644
index 00000000..e3c47719
--- /dev/null
+++ b/src/api/dashboard-templates-new.ts
@@ -0,0 +1,367 @@
+import { LayoutItem } from 'react-grid-layout';
+import { ScalprumComponentProps } from '@scalprum/react-core';
+import { dropping_elem_id } from '../consts';
+import { VisibilityFunctions } from '@redhat-cloud-services/types';
+
+const API_BASE = '/api/widget-layout/v1';
+
+const getRequestHeaders = () => ({
+ Accept: 'application/json',
+ 'Content-Type': 'application/json',
+});
+
+export const widgetIdSeparator = '#';
+
+export type LayoutTypes = string;
+
+export type Variants = 'sm' | 'md' | 'lg' | 'xl';
+
+export type LayoutWithTitle = LayoutItem & { title: string };
+
+export type TemplateConfig = {
+ [k in Variants]: LayoutWithTitle[];
+};
+
+export type PartialTemplateConfig = Partial;
+
+export type ExtendedLayoutItem = LayoutWithTitle & {
+ widgetType: string;
+ config?: WidgetConfiguration;
+ locked?: boolean;
+};
+
+export type ExtendedTemplateConfig = {
+ [k in Variants]: ExtendedLayoutItem[];
+};
+
+export type PartialExtendedTemplateConfig = Partial;
+
+export type BaseTemplate = {
+ name: string;
+ displayName: string;
+ templateConfig: TemplateConfig;
+};
+
+export type DashboardTemplate = {
+ id: number;
+ createdAt: string;
+ updatedAt: string;
+ deletedAt: string | null;
+ userId: string;
+ default: boolean;
+ templateBase: {
+ name: string;
+ displayName: string;
+ };
+ templateConfig: TemplateConfig;
+ dashboardName: string;
+};
+
+export type ExportDashboardTemplate = {
+ templateBase: {
+ name: string;
+ displayName: string;
+ };
+ templateConfig: TemplateConfig;
+};
+
+export class DashboardTemplatesError extends Error {
+ constructor(message: string, readonly status: number, readonly response: Response) {
+ super(message);
+
+ Object.defineProperty(this, 'name', {
+ value: new.target.name,
+ configurable: true,
+ });
+
+ if (typeof Error.captureStackTrace === 'function') {
+ Error.captureStackTrace(this, this.constructor);
+ } else {
+ this.stack = new Error(message).stack;
+ }
+ }
+}
+
+export type WidgetDefaults = {
+ w: number;
+ h: number;
+ maxH: number;
+ minH: number;
+ minW?: number;
+};
+
+export type WidgetHeaderLink = {
+ title?: string;
+ href?: string;
+};
+
+type VisibilityFunctionKeys = keyof VisibilityFunctions;
+
+export type WidgetPermission = {
+ method: VisibilityFunctionKeys;
+ args?: Parameters;
+};
+
+export type WidgetConfiguration = {
+ icon?: string;
+ headerLink?: WidgetHeaderLink;
+ title?: string;
+ permissions?: WidgetPermission[];
+};
+
+export type WidgetMapping = {
+ [key: string]: Pick & {
+ defaults: WidgetDefaults;
+ config?: WidgetConfiguration;
+ };
+};
+
+const handleErrors = (resp: Response) => {
+ if (!resp.ok) {
+ throw new DashboardTemplatesError('widget-layout API fetch error', resp.status, resp);
+ }
+};
+
+export const getWidgetIdentifier = (widgetType: string, uniqueId: string = crypto.randomUUID()) => {
+ return `${widgetType}${widgetIdSeparator}${uniqueId}`;
+};
+
+// GET /api/widget-layout/v1/{id}
+export const getDashboardTemplate = async (templateId: number): Promise => {
+ const resp = await fetch(`${API_BASE}/${templateId}`, {
+ method: 'GET',
+ headers: getRequestHeaders(),
+ });
+ handleErrors(resp);
+ const json = await resp.json();
+ return json;
+};
+
+// GET /api/widget-layout/v1/base-templates
+// GET /api/widget-layout/v1/base-templates/{name}
+export async function getBaseDashboardTemplate(): Promise;
+export async function getBaseDashboardTemplate(type: LayoutTypes): Promise;
+export async function getBaseDashboardTemplate(type?: LayoutTypes): Promise {
+ if (type) {
+ const resp = await fetch(`${API_BASE}/base-templates/${type}`, {
+ method: 'GET',
+ headers: getRequestHeaders(),
+ });
+ handleErrors(resp);
+ return resp.json();
+ }
+ const resp = await fetch(`${API_BASE}/base-templates`, {
+ method: 'GET',
+ headers: getRequestHeaders(),
+ });
+ handleErrors(resp);
+ const json = await resp.json();
+ return json.data;
+}
+
+// GET /api/widget-layout/v1/?dashboardType={type}
+export async function getDashboardTemplates(): Promise;
+export async function getDashboardTemplates(type: LayoutTypes): Promise;
+export async function getDashboardTemplates(type?: LayoutTypes): Promise {
+ const resp = await fetch(`${API_BASE}/${type ? `?dashboardType=${type}` : ''}`, {
+ method: 'GET',
+ headers: getRequestHeaders(),
+ });
+ handleErrors(resp);
+ const json = await resp.json();
+ return json.data;
+}
+
+// GET /api/widget-layout/v1/
+export async function getUsersDashboards(): Promise {
+ const resp = await fetch(`${API_BASE}/`, {
+ method: 'GET',
+ headers: getRequestHeaders(),
+ });
+ handleErrors(resp);
+ const json = await resp.json();
+ return json.data;
+}
+
+// GET /api/widget-layout/v1/widget-mapping
+export async function getWidgetMapping(): Promise {
+ const resp = await fetch(`${API_BASE}/widget-mapping`, {
+ method: 'GET',
+ headers: getRequestHeaders(),
+ });
+ handleErrors(resp);
+ const json = await resp.json();
+ return json.data;
+}
+
+// POST /api/widget-layout/v1/{id}/reset
+export const resetDashboardTemplate = async (templateId: number): Promise => {
+ const resp = await fetch(`${API_BASE}/${templateId}/reset`, {
+ method: 'POST',
+ headers: getRequestHeaders(),
+ });
+ handleErrors(resp);
+ const json = await resp.json();
+ return json.data;
+};
+
+// PATCH /api/widget-layout/v1/{id}
+export const patchDashboardTemplate = async (
+ templateId: DashboardTemplate['id'],
+ data: { templateConfig: PartialTemplateConfig }
+): Promise => {
+ const resp = await fetch(`${API_BASE}/${templateId}`, {
+ method: 'PATCH',
+ headers: getRequestHeaders(),
+ body: JSON.stringify(data),
+ });
+ handleErrors(resp);
+ const json = await resp.json();
+ return json.data;
+};
+
+// PATCH /api/widget-layout/v1/{id}
+export const patchDashboardTemplateHub = async (
+ templateId: DashboardTemplate['id'],
+ data: { templateConfig: PartialTemplateConfig }
+): Promise => {
+ const resp = await fetch(`${API_BASE}/${templateId}`, {
+ method: 'PATCH',
+ headers: getRequestHeaders(),
+ body: JSON.stringify(data),
+ });
+ handleErrors(resp);
+ const json = await resp.json();
+ return json.data;
+};
+
+// DELETE /api/widget-layout/v1/{id}
+export const deleteDashboardTemplate = async (templateId: DashboardTemplate['id']): Promise => {
+ const resp = await fetch(`${API_BASE}/${templateId}`, {
+ method: 'DELETE',
+ headers: getRequestHeaders(),
+ });
+ handleErrors(resp);
+ return resp.status === 204;
+};
+
+// DELETE /api/widget-layout/v1/{id}
+export const deleteDashboardTemplateFromHub = async (templateId: DashboardTemplate['id']): Promise => {
+ const resp = await fetch(`${API_BASE}/${templateId}`, {
+ method: 'DELETE',
+ headers: getRequestHeaders(),
+ });
+ handleErrors(resp);
+ return resp.status === 204;
+};
+
+// PATCH /api/widget-layout/v1/{id}/rename
+export const renameDashboardTemplate = async (templateId: DashboardTemplate['id'], data: { dashboardName: string }): Promise => {
+ const resp = await fetch(`/api/widget-layout/v1/${templateId}/rename`, {
+ method: 'PATCH',
+ headers: getRequestHeaders(),
+ body: JSON.stringify(data),
+ });
+ handleErrors(resp);
+ const json = await resp.json();
+ return json.data;
+};
+
+// POST /api/widget-layout/v1/{id}/copy
+export const copyDashboardTemplate = async (templateId: DashboardTemplate['id'], data: { dashboardName: string }): Promise => {
+ const resp = await fetch(`${API_BASE}/${templateId}/copy`, {
+ method: 'POST',
+ headers: getRequestHeaders(),
+ body: JSON.stringify(data),
+ });
+ handleErrors(resp);
+ const json = await resp.json();
+ return json;
+};
+
+export const getDefaultTemplate = (templates: DashboardTemplate[]): DashboardTemplate | undefined => {
+ return templates.find((itm) => itm.default === true);
+};
+
+// POST /api/widget-layout/v1/{id}/default
+export const setDefaultTemplate = async (templateId: DashboardTemplate['id']): Promise => {
+ const resp = await fetch(`${API_BASE}/${templateId}/default`, {
+ method: 'POST',
+ headers: getRequestHeaders(),
+ });
+ handleErrors(resp);
+ const json = await resp.json();
+ return json;
+};
+
+// // GET /api/widget-layout/v1/base-templates/{name}/fork
+// export const forkBaseTemplate = async (baseTemplateName: string): Promise => {
+// const resp = await fetch(`${API_BASE}/base-templates/${baseTemplateName}/fork`, {
+// method: 'GET',
+// headers: getRequestHeaders(),
+// });
+// handleErrors(resp);
+// const json = await resp.json();
+// return json;
+// };
+
+// POST /api/widget-layout/v1/import
+export const importDashboardTemplate = async (data: {
+ dashboardName: string;
+ templateBase: {
+ name: string;
+ displayName: string;
+ };
+ templateConfig: TemplateConfig;
+}): Promise => {
+ const resp = await fetch(`${API_BASE}/import`, {
+ method: 'POST',
+ headers: getRequestHeaders(),
+ body: JSON.stringify(data),
+ });
+ handleErrors(resp);
+ const json = await resp.json();
+ return json;
+};
+
+// GET /api/widget-layout/v1/{id}/export
+export const exportDashboardTemplate = async (templateId: number): Promise => {
+ const resp = await fetch(`${API_BASE}/${templateId}/export`, {
+ method: 'GET',
+ headers: getRequestHeaders(),
+ });
+ handleErrors(resp);
+ const json = await resp.json();
+ return json;
+};
+
+export const mapWidgetDefaults = (id: string): [string, string] => {
+ const [widgetType, i] = id.split(widgetIdSeparator);
+ return [widgetType, i];
+};
+
+export const mapTemplateConfigToExtendedTemplateConfig = (templateConfig: TemplateConfig): ExtendedTemplateConfig => {
+ const result: ExtendedTemplateConfig = { sm: [], md: [], lg: [], xl: [] };
+ (Object.keys(templateConfig) as Variants[]).forEach((key) => {
+ result[key] = templateConfig[key].map(
+ (layoutWithTitle: LayoutWithTitle): ExtendedLayoutItem => ({
+ ...layoutWithTitle,
+ widgetType: mapWidgetDefaults(layoutWithTitle.i)[0],
+ })
+ );
+ });
+ return result;
+};
+
+export const extendLayout = (extendedTemplateConfig: ExtendedTemplateConfig): ExtendedTemplateConfig => {
+ const result: ExtendedTemplateConfig = { sm: [], md: [], lg: [], xl: [] };
+ (Object.keys(extendedTemplateConfig) as Variants[]).forEach((key) => {
+ result[key] = extendedTemplateConfig[key]
+ .filter(({ i }) => i !== dropping_elem_id)
+ .map((item) => ({
+ ...item,
+ widgetType: mapWidgetDefaults(item.i)[0],
+ }));
+ });
+ return result;
+};
diff --git a/src/api/dashboard-templates-old.ts b/src/api/dashboard-templates-old.ts
new file mode 100644
index 00000000..06fe50e1
--- /dev/null
+++ b/src/api/dashboard-templates-old.ts
@@ -0,0 +1,341 @@
+import { LayoutItem } from 'react-grid-layout';
+import { ScalprumComponentProps } from '@scalprum/react-core';
+import { dropping_elem_id } from '../consts';
+import { VisibilityFunctions } from '@redhat-cloud-services/types';
+
+const getRequestHeaders = () => ({
+ Accept: 'application/json',
+ 'Content-Type': 'application/json',
+});
+
+export const widgetIdSeparator = '#';
+
+export type LayoutTypes = string;
+
+export type Variants = 'sm' | 'md' | 'lg' | 'xl';
+
+export type LayoutWithTitle = LayoutItem & { title: string };
+
+export type TemplateConfig = {
+ [k in Variants]: LayoutWithTitle[];
+};
+
+export type PartialTemplateConfig = Partial;
+
+// extended type the UI tracks but not the backend
+export type ExtendedLayoutItem = LayoutWithTitle & {
+ widgetType: string;
+ config?: WidgetConfiguration;
+ locked?: boolean;
+};
+
+// extended type the UI tracks but not the backend
+export type ExtendedTemplateConfig = {
+ [k in Variants]: ExtendedLayoutItem[];
+};
+
+// extended type the UI tracks but not the backend
+export type PartialExtendedTemplateConfig = Partial;
+
+export type BaseTemplate = {
+ name: string;
+ displayName: string;
+ templateConfig: TemplateConfig;
+};
+
+export type DashboardTemplate = {
+ id: number;
+ createdAt: string;
+ updatedAt: string;
+ deletedAt: string | null;
+ userIdentityID: number;
+ default: boolean;
+ templateBase: {
+ name: string;
+ displayName: string;
+ };
+ templateConfig: TemplateConfig;
+ dashboardName: string;
+};
+
+export type ExportDashboardTemplate = {
+ templateBase: {
+ name: string;
+ displayName: string;
+ };
+ templateConfig: TemplateConfig;
+};
+
+// TODO use dynamic-plugin-sdk CustomError as base class instead
+export class DashboardTemplatesError extends Error {
+ constructor(message: string, readonly status: number, readonly response: Response) {
+ super(message);
+
+ Object.defineProperty(this, 'name', {
+ value: new.target.name,
+ configurable: true,
+ });
+
+ if (typeof Error.captureStackTrace === 'function') {
+ Error.captureStackTrace(this, this.constructor);
+ } else {
+ this.stack = new Error(message).stack;
+ }
+ }
+}
+
+export type WidgetDefaults = {
+ w: number;
+ h: number;
+ maxH: number;
+ minH: number;
+ minW?: number;
+};
+
+export type WidgetHeaderLink = {
+ title?: string;
+ href?: string;
+};
+
+type VisibilityFunctionKeys = keyof VisibilityFunctions;
+
+export type WidgetPermission = {
+ method: VisibilityFunctionKeys;
+ args?: Parameters;
+};
+
+export type WidgetConfiguration = {
+ icon?: string;
+ headerLink?: WidgetHeaderLink;
+ title?: string;
+ permissions?: WidgetPermission[];
+};
+
+export type WidgetMapping = {
+ [key: string]: Pick & {
+ defaults: WidgetDefaults;
+ config?: WidgetConfiguration;
+ };
+};
+
+const handleErrors = (resp: Response) => {
+ if (!resp.ok) {
+ throw new DashboardTemplatesError('chrome-service dashboard-templates API fetch error', resp.status, resp);
+ }
+};
+
+export const getWidgetIdentifier = (widgetType: string, uniqueId: string = crypto.randomUUID()) => {
+ return `${widgetType}${widgetIdSeparator}${uniqueId}`;
+};
+
+export const getDashboardTemplate = async (templateId: number): Promise => {
+ const resp = await fetch(`/api/widget-layout/v1/${templateId}`, {
+ method: 'GET',
+ headers: getRequestHeaders(),
+ });
+ handleErrors(resp);
+ const json = await resp.json();
+ return json;
+};
+
+export async function getBaseDashboardTemplate(): Promise;
+export async function getBaseDashboardTemplate(type: LayoutTypes): Promise;
+export async function getBaseDashboardTemplate(type?: LayoutTypes): Promise {
+ const resp = await fetch(`/api/chrome-service/v1/dashboard-templates/base-template${type ? `?dashboard=${type}` : ''}`, {
+ method: 'GET',
+ headers: getRequestHeaders(),
+ });
+ handleErrors(resp);
+ const json = await resp.json();
+ return json.data;
+}
+
+// Returns multiple templates for a user (user can have multiple template copies) - we will render the one marked default: true by default
+export async function getDashboardTemplates(): Promise;
+export async function getDashboardTemplates(type: LayoutTypes): Promise;
+export async function getDashboardTemplates(type?: LayoutTypes): Promise {
+ const resp = await fetch(`/api/chrome-service/v1/dashboard-templates${type ? `?dashboard=${type}` : ''}`, {
+ // const resp = await fetch(`/api/widget-layout/v1${type ? `?dashboardType=${type}` : ''}`, {
+ method: 'GET',
+ headers: getRequestHeaders(),
+ });
+ handleErrors(resp);
+ const json = await resp.json();
+ return json.data;
+}
+
+// Returns user's dashboards
+export async function getUsersDashboards(): Promise {
+ const resp = await fetch(`/api/widget-layout/v1/`, {
+ method: 'GET',
+ headers: getRequestHeaders(),
+ });
+ handleErrors(resp);
+ const json = await resp.json();
+ return json.data;
+}
+
+export async function getWidgetMapping(): Promise {
+ const resp = await fetch(`/api/chrome-service/v1/dashboard-templates/widget-mapping`, {
+ // const resp = await fetch(`/api/widget-layout/v1/widget-mapping`, {
+ method: 'GET',
+ headers: getRequestHeaders(),
+ });
+ handleErrors(resp);
+ const json = await resp.json();
+ return json.data;
+}
+
+export const resetDashboardTemplate = async (templateId: number): Promise => {
+ const resp = await fetch(`/api/chrome-service/v1/dashboard-templates/${templateId}/reset`, {
+ // const resp = await fetch(`/api/widget-layout/v1/${templateId}/reset`, {
+ method: 'POST',
+ headers: getRequestHeaders(),
+ });
+ handleErrors(resp);
+ const json = await resp.json();
+ return json.data;
+};
+
+export const patchDashboardTemplate = async (
+ templateId: DashboardTemplate['id'],
+ data: { templateConfig: PartialTemplateConfig }
+): Promise => {
+ const resp = await fetch(`/api/chrome-service/v1/dashboard-templates/${templateId}`, {
+ // const resp = await fetch(`/api/widget-layout/v1/${templateId}`, {
+ method: 'PATCH',
+ headers: getRequestHeaders(),
+ body: JSON.stringify(data),
+ });
+ handleErrors(resp);
+ const json = await resp.json();
+ return json.data;
+};
+
+export const patchDashboardTemplateHub = async (
+ templateId: DashboardTemplate['id'],
+ data: { templateConfig: PartialTemplateConfig }
+): Promise => {
+ const resp = await fetch(`/api/widget-layout/v1/${templateId}`, {
+ method: 'PATCH',
+ headers: getRequestHeaders(),
+ body: JSON.stringify(data),
+ });
+ handleErrors(resp);
+ const json = await resp.json();
+ return json.data;
+};
+
+export const deleteDashboardTemplate = async (templateId: DashboardTemplate['id']): Promise => {
+ const resp = await fetch(`/api/chrome-service/v1/dashboard-templates/${templateId}`, {
+ method: 'DELETE',
+ headers: getRequestHeaders(),
+ });
+ handleErrors(resp);
+ return resp.status === 204;
+};
+
+export const deleteDashboardTemplateFromHub = async (templateId: DashboardTemplate['id']): Promise => {
+ const resp = await fetch(`/api/widget-layout/v1/${templateId}`, {
+ method: 'DELETE',
+ headers: getRequestHeaders(),
+ });
+ handleErrors(resp);
+ return resp.status === 204;
+};
+
+export const importDashboardTemplate = async (data: {
+ dashboardName: string;
+ templateBase: {
+ name: string;
+ displayName: string;
+ };
+ templateConfig: TemplateConfig;
+}): Promise => {
+ const resp = await fetch(`/api/widget-layout/v1/import`, {
+ method: 'POST',
+ headers: getRequestHeaders(),
+ body: JSON.stringify(data),
+ });
+ handleErrors(resp);
+ const json = await resp.json();
+ return json;
+};
+
+export const exportDashboardTemplate = async (templateId: number): Promise => {
+ const resp = await fetch(`/api/widget-layout/v1/${templateId}/export`, {
+ method: 'GET',
+ headers: getRequestHeaders(),
+ });
+ handleErrors(resp);
+ const json = await resp.json();
+ return json;
+};
+
+export const copyDashboardTemplate = async (templateId: DashboardTemplate['id'], data: { dashboardName: string }): Promise => {
+ const resp = await fetch(`/api/widget-layout/v1/${templateId}/copy`, {
+ method: 'POST',
+ headers: getRequestHeaders(),
+ body: JSON.stringify(data),
+ });
+ handleErrors(resp);
+ const json = await resp.json();
+ return json;
+};
+
+export const renameDashboardTemplate = async (templateId: DashboardTemplate['id'], dashboardName: string): Promise => {
+ const resp = await fetch(`/api/widget-layout/v1/${templateId}/rename`, {
+ method: 'PATCH',
+ headers: getRequestHeaders(),
+ body: JSON.stringify({ dashboardName }),
+ });
+ handleErrors(resp);
+ const json = await resp.json();
+ return json;
+};
+
+export const getDefaultTemplate = (templates: DashboardTemplate[]): DashboardTemplate | undefined => {
+ return templates.find((itm) => itm.default === true);
+};
+
+export const setDefaultTemplate = async (templateId: DashboardTemplate['id']): Promise => {
+ const resp = await fetch(`/api/widget-layout/v1/${templateId}/default`, {
+ method: 'POST',
+ headers: getRequestHeaders(),
+ });
+ handleErrors(resp);
+ const json = await resp.json();
+ return json;
+};
+
+export const mapWidgetDefaults = (id: string): [string, string] => {
+ const [widgetType, i] = id.split(widgetIdSeparator);
+ return [widgetType, i];
+};
+
+// Returns template enhanced with widgetTypes
+export const mapTemplateConfigToExtendedTemplateConfig = (templateConfig: TemplateConfig): ExtendedTemplateConfig => {
+ const result: ExtendedTemplateConfig = { sm: [], md: [], lg: [], xl: [] };
+ (Object.keys(templateConfig) as Variants[]).forEach((key) => {
+ result[key] = templateConfig[key].map(
+ (layoutWithTitle: LayoutWithTitle): ExtendedLayoutItem => ({
+ ...layoutWithTitle,
+ widgetType: mapWidgetDefaults(layoutWithTitle.i)[0],
+ })
+ );
+ });
+ return result;
+};
+
+export const extendLayout = (extendedTemplateConfig: ExtendedTemplateConfig): ExtendedTemplateConfig => {
+ const result: ExtendedTemplateConfig = { sm: [], md: [], lg: [], xl: [] };
+ (Object.keys(extendedTemplateConfig) as Variants[]).forEach((key) => {
+ result[key] = extendedTemplateConfig[key]
+ .filter(({ i }) => i !== dropping_elem_id)
+ .map((item) => ({
+ ...item,
+ widgetType: mapWidgetDefaults(item.i)[0],
+ }));
+ });
+ return result;
+};
diff --git a/src/api/dashboard-templates.ts b/src/api/dashboard-templates.ts
index f2d66351..0b8860c8 100644
--- a/src/api/dashboard-templates.ts
+++ b/src/api/dashboard-templates.ts
@@ -1,326 +1,28 @@
-import { LayoutItem } from 'react-grid-layout';
-import { ScalprumComponentProps } from '@scalprum/react-core';
-import { dropping_elem_id } from '../consts';
-import { VisibilityFunctions } from '@redhat-cloud-services/types';
-
-const getRequestHeaders = () => ({
- Accept: 'application/json',
- 'Content-Type': 'application/json',
-});
-
-export const widgetIdSeparator = '#';
-
-export type LayoutTypes = 'landingPage' | 'landing-landingPage';
-
-export type Variants = 'sm' | 'md' | 'lg' | 'xl';
-
-export type LayoutWithTitle = LayoutItem & { title: string };
-
-export type TemplateConfig = {
- [k in Variants]: LayoutWithTitle[];
-};
-
-export type PartialTemplateConfig = Partial;
-
-// extended type the UI tracks but not the backend
-export type ExtendedLayoutItem = LayoutWithTitle & {
- widgetType: string;
- config?: WidgetConfiguration;
- locked?: boolean;
-};
-
-// extended type the UI tracks but not the backend
-export type ExtendedTemplateConfig = {
- [k in Variants]: ExtendedLayoutItem[];
-};
-
-// extended type the UI tracks but not the backend
-export type PartialExtendedTemplateConfig = Partial;
-
-export type BaseTemplate = {
- name: string;
- displayName: string;
- templateConfig: TemplateConfig;
-};
-
-export type DashboardTemplate = {
- id: number;
- createdAt: string;
- updatedAt: string;
- deletedAt: string | null;
- userIdentityID: number;
- default: boolean;
- templateBase: {
- name: string;
- displayName: string;
- };
- templateConfig: TemplateConfig;
- dashboardName: string;
-};
-
-export type ExportDashboardTemplate = {
- templateBase: {
- name: string;
- displayName: string;
- };
- templateConfig: TemplateConfig;
-};
-
-// TODO use dynamic-plugin-sdk CustomError as base class instead
-export class DashboardTemplatesError extends Error {
- constructor(message: string, readonly status: number, readonly response: Response) {
- super(message);
-
- Object.defineProperty(this, 'name', {
- value: new.target.name,
- configurable: true,
- });
-
- if (typeof Error.captureStackTrace === 'function') {
- Error.captureStackTrace(this, this.constructor);
- } else {
- this.stack = new Error(message).stack;
- }
- }
-}
-
-export type WidgetDefaults = {
- w: number;
- h: number;
- maxH: number;
- minH: number;
- minW?: number;
-};
-
-export type WidgetHeaderLink = {
- title?: string;
- href?: string;
-};
-
-type VisibilityFunctionKeys = keyof VisibilityFunctions;
-
-export type WidgetPermission = {
- method: VisibilityFunctionKeys;
- args?: Parameters;
-};
-
-export type WidgetConfiguration = {
- icon?: string;
- headerLink?: WidgetHeaderLink;
- title?: string;
- permissions?: WidgetPermission[];
-};
-
-export type WidgetMapping = {
- [key: string]: Pick & {
- defaults: WidgetDefaults;
- config?: WidgetConfiguration;
- };
-};
-
-const handleErrors = (resp: Response) => {
- if (!resp.ok) {
- throw new DashboardTemplatesError('chrome-service dashboard-templates API fetch error', resp.status, resp);
- }
-};
-
-export const getWidgetIdentifier = (widgetType: string, uniqueId: string = crypto.randomUUID()) => {
- return `${widgetType}${widgetIdSeparator}${uniqueId}`;
-};
-
-export const getDashboardTemplate = async (templateId: number): Promise => {
- const resp = await fetch(`/api/widget-layout/v1/${templateId}`, {
- method: 'GET',
- headers: getRequestHeaders(),
- });
- handleErrors(resp);
- const json = await resp.json();
- return json;
-};
-
-export async function getBaseDashboardTemplate(): Promise;
-export async function getBaseDashboardTemplate(type: LayoutTypes): Promise;
-export async function getBaseDashboardTemplate(type?: LayoutTypes): Promise {
- const resp = await fetch(`/api/chrome-service/v1/dashboard-templates/base-template${type ? `?dashboard=${type}` : ''}`, {
- method: 'GET',
- headers: getRequestHeaders(),
- });
- handleErrors(resp);
- const json = await resp.json();
- return json.data;
-}
-
-// Returns multiple templates for a user (user can have multiple template copies) - we will render the one marked default: true by default
-export async function getDashboardTemplates(): Promise;
-export async function getDashboardTemplates(type: LayoutTypes): Promise;
-export async function getDashboardTemplates(type?: LayoutTypes): Promise {
- const resp = await fetch(`/api/chrome-service/v1/dashboard-templates${type ? `?dashboard=${type}` : ''}`, {
- method: 'GET',
- headers: getRequestHeaders(),
- });
- handleErrors(resp);
- const json = await resp.json();
- return json.data;
-}
-
-// Returns user's dashboards
-export async function getUsersDashboards(): Promise {
- const resp = await fetch(`/api/widget-layout/v1/`, {
- method: 'GET',
- headers: getRequestHeaders(),
- });
- handleErrors(resp);
- const json = await resp.json();
- return json.data;
-}
-
-export async function getWidgetMapping(): Promise {
- const resp = await fetch(`/api/chrome-service/v1/dashboard-templates/widget-mapping`, {
- method: 'GET',
- headers: getRequestHeaders(),
- });
- handleErrors(resp);
- const json = await resp.json();
- return json.data;
-}
-
-export const resetDashboardTemplate = async (templateId: number): Promise => {
- const resp = await fetch(`/api/chrome-service/v1/dashboard-templates/${templateId}/reset`, {
- method: 'POST',
- headers: getRequestHeaders(),
- });
- handleErrors(resp);
- const json = await resp.json();
- return json.data;
-};
-
-export const patchDashboardTemplate = async (
- templateId: DashboardTemplate['id'],
- data: { templateConfig: PartialTemplateConfig }
-): Promise => {
- const resp = await fetch(`/api/chrome-service/v1/dashboard-templates/${templateId}`, {
- method: 'PATCH',
- headers: getRequestHeaders(),
- body: JSON.stringify(data),
- });
- handleErrors(resp);
- const json = await resp.json();
- return json.data;
-};
-
-export const patchDashboardTemplateHub = async (
- templateId: DashboardTemplate['id'],
- data: { templateConfig: PartialTemplateConfig }
-): Promise => {
- const resp = await fetch(`/api/widget-layout/v1/${templateId}`, {
- method: 'PATCH',
- headers: getRequestHeaders(),
- body: JSON.stringify(data),
- });
- handleErrors(resp);
- const json = await resp.json();
- return json.data;
-};
-
-export const deleteDashboardTemplate = async (templateId: DashboardTemplate['id']): Promise => {
- const resp = await fetch(`/api/chrome-service/v1/dashboard-templates/${templateId}`, {
- method: 'DELETE',
- headers: getRequestHeaders(),
- });
- handleErrors(resp);
- return resp.status === 204;
-};
-
-export const deleteDashboardTemplateFromHub = async (templateId: DashboardTemplate['id']): Promise => {
- const resp = await fetch(`/api/widget-layout/v1/${templateId}`, {
- method: 'DELETE',
- headers: getRequestHeaders(),
- });
- handleErrors(resp);
- return resp.status === 204;
-};
-
-export const importDashboardTemplate = async (data: {
- dashboardName: string;
- templateBase: {
- name: string;
- displayName: string;
- };
- templateConfig: TemplateConfig;
-}): Promise => {
- const resp = await fetch(`/api/widget-layout/v1/import`, {
- method: 'POST',
- headers: getRequestHeaders(),
- body: JSON.stringify(data),
- });
- handleErrors(resp);
- const json = await resp.json();
- return json;
-};
-
-export const exportDashboardTemplate = async (templateId: number): Promise => {
- const resp = await fetch(`/api/widget-layout/v1/${templateId}/export`, {
- method: 'GET',
- headers: getRequestHeaders(),
- });
- handleErrors(resp);
- const json = await resp.json();
- return json;
-};
-
-export const copyDashboardTemplate = async (templateId: DashboardTemplate['id'], data: { dashboardName: string }): Promise => {
- const resp = await fetch(`/api/widget-layout/v1/${templateId}/copy`, {
- method: 'POST',
- headers: getRequestHeaders(),
- body: JSON.stringify(data),
- });
- handleErrors(resp);
- const json = await resp.json();
- return json;
-};
-
-export const getDefaultTemplate = (templates: DashboardTemplate[]): DashboardTemplate | undefined => {
- return templates.find((itm) => itm.default === true);
-};
-
-export const setDefaultTemplate = async (templateId: DashboardTemplate['id']): Promise => {
- const resp = await fetch(`/api/widget-layout/v1/${templateId}/default`, {
- method: 'POST',
- headers: getRequestHeaders(),
- });
- handleErrors(resp);
- const json = await resp.json();
- return json;
-};
-
-export const mapWidgetDefaults = (id: string): [string, string] => {
- const [widgetType, i] = id.split(widgetIdSeparator);
- return [widgetType, i];
-};
-
-// Returns template enhanced with widgetTypes
-export const mapTemplateConfigToExtendedTemplateConfig = (templateConfig: TemplateConfig): ExtendedTemplateConfig => {
- const result: ExtendedTemplateConfig = { sm: [], md: [], lg: [], xl: [] };
- (Object.keys(templateConfig) as Variants[]).forEach((key) => {
- result[key] = templateConfig[key].map(
- (layoutWithTitle: LayoutWithTitle): ExtendedLayoutItem => ({
- ...layoutWithTitle,
- widgetType: mapWidgetDefaults(layoutWithTitle.i)[0],
- })
- );
- });
- return result;
-};
-
-export const extendLayout = (extendedTemplateConfig: ExtendedTemplateConfig): ExtendedTemplateConfig => {
- const result: ExtendedTemplateConfig = { sm: [], md: [], lg: [], xl: [] };
- (Object.keys(extendedTemplateConfig) as Variants[]).forEach((key) => {
- result[key] = extendedTemplateConfig[key]
- .filter(({ i }) => i !== dropping_elem_id)
- .map((item) => ({
- ...item,
- widgetType: mapWidgetDefaults(item.i)[0],
- }));
- });
- return result;
-};
+export type {
+ LayoutTypes,
+ Variants,
+ LayoutWithTitle,
+ TemplateConfig,
+ PartialTemplateConfig,
+ ExtendedLayoutItem,
+ ExtendedTemplateConfig,
+ PartialExtendedTemplateConfig,
+ BaseTemplate,
+ DashboardTemplate,
+ ExportDashboardTemplate,
+ WidgetDefaults,
+ WidgetHeaderLink,
+ WidgetPermission,
+ WidgetConfiguration,
+ WidgetMapping,
+} from './dashboard-templates-new';
+
+export {
+ widgetIdSeparator,
+ DashboardTemplatesError,
+ getWidgetIdentifier,
+ getDefaultTemplate,
+ mapWidgetDefaults,
+ mapTemplateConfigToExtendedTemplateConfig,
+ extendLayout,
+} from './dashboard-templates-new';
diff --git a/src/hooks/tests/useCreateBlankDashboard.test.ts b/src/hooks/tests/useCreateBlankDashboard.test.ts
index 3f1a2989..b21a41ae 100644
--- a/src/hooks/tests/useCreateBlankDashboard.test.ts
+++ b/src/hooks/tests/useCreateBlankDashboard.test.ts
@@ -1,9 +1,11 @@
import { act, renderHook } from '@testing-library/react';
import { useCreateBlankDashboard } from '../useCreateBlankDashboard';
-import { DashboardTemplatesError, importDashboardTemplate, setDefaultTemplate } from '../../api/dashboard-templates';
+import { DashboardTemplatesError } from '../../api/dashboard-templates';
+import { importDashboardTemplate, setDefaultTemplate } from '../../api/dashboard-templates-new';
+import { backendFlagAtom, store } from '../../state/store';
-jest.mock('../../api/dashboard-templates', () => ({
- ...jest.requireActual('../../api/dashboard-templates'),
+jest.mock('../../api/dashboard-templates-new', () => ({
+ ...jest.requireActual('../../api/dashboard-templates-new'),
importDashboardTemplate: jest.fn(),
setDefaultTemplate: jest.fn(),
getUsersDashboards: jest.fn().mockResolvedValue([]),
@@ -17,7 +19,7 @@ const mockDashboardTemplate = {
createdAt: '2026-01-01T00:00:00Z',
updatedAt: '2026-01-01T00:00:00Z',
deletedAt: null,
- userIdentityID: 1,
+ userId: '1',
default: false,
templateBase: { name: 'custom', displayName: 'Custom' },
templateConfig: { sm: [], md: [], lg: [], xl: [] },
@@ -27,6 +29,7 @@ const mockDashboardTemplate = {
describe('useCreateBlankDashboard', () => {
beforeEach(() => {
jest.clearAllMocks();
+ store.set(backendFlagAtom, true);
});
it('should return initial state', () => {
@@ -120,7 +123,7 @@ describe('useCreateBlankDashboard', () => {
expect(mockedImportDashboardTemplate).toHaveBeenCalledWith({
dashboardName: 'My Dashboard',
- templateBase: { name: 'custom', displayName: 'Custom' },
+ templateBase: { name: 'landing-landingPage', displayName: 'Landing Landing Page' },
templateConfig: { sm: [], md: [], lg: [], xl: [] },
});
expect(createResult).toEqual(mockDashboardTemplate);
diff --git a/src/hooks/tests/useDashboardConfig.test.ts b/src/hooks/tests/useDashboardConfig.test.ts
index f6ddbb64..4aeb41e1 100644
--- a/src/hooks/tests/useDashboardConfig.test.ts
+++ b/src/hooks/tests/useDashboardConfig.test.ts
@@ -2,14 +2,13 @@ import { act, renderHook } from '@testing-library/react';
import { createElement } from 'react';
import { Provider, createStore } from 'jotai';
import useDashboardConfig from '../useDashboardConfig';
+import { DashboardTemplate, ExtendedTemplateConfig } from '../../api/dashboard-templates';
import {
- DashboardTemplate,
- ExtendedTemplateConfig,
getDashboardTemplates,
getDefaultTemplate,
mapTemplateConfigToExtendedTemplateConfig,
patchDashboardTemplate,
-} from '../../api/dashboard-templates';
+} from '../../api/dashboard-templates-new';
import useCurrentUser from '../useCurrentUser';
import { useAddNotification } from '../../state/notificationsAtom';
import { templateIdAtom } from '../../state/templateAtom';
@@ -20,7 +19,12 @@ jest.mock('awesome-debounce-promise', () => ({
default: (fn: (...args: any[]) => any) => fn,
}));
-jest.mock('../../api/dashboard-templates', () => ({
+jest.mock('@unleash/proxy-client-react', () => ({
+ useFlag: () => true,
+}));
+
+jest.mock('../../api/dashboard-templates-new', () => ({
+ ...jest.requireActual('../../api/dashboard-templates-new'),
getDashboardTemplates: jest.fn(),
getDefaultTemplate: jest.fn(),
mapTemplateConfigToExtendedTemplateConfig: jest.fn(),
@@ -50,7 +54,7 @@ const createMockDashboardTemplate = (overrides: Partial = {})
createdAt: '2024-01-01T00:00:00Z',
updatedAt: '2024-01-01T00:00:00Z',
deletedAt: null,
- userIdentityID: 1,
+ userId: '1',
default: true,
templateBase: { name: 'test', displayName: 'Test' },
templateConfig: { sm: [], md: [], lg: [], xl: [] },
@@ -86,7 +90,7 @@ describe('useDashboardConfig', () => {
it('should return initial state with isLoaded false and empty template', () => {
const { wrapper } = createWrapper();
- const { result } = renderHook(() => useDashboardConfig(), { wrapper });
+ const { result } = renderHook(() => useDashboardConfig('landingPage'), { wrapper });
expect(result.current.isLoaded).toBe(false);
expect(result.current.template).toEqual(emptyTemplate);
@@ -99,7 +103,7 @@ describe('useDashboardConfig', () => {
mockedUseCurrentUser.mockReturnValue({ currentUser: undefined, isLoaded: false });
const { wrapper } = createWrapper();
- renderHook(() => useDashboardConfig(), { wrapper });
+ renderHook(() => useDashboardConfig('landingPage'), { wrapper });
await act(async () => {
/* flush */
@@ -118,7 +122,7 @@ describe('useDashboardConfig', () => {
const { wrapper, store } = createWrapper();
store.set(templateIdAtom, 5);
- renderHook(() => useDashboardConfig(), { wrapper });
+ renderHook(() => useDashboardConfig('landingPage'), { wrapper });
await act(async () => {
/* flush */
@@ -163,7 +167,7 @@ describe('useDashboardConfig', () => {
mockedGetDashboardTemplates.mockRejectedValue(new Error('Network error'));
const { wrapper } = createWrapper();
- const { result } = renderHook(() => useDashboardConfig(), { wrapper });
+ const { result } = renderHook(() => useDashboardConfig('landingPage'), { wrapper });
await act(async () => {
/* flush */
@@ -188,7 +192,7 @@ describe('useDashboardConfig', () => {
mockedGetDefaultTemplate.mockReturnValue(undefined as unknown as ReturnType);
const { wrapper } = createWrapper();
- const { result } = renderHook(() => useDashboardConfig(), { wrapper });
+ const { result } = renderHook(() => useDashboardConfig('landingPage'), { wrapper });
await act(async () => {
/* flush */
@@ -221,7 +225,7 @@ describe('useDashboardConfig', () => {
Object.defineProperty(document.body, 'clientWidth', { value: 1250, configurable: true });
const { wrapper } = createWrapper();
- renderHook(() => useDashboardConfig(), { wrapper });
+ renderHook(() => useDashboardConfig('landingPage'), { wrapper });
await act(async () => {
/* flush */
@@ -237,7 +241,7 @@ describe('useDashboardConfig', () => {
Object.defineProperty(document.body, 'clientWidth', { value: 1100, configurable: true });
const { wrapper } = createWrapper();
- renderHook(() => useDashboardConfig(), { wrapper });
+ renderHook(() => useDashboardConfig('landingPage'), { wrapper });
await act(async () => {
/* flush */
@@ -251,7 +255,7 @@ describe('useDashboardConfig', () => {
Object.defineProperty(document.body, 'clientWidth', { value: 800, configurable: true });
const { wrapper } = createWrapper();
- renderHook(() => useDashboardConfig(), { wrapper });
+ renderHook(() => useDashboardConfig('landingPage'), { wrapper });
await act(async () => {
/* flush */
@@ -265,7 +269,7 @@ describe('useDashboardConfig', () => {
Object.defineProperty(document.body, 'clientWidth', { value: 400, configurable: true });
const { wrapper } = createWrapper();
- renderHook(() => useDashboardConfig(), { wrapper });
+ renderHook(() => useDashboardConfig('landingPage'), { wrapper });
await act(async () => {
/* flush */
@@ -288,7 +292,7 @@ describe('useDashboardConfig', () => {
mockedPatchDashboardTemplate.mockResolvedValue(mockDefault);
const { wrapper } = createWrapper();
- const hookResult = renderHook(() => useDashboardConfig(), { wrapper });
+ const hookResult = renderHook(() => useDashboardConfig('landingPage'), { wrapper });
await act(async () => {
/* flush initial load */
@@ -300,7 +304,7 @@ describe('useDashboardConfig', () => {
it('should skip patch when templateId < 0', async () => {
mockedUseCurrentUser.mockReturnValue({ currentUser: undefined, isLoaded: false });
const { wrapper } = createWrapper();
- const { result } = renderHook(() => useDashboardConfig(), { wrapper });
+ const { result } = renderHook(() => useDashboardConfig('landingPage'), { wrapper });
await act(async () => {
await result.current.saveTemplate(emptyTemplate);
diff --git a/src/hooks/tests/useDashboardTemplate.test.ts b/src/hooks/tests/useDashboardTemplate.test.ts
index fe9df17e..a5a047d8 100644
--- a/src/hooks/tests/useDashboardTemplate.test.ts
+++ b/src/hooks/tests/useDashboardTemplate.test.ts
@@ -1,14 +1,12 @@
import { act, renderHook } from '@testing-library/react';
import useDashboardTemplate from '../useDashboardTemplate';
+import { DashboardTemplate, ExtendedTemplateConfig, WidgetMapping } from '../../api/dashboard-templates';
import {
- DashboardTemplate,
- ExtendedTemplateConfig,
- WidgetMapping,
getDashboardTemplate,
getWidgetMapping,
mapTemplateConfigToExtendedTemplateConfig,
patchDashboardTemplateHub,
-} from '../../api/dashboard-templates';
+} from '../../api/dashboard-templates-new';
jest.mock('awesome-debounce-promise', () => ({
__esModule: true,
@@ -16,12 +14,16 @@ jest.mock('awesome-debounce-promise', () => ({
default: (fn: (...args: any[]) => any) => fn,
}));
-jest.mock('../../api/dashboard-templates', () => ({
+jest.mock('@unleash/proxy-client-react', () => ({
+ useFlag: () => true,
+}));
+
+jest.mock('../../api/dashboard-templates-new', () => ({
+ ...jest.requireActual('../../api/dashboard-templates-new'),
getDashboardTemplate: jest.fn(),
getWidgetMapping: jest.fn(),
mapTemplateConfigToExtendedTemplateConfig: jest.fn(),
patchDashboardTemplateHub: jest.fn(),
- widgetIdSeparator: '#',
}));
const mockedGetDashboardTemplate = getDashboardTemplate as jest.MockedFunction;
@@ -36,7 +38,7 @@ const createMockDashboardTemplate = (overrides: Partial = {})
createdAt: '2024-01-01T00:00:00Z',
updatedAt: '2024-01-01T00:00:00Z',
deletedAt: null,
- userIdentityID: 1,
+ userId: '1',
default: true,
templateBase: { name: 'test', displayName: 'Test' },
templateConfig: { sm: [], md: [], lg: [], xl: [] },
diff --git a/src/hooks/tests/useDuplicateDashboard.test.ts b/src/hooks/tests/useDuplicateDashboard.test.ts
index 33b6cdcf..87a801ba 100644
--- a/src/hooks/tests/useDuplicateDashboard.test.ts
+++ b/src/hooks/tests/useDuplicateDashboard.test.ts
@@ -1,9 +1,11 @@
import { act, renderHook } from '@testing-library/react';
import { useDuplicateDashboard } from '../useDuplicateDashboard';
-import { DashboardTemplate, DashboardTemplatesError, copyDashboardTemplate, setDefaultTemplate } from '../../api/dashboard-templates';
+import { DashboardTemplate, DashboardTemplatesError } from '../../api/dashboard-templates';
+import { copyDashboardTemplate, setDefaultTemplate } from '../../api/dashboard-templates-new';
+import { backendFlagAtom, store } from '../../state/store';
-jest.mock('../../api/dashboard-templates', () => ({
- ...jest.requireActual('../../api/dashboard-templates'),
+jest.mock('../../api/dashboard-templates-new', () => ({
+ ...jest.requireActual('../../api/dashboard-templates-new'),
copyDashboardTemplate: jest.fn(),
setDefaultTemplate: jest.fn(),
getUsersDashboards: jest.fn().mockResolvedValue([]),
@@ -17,7 +19,7 @@ const mockDashboard: DashboardTemplate = {
createdAt: '2024-01-01',
updatedAt: '2024-01-01',
deletedAt: null,
- userIdentityID: 1,
+ userId: '1',
default: false,
templateBase: { name: 'test', displayName: 'Test' },
templateConfig: { sm: [], md: [], lg: [], xl: [] },
@@ -27,6 +29,7 @@ const mockDashboard: DashboardTemplate = {
describe('useDuplicateDashboard', () => {
beforeEach(() => {
jest.clearAllMocks();
+ store.set(backendFlagAtom, true);
});
it('should return initial state with isLoading false, error null, and isFormValid false', () => {
diff --git a/src/hooks/tests/useExportDashboard.test.ts b/src/hooks/tests/useExportDashboard.test.ts
index fa5c59cf..4353e349 100644
--- a/src/hooks/tests/useExportDashboard.test.ts
+++ b/src/hooks/tests/useExportDashboard.test.ts
@@ -1,8 +1,14 @@
import { act, renderHook } from '@testing-library/react';
import { useExportDashboard } from '../useExportDashboard';
-import { ExportDashboardTemplate, exportDashboardTemplate } from '../../api/dashboard-templates';
+import { ExportDashboardTemplate } from '../../api/dashboard-templates';
+import { exportDashboardTemplate } from '../../api/dashboard-templates-new';
-jest.mock('../../api/dashboard-templates', () => ({
+jest.mock('@unleash/proxy-client-react', () => ({
+ useFlag: () => true,
+}));
+
+jest.mock('../../api/dashboard-templates-new', () => ({
+ ...jest.requireActual('../../api/dashboard-templates-new'),
exportDashboardTemplate: jest.fn(),
}));
diff --git a/src/hooks/tests/useGetDashboards.test.ts b/src/hooks/tests/useGetDashboards.test.ts
index d983e06f..05d6b73f 100644
--- a/src/hooks/tests/useGetDashboards.test.ts
+++ b/src/hooks/tests/useGetDashboards.test.ts
@@ -1,8 +1,14 @@
import { act, renderHook } from '@testing-library/react';
import useGetDashboards from '../useGetDashboards';
-import { DashboardTemplate, getUsersDashboards } from '../../api/dashboard-templates';
+import { DashboardTemplate } from '../../api/dashboard-templates';
+import { getUsersDashboards } from '../../api/dashboard-templates-new';
-jest.mock('../../api/dashboard-templates', () => ({
+jest.mock('@unleash/proxy-client-react', () => ({
+ useFlag: () => true,
+}));
+
+jest.mock('../../api/dashboard-templates-new', () => ({
+ ...jest.requireActual('../../api/dashboard-templates-new'),
getUsersDashboards: jest.fn(),
}));
@@ -34,7 +40,7 @@ const mockDashboards: DashboardTemplate[] = [
createdAt: '2024-01-01',
updatedAt: '2024-01-01',
deletedAt: null,
- userIdentityID: 1,
+ userId: '1',
default: true,
templateBase: {
name: 'dashboard-1',
@@ -46,7 +52,7 @@ const mockDashboards: DashboardTemplate[] = [
createdAt: '2024-01-02',
updatedAt: '2024-01-02',
deletedAt: null,
- userIdentityID: 1,
+ userId: '1',
default: false,
templateBase: {
name: 'dashboard-2',
diff --git a/src/hooks/tests/useImportDashboard.test.ts b/src/hooks/tests/useImportDashboard.test.ts
index b280f3be..ce4c2228 100644
--- a/src/hooks/tests/useImportDashboard.test.ts
+++ b/src/hooks/tests/useImportDashboard.test.ts
@@ -1,8 +1,11 @@
import { act, renderHook } from '@testing-library/react';
import { useImportDashboard } from '../useImportDashboard';
-import { DashboardTemplate, importDashboardTemplate } from '../../api/dashboard-templates';
+import { DashboardTemplate } from '../../api/dashboard-templates';
+import { importDashboardTemplate } from '../../api/dashboard-templates-new';
+import { backendFlagAtom, store } from '../../state/store';
-jest.mock('../../api/dashboard-templates', () => ({
+jest.mock('../../api/dashboard-templates-new', () => ({
+ ...jest.requireActual('../../api/dashboard-templates-new'),
importDashboardTemplate: jest.fn(),
getUsersDashboards: jest.fn().mockResolvedValue([]),
}));
@@ -14,7 +17,7 @@ const mockDashboardTemplate: DashboardTemplate = {
createdAt: '2026-01-01T00:00:00Z',
updatedAt: '2026-01-01T00:00:00Z',
deletedAt: null,
- userIdentityID: 42,
+ userId: '42',
default: false,
templateBase: { name: 'test-base', displayName: 'Test Base' },
templateConfig: {} as DashboardTemplate['templateConfig'],
@@ -24,6 +27,7 @@ const mockDashboardTemplate: DashboardTemplate = {
describe('useImportDashboard', () => {
beforeEach(() => {
jest.clearAllMocks();
+ store.set(backendFlagAtom, true);
});
it('should return correct initial state', () => {
diff --git a/src/hooks/useApi.ts b/src/hooks/useApi.ts
new file mode 100644
index 00000000..b92f0c06
--- /dev/null
+++ b/src/hooks/useApi.ts
@@ -0,0 +1,19 @@
+import { useFlag } from '@unleash/proxy-client-react';
+import { useMemo } from 'react';
+import * as oldApi from '../api/dashboard-templates-old';
+import * as newApi from '../api/dashboard-templates-new';
+
+const mapLayoutType = (type?: string) => (type === 'landing-landingPage' ? 'landingPage' : type);
+
+export const useApi = () => {
+ const isNewBackend = useFlag('platform.widget-layout.new-backend');
+ return useMemo(() => {
+ if (!isNewBackend)
+ return {
+ ...oldApi,
+ getBaseDashboardTemplate: (type?: any) => oldApi.getBaseDashboardTemplate(mapLayoutType(type) as any),
+ getDashboardTemplates: (type?: any) => oldApi.getDashboardTemplates(mapLayoutType(type) as any),
+ } as unknown as typeof newApi;
+ return newApi;
+ }, [isNewBackend]);
+};
diff --git a/src/hooks/useCreateBlankDashboard.ts b/src/hooks/useCreateBlankDashboard.ts
index 7403ccfe..172bda0c 100644
--- a/src/hooks/useCreateBlankDashboard.ts
+++ b/src/hooks/useCreateBlankDashboard.ts
@@ -32,7 +32,7 @@ type UseCreateBlankDashboardReturn = CreateBlankDashboardState & {
reset: () => void;
};
-export const useCreateBlankDashboard = (): UseCreateBlankDashboardReturn => {
+export const useCreateBlankDashboard = (layoutType = 'landing-landingPage'): UseCreateBlankDashboardReturn => {
const [state, setState] = useState(initState);
const create = useSetAtom(createDashboardAtom);
@@ -51,12 +51,16 @@ export const useCreateBlankDashboard = (): UseCreateBlankDashboardReturn => {
setState((prev) => ({ ...prev, isLoading: true, error: null }));
+ // layoutType: 'landing-landingPage' → displayName: 'Landing Landing Page'
try {
const result = await create({
dashboardName: state.name,
templateBase: {
- name: 'custom',
- displayName: 'Custom',
+ name: layoutType,
+ displayName: layoutType
+ .split(/[-]|(?=[A-Z])/)
+ .map((w) => w[0].toUpperCase() + w.slice(1))
+ .join(' '),
},
templateConfig: blankTemplateConfig,
setAsHomepage: state.setAsHomepage,
diff --git a/src/hooks/useDashboardConfig.ts b/src/hooks/useDashboardConfig.ts
index e42667f0..f4e2721f 100644
--- a/src/hooks/useDashboardConfig.ts
+++ b/src/hooks/useDashboardConfig.ts
@@ -1,4 +1,4 @@
-import { useCallback, useEffect, useRef, useState } from 'react';
+import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useAtom } from 'jotai';
import DebouncePromise from 'awesome-debounce-promise';
import { templateAtom, templateIdAtom } from '../state/templateAtom';
@@ -8,21 +8,19 @@ import {
LayoutTypes,
PartialTemplateConfig,
Variants,
- getDashboardTemplates,
getDefaultTemplate,
mapTemplateConfigToExtendedTemplateConfig,
- patchDashboardTemplate,
} from '../api/dashboard-templates';
import useCurrentUser from './useCurrentUser';
import { useAddNotification } from '../state/notificationsAtom';
+import { useApi } from './useApi';
+import { useFlag } from '@unleash/proxy-client-react';
const sidebarBreakpoints = { xl: 1250, lg: 1100, md: 800, sm: 500 };
-const debouncedPatchDashboardTemplate = DebouncePromise(patchDashboardTemplate, 1500, {
- onlyResolvesLast: true,
-});
-
-const useDashboardConfig = (layoutType: LayoutTypes = 'landingPage') => {
+const useDashboardConfig = (layoutType: LayoutTypes = 'landing-landingPage') => {
+ const isNewBackend = useFlag('platform.widget-layout.new-backend');
+ const mappedLayoutType = !isNewBackend && layoutType === 'landing-landingPage' ? 'landingPage' : layoutType;
const [isLoaded, setIsLoaded] = useState(false);
const [template, setTemplate] = useAtom(templateAtom);
const [templateId, setTemplateId] = useAtom(templateIdAtom);
@@ -30,13 +28,16 @@ const useDashboardConfig = (layoutType: LayoutTypes = 'landingPage') => {
const { currentUser } = useCurrentUser();
const addNotification = useAddNotification();
const layoutRef = useRef(null);
+ const api = useApi();
+ const debouncedPatchDashboardTemplate = useMemo(() => DebouncePromise(api.patchDashboardTemplate, 1500, { onlyResolvesLast: true }), [api]);
useEffect(() => {
if (!currentUser || templateId >= 0) {
return;
}
- getDashboardTemplates(layoutType)
+ api
+ .getDashboardTemplates(mappedLayoutType)
.then((templates) => {
const customDefaultTemplate = getDefaultTemplate(templates);
if (!customDefaultTemplate) {
@@ -94,7 +95,7 @@ const useDashboardConfig = (layoutType: LayoutTypes = 'landingPage') => {
}));
});
- await debouncedPatchDashboardTemplate(templateId, { templateConfig } as Parameters[1]);
+ await debouncedPatchDashboardTemplate(templateId, { templateConfig });
} catch (error) {
console.error(error);
addNotification({
diff --git a/src/hooks/useDashboardTemplate.ts b/src/hooks/useDashboardTemplate.ts
index 53b04c3d..b1437ad5 100644
--- a/src/hooks/useDashboardTemplate.ts
+++ b/src/hooks/useDashboardTemplate.ts
@@ -1,4 +1,4 @@
-import { useCallback, useEffect, useState } from 'react';
+import { useCallback, useEffect, useMemo, useState } from 'react';
import DebouncePromise from 'awesome-debounce-promise';
import {
DashboardTemplate,
@@ -6,16 +6,10 @@ import {
PartialTemplateConfig,
Variants,
WidgetMapping,
- getDashboardTemplate,
- getWidgetMapping,
mapTemplateConfigToExtendedTemplateConfig,
- patchDashboardTemplateHub,
widgetIdSeparator,
} from '../api/dashboard-templates';
-
-const debouncedPatchDashboardTemplate = DebouncePromise(patchDashboardTemplateHub, 1500, {
- onlyResolvesLast: true,
-});
+import { useApi } from './useApi';
const remapWidgetTypes = (extendedTemplate: ExtendedTemplateConfig, widgetMapping: WidgetMapping): ExtendedTemplateConfig => {
// Build reverse lookup: "landing-./RhelWidget" -> "rhel"
@@ -48,7 +42,8 @@ const useDashboardTemplate = (id: number) => {
const [isLoaded, setIsLoaded] = useState(false);
const [error, setError] = useState(null);
const [dashboard, setDashboard] = useState();
- // widget mapping
+ const api = useApi();
+ const debouncedPatchDashboardTemplate = useMemo(() => DebouncePromise(api.patchDashboardTemplateHub, 1500, { onlyResolvesLast: true }), [api]);
useEffect(() => {
const fetchTemplate = async () => {
@@ -56,10 +51,10 @@ const useDashboardTemplate = (id: number) => {
setError(null);
try {
- const result = await getDashboardTemplate(id);
+ const result = await api.getDashboardTemplate(id);
setDashboard(result);
const extendedTemplateConfig = mapTemplateConfigToExtendedTemplateConfig(result.templateConfig);
- const widgetMap = await getWidgetMapping();
+ const widgetMap = await api.getWidgetMapping();
const remappedTemplate = remapWidgetTypes(extendedTemplateConfig, widgetMap);
setTemplate(remappedTemplate);
} catch (err) {
@@ -91,7 +86,7 @@ const useDashboardTemplate = (id: number) => {
}));
});
- await debouncedPatchDashboardTemplate(id, { templateConfig } as Parameters[1]);
+ await debouncedPatchDashboardTemplate(id, { templateConfig });
} catch (err) {
console.error(err);
}
diff --git a/src/hooks/useExportDashboard.ts b/src/hooks/useExportDashboard.ts
index 63c78e7f..5d78cfbf 100644
--- a/src/hooks/useExportDashboard.ts
+++ b/src/hooks/useExportDashboard.ts
@@ -1,16 +1,18 @@
import { useState } from 'react';
-import { ExportDashboardTemplate, exportDashboardTemplate } from '../api/dashboard-templates';
+import { ExportDashboardTemplate } from '../api/dashboard-templates';
+import { useApi } from './useApi';
export const useExportDashboard = () => {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
+ const api = useApi();
const exportDashboard = async (id: number): Promise => {
setIsLoading(true);
setError(null);
try {
- const result = await exportDashboardTemplate(id);
+ const result = await api.exportDashboardTemplate(id);
setIsLoading(false);
return result;
} catch (err) {
diff --git a/src/hooks/useGetDashboards.ts b/src/hooks/useGetDashboards.ts
index de377c65..875b1550 100644
--- a/src/hooks/useGetDashboards.ts
+++ b/src/hooks/useGetDashboards.ts
@@ -1,16 +1,17 @@
import { useEffect } from 'react';
-import { getUsersDashboards } from '../api/dashboard-templates';
import { useAtom } from 'jotai';
+import { useApi } from './useApi';
import { dashboardsAtom } from '../state/dashboardsAtom';
import useCurrentUser from './useCurrentUser';
const useGetDashboards = () => {
const { currentUser } = useCurrentUser();
const [dashboards, setDashboards] = useAtom(dashboardsAtom);
+ const api = useApi();
const fetchDashboards = async () => {
try {
- const userDashboards = await getUsersDashboards();
+ const userDashboards = await api.getUsersDashboards();
setDashboards(userDashboards);
} catch (error) {
console.error('Error fetching user dashboards:', error);
diff --git a/src/state/dashboardsAtom.ts b/src/state/dashboardsAtom.ts
index c94c0d97..d9ad4e86 100644
--- a/src/state/dashboardsAtom.ts
+++ b/src/state/dashboardsAtom.ts
@@ -1,25 +1,20 @@
import { atom } from 'jotai';
-import {
- DashboardTemplate,
- TemplateConfig,
- copyDashboardTemplate,
- deleteDashboardTemplateFromHub,
- getUsersDashboards,
- importDashboardTemplate,
- setDefaultTemplate,
-} from '../api/dashboard-templates';
+import { DashboardTemplate, TemplateConfig } from '../api/dashboard-templates';
+import { getApi } from './store';
export const dashboardsAtom = atom([]);
export const deleteDashboardAtom = atom(null, async (_get, set, id: DashboardTemplate['id']) => {
- await deleteDashboardTemplateFromHub(id);
- const dashboards = await getUsersDashboards();
+ const api = getApi();
+ await api.deleteDashboardTemplateFromHub(id);
+ const dashboards = await api.getUsersDashboards();
set(dashboardsAtom, dashboards);
});
export const setDefaultDashboardAtom = atom(null, async (_get, set, id: DashboardTemplate['id']) => {
- await setDefaultTemplate(id);
- const dashboards = await getUsersDashboards();
+ const api = getApi();
+ await api.setDefaultTemplate(id);
+ const dashboards = await api.getUsersDashboards();
set(dashboardsAtom, dashboards);
});
@@ -30,12 +25,13 @@ export const createDashboardAtom = atom(
set,
data: { dashboardName: string; templateBase: { name: string; displayName: string }; templateConfig: TemplateConfig; setAsHomepage?: boolean }
) => {
+ const api = getApi();
const { setAsHomepage, ...importData } = data;
- const result = await importDashboardTemplate(importData);
+ const result = await api.importDashboardTemplate(importData);
if (setAsHomepage) {
- await setDefaultTemplate(result.id);
+ await api.setDefaultTemplate(result.id);
}
- const dashboards = await getUsersDashboards();
+ const dashboards = await api.getUsersDashboards();
set(dashboardsAtom, dashboards);
return result;
}
@@ -44,8 +40,9 @@ export const createDashboardAtom = atom(
export const importDashboardAtom = atom(
null,
async (_get, set, data: { dashboardName: string; templateBase: { name: string; displayName: string }; templateConfig: TemplateConfig }) => {
- const result = await importDashboardTemplate(data);
- const dashboards = await getUsersDashboards();
+ const api = getApi();
+ const result = await api.importDashboardTemplate(data);
+ const dashboards = await api.getUsersDashboards();
set(dashboardsAtom, dashboards);
return result;
}
@@ -54,11 +51,12 @@ export const importDashboardAtom = atom(
export const duplicateDashboardAtom = atom(
null,
async (_get, set, data: { id: DashboardTemplate['id']; dashboardName: string; setAsHomepage?: boolean }) => {
- const result = await copyDashboardTemplate(data.id, { dashboardName: data.dashboardName });
+ const api = getApi();
+ const result = await api.copyDashboardTemplate(data.id, { dashboardName: data.dashboardName });
if (data.setAsHomepage) {
- await setDefaultTemplate(result.id);
+ await api.setDefaultTemplate(result.id);
}
- const dashboards = await getUsersDashboards();
+ const dashboards = await api.getUsersDashboards();
set(dashboardsAtom, dashboards);
return result;
}
diff --git a/src/state/store.ts b/src/state/store.ts
new file mode 100644
index 00000000..89e00519
--- /dev/null
+++ b/src/state/store.ts
@@ -0,0 +1,12 @@
+import { atom, createStore } from 'jotai';
+import * as oldApi from '../api/dashboard-templates-old';
+import * as newApi from '../api/dashboard-templates-new';
+
+export const store = createStore();
+
+export const backendFlagAtom = atom(false);
+
+export const getApi = (): typeof newApi => {
+ const isNew = store.get(backendFlagAtom);
+ return (isNew ? newApi : oldApi) as typeof newApi;
+};
diff --git a/src/state/widgetMappingAtom.ts b/src/state/widgetMappingAtom.ts
index 19d6a6ca..015173b1 100644
--- a/src/state/widgetMappingAtom.ts
+++ b/src/state/widgetMappingAtom.ts
@@ -1,7 +1,7 @@
import { atom } from 'jotai';
import type { VisibilityFunctions } from '@redhat-cloud-services/types';
import type { WidgetMapping, WidgetPermission } from '../api/dashboard-templates';
-import { getWidgetMapping } from '../api/dashboard-templates';
+import { getApi } from './store';
const checkPermissions = async (visibilityFunctions: VisibilityFunctions, permissions: WidgetPermission[]): Promise => {
const results = await Promise.all(
@@ -24,7 +24,8 @@ const checkPermissions = async (visibilityFunctions: VisibilityFunctions, permis
export const widgetMappingAtom = atom({});
export const resolvedWidgetMappingAtom = atom(null, async (_get, set, visibilityFunctions: VisibilityFunctions) => {
- const mapping = await getWidgetMapping();
+ const api = getApi();
+ const mapping = await api.getWidgetMapping();
if (!mapping) {
return;