Skip to content

Commit

Permalink
[BACKPORT MAIN]Add Toasts to Observability Dashboards (#435) (#455)
Browse files Browse the repository at this point in the history
* Add Toasts to Observability Dashboards  (#435)

* Fixes
* Panel View (legacy)
  - Duplicate
  - Rename

Signed-off-by: Peter Fitzgibbons <[email protected]>

* Toasts use hook from useOpenSearchDashboards context provider

Signed-off-by: Peter Fitzgibbons <[email protected]>

* Testing for CustomPanel Toast

Signed-off-by: Peter Fitzgibbons <[email protected]>

* update catches from comments, minor code cleaning

Signed-off-by: Shenoy Pratik <[email protected]>

* update tests

Signed-off-by: Shenoy Pratik <[email protected]>

* remove unused redux slice

Signed-off-by: Shenoy Pratik <[email protected]>

* revert cypress changes

Signed-off-by: Shenoy Pratik <[email protected]>

* add toasts to SOflyout

Signed-off-by: Shenoy Pratik <[email protected]>

* fix messaging for multiple delete

Signed-off-by: Derek Ho <[email protected]>

* fix up toast and error handling for create and delete flows

Signed-off-by: Derek Ho <[email protected]>

* fix up clone

Signed-off-by: Derek Ho <[email protected]>

* fix rename in table

Signed-off-by: Derek Ho <[email protected]>

* fix rename in custom panel so view

Signed-off-by: Derek Ho <[email protected]>

* fix up panel toasts

Signed-off-by: Derek Ho <[email protected]>

* fix up for flyout

Signed-off-by: Derek Ho <[email protected]>

* code cleanup

Signed-off-by: Derek Ho <[email protected]>

* finish merge

Signed-off-by: Derek Ho <[email protected]>

* fix up PR comments

Signed-off-by: Derek Ho <[email protected]>

---------

Signed-off-by: Peter Fitzgibbons <[email protected]>
Signed-off-by: Shenoy Pratik <[email protected]>
Signed-off-by: Derek Ho <[email protected]>
Co-authored-by: Peter Fitzgibbons <[email protected]>
Co-authored-by: Derek Ho <[email protected]>
(cherry picked from commit a9d1d37)
Signed-off-by: Derek Ho <[email protected]>

* remove duplicate inport

Signed-off-by: Derek Ho <[email protected]>

* remove duplicate code block

Signed-off-by: Derek Ho <[email protected]>

---------

Signed-off-by: Peter Fitzgibbons <[email protected]>
Signed-off-by: Shenoy Pratik <[email protected]>
Signed-off-by: Derek Ho <[email protected]>
Co-authored-by: Shenoy Pratik <[email protected]>
Co-authored-by: Peter Fitzgibbons <[email protected]>
  • Loading branch information
3 people authored May 10, 2023
1 parent 72970aa commit 0a015f8
Show file tree
Hide file tree
Showing 15 changed files with 211 additions and 261 deletions.
3 changes: 1 addition & 2 deletions .cypress/integration/3_panels.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -644,8 +644,7 @@ const eraseTestPanels = () => {
eraseLegacyPanels();
eraseSavedObjectPaenls();
};
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}/;
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}/;

const clickCreatePanelButton = () =>
cy.get('a[data-test-subj="customPanels__createNewPanels"]').click();
Expand Down
2 changes: 1 addition & 1 deletion common/constants/custom_panels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { v4 as uuidv4 } from 'uuid';
export const CUSTOM_PANELS_API_PREFIX = '/api/observability/operational_panels';
export const CUSTOM_PANELS_DOCUMENTATION_URL =
'https://opensearch.org/docs/latest/observability-plugin/operational-panels/';
export const CREATE_PANEL_MESSAGE = 'Enter a name to describe the purpose of this custom panel.';
export const CREATE_PANEL_MESSAGE = 'Enter a name to describe the purpose of this Observability Dashboard.';

export const CUSTOM_PANELS_SAVED_OBJECT_TYPE = 'observability-panel';

Expand Down
2 changes: 1 addition & 1 deletion common/constants/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const DSL_ENDPOINT = '/_plugins/_dsl';

export const observabilityID = 'observability-logs';
export const observabilityTitle = 'Observability';
export const observabilityPluginOrder = 6000;
export const observabilityPluginOrder = 1500;

