diff --git a/.cypress/integration/3_panels.spec.ts b/.cypress/integration/3_panels.spec.ts index 43cc625c2..4fac68df1 100644 --- a/.cypress/integration/3_panels.spec.ts +++ b/.cypress/integration/3_panels.spec.ts @@ -88,10 +88,10 @@ describe('Testing panels table', () => { moveToPanelHome(); }); - it('Displays error toast for invalid panel name', () => { + it.skip('Displays error toast for invalid panel name', () => { clickCreatePanelButton(); confirmModal(); - expectToastWith('Invalid Operational Panel name'); + expectToastWith('Invalid Dashboard name'); }); it('Creates a panel and redirects to the panel', () => { @@ -150,6 +150,37 @@ describe('Testing panels table', () => { cy.get('button[data-test-subj="popoverModal__deleteButton"]').click(); cy.get('h2[data-test-subj="customPanels__noPanelsHome"]').should('exist'); }); + + it('Searches panels', () => { + createLegacyPanel('Legacy Named'); + createSavedObjectPanel('Saved Object'); + cy.reload(); + cy.get('input[data-test-subj="operationalPanelSearchBar"]') + .focus() + .type('this panel should not exist', { + delay: 50, + }); + + cy.get('.euiTableCellContent__text').contains('No items found').should('exist'); + + // Search for oriignal Legacy Panel + cy.get('[aria-label="Clear input"]').click(); + cy.get('input[data-test-subj="operationalPanelSearchBar"]').focus().type(TEST_PANEL, { + delay: 50, + }); + + cy.get('a.euiLink').contains(TEST_PANEL).should('exist'); + cy.get('.euiTableRow').should('have.length', 1); + + // Search for teh Saved Object panel + cy.get('[aria-label="Clear input"]').click(); + cy.get('input[data-test-subj="operationalPanelSearchBar"]').focus().type('Saved Object', { + delay: 50, + }); + + cy.get('a.euiLink').contains('Saved Object').should('exist'); + cy.get('.euiTableRow').should('have.length', 1); + }); }); describe('with a SavedObjects Panel', () => { @@ -196,38 +227,6 @@ describe('Testing panels table', () => { cy.get('h2[data-test-subj="customPanels__noPanelsHome"]').should('exist'); }); }); - - it('Searches existing panel', () => { - createLegacyPanel(); - cy.get('input[data-test-subj="operationalPanelSearchBar"]') - .focus() - .type('this panel should not exist', { - delay: 50, - }); - - cy.get('.euiTableCellContent__text').contains('No items found').should('exist'); - - cy.get('[aria-label="Clear input"]').click(); - cy.get('input[data-test-subj="operationalPanelSearchBar"]') - .focus() - .type(TEST_PANEL + ' (copy) (rename)', { - delay: 50, - }); - - cy.get('a.euiLink') - .contains(TEST_PANEL + ' (copy) (rename)') - .should('exist'); - }); - - it('Create a panel for testing', () => { - moveToPanelHome(); - // keep a panel for testing - clickCreatePanelButton(); - cy.get('input.euiFieldText').focus().type(TEST_PANEL, { - delay: 50, - }); - cy.get('button[data-test-subj="runModalButton"]').click(); - }); }); describe('Testing a panel', () => { @@ -274,7 +273,7 @@ describe('Testing a panel', () => { cy.get(`input.euiFieldText[value="${TEST_PANEL} (copy)"]`) .focus() - .clear({force: true}) + .clear({ force: true }) .focus() .type('Renamed Panel', { delay: 200, @@ -347,9 +346,9 @@ describe('Testing a panel', () => { cy.get('h5[data-test-subj="visualizationHeader"]') .contains(PPL_VISUALIZATIONS_NAMES[1]) - .trigger('mousedown', {which: 1}) - .trigger('mousemove', {clientX: 1100, clientY: 0}) - .trigger('mouseup', {force: true}); + .trigger('mousedown', { which: 1 }) + .trigger('mousemove', { clientX: 1100, clientY: 0 }) + .trigger('mouseup', { force: true }); cy.get('button[data-test-subj="savePanelButton"]').click(); cy.wait(delay * 3); @@ -364,9 +363,9 @@ describe('Testing a panel', () => { cy.get('.react-resizable-handle') .eq(1) - .trigger('mousedown', {which: 1}) - .trigger('mousemove', {clientX: 2000, clientY: 800}) - .trigger('mouseup', {force: true}); + .trigger('mousedown', { which: 1 }) + .trigger('mousemove', { clientX: 2000, clientY: 800 }) + .trigger('mouseup', { force: true }); cy.get('button[data-test-subj="savePanelButton"]').click(); cy.wait(delay * 3); @@ -481,7 +480,7 @@ describe('Testing a panel', () => { cy.get('[data-test-subj="eventExplorer__saveManagementPopover"]').trigger('mouseover').click(); cy.wait(1000); cy.get('[data-test-subj="eventExplorer__querySaveName"]') - .clear({force: true}) + .clear({ force: true }) .type(NEW_VISUALIZATION_NAME, { delay: 200, }); @@ -537,7 +536,7 @@ describe('Clean up all test data', () => { const moveToEventsHome = () => { cy.visit(`${Cypress.env('opensearchDashboards')}/app/observability-logs#/`); - cy.wait(delay * 3); + cy.wait(6000); }; const moveToPanelHome = () => { @@ -620,7 +619,7 @@ 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 const clickCreatePanelButton = () => cy.get('a[data-test-subj="customPanels__createNewPanels"]').click(); -const createSavedObjectPanel = () => { +const createSavedObjectPanel = (newName = TEST_PANEL) => { const result = cy .request({ method: 'POST', @@ -632,7 +631,7 @@ const createSavedObjectPanel = () => { }, body: { attributes: { - title: TEST_PANEL, + title: newName, description: '', dateCreated: 1681127334085, dateModified: 1681127334085, @@ -652,7 +651,7 @@ const createSavedObjectPanel = () => { .then((response) => console.log(response)); }; -const createLegacyPanel = () => { +const createLegacyPanel = (newName = TEST_PANEL) => { const result = cy.request({ method: 'POST', failOnStatusCode: false, @@ -661,7 +660,7 @@ const createLegacyPanel = () => { 'content-type': 'application/json;charset=UTF-8', 'osd-xsrf': true, }, - body: { panelName: TEST_PANEL }, + body: { panelName: newName }, }); }; diff --git a/public/components/custom_panels/__tests__/__snapshots__/custom_panel_view.test.tsx.snap b/public/components/custom_panels/__tests__/__snapshots__/custom_panel_view.test.tsx.snap index 8bd0d4395..05379e0bc 100644 --- a/public/components/custom_panels/__tests__/__snapshots__/custom_panel_view.test.tsx.snap +++ b/public/components/custom_panels/__tests__/__snapshots__/custom_panel_view.test.tsx.snap @@ -1,697 +1,581 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Panels View Component renders panel view container with visualizations 1`] = ` - + -
- -
+
+ - -
- -
- -
- + +
+ +
-

- - -
- + + +
-
- -
- - Created on - Invalid date -
- - -
- +
+ +
+ + Created on + Invalid date +
+
+ +
-
- -
- + +
- - - - -
-
- -
- - Dashboard Actions - - } - closePopover={[Function]} - display="inlineBlock" - hasArrow={true} - isOpen={false} - ownFocus={true} - panelPaddingSize="none" - withTitle={true} - > -
-
- - - - - -
-
-
-
-
- -
+ + + + + Edit + + + + + + +
+
+ - - Add visualization + Dashboard Actions
} closePopover={[Function]} display="inlineBlock" hasArrow={true} - id="addVisualizationContextMenu" isOpen={false} ownFocus={true} panelPaddingSize="none" + withTitle={true} >
- -
-
-
- -
-
-

-
- -
- -
- -
- - PPL - - } - baseQuery="source = " - dslService={ - DSLService { - "fetch": [Function], - "fetchFields": [Function], - "fetchIndices": [Function], - "http": [MockFunction], - } - } - getSuggestions={[Function]} - handleQueryChange={[Function]} - handleQuerySearch={[Function]} - isDisabled={true} - key="autocomplete-search-bar" - onItemSelect={[Function]} - placeholder="Use PPL 'where' clauses to add filters on all visualizations [where Carrier = 'OpenSearch-Air']" - possibleCommands={ - Array [ - Object { - "label": "where", - }, - ] - } - query="" - tabId="panels-filter" - tempQuery="" - > -
+ + - - PPL - - } - aria-autocomplete="both" - aria-labelledby="autocomplete-4-label" - autoCapitalize="off" - autoComplete="off" - autoCorrect="off" - autoFocus={false} - data-test-subj="searchAutocompleteTextArea" - disabled={true} - enterKeyHint="search" - fullWidth={true} - id="autocomplete-textarea" - maxLength={512} - onBlur={[Function]} - onChange={[Function]} - onClick={[Function]} - onFocus={[Function]} - onKeyDown={[Function]} - placeholder="Use PPL 'where' clauses to add filters on all visualizations [where Carrier = 'OpenSearch-Air']" - spellCheck="false" - type="search" - value="" +
- - PPL - - } - fullWidth={true} - inputId="autocomplete-textarea" + -
+ Add visualization + + } + closePopover={[Function]} + display="inlineBlock" + hasArrow={true} + id="addVisualizationContextMenu" + isOpen={false} + ownFocus={true} + panelPaddingSize="none" >
- - - - -
- - - -
-
- -
- -
- - -
- + + + + +
+
+ + +
+ +
+ +
+
+
+
+ +
+ +
+ +
- + PPL + + } + baseQuery="source = " + dslService={ + DSLService { + "fetch": [Function], + "fetchFields": [Function], + "fetchIndices": [Function], + "http": [MockFunction], + } + } + getSuggestions={[Function]} + handleQueryChange={[Function]} + handleQuerySearch={[Function]} + isDisabled={true} + key="autocomplete-search-bar" + onItemSelect={[Function]} + placeholder="Use PPL 'where' clauses to add filters on all visualizations [where Carrier = 'OpenSearch-Air']" + possibleCommands={ + Array [ + Object { + "label": "where", + }, + ] + } + query="" + tabId="panels-filter" + tempQuery="" >
- -
- + + PPL + + } + aria-autocomplete="both" + aria-labelledby="autocomplete-4-label" + autoCapitalize="off" + autoComplete="off" + autoCorrect="off" + autoFocus={false} + data-test-subj="searchAutocompleteTextArea" + disabled={true} + enterKeyHint="search" + fullWidth={true} + id="autocomplete-textarea" + maxLength={512} + onBlur={[Function]} + onChange={[Function]} + onClick={[Function]} + onFocus={[Function]} + onKeyDown={[Function]} + placeholder="Use PPL 'where' clauses to add filters on all visualizations [where Carrier = 'OpenSearch-Air']" + spellCheck="false" + type="search" + value="" + > + + PPL + + } + fullWidth={true} + inputId="autocomplete-textarea" + > +
- + + + +
+ + + +
+
+
+
+ +
+ + +
+ + +
+ +
+ + } + > +
+ -
-
+ - - + + + +
-
- - -
- } - iconType={false} - isCustom={true} - startDateControl={
} + + +
-
} + iconType={false} + isCustom={true} + startDateControl={
} > - -
- - + + Show dates + + + +
+ + +
-
-
-
-
- -
+
+
+ - - - - - - - - - - - -
- -
-
- -
-
-
-
- -
- - -
- -
- - -
+ + + + + + +
+ +
+ + +
+ +
+ + +
+ + +
+ +
+ + - -
-

