From 718d96370e889d27f299f715868674aa0e394827 Mon Sep 17 00:00:00 2001 From: Shenoy Pratik Date: Wed, 20 Oct 2021 14:30:09 -0700 Subject: [PATCH 1/5] modified common files --- .../common/constants/custom_panels.ts | 23 ------------------- .../common/types/custom_panels.ts | 5 +--- 2 files changed, 1 insertion(+), 27 deletions(-) diff --git a/dashboards-observability/common/constants/custom_panels.ts b/dashboards-observability/common/constants/custom_panels.ts index ae72d893d..d8db8a5fe 100644 --- a/dashboards-observability/common/constants/custom_panels.ts +++ b/dashboards-observability/common/constants/custom_panels.ts @@ -12,26 +12,3 @@ export const CUSTOM_PANELS_API_PREFIX = '/api/observability/operational_panels'; export const CUSTOM_PANELS_DOCUMENTATION_URL = 'https://www.opensearch.org'; export const CREATE_PANEL_MESSAGE = 'Enter a name to describe the purpose of this custom panel.'; -export const RENAME_VISUALIZATION_MESSAGE = - 'Enter a name to describe the purpose of this visualization.'; - -export type VisualizationType = { - id: string; - title: string; - x: number; - y: number; - w: number; - h: number; - query: string; - type: string; -}; - -export type PanelType = { - name: string; - dateCreated: string; - dateModified: string; - visualizations: VisualizationType[]; - timeRange: { to: string; from: string }; - queryFilter: { query: string; language: string }; - refreshConfig: { pause: string; value: string }; -}; diff --git a/dashboards-observability/common/types/custom_panels.ts b/dashboards-observability/common/types/custom_panels.ts index 4d8f62145..36449130c 100644 --- a/dashboards-observability/common/types/custom_panels.ts +++ b/dashboards-observability/common/types/custom_panels.ts @@ -18,14 +18,11 @@ export type CustomPanelListType = { export type VisualizationType = { id: string; - title: string; + savedVisualizationId: string; x: number; y: number; w: number; h: number; - query: string; - type: string; - timeField: string; }; export type PanelType = { From 00ec424ca05c6b5551bf5b4791d2027d528d2127 Mon Sep 17 00:00:00 2001 From: Shenoy Pratik Date: Wed, 20 Oct 2021 14:31:38 -0700 Subject: [PATCH 2/5] modified UI components --- .../custom_panels/custom_panel_table.tsx | 3 +- .../custom_panels/custom_panel_view.tsx | 69 +----------- .../custom_panels/helpers/utils.tsx | 100 +++++++++++++++++- .../public/components/custom_panels/home.tsx | 2 + .../panel_modules/panel_grid/panel_grid.tsx | 48 ++++++--- .../visualization_container.tsx | 46 ++++---- .../visualization_flyout.tsx | 17 +-- 7 files changed, 165 insertions(+), 120 deletions(-) diff --git a/dashboards-observability/public/components/custom_panels/custom_panel_table.tsx b/dashboards-observability/public/components/custom_panels/custom_panel_table.tsx index 8c33c5005..0579236ba 100644 --- a/dashboards-observability/public/components/custom_panels/custom_panel_table.tsx +++ b/dashboards-observability/public/components/custom_panels/custom_panel_table.tsx @@ -36,7 +36,6 @@ import { } from '@elastic/eui'; import React, { CSSProperties, ReactElement, useEffect, useState } from 'react'; import { ChromeBreadcrumb } from '../../../../../src/core/public'; -import { CustomPanelListType } from './home'; import { CREATE_PANEL_MESSAGE, CUSTOM_PANELS_DOCUMENTATION_URL, @@ -45,6 +44,7 @@ import { UI_DATE_FORMAT } from '../../../common/constants/shared'; import { getCustomModal, DeletePanelModal } from './helpers/modal_containers'; import moment from 'moment'; import _ from 'lodash'; +import { CustomPanelListType } from '../../../common/types/custom_panels'; const pageStyles: CSSProperties = { float: 'left', @@ -65,7 +65,6 @@ const pageStyles: CSSProperties = { * renameCustomPanel: rename function for the panel * cloneCustomPanel: clone function for the panel * deleteCustomPanelList: delete function for the panels - * setToast: create Toast function */ type Props = { diff --git a/dashboards-observability/public/components/custom_panels/custom_panel_view.tsx b/dashboards-observability/public/components/custom_panels/custom_panel_view.tsx index d5373dc63..974bed04b 100644 --- a/dashboards-observability/public/components/custom_panels/custom_panel_view.tsx +++ b/dashboards-observability/public/components/custom_panels/custom_panel_view.tsx @@ -27,7 +27,6 @@ import { EuiSuperDatePicker, EuiSuperDatePickerProps, EuiTitle, - htmlIdGenerator, ShortDate, } from '@elastic/eui'; import _ from 'lodash'; @@ -35,7 +34,6 @@ import React, { useEffect, useState } from 'react'; import { CoreStart } from '../../../../../src/core/public'; import { EmptyPanelView } from './panel_modules/empty_panel'; import { - RENAME_VISUALIZATION_MESSAGE, CREATE_PANEL_MESSAGE, CUSTOM_PANELS_API_PREFIX, } from '../../../common/constants/custom_panels'; @@ -292,79 +290,22 @@ export const CustomPanelView = ({ }); }; - const cloneVisualization = ( - newVisualizationTitle: string, - pplQuery: string, - newVisualizationType: string, - newVisualizationTimeField: string - ) => { - setModalLayout( - getCustomModal( - onCloneVisualization, - closeModal, - 'Name', - 'Duplicate Visualization', - 'Cancel', - 'Duplicate', - newVisualizationTitle + ' (copy)', - RENAME_VISUALIZATION_MESSAGE, - [pplQuery, newVisualizationType, newVisualizationTimeField] - ) - ); - showModal(); - }; - - const onCloneVisualization = ( - newVisualizationTitle: string, - pplQuery: string, - newVisualizationType: string, - newVisualizationTimeField: string - ) => { + const cloneVisualization = (visualzationTitle: string, savedVisualizationId: string) => { http .post(`${CUSTOM_PANELS_API_PREFIX}/visualizations`, { body: JSON.stringify({ panelId: panelId, - newVisualization: { - id: 'panelViz_' + htmlIdGenerator()(), - title: newVisualizationTitle, - query: pplQuery, - type: newVisualizationType, - timeField: newVisualizationTimeField, - }, + savedVisualizationId: savedVisualizationId, }), }) .then(async (res) => { setPanelVisualizations(res.visualizations); - setToast(`Visualization ${newVisualizationTitle} successfully added!`, 'success'); + setToast(`Visualization ${visualzationTitle} successfully added!`, 'success'); }) .catch((err) => { - setToast(`Error in adding ${newVisualizationTitle} visualization to the panel`, 'danger'); + setToast(`Error in adding ${visualzationTitle} visualization to the panel`, 'danger'); console.error(err); }); - closeModal(); - }; - - const removeVisualization = (visualizationId: string) => { - const newVisualizationList = _.reject(panelVisualizations, { - id: visualizationId, - }); - if (newVisualizationList.length === 0) { - setEditMode(false); - http - .put(`${CUSTOM_PANELS_API_PREFIX}/visualizations/edit`, { - body: JSON.stringify({ - panelId: panelId, - visualizationParams: [], - }), - }) - .then(async (res) => { - setPanelVisualizations(res.visualizations); - }) - .catch((err) => { - console.error(err); - }); - } - setPanelVisualizations(newVisualizationList); }; //Add Visualization Button @@ -533,7 +474,7 @@ export const CustomPanelView = ({ cloneVisualization={cloneVisualization} pplFilterValue={pplFilterValue} showFlyout={showFlyout} - removeVisualization={removeVisualization} + setEditMode={setEditMode} /> )} <> diff --git a/dashboards-observability/public/components/custom_panels/helpers/utils.tsx b/dashboards-observability/public/components/custom_panels/helpers/utils.tsx index 5ebbcfbc3..50c206f7c 100644 --- a/dashboards-observability/public/components/custom_panels/helpers/utils.tsx +++ b/dashboards-observability/public/components/custom_panels/helpers/utils.tsx @@ -20,16 +20,23 @@ import React from 'react'; import { Bar } from '../../visualizations/charts/bar'; import { HorizontalBar } from '../../visualizations/charts/horizontal_bar'; import { Line } from '../../visualizations/charts/line'; +import { CoreStart } from '../../../../../../src/core/public'; +import { CUSTOM_PANELS_API_PREFIX } from '../../../../common/constants/custom_panels'; +import { VisualizationType, SavedVisualizationType } from '../../../../common/types/custom_panels'; +import { Layout } from 'react-grid-layout'; /* * "Utils" This file contains different reused functions in operational panels + * * isNameValid - Validates string to length > 0 and < 50 * convertDateTime - Converts input datetime string to required format + * mergeLayoutAndVisualizations - Function to merge current panel layout into the visualizations list * getQueryResponse - Get response of PPL query to load visualizations + * renderSavedVisualization - Fetches savedVisualization by Id and runs getQueryResponse * onTimeChange - Function to store recently used time filters and set start and end time. * isDateValid - Function to check date validity * isPPLFilterValid - Validate if the panel PPL query doesn't contain any Index/Time/Field filters - * displayVisualization - This function renders the visualzation based of its type + * displayVisualization - Function to render the visualzation based of its type */ // Name validation 0>Name<=50 @@ -50,6 +57,30 @@ export const convertDateTime = (datetime: string, isStart = true, formatted = tr return returnTime; }; +// Merges new layout into visualizations +export const mergeLayoutAndVisualizations = ( + layout: Layout[], + newVisualizationList: VisualizationType[], + setPanelVisualizations: (value: React.SetStateAction) => void +) => { + let newPanelVisualizations: VisualizationType[] = []; + + for (var i = 0; i < newVisualizationList.length; i++) { + for (var j = 0; j < layout.length; j++) { + if (newVisualizationList[i].id == layout[j].i) { + newPanelVisualizations.push({ + ...newVisualizationList[i], + x: layout[j].x, + y: layout[j].y, + w: layout[j].w, + h: layout[j].h, + }); + } + } + } + setPanelVisualizations(newPanelVisualizations); +}; + /* Builds Final Query by adding time and query filters(From panel UI) to the original visualization query * -> Final Query is as follows: * -> finalQuery = indexPartOfQuery + timeQueryFilter + panelFilterQuery + filterPartOfQuery @@ -102,6 +133,27 @@ const pplServiceRequestor = async ( }); }; +//Fetched Saved Visualization By Id +const fetchVisualizationById = async ( + http: CoreStart['http'], + savedVisualizationId: string, + setIsError: React.Dispatch> +) => { + let savedVisualization = {} as SavedVisualizationType; + await http + .get(`${CUSTOM_PANELS_API_PREFIX}/visualizations/${savedVisualizationId}`) + .then((res) => { + savedVisualization = res.visualization; + }) + .catch((err) => { + const errorMessage = 'Issue in fetching the saved Visualization by Id'; + setIsError(errorMessage); + console.error(errorMessage, err); + }); + + return savedVisualization; +}; + // Get PPL Query Response export const getQueryResponse = ( pplService: PPLService, @@ -122,7 +174,9 @@ export const getQueryResponse = ( try { finalQuery = queryAccumulator(query, timestampField, startTime, endTime, filterQuery); } catch (error) { - console.error('Issue in building final query', error.stack); + const errorMessage = 'Issue in building final query'; + setIsError(errorMessage); + console.error(errorMessage, error); setIsLoading(false); return; } @@ -130,6 +184,48 @@ export const getQueryResponse = ( pplServiceRequestor(pplService, finalQuery, type, setVisualizationData, setIsLoading, setIsError); }; +// Fetches savedVisualization by Id and runs getQueryResponse +export const renderSavedVisualization = async ( + http: CoreStart['http'], + pplService: PPLService, + savedVisualizationId: string, + startTime: string, + endTime: string, + filterQuery: string, + setVisualizationTitle: React.Dispatch>, + setVisualizationType: React.Dispatch>, + setVisualizationData: React.Dispatch>, + setIsLoading: React.Dispatch>, + setIsError: React.Dispatch> +) => { + setIsLoading(true); + setIsError(''); + + let visualization = {} as SavedVisualizationType; + visualization = await fetchVisualizationById(http, savedVisualizationId, setIsError); + + if (visualization.name) { + setVisualizationTitle(visualization.name); + } + + if (visualization.type) { + setVisualizationType(visualization.type); + } + + getQueryResponse( + pplService, + visualization.query, + visualization.type, + startTime, + endTime, + setVisualizationData, + setIsLoading, + setIsError, + filterQuery, + visualization.timeField + ); +}; + // Function to store recently used time filters and set start and end time. export const onTimeChange = ( start: ShortDate, diff --git a/dashboards-observability/public/components/custom_panels/home.tsx b/dashboards-observability/public/components/custom_panels/home.tsx index 4742b0cd6..328ae33ef 100644 --- a/dashboards-observability/public/components/custom_panels/home.tsx +++ b/dashboards-observability/public/components/custom_panels/home.tsx @@ -59,10 +59,12 @@ export const Home = ({ http, chrome, parentBreadcrumb, pplService, renderProps } // Fetches all saved Custom Panels const fetchCustomPanels = () => { + setLoading(true); return http .get(`${CUSTOM_PANELS_API_PREFIX}/panels`) .then((res) => { setcustomPanelData(res.panels); + setLoading(false); }) .catch((err) => { console.error('Issue in fetching the operational panels', err.body.message); diff --git a/dashboards-observability/public/components/custom_panels/panel_modules/panel_grid/panel_grid.tsx b/dashboards-observability/public/components/custom_panels/panel_modules/panel_grid/panel_grid.tsx index 039f9f4e3..beab4e7c1 100644 --- a/dashboards-observability/public/components/custom_panels/panel_modules/panel_grid/panel_grid.tsx +++ b/dashboards-observability/public/components/custom_panels/panel_modules/panel_grid/panel_grid.tsx @@ -19,6 +19,7 @@ import { VisualizationContainer } from '../visualiation_container'; import { VisualizationType } from '../../../../../common/types/custom_panels'; import { CUSTOM_PANELS_API_PREFIX } from '../../../../../common/constants/custom_panels'; import './panel_grid.scss'; +import { mergeLayoutAndVisualizations } from '../../helpers/utils'; // HOC container to provide dynamic width for Grid layout const ResponsiveGridLayout = WidthProvider(Responsive); @@ -29,6 +30,7 @@ const ResponsiveGridLayout = WidthProvider(Responsive); * Props taken in as params are: * http: http core service; * chrome: chrome core service; + * panelId: OpenPanel Id * panelVisualizations: list of panel visualizations * setPanelVisualizations: function to set panel visualizations * editMode: boolean to check if the panel is in edit mode @@ -39,7 +41,7 @@ const ResponsiveGridLayout = WidthProvider(Responsive); * cloneVisualization: function to clone a visualization in panel * pplFilterValue: string with panel PPL filter value * showFlyout: function to show the flyout - * removeVisualization: function to remove all the visualizations + * setEditMode: function to set edit mode in panel */ type Props = { @@ -53,15 +55,10 @@ type Props = { startTime: string; endTime: string; onRefresh: boolean; - cloneVisualization: ( - newVisualizationTitle: string, - pplQuery: string, - newVisualizationType: string, - newVisualizationTimeField: string - ) => void; + cloneVisualization: (visualzationTitle: string, savedVisualizationId: string) => void; pplFilterValue: string; showFlyout: (isReplacement?: boolean | undefined, replaceVizId?: string | undefined) => void; - removeVisualization: (visualizationId: string) => void; + setEditMode: React.Dispatch>; }; export const PanelGrid = ({ @@ -78,7 +75,7 @@ export const PanelGrid = ({ cloneVisualization, pplFilterValue, showFlyout, - removeVisualization, + setEditMode, }: Props) => { const [layout, setLayout] = useState([]); const [editedLayout, setEditedLayout] = useState([]); @@ -105,6 +102,33 @@ export const PanelGrid = ({ setLayout(tempLayout); }; + // remove visualization from panel in edit mode + const removeVisualization = (visualizationId: string) => { + const newVisualizationList = _.reject(panelVisualizations, { + id: visualizationId, + }); + + if (newVisualizationList.length === 0) { + setEditMode(false); + http + .put(`${CUSTOM_PANELS_API_PREFIX}/visualizations/edit`, { + body: JSON.stringify({ + panelId: panelId, + visualizationParams: [], + }), + }) + .then(async (res) => { + setPanelVisualizations(res.visualizations); + return; + }) + .catch((err) => { + console.error(err); + }); + } + mergeLayoutAndVisualizations(editedLayout, newVisualizationList, setPanelVisualizations); + }; + + // Save Visualization Layouts when not in edit mode anymore (after users saves the panel) const saveVisualizationLayouts = async (panelId: string, visualizationParams: any) => { return http .put(`${CUSTOM_PANELS_API_PREFIX}/visualizations/edit`, { @@ -158,12 +182,10 @@ export const PanelGrid = ({ {panelVisualizations.map((panelVisualization: VisualizationType) => (
void; + cloneVisualization: (visualzationTitle: string, savedVisualizationId: string) => void; pplFilterValue: string; showFlyout: (isReplacement?: boolean | undefined, replaceVizId?: string | undefined) => void; removeVisualization: (visualizationId: string) => void; }; export const VisualizationContainer = ({ + http, editMode, visualizationId, - visualizationTitle, + savedVisualizationId, pplService, - query, - type, - timeField, fromTime, toTime, onRefresh, @@ -86,6 +81,8 @@ export const VisualizationContainer = ({ }: Props) => { const [isPopoverOpen, setIsPopoverOpen] = useState(false); const [disablePopover, setDisablePopover] = useState(false); + const [visualizationTitle, setVisualizationTitle] = useState(''); + const [visualizationType, setVisualizationType] = useState(''); const [visualizationData, setVisualizationData] = useState([]); const [isLoading, setIsLoading] = useState(true); const [isError, setIsError] = useState(''); @@ -108,7 +105,7 @@ export const VisualizationContainer = ({ disabled={disablePopover} onClick={() => { closeActionsMenu(); - cloneVisualization(visualizationTitle, query, type, timeField); + cloneVisualization(visualizationTitle, savedVisualizationId); }} > Duplicate @@ -116,17 +113,18 @@ export const VisualizationContainer = ({ ]; const loadVisaulization = async () => { - await getQueryResponse( + await renderSavedVisualization( + http, pplService, - query, - type, + savedVisualizationId, fromTime, toTime, + pplFilterValue, + setVisualizationTitle, + setVisualizationType, setVisualizationData, setIsLoading, - setIsError, - pplFilterValue, - timeField + setIsError ); }; @@ -149,11 +147,11 @@ export const VisualizationContainer = ({
) : ( - displayVisualization(visualizationData, type) + displayVisualization(visualizationData, visualizationType) )} ), - [onRefresh, isLoading, isError, visualizationData, type] + [onRefresh, isLoading, isError, visualizationData, visualizationType] ); useEffect(() => { diff --git a/dashboards-observability/public/components/custom_panels/panel_modules/visualization_flyout/visualization_flyout.tsx b/dashboards-observability/public/components/custom_panels/panel_modules/visualization_flyout/visualization_flyout.tsx index 4b90d439b..75f249133 100644 --- a/dashboards-observability/public/components/custom_panels/panel_modules/visualization_flyout/visualization_flyout.tsx +++ b/dashboards-observability/public/components/custom_panels/panel_modules/visualization_flyout/visualization_flyout.tsx @@ -29,7 +29,6 @@ import { EuiSpacer, EuiText, EuiTitle, - htmlIdGenerator, ShortDate, } from '@elastic/eui'; import _ from 'lodash'; @@ -143,14 +142,8 @@ export const VisaulizationFlyout = ({ .post(`${CUSTOM_PANELS_API_PREFIX}/visualizations/replace`, { body: JSON.stringify({ panelId: panelId, + savedVisualizationId: selectValue, oldVisualizationId: replaceVisualizationId, - newVisualization: { - id: 'panelViz_' + htmlIdGenerator()(), - title: newVisualizationTitle, - query: pplQuery, - type: newVisualizationType, - timeField: newVisualizationTimeField, - }, }), }) .then(async (res) => { @@ -166,13 +159,7 @@ export const VisaulizationFlyout = ({ .post(`${CUSTOM_PANELS_API_PREFIX}/visualizations`, { body: JSON.stringify({ panelId: panelId, - newVisualization: { - id: 'panelViz_' + htmlIdGenerator()(), - title: newVisualizationTitle, - query: pplQuery, - type: newVisualizationType, - timeField: newVisualizationTimeField, - }, + savedVisualizationId: selectValue, }), }) .then(async (res) => { From 14424b0a4cb68100b5f2c9bcb229daaa796d0ad1 Mon Sep 17 00:00:00 2001 From: Shenoy Pratik Date: Wed, 20 Oct 2021 14:33:41 -0700 Subject: [PATCH 3/5] modified panel adaptor and router --- .../custom_panels/custom_panel_adaptor.ts | 89 ++++++---------- .../custom_panels/visualizations_router.ts | 100 +++--------------- 2 files changed, 48 insertions(+), 141 deletions(-) diff --git a/dashboards-observability/server/adaptors/custom_panels/custom_panel_adaptor.ts b/dashboards-observability/server/adaptors/custom_panels/custom_panel_adaptor.ts index f5992c712..13052e93c 100644 --- a/dashboards-observability/server/adaptors/custom_panels/custom_panel_adaptor.ts +++ b/dashboards-observability/server/adaptors/custom_panels/custom_panel_adaptor.ts @@ -9,6 +9,7 @@ * GitHub history for details. */ +import { v4 as uuidv4 } from 'uuid'; import { PanelType, VisualizationType } from '../../../common/types/custom_panels'; import { ILegacyScopedClusterClient } from '../../../../../src/core/server'; @@ -190,8 +191,8 @@ export class CustomPanelsAdaptor { } }; - // gets list of panels stored in index - viewSavedVisualiationList = async (client: ILegacyScopedClusterClient) => { + // gets all saved visualizations + getAllSavedVisualizations = async (client: ILegacyScopedClusterClient) => { try { const response = await client.callAsCurrentUser('observability.getObject', { objectType: 'savedVisualization', @@ -208,6 +209,28 @@ export class CustomPanelsAdaptor { } }; + // gets list of savedVisualizations by Id + getSavedVisualizationById = async ( + client: ILegacyScopedClusterClient, + savedVisualizationId: string + ) => { + try { + const response = await client.callAsCurrentUser('observability.getObjectById', { + objectId: savedVisualizationId, + }); + const visualization = response.observabilityObjectList[0]; + return { + id: visualization.objectId, + name: visualization.savedVisualization.name, + query: visualization.savedVisualization.query, + type: visualization.savedVisualization.type, + timeField: visualization.savedVisualization.selected_timestamp.name, + }; + } catch (error) { + throw new Error('Fetch Saved Visualizations By Id Error:' + error); + } + }; + //Get All Visualizations from a Panel //Add Visualization getVisualizations = async (client: ILegacyScopedClusterClient, panelId: string) => { @@ -241,13 +264,7 @@ export class CustomPanelsAdaptor { addVisualization = async ( client: ILegacyScopedClusterClient, panelId: string, - newVisualization: { - id: string; - title: string; - query: string; - type: string; - timeField: string; - }, + savedVisualizationId: string, oldVisualizationId?: string ) => { try { @@ -274,35 +291,11 @@ export class CustomPanelsAdaptor { } const newPanelVisualizations = [ ...visualizationsList, - { ...newVisualization, ...newDimensions }, - ]; - const updatePanelResponse = await this.updatePanel(client, panelId, { - visualizations: newPanelVisualizations, - }); - return newPanelVisualizations; - } catch (error) { - throw new Error('Add/Replace Visualization Error:' + error); - } - }; - - //Add Visualization in the Panel from Event Explorer - addVisualizationFromEvents = async ( - client: ILegacyScopedClusterClient, - panelId: string, - paramVisualization: { - id: string; - title: string; - query: string; - type: string; - timeField: string; - } - ) => { - try { - const allPanelVisualizations = await this.getVisualizations(client, panelId); - const newDimensions = this.getNewVizDimensions(allPanelVisualizations); - const newPanelVisualizations = [ - ...allPanelVisualizations, - { ...paramVisualization, ...newDimensions }, + { + id: 'panel_viz_' + uuidv4(), + savedVisualizationId: savedVisualizationId, + ...newDimensions, + }, ]; const updatePanelResponse = await this.updatePanel(client, panelId, { visualizations: newPanelVisualizations, @@ -313,26 +306,6 @@ export class CustomPanelsAdaptor { } }; - //Delete a Visualization in the Panel - deleteVisualization = async ( - client: ILegacyScopedClusterClient, - panelId: string, - visualizationId: string - ) => { - try { - const allPanelVisualizations = await this.getVisualizations(client, panelId); - const filteredPanelVisualizations = allPanelVisualizations.filter( - (panelVisualization: VisualizationType) => panelVisualization.id != visualizationId - ); - const updatePanelResponse = await this.updatePanel(client, panelId, { - visualizations: filteredPanelVisualizations, - }); - return filteredPanelVisualizations; - } catch (error) { - throw new Error('Delete Visualization Error:' + error); - } - }; - //Edits all Visualizations in the Panel editVisualization = async ( client: ILegacyScopedClusterClient, diff --git a/dashboards-observability/server/routes/custom_panels/visualizations_router.ts b/dashboards-observability/server/routes/custom_panels/visualizations_router.ts index c935b9d0c..584d3f30c 100644 --- a/dashboards-observability/server/routes/custom_panels/visualizations_router.ts +++ b/dashboards-observability/server/routes/custom_panels/visualizations_router.ts @@ -36,7 +36,7 @@ export function VisualizationsRouter(router: IRouter) { request ); try { - const visualizationList = await customPanelBackend.viewSavedVisualiationList( + const visualizationList = await customPanelBackend.getAllSavedVisualizations( opensearchNotebooksClient ); return response.ok({ @@ -54,20 +54,13 @@ export function VisualizationsRouter(router: IRouter) { } ); - // Add a new visualization to the panel - router.post( + // get all saved visualizations by Id + router.get( { - path: `${API_PREFIX}/visualizations`, + path: `${API_PREFIX}/visualizations/{savedVisualizationId}`, validate: { - body: schema.object({ - panelId: schema.string(), - newVisualization: schema.object({ - id: schema.string(), - title: schema.string(), - query: schema.string(), - type: schema.string(), - timeField: schema.string(), - }), + params: schema.object({ + savedVisualizationId: schema.string(), }), }, }, @@ -79,21 +72,18 @@ export function VisualizationsRouter(router: IRouter) { const opensearchNotebooksClient: ILegacyScopedClusterClient = context.observability_plugin.observabilityClient.asScoped( request ); - try { - const newVisualizations = await customPanelBackend.addVisualization( + const savedVisualization = await customPanelBackend.getSavedVisualizationById( opensearchNotebooksClient, - request.body.panelId, - request.body.newVisualization + request.params.savedVisualizationId ); return response.ok({ body: { - message: 'Visualization Added', - visualizations: newVisualizations, + visualization: savedVisualization, }, }); } catch (error) { - console.error('Issue in adding visualization:', error); + console.error('Issue in fetching saved visualizations by ids:', error); return response.custom({ statusCode: error.statusCode || 500, body: error.message, @@ -102,22 +92,14 @@ export function VisualizationsRouter(router: IRouter) { } ); - // Add a new visualization to panel from event explorer - // NOTE: This is a separate endpoint for adding event explorer visualizations to Operational Panels - // Please use `id = 'panelViz_' + htmlIdGenerator()()` to create unique visualization Id + // Add a new visualization to the panel router.post( { - path: `${API_PREFIX}/visualizations/event_explorer`, + path: `${API_PREFIX}/visualizations`, validate: { body: schema.object({ panelId: schema.string(), - newVisualization: schema.object({ - id: schema.string(), - title: schema.string(), - query: schema.string(), - type: schema.string(), - timeField: schema.string(), - }), + savedVisualizationId: schema.string(), }), }, }, @@ -131,10 +113,10 @@ export function VisualizationsRouter(router: IRouter) { ); try { - const newVisualizations = await customPanelBackend.addVisualizationFromEvents( + const newVisualizations = await customPanelBackend.addVisualization( opensearchNotebooksClient, request.body.panelId, - request.body.newVisualization + request.body.savedVisualizationId ); return response.ok({ body: { @@ -159,14 +141,8 @@ export function VisualizationsRouter(router: IRouter) { validate: { body: schema.object({ panelId: schema.string(), + savedVisualizationId: schema.string(), oldVisualizationId: schema.string(), - newVisualization: schema.object({ - id: schema.string(), - title: schema.string(), - query: schema.string(), - type: schema.string(), - timeField: schema.string(), - }), }), }, }, @@ -183,7 +159,7 @@ export function VisualizationsRouter(router: IRouter) { const newVisualizations = await customPanelBackend.addVisualization( opensearchNotebooksClient, request.body.panelId, - request.body.newVisualization, + request.body.savedVisualizationId, request.body.oldVisualizationId ); return response.ok({ @@ -202,48 +178,6 @@ export function VisualizationsRouter(router: IRouter) { } ); - // Delete an existing visualization - router.delete( - { - path: `${API_PREFIX}/visualizations/{panelId}/{visualizationId}`, - validate: { - params: schema.object({ - panelId: schema.string(), - visualizationId: schema.string(), - }), - }, - }, - async ( - context, - request, - response - ): Promise> => { - const opensearchNotebooksClient: ILegacyScopedClusterClient = context.observability_plugin.observabilityClient.asScoped( - request - ); - - try { - const newVisualizations = await customPanelBackend.deleteVisualization( - opensearchNotebooksClient, - request.params.panelId, - request.params.visualizationId - ); - return response.ok({ - body: { - message: 'Visualization Deleted', - visualizations: newVisualizations, - }, - }); - } catch (error) { - console.error('Issue in deleting visualization:', error); - return response.custom({ - statusCode: error.statusCode || 500, - body: error.message, - }); - } - } - ); - // changes the position of the mentioned visualizations // Also removes the visualiations not mentioned router.put( From a97f1d7004928469363c3263815dcab91645e850 Mon Sep 17 00:00:00 2001 From: Shenoy Pratik Date: Thu, 21 Oct 2021 10:40:52 -0700 Subject: [PATCH 4/5] moved loading to post http call --- .../custom_panels/custom_panel_table.tsx | 2 +- .../public/components/custom_panels/home.tsx | 140 ++++++++++++------ 2 files changed, 93 insertions(+), 49 deletions(-) diff --git a/dashboards-observability/public/components/custom_panels/custom_panel_table.tsx b/dashboards-observability/public/components/custom_panels/custom_panel_table.tsx index 0579236ba..1c1f5bd34 100644 --- a/dashboards-observability/public/components/custom_panels/custom_panel_table.tsx +++ b/dashboards-observability/public/components/custom_panels/custom_panel_table.tsx @@ -69,7 +69,7 @@ const pageStyles: CSSProperties = { type Props = { loading: boolean; - fetchCustomPanels: () => Promise; + fetchCustomPanels: () => void; customPanels: Array; createCustomPanel: (newCustomPanelName: string) => void; setBreadcrumbs: (newBreadcrumbs: ChromeBreadcrumb[]) => void; diff --git a/dashboards-observability/public/components/custom_panels/home.tsx b/dashboards-observability/public/components/custom_panels/home.tsx index 328ae33ef..27d9f1553 100644 --- a/dashboards-observability/public/components/custom_panels/home.tsx +++ b/dashboards-observability/public/components/custom_panels/home.tsx @@ -9,22 +9,22 @@ * GitHub history for details. */ -import { EuiBreadcrumb, EuiGlobalToastList, EuiLink } from '@elastic/eui'; -import { Toast } from '@elastic/eui/src/components/toast/global_toast_list'; -import { CustomPanelListType } from '../../../common/types/custom_panels'; -import _ from 'lodash'; -import React, { ReactChild, useState } from 'react'; -import { StaticContext } from 'react-router'; -import { Route, RouteComponentProps } from 'react-router-dom'; -import { CoreStart } from '../../../../../src/core/public'; +import { EuiBreadcrumb, EuiGlobalToastList, EuiLink } from "@elastic/eui"; +import { Toast } from "@elastic/eui/src/components/toast/global_toast_list"; +import { CustomPanelListType } from "../../../common/types/custom_panels"; +import _ from "lodash"; +import React, { ReactChild, useState } from "react"; +import { StaticContext } from "react-router"; +import { Route, RouteComponentProps } from "react-router-dom"; +import { CoreStart } from "../../../../../src/core/public"; import { CUSTOM_PANELS_API_PREFIX, CUSTOM_PANELS_DOCUMENTATION_URL, -} from '../../../common/constants/custom_panels'; -import { renderPageWithSidebar } from '../common/side_nav'; -import { CustomPanelTable } from './custom_panel_table'; -import { CustomPanelView } from './custom_panel_view'; -import { isNameValid } from './helpers/utils'; +} from "../../../common/constants/custom_panels"; +import { renderPageWithSidebar } from "../common/side_nav"; +import { CustomPanelTable } from "./custom_panel_table"; +import { CustomPanelView } from "./custom_panel_view"; +import { isNameValid } from "./helpers/utils"; /* * "Home" module is initial page for Operantional Panels @@ -38,43 +38,62 @@ import { isNameValid } from './helpers/utils'; */ type Props = { - http: CoreStart['http']; - chrome: CoreStart['chrome']; + http: CoreStart["http"]; + chrome: CoreStart["chrome"]; parentBreadcrumb: EuiBreadcrumb[]; pplService: any; renderProps: RouteComponentProps; }; -export const Home = ({ http, chrome, parentBreadcrumb, pplService, renderProps }: Props) => { - const [customPanelData, setcustomPanelData] = useState>([]); +export const Home = ({ + http, + chrome, + parentBreadcrumb, + pplService, + renderProps, +}: Props) => { + const [customPanelData, setcustomPanelData] = useState< + Array + >([]); const [toasts, setToasts] = useState>([]); const [loading, setLoading] = useState(false); const [toastRightSide, setToastRightSide] = useState(true); - const setToast = (title: string, color = 'success', text?: ReactChild, side?: string) => { - if (!text) text = ''; + const setToast = ( + title: string, + color = "success", + text?: ReactChild, + side?: string + ) => { + if (!text) text = ""; setToastRightSide(!side ? true : false); - setToasts([...toasts, { id: new Date().toISOString(), title, text, color } as Toast]); + setToasts([ + ...toasts, + { id: new Date().toISOString(), title, text, color } as Toast, + ]); }; // Fetches all saved Custom Panels const fetchCustomPanels = () => { setLoading(true); - return http + http .get(`${CUSTOM_PANELS_API_PREFIX}/panels`) .then((res) => { setcustomPanelData(res.panels); - setLoading(false); }) .catch((err) => { - console.error('Issue in fetching the operational panels', err.body.message); + console.error( + "Issue in fetching the operational panels", + err.body.message + ); }); + setLoading(false); }; // Creates a new CustomPanel const createCustomPanel = (newCustomPanelName: string) => { if (!isNameValid(newCustomPanelName)) { - setToast('Invalid Operational Panel name', 'danger'); + setToast("Invalid Operational Panel name", "danger"); return; } @@ -85,13 +104,17 @@ export const Home = ({ http, chrome, parentBreadcrumb, pplService, renderProps } }), }) .then(async (res) => { - setToast(`Operational Panel "${newCustomPanelName}" successfully created!`); - window.location.assign(`${_.last(parentBreadcrumb).href}${res.newPanelId}`); + setToast( + `Operational Panel "${newCustomPanelName}" successfully created!` + ); + window.location.assign( + `${_.last(parentBreadcrumb).href}${res.newPanelId}` + ); }) .catch((err) => { setToast( - 'Please ask your administrator to enable Operational Panels for you.', - 'danger', + "Please ask your administrator to enable Operational Panels for you.", + "danger", Documentation @@ -101,9 +124,12 @@ export const Home = ({ http, chrome, parentBreadcrumb, pplService, renderProps } }; // Renames an existing CustomPanel - const renameCustomPanel = (editedCustomPanelName: string, editedCustomPanelId: string) => { + const renameCustomPanel = ( + editedCustomPanelName: string, + editedCustomPanelId: string + ) => { if (!isNameValid(editedCustomPanelName)) { - setToast('Invalid Custom Panel name', 'danger'); + setToast("Invalid Custom Panel name", "danger"); return; } const renamePanelObject = { @@ -121,24 +147,30 @@ export const Home = ({ http, chrome, parentBreadcrumb, pplService, renderProps } const renamedCustomPanel = newCustomPanelData.find( (customPanel) => customPanel.id === editedCustomPanelId ); - if (renamedCustomPanel) renamedCustomPanel.name = editedCustomPanelName; + if (renamedCustomPanel) + renamedCustomPanel.name = editedCustomPanelName; return newCustomPanelData; }); - setToast(`Operational Panel successfully renamed into "${editedCustomPanelName}"`); + setToast( + `Operational Panel successfully renamed into "${editedCustomPanelName}"` + ); }) .catch((err) => { setToast( - 'Error renaming Operational Panel, please make sure you have the correct permission.', - 'danger' + "Error renaming Operational Panel, please make sure you have the correct permission.", + "danger" ); console.error(err.body.message); }); }; // Clones an existing Custom Panel, return new Custom Panel id - const cloneCustomPanel = (clonedCustomPanelName: string, clonedCustomPanelId: string) => { + const cloneCustomPanel = ( + clonedCustomPanelName: string, + clonedCustomPanelId: string + ) => { if (!isNameValid(clonedCustomPanelName)) { - setToast('Invalid Operational Panel name', 'danger'); + setToast("Invalid Operational Panel name", "danger"); return Promise.reject(); } const clonePanelObject = { @@ -162,19 +194,24 @@ export const Home = ({ http, chrome, parentBreadcrumb, pplService, renderProps } }, ]; }); - setToast(`Operational Panel "${clonedCustomPanelName}" successfully created!`); + setToast( + `Operational Panel "${clonedCustomPanelName}" successfully created!` + ); }) .catch((err) => { setToast( - 'Error cloning Operational Panel, please make sure you have the correct permission.', - 'danger' + "Error cloning Operational Panel, please make sure you have the correct permission.", + "danger" ); console.error(err.body.message); }); }; // Deletes an existing Operational Panel - const deleteCustomPanelList = (customPanelIdList: string[], toastMessage: string) => { + const deleteCustomPanelList = ( + customPanelIdList: string[], + toastMessage: string + ) => { const concatList = customPanelIdList.toString(); return http .delete(`${CUSTOM_PANELS_API_PREFIX}/panelList/` + concatList) @@ -189,28 +226,35 @@ export const Home = ({ http, chrome, parentBreadcrumb, pplService, renderProps } }) .catch((err) => { setToast( - 'Error deleting Operational Panels, please make sure you have the correct permission.', - 'danger' + "Error deleting Operational Panels, 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) => { + const deleteCustomPanel = ( + customPanelId: string, + customPanelName: string + ) => { return http .delete(`${CUSTOM_PANELS_API_PREFIX}/panels/` + customPanelId) .then((res) => { setcustomPanelData((prevCustomPanelData) => { - return prevCustomPanelData.filter((customPanel) => customPanel.id !== customPanelId); + return prevCustomPanelData.filter( + (customPanel) => customPanel.id !== customPanelId + ); }); - setToast(`Operational Panel "${customPanelName}" successfully deleted!`); + setToast( + `Operational Panel "${customPanelName}" successfully deleted!` + ); return res; }) .catch((err) => { setToast( - 'Error deleting Operational Panel, please make sure you have the correct permission.', - 'danger' + "Error deleting Operational Panel, please make sure you have the correct permission.", + "danger" ); console.error(err.body.message); }); @@ -223,7 +267,7 @@ export const Home = ({ http, chrome, parentBreadcrumb, pplService, renderProps } dismissToast={(removedToast) => { setToasts(toasts.filter((toast) => toast.id !== removedToast.id)); }} - side={toastRightSide ? 'right' : 'left'} + side={toastRightSide ? "right" : "left"} toastLifeTimeMs={6000} /> Date: Thu, 21 Oct 2021 10:49:59 -0700 Subject: [PATCH 5/5] fixed prettier quotes --- .../public/components/custom_panels/home.tsx | 136 ++++++------------ 1 file changed, 46 insertions(+), 90 deletions(-) diff --git a/dashboards-observability/public/components/custom_panels/home.tsx b/dashboards-observability/public/components/custom_panels/home.tsx index 27d9f1553..a5f10d330 100644 --- a/dashboards-observability/public/components/custom_panels/home.tsx +++ b/dashboards-observability/public/components/custom_panels/home.tsx @@ -9,22 +9,22 @@ * GitHub history for details. */ -import { EuiBreadcrumb, EuiGlobalToastList, EuiLink } from "@elastic/eui"; -import { Toast } from "@elastic/eui/src/components/toast/global_toast_list"; -import { CustomPanelListType } from "../../../common/types/custom_panels"; -import _ from "lodash"; -import React, { ReactChild, useState } from "react"; -import { StaticContext } from "react-router"; -import { Route, RouteComponentProps } from "react-router-dom"; -import { CoreStart } from "../../../../../src/core/public"; +import { EuiBreadcrumb, EuiGlobalToastList, EuiLink } from '@elastic/eui'; +import { Toast } from '@elastic/eui/src/components/toast/global_toast_list'; +import { CustomPanelListType } from '../../../common/types/custom_panels'; +import _ from 'lodash'; +import React, { ReactChild, useState } from 'react'; +import { StaticContext } from 'react-router'; +import { Route, RouteComponentProps } from 'react-router-dom'; +import { CoreStart } from '../../../../../src/core/public'; import { CUSTOM_PANELS_API_PREFIX, CUSTOM_PANELS_DOCUMENTATION_URL, -} from "../../../common/constants/custom_panels"; -import { renderPageWithSidebar } from "../common/side_nav"; -import { CustomPanelTable } from "./custom_panel_table"; -import { CustomPanelView } from "./custom_panel_view"; -import { isNameValid } from "./helpers/utils"; +} from '../../../common/constants/custom_panels'; +import { renderPageWithSidebar } from '../common/side_nav'; +import { CustomPanelTable } from './custom_panel_table'; +import { CustomPanelView } from './custom_panel_view'; +import { isNameValid } from './helpers/utils'; /* * "Home" module is initial page for Operantional Panels @@ -38,39 +38,23 @@ import { isNameValid } from "./helpers/utils"; */ type Props = { - http: CoreStart["http"]; - chrome: CoreStart["chrome"]; + http: CoreStart['http']; + chrome: CoreStart['chrome']; parentBreadcrumb: EuiBreadcrumb[]; pplService: any; renderProps: RouteComponentProps; }; -export const Home = ({ - http, - chrome, - parentBreadcrumb, - pplService, - renderProps, -}: Props) => { - const [customPanelData, setcustomPanelData] = useState< - Array - >([]); +export const Home = ({ http, chrome, parentBreadcrumb, pplService, renderProps }: Props) => { + const [customPanelData, setcustomPanelData] = useState>([]); const [toasts, setToasts] = useState>([]); const [loading, setLoading] = useState(false); const [toastRightSide, setToastRightSide] = useState(true); - const setToast = ( - title: string, - color = "success", - text?: ReactChild, - side?: string - ) => { - if (!text) text = ""; + const setToast = (title: string, color = 'success', text?: ReactChild, side?: string) => { + if (!text) text = ''; setToastRightSide(!side ? true : false); - setToasts([ - ...toasts, - { id: new Date().toISOString(), title, text, color } as Toast, - ]); + setToasts([...toasts, { id: new Date().toISOString(), title, text, color } as Toast]); }; // Fetches all saved Custom Panels @@ -82,10 +66,7 @@ export const Home = ({ setcustomPanelData(res.panels); }) .catch((err) => { - console.error( - "Issue in fetching the operational panels", - err.body.message - ); + console.error('Issue in fetching the operational panels', err.body.message); }); setLoading(false); }; @@ -93,7 +74,7 @@ export const Home = ({ // Creates a new CustomPanel const createCustomPanel = (newCustomPanelName: string) => { if (!isNameValid(newCustomPanelName)) { - setToast("Invalid Operational Panel name", "danger"); + setToast('Invalid Operational Panel name', 'danger'); return; } @@ -104,17 +85,13 @@ export const Home = ({ }), }) .then(async (res) => { - setToast( - `Operational Panel "${newCustomPanelName}" successfully created!` - ); - window.location.assign( - `${_.last(parentBreadcrumb).href}${res.newPanelId}` - ); + setToast(`Operational Panel "${newCustomPanelName}" successfully created!`); + window.location.assign(`${_.last(parentBreadcrumb).href}${res.newPanelId}`); }) .catch((err) => { setToast( - "Please ask your administrator to enable Operational Panels for you.", - "danger", + 'Please ask your administrator to enable Operational Panels for you.', + 'danger', Documentation @@ -124,12 +101,9 @@ export const Home = ({ }; // Renames an existing CustomPanel - const renameCustomPanel = ( - editedCustomPanelName: string, - editedCustomPanelId: string - ) => { + const renameCustomPanel = (editedCustomPanelName: string, editedCustomPanelId: string) => { if (!isNameValid(editedCustomPanelName)) { - setToast("Invalid Custom Panel name", "danger"); + setToast('Invalid Custom Panel name', 'danger'); return; } const renamePanelObject = { @@ -147,30 +121,24 @@ export const Home = ({ const renamedCustomPanel = newCustomPanelData.find( (customPanel) => customPanel.id === editedCustomPanelId ); - if (renamedCustomPanel) - renamedCustomPanel.name = editedCustomPanelName; + if (renamedCustomPanel) renamedCustomPanel.name = editedCustomPanelName; return newCustomPanelData; }); - setToast( - `Operational Panel successfully renamed into "${editedCustomPanelName}"` - ); + setToast(`Operational Panel successfully renamed into "${editedCustomPanelName}"`); }) .catch((err) => { setToast( - "Error renaming Operational Panel, please make sure you have the correct permission.", - "danger" + 'Error renaming Operational Panel, please make sure you have the correct permission.', + 'danger' ); console.error(err.body.message); }); }; // Clones an existing Custom Panel, return new Custom Panel id - const cloneCustomPanel = ( - clonedCustomPanelName: string, - clonedCustomPanelId: string - ) => { + const cloneCustomPanel = (clonedCustomPanelName: string, clonedCustomPanelId: string) => { if (!isNameValid(clonedCustomPanelName)) { - setToast("Invalid Operational Panel name", "danger"); + setToast('Invalid Operational Panel name', 'danger'); return Promise.reject(); } const clonePanelObject = { @@ -194,24 +162,19 @@ export const Home = ({ }, ]; }); - setToast( - `Operational Panel "${clonedCustomPanelName}" successfully created!` - ); + setToast(`Operational Panel "${clonedCustomPanelName}" successfully created!`); }) .catch((err) => { setToast( - "Error cloning Operational Panel, please make sure you have the correct permission.", - "danger" + 'Error cloning Operational Panel, please make sure you have the correct permission.', + 'danger' ); console.error(err.body.message); }); }; // Deletes an existing Operational Panel - const deleteCustomPanelList = ( - customPanelIdList: string[], - toastMessage: string - ) => { + const deleteCustomPanelList = (customPanelIdList: string[], toastMessage: string) => { const concatList = customPanelIdList.toString(); return http .delete(`${CUSTOM_PANELS_API_PREFIX}/panelList/` + concatList) @@ -226,35 +189,28 @@ export const Home = ({ }) .catch((err) => { setToast( - "Error deleting Operational Panels, please make sure you have the correct permission.", - "danger" + 'Error deleting Operational Panels, 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 - ) => { + const deleteCustomPanel = (customPanelId: string, customPanelName: string) => { return http .delete(`${CUSTOM_PANELS_API_PREFIX}/panels/` + customPanelId) .then((res) => { setcustomPanelData((prevCustomPanelData) => { - return prevCustomPanelData.filter( - (customPanel) => customPanel.id !== customPanelId - ); + return prevCustomPanelData.filter((customPanel) => customPanel.id !== customPanelId); }); - setToast( - `Operational Panel "${customPanelName}" successfully deleted!` - ); + setToast(`Operational Panel "${customPanelName}" successfully deleted!`); return res; }) .catch((err) => { setToast( - "Error deleting Operational Panel, please make sure you have the correct permission.", - "danger" + 'Error deleting Operational Panel, please make sure you have the correct permission.', + 'danger' ); console.error(err.body.message); }); @@ -267,7 +223,7 @@ export const Home = ({ dismissToast={(removedToast) => { setToasts(toasts.filter((toast) => toast.id !== removedToast.id)); }} - side={toastRightSide ? "right" : "left"} + side={toastRightSide ? 'right' : 'left'} toastLifeTimeMs={6000} />