Skip to content

Commit e8d8c86

Browse files
authored
fix: prevent widget drawer from auto-opening, fix dashboard rename updating UI (#346)
* fix: prevent widget drawer from auto-opening during dashboard loading, add tests * fix: dashboard rename updating UI and e2e test stability * fix: move drawer auto-open logic up to GridLayout * fix: close the drawer after leaving the dashboard page" * test: fix drawer tests
1 parent c4d5a92 commit e8d8c86

7 files changed

Lines changed: 105 additions & 29 deletions

File tree

playwright/editing-dashboard.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const navigateToDashboardHub = async (page: Page) => {
1111

1212
const navigateToGenericDashboard = async (page: Page, dashboardName: string) => {
1313
await navigateToDashboardHub(page);
14-
await page.getByRole('link', { name: dashboardName }).click();
14+
await page.getByRole('link', { name: dashboardName, exact: true }).click();
1515
await page.getByRole('button', { name: 'Add widgets' }).waitFor({ state: 'visible', timeout: 30000 });
1616
};
1717

@@ -123,6 +123,7 @@ test.describe('Set Dashboard as Homepage from Generic Page', () => {
123123
await page.getByText(`'${nonDefaultName}' has been set as homepage`).waitFor({ state: 'visible', timeout: 10000 });
124124

125125
await navigateToDashboardHub(page);
126+
await page.getByRole('link', { name: nonDefaultName, exact: true }).waitFor({ state: 'visible', timeout: 10000 });
126127

127128
expect(await hasHomeIcon(page, nonDefaultName)).toBe(true);
128129
expect(await hasHomeIcon(page, defaultName)).toBe(false);

playwright/widget-layout.spec.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,4 +124,50 @@ test.describe('Widget Layout - Add Widget from Drawer', () => {
124124
const resetButton = page.getByRole('button', { name: 'Reset to default' });
125125
await expect(resetButton).toBeVisible();
126126
});
127+
128+
test('should not show the widget drawer by default on page load', async ({ page }) => {
129+
await page.locator('#widget-layout-container .pf-v6-widget-grid-tile__title')
130+
.first()
131+
.waitFor({ state: 'visible', timeout: 10000 });
132+
133+
const drawerText = page.getByText('Add new and previously removed widgets');
134+
await expect(drawerText).not.toBeVisible();
135+
});
136+
});
137+
138+
test.describe('Widget Layout - Empty Dashboard', () => {
139+
test('should auto-open the widget drawer when dashboard has no widgets', async ({ page }) => {
140+
await disableCookiePrompt(page);
141+
await page.addInitScript(() => {
142+
const originalFetch = window.fetch;
143+
window.fetch = async (...args) => {
144+
const url = typeof args[0] === 'string' ? args[0] : args[0] instanceof Request ? args[0].url : '';
145+
if (
146+
(url.includes('/api/widget-layout/v1/') && url.includes('dashboardType=')) ||
147+
(url.includes('/api/chrome-service/v1/dashboard-templates') && url.includes('dashboard='))
148+
) {
149+
return new Response(JSON.stringify({
150+
data: [{
151+
id: 1,
152+
default: true,
153+
templateBase: { name: 'landing-landingPage', displayName: 'Landing Page' },
154+
templateConfig: { sm: [], md: [], lg: [], xl: [] },
155+
dashboardName: 'Test Dashboard',
156+
createdAt: '2024-01-01T00:00:00Z',
157+
updatedAt: '2024-01-01T00:00:00Z',
158+
deletedAt: null,
159+
userId: 'test-user',
160+
}],
161+
}), { status: 200, headers: { 'Content-Type': 'application/json' } });
162+
}
163+
return originalFetch(...args);
164+
};
165+
});
166+
167+
await page.goto('/');
168+
await page.getByRole('button', { name: 'Add widgets' }).waitFor({ state: 'visible', timeout: 10000 });
169+
170+
const drawerText = page.getByText('Add new and previously removed widgets');
171+
await expect(drawerText).toBeVisible({ timeout: 10000 });
172+
});
127173
});

src/Components/DnDLayout/GridLayout.tsx

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -22,29 +22,21 @@ const sidebarBreakpoints = { xl: 1250, lg: 1100, md: 800, sm: 500 };
2222
const documentationLink =
2323
'https://docs.redhat.com/en/documentation/red_hat_hybrid_cloud_console/1-latest/html-single/getting_started_with_the_red_hat_hybrid_cloud_console/index#customizing-main-page_navigating-the-console';
2424

25-
const LayoutEmptyState = () => {
26-
const setDrawerExpanded = useSetAtom(drawerExpandedAtom);
27-
28-
useEffect(() => {
29-
setDrawerExpanded(true);
30-
}, []);
31-
32-
return (
33-
<PageSection hasBodyWrapper={false} className="empty-layout pf-v6-u-p-0">
34-
<EmptyState headingLevel="h2" icon={PlusCircleIcon} titleText="No dashboard content" variant={EmptyStateVariant.lg} className="pf-v6-u-p-sm">
35-
<EmptyStateBody>
36-
You don&apos;t have any widgets on your dashboard. To populate your dashboard, drag <GripVerticalIcon /> items from the blue widget bank to
37-
this dashboard body here.
38-
</EmptyStateBody>
39-
<EmptyStateActions>
40-
<Button variant="link" icon={<ExternalLinkAltIcon />} iconPosition="end" component="a" href={documentationLink}>
41-
Learn about your widget dashboard
42-
</Button>
43-
</EmptyStateActions>
44-
</EmptyState>
45-
</PageSection>
46-
);
47-
};
25+
const LayoutEmptyState = () => (
26+
<PageSection hasBodyWrapper={false} className="empty-layout pf-v6-u-p-0">
27+
<EmptyState headingLevel="h2" icon={PlusCircleIcon} titleText="No dashboard content" variant={EmptyStateVariant.lg} className="pf-v6-u-p-sm">
28+
<EmptyStateBody>
29+
You don&apos;t have any widgets on your dashboard. To populate your dashboard, drag <GripVerticalIcon /> items from the blue widget bank to
30+
this dashboard body here.
31+
</EmptyStateBody>
32+
<EmptyStateActions>
33+
<Button variant="link" icon={<ExternalLinkAltIcon />} iconPosition="end" component="a" href={documentationLink}>
34+
Learn about your widget dashboard
35+
</Button>
36+
</EmptyStateActions>
37+
</EmptyState>
38+
</PageSection>
39+
);
4840

4941
const getResizeHandle = (resizeHandleAxis: string, ref: React.Ref<HTMLDivElement>) => (
5042
<div ref={ref} className={`react-resizable-handle react-resizable-handle-${resizeHandleAxis}`}>
@@ -95,6 +87,10 @@ const GridLayout = ({ template, saveTemplate, isLoaded, isLayoutLocked = false,
9587
const activeLayout = newTemplate[layoutVariant] || [];
9688
setCurrentlyUsedWidgets(activeLayout.map((item) => item.widgetType));
9789

90+
if (activeLayout.length === 0) {
91+
setDrawerExpanded(true);
92+
}
93+
9894
await saveTemplate(newTemplate as LocalExtendedTemplateConfig);
9995
};
10096

@@ -108,6 +104,12 @@ const GridLayout = ({ template, saveTemplate, isLoaded, isLayoutLocked = false,
108104

109105
const activeLayout = patternFlyTemplate[layoutVariant] || [];
110106

107+
useEffect(() => {
108+
if (isLoaded && activeLayout.length === 0) {
109+
setDrawerExpanded(true);
110+
}
111+
}, [isLoaded]);
112+
111113
return (
112114
<div id="widget-layout-container" style={{ position: 'relative' }} ref={layoutRef}>
113115
{activeLayout.length === 0 && isLoaded && <LayoutEmptyState />}

src/Modules/GenericDashboardPage.tsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Breadcrumb, BreadcrumbItem, PageSection } from '@patternfly/react-core';
22
import React, { useEffect, useRef } from 'react';
33
import GridLayout from '../Components/DnDLayout/GridLayout';
4-
import { useAtomValue, useSetAtom } from 'jotai';
4+
import { Provider, useAtomValue, useSetAtom } from 'jotai';
55
import { lockedLayoutAtom } from '../state/lockedLayoutAtom';
66
import { Link, useParams } from 'react-router-dom';
77
import useDashboardTemplate from '../hooks/useDashboardTemplate';
@@ -11,8 +11,10 @@ import useChrome from '@redhat-cloud-services/frontend-components/useChrome';
1111
import { resolvedWidgetMappingAtom } from '../state/widgetMappingAtom';
1212
import { notificationsAtom, useRemoveNotification } from '../state/notificationsAtom';
1313
import Portal from '@redhat-cloud-services/frontend-components-notifications/Portal';
14+
import { backendFlagAtom, store } from '../state/store';
15+
import { useFlag } from '@unleash/proxy-client-react';
1416

15-
const GenericDashboardPage = () => {
17+
const GenericDashboardPageInner = () => {
1618
const { id } = useParams<{ id: string }>();
1719
const isLayoutLocked = useAtomValue(lockedLayoutAtom);
1820
const { template, saveTemplate, renameDashboard, isLoaded, dashboard } = useDashboardTemplate(Number(id));
@@ -23,6 +25,13 @@ const GenericDashboardPage = () => {
2325
const notifications = useAtomValue(notificationsAtom);
2426
const removeNotification = useRemoveNotification();
2527

28+
const setBackendFlag = useSetAtom(backendFlagAtom);
29+
const isNewBackend = useFlag('platform.widget-layout.new-backend');
30+
31+
useEffect(() => {
32+
setBackendFlag(isNewBackend);
33+
}, [isNewBackend]);
34+
2635
useEffect(() => {
2736
if (visibilityFunctions) {
2837
resolveWidgetMapping(visibilityFunctions);
@@ -53,4 +62,10 @@ const GenericDashboardPage = () => {
5362
);
5463
};
5564

65+
const GenericDashboardPage = () => (
66+
<Provider store={store}>
67+
<GenericDashboardPageInner />
68+
</Provider>
69+
);
70+
5671
export default GenericDashboardPage;

src/api/dashboard-templates-new.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ export const renameDashboardTemplate = async (templateId: DashboardTemplate['id'
264264
});
265265
handleErrors(resp);
266266
const json = await resp.json();
267-
return json.data;
267+
return json;
268268
};
269269

270270
// POST /api/widget-layout/v1/{id}/copy

src/hooks/useDashboardConfig.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2-
import { useAtom } from 'jotai';
2+
import { useAtom, useSetAtom } from 'jotai';
33
import DebouncePromise from 'awesome-debounce-promise';
44
import { templateAtom, templateIdAtom } from '../state/templateAtom';
55
import { layoutVariantAtom } from '../state/layoutAtom';
@@ -14,6 +14,7 @@ import {
1414
import useCurrentUser from './useCurrentUser';
1515
import { useAddNotification } from '../state/notificationsAtom';
1616
import { useApi } from './useApi';
17+
import { drawerExpandedAtom } from '../state/drawerExpandedAtom';
1718
import { useFlag } from '@unleash/proxy-client-react';
1819

1920
const sidebarBreakpoints = { xl: 1250, lg: 1100, md: 800, sm: 500 };
@@ -30,12 +31,20 @@ const useDashboardConfig = (layoutType: LayoutTypes = 'landing-landingPage') =>
3031
const layoutRef = useRef<HTMLDivElement>(null);
3132
const api = useApi();
3233
const debouncedPatchDashboardTemplate = useMemo(() => DebouncePromise(api.patchDashboardTemplate, 1500, { onlyResolvesLast: true }), [api]);
34+
const setDrawerExpanded = useSetAtom(drawerExpandedAtom);
3335

3436
useEffect(() => {
35-
if (!currentUser || templateId >= 0) {
37+
if (!currentUser) {
3638
return;
3739
}
3840

41+
if (templateId >= 0) {
42+
setIsLoaded(true);
43+
return;
44+
}
45+
46+
setDrawerExpanded(false);
47+
3948
api
4049
.getDashboardTemplates(mappedLayoutType)
4150
.then((templates) => {

src/hooks/useDashboardTemplate.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { useAtomValue, useSetAtom } from 'jotai';
1414
import { renameDashboardAtom } from '../state/dashboardsAtom';
1515
import { templateIdAtom } from '../state/templateAtom';
1616
import { backendFlagAtom } from '../state/store';
17+
import { drawerExpandedAtom } from '../state/drawerExpandedAtom';
1718
import { widgetKeyMap } from '../consts';
1819

1920
const remapShortKeys = (config: ExtendedTemplateConfig): ExtendedTemplateConfig => {
@@ -68,8 +69,10 @@ const useDashboardTemplate = (id: number) => {
6869
const debouncedPatchDashboardTemplate = useMemo(() => DebouncePromise(api.patchDashboardTemplateHub, 1500, { onlyResolvesLast: true }), [api]);
6970
const renameDashboardInList = useSetAtom(renameDashboardAtom);
7071
const invalidateStartPage = useSetAtom(templateIdAtom);
72+
const setDrawerExpanded = useSetAtom(drawerExpandedAtom);
7173

7274
useEffect(() => {
75+
setDrawerExpanded(false);
7376
const fetchTemplate = async () => {
7477
setIsLoaded(false);
7578
setError(null);

0 commit comments

Comments
 (0)