- Start by adding your first visualization -

- -
- - -
+ Start by adding your first visualization + + +
+ + - -
- Use PPL Queries to fetch & filter observability data and create visualizations -
-
-
- -
- -
- - -
- - -
+ Use PPL Queries to fetch & filter observability data and create visualizations +
+ +
+ +
+
+
+ + - + + +
-
- - - Add visualization - - } - closePopover={[Function]} - display="inlineBlock" - hasArrow={true} - id="addVisualizationContextMenu" - isOpen={false} - ownFocus={true} - panelPaddingSize="none" + -
-
+ - + } + closePopover={[Function]} + display="inlineBlock" + hasArrow={true} + id="addVisualizationContextMenu" + isOpen={false} + ownFocus={true} + panelPaddingSize="none" + > +
+
+ - - - + + + + +
-
- - -
- -
- - -
- -
- - + +
+ +
+ + +
+ +
+
+ - - - -
- - - - -
- -
- -
- -
- + maxRows={Infinity} + onDrag={[Function]} + onDragStart={[Function]} + onDragStop={[Function]} + onLayoutChange={[Function]} + onResize={[Function]} + onResizeStart={[Function]} + onResizeStop={[Function]} + preventCollision={false} + rowHeight={150} + style={Object {}} + useCSSTransforms={true} + verticalCompact={true} + width={0} + > +
+ + + + +
+ +
+ +
+
+
+
+ `; exports[`Panels View Component renders panel view container without visualizations 1`] = ` - + -
- -
+
+ - -
- -
- -
- + +
+ +
-

