Skip to content

Commit a9d1d37

Browse files
ps48Peter Fitzgibbonsderek-ho
authored
Add Toasts to Observability Dashboards (#435)
* Fixes * Panel View (legacy) - Duplicate - Rename Signed-off-by: Peter Fitzgibbons <pjfitz@amazon.com> * Toasts use hook from useOpenSearchDashboards context provider Signed-off-by: Peter Fitzgibbons <pjfitz@amazon.com> * Testing for CustomPanel Toast Signed-off-by: Peter Fitzgibbons <pjfitz@amazon.com> * update catches from comments, minor code cleaning Signed-off-by: Shenoy Pratik <sgguruda@amazon.com> * update tests Signed-off-by: Shenoy Pratik <sgguruda@amazon.com> * remove unused redux slice Signed-off-by: Shenoy Pratik <sgguruda@amazon.com> * revert cypress changes Signed-off-by: Shenoy Pratik <sgguruda@amazon.com> * add toasts to SOflyout Signed-off-by: Shenoy Pratik <sgguruda@amazon.com> * fix messaging for multiple delete Signed-off-by: Derek Ho <dxho@amazon.com> * fix up toast and error handling for create and delete flows Signed-off-by: Derek Ho <dxho@amazon.com> * fix up clone Signed-off-by: Derek Ho <dxho@amazon.com> * fix rename in table Signed-off-by: Derek Ho <dxho@amazon.com> * fix rename in custom panel so view Signed-off-by: Derek Ho <dxho@amazon.com> * fix up panel toasts Signed-off-by: Derek Ho <dxho@amazon.com> * fix up for flyout Signed-off-by: Derek Ho <dxho@amazon.com> * code cleanup Signed-off-by: Derek Ho <dxho@amazon.com> * finish merge Signed-off-by: Derek Ho <dxho@amazon.com> * fix up PR comments Signed-off-by: Derek Ho <dxho@amazon.com> --------- Signed-off-by: Peter Fitzgibbons <pjfitz@amazon.com> Signed-off-by: Shenoy Pratik <sgguruda@amazon.com> Signed-off-by: Derek Ho <dxho@amazon.com> Co-authored-by: Peter Fitzgibbons <pjfitz@amazon.com> Co-authored-by: Derek Ho <dxho@amazon.com>
1 parent b3c0847 commit a9d1d37

File tree

15 files changed

+212
-261
lines changed

15 files changed

+212
-261
lines changed

.cypress/integration/3_panels.spec.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -644,8 +644,7 @@ const eraseTestPanels = () => {
644644
eraseLegacyPanels();
645645
eraseSavedObjectPaenls();
646646
};
647-
const uuidRx =
648-
/[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}/;
647+
const uuidRx = /[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}/;
649648

650649
const clickCreatePanelButton = () =>
651650
cy.get('a[data-test-subj="customPanels__createNewPanels"]').click();

common/constants/custom_panels.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
export const CUSTOM_PANELS_API_PREFIX = '/api/observability/operational_panels';
77
export const CUSTOM_PANELS_DOCUMENTATION_URL =
88
'https://opensearch.org/docs/latest/observability-plugin/operational-panels/';
9-
export const CREATE_PANEL_MESSAGE = 'Enter a name to describe the purpose of this custom panel.';
9+
export const CREATE_PANEL_MESSAGE = 'Enter a name to describe the purpose of this Observability Dashboard.';
1010

1111
export const CUSTOM_PANELS_SAVED_OBJECT_TYPE = 'observability-panel';
1212

common/constants/shared.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export const DSL_ENDPOINT = '/_plugins/_dsl';
2525

2626
export const observabilityID = 'observability-logs';
2727
export const observabilityTitle = 'Observability';
28-
export const observabilityPluginOrder = 6000;
28+
export const observabilityPluginOrder = 1500;
2929

3030
export const observabilityApplicationsID = 'observability-applications';
3131
export const observabilityApplicationsTitle = 'Applications';
+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import { ToastInputFields } from '../../../../../../src/core/public';
7+
import { coreRefs } from '../../../framework/core_refs';
8+
9+
type Color = 'success' | 'primary' | 'warning' | 'danger' | undefined;
10+
11+
export const useToast = () => {
12+
const toasts = coreRefs.toasts!;
13+
14+
const setToast = (title: string, color: Color = 'success', text?: string) => {
15+
const newToast: ToastInputFields = {
16+
id: new Date().toISOString(),
17+
title,
18+
text,
19+
};
20+
switch (color) {
21+
case 'danger': {
22+
toasts.addDanger(newToast);
23+
break;
24+
}
25+
case 'warning': {
26+
toasts.addWarning(newToast);
27+
break;
28+
}
29+
default: {
30+
toasts.addSuccess(newToast);
31+
break;
32+
}
33+
}
34+
};
35+
36+
return { setToast };
37+
};

public/components/custom_panels/__tests__/custom_panel_view.test.tsx

+4-2
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,15 @@ import PPLService from '../../../../public/services/requests/ppl';
2020
import DSLService from '../../../../public/services/requests/dsl';
2121
import { coreStartMock } from '../../../../test/__mocks__/coreMocks';
2222
import { HttpResponse } from '../../../../../../src/core/public';
23-
import { createStore } from '@reduxjs/toolkit';
23+
import { applyMiddleware, createStore } from 'redux';
2424
import { rootReducer } from '../../../framework/redux/reducers';
25+
import thunk from 'redux-thunk';
2526
import { Provider } from 'react-redux';
2627

2728
describe('Panels View Component', () => {
2829
configure({ adapter: new Adapter() });
29-
const store = createStore(rootReducer);
30+
31+
const store = createStore(rootReducer, applyMiddleware(thunk));
3032

3133
it('renders panel view container without visualizations', async () => {
3234
httpClientMock.get = jest.fn(() =>

public/components/custom_panels/custom_panel_table.tsx

+40-44
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ import { getSampleDataModal } from '../common/helpers/add_sample_modal';
4848
import { pageStyles } from '../../../common/constants/shared';
4949
import { DeleteModal } from '../common/helpers/delete_modal';
5050
import {
51-
clonePanel,
5251
createPanel,
5352
deletePanels,
5453
fetchPanels,
@@ -57,6 +56,8 @@ import {
5756
renameCustomPanel,
5857
selectPanelList,
5958
} from './redux/panel_slice';
59+
import { isNameValid } from './helpers/utils';
60+
import { useToast } from '../common/toast';
6061

6162
/*
6263
* "CustomPanelTable" module, used to view all the saved panels
@@ -77,17 +78,13 @@ interface Props {
7778
loading: boolean;
7879
setBreadcrumbs: (newBreadcrumbs: ChromeBreadcrumb[]) => void;
7980
parentBreadcrumbs: EuiBreadcrumb[];
80-
cloneCustomPanel: (newCustomPanelName: string, customPanelId: string) => void;
81-
deleteCustomPanelList: (customPanelIdList: string[], toastMessage: string) => any;
8281
addSamplePanels: () => void;
8382
}
8483

8584
export const CustomPanelTable = ({
8685
loading,
8786
setBreadcrumbs,
8887
parentBreadcrumbs,
89-
cloneCustomPanel,
90-
deleteCustomPanelList,
9188
addSamplePanels,
9289
}: Props) => {
9390
const customPanels = useSelector<CustomPanelType[]>(selectPanelList);
@@ -100,16 +97,13 @@ export const CustomPanelTable = ({
10097
const history = useHistory();
10198

10299
const dispatch = useDispatch();
100+
const { setToast } = useToast();
103101

104102
useEffect(() => {
105103
setBreadcrumbs(parentBreadcrumbs);
106104
dispatch(fetchPanels());
107105
}, []);
108106

109-
// useEffect(() =>
110-
// console.log({ customPanels, selectedCustomPanels }, [customPanels, selectedCustomPanels])
111-
// );
112-
113107
useEffect(() => {
114108
const url = window.location.hash.split('/');
115109
if (url[url.length - 1] === 'create') {
@@ -126,55 +120,58 @@ export const CustomPanelTable = ({
126120
};
127121

128122
const onCreate = async (newCustomPanelName: string) => {
129-
const newPanel = newPanelTemplate(newCustomPanelName);
130-
dispatch(createPanel(newPanel));
123+
if (!isNameValid(newCustomPanelName)) {
124+
setToast('Invalid Dashboard name', 'danger');
125+
} else {
126+
const newPanel = newPanelTemplate(newCustomPanelName);
127+
dispatch(createPanel(newPanel));
128+
}
131129
closeModal();
132130
};
133131

134132
const onRename = async (newCustomPanelName: string) => {
135-
dispatch(renameCustomPanel(newCustomPanelName, selectedCustomPanels[0].id));
133+
if (!isNameValid(newCustomPanelName)) {
134+
setToast('Invalid Dashboard name', 'danger');
135+
} else {
136+
dispatch(renameCustomPanel(newCustomPanelName, selectedCustomPanels[0].id));
137+
}
136138
closeModal();
137139
};
138140

139141
const onClone = async (newName: string) => {
140-
let sourcePanel = selectedCustomPanels[0];
141-
try {
142-
if (!isUuid(sourcePanel.id)) {
143-
// Observability Panel API returns partial record, so for duplication
144-
// we will retrieve the entire record and allow new process to continue.
145-
const legacyFetchResult = await coreRefs.http!.get(
146-
`${CUSTOM_PANELS_API_PREFIX}/panels/${sourcePanel.id}`
147-
);
148-
sourcePanel = legacyFetchResult.operationalPanel;
149-
}
142+
if (!isNameValid(newName)) {
143+
setToast('Invalid Operational Panel name', 'danger');
144+
} else {
145+
let sourcePanel = selectedCustomPanels[0];
146+
try {
147+
if (!isUuid(sourcePanel.id)) {
148+
// Observability Panel API returns partial record, so for duplication
149+
// we will retrieve the entire record and allow new process to continue.
150+
const legacyFetchResult = await coreRefs.http!.get(
151+
`${CUSTOM_PANELS_API_PREFIX}/panels/${sourcePanel.id}`
152+
);
153+
sourcePanel = legacyFetchResult.operationalPanel;
154+
}
150155

151-
const { id, ...newPanel } = {
152-
...sourcePanel,
153-
title: newName,
154-
};
156+
const { id, ...newPanel } = {
157+
...sourcePanel,
158+
title: newName,
159+
};
155160

156-
dispatch(createPanel(newPanel));
157-
} catch (err) {
158-
console.log(err);
161+
dispatch(createPanel(newPanel));
162+
} catch (err) {
163+
setToast(
164+
'Error cloning Observability Dashboard, please make sure you have the correct permission.',
165+
'danger'
166+
);
167+
console.error(err);
168+
}
159169
}
160170
closeModal();
161171
};
162172

163173
const onDelete = async () => {
164-
const toastMessage = `Observability Dashboards ${
165-
selectedCustomPanels.length > 1 ? 's' : ' ' + selectedCustomPanels[0].title
166-
} successfully deleted!`;
167-
168-
try {
169-
dispatch(deletePanels(selectedCustomPanels));
170-
} catch (err) {
171-
// setToast(
172-
// 'Error deleting Operational Panels, please make sure you have the correct permission.',
173-
// 'danger'
174-
// );
175-
console.error(err.body?.message || err);
176-
}
177-
174+
dispatch(deletePanels(selectedCustomPanels));
178175
closeModal();
179176
};
180177

@@ -337,7 +334,6 @@ export const CustomPanelTable = ({
337334
},
338335
] as Array<EuiTableFieldDataColumnType<CustomPanelListType>>;
339336

340-
// console.log('rendering', { customPanels, selectedCustomPanels });
341337
return (
342338
<div style={pageStyles}>
343339
<EuiPage>

public/components/custom_panels/custom_panel_view.tsx

+7-24
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ import { AddVisualizationPopover } from './helpers/add_visualization_popover';
7070
import { DeleteModal } from '../common/helpers/delete_modal';
7171
import { coreRefs } from '../../framework/core_refs';
7272
import { clonePanel } from './redux/panel_slice';
73+
import { useToast } from '../common/toast';
7374

7475
/*
7576
* "CustomPanelsView" module used to render an Observability Dashboard
@@ -104,12 +105,6 @@ interface CustomPanelViewProps {
104105
chrome: CoreStart['chrome'];
105106
parentBreadcrumbs: EuiBreadcrumb[];
106107
cloneCustomPanel: (clonedCustomPanelName: string, clonedCustomPanelId: string) => Promise<string>;
107-
setToast: (
108-
title: string,
109-
color?: string,
110-
text?: React.ReactChild | undefined,
111-
side?: string | undefined
112-
) => void;
113108
onEditClick: (savedVisualizationId: string) => any;
114109
startTime: string;
115110
endTime: string;
@@ -138,7 +133,6 @@ export const CustomPanelView = (props: CustomPanelViewProps) => {
138133
setEndTime,
139134
updateAvailabilityVizId,
140135
cloneCustomPanel,
141-
setToast,
142136
onEditClick,
143137
onAddClick,
144138
} = props;
@@ -169,6 +163,8 @@ export const CustomPanelView = (props: CustomPanelViewProps) => {
169163

170164
const dispatch = useDispatch();
171165

166+
const { setToast } = useToast();
167+
172168
const closeHelpFlyout = () => {
173169
setAddVizDisabled(false);
174170
setHelpIsFlyoutVisible(false);
@@ -318,24 +314,11 @@ export const CustomPanelView = (props: CustomPanelViewProps) => {
318314
};
319315

320316
const onClone = async (newCustomPanelName: string) => {
321-
try {
322-
await dispatch(clonePanel(panel, newCustomPanelName));
323-
} catch (err) {
324-
setToast('Error while attempting to Duplicate this Dashboard.', 'danger');
317+
if (!isNameValid(newCustomPanelName)) {
318+
setToast('Invalid Operational Panel name', 'danger');
319+
} else {
320+
dispatch(clonePanel(panel, newCustomPanelName));
325321
}
326-
327-
// const newPanel = {
328-
// ...panel,
329-
// title: newCustomPanelName,
330-
// dateCreated: new Date().getTime(),
331-
// dateModified: new Date().getTime(),
332-
// } as PanelType;
333-
// const newSOPanel = await coreRefs.savedObjectsClient!.create(
334-
// CUSTOM_PANELS_SAVED_OBJECT_TYPE,
335-
// newPanel
336-
// );
337-
//
338-
// window.location.assign(`${last(parentBreadcrumbs)!.href}${newSOPanel.id}`);
339322
closeModal();
340323
};
341324

0 commit comments

Comments
 (0)