export const observabilityApplicationsID = 'observability-applications';
export const observabilityApplicationsTitle = 'Applications';
Expand Down
37 changes: 37 additions & 0 deletions public/components/common/toast/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { ToastInputFields } from '../../../../../../src/core/public';
import { coreRefs } from '../../../framework/core_refs';

type Color = 'success' | 'primary' | 'warning' | 'danger' | undefined;

export const useToast = () => {
const toasts = coreRefs.toasts!;

const setToast = (title: string, color: Color = 'success', text?: string) => {
const newToast: ToastInputFields = {
id: new Date().toISOString(),
title,
text,
};
switch (color) {
case 'danger': {
toasts.addDanger(newToast);
break;
}
case 'warning': {
toasts.addWarning(newToast);
break;
}
default: {
toasts.addSuccess(newToast);
break;
}
}
};

return { setToast };
};
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@ import PPLService from '../../../../public/services/requests/ppl';
import DSLService from '../../../../public/services/requests/dsl';
import { coreStartMock } from '../../../../test/__mocks__/coreMocks';
import { HttpResponse } from '../../../../../../src/core/public';
import { createStore } from '@reduxjs/toolkit';
import { applyMiddleware, createStore } from 'redux';
import { rootReducer } from '../../../framework/redux/reducers';
import thunk from 'redux-thunk';
import { Provider } from 'react-redux';