- - -
- + + +
-
- -
- - Created on - Invalid date -
- - -
- +
+ +
+ + Created on + Invalid date +
+
+ +
-
- -
- + +
- - - - -
-
- -
- - Dashboard Actions - - } - closePopover={[Function]} - display="inlineBlock" - hasArrow={true} - isOpen={false} - ownFocus={true} - panelPaddingSize="none" - withTitle={true} - > -
-
- - - - - -
-
-
-
-
- -
+ + + + Edit + + + + + + +
+
+ - - Add visualization + Dashboard Actions
} closePopover={[Function]} display="inlineBlock" hasArrow={true} - id="addVisualizationContextMenu" isOpen={false} ownFocus={true} panelPaddingSize="none" + withTitle={true} >
+ + +
+
+ +
+
+ +
+ + + Add visualization + + } + closePopover={[Function]} + display="inlineBlock" + hasArrow={true} + id="addVisualizationContextMenu" + isOpen={false} + ownFocus={true} + panelPaddingSize="none" + > +
+
+ + + - - + + + + +
-
- - -
- -
- -
- -

-
- -
- + +
+ +
+ +
+ + + + +
-
- -
- + +
+ + PPL + + } + baseQuery="source = " + dslService={ + DSLService { + "fetch": [Function], + "fetchFields": [Function], + "fetchIndices": [Function], + "http": [MockFunction], } - > - PPL - - } - baseQuery="source = " - dslService={ - DSLService { - "fetch": [Function], - "fetchFields": [Function], - "fetchIndices": [Function], - "http": [MockFunction], } - } - getSuggestions={[Function]} - handleQueryChange={[Function]} - handleQuerySearch={[Function]} - isDisabled={true} - key="autocomplete-search-bar" - onItemSelect={[Function]} - placeholder="Use PPL 'where' clauses to add filters on all visualizations [where Carrier = 'OpenSearch-Air']" - possibleCommands={ - Array [ - Object { - "label": "where", - }, - ] - } - query="" - tabId="panels-filter" - tempQuery="" - > -
- - PPL - - } - aria-autocomplete="both" +
- } + aria-autocomplete="both" + aria-labelledby="autocomplete-1-label" + autoCapitalize="off" + autoComplete="off" + autoCorrect="off" + autoFocus={false} + data-test-subj="searchAutocompleteTextArea" + disabled={true} + enterKeyHint="search" fullWidth={true} - inputId="autocomplete-textarea" + id="autocomplete-textarea" + maxLength={512} + onBlur={[Function]} + onChange={[Function]} + onClick={[Function]} + onFocus={[Function]} + onKeyDown={[Function]} + placeholder="Use PPL 'where' clauses to add filters on all visualizations [where Carrier = 'OpenSearch-Air']" + spellCheck="false" + type="search" + value="" > -
+ PPL + + } + fullWidth={true} + inputId="autocomplete-textarea" >
- - - - -
- -
+ - PPL - - -
- -
-
-
-
-
- -
+ PPL + + +
+ + +
+ +
+ + - - -
- -
- - } +
+ +
-
+ } > - - - - + -
-
+ - - + + + +
-
- - -
- } - iconType={false} - isCustom={true} - startDateControl={
} + + +
-
} + iconType={false} + isCustom={true} + startDateControl={
} > - -
- - + + Show dates + + + +
+ + +
-
- -
-
- -
+
+
+ - - - - - - - - - - - -
- -
- - -
-
-
- - -
- - -
- -
- - -
+ + + + + + +
+ +
+ + +
+ +
+ + +
+ + +
+ +
+ + - -
-

- Start by adding your first visualization -

- -
- - -
+ Start by adding your first visualization + + +
+ + - -
- Use PPL Queries to fetch & filter observability data and create visualizations -
-
-
- -
- -
- - -
- - -
+ Use PPL Queries to fetch & filter observability data and create visualizations +
+ +
+ +
+
+
+ + +
+ + - -
- - - Add visualization - - } - closePopover={[Function]} - display="inlineBlock" - hasArrow={true} - id="addVisualizationContextMenu" - isOpen={false} - ownFocus={true} - panelPaddingSize="none" + -
-
+ - + } + closePopover={[Function]} + display="inlineBlock" + hasArrow={true} + id="addVisualizationContextMenu" + isOpen={false} + ownFocus={true} + panelPaddingSize="none" + > +
+
+ - - - + + + + +
-
- - -
- -
-
- -
- -
- - + +
+ +
+ + +
+ +
+
+ - - - -
- - - - -
- -
-
-
- -
- + maxRows={Infinity} + onDrag={[Function]} + onDragStart={[Function]} + onDragStop={[Function]} + onLayoutChange={[Function]} + onResize={[Function]} + onResizeStart={[Function]} + onResizeStop={[Function]} + preventCollision={false} + rowHeight={150} + style={Object {}} + useCSSTransforms={true} + verticalCompact={true} + width={0} + > +
+ + + + +
+ +
+ + + + +
+ `; diff --git a/public/components/custom_panels/__tests__/custom_panel_view.test.tsx b/public/components/custom_panels/__tests__/custom_panel_view.test.tsx index 81cb151cd..b37601519 100644 --- a/public/components/custom_panels/__tests__/custom_panel_view.test.tsx +++ b/public/components/custom_panels/__tests__/custom_panel_view.test.tsx @@ -20,9 +20,13 @@ 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 { rootReducer } from '../../../framework/redux/reducers'; +import { Provider } from 'react-redux'; describe('Panels View Component', () => { configure({ adapter: new Adapter() }); + const store = createStore(rootReducer); it('renders panel view container without visualizations', async () => { httpClientMock.get = jest.fn(() => @@ -47,24 +51,26 @@ describe('Panels View Component', () => { }; const wrapper = mount( - + + + ); wrapper.update(); @@ -106,24 +112,26 @@ describe('Panels View Component', () => { }; const wrapper = mount( - + + + ); wrapper.update(); diff --git a/public/components/custom_panels/custom_panel_table.tsx b/public/components/custom_panels/custom_panel_table.tsx index c0914e0b7..cf4273de6 100644 --- a/public/components/custom_panels/custom_panel_table.tsx +++ b/public/components/custom_panels/custom_panel_table.tsx @@ -33,22 +33,26 @@ import React, { ReactElement, useEffect, useState } from 'react'; import moment from 'moment'; import _ from 'lodash'; import { useHistory, useLocation } from 'react-router-dom'; -import { coreRefs } from 'public/framework/core_refs'; import { useDispatch, useSelector } from 'react-redux'; +import { coreRefs } from '../../framework/core_refs'; import { ChromeBreadcrumb } from '../../../../../src/core/public'; import { CREATE_PANEL_MESSAGE, + CUSTOM_PANELS_API_PREFIX, CUSTOM_PANELS_DOCUMENTATION_URL, } from '../../../common/constants/custom_panels'; import { UI_DATE_FORMAT } from '../../../common/constants/shared'; import { getCustomModal } from './helpers/modal_containers'; -import { CustomPanelListType } from '../../../common/types/custom_panels'; +import { CustomPanelListType, CustomPanelType } from '../../../common/types/custom_panels'; 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, + isUuid, newPanelTemplate, renameCustomPanel, selectPanelList, @@ -86,11 +90,11 @@ export const CustomPanelTable = ({ deleteCustomPanelList, addSamplePanels, }: Props) => { - const customPanels = useSelector(selectPanelList); + const customPanels = useSelector(selectPanelList); const [isModalVisible, setIsModalVisible] = useState(false); // Modal Toggle const [modalLayout, setModalLayout] = useState(); // Modal Layout const [isActionsPopoverOpen, setIsActionsPopoverOpen] = useState(false); - const [selectedCustomPanels, setselectedCustomPanels] = useState([]); + const [selectedCustomPanels, setselectedCustomPanels] = useState([]); const [searchQuery, setSearchQuery] = useState(''); const location = useLocation(); const history = useHistory(); @@ -133,10 +137,26 @@ export const CustomPanelTable = ({ }; const onClone = async (newName: string) => { - const sourcePanel = selectedCustomPanels[0]; - const { id, ...newPanel } = { ...sourcePanel, title: sourcePanel.title + ' (copy)' }; + 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; + } - dispatch(createPanel(newPanel)); + const { id, ...newPanel } = { + ...sourcePanel, + title: newName, + }; + + dispatch(createPanel(newPanel)); + } catch (err) { + console.log(err); + } closeModal(); }; @@ -144,8 +164,17 @@ export const CustomPanelTable = ({ const toastMessage = `Observability Dashboards ${ selectedCustomPanels.length > 1 ? 's' : ' ' + selectedCustomPanels[0].title } successfully deleted!`; - const PanelList = selectedCustomPanels.map((panel) => panel.id); - deleteCustomPanelList(PanelList, toastMessage); + + 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); + } + closeModal(); }; @@ -184,7 +213,7 @@ export const CustomPanelTable = ({ showModal(); }; - const clonePanel = () => { + const clonePanelModal = () => { setModalLayout( getCustomModal( onClone, @@ -254,7 +283,7 @@ export const CustomPanelTable = ({ disabled={customPanels.length === 0 || selectedCustomPanels.length !== 1} onClick={() => { setIsActionsPopoverOpen(false); - clonePanel(); + clonePanelModal(); }} > Duplicate diff --git a/public/components/custom_panels/custom_panel_view.tsx b/public/components/custom_panels/custom_panel_view.tsx index 99075358b..2384d82ef 100644 --- a/public/components/custom_panels/custom_panel_view.tsx +++ b/public/components/custom_panels/custom_panel_view.tsx @@ -30,6 +30,7 @@ import React, { useEffect, useState } from 'react'; import { DurationRange } from '@elastic/eui/src/components/date_picker/types'; import moment from 'moment'; import _ from 'lodash'; +import { useDispatch } from 'react-redux'; import DSLService from '../../services/requests/dsl'; import { CoreStart } from '../../../../../src/core/public'; import { EmptyPanelView } from './panel_modules/empty_panel'; @@ -53,6 +54,7 @@ import { onTimeChange, isPPLFilterValid, fetchVisualizationById, + isNameValid, } from './helpers/utils'; import { UI_DATE_FORMAT } from '../../../common/constants/shared'; import { VisaulizationFlyout } from './panel_modules/visualization_flyout'; @@ -67,6 +69,7 @@ import { 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'; /* * "CustomPanelsView" module used to render an Observability Dashboard @@ -79,7 +82,6 @@ import { coreRefs } from '../../framework/core_refs'; * dslService: dsl requestor service * chrome: chrome core service * parentBreadcrumb: parent breadcrumb - * renameCustomPanel: Rename function for the panel * deleteCustomPanel: Delete function for the panel * cloneCustomPanel: Clone function for the panel * setToast: create Toast function @@ -101,8 +103,6 @@ interface CustomPanelViewProps { dslService: DSLService; chrome: CoreStart['chrome']; parentBreadcrumbs: EuiBreadcrumb[]; - renameCustomPanel: (editedCustomPanelName: string, editedCustomPanelId: string) => Promise; - deleteCustomPanel: (customPanelId: string, customPanelName: string) => Promise; cloneCustomPanel: (clonedCustomPanelName: string, clonedCustomPanelId: string) => Promise; setToast: ( title: string, @@ -137,8 +137,6 @@ export const CustomPanelView = (props: CustomPanelViewProps) => { setStartTime, setEndTime, updateAvailabilityVizId, - renameCustomPanel, - deleteCustomPanel, cloneCustomPanel, setToast, onEditClick, @@ -169,6 +167,8 @@ export const CustomPanelView = (props: CustomPanelViewProps) => { const appPanel = page === 'app'; + const dispatch = useDispatch(); + const closeHelpFlyout = () => { setAddVizDisabled(false); setHelpIsFlyoutVisible(false); @@ -200,6 +200,59 @@ export const CustomPanelView = (props: CustomPanelViewProps) => { }); }; + // Renames an existing CustomPanel + const renameCustomPanel = (editedCustomPanelName: string, editedCustomPanelId: string) => { + if (!isNameValid(editedCustomPanelName)) { + setToast('Invalid Custom Panel name', 'danger'); + return Promise.reject(); + } + const renamePanelObject = { + panelId: editedCustomPanelId, + panelName: editedCustomPanelName, + }; + + return http + .post(`${CUSTOM_PANELS_API_PREFIX}/panels/rename`, { + body: JSON.stringify(renamePanelObject), + }) + .then((res) => { + setOpenPanelName(editedCustomPanelName); + // setOpenPanelName((prevCustomPanelData) => { + // const newCustomPanelData = [...prevCustomPanelData]; + // const renamedCustomPanel = newCustomPanelData.find( + // (customPanel) => customPanel.id === editedCustomPanelId + // ); + // if (renamedCustomPanel) renamedCustomPanel.name = editedCustomPanelName; + // return newCustomPanelData; + // }); + setToast(`Operational Panel successfully renamed into "${editedCustomPanelName}"`); + }) + .catch((err) => { + setToast( + 'Error renaming Operational Panel, please make sure you have the correct permission.', + 'danger' + ); + console.error(err.body.message); + }); + }; + + // Deletes an existing Operational Panel + const deleteCustomPanel = (customPanelId: string, customPanelName: string) => { + return coreRefs + .http!.delete(`${CUSTOM_PANELS_API_PREFIX}/panels/` + customPanelId) + .then((res) => { + setToast(`Operational Panel "${customPanelName}" successfully deleted!`); + return res; + }) + .catch((err) => { + setToast( + 'Error deleting Operational Panel, please make sure you have the correct permission.', + 'danger' + ); + console.error(err.body.message); + }); + }; + const handleQueryChange = (newQuery: string) => { setPPLFilterValue(newQuery); }; @@ -265,22 +318,28 @@ export const CustomPanelView = (props: CustomPanelViewProps) => { }; const onClone = async (newCustomPanelName: string) => { - 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 - ); + try { + await dispatch(clonePanel(panel, newCustomPanelName)); + } catch (err) { + setToast('Error while attempting to Duplicate this Dashboard.', 'danger'); + } - window.location.assign(`${last(parentBreadcrumbs)!.href}${newSOPanel.id}`); + // 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(); }; - const clonePanel = () => { + const clonePanelModal = () => { setModalLayout( getCustomModal( onClone, @@ -518,7 +577,7 @@ export const CustomPanelView = (props: CustomPanelViewProps) => { 'data-test-subj': 'duplicatePanelContextMenuItem', onClick: () => { setPanelsMenuPopover(false); - clonePanel(); + clonePanelModal(); }, }, { diff --git a/public/components/custom_panels/custom_panel_view_so.tsx b/public/components/custom_panels/custom_panel_view_so.tsx index f741d79b4..52b19ea1a 100644 --- a/public/components/custom_panels/custom_panel_view_so.tsx +++ b/public/components/custom_panels/custom_panel_view_so.tsx @@ -53,7 +53,19 @@ import { import { EmptyPanelView } from './panel_modules/empty_panel'; import { PanelGridSO } from './panel_modules/panel_grid/panel_grid_so'; import { VisaulizationFlyoutSO } from './panel_modules/visualization_flyout/visualization_flyout_so'; -import { clonePanel, fetchPanel, selectPanel, setPanel, updatePanel } from './redux/panel_slice'; +import { + clonePanel, + createPanel, + deletePanels, + fetchPanel, + newPanelTemplate, + selectPanel, + setPanel, + setPanelEt, + setPanelId, + setPanelSt, + updatePanel, +} from './redux/panel_slice'; /* * "CustomPanelsView" module used to render an Observability Dashboard @@ -87,7 +99,6 @@ interface CustomPanelViewProps { coreSavedObjects: CoreStart['savedObjects']; chrome: CoreStart['chrome']; parentBreadcrumbs: EuiBreadcrumb[]; - deleteCustomPanel: (customPanelId: string, customPanelName: string) => Promise; cloneCustomPanel: (clonedCustomPanelName: string, clonedCustomPanelId: string) => Promise; setToast: ( title: string, @@ -115,7 +126,6 @@ export const CustomPanelViewSO = (props: CustomPanelViewProps) => { parentBreadcrumbs, childBreadcrumbs, updateAvailabilityVizId, - deleteCustomPanel, cloneCustomPanel, setToast, onEditClick, @@ -185,11 +195,20 @@ export const CustomPanelViewSO = (props: CustomPanelViewProps) => { }; const onDelete = async () => { - deleteCustomPanel(panelId, panel?.title).then((res) => { + const toastMessage = `Observability Dashboard ${panel.title} successfully deleted!"`; + try { + await dispatch(deletePanels([panel])); + setTimeout(() => { window.location.assign(`${last(parentBreadcrumbs)!.href}`); }, 1000); - }); + } catch (err) { + setToast( + 'Error deleting Operational Panels, please make sure you have the correct permission.', + 'danger' + ); + console.error(err.body?.message || err); + } closeModal(); }; @@ -205,6 +224,21 @@ export const CustomPanelViewSO = (props: CustomPanelViewProps) => { showModal(); }; + const onRename = async (newCustomPanelName: string) => { + const newPanel = { ...panel, title: newCustomPanelName }; + try { + dispatch(updatePanel(newPanel)); + setToast(`Operational Panel successfully renamed into "${newCustomPanelName}"`); + } catch (err) { + setToast( + 'Error renaming Operational Panel, please make sure you have the correct permission.', + 'danger' + ); + console.error(err.body.message); + } + closeModal(); + }; + const renamePanel = () => { setModalLayout( getCustomModal( diff --git a/public/components/custom_panels/home.tsx b/public/components/custom_panels/home.tsx index 14de98e91..c113307e1 100644 --- a/public/components/custom_panels/home.tsx +++ b/public/components/custom_panels/home.tsx @@ -28,7 +28,7 @@ import PPLService from '../../services/requests/ppl'; import { CustomPanelTable } from './custom_panel_table'; import { CustomPanelView } from './custom_panel_view'; import { CustomPanelViewSO } from './custom_panel_view_so'; -import { deletePanel, fetchPanels, uuidRx } from './redux/panel_slice'; +import { fetchPanels, uuidRx } from './redux/panel_slice'; // import { ObjectFetcher } from '../common/objectFetcher'; @@ -91,65 +91,6 @@ export const Home = ({ window.location.assign(`${observabilityLogsID}#/explorer/${savedVisualizationId}`); }; - const deletePanelSO = (customPanelIdList: string[]) => { - const soPanelIds = customPanelIdList.filter((id) => id.match(uuidRx)); - return Promise.all( - soPanelIds.map((id) => - coreRefs.savedObjectsClient?.delete(CUSTOM_PANELS_SAVED_OBJECT_TYPE, id) - ) - ); - }; - - const deletePanels = (customPanelIdList: string[]) => { - const panelIds = customPanelIdList.filter((id) => !id.match(uuidRx)); - const concatList = panelIds.toString(); - return http.delete(`${CUSTOM_PANELS_API_PREFIX}/panelList/` + concatList); - }; - - // Deletes multiple existing Operational Panels - const deleteCustomPanelList = (customPanelIdList: string[], toastMessage: string) => { - Promise.all([deletePanelSO(customPanelIdList), deletePanels(customPanelIdList)]) - .then((res) => { - // setcustomPanelData((prevCustomPanelData) => { - // return prevCustomPanelData.filter( - // (customPanel) => !customPanelIdList.includes(customPanel.id) - // ); - // }); - // setToast(toastMessage); - }) - .catch((err) => { - setToast( - 'Error deleting Operational Panels, please make sure you have the correct permission.', - 'danger' - ); - console.error(err.body.message); - }); - }; - - // Deletes an existing Observability Dashboard - const deleteCustomPanel = async (customPanelId: string, customPanelName: string) => { - return http - .delete(`${CUSTOM_PANELS_API_PREFIX}/panels/` + customPanelId) - .then((res) => { - dispatch(fetchPanels()); - setToast(`Observability Dashboard "${customPanelName}" successfully deleted!`); - return res; - }) - .catch((err) => { - setToast( - 'Error deleting Observability Dashboard, please make sure you have the correct permission.', - 'danger' - ); - console.error(err.body.message); - }); - }; - - // Deletes an existing SO Observability Dashboard - const deleteCustomPanelSO = async (customPanelId: string, customPanelName: string) => { - dispatch(deletePanel(customPanelId)); - // TODO: toast here - }; - const addSamplePanels = async () => { try { setLoading(true); @@ -222,7 +163,6 @@ export const Home = ({ loading={loading} setBreadcrumbs={chrome.setBreadcrumbs} parentBreadcrumbs={customPanelBreadCrumbs} - deleteCustomPanelList={deleteCustomPanelList} addSamplePanels={addSamplePanels} /> ); @@ -240,7 +180,6 @@ export const Home = ({ pplService={pplService} dslService={dslService} parentBreadcrumbs={customPanelBreadCrumbs} - deleteCustomPanel={deleteCustomPanel} setToast={setToast} onEditClick={onEditClick} page="operationalPanels" @@ -255,7 +194,6 @@ export const Home = ({ chrome={chrome} parentBreadcrumbs={customPanelBreadCrumbs} // renameCustomPanel={renameCustomPanel} - deleteCustomPanel={deleteCustomPanel} setToast={setToast} onEditClick={onEditClick} startTime={start} diff --git a/public/components/custom_panels/redux/panel_slice.ts b/public/components/custom_panels/redux/panel_slice.ts index a68c27155..d13909af1 100644 --- a/public/components/custom_panels/redux/panel_slice.ts +++ b/public/components/custom_panels/redux/panel_slice.ts @@ -28,7 +28,7 @@ interface InitialState { panelList: CustomPanelType[]; } -export const newPanelTemplate = (newName) => ({ +export const newPanelTemplate = (newName): PanelType => ({ title: newName, dateCreated: new Date().getTime(), dateModified: new Date().getTime(), @@ -69,18 +69,13 @@ export const selectPanel = createSelector( (panel) => normalizedPanel(panel) ); -const normalizedPanel = (panel): PanelType => ({ +const normalizedPanel = (panel: CustomPanelType): CustomPanelType => ({ ...newPanelTemplate(''), ...panel, }); export const selectPanelList = (rootState): CustomPanelType[] => rootState.customPanel.panelList; -// export const selectPanelList = createSelector( -// rootState => { console.log("selectPanelList", { rootState }); return rootState.customPanel.panelList }, -// panelList => panelList.map(p => p as CustomPanelListType) -// ); - /* ** ASYNC DISPATCH FUNCTIONS */ @@ -134,7 +129,7 @@ const updateSavedObjectPanel = (panel: CustomPanelType) => savedObjectPanelsClie export 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 isUuid = (id) => !!id.match(uuidRx); +export const isUuid = (id) => !!id.match(uuidRx); export const updatePanel = (panel: CustomPanelType) => async (dispatch, getState) => { try { @@ -198,9 +193,26 @@ export const replaceVizInPanel = (oldPanel, oldVizId, vizId) => async (dispatch, } }; -export const deletePanel = (id) => async (dispatch, getState) => { - await savedObjectPanelsClient.delete(id); - const panelList: CustomPanelType[] = getState().panelList.filter((p) => p.id !== id); +const deletePanelSO = (customPanelIdList: string[]) => { + const soPanelIds = customPanelIdList.filter((id) => isUuid(id)); + return Promise.all(soPanelIds.map((id) => savedObjectPanelsClient.delete(id))); +}; + +const deleteLegacyPanels = (customPanelIdList: string[]) => { + const panelIds = customPanelIdList.filter((id) => !isUuid(id)); + if (panelIds.length === 0) return; + + const concatList = panelIds.toString(); + return coreRefs.http!.delete(`${CUSTOM_PANELS_API_PREFIX}/panelList/` + concatList); +}; + +export const deletePanels = (panelsToDelete: CustomPanelType[]) => async (dispatch, getState) => { + const ids = panelsToDelete.map((p) => p.id); + await Promise.all([deleteLegacyPanels(ids), deletePanelSO(ids)]); + + const panelList: CustomPanelType[] = getState().customPanel.panelList.filter( + (p) => !ids.includes(p.id) + ); dispatch(setPanelList(panelList)); }; diff --git a/public/components/event_analytics/home/home.tsx b/public/components/event_analytics/home/home.tsx index 2d9aa1b87..e23d242f7 100644 --- a/public/components/event_analytics/home/home.tsx +++ b/public/components/event_analytics/home/home.tsx @@ -356,7 +356,7 @@ const EventAnalyticsHome = (props: IHomeProps) => { -

Event analytics

+

Logs