From 68794342bf4e93aac8a56ea43daa4b0fac1ea481 Mon Sep 17 00:00:00 2001 From: Jenny Zhu Date: Sun, 11 Jan 2026 22:32:50 -0500 Subject: [PATCH 1/3] OU1170 Fix Add Variable - Name with more than 75 chars --- web/package-lock.json | 60 +++++++++---------- web/package.json | 12 ++-- .../dashboards/perses/PersesWrapper.tsx | 2 - .../dashboards/perses/dashboard-app.tsx | 60 +++++++++++++++++-- .../dashboards/perses/dashboard-page.tsx | 36 +---------- 5 files changed, 91 insertions(+), 79 deletions(-) diff --git a/web/package-lock.json b/web/package-lock.json index 7e38109c..fb1a8839 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -31,11 +31,11 @@ "@patternfly/react-icons": "^6.2.0", "@patternfly/react-table": "^6.2.0", "@patternfly/react-templates": "^6.2.0", - "@perses-dev/components": "0.53.0-beta.4", - "@perses-dev/core": "0.53.0-beta.3", - "@perses-dev/dashboards": "0.53.0-beta.4", - "@perses-dev/explore": "0.53.0-beta.4", - "@perses-dev/plugin-system": "0.53.0-beta.4", + "@perses-dev/components": "0.53.0-rc.1", + "@perses-dev/core": "0.53.0-beta.4", + "@perses-dev/dashboards": "0.53.0-rc.1", + "@perses-dev/explore": "0.53.0-rc.1", + "@perses-dev/plugin-system": "0.53.0-rc.1", "@prometheus-io/codemirror-promql": "^0.37.0", "@tanstack/react-query": "^4.36.1", "@types/ajv": "^0.0.5", @@ -5184,9 +5184,9 @@ } }, "node_modules/@perses-dev/components": { - "version": "0.53.0-beta.4", - "resolved": "https://registry.npmjs.org/@perses-dev/components/-/components-0.53.0-beta.4.tgz", - "integrity": "sha512-+yW6pXY0q3q3gMVjg3ZPVywYU6HVuKTRPL6fS+k174j6rb97ox6Lo8LL+GWhWYID2oQCvIrMbF9lCtBMPqtMNA==", + "version": "0.53.0-rc.1", + "resolved": "https://registry.npmjs.org/@perses-dev/components/-/components-0.53.0-rc.1.tgz", + "integrity": "sha512-yp8pzcPe2XyE21qBLAujk+gMIl07InIaY/hq19YGAeu9ycxi2hRKily0S89qEmhjS525KJHka+f6wjEztJFnlg==", "license": "Apache-2.0", "dependencies": { "@atlaskit/pragmatic-drag-and-drop": "^1.4.0", @@ -5194,7 +5194,7 @@ "@codemirror/lang-json": "^6.0.1", "@fontsource/lato": "^4.5.10", "@mui/x-date-pickers": "^7.23.1", - "@perses-dev/core": "0.53.0-beta.3", + "@perses-dev/core": "0.53.0-beta.4", "@tanstack/react-table": "^8.20.5", "@uiw/react-codemirror": "^4.19.1", "date-fns": "^4.1.0", @@ -5218,9 +5218,9 @@ } }, "node_modules/@perses-dev/core": { - "version": "0.53.0-beta.3", - "resolved": "https://registry.npmjs.org/@perses-dev/core/-/core-0.53.0-beta.3.tgz", - "integrity": "sha512-kC989dSUTbfwSMnd47KdtLTWgD/p8kvDmAtuE6NEj49Lv16MRRBNRoQ5MAe1rYqI9O848FpPkEpBp2kkldLdpg==", + "version": "0.53.0-beta.4", + "resolved": "https://registry.npmjs.org/@perses-dev/core/-/core-0.53.0-beta.4.tgz", + "integrity": "sha512-f31l/80YHuU172BvhVLzuMShBNcv0ehMLX1K2xRIuXQ886ULwihB6Q1K6pm1duxP7KtQFFQHAgE5flKJ5gKboA==", "license": "Apache-2.0", "dependencies": { "date-fns": "^4.1.0", @@ -5231,14 +5231,14 @@ } }, "node_modules/@perses-dev/dashboards": { - "version": "0.53.0-beta.4", - "resolved": "https://registry.npmjs.org/@perses-dev/dashboards/-/dashboards-0.53.0-beta.4.tgz", - "integrity": "sha512-L/eodWNSmtndUQ0Ss3mAYNo7rGCJNmXlSw+NNBgq3r2Sei05fz2f1zYkmLRDe0nD3Uac7ea88Ve+OVbzmPfjvg==", + "version": "0.53.0-rc.1", + "resolved": "https://registry.npmjs.org/@perses-dev/dashboards/-/dashboards-0.53.0-rc.1.tgz", + "integrity": "sha512-ndpz6I+fQiHEtQyvmD/hBjinj8Hk3pdwsBCoWFuUsCgNEt+uuukL/dT1nK5F3P3QEGwIb0eMtSQ1cGl1jtKtiA==", "license": "Apache-2.0", "dependencies": { - "@perses-dev/components": "0.53.0-beta.4", - "@perses-dev/core": "0.53.0-beta.3", - "@perses-dev/plugin-system": "0.53.0-beta.4", + "@perses-dev/components": "0.53.0-rc.1", + "@perses-dev/core": "0.53.0-beta.4", + "@perses-dev/plugin-system": "0.53.0-rc.1", "@types/react-grid-layout": "^1.3.2", "date-fns": "^4.1.0", "immer": "^10.1.1", @@ -5260,16 +5260,16 @@ } }, "node_modules/@perses-dev/explore": { - "version": "0.53.0-beta.4", - "resolved": "https://registry.npmjs.org/@perses-dev/explore/-/explore-0.53.0-beta.4.tgz", - "integrity": "sha512-88+9descggGiXI7rrI0jbFxH3LEudZ4iLEmfdFvUZ97wni8xkpFKipCBF/uNTofK5hVCm917CYlxLh61ZAP9sA==", + "version": "0.53.0-rc.1", + "resolved": "https://registry.npmjs.org/@perses-dev/explore/-/explore-0.53.0-rc.1.tgz", + "integrity": "sha512-mPk4jq+yOveZ+GcmNsJuiaql9/KI5YiLINl6fSq+cajBBZMviRomidFccbQcoVFXSgtssZeY4cXLkdQFyNgT4A==", "license": "Apache-2.0", "dependencies": { "@nexucis/fuzzy": "^0.5.1", - "@perses-dev/components": "0.53.0-beta.4", - "@perses-dev/core": "0.53.0-beta.3", - "@perses-dev/dashboards": "0.53.0-beta.4", - "@perses-dev/plugin-system": "0.53.0-beta.4", + "@perses-dev/components": "0.53.0-rc.1", + "@perses-dev/core": "0.53.0-beta.4", + "@perses-dev/dashboards": "0.53.0-rc.1", + "@perses-dev/plugin-system": "0.53.0-rc.1", "@types/react-grid-layout": "^1.3.2", "date-fns": "^4.1.0", "immer": "^10.1.1", @@ -5293,14 +5293,14 @@ } }, "node_modules/@perses-dev/plugin-system": { - "version": "0.53.0-beta.4", - "resolved": "https://registry.npmjs.org/@perses-dev/plugin-system/-/plugin-system-0.53.0-beta.4.tgz", - "integrity": "sha512-YBS+xcYiRig8SuSWo3nL2yT8hqJr6QW6KDCpcuGyaKoBFCW1ACIYmUVNt3Nduv3/TZMTaWD8hwNyQFOj/PYcxw==", + "version": "0.53.0-rc.1", + "resolved": "https://registry.npmjs.org/@perses-dev/plugin-system/-/plugin-system-0.53.0-rc.1.tgz", + "integrity": "sha512-RECE2Yms5fBU8+OjVNm4nu3RbPOl9eelKwrsBJpcg+iWG8cARK/IQGlICRVVuo4vv50pqaK6fijMt2TYppFtYA==", "license": "Apache-2.0", "dependencies": { "@module-federation/enhanced": "^0.21.4", - "@perses-dev/components": "0.53.0-beta.4", - "@perses-dev/core": "0.53.0-beta.3", + "@perses-dev/components": "0.53.0-rc.1", + "@perses-dev/core": "0.53.0-beta.4", "date-fns": "^4.1.0", "date-fns-tz": "^3.2.0", "immer": "^10.1.1", diff --git a/web/package.json b/web/package.json index d429d418..c1e65cc0 100644 --- a/web/package.json +++ b/web/package.json @@ -67,11 +67,11 @@ "@patternfly/react-icons": "^6.2.0", "@patternfly/react-table": "^6.2.0", "@patternfly/react-templates": "^6.2.0", - "@perses-dev/components": "0.53.0-beta.4", - "@perses-dev/core": "0.53.0-beta.3", - "@perses-dev/dashboards": "0.53.0-beta.4", - "@perses-dev/explore": "0.53.0-beta.4", - "@perses-dev/plugin-system": "0.53.0-beta.4", + "@perses-dev/components": "0.53.0-rc.1", + "@perses-dev/core": "0.53.0-beta.4", + "@perses-dev/dashboards": "0.53.0-rc.1", + "@perses-dev/explore": "0.53.0-rc.1", + "@perses-dev/plugin-system": "0.53.0-rc.1", "@prometheus-io/codemirror-promql": "^0.37.0", "@tanstack/react-query": "^4.36.1", "@types/ajv": "^0.0.5", @@ -184,4 +184,4 @@ "@console/pluginAPI": "*" } } -} \ No newline at end of file +} diff --git a/web/src/components/dashboards/perses/PersesWrapper.tsx b/web/src/components/dashboards/perses/PersesWrapper.tsx index 99b3ff9a..0a0610fb 100644 --- a/web/src/components/dashboards/perses/PersesWrapper.tsx +++ b/web/src/components/dashboards/perses/PersesWrapper.tsx @@ -365,7 +365,6 @@ export function PersesWrapper({ children, project }: PersesWrapperProps) { {!project ? ( @@ -461,7 +460,6 @@ function InnerWrapper({ children, project, dashboardName }) { {clearedDashboardResource ? ( diff --git a/web/src/components/dashboards/perses/dashboard-app.tsx b/web/src/components/dashboards/perses/dashboard-app.tsx index 16c899ea..d689cad8 100644 --- a/web/src/components/dashboards/perses/dashboard-app.tsx +++ b/web/src/components/dashboards/perses/dashboard-app.tsx @@ -1,7 +1,17 @@ -import { ReactElement, ReactNode, useState } from 'react'; +import { ReactElement, ReactNode, useState, useCallback, useEffect } from 'react'; import { Box } from '@mui/material'; -import { ChartsProvider, ErrorAlert, ErrorBoundary, useChartsTheme } from '@perses-dev/components'; -import { DashboardResource, EphemeralDashboardResource } from '@perses-dev/core'; +import { + ChartsProvider, + ErrorAlert, + ErrorBoundary, + useChartsTheme, + useSnackbar, +} from '@perses-dev/components'; +import { + DashboardResource, + EphemeralDashboardResource, + getResourceExtendedDisplayName, +} from '@perses-dev/core'; import { useDatasourceStore } from '@perses-dev/plugin-system'; import { PanelDrawer, @@ -16,12 +26,12 @@ import { LeaveDialog, } from '@perses-dev/dashboards'; import { - OnSaveDashboard, useDashboard, useDiscardChangesConfirmationDialog, useEditMode, } from '@perses-dev/dashboards'; import { OCPDashboardToolbar } from './dashboard-toolbar'; +import { useUpdateDashboardMutation } from './dashboard-api'; export interface DashboardAppProps { dashboardResource: DashboardResource | EphemeralDashboardResource; @@ -35,7 +45,6 @@ export interface DashboardAppProps { // when navigating away with unsaved changes (closing tab, ...). isLeavingConfirmDialogEnabled?: boolean; dashboardTitleComponent?: ReactNode; - onSave?: OnSaveDashboard; onDiscard?: (entity: DashboardResource) => void; } @@ -49,17 +58,26 @@ export const OCPDashboardApp = (props: DashboardAppProps): ReactElement => { isCreating, isInitialVariableSticky, isLeavingConfirmDialogEnabled, - onSave, onDiscard, } = props; const chartsTheme = useChartsTheme(); + const { successSnackbar, exceptionSnackbar } = useSnackbar(); const { isEditMode, setEditMode } = useEditMode(); const { dashboard, setDashboard } = useDashboard(); + const [originalDashboard, setOriginalDashboard] = useState< DashboardResource | EphemeralDashboardResource | undefined >(undefined); + const [saveErrorOccurred, setSaveErrorOccurred] = useState(false); + + useEffect(() => { + if (saveErrorOccurred && !isEditMode) { + setEditMode(true); + setSaveErrorOccurred(false); + } + }, [isEditMode, saveErrorOccurred, setEditMode]); const { setSavedDatasources } = useDatasourceStore(); const { openDiscardChangesConfirmationDialog, closeDiscardChangesConfirmationDialog } = @@ -99,6 +117,36 @@ export const OCPDashboardApp = (props: DashboardAppProps): ReactElement => { } }; + const updateDashboardMutation = useUpdateDashboardMutation(); + + const onSave = useCallback( + async (data: DashboardResource | EphemeralDashboardResource) => { + if (data.kind !== 'Dashboard') { + throw new Error('Invalid kind'); + } + + try { + const result = await updateDashboardMutation.mutateAsync(data, { + onSuccess: (updatedDashboard: DashboardResource) => { + successSnackbar( + `Dashboard ${getResourceExtendedDisplayName( + updatedDashboard, + )} has been successfully updated`, + ); + setSaveErrorOccurred(false); + return updatedDashboard; + }, + }); + return result; + } catch (error) { + exceptionSnackbar(error); + setSaveErrorOccurred(true); + return null; + } + }, + [updateDashboardMutation, successSnackbar, exceptionSnackbar, setSaveErrorOccurred], + ); + return ( { combinedInitialLoad, } = useDashboardsData(); - const updateDashboardMutation = useUpdateDashboardMutation(); - const { successSnackbar, exceptionSnackbar } = useSnackbar(); - - const handleDashboardSave = useCallback( - (data: DashboardResource | EphemeralDashboardResource) => { - if (data.kind !== 'Dashboard') { - throw new Error('Invalid kind'); - } - return updateDashboardMutation.mutateAsync(data, { - onSuccess: (updatedDashboard: DashboardResource) => { - successSnackbar( - `Dashboard ${getResourceExtendedDisplayName( - updatedDashboard, - )} has been successfully updated`, - ); - return updatedDashboard; - }, - onError: (err) => { - exceptionSnackbar(err); - throw err; - }, - }); - }, - [exceptionSnackbar, successSnackbar, updateDashboardMutation], - ); - // Get dashboard and project from URL parameters const urlDashboard = searchParams.get('dashboard'); const urlProject = searchParams.get('project'); @@ -120,7 +87,6 @@ const DashboardPage_: FC = () => { isReadonly={false} isVariableEnabled={true} isDatasourceEnabled={false} - onSave={handleDashboardSave} emptyDashboardProps={{ title: t('Empty Dashboard'), description: t('To get started add something to your dashboard'), From e9b7e33c21b1f1f4549a12da2fd143abaa82ab64 Mon Sep 17 00:00:00 2001 From: Jenny Zhu Date: Mon, 12 Jan 2026 14:18:11 -0500 Subject: [PATCH 2/3] fix: add static text translation to success message --- .../components/dashboards/perses/dashboard-app.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/web/src/components/dashboards/perses/dashboard-app.tsx b/web/src/components/dashboards/perses/dashboard-app.tsx index d689cad8..c77d9edc 100644 --- a/web/src/components/dashboards/perses/dashboard-app.tsx +++ b/web/src/components/dashboards/perses/dashboard-app.tsx @@ -32,6 +32,7 @@ import { } from '@perses-dev/dashboards'; import { OCPDashboardToolbar } from './dashboard-toolbar'; import { useUpdateDashboardMutation } from './dashboard-api'; +import { useTranslation } from 'react-i18next'; export interface DashboardAppProps { dashboardResource: DashboardResource | EphemeralDashboardResource; @@ -61,6 +62,8 @@ export const OCPDashboardApp = (props: DashboardAppProps): ReactElement => { onDiscard, } = props; + const { t } = useTranslation(process.env.I18N_NAMESPACE); + const chartsTheme = useChartsTheme(); const { successSnackbar, exceptionSnackbar } = useSnackbar(); @@ -129,9 +132,11 @@ export const OCPDashboardApp = (props: DashboardAppProps): ReactElement => { const result = await updateDashboardMutation.mutateAsync(data, { onSuccess: (updatedDashboard: DashboardResource) => { successSnackbar( - `Dashboard ${getResourceExtendedDisplayName( - updatedDashboard, - )} has been successfully updated`, + t( + `Dashboard ${getResourceExtendedDisplayName( + updatedDashboard, + )} has been successfully updated`, + ), ); setSaveErrorOccurred(false); return updatedDashboard; @@ -144,7 +149,7 @@ export const OCPDashboardApp = (props: DashboardAppProps): ReactElement => { return null; } }, - [updateDashboardMutation, successSnackbar, exceptionSnackbar, setSaveErrorOccurred], + [updateDashboardMutation, successSnackbar, t, exceptionSnackbar], ); return ( From add73fb128a6740beddccf905bfcafcc215509c1 Mon Sep 17 00:00:00 2001 From: Jenny Zhu Date: Tue, 13 Jan 2026 12:39:30 -0500 Subject: [PATCH 3/3] feat: In dashboard add Patternfly Toast Alerts, remove Perses Snackbar --- .../dashboards/perses/ToastProvider.tsx | 65 +++++++++++++++++++ .../dashboards/perses/dashboard-app.tsx | 19 +++--- .../dashboards/perses/dashboard-frame.tsx | 31 +++++---- 3 files changed, 90 insertions(+), 25 deletions(-) create mode 100644 web/src/components/dashboards/perses/ToastProvider.tsx diff --git a/web/src/components/dashboards/perses/ToastProvider.tsx b/web/src/components/dashboards/perses/ToastProvider.tsx new file mode 100644 index 00000000..1f4c2642 --- /dev/null +++ b/web/src/components/dashboards/perses/ToastProvider.tsx @@ -0,0 +1,65 @@ +import React, { createContext, useContext, useState, ReactNode } from 'react'; +import { + Alert, + AlertProps, + AlertGroup, + AlertActionCloseButton, + AlertVariant, +} from '@patternfly/react-core'; + +interface ToastItem { + key: string; + title: string; + variant: AlertProps['variant']; +} + +interface ToastContextType { + addAlert: (title: string, variant: AlertProps['variant']) => void; + removeAlert: (key: string) => void; + alerts: ToastItem[]; +} + +const ToastContext = createContext(undefined); + +export const useToast = () => { + const context = useContext(ToastContext); + if (!context) { + throw new Error('useAlerts must be used within AlertProvider'); + } + return context; +}; + +export const ToastProvider: React.FC<{ children: ReactNode }> = ({ children }) => { + const [alerts, setAlerts] = useState([]); + + const addAlert = (title: string, variant: AlertProps['variant']) => { + const key = new Date().getTime().toString(); + setAlerts((prevAlerts) => [{ title, variant, key }, ...prevAlerts]); + }; + + const removeAlert = (key: string) => { + setAlerts((prevAlerts) => prevAlerts.filter((alert) => alert.key !== key)); + }; + + return ( + + {children} + + {alerts.map(({ key, variant, title }) => ( + removeAlert(key)} + /> + } + key={key} + /> + ))} + + + ); +}; diff --git a/web/src/components/dashboards/perses/dashboard-app.tsx b/web/src/components/dashboards/perses/dashboard-app.tsx index c77d9edc..5adb0411 100644 --- a/web/src/components/dashboards/perses/dashboard-app.tsx +++ b/web/src/components/dashboards/perses/dashboard-app.tsx @@ -1,12 +1,6 @@ import { ReactElement, ReactNode, useState, useCallback, useEffect } from 'react'; import { Box } from '@mui/material'; -import { - ChartsProvider, - ErrorAlert, - ErrorBoundary, - useChartsTheme, - useSnackbar, -} from '@perses-dev/components'; +import { ChartsProvider, ErrorAlert, ErrorBoundary, useChartsTheme } from '@perses-dev/components'; import { DashboardResource, EphemeralDashboardResource, @@ -33,6 +27,7 @@ import { import { OCPDashboardToolbar } from './dashboard-toolbar'; import { useUpdateDashboardMutation } from './dashboard-api'; import { useTranslation } from 'react-i18next'; +import { useToast } from './ToastProvider'; export interface DashboardAppProps { dashboardResource: DashboardResource | EphemeralDashboardResource; @@ -65,7 +60,7 @@ export const OCPDashboardApp = (props: DashboardAppProps): ReactElement => { const { t } = useTranslation(process.env.I18N_NAMESPACE); const chartsTheme = useChartsTheme(); - const { successSnackbar, exceptionSnackbar } = useSnackbar(); + const { addAlert } = useToast(); const { isEditMode, setEditMode } = useEditMode(); const { dashboard, setDashboard } = useDashboard(); @@ -131,25 +126,27 @@ export const OCPDashboardApp = (props: DashboardAppProps): ReactElement => { try { const result = await updateDashboardMutation.mutateAsync(data, { onSuccess: (updatedDashboard: DashboardResource) => { - successSnackbar( + addAlert( t( `Dashboard ${getResourceExtendedDisplayName( updatedDashboard, )} has been successfully updated`, ), + 'success', ); + setSaveErrorOccurred(false); return updatedDashboard; }, }); return result; } catch (error) { - exceptionSnackbar(error); + addAlert(`${error}`, 'danger'); setSaveErrorOccurred(true); return null; } }, - [updateDashboardMutation, successSnackbar, t, exceptionSnackbar], + [updateDashboardMutation, addAlert, t], ); return ( diff --git a/web/src/components/dashboards/perses/dashboard-frame.tsx b/web/src/components/dashboards/perses/dashboard-frame.tsx index a75ee755..2d539e26 100644 --- a/web/src/components/dashboards/perses/dashboard-frame.tsx +++ b/web/src/components/dashboards/perses/dashboard-frame.tsx @@ -5,6 +5,7 @@ import { CombinedDashboardMetadata } from './hooks/useDashboardsData'; import { ProjectBar } from './project/ProjectBar'; import { PersesWrapper } from './PersesWrapper'; import { Overview } from '@openshift-console/dynamic-plugin-sdk'; +import { ToastProvider } from './ToastProvider'; interface DashboardFrameProps { activeProject: string | null; @@ -26,20 +27,22 @@ export const DashboardFrame: React.FC = ({ return ( <> - - {activeProjectDashboardsMetadata?.length === 0 ? ( - - ) : ( - - {children} - - )} - + + + {activeProjectDashboardsMetadata?.length === 0 ? ( + + ) : ( + + {children} + + )} + + ); };