From 7e314d2b9100878954d71bdbe715513e7eebf5cc Mon Sep 17 00:00:00 2001 From: Peter Fitzgibbons Date: Thu, 21 Dec 2023 14:54:50 -0800 Subject: [PATCH] Backport #1309 to main (#1318) Signed-off-by: Peter Fitzgibbons --- .../custom_panels/helpers/utils.tsx | 44 ++++++++++------- .../visualization_container.tsx | 18 +++++-- .../metrics/redux/slices/metrics_slice.ts | 48 +++++++++---------- public/components/metrics/sidebar/sidebar.tsx | 8 ++-- .../components/metrics/view/metrics_grid.tsx | 16 ++++++- public/services/requests/ppl.ts | 2 +- 6 files changed, 87 insertions(+), 49 deletions(-) diff --git a/public/components/custom_panels/helpers/utils.tsx b/public/components/custom_panels/helpers/utils.tsx index 9abc0d8d7b..e06c795df7 100644 --- a/public/components/custom_panels/helpers/utils.tsx +++ b/public/components/custom_panels/helpers/utils.tsx @@ -259,25 +259,34 @@ const dynamicLayoutFromQueryData = (queryData) => { }; }; -const createCatalogVisualizationMetaData = ( - catalogSource: string, - visualizationQuery: string, - visualizationType: string, - visualizationTimeField: string, - queryData: object -) => { +const createCatalogVisualizationMetaData = ({ + catalogSource, + query, + type, + subType, + timeField, + queryData, +}: { + catalogSource: string; + query: string; + type: string; + subType: string; + timeField: string; + queryData: object; +}) => { return { name: catalogSource, description: '', - query: visualizationQuery, - type: visualizationType, + query, + type, + subType, selected_date_range: { start: 'now/y', end: 'now', text: '', }, selected_timestamp: { - name: visualizationTimeField, + name: timeField, type: 'timestamp', }, selected_fields: { @@ -332,6 +341,7 @@ export const renderCatalogVisualization = async ({ const visualizationType = 'line'; const visualizationTimeField = '@timestamp'; + const visualizationSubType = visualization.subType; const visualizationQuery = updateCatalogVisualizationQuery({ ...visualization.queryMetaData, @@ -357,15 +367,15 @@ export const renderCatalogVisualization = async ({ ); setVisualizationData(queryData); - const visualizationMetaData = createCatalogVisualizationMetaData( + const visualizationMetaData = createCatalogVisualizationMetaData({ catalogSource, - visualizationQuery, - visualizationType, - visualizationTimeField, - queryData - ); + query: visualizationQuery, + type: visualizationType, + subType: visualization.subType, + timeField: visualizationTimeField, + queryData, + }); - console.log('renderCatalogVisualization', { visualizationMetaData }); setVisualizationMetaData(visualizationMetaData); } catch (error) { setIsError({ error }); 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 3d52f9eddd..6cc87f8e02 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 @@ -37,7 +37,10 @@ import './visualization_container.scss'; import { VizContainerError } from '../../../../../common/types/custom_panels'; import { metricQuerySelector } from '../../../metrics/redux/slices/metrics_slice'; import { coreRefs } from '../../../../framework/core_refs'; -import { PROMQL_METRIC_SUBTYPE } from '../../../../../common/constants/shared'; +import { + PROMQL_METRIC_SUBTYPE, + observabilityMetricsID, +} from '../../../../../common/constants/shared'; /* * Visualization container - This module is a placeholder to add visualizations in react-grid-layout @@ -78,6 +81,7 @@ interface Props { removeVisualization?: (visualizationId: string) => void; catalogVisualization?: boolean; inlineEditor?: JSX.Element; + actionMenuType?: string; } export const VisualizationContainer = ({ @@ -98,6 +102,7 @@ export const VisualizationContainer = ({ removeVisualization, catalogVisualization, inlineEditor, + actionMenuType, }: Props) => { const [isPopoverOpen, setIsPopoverOpen] = useState(false); const [visualizationTitle, setVisualizationTitle] = useState(''); @@ -175,7 +180,11 @@ export const VisualizationContainer = ({ disabled={editMode} onClick={() => { closeActionsMenu(); - onEditClick(savedVisualizationId); + if (visualizationMetaData?.subType === PROMQL_METRIC_SUBTYPE) { + window.location.assign(`${observabilityMetricsID}#/${savedVisualizationId}`); + } else { + onEditClick(savedVisualizationId); + } }} > Edit @@ -217,7 +226,10 @@ export const VisualizationContainer = ({ , ]; - if (visualizationMetaData?.subType === PROMQL_METRIC_SUBTYPE) { + if ( + visualizationMetaData?.subType === PROMQL_METRIC_SUBTYPE && + actionMenuType === 'metricsGrid' + ) { popoverPanel = [showPPLQueryPanel]; } else if (usedInNotebooks) { popoverPanel = [popoverPanel[0]]; diff --git a/public/components/metrics/redux/slices/metrics_slice.ts b/public/components/metrics/redux/slices/metrics_slice.ts index b372a86cff..f623585c37 100644 --- a/public/components/metrics/redux/slices/metrics_slice.ts +++ b/public/components/metrics/redux/slices/metrics_slice.ts @@ -3,8 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; -import { keyBy, mergeWith, pick, sortBy, merge } from 'lodash'; +import { createSlice } from '@reduxjs/toolkit'; +import { keyBy, mergeWith, pick, sortBy } from 'lodash'; import { ouiPaletteColorBlindBehindText } from '@elastic/eui'; import { OBSERVABILITY_CUSTOM_METRIC, @@ -18,7 +18,7 @@ import { ObservabilitySavedVisualization } from '../../../../services/saved_obje import { pplServiceRequestor } from '../../helpers/utils'; import { coreRefs } from '../../../../framework/core_refs'; import { PPL_METRIC_SUBTYPE, PROMQL_METRIC_SUBTYPE } from '../../../../../common/constants/shared'; -import { getOSDHttp, getPPLService } from '../../../../../common/utils'; +import { getPPLService } from '../../../../../common/utils'; export interface IconAttributes { color: string; @@ -38,6 +38,14 @@ const coloredIconsFrom = (dataSources: string[]): { [dataSource: string]: IconAt return Object.fromEntries(keyedIcons); }; +export interface DateSpanFilter { + start: string; + end: string; + span: number; + resolution: string; + recentlyUsedRanges: string[]; +} + const initialState = { metrics: {}, selectedIds: [], @@ -66,8 +74,13 @@ const mergeMetricCustomizer = function (objValue, srcValue) { : srcValue.availableAttributes, }; }; +export const mergeMetrics = (newMetricMap) => (dispatch, getState) => { + const { metrics } = getState().metrics; + const modifiableMetricsMap = { ...metrics }; -const now = () => new Date().getMilliseconds(); + const mergedMetrics = mergeWith(modifiableMetricsMap, newMetricMap, mergeMetricCustomizer); + dispatch(setMetrics(mergedMetrics)); +}; export const loadMetrics = () => async (dispatch) => { const pplService = getPPLService(); @@ -81,15 +94,15 @@ export const loadMetrics = () => async (dispatch) => { setDataSourceIcons(coloredIconsFrom([OBSERVABILITY_CUSTOM_METRIC, ...remoteDataSources])) ); - const remoteDataRequests = fetchRemoteMetrics(remoteDataSources); + const remoteDataRequests = await fetchRemoteMetrics(remoteDataSources); const metricsResultSet = await Promise.all([customDataRequest, ...remoteDataRequests]); const metricsResult = metricsResultSet.flat(); const metricsMapById = keyBy(metricsResult.flat(), 'id'); - await dispatch(mergeMetrics(metricsMapById)); + dispatch(mergeMetrics(metricsMapById)); const sortedIds = sortBy(metricsResult, 'catalog', 'id').map((m) => m.id); - await dispatch(setSortedIds(sortedIds)); + dispatch(setSortedIds(sortedIds)); }; const fetchCustomMetrics = async () => { @@ -150,17 +163,9 @@ export const metricSlice = createSlice({ name: REDUX_SLICE_METRICS, initialState, reducers: { - mergeMetrics: (state, { payload }) => { - const { metrics } = state; - const modifiableMetricsMap = { ...metrics }; - - const mergedMetrics = mergeWith(modifiableMetricsMap, payload, mergeMetricCustomizer); - state.metrics = mergedMetrics; + setMetrics: (state, { payload }) => { + state.metrics = payload; }, - - // setMetrics: (state, { payload }) => { - // state.metrics = payload; - // }, setMetric: (state, { payload }) => { state.metrics[payload.id] = payload; }, @@ -214,8 +219,7 @@ export const metricSlice = createSlice({ export const { deSelectMetric, clearSelectedMetrics, - - mergeMetrics, + selectMetric, moveMetric, setSearch, setDateSpan, @@ -227,7 +231,7 @@ export const { /** private actions */ -const { selectMetric, setMetric, setSortedIds } = metricSlice.actions; +const { setMetrics, setMetric, setSortedIds } = metricSlice.actions; const getAvailableAttributes = (id, metricIndex) => async (dispatch, getState) => { const { toasts } = coreRefs; @@ -300,16 +304,12 @@ export const availableMetricsSelector = (state) => { export const selectedMetricsSelector = (state) => pick(state.metrics.metrics, state.metrics.selectedIds) ?? {}; -export const selectedMetricByIdSelector = (id) => (state) => state.metrics.metrics[id]; - export const selectedMetricsIdsSelector = (state) => state.metrics.selectedIds ?? []; export const searchSelector = (state) => state.metrics.search; export const metricIconsSelector = (state) => state.metrics.dataSourceIcons; -export const metricsLayoutSelector = (state) => state.metrics.metricsLayout; - export const dateSpanFilterSelector = (state) => state.metrics.dateSpanFilter; export const refreshSelector = (state) => state.metrics.refresh; diff --git a/public/components/metrics/sidebar/sidebar.tsx b/public/components/metrics/sidebar/sidebar.tsx index 6afb40c1fb..ac93037fc5 100644 --- a/public/components/metrics/sidebar/sidebar.tsx +++ b/public/components/metrics/sidebar/sidebar.tsx @@ -43,10 +43,12 @@ export const Sidebar = ({ useEffect(() => { if (additionalMetric) { - dispatch(clearSelectedMetrics()); - dispatch(addSelectedMetric(additionalMetric)); + (async function () { + await dispatch(clearSelectedMetrics()); + await dispatch(addSelectedMetric(additionalMetric)); + })(); } - }, [additionalMetric]); + }, [additionalMetric?.id]); const selectedMetricsList = useMemo(() => { return selectedMetricsIds.map((id) => selectedMetrics[id]).filter((m) => m); // filter away null entries diff --git a/public/components/metrics/view/metrics_grid.tsx b/public/components/metrics/view/metrics_grid.tsx index 2c41224552..161431918d 100644 --- a/public/components/metrics/view/metrics_grid.tsx +++ b/public/components/metrics/view/metrics_grid.tsx @@ -4,7 +4,7 @@ */ import React, { useEffect, useMemo } from 'react'; -import { EuiDragDropContext, EuiDraggable, EuiDroppable } from '@elastic/eui'; +import { EuiContextMenuItem, EuiDragDropContext, EuiDraggable, EuiDroppable } from '@elastic/eui'; import { useObservable } from 'react-use'; import { connect } from 'react-redux'; import { CoreStart } from '../../../../../../src/core/public'; @@ -53,6 +53,19 @@ const visualizationFromMetric = (metric, dateSpanFilter): SavedVisualizationType }, }); +const promQLActionMenu = [ + { + closeActionsMenu(); + showModal('catalogModal'); + }} + > + View query + , +]; + const navigateToEventExplorerVisualization = (savedVisualizationId: string) => { window.location.assign(`${observabilityLogsID}#/explorer/${savedVisualizationId}`); }; @@ -80,6 +93,7 @@ export const InnerGridVisualization = ({ id, idx, dateSpanFilter, metric, refres inlineEditor={ metric.subType === PROMQL_METRIC_SUBTYPE && } + actionMenuType="metricsGrid" /> ); diff --git a/public/services/requests/ppl.ts b/public/services/requests/ppl.ts index 72fb58f33b..2ab46e8b32 100644 --- a/public/services/requests/ppl.ts +++ b/public/services/requests/ppl.ts @@ -9,6 +9,7 @@ import { PPL_BASE, PPL_SEARCH } from '../../../common/constants/shared'; /* eslint-disable import/no-default-export */ export default class PPLService { private http; + constructor(http: CoreStart['http']) { this.http = http; } @@ -20,7 +21,6 @@ export default class PPLService { }, errorHandler?: (error: any) => void ) => { - console.log('real fetch'); return this.http .post(`${PPL_BASE}${PPL_SEARCH}`, { body: JSON.stringify(params),