describe('Panels View Component', () => {
configure({ adapter: new Adapter() });
const store = createStore(rootReducer);

const store = createStore(rootReducer, applyMiddleware(thunk));

it('renders panel view container without visualizations', async () => {
httpClientMock.get = jest.fn(() =>
Expand Down
84 changes: 40 additions & 44 deletions public/components/custom_panels/custom_panel_table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ import { getSampleDataModal } from '../common/helpers/add_sample_modal';
import { pageStyles } from '../../../common/constants/shared';
import { DeleteModal } from '../common/helpers/delete_modal';
import {
clonePanel,
createPanel,
deletePanels,
fetchPanels,
Expand All @@ -57,6 +56,8 @@ import {
renameCustomPanel,
selectPanelList,
} from './redux/panel_slice';
import { isNameValid } from './helpers/utils';
import { useToast } from '../common/toast';

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

export const CustomPanelTable = ({
loading,
setBreadcrumbs,
parentBreadcrumbs,
cloneCustomPanel,
deleteCustomPanelList,
addSamplePanels,
}: Props) => {
const customPanels = useSelector<CustomPanelType[]>(selectPanelList);
Expand All @@ -100,16 +97,13 @@ export const CustomPanelTable = ({
const history = useHistory();

const dispatch = useDispatch();
const { setToast } = useToast();

useEffect(() => {
setBreadcrumbs(parentBreadcrumbs);
dispatch(fetchPanels());
}, []);

// useEffect(() =>
// console.log({ customPanels, selectedCustomPanels }, [customPanels, selectedCustomPanels])
// );

useEffect(() => {
const url = window.location.hash.split('/');
if (url[url.length - 1] === 'create') {
Expand All @@ -126,55 +120,58 @@ export const CustomPanelTable = ({
};

const onCreate = async (newCustomPanelName: string) => {
const newPanel = newPanelTemplate(newCustomPanelName);
dispatch(createPanel(newPanel));
if (!isNameValid(newCustomPanelName)) {
setToast('Invalid Dashboard name', 'danger');
} else {
const newPanel = newPanelTemplate(newCustomPanelName);
dispatch(createPanel(newPanel));
}
closeModal();
};

const onRename = async (newCustomPanelName: string) => {
dispatch(renameCustomPanel(newCustomPanelName, selectedCustomPanels[0].id));
if (!isNameValid(newCustomPanelName)) {
setToast('Invalid Dashboard name', 'danger');
} else {
dispatch(renameCustomPanel(newCustomPanelName, selectedCustomPanels[0].id));
}
closeModal();
};

const onClone = async (newName: string) => {
let sourcePanel = selectedCustomPanels[0];
try {
if (!isUuid(sourcePanel.id)) {
// Observability Panel API returns partial record, so for duplication
// we will retrieve the entire record and allow new process to continue.
const legacyFetchResult = await coreRefs.http!.get(
`${CUSTOM_PANELS_API_PREFIX}/panels/${sourcePanel.id}`
);
sourcePanel = legacyFetchResult.operationalPanel;
}
if (!isNameValid(newName)) {
setToast('Invalid Operational Panel name', 'danger');
} else {
let sourcePanel = selectedCustomPanels[0];
try {
if (!isUuid(sourcePanel.id)) {
// Observability Panel API returns partial record, so for duplication
// we will retrieve the entire record and allow new process to continue.
const legacyFetchResult = await coreRefs.http!.get(
`${CUSTOM_PANELS_API_PREFIX}/panels/${sourcePanel.id}`
);
sourcePanel = legacyFetchResult.operationalPanel;
}

const { id, ...newPanel } = {
...sourcePanel,
title: newName,
};
const { id, ...newPanel } = {
...sourcePanel,
title: newName,
};

dispatch(createPanel(newPanel));
} catch (err) {
console.log(err);
dispatch(createPanel(newPanel));
} catch (err) {
setToast(
'Error cloning Observability Dashboard, please make sure you have the correct permission.',
'danger'
);
console.error(err);
}
}
closeModal();
};

const onDelete = async () => {
const toastMessage = `Observability Dashboards ${
selectedCustomPanels.length > 1 ? 's' : ' ' + selectedCustomPanels[0].title
} successfully deleted!`;

try {
dispatch(deletePanels(selectedCustomPanels));
} catch (err) {
// setToast(
// 'Error deleting Operational Panels, please make sure you have the correct permission.',
// 'danger'
// );
console.error(err.body?.message || err);
}

dispatch(deletePanels(selectedCustomPanels));
closeModal();
};

Expand Down Expand Up @@ -337,7 +334,6 @@ export const CustomPanelTable = ({
},
] as Array<EuiTableFieldDataColumnType<CustomPanelListType>>;

// console.log('rendering', { customPanels, selectedCustomPanels });
return (
<div style={pageStyles}>
<EuiPage>
Expand Down
31 changes: 7 additions & 24 deletions public/components/custom_panels/custom_panel_view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ import { AddVisualizationPopover } from './helpers/add_visualization_popover';
import { DeleteModal } from '../common/helpers/delete_modal';
import { coreRefs } from '../../framework/core_refs';
import { clonePanel } from './redux/panel_slice';
import { useToast } from '../common/toast';

/*
* "CustomPanelsView" module used to render an Observability Dashboard
Expand Down Expand Up @@ -104,12 +105,6 @@ interface CustomPanelViewProps {
chrome: CoreStart['chrome'];
parentBreadcrumbs: EuiBreadcrumb[];
cloneCustomPanel: (clonedCustomPanelName: string, clonedCustomPanelId: string) => Promise<string>;
setToast: (
title: string,
color?: string,
text?: React.ReactChild | undefined,
side?: string | undefined
) => void;
onEditClick: (savedVisualizationId: string) => any;
startTime: string;
endTime: string;
Expand Down Expand Up @@ -138,7 +133,6 @@ export const CustomPanelView = (props: CustomPanelViewProps) => {
setEndTime,
updateAvailabilityVizId,
cloneCustomPanel,
setToast,
onEditClick,
onAddClick,
} = props;
Expand Down Expand Up @@ -169,6 +163,8 @@ export const CustomPanelView = (props: CustomPanelViewProps) => {

const dispatch = useDispatch();

const { setToast } = useToast();

const closeHelpFlyout = () => {
setAddVizDisabled(false);
setHelpIsFlyoutVisible(false);
Expand Down Expand Up @@ -318,24 +314,11 @@ export const CustomPanelView = (props: CustomPanelViewProps) => {
};

const onClone = async (newCustomPanelName: string) => {
try {
await dispatch(clonePanel(panel, newCustomPanelName));
} catch (err) {
setToast('Error while attempting to Duplicate this Dashboard.', 'danger');
if (!isNameValid(newCustomPanelName)) {
setToast('Invalid Operational Panel name', 'danger');
} else {
dispatch(clonePanel(panel, newCustomPanelName));
}

// const newPanel = {
// ...panel,
// title: newCustomPanelName,
// dateCreated: new Date().getTime(),
// dateModified: new Date().getTime(),
// } as PanelType;
// const newSOPanel = await coreRefs.savedObjectsClient!.create(
// CUSTOM_PANELS_SAVED_OBJECT_TYPE,
// newPanel
// );
//
// window.location.assign(`${last(parentBreadcrumbs)!.href}${newSOPanel.id}`);
closeModal();
};

Expand Down
Loading

0 comments on commit 0a015f8

Please sign in to comment.