From 01b6bab1ad17bafbfdd102096ce32748c0ce4362 Mon Sep 17 00:00:00 2001 From: Peter Fitzgibbons Date: Wed, 16 Aug 2023 10:02:17 -0700 Subject: [PATCH] Metrics Inline Editor and PromQL Parsing Signed-off-by: Peter Fitzgibbons --- common/constants/metrics.ts | 12 +-- common/constants/shared.ts | 2 +- common/types/metrics.ts | 14 +-- .../application_analytics/helpers/utils.tsx | 2 +- .../custom_panels/helpers/utils.tsx | 7 +- .../panel_modules/panel_grid/panel_grid.tsx | 2 +- .../panel_grid/panel_grid_so.tsx | 1 + .../visualization_container.test.tsx | 1 + .../visualization_container.scss | 37 +++++++ .../visualization_container.tsx | 16 ++- .../event_analytics/explorer/explorer.tsx | 16 ++- .../config_panes/default_vis_editor.tsx | 2 +- public/components/metrics/helpers/utils.tsx | 26 ++--- .../metrics/redux/slices/metrics_slice.ts | 83 +++++++++++++++- .../metrics/sidebar/metrics_edit_inline.tsx | 99 +++++++++++++++++++ public/components/metrics/sidebar/sidebar.tsx | 3 +- .../components/metrics/view/metrics_grid.tsx | 16 ++- .../paragraph_components/para_output.tsx | 60 +++++------ .../paragraph_components/para_query_grid.tsx | 86 +++++++--------- .../visualizations/visualization_chart.tsx | 6 +- .../saved_object_loaders/ppl/ppl_loader.ts | 2 +- test/metrics_contants.ts | 12 +-- 22 files changed, 371 insertions(+), 134 deletions(-) create mode 100644 public/components/metrics/sidebar/metrics_edit_inline.tsx diff --git a/common/constants/metrics.ts b/common/constants/metrics.ts index 853ef2865..d180ea953 100644 --- a/common/constants/metrics.ts +++ b/common/constants/metrics.ts @@ -24,13 +24,13 @@ export const resolutionOptions = [ { value: 'y', text: 'years' }, ]; -export const DEFAULT_METRIC_HEIGHT = 2; +export const DEFAULT_METRIC_HEIGHT = 3; export const DEFAULT_METRIC_WIDTH = 12; export const AGGREGATION_OPTIONS = [ - { label: 'avg' }, - { label: 'sum' }, - { label: 'count' }, - { label: 'min' }, - { label: 'max' }, + { value: 'avg', text: 'avg()' }, + { value: 'sum', text: 'sum()' }, + { value: 'count', text: 'count()' }, + { value: 'min', text: 'min()' }, + { value: 'max', text: 'max()' }, ]; diff --git a/common/constants/shared.ts b/common/constants/shared.ts index f26a59820..79d4b0cab 100644 --- a/common/constants/shared.ts +++ b/common/constants/shared.ts @@ -203,7 +203,7 @@ export const DEFAULT_CHART_STYLES: DefaultChartStylesProps = { FillOpacity: 100, MarkerSize: 5, ShowLegend: 'show', - LegendPosition: 'v', + LegendPosition: 'h', LabelAngle: 0, DefaultSortSectors: 'largest_to_smallest', DefaultModeScatter: 'markers', diff --git a/common/types/metrics.ts b/common/types/metrics.ts index 64ebd07ab..7cefde98b 100644 --- a/common/types/metrics.ts +++ b/common/types/metrics.ts @@ -5,12 +5,6 @@ import { VisualizationType } from './custom_panels'; -export interface MetricData { - metricId: string; - metricType: 'savedCustomMetric' | 'prometheusMetric'; - metricName: string; -} - export interface MetricType extends VisualizationType { id: string; savedVisualizationId: string; @@ -18,5 +12,11 @@ export interface MetricType extends VisualizationType { y: number; w: number; h: number; - metricType: 'savedCustomMetric' | 'prometheusMetric'; + query: { + type: 'savedCustomMetric' | 'prometheusMetric'; + aggregation: string; + attributesGroupBy: string[]; + catalog: string; + availableAttributes?: string[]; + }; } diff --git a/public/components/application_analytics/helpers/utils.tsx b/public/components/application_analytics/helpers/utils.tsx index 08ba77f32..025ea1a49 100644 --- a/public/components/application_analytics/helpers/utils.tsx +++ b/public/components/application_analytics/helpers/utils.tsx @@ -218,7 +218,7 @@ export const calculateAvailability = async ( const visData = await fetchVisualizationById(http, visualizationId, (value: string) => console.error(value) ); - const userConfigs = visData.user_configs ? JSON.parse(visData.user_configs) : {}; + const userConfigs = visData.user_configs || {}; // If there are levels, we get the current value if (userConfigs.availabilityConfig?.hasOwnProperty('level')) { // For every saved visualization with availability levels we push it to visWithAvailability diff --git a/public/components/custom_panels/helpers/utils.tsx b/public/components/custom_panels/helpers/utils.tsx index ec864cb54..c0b078b59 100644 --- a/public/components/custom_panels/helpers/utils.tsx +++ b/public/components/custom_panels/helpers/utils.tsx @@ -397,14 +397,13 @@ export const renderCatalogVisualization = async ({ const catalogSourceName = catalogSource.split('.')[0]; const catalogTableName = catalogSource.split('.')[1]; - const defaultAggregation = 'avg'; // pass in attributes to this function - const attributes: string[] = []; + const defaultAggregation = 'avg'; const visualizationQuery = updateCatalogVisualizationQuery({ catalogSourceName, catalogTableName, - aggregation: defaultAggregation, - attributesGroupBy: attributes, + aggregation: queryMetaData.aggregation, + attributesGroupBy: queryMetaData.attributesGroupBy, startTime, endTime, spanParam, diff --git a/public/components/custom_panels/panel_modules/panel_grid/panel_grid.tsx b/public/components/custom_panels/panel_modules/panel_grid/panel_grid.tsx index dc70ba7fd..3f4754c23 100644 --- a/public/components/custom_panels/panel_modules/panel_grid/panel_grid.tsx +++ b/public/components/custom_panels/panel_modules/panel_grid/panel_grid.tsx @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ /* eslint-disable react-hooks/exhaustive-deps */ -/* eslint-disable no-console */ import _ from 'lodash'; import React, { useEffect, useState } from 'react'; @@ -109,6 +108,7 @@ export const PanelGrid = (props: PanelGridProps) => { pplFilterValue={pplFilterValue} showFlyout={showFlyout} removeVisualization={removeVisualization} + contextMenuId="visualization" /> ) ); diff --git a/public/components/custom_panels/panel_modules/panel_grid/panel_grid_so.tsx b/public/components/custom_panels/panel_modules/panel_grid/panel_grid_so.tsx index a2fa5e02b..a4bf9c219 100644 --- a/public/components/custom_panels/panel_modules/panel_grid/panel_grid_so.tsx +++ b/public/components/custom_panels/panel_modules/panel_grid/panel_grid_so.tsx @@ -105,6 +105,7 @@ export const PanelGridSO = (props: PanelGridProps) => { pplFilterValue={pplFilterValue} showFlyout={showFlyout} removeVisualization={removeVisualization} + contextMenuId="visualization" /> ) ); diff --git a/public/components/custom_panels/panel_modules/visualization_container/__tests__/visualization_container.test.tsx b/public/components/custom_panels/panel_modules/visualization_container/__tests__/visualization_container.test.tsx index 9ea46f4d4..71847fcfb 100644 --- a/public/components/custom_panels/panel_modules/visualization_container/__tests__/visualization_container.test.tsx +++ b/public/components/custom_panels/panel_modules/visualization_container/__tests__/visualization_container.test.tsx @@ -58,6 +58,7 @@ describe('Visualization Container Component', () => { showFlyout={showFlyout} removeVisualization={removeVisualization} onEditClick={onEditClick} + contextMenuId="visualization" /> ); wrapper.update(); diff --git a/public/components/custom_panels/panel_modules/visualization_container/visualization_container.scss b/public/components/custom_panels/panel_modules/visualization_container/visualization_container.scss index 0130b340f..6003ce53b 100644 --- a/public/components/custom_panels/panel_modules/visualization_container/visualization_container.scss +++ b/public/components/custom_panels/panel_modules/visualization_container/visualization_container.scss @@ -34,6 +34,43 @@ text-align: center; } +.metricVis { + #metricsEditInline { + /* for ... reasons (?), margin here causes + other rules to activate... providing + a more-correct display. + */ + margin: 0 0px; + } + + + #aggregation__field { + .euiFormLabel { + width: 130px + } + } + + // #explorerPlotComponent { + // //overflow: hidden; + // } + // + .svg-container { + //height: 425px !important; + margin-top: -25px !important; + } + + // .main-svg { + // //margin-top: -10px; + // //height: calc(100% - 10px); + // } + // + + .main-svg { + margin-top: -10px; + //height: calc(100% - 60px); + } +} + %center-div { top: 50%; left: 50%; diff --git a/public/components/custom_panels/panel_modules/visualization_container/visualization_container.tsx b/public/components/custom_panels/panel_modules/visualization_container/visualization_container.tsx index a5fb52474..c0f5ee81e 100644 --- a/public/components/custom_panels/panel_modules/visualization_container/visualization_container.tsx +++ b/public/components/custom_panels/panel_modules/visualization_container/visualization_container.tsx @@ -26,6 +26,7 @@ import { } from '@elastic/eui'; import React, { useEffect, useMemo, useState } from 'react'; import _ from 'lodash'; +import { useSelector } from 'react-redux'; import { displayVisualization, renderCatalogVisualization, @@ -33,6 +34,8 @@ import { } from '../../helpers/utils'; import './visualization_container.scss'; import { VizContainerError } from '../../../../../common/types/custom_panels'; +import { MetricsEditInline } from '../../../metrics/sidebar/metrics_edit_inline'; +import { metricQuerySelector } from '../../../metrics/redux/slices/metrics_slice'; import { coreRefs } from '../../../../framework/core_refs'; /* @@ -71,6 +74,7 @@ interface Props { removeVisualization?: (visualizationId: string) => void; catalogVisualization?: boolean; spanParam?: string; + contextMenuId: 'visualization' | 'notebook' | 'metrics'; } export const VisualizationContainer = ({ @@ -88,6 +92,7 @@ export const VisualizationContainer = ({ removeVisualization, catalogVisualization, spanParam, + contextMenuId, }: Props) => { const [isPopoverOpen, setIsPopoverOpen] = useState(false); const [visualizationTitle, setVisualizationTitle] = useState(''); @@ -103,6 +108,7 @@ export const VisualizationContainer = ({ const [isModalVisible, setIsModalVisible] = useState(false); const [modalContent, setModalContent] = useState(<>); + const queryMetaData = useSelector(metricQuerySelector(visualizationId)); const closeModal = () => setIsModalVisible(false); const showModal = (modalType: string) => { if (modalType === 'catalogModal') @@ -227,6 +233,7 @@ export const VisualizationContainer = ({ setVisualizationMetaData, setIsLoading, setIsError, + queryMetaData, }); else await renderSavedVisualization( @@ -283,11 +290,17 @@ export const VisualizationContainer = ({ loadVisaulization(); }, [onRefresh]); + useEffect(() => { + if (catalogVisualization) loadVisaulization(); + }, [queryMetaData]); + + const metricVisCssClassName = catalogVisualization ? 'metricVis' : ''; + return ( <>
@@ -333,6 +346,7 @@ export const VisualizationContainer = ({ )} + {catalogVisualization && }
{memoisedVisualizationBox}
diff --git a/public/components/event_analytics/explorer/explorer.tsx b/public/components/event_analytics/explorer/explorer.tsx index 52c9abaa9..146cb3ad2 100644 --- a/public/components/event_analytics/explorer/explorer.tsx +++ b/public/components/event_analytics/explorer/explorer.tsx @@ -118,6 +118,7 @@ import { Sidebar } from './sidebar'; import { TimechartHeader } from './timechart_header'; import { ExplorerVisualizations } from './visualizations'; import { CountDistribution } from './visualizations/count_distribution'; +import { processMetricsData } from '../../custom_panels/helpers/utils'; export const Explorer = ({ pplService, @@ -627,17 +628,22 @@ export const Explorer = ({ isOverridingPattern, ]); + const visualizationSettings = !isEmpty(userVizConfigs[curVisId]) + ? { ...userVizConfigs[curVisId] } + : { + dataConfig: getDefaultVisConfig(queryManager.queryParser().parse(tempQuery).getStats()), + }; + const visualizations: IVisualizationContainerProps = useMemo(() => { return getVizContainerProps({ vizId: curVisId, rawVizData: explorerVisualizations, query, indexFields: explorerFields, - userConfigs: !isEmpty(userVizConfigs[curVisId]) - ? { ...userVizConfigs[curVisId] } - : { - dataConfig: getDefaultVisConfig(queryManager.queryParser().parse(tempQuery).getStats()), - }, + userConfigs: { + ...visualizationSettings, + ...processMetricsData(explorerData.schema, visualizationSettings), + }, appData: { fromApp: appLogEvents }, explorer: { explorerData, explorerFields, query, http, pplService }, }); diff --git a/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/default_vis_editor.tsx b/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/default_vis_editor.tsx index 6ee5397d8..ef8e17b50 100644 --- a/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/default_vis_editor.tsx +++ b/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/default_vis_editor.tsx @@ -20,7 +20,7 @@ export const VizDataPanel = ({ visualizations, onConfigChange, vizState = {}, ta const dynamicContent = tabProps.sections.map((section) => { const Editor = section.editor; return ( - + >, setEnd: React.Dispatch> ) => { - const recentlyUsedRange = recentlyUsedRanges.filter((recentlyUsedRange) => { + const dedupedRanges = recentlyUsedRanges.filter((recentlyUsedRange) => { const isDuplicate = recentlyUsedRange.start === start && recentlyUsedRange.end === end; return !isDuplicate; }); - recentlyUsedRange.unshift({ start, end }); + dedupedRanges.unshift({ start, end }); setStart(start); setEnd(end); - setRecentlyUsedRanges(recentlyUsedRange.slice(0, 9)); + setRecentlyUsedRanges(dedupedRanges.slice(0, 9)); }; // PPL Service requestor @@ -58,21 +58,21 @@ export const getVisualizations = (http: CoreStart['http']) => { }); }; -interface boxType { +interface BoxType { x1: number; y1: number; x2: number; y2: number; } -const calculatOverlapArea = (bb1: boxType, bb2: boxType) => { - const x_left = Math.max(bb1.x1, bb2.x1); - const y_top = Math.max(bb1.y1, bb2.y1); - const x_right = Math.min(bb1.x2, bb2.x2); - const y_bottom = Math.min(bb1.y2, bb2.y2); +const calculatOverlapArea = (bb1: BoxType, bb2: BoxType) => { + const xLeft = Math.max(bb1.x1, bb2.x1); + const yTop = Math.max(bb1.y1, bb2.y1); + const xRight = Math.min(bb1.x2, bb2.x2); + const yBottom = Math.min(bb1.y2, bb2.y2); - if (x_right < x_left || y_bottom < y_top) return 0; - return (x_right - x_left) * (y_bottom - y_top); + if (xRight < xLeft || yBottom < yTop) return 0; + return (xRight - xLeft) * (yBottom - yTop); }; const getTotalOverlapArea = (panelVisualizations: MetricType[]) => { @@ -149,7 +149,7 @@ export const mergeLayoutAndMetrics = ( for (let i = 0; i < newVisualizationList.length; i++) { for (let j = 0; j < layout.length; j++) { - if (newVisualizationList[i].id == layout[j].i) { + if (newVisualizationList[i].id === layout[j].i) { newPanelVisualizations.push({ ...newVisualizationList[i], x: layout[j].x, @@ -206,6 +206,6 @@ export const updateMetricsWithSelections = ( description: savedVisualization.description, type: 'line', subType: 'metric', - userConfigs: JSON.stringify(savedVisualization.user_configs), + userConfigs: savedVisualization.user_configs, }; }; diff --git a/public/components/metrics/redux/slices/metrics_slice.ts b/public/components/metrics/redux/slices/metrics_slice.ts index e323ba628..855449a7a 100644 --- a/public/components/metrics/redux/slices/metrics_slice.ts +++ b/public/components/metrics/redux/slices/metrics_slice.ts @@ -17,10 +17,12 @@ import { SavedObjectsActions } from '../../../../services/saved_objects/saved_ob import { ObservabilitySavedVisualization } from '../../../../services/saved_objects/saved_object_client/types'; import { getNewVizDimensions, pplServiceRequestor, sortMetricLayout } from '../../helpers/utils'; import { coreRefs } from '../../../../framework/core_refs'; +import { useToast } from '../../../common/toast'; export interface IconAttributes { color: string; } + const coloredIconsFrom = (dataSources: string[]): { [dataSource: string]: IconAttributes } => { const colorCycle = ouiPaletteColorBlindBehindText({ sortBy: 'natural' }); const keyedIcons = dataSources.map((dataSource, index) => { @@ -113,8 +115,16 @@ const updateLayoutBySelection = (state: any, newMetric: any) => { y: newDimensions.y, h: newDimensions.h, w: newDimensions.w, - metricType: - newMetric.catalog === OBSERVABILITY_CUSTOM_METRIC ? 'savedCustomMetric' : 'prometheusMetric', + query: { + type: + newMetric.catalog === OBSERVABILITY_CUSTOM_METRIC + ? 'savedCustomMetric' + : 'prometheusMetric', + catalog: newMetric.id, + aggregation: 'avg', + attributesGroupBy: [], + availableAttributes: [], + }, }; state.metricsLayout = [...state.metricsLayout, metricVisualization]; }; @@ -159,6 +169,14 @@ export const metricSlice = createSlice({ state.metricsLayout = payload; state.selected = sortBy(payload, ['x', 'y']).map(({ id }) => id); }, + setMetricSelectedAttributes: (state, { payload }) => { + const { visualizationId, attributesGroupBy } = payload; + const metric: MetricType = state.metricsLayout.find( + (layout) => layout.id === visualizationId + ); + metric.query.attributesGroupBy = attributesGroupBy; + }, + setDataSources: (state, { payload }) => { state.dataSources = [OBSERVABILITY_CUSTOM_METRIC, ...payload]; }, @@ -180,8 +198,62 @@ export const { setDataSources, setDataSourceTitles, setDataSourceIcons, + setMetricSelectedAttributes, } = metricSlice.actions; +const getAvailableAttributes = (id) => async (dispatch, getState) => { + const { pplService } = coreRefs; + const { setToast } = useToast(); + + try { + const columnSchema = await pplService.fetch({ + query: 'describe ' + id + ' | fields COLUMN_NAME', + format: 'jdbc', + }); + const columns = columnSchema.jsonData + .map((sch) => sch.COLUMN_NAME) + .filter((col) => col[0] !== '@'); + + const state = getState(); + const updatedLayout = state.metrics.metricsLayout.map((metricLayout) => + metricLayout.id === id + ? { ...metricLayout, query: { ...metricLayout.query, availableAttributes: columns } } + : metricLayout + ); + + dispatch(updateMetricsLayout(updatedLayout)); + } catch (e) { + setToast(`An error occurred retrieving attributes for metric ${id} `, 'danger'); + console.error(`An error occurred retrieving attributes for metric ${id} `, e); + } +}; + +export const addSelectedMetric = (metric: MetricType) => async (dispatch) => { + await dispatch(selectMetric(metric)); + if (metric.catalog !== OBSERVABILITY_CUSTOM_METRIC) + await dispatch(getAvailableAttributes(metric.id)); +}; + +export const updateMetricQuery = (visualizationId, { aggregation, attributesGroupBy }) => ( + dispatch, + getState +) => { + const state = getState(); + const updatedLayout = state.metrics.metricsLayout.map((metricLayout) => + metricLayout.id === visualizationId + ? { + ...metricLayout, + query: { + ...metricLayout.query, + aggregation: aggregation || metricLayout.query.aggregation, + attributesGroupBy: attributesGroupBy || metricLayout.query.attributesGroupBy, + }, + } + : metricLayout + ); + dispatch(updateMetricsLayout(updatedLayout)); +}; + export const availableMetricsSelector = (state) => state.metrics.metrics .filter((metric) => !state.metrics.selected.includes(metric.id)) @@ -199,6 +271,13 @@ export const metricIconsSelector = (state) => state.metrics.dataSourceIcons; export const metricsLayoutSelector = (state) => state.metrics.metricsLayout; +export const metricQuerySelector = (id) => (state) => + state.metrics.metricsLayout.find((layout) => layout.id === id)?.query || { + aggregation: '', + attributesGroupBy: [], + availableAttributes: [], + }; + export const dataSourcesSelector = (state) => state.metrics.dataSources; export const metricsReducers = metricSlice.reducer; diff --git a/public/components/metrics/sidebar/metrics_edit_inline.tsx b/public/components/metrics/sidebar/metrics_edit_inline.tsx new file mode 100644 index 000000000..00c4aa881 --- /dev/null +++ b/public/components/metrics/sidebar/metrics_edit_inline.tsx @@ -0,0 +1,99 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { useEffect, useState } from 'react'; +import { + EuiComboBox, + EuiFormControlLayout, + EuiFlexGroup, + EuiFlexItem, + EuiFormLabel, + EuiSelect, +} from '@elastic/eui'; +import { useDispatch, useSelector } from 'react-redux'; +import { AGGREGATION_OPTIONS } from '../../../../common/constants/metrics'; +import { + metricQuerySelector, + setMetricSelectedAttributes, + updateMetricQuery, +} from '../redux/slices/metrics_slice'; + +const useObservable = (observable, observer, deps) => { + // useEffect with empty deps will call this only once + useEffect(() => { + const sub = observable.subscribe(observer); // connect + return () => sub.unsubscribe(); // < unsub on unmount + }, deps); +}; + +export const MetricsEditInline = ({ visualizationId }: { visualizationId: string }) => { + const [aggregationIsOpen, setAggregationIsOpen] = useState(false); + const [attributesGroupByIsOpen, setAttributesGroupByIsOpen] = useState(false); + + const dispatch = useDispatch(); + + const query = useSelector(metricQuerySelector(visualizationId)); + useEffect(() => { + console.log({ visualizationId, query }); + }, [query, visualizationId]); + + const availableAttributesLabels = query.availableAttributes.map((attribute) => ({ + label: attribute, + name: attribute, + })); + + const onChangeAggregation = (e) => { + dispatch(updateMetricQuery(visualizationId, { aggregation: e.target.value })); + setAggregationIsOpen(false); + }; + + const onChangeAttributesGroupBy = async (selectedAttributes) => { + console.log('onChangeAttributes', selectedAttributes); + const attributesGroupBy = selectedAttributes.map(({ label }) => label); + dispatch(setMetricSelectedAttributes({ visualizationId, attributesGroupBy })); + + setAttributesGroupByIsOpen(false); + }; + + const renderAggregationEditor = () => ( + + + + + + ); + + const renderAttributesGroupByEditor = () => ( + ATTRIBUTES GROUP BY} + > + ({ label, value: label }))} + onChange={onChangeAttributesGroupBy} + options={availableAttributesLabels} + prepend={'ATTRIBUTES GROUP BY'} + /> + + ); + + return ( + + {renderAggregationEditor()} + {renderAttributesGroupByEditor()} + + ); +}; diff --git a/public/components/metrics/sidebar/sidebar.tsx b/public/components/metrics/sidebar/sidebar.tsx index 22bbbaadc..983d48c0d 100644 --- a/public/components/metrics/sidebar/sidebar.tsx +++ b/public/components/metrics/sidebar/sidebar.tsx @@ -15,6 +15,7 @@ import { selectMetric, loadMetrics, selectedMetricsSelector, + addSelectedMetric, } from '../redux/slices/metrics_slice'; import { CoreStart } from '../../../../../../src/core/public'; import PPLService from '../../../services/requests/ppl'; @@ -33,7 +34,7 @@ export const Sidebar = () => { }); }, [dispatch]); - const handleAddMetric = (metric: any) => dispatch(selectMetric(metric)); + const handleAddMetric = (metric: any) => dispatch(addSelectedMetric(metric)); const handleRemoveMetric = (metric: any) => { dispatch(deSelectMetric(metric)); diff --git a/public/components/metrics/view/metrics_grid.tsx b/public/components/metrics/view/metrics_grid.tsx index edb68ba8b..6d6a3ec8e 100644 --- a/public/components/metrics/view/metrics_grid.tsx +++ b/public/components/metrics/view/metrics_grid.tsx @@ -16,6 +16,7 @@ import { updateMetricsLayout, deSelectMetric } from '../redux/slices/metrics_sli import { mergeLayoutAndMetrics } from '../helpers/utils'; import './metrics_grid.scss'; +import { coreRefs } from '../../../framework/core_refs'; // HOC container to provide dynamic width for Grid layout const ResponsiveGridLayout = WidthProvider(Responsive); @@ -47,6 +48,7 @@ export const MetricsGrid = ({ setEditActionType, spanParam, }: MetricsGridProps) => { + const { http, pplService } = coreRefs; // Redux tools const dispatch = useDispatch(); const updateLayout = (metric: any) => dispatch(updateMetricsLayout(metric)); @@ -84,6 +86,7 @@ export const MetricsGrid = ({ panelVisualization.metricType === 'savedCustomMetric' ? undefined : true } spanParam={spanParam} + contextMenuId="metrics" /> )); setGridData(gridDataComps); @@ -121,7 +124,7 @@ export const MetricsGrid = ({ reloadLayout(); loadVizComponents(); } - }, [editMode]); + }, [editMode, reloadLayout, loadVizComponents]); useEffect(() => { if (editActionType === 'cancel') { @@ -132,13 +135,20 @@ export const MetricsGrid = ({ updateLayout(mergeLayoutAndMetrics(postEditLayout, panelVisualizations)); setEditActionType(''); } - }, [editActionType]); + }, [ + editActionType, + handleRemoveMetric, + postEditLayout, + removeMetricsList, + panelVisualizations, + setEditActionType, + ]); // Update layout whenever visualizations are updated useEffect(() => { reloadLayout(); loadVizComponents(); - }, [panelVisualizations]); + }, [panelVisualizations, reloadLayout, loadVizComponents]); // Reset Size of Panel Grid when Nav Dock is Locked useEffect(() => { diff --git a/public/components/notebooks/components/paragraph_components/para_output.tsx b/public/components/notebooks/components/paragraph_components/para_output.tsx index d0d6f5ad1..4bf9a9291 100644 --- a/public/components/notebooks/components/paragraph_components/para_output.tsx +++ b/public/components/notebooks/components/paragraph_components/para_output.tsx @@ -70,44 +70,45 @@ export const ParaOutput = (props: { return data; }; - const QueryOutput = ({ typeOut, val }: { typeOut: string; val: string }) => { - const inputQuery = para.inp.substring(4, para.inp.length); - const queryObject = JSON.parse(val); - const columns = createQueryColumns(queryObject.schema); - const data = getQueryOutputData(queryObject); - const [visibleColumns, setVisibleColumns] = useState(() => columns.map(({ id }) => id)); - if (queryObject.hasOwnProperty('error')) { - return {val}; - } else { - return ( -
- - {inputQuery} - - - -
- ); - } - }; - - const OutputBody = ({ typeOut, val }: { typeOut: string; val: string }) => { + const OutputBody = ({ key, typeOut, val }: { key: string; typeOut: string; val: string }) => { /* Returns a component to render paragraph outputs using the para.typeOut property * Currently supports HTML, TABLE, IMG * TODO: add table rendering */ const dateFormat = uiSettingsService.get('dateFormat'); + const [visibleColumns, setVisibleColumns] = useState>( + [] + ); + if (typeOut !== undefined) { switch (typeOut) { case 'QUERY': - return ; + const inputQuery = para.inp.substring(4, para.inp.length); + const queryObject = JSON.parse(val); + if (queryObject.hasOwnProperty('error')) { + return {val}; + } else { + const columns = createQueryColumns(queryObject.schema); + setVisibleColumns(columns); + const data = getQueryOutputData(queryObject); + return ( +
+ + {inputQuery} + + + +
+ ); + } case 'MARKDOWN': return ( @@ -153,6 +154,7 @@ export const ParaOutput = (props: { onRefresh={false} pplFilterValue={''} usedInNotebooks={true} + contextMenuId="notebook" /> diff --git a/public/components/notebooks/components/paragraph_components/para_query_grid.tsx b/public/components/notebooks/components/paragraph_components/para_query_grid.tsx index 8b8425278..07b4e553a 100644 --- a/public/components/notebooks/components/paragraph_components/para_query_grid.tsx +++ b/public/components/notebooks/components/paragraph_components/para_query_grid.tsx @@ -5,49 +5,41 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import $ from 'jquery'; -import { - EuiDataGrid, - EuiLoadingSpinner, - EuiSpacer -} from '@elastic/eui'; +import { EuiDataGrid, EuiLoadingSpinner, EuiSpacer } from '@elastic/eui'; +import { Dispatch } from 'react'; +import { SetStateAction } from 'react'; -type QueryDataGridProps = { - rowCount: number, - queryColumns: Array, - visibleColumns: Array, - setVisibleColumns: (visibleColumns: string[]) => void, - dataValues: Array, +interface QueryDataGridProps { + rowCount: number; + queryColumns: any[]; + visibleColumns: any[]; + setVisibleColumns: Dispatch>>; + dataValues: any[]; } -type RenderCellValueProps = { - rowIndex: number, - columnId: string +interface RenderCellValueProps { + rowIndex: number; + columnId: string; } function QueryDataGrid(props: QueryDataGridProps) { - const { - rowCount, - queryColumns, - visibleColumns, - setVisibleColumns, - dataValues, - } = props; + const { rowCount, queryColumns, visibleColumns, setVisibleColumns, dataValues } = props; const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 10 }); // ** Sorting config const [sortingColumns, setSortingColumns] = useState([]); const [isVisible, setIsVisible] = useState(false); const onSort = useCallback( - (sortingColumns) => { - setSortingColumns(sortingColumns); + (newColumns) => { + setSortingColumns(newColumns); }, [setSortingColumns] ); const onChangeItemsPerPage = useCallback( (pageSize) => - setPagination((pagination) => ({ - ...pagination, + setPagination((currentPagination) => ({ + ...currentPagination, pageSize, pageIndex: 0, })), @@ -55,28 +47,24 @@ function QueryDataGrid(props: QueryDataGridProps) { ); const onChangePage = useCallback( - (pageIndex) => - setPagination((pagination) => ({ ...pagination, pageIndex })), + (pageIndex) => setPagination((currentPagination) => ({ ...currentPagination, pageIndex })), [setPagination] ); const renderCellValue = useMemo(() => { return ({ rowIndex, columnId }: RenderCellValueProps) => { - return dataValues.hasOwnProperty(rowIndex) - ? dataValues[rowIndex][columnId] - : null; + return dataValues.hasOwnProperty(rowIndex) ? dataValues[rowIndex][columnId] : null; }; - }, []); + }, [dataValues]); - const getUpdatedVisibleColumns = (queryColumns: Array) => { - let updatedVisibleColumns = []; - for (let index = 0; index < queryColumns.length; ++index) { - updatedVisibleColumns.push(queryColumns[index].displayAsText); + const getUpdatedVisibleColumns = (newQueryColumns: unknown[]) => { + const updatedVisibleColumns = []; + for (let index = 0; index < newQueryColumns.length; ++index) { + updatedVisibleColumns.push(newQueryColumns[index].displayAsText); } return updatedVisibleColumns; - } + }; - useEffect(() => { if ($('.euiDataGrid__overflow').is(':visible')) { setIsVisible(true); @@ -89,9 +77,9 @@ function QueryDataGrid(props: QueryDataGridProps) { setVisibleColumns(getUpdatedVisibleColumns(queryColumns)); }, []); - const displayLoadingSpinner = (!isVisible) ? ( + const displayLoadingSpinner = !isVisible ? ( <> - + ) : null; @@ -100,7 +88,7 @@ function QueryDataGrid(props: QueryDataGridProps) {
{displayLoadingSpinner}
- ) + ); } function queryDataGridPropsAreEqual(prevProps: QueryDataGridProps, nextProps: QueryDataGridProps) { - return prevProps.rowCount === nextProps.rowCount - && JSON.stringify(prevProps.queryColumns) === JSON.stringify(nextProps.queryColumns) - && JSON.stringify(prevProps.visibleColumns) === JSON.stringify(nextProps.visibleColumns) - && JSON.stringify(prevProps.dataValues) === JSON.stringify(nextProps.dataValues) + return ( + prevProps.rowCount === nextProps.rowCount && + JSON.stringify(prevProps.queryColumns) === JSON.stringify(nextProps.queryColumns) && + JSON.stringify(prevProps.visibleColumns) === JSON.stringify(nextProps.visibleColumns) && + JSON.stringify(prevProps.dataValues) === JSON.stringify(nextProps.dataValues) + ); } -export const QueryDataGridMemo = React.memo(QueryDataGrid, queryDataGridPropsAreEqual); \ No newline at end of file +export const QueryDataGridMemo = React.memo(QueryDataGrid, queryDataGridPropsAreEqual); diff --git a/public/components/visualizations/visualization_chart.tsx b/public/components/visualizations/visualization_chart.tsx index cc5a667ff..7f8461978 100644 --- a/public/components/visualizations/visualization_chart.tsx +++ b/public/components/visualizations/visualization_chart.tsx @@ -5,9 +5,7 @@ import React, { useMemo } from 'react'; -interface IVisualizationChart {} - -export const VisualizationChart = ({ visualizations }: IVisualizationChart) => { +export const VisualizationChart = ({ visualizations }) => { const { vis } = visualizations; const { layout = {}, config = {} } = visualizations?.data?.userConfigs; const Visualization = visualizations?.vis?.component; @@ -29,7 +27,7 @@ export const VisualizationChart = ({ visualizations }: IVisualizationChart) => { return ( ); diff --git a/public/services/saved_objects/saved_object_loaders/ppl/ppl_loader.ts b/public/services/saved_objects/saved_object_loaders/ppl/ppl_loader.ts index 21fc4b27a..ab1a2f4eb 100644 --- a/public/services/saved_objects/saved_object_loaders/ppl/ppl_loader.ts +++ b/public/services/saved_objects/saved_object_loaders/ppl/ppl_loader.ts @@ -190,7 +190,7 @@ export class PPLSavedObjectLoader extends SavedObjectLoaderBase implements ISave const { tabId, queryManager, getDefaultVisConfig } = this.loadContext; // fill saved user configs let visConfig = {}; - const customConfig = objectData.user_configs ? JSON.parse(objectData.user_configs) : {}; + const customConfig = objectData.user_configs || {}; if (!isEmpty(customConfig.dataConfig) && !isEmpty(customConfig.dataConfig?.series)) { visConfig = { ...customConfig }; } else { diff --git a/test/metrics_contants.ts b/test/metrics_contants.ts index 7fd210468..59edb8057 100644 --- a/test/metrics_contants.ts +++ b/test/metrics_contants.ts @@ -7,7 +7,7 @@ export const sampleMetricsVisualizations = [ { h: 2, id: 'Y4muP4QBiaYaSxpXk7r8', - metricType: 'savedCustomMetric', + query: { type: 'savedCustomMetric', aggregation: 'avg', attributesGroupBy: [] }, savedVisualizationId: 'Y4muP4QBiaYaSxpXk7r8', w: 12, x: 0, @@ -25,7 +25,7 @@ export const sampleMetricsVisualizations = [ { h: 2, id: 'prometheus.process_resident_memory_bytes', - metricType: 'prometheusMetric', + query: { type: 'prometheusMetric', aggregation: 'avg', attributesGroupBy: [] }, savedVisualizationId: 'prometheus.process_resident_memory_bytes', w: 12, x: 0, @@ -59,7 +59,7 @@ export const sampleSortedMetricsLayout = [ { h: 2, id: 'Y4muP4QBiaYaSxpXk7r8', - metricType: 'savedCustomMetric', + query: { type: 'savedCustomMetric', aggregation: 'avg', attributesGroupBy: [] }, savedVisualizationId: 'Y4muP4QBiaYaSxpXk7r8', w: 12, x: 0, @@ -68,7 +68,7 @@ export const sampleSortedMetricsLayout = [ { h: 2, id: 'prometheus.process_resident_memory_bytes', - metricType: 'prometheusMetric', + query: { type: 'prometheusMetric', aggregation: 'avg', attributesGroupBy: [] }, savedVisualizationId: 'prometheus.process_resident_memory_bytes', w: 12, x: 0, @@ -168,7 +168,7 @@ export const samplePanelVisualizations2 = [ y: 0, h: 2, w: 12, - metricType: 'savedCustomMetric', + query: { type: 'savedCustomMetric', aggregation: 'avg', attributesGroupBy: [] }, }, { id: 'tomAP4QBiaYaSxpXALls', @@ -177,7 +177,7 @@ export const samplePanelVisualizations2 = [ y: 2, h: 2, w: 12, - metricType: 'savedCustomMetric', + query: { type: 'savedCustomMetric', aggregation: 'avg', attributesGroupBy: [] }, }, ];