From c19259d639cb783b7e881fbdf1c2d8a9e86642a1 Mon Sep 17 00:00:00 2001 From: Peter Fitzgibbons Date: Thu, 7 Dec 2023 04:24:39 -0800 Subject: [PATCH 1/8] Metrics Explorer updated with PromQL query for Prometheus Metrics and updated Export Panel Signed-off-by: Peter Fitzgibbons --- .cypress/integration/3_panels.spec.ts | 108 +++-- .../integration/8_metrics_analytics.spec.js | 8 +- common/constants/metrics.ts | 15 +- common/constants/shared.ts | 6 + common/types/custom_panels.ts | 2 +- common/types/explorer.ts | 26 +- common/types/metrics.ts | 14 +- .../application_analytics/helpers/utils.tsx | 4 +- public/components/common/query_utils/index.ts | 54 ++- public/components/common/search/search.tsx | 10 +- .../custom_panels/custom_panel_view_so.tsx | 14 +- .../__snapshots__/utils.test.tsx.snap | 160 +++++-- .../custom_panels/helpers/utils.tsx | 154 +++---- public/components/custom_panels/home.tsx | 19 +- .../panel_modules/panel_grid/panel_grid.tsx | 3 +- .../panel_grid/panel_grid_so.tsx | 2 + .../visualization_container.test.tsx.snap | 243 ++++++----- .../visualization_container.test.tsx | 39 +- .../visualization_container.scss | 41 +- .../visualization_container.tsx | 76 +++- .../custom_panels/redux/panel_slice.ts | 125 ++++-- .../event_analytics/explorer/explorer.tsx | 60 ++- .../explorer/save_panel/save_panel.tsx | 3 +- .../__snapshots__/sidebar.test.tsx.snap | 238 ++++------- .../__snapshots__/config_panel.test.tsx.snap | 110 ++--- .../config_panes/default_vis_editor.tsx | 2 +- .../home/saved_objects_table.tsx | 14 +- .../event_analytics/utils/utils.tsx | 43 +- .../metrics/helpers/__tests__/utils.test.tsx | 51 +-- public/components/metrics/helpers/utils.tsx | 175 ++------ public/components/metrics/index.tsx | 63 +-- .../metrics/redux/slices/metrics_slice.ts | 241 +++++++---- .../metrics/sidebar/metrics_accordion.tsx | 13 +- .../metrics/sidebar/metrics_edit_inline.tsx | 78 ++++ .../components/metrics/sidebar/search_bar.tsx | 2 +- public/components/metrics/sidebar/sidebar.tsx | 37 +- .../metrics_export_panel.test.tsx.snap | 12 +- .../metrics/top_menu/metrics_export.tsx | 399 ++++++++++++++++++ .../metrics/top_menu/metrics_export_panel.tsx | 87 ++-- .../components/metrics/top_menu/top_menu.tsx | 302 ++----------- .../components/metrics/view/metrics_grid.tsx | 230 +++++----- .../__snapshots__/para_output.test.tsx.snap | 2 +- .../__snapshots__/paragraphs.test.tsx.snap | 2 +- .../paragraph_components/para_output.tsx | 245 +++++------ .../__tests__/__snapshots__/bar.test.tsx.snap | 8 +- .../__snapshots__/heatmap.test.tsx.snap | 4 +- .../__snapshots__/histogram.test.tsx.snap | 8 +- .../horizontal_bar.test.tsx.snap | 8 +- .../__snapshots__/line.test.tsx.snap | 8 +- .../__tests__/__snapshots__/pie.test.tsx.snap | 8 +- .../__snapshots__/text.test.tsx.snap | 4 +- .../__snapshots__/treemap.test.tsx.snap | 4 +- .../saved_object_visualization.tsx | 51 ++- .../visualizations/visualization.tsx | 21 +- .../visualizations/visualization_chart.tsx | 6 +- .../embeddable/observability_embeddable.tsx | 30 +- .../observability_embeddable_component.tsx | 1 + public/framework/core_refs.ts | 3 + public/plugin.ts | 1 + .../event_analytics/saved_objects.ts | 8 +- .../osd_saved_object_client.ts | 9 +- .../osd_saved_objects/saved_visualization.ts | 3 + .../saved_object_client/ppl/ppl_client.ts | 12 +- .../explorer_saved_object_loader.ts | 9 +- .../saved_object_loaders/ppl/ppl_loader.ts | 9 +- .../custom_panels/custom_panel_adaptor.ts | 15 +- .../event_analytics/event_analytics_router.ts | 12 +- test/metrics_contants.ts | 54 ++- 68 files changed, 2138 insertions(+), 1690 deletions(-) create mode 100644 public/components/metrics/sidebar/metrics_edit_inline.tsx create mode 100644 public/components/metrics/top_menu/metrics_export.tsx diff --git a/.cypress/integration/3_panels.spec.ts b/.cypress/integration/3_panels.spec.ts index 7096f5aa8..cad62d989 100644 --- a/.cypress/integration/3_panels.spec.ts +++ b/.cypress/integration/3_panels.spec.ts @@ -17,7 +17,7 @@ import { } from '../utils/panel_constants'; describe('Panels testing with Sample Data', () => { - suppressResizeObserverIssue();//needs to be in file once + suppressResizeObserverIssue(); //needs to be in file once before(() => { cy.visit(`${Cypress.env('opensearchDashboards')}/app/home#/tutorial_directory/sampleData`); @@ -30,7 +30,7 @@ describe('Panels testing with Sample Data', () => { beforeEach(() => { eraseTestPanels(); eraseSavedVisualizations(); - }) + }); after(() => { eraseTestPanels(); @@ -54,8 +54,8 @@ describe('Panels testing with Sample Data', () => { .click({ force: true }); cy.get('[data-test-subj="eventExplorer__saveManagementPopover"]') .trigger('mouseover') - .click({force: true}); - cy.wait(delay*5); //Wont save as correct name without wait + .click({ force: true }); + cy.wait(delay * 5); //Wont save as correct name without wait cy.get('[data-test-subj="eventExplorer__querySaveName"]') .focus() .type(PPL_VISUALIZATIONS_NAMES[0]); @@ -151,7 +151,7 @@ describe('Panels testing with Sample Data', () => { it('Searches panels', () => { createLegacyPanel('Legacy Named'); createSavedObjectPanel('Saved Object'); - cy.wait(delay);//Needed so the panel appears on the dashboard page + cy.wait(delay); //Needed so the panel appears on the dashboard page cy.reload(); cy.get('input[data-test-subj="operationalPanelSearchBar"]') .focus() @@ -203,7 +203,7 @@ describe('Panels testing with Sample Data', () => { it('Deletes the panel', () => { createSavedObjectPanel(); - cy.get('a[data-test-subj="breadcrumb last"]').click();//refresh so panel appears + cy.get('a[data-test-subj="breadcrumb last"]').click(); //refresh so panel appears cy.get('input[data-test-subj="checkboxSelectAll"]').click(); openActionsDropdown(); cy.get('button[data-test-subj="deleteContextMenuItem"]').click({ force: true }); @@ -251,7 +251,7 @@ describe('Panels testing with Sample Data', () => { beforeEach(() => { const test_name = `test_${new Date().getTime()}`; createSavedObjectPanel(test_name).as('thePanel'); - cy.then(function (){ + cy.then(function () { moveToThePanel(this.thePanel.id); }); }); @@ -264,19 +264,23 @@ describe('Panels testing with Sample Data', () => { it('Redirects to correct page on breadcrumb click', () => { cy.get('a[data-test-subj="breadcrumb last"]').click(); - cy.then(function (){ - cy.get('h1[data-test-subj="panelNameHeader"]').contains(this.thePanel.attributes.title).should('exist'); + cy.then(function () { + cy.get('h1[data-test-subj="panelNameHeader"]') + .contains(this.thePanel.attributes.title) + .should('exist'); }); }); it('Duplicate the open panel', () => { cy.get('button[data-test-subj="panelActionContextMenu"]').click(); cy.get('button[data-test-subj="duplicatePanelContextMenuItem"]').click(); - cy.then(function (){ - cy.get(`input.euiFieldText[value="${this.thePanel.attributes.title} (copy)"]`).should('exist'); + cy.then(function () { + cy.get(`input.euiFieldText[value="${this.thePanel.attributes.title} (copy)"]`).should( + 'exist' + ); }); cy.get('button[data-test-subj="runModalButton"]').click(); - cy.then(function (){ + cy.then(function () { cy.get('h1[data-test-subj="panelNameHeader"]') .contains(this.thePanel.attributes.title + ' (copy)') .should('exist'); @@ -284,9 +288,11 @@ describe('Panels testing with Sample Data', () => { }); it('Rename the open panel', () => { - cy.then(function (){ + cy.then(function () { cy.get('[data-test-subj="breadcrumb"]').click({ force: true }); - cy.get('input[data-test-subj="operationalPanelSearchBar"]').focus().type(this.thePanel.attributes.title); + cy.get('input[data-test-subj="operationalPanelSearchBar"]') + .focus() + .type(this.thePanel.attributes.title); cy.get('a.euiLink').contains(this.thePanel.attributes.title).click(); cy.get('button[data-test-subj="panelActionContextMenu"]').click(); cy.get('button[data-test-subj="renamePanelContextMenuItem"]').click(); @@ -295,15 +301,19 @@ describe('Panels testing with Sample Data', () => { .clear({ force: true }) .focus() .type('Renamed Panel'); - }); + }); cy.get('button[data-test-subj="runModalButton"]').click(); cy.get('h1[data-test-subj="panelNameHeader"]').contains('Renamed Panel').should('exist'); }); it('Change date filter of the panel', () => { - cy.get('.euiButtonEmpty[data-test-subj="superDatePickerToggleQuickMenuButton"]').click({force: true}); - cy.wait(delay);//flyout won't open sometimes without - cy.get('button[data-test-subj="superDatePickerCommonlyUsed_This_year"]').click({force: true}); + cy.get('.euiButtonEmpty[data-test-subj="superDatePickerToggleQuickMenuButton"]').click({ + force: true, + }); + cy.wait(delay); //flyout won't open sometimes without + cy.get('button[data-test-subj="superDatePickerCommonlyUsed_This_year"]').click({ + force: true, + }); cy.get('button[data-test-subj="superDatePickerShowDatesButton"]') .contains('This year') .should('exist'); @@ -320,7 +330,7 @@ describe('Panels testing with Sample Data', () => { cy.get('select').select(PPL_VISUALIZATIONS_NAMES[0]); cy.get('button[aria-label="refreshPreview"]').trigger('mouseover').click(); cy.get('.plot-container').should('exist'); - cy.get('button[data-test-subj="addFlyoutButton"]').click({force: true}); + cy.get('button[data-test-subj="addFlyoutButton"]').click({ force: true }); cy.get('.euiToastHeader__title').contains('successfully').should('exist'); }); @@ -335,7 +345,7 @@ describe('Panels testing with Sample Data', () => { cy.get('select').select(PPL_VISUALIZATIONS_NAMES[1]); cy.get('button[aria-label="refreshPreview"]').trigger('mouseover').click(); cy.get('.plot-container').should('exist'); - cy.get('button[data-test-subj="addFlyoutButton"]').click({force: true}); + cy.get('button[data-test-subj="addFlyoutButton"]').click({ force: true }); cy.get('.euiToastHeader__title').contains('successfully').should('exist'); }); @@ -350,11 +360,15 @@ describe('Panels testing with Sample Data', () => { addVisualizationsToPanel(this.thePanel, [this.vis1.id]); moveToThePanel(this.thePanel.id); cy.get('[data-test-subj="breadcrumb"]').click({ force: true }); - cy.get('input[data-test-subj="operationalPanelSearchBar"]').focus().type(this.thePanel.attributes.title); + cy.get('input[data-test-subj="operationalPanelSearchBar"]') + .focus() + .type(this.thePanel.attributes.title); cy.get('a.euiLink').contains(this.thePanel.attributes.title).click(); }); - cy.get('.euiButtonEmpty[data-test-subj="superDatePickerToggleQuickMenuButton"]').click({force: true,}); + cy.get('.euiButtonEmpty[data-test-subj="superDatePickerToggleQuickMenuButton"]').click({ + force: true, + }); cy.get('[data-test-subj="superDatePickerQuickMenu"') .first() .within(() => { @@ -365,11 +379,11 @@ describe('Panels testing with Sample Data', () => { cy.get('[data-test-subj="searchAutocompleteTextArea"]') .trigger('mouseover') - .click({force: true}) - .wait(delay*5) + .click({ force: true }) + .wait(delay * 5) .focus() .type(PPL_FILTER); - cy.get('button[data-test-subj="superDatePickerApplyTimeButton"]').click({force: true}); + cy.get('button[data-test-subj="superDatePickerApplyTimeButton"]').click({ force: true }); cy.get('.euiButton__text').contains('Refresh').trigger('mouseover').click(); cy.get('.xtick').should('contain', 'Munich Airport'); cy.get('.xtick').contains('Zurich Airport').should('not.exist'); @@ -389,7 +403,9 @@ describe('Panels testing with Sample Data', () => { addVisualizationsToPanel(this.thePanel, [this.vis1.id]); moveToThePanel(this.thePanel.id); cy.get('[data-test-subj="breadcrumb"]').click({ force: true }); - cy.get('input[data-test-subj="operationalPanelSearchBar"]').focus().type(this.thePanel.attributes.title); + cy.get('input[data-test-subj="operationalPanelSearchBar"]') + .focus() + .type(this.thePanel.attributes.title); cy.get('a.euiLink').contains(this.thePanel.attributes.title).click(); }); @@ -419,7 +435,9 @@ describe('Panels testing with Sample Data', () => { addVisualizationsToPanel(this.thePanel, [this.vis1.id]); moveToThePanel(this.thePanel.id); cy.get('[data-test-subj="breadcrumb"]').click({ force: true }); - cy.get('input[data-test-subj="operationalPanelSearchBar"]').focus().type(this.thePanel.attributes.title); + cy.get('input[data-test-subj="operationalPanelSearchBar"]') + .focus() + .type(this.thePanel.attributes.title); cy.get('a.euiLink').contains(this.thePanel.attributes.title).click(); }); @@ -446,7 +464,9 @@ describe('Panels testing with Sample Data', () => { addVisualizationsToPanel(this.thePanel, [this.vis1.id]); moveToThePanel(this.thePanel.id); cy.get('[data-test-subj="breadcrumb"]').click({ force: true }); - cy.get('input[data-test-subj="operationalPanelSearchBar"]').focus().type(this.thePanel.attributes.title); + cy.get('input[data-test-subj="operationalPanelSearchBar"]') + .focus() + .type(this.thePanel.attributes.title); cy.get('a.euiLink').contains(this.thePanel.attributes.title).click(); }); @@ -470,7 +490,9 @@ describe('Panels testing with Sample Data', () => { addVisualizationsToPanel(this.thePanel, [this.vis1.id]); moveToThePanel(this.thePanel.id); cy.get('[data-test-subj="breadcrumb"]').click({ force: true }); - cy.get('input[data-test-subj="operationalPanelSearchBar"]').focus().type(this.thePanel.attributes.title); + cy.get('input[data-test-subj="operationalPanelSearchBar"]') + .focus() + .type(this.thePanel.attributes.title); cy.get('a.euiLink').contains(this.thePanel.attributes.title).click(); }); @@ -503,15 +525,17 @@ describe('Panels testing with Sample Data', () => { PPL_VISUALIZATIONS[2], PPL_VISUALIZATION_CONFIGS[2] ).as('vis2'); - + cy.then(function () { addVisualizationsToPanel(this.thePanel, [this.vis1.id]); moveToThePanel(this.thePanel.id); cy.get('[data-test-subj="breadcrumb"]').click({ force: true }); - cy.get('input[data-test-subj="operationalPanelSearchBar"]').focus().type(this.thePanel.attributes.title); + cy.get('input[data-test-subj="operationalPanelSearchBar"]') + .focus() + .type(this.thePanel.attributes.title); cy.get('a.euiLink').contains(this.thePanel.attributes.title).click({ force: true }); }); - + cy.get('button[aria-label="actionMenuButton"]').eq(0).click(); cy.get('button[data-test-subj="replaceVizContextMenuItem"]').click(); cy.get('select').select(PPL_VISUALIZATIONS_NAMES[2]); @@ -540,18 +564,20 @@ describe('Panels testing with Sample Data', () => { cy.get('[data-test-subj="eventExplorer__saveManagementPopover"]') .trigger('mouseover') .click(); - - cy.then(function () { - cy.get('[data-test-subj="eventExplorer__querySaveComboBox"]').type(this.thePanel.attributes.title); - cy.get(`input[value="${this.thePanel.attributes.title}"]`).trigger('mouseover').click(); - }); + + cy.then(function () { + cy.get('[data-test-subj="eventExplorer__querySaveComboBox"]').type( + this.thePanel.attributes.title + ); + cy.get(`input[value="${this.thePanel.attributes.title}"]`).trigger('mouseover').click(); + }); cy.get('[data-test-subj="eventExplorer__querySaveName"]') .focus() .type(PPL_VISUALIZATIONS_NAMES[2]); cy.get('[data-test-subj="eventExplorer__querySaveConfirm"]').trigger('mouseover').click(); cy.get('.euiToastHeader__title').contains('successfully').should('exist'); - + cy.then(function () { moveToThePanel(this.thePanel.id); }); @@ -572,7 +598,9 @@ describe('Panels testing with Sample Data', () => { addVisualizationsToPanel(this.thePanel, [this.vis1.id]); moveToThePanel(this.thePanel.id); cy.get('[data-test-subj="breadcrumb"]').click({ force: true }); - cy.get('input[data-test-subj="operationalPanelSearchBar"]').focus().type(this.thePanel.attributes.title); + cy.get('input[data-test-subj="operationalPanelSearchBar"]') + .focus() + .type(this.thePanel.attributes.title); cy.get('a.euiLink').contains(this.thePanel.attributes.title).click(); }); @@ -813,7 +841,7 @@ const createVisualization = (newName, query, vizConfig) => { description: '', type: 'bar', user_configs: vizConfig, - sub_type: 'visualization', + subType: 'visualization', }, }, }, diff --git a/.cypress/integration/8_metrics_analytics.spec.js b/.cypress/integration/8_metrics_analytics.spec.js index 255a35434..768c62294 100644 --- a/.cypress/integration/8_metrics_analytics.spec.js +++ b/.cypress/integration/8_metrics_analytics.spec.js @@ -9,11 +9,11 @@ import { delay, PPL_METRICS, PPL_METRICS_NAMES, - VIS_TYPE_LINE, TESTING_PANEL, + VIS_TYPE_LINE, } from '../utils/metrics_constants'; -import { suppressResizeObserverIssue, COMMAND_TIMEOUT_LONG } from '../utils/constants'; -import { landOnPanels, clearQuerySearchBoxText } from '../utils/event_analytics/helpers'; +import { COMMAND_TIMEOUT_LONG, suppressResizeObserverIssue } from '../utils/constants'; +import { clearQuerySearchBoxText, landOnPanels } from '../utils/event_analytics/helpers'; describe('Metrics Analytics', () => { beforeEach(() => { @@ -281,7 +281,7 @@ const createSavedObjectMetric = ({ testMetricIndex }) => { name: PPL_METRICS_NAMES[testMetricIndex], description: '', type: 'line', - sub_type: 'metric', + subType: 'metric', }, }, }, diff --git a/common/constants/metrics.ts b/common/constants/metrics.ts index 853ef2865..7e92e6663 100644 --- a/common/constants/metrics.ts +++ b/common/constants/metrics.ts @@ -3,6 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ +export const METRIC_EXPLORER_BASE_PATH = 'observability-metrics#/'; + // requests constants export const VISUALIZATION = 'viz'; export const SAVED_VISUALIZATION = 'savedVisualization'; @@ -24,13 +26,10 @@ export const resolutionOptions = [ { value: 'y', text: 'years' }, ]; -export const DEFAULT_METRIC_HEIGHT = 2; -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 211645485..9baffcf10 100644 --- a/common/constants/shared.ts +++ b/common/constants/shared.ts @@ -78,6 +78,10 @@ export const PPL_PATTERNS_DOCUMENTATION_URL = export const UI_DATE_FORMAT = 'MM/DD/YYYY hh:mm A'; export const PPL_DATE_FORMAT = 'YYYY-MM-DD HH:mm:ss.SSSSSS'; export const SPAN_REGEX = /span/; + +export const PROMQL_METRIC_SUBTYPE = 'promqlmetric'; +export const PPL_METRIC_SUBTYPE = 'metric'; + export const PPL_SPAN_REGEX = /by\s*span/i; export const PPL_STATS_REGEX = /\|\s*stats/i; export const PPL_INDEX_INSERT_POINT_REGEX = /(search source|source|index)\s*=\s*([^|\s]+)(.*)/i; @@ -190,6 +194,7 @@ export const LIVE_OPTIONS = [ ]; export const LIVE_END_TIME = 'now'; + export interface DefaultChartStylesProps { DefaultModeLine: string; Interpolation: string; @@ -243,6 +248,7 @@ export const VISUALIZATION_ERROR = { NO_DATA: 'No data found.', INVALID_DATA: 'Invalid visualization data', NO_SERIES: 'Add a field to start', + NO_METRIC: 'Invalid Metric MetaData', }; export const S3_DATASOURCE_TYPE = 'S3_DATASOURCE'; diff --git a/common/types/custom_panels.ts b/common/types/custom_panels.ts index a24cbe839..11834906c 100644 --- a/common/types/custom_panels.ts +++ b/common/types/custom_panels.ts @@ -50,7 +50,7 @@ export interface SavedVisualizationType { selected_date_range: { start: string; end: string; text: string }; timeField: string; application_id?: string; - user_configs: any; + userConfigs: any; } export interface PPLResponse { diff --git a/common/types/explorer.ts b/common/types/explorer.ts index 9daf69074..c46a65249 100644 --- a/common/types/explorer.ts +++ b/common/types/explorer.ts @@ -8,23 +8,23 @@ import Plotly from 'plotly.js-dist'; import { QueryManager } from 'common/query_manager'; import { VIS_CHART_TYPES } from '../../common/constants/shared'; import { - RAW_QUERY, - SELECTED_FIELDS, - UNSELECTED_FIELDS, + AGGREGATIONS, AVAILABLE_FIELDS, - QUERIED_FIELDS, - INDEX, + BREAKDOWNS, + CUSTOM_LABEL, FINAL_QUERY, - SELECTED_TIMESTAMP, - SELECTED_DATE_RANGE, GROUPBY, - AGGREGATIONS, - CUSTOM_LABEL, - BREAKDOWNS, + INDEX, + QUERIED_FIELDS, + RAW_QUERY, + SELECTED_DATE_RANGE, + SELECTED_FIELDS, + SELECTED_TIMESTAMP, + UNSELECTED_FIELDS, } from '../constants/explorer'; import { - CoreStart, CoreSetup, + CoreStart, HttpSetup, HttpStart, NotificationsStart, @@ -39,6 +39,7 @@ import { } from '../../../../src/core/public/saved_objects'; import { ChromeBreadcrumb } from '../../../../src/core/public/chrome'; import { DataSourceType } from '../../../../src/plugins/data/public'; +import { PROMQL_METRIC_SUBTYPE } from '../constants/shared'; export interface IQueryTab { id: string; @@ -173,7 +174,7 @@ export interface SavedVisualization extends SavedObjectAttributes { selected_fields: { text: string; tokens: [] }; selected_timestamp: IField; type: string; - sub_type?: 'metric' | 'visualization'; // exists if sub type is metric + subType?: 'metric' | 'visualization' | typeof PROMQL_METRIC_SUBTYPE; // exists if sub type is metric user_configs?: string; units_of_measure?: string; application_id?: string; @@ -346,6 +347,7 @@ export interface DataConfigPanelProps { visualizations: IVisualizationContainerProps; queryManager?: QueryManager; } + export interface GetTooltipHoverInfoType { tooltipMode: string; tooltipText: string; 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..8c66cc215 100644 --- a/public/components/application_analytics/helpers/utils.tsx +++ b/public/components/application_analytics/helpers/utils.tsx @@ -218,7 +218,9 @@ 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.userConfigs + ? JSON.parse(visData.user_configs || visData.userConfigs) + : {}; // 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/common/query_utils/index.ts b/public/components/common/query_utils/index.ts index 9f4024bb3..819ba3bc2 100644 --- a/public/components/common/query_utils/index.ts +++ b/public/components/common/query_utils/index.ts @@ -6,7 +6,7 @@ import dateMath from '@elastic/datemath'; import { Moment } from 'moment-timezone'; import { isEmpty } from 'lodash'; -import { SearchMetaData } from 'public/components/event_analytics/redux/slices/search_meta_data_slice'; +import { SearchMetaData } from '../../event_analytics/redux/slices/search_meta_data_slice'; import { PPL_DEFAULT_PATTERN_REGEX_FILETER, SELECTED_DATE_RANGE, @@ -20,6 +20,7 @@ import { PPL_NEWLINE_REGEX, } from '../../../../common/constants/shared'; import { IExplorerFields, IQuery } from '../../../../common/types/explorer'; +import { updateCatalogVisualizationQuery } from '../../custom_panels/helpers/utils'; /* * "Query Utils" This file contains different reused functions in operational panels @@ -35,6 +36,32 @@ const escapeQuotes = (literal: string) => { return literal.replaceAll("'", "''"); }; +export const findMinInterval = (start: string = '', end: string = '') => { + const momentStart = dateMath.parse(start)!; + const momentEnd = dateMath.parse(end, { roundUp: true })!; + const diffSeconds = momentEnd.unix() - momentStart.unix(); + let minInterval = 'y'; + + // less than 1 second + if (diffSeconds <= 1) minInterval = 'ms'; + // less than 2 minutes + else if (diffSeconds <= 60 * 2) minInterval = 's'; + // less than 2 hours + else if (diffSeconds <= 3600 * 2) minInterval = 'm'; + // less than 2 days + else if (diffSeconds <= 86400 * 2) minInterval = 'h'; + // less than 1 month + else if (diffSeconds <= 86400 * 31) minInterval = 'd'; + // less than 3 months + else if (diffSeconds <= 86400 * 93) minInterval = 'w'; + // less than 2 year + else if (diffSeconds <= 86400 * 366) minInterval = 'w'; + + console.log('findMinInterval', { momentStart, momentEnd, diffSeconds, minInterval }); + + return minInterval; +}; + export const convertDateTime = ( datetime: string, isStart = true, @@ -128,13 +155,6 @@ export const updatePromQLQueryFilters = ( const { connection, metric, aggregation, attributesGroupBy } = parsePromQLIntoKeywords( promQLQuery ); - console.log('updatePromQLQueryFilters', { - connection, - metric, - aggregation, - attributesGroupBy, - promQLQuery, - }); const promQLPart = buildPromQLFromMetricQuery({ metric, attributesGroupBy: attributesGroupBy.split(','), @@ -157,6 +177,24 @@ export const getIndexPatternFromRawQuery = (query: string): string => { return getPromQLIndex(query) || getPPLIndex(query); }; +export const preprocessMetricQuery = ({ metaData, startTime, endTime }) => { + // convert to moment + const start = convertDateTime(startTime, true); + const end = convertDateTime(endTime, false); + + const resolution = findMinInterval(start, end); + + const visualizationQuery = updateCatalogVisualizationQuery({ + ...metaData.queryMetaData, + start, + end, + span: '1', + resolution, + }); + + return visualizationQuery; +}; + // insert time filter command and additional commands based on raw query export const preprocessQuery = ({ rawQuery, diff --git a/public/components/common/search/search.tsx b/public/components/common/search/search.tsx index de450d09c..c849c25cb 100644 --- a/public/components/common/search/search.tsx +++ b/public/components/common/search/search.tsx @@ -323,9 +323,13 @@ export const Search = (props: any) => { } curVisId={curVisId} setSubType={setSubType} - isSaveAsMetricEnabled={ - isEqual(curVisId, 'line') && tempQuery.match(PPL_SPAN_REGEX) !== null - } + isSaveAsMetricEnabled={() => { + return ( + isEqual(curVisId, 'line') && + tempQuery && + tempQuery.match(PPL_SPAN_REGEX) !== null + ); + }} /> diff --git a/public/components/custom_panels/custom_panel_view_so.tsx b/public/components/custom_panels/custom_panel_view_so.tsx index 65b10416a..382bc50d0 100644 --- a/public/components/custom_panels/custom_panel_view_so.tsx +++ b/public/components/custom_panels/custom_panel_view_so.tsx @@ -45,7 +45,6 @@ import { addVisualizationPanel } from './helpers/add_visualization_helper'; import { AddVisualizationPopover } from './helpers/add_visualization_popover'; import { getCustomModal } from './helpers/modal_containers'; import { - convertDateTime, isDateValid, isNameValid, isPPLFilterValid, @@ -66,7 +65,8 @@ import { import { useToast } from '../common/toast'; import PPLService from '../../services/requests/ppl'; import DSLService from '../../services/requests/dsl'; - +import { convertDateTime } from '../common/query_utils'; + /* * "CustomPanelsView" module used to render an Observability Dashboard * @@ -181,7 +181,9 @@ export const CustomPanelViewSO = (props: CustomPanelViewProps) => { timeProps.end, recentlyUsedRanges ); - dispatch(updatePanel({ ...panel, timeRange: { from: timeProps.start, to: timeProps.end } }, '', '')); + dispatch( + updatePanel({ ...panel, timeRange: { from: timeProps.start, to: timeProps.end } }, '', '') + ); setRecentlyUsedRanges(updatedRanges.slice(0, 9)); onRefreshFilters(timeProps.start, timeProps.end); @@ -354,7 +356,11 @@ export const CustomPanelViewSO = (props: CustomPanelViewProps) => { }; const cloneVisualization = (visualzationTitle: string, savedVisualizationId: string) => { - addVisualizationToCurrentPanel({ savedVisualizationId, successMsg: `Visualization ${visualzationTitle} successfully added!`, failureMsg: `Error in adding ${visualzationTitle} visualization to the panel` }); + addVisualizationToCurrentPanel({ + savedVisualizationId, + successMsg: `Visualization ${visualzationTitle} successfully added!`, + failureMsg: `Error in adding ${visualzationTitle} visualization to the panel`, + }); }; const cancelButton = ( diff --git a/public/components/custom_panels/helpers/__tests__/__snapshots__/utils.test.tsx.snap b/public/components/custom_panels/helpers/__tests__/__snapshots__/utils.test.tsx.snap index adf75a180..bd5d1ac5d 100644 --- a/public/components/custom_panels/helpers/__tests__/__snapshots__/utils.test.tsx.snap +++ b/public/components/custom_panels/helpers/__tests__/__snapshots__/utils.test.tsx.snap @@ -257,7 +257,7 @@ exports[`Utils helper functions renders displayVisualization function 1`] = ` "props": Object { "defaultSelections": Array [ Object { - "id": "v", + "id": "h", "name": "Right", }, ], @@ -420,7 +420,7 @@ exports[`Utils helper functions renders displayVisualization function 1`] = ` "id": "bar", "label": "Vertical bar", "labelangle": 0, - "legendposition": "v", + "legendposition": "h", "linewidth": 0, "mode": "group", "name": "bar", @@ -712,7 +712,7 @@ exports[`Utils helper functions renders displayVisualization function 1`] = ` "props": Object { "defaultSelections": Array [ Object { - "id": "v", + "id": "h", "name": "Right", }, ], @@ -875,7 +875,7 @@ exports[`Utils helper functions renders displayVisualization function 1`] = ` "id": "bar", "label": "Vertical bar", "labelangle": 0, - "legendposition": "v", + "legendposition": "h", "linewidth": 0, "mode": "group", "name": "bar", @@ -919,7 +919,23 @@ exports[`Utils helper functions renders displayVisualization function 1`] = ` "responsive": true, } } - layout={[Function]} + layout={ + Object { + "height": 1180, + "legend": Object { + "orientation": "v", + "traceorder": "normal", + }, + "margin": Object { + "b": 30, + "l": 60, + "pad": 0, + "r": 30, + "t": 50, + }, + "showlegend": true, + } + } visualizations={ Object { "data": Object { @@ -1174,7 +1190,7 @@ exports[`Utils helper functions renders displayVisualization function 1`] = ` "props": Object { "defaultSelections": Array [ Object { - "id": "v", + "id": "h", "name": "Right", }, ], @@ -1337,7 +1353,7 @@ exports[`Utils helper functions renders displayVisualization function 1`] = ` "id": "bar", "label": "Vertical bar", "labelangle": 0, - "legendposition": "v", + "legendposition": "h", "linewidth": 0, "mode": "group", "name": "bar", @@ -1433,9 +1449,11 @@ exports[`Utils helper functions renders displayVisualization function 1`] = ` "#BD6F26", "#4C636F", ], + "height": 1180, "hovermode": "closest", "legend": Object { - "orientation": "v", + "orientation": "h", + "traceorder": "normal", }, "margin": Object { "b": 30, @@ -1526,9 +1544,11 @@ exports[`Utils helper functions renders displayVisualization function 1`] = ` "#BD6F26", "#4C636F", ], + "height": 1180, "hovermode": "closest", "legend": Object { - "orientation": "v", + "orientation": "h", + "traceorder": "normal", }, "margin": Object { "b": 30, @@ -1792,7 +1812,7 @@ exports[`Utils helper functions renders displayVisualization function 2`] = ` "props": Object { "defaultSelections": Array [ Object { - "id": "v", + "id": "h", "name": "Right", }, ], @@ -2308,7 +2328,7 @@ exports[`Utils helper functions renders displayVisualization function 2`] = ` "props": Object { "defaultSelections": Array [ Object { - "id": "v", + "id": "h", "name": "Right", }, ], @@ -2628,7 +2648,47 @@ exports[`Utils helper functions renders displayVisualization function 2`] = ` }, } } - layout={[Function]} + layout={ + Object { + "colorway": Array [ + "#3CA1C7", + "#8C55A3", + "#DB748A", + "#F2BE4B", + "#68CCC2", + "#2A7866", + "#843769", + "#374FB8", + "#BD6F26", + "#4C636F", + ], + "height": 1180, + "legend": Object { + "orientation": "v", + "traceorder": "normal", + }, + "margin": Object { + "b": 30, + "l": 60, + "pad": 0, + "r": 30, + "t": 50, + }, + "paper_bgcolor": "rgba(0, 0, 0, 0)", + "plot_bgcolor": "rgba(0, 0, 0, 0)", + "showlegend": true, + "xaxis": Object { + "fixedrange": true, + "showgrid": false, + "visible": true, + }, + "yaxis": Object { + "fixedrange": true, + "showgrid": false, + "visible": true, + }, + } + } visualizations={ Object { "data": Object { @@ -2838,7 +2898,7 @@ exports[`Utils helper functions renders displayVisualization function 2`] = ` "props": Object { "defaultSelections": Array [ Object { - "id": "v", + "id": "h", "name": "Right", }, ], @@ -3205,8 +3265,22 @@ exports[`Utils helper functions renders displayVisualization function 2`] = ` layout={ Object { "autosize": true, + "colorway": Array [ + "#3CA1C7", + "#8C55A3", + "#DB748A", + "#F2BE4B", + "#68CCC2", + "#2A7866", + "#843769", + "#374FB8", + "#BD6F26", + "#4C636F", + ], + "height": 1180, "legend": Object { - "orientation": "v", + "orientation": "h", + "traceorder": "normal", }, "margin": Object { "b": 30, @@ -3215,6 +3289,8 @@ exports[`Utils helper functions renders displayVisualization function 2`] = ` "r": 5, "t": 50, }, + "paper_bgcolor": "rgba(0, 0, 0, 0)", + "plot_bgcolor": "rgba(0, 0, 0, 0)", "shapes": Array [], "showlegend": true, "title": "", @@ -3295,9 +3371,23 @@ exports[`Utils helper functions renders displayVisualization function 2`] = ` Object { "autosize": true, "barmode": "stack", + "colorway": Array [ + "#3CA1C7", + "#8C55A3", + "#DB748A", + "#F2BE4B", + "#68CCC2", + "#2A7866", + "#843769", + "#374FB8", + "#BD6F26", + "#4C636F", + ], + "height": 1180, "hovermode": "closest", "legend": Object { - "orientation": "v", + "orientation": "h", + "traceorder": "normal", }, "margin": Object { "b": 30, @@ -3306,6 +3396,8 @@ exports[`Utils helper functions renders displayVisualization function 2`] = ` "r": 5, "t": 50, }, + "paper_bgcolor": "rgba(0, 0, 0, 0)", + "plot_bgcolor": "rgba(0, 0, 0, 0)", "shapes": Array [], "showlegend": true, "title": "", @@ -3602,7 +3694,7 @@ exports[`Utils helper functions renders displayVisualization function 3`] = ` "props": Object { "defaultSelections": Array [ Object { - "id": "v", + "id": "h", "name": "Right", }, ], @@ -3765,7 +3857,7 @@ exports[`Utils helper functions renders displayVisualization function 3`] = ` "id": "horizontal_bar", "label": "Horizontal bar", "labelangle": 0, - "legendposition": "v", + "legendposition": "h", "linewidth": 0, "mode": "group", "name": "horizontal_bar", @@ -4057,7 +4149,7 @@ exports[`Utils helper functions renders displayVisualization function 3`] = ` "props": Object { "defaultSelections": Array [ Object { - "id": "v", + "id": "h", "name": "Right", }, ], @@ -4220,7 +4312,7 @@ exports[`Utils helper functions renders displayVisualization function 3`] = ` "id": "horizontal_bar", "label": "Horizontal bar", "labelangle": 0, - "legendposition": "v", + "legendposition": "h", "linewidth": 0, "mode": "group", "name": "horizontal_bar", @@ -4264,7 +4356,23 @@ exports[`Utils helper functions renders displayVisualization function 3`] = ` "responsive": true, } } - layout={[Function]} + layout={ + Object { + "height": 1180, + "legend": Object { + "orientation": "v", + "traceorder": "normal", + }, + "margin": Object { + "b": 30, + "l": 60, + "pad": 0, + "r": 30, + "t": 50, + }, + "showlegend": true, + } + } visualizations={ Object { "data": Object { @@ -4519,7 +4627,7 @@ exports[`Utils helper functions renders displayVisualization function 3`] = ` "props": Object { "defaultSelections": Array [ Object { - "id": "v", + "id": "h", "name": "Right", }, ], @@ -4682,7 +4790,7 @@ exports[`Utils helper functions renders displayVisualization function 3`] = ` "id": "horizontal_bar", "label": "Horizontal bar", "labelangle": 0, - "legendposition": "v", + "legendposition": "h", "linewidth": 0, "mode": "group", "name": "horizontal_bar", @@ -4778,9 +4886,11 @@ exports[`Utils helper functions renders displayVisualization function 3`] = ` "#BD6F26", "#4C636F", ], + "height": 1180, "hovermode": "closest", "legend": Object { - "orientation": "v", + "orientation": "h", + "traceorder": "normal", }, "margin": Object { "b": 30, @@ -4871,9 +4981,11 @@ exports[`Utils helper functions renders displayVisualization function 3`] = ` "#BD6F26", "#4C636F", ], + "height": 1180, "hovermode": "closest", "legend": Object { - "orientation": "v", + "orientation": "h", + "traceorder": "normal", }, "margin": Object { "b": 30, diff --git a/public/components/custom_panels/helpers/utils.tsx b/public/components/custom_panels/helpers/utils.tsx index aac147718..8586ed8f4 100644 --- a/public/components/custom_panels/helpers/utils.tsx +++ b/public/components/custom_panels/helpers/utils.tsx @@ -80,11 +80,12 @@ export const mergeLayoutAndVisualizations = ( export const updateQuerySpanInterval = ( query: string, timestampField: string, - spanParam: string + span: number | string = '1', + resolution: string = 'h' ) => { return query.replace( new RegExp(`span\\(\\s*${timestampField}\\s*,(.*?)\\)`), - `span(${timestampField},${spanParam})` + `span(${timestampField},${span}${resolution})` ); }; @@ -210,29 +211,38 @@ export const getQueryResponse = ( }; // Fetches savedVisualization by Id and runs getQueryResponse -export const renderSavedVisualization = async ( - http: CoreStart['http'], - pplService: PPLService, - savedVisualizationId: string, - startTime: string, - endTime: string, - filterQuery: string, - spanParam: string | undefined, - setVisualizationTitle: React.Dispatch>, - setVisualizationType: React.Dispatch>, - setVisualizationData: React.Dispatch>, - setVisualizationMetaData: React.Dispatch>, - setIsLoading: React.Dispatch>, - setIsError: React.Dispatch> -) => { +export const renderSavedVisualization = async ({ + pplService, + startTime, + endTime, + filterQuery, + span = '1', + resolution = 'h', + setVisualizationTitle, + setVisualizationType, + setVisualizationData, + setVisualizationMetaData, + setIsLoading, + setIsError, + visualization, +}: { + pplService: PPLService; + startTime: string; + endTime: string; + filterQuery: string; + span?: number | string; + resolution?: string; + setVisualizationTitle: React.Dispatch>; + setVisualizationType: React.Dispatch>; + setVisualizationData: React.Dispatch>; + setVisualizationMetaData: React.Dispatch>; + setIsLoading: React.Dispatch>; + setIsError: React.Dispatch>; + visualization: SavedVisualizationType; +}) => { setIsLoading(true); setIsError({} as VizContainerError); - let visualization: SavedVisualizationType = {}; - let updatedVisualizationQuery = ''; - - visualization = await fetchVisualizationById(http, savedVisualizationId, setIsError); - if (_.isEmpty(visualization)) { setIsLoading(false); return; @@ -246,15 +256,10 @@ export const renderSavedVisualization = async ( setVisualizationType(visualization.type); } - if (spanParam !== undefined) { - updatedVisualizationQuery = updateQuerySpanInterval( - visualization.query, - visualization.timeField, - spanParam - ); - } else { - updatedVisualizationQuery = visualization.query; - } + const updatedVisualizationQuery = + span !== undefined + ? updateQuerySpanInterval(visualization.query, visualization.timeField, span, resolution) + : visualization.query; setVisualizationMetaData({ ...visualization, query: updatedVisualizationQuery }); @@ -299,34 +304,35 @@ const createCatalogVisualizationMetaData = ( }; }; -const updateCatalogVisualizationQuery = ({ +export const updateCatalogVisualizationQuery = ({ catalogSourceName, catalogTableName, aggregation, attributesGroupBy, - startTime, - endTime, - spanParam, + start, + end, + span = '1', + resolution = 'h', }: { catalogSourceName: string; catalogTableName: string; aggregation: string; attributesGroupBy: string[]; - startTime: string; - endTime: string; - spanParam: string | undefined; + start: string; + end: string; + span: string; + resolution: string; }) => { - const attributesGroupString = attributesGroupBy.toString(); - const startEpochTime = convertDateTime(startTime, true, false, true); - const endEpochTime = convertDateTime(endTime, false, false, true); - // const promQuery = - // attributesGroupBy.length === 0 - // ? `${aggregation} (${catalogTableName})` - // : `${aggregation} by(${attributesGroupString}) (${catalogTableName})`; - - const promQuery = `${aggregation} (${catalogTableName})`; - - return `source = ${catalogSourceName}.query_range('${promQuery}', ${startEpochTime}, ${endEpochTime}, '${spanParam}')`; + const attributesGroupString = attributesGroupBy.join(','); + const startEpochTime = convertDateTime(start, true, false, true); + const endEpochTime = convertDateTime(end, false, false, true); + const promQuery = + attributesGroupBy.length === 0 + ? `${aggregation} (${catalogTableName})` + : `${aggregation} by(${attributesGroupString}) (${catalogTableName})`; + + const newQuery = `source = ${catalogSourceName}.query_range('${promQuery}', ${startEpochTime}, ${endEpochTime}, '${span}${resolution}')`; + return newQuery; }; // Creates a catalogVisualization for a runtime catalog based PPL query and runs getQueryResponse @@ -337,7 +343,8 @@ export const renderCatalogVisualization = async ({ startTime, endTime, filterQuery, - spanParam, + span, + resolution, setVisualizationTitle, setVisualizationType, setVisualizationData, @@ -345,7 +352,7 @@ export const renderCatalogVisualization = async ({ setIsLoading, setIsError, spanResolution, - queryMetaData, + visualization, }: { http: CoreStart['http']; pplService: PPLService; @@ -353,7 +360,8 @@ export const renderCatalogVisualization = async ({ startTime: string; endTime: string; filterQuery: string; - spanParam: string | undefined; + span?: number | string; + resolution?: string; setVisualizationTitle: React.Dispatch>; setVisualizationType: React.Dispatch>; setVisualizationData: React.Dispatch>; @@ -362,6 +370,7 @@ export const renderCatalogVisualization = async ({ setIsError: React.Dispatch>; spanResolution?: string; queryMetaData?: MetricType; + visualization: SavedVisualizationType; }) => { setIsLoading(true); setIsError({} as VizContainerError); @@ -369,20 +378,12 @@ export const renderCatalogVisualization = async ({ const visualizationType = 'line'; const visualizationTimeField = '@timestamp'; - const catalogSourceName = catalogSource.split('.')[0]; - const catalogTableName = catalogSource.split('.')[1]; - - const defaultAggregation = 'avg'; // pass in attributes to this function - const attributes: string[] = []; - const visualizationQuery = updateCatalogVisualizationQuery({ - catalogSourceName, - catalogTableName, - aggregation: defaultAggregation, - attributesGroupBy: attributes, - startTime, - endTime, - spanParam, + ...visualization.queryMetaData, + start: startTime, + end: endTime, + span, + resolution, }); const visualizationMetaData = createCatalogVisualizationMetaData( @@ -392,18 +393,17 @@ export const renderCatalogVisualization = async ({ visualizationTimeField ); - visualizationMetaData.user_configs = { + visualizationMetaData.userConfigs = { layoutConfig: { - height: 390, - margin: { t: 5 }, - legend: { visible: false }, + height: 280, + legend: { orientation: 'h', y: -0.3 }, }, }; - setVisualizationTitle(catalogSource); + setVisualizationTitle(visualization.name); setVisualizationType(visualizationType); - setVisualizationMetaData({ ...visualizationMetaData, query: visualizationQuery }); + setVisualizationMetaData(visualizationMetaData); getQueryResponse( pplService, @@ -449,9 +449,9 @@ export const parseSavedVisualizations = ( timeField: visualization.savedVisualization.selected_timestamp.name, selected_date_range: visualization.savedVisualization.selected_date_range, selected_fields: visualization.savedVisualization.selected_fields, - user_configs: visualization.savedVisualization.user_configs || {}, - sub_type: visualization.savedVisualization.hasOwnProperty('sub_type') - ? visualization.savedVisualization.sub_type + userConfigs: visualization.savedVisualization.userConfigs || {}, + subType: visualization.savedVisualization.hasOwnProperty('subType') + ? visualization.savedVisualization.subType : '', units_of_measure: visualization.savedVisualization.hasOwnProperty('units_of_measure') ? visualization.savedVisualization.units_of_measure @@ -549,7 +549,7 @@ export const displayVisualization = (metaData: any, data: any, type: string) => return <>; } - const dataConfig = { ...(metaData.user_configs?.dataConfig || {}) }; + const dataConfig = { ...(metaData.userConfigs?.dataConfig || {}) }; const hasBreakdowns = !_.isEmpty(dataConfig.breakdowns); const realTimeParsedStats = { ...getDefaultVisConfig(new QueryManager().queryParser().parse(metaData.query).getStats()), @@ -576,13 +576,13 @@ export const displayVisualization = (metaData: any, data: any, type: string) => const mixedUserConfigs = { availabilityConfig: { - ...(metaData.user_configs?.availabilityConfig || {}), + ...(metaData.userConfigs?.availabilityConfig || {}), }, dataConfig: { ...finalDataConfig, }, layoutConfig: { - ...(metaData.user_configs?.layoutConfig || {}), + ...(metaData.userConfigs?.layoutConfig || {}), }, }; diff --git a/public/components/custom_panels/home.tsx b/public/components/custom_panels/home.tsx index 2e2af8de8..c5067dd57 100644 --- a/public/components/custom_panels/home.tsx +++ b/public/components/custom_panels/home.tsx @@ -3,19 +3,18 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { EuiBreadcrumb, ShortDate, htmlIdGenerator } from '@elastic/eui'; +import { EuiBreadcrumb, htmlIdGenerator, ShortDate } from '@elastic/eui'; import React, { useState } from 'react'; -import { useDispatch, batch } from 'react-redux'; +import { batch, useDispatch } from 'react-redux'; // eslint-disable-next-line @osd/eslint/module_migration import { StaticContext } from 'react-router'; import { HashRouter, Route, RouteComponentProps, Switch } from 'react-router-dom'; import { CoreStart, SavedObjectsStart } from '../../../../../src/core/public'; -import { CUSTOM_PANELS_API_PREFIX } from '../../../common/constants/custom_panels'; import { EVENT_ANALYTICS, + OBSERVABILITY_BASE, observabilityLogsID, observabilityPanelsID, - OBSERVABILITY_BASE, SAVED_OBJECTS, } from '../../../common/constants/shared'; import DSLService from '../../services/requests/dsl'; @@ -23,15 +22,7 @@ import PPLService from '../../services/requests/ppl'; import { CustomPanelTable } from './custom_panel_table'; import { CustomPanelView } from './custom_panel_view'; import { CustomPanelViewSO } from './custom_panel_view_so'; -import { - createPanel, - createPanelSample, - createPanelWithVizs, - deletePanel, - fetchPanels, - newPanelTemplate, - uuidRx, -} from './redux/panel_slice'; +import { createPanelSample, uuidRx } from './redux/panel_slice'; import { REDIRECT_TAB, TAB_CREATED_TYPE, TAB_ID_TXT_PFX } from '../../../common/constants/explorer'; import { init as initFields } from '../event_analytics/redux/slices/field_slice'; import { init as initPatterns } from '../event_analytics/redux/slices/patterns_slice'; @@ -186,7 +177,7 @@ export const Home = ({ }} /> { const isSavedObject = !!props.match.params.id.match(uuidRx); 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..f7d7de672 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" /> ) ); @@ -203,6 +203,7 @@ export const PanelGrid = (props: PanelGridProps) => { breakpoints={{ lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }} cols={{ lg: 12, md: 12, sm: 12, xs: 1, xxs: 1 }} onLayoutChange={layoutChanged} + draggableHandle=".mouseGrabber" > {panelVisualizations.map((panelVisualization: VisualizationType, index) => (
{gridData[index]}
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..f2a1cff64 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" /> ) ); @@ -210,6 +211,7 @@ export const PanelGridSO = (props: PanelGridProps) => { breakpoints={{ lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }} cols={{ lg: 12, md: 12, sm: 12, xs: 1, xxs: 1 }} onLayoutChange={layoutChanged} + draggableHandle=".mouseGrabber" > {panelVisualizations.map((panelVisualization: VisualizationType, index) => (
{gridData[index]}
diff --git a/public/components/custom_panels/panel_modules/visualization_container/__tests__/__snapshots__/visualization_container.test.tsx.snap b/public/components/custom_panels/panel_modules/visualization_container/__tests__/__snapshots__/visualization_container.test.tsx.snap index 4ba3e3ec3..a38755e25 100644 --- a/public/components/custom_panels/panel_modules/visualization_container/__tests__/__snapshots__/visualization_container.test.tsx.snap +++ b/public/components/custom_panels/panel_modules/visualization_container/__tests__/__snapshots__/visualization_container.test.tsx.snap @@ -1,156 +1,169 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Visualization Container Component renders add visualization container 1`] = ` - - -
- -
- -
- -
- - -
- - -
-
-
-
- -
+
+ + +
+ +
+ + - - - - - -
- -
-
- -
- + + + +
+ + + + +
- - - - - - + className="euiLoadingChart euiLoadingChart--mono visualization-loading-chart euiLoadingChart--xLarge" + > + + + + + + +
- - - + + + `; 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..507208fad 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 @@ -15,6 +15,9 @@ import { sampleSavedVisualization, samplePPLResponse, } from '../../../../../../test/panels_constants'; +import { createStore } from '@reduxjs/toolkit'; +import { rootReducer } from '../../../../../framework/redux/reducers'; +import { Provider } from 'react-redux'; describe('Visualization Container Component', () => { configure({ adapter: new Adapter() }); @@ -28,6 +31,9 @@ describe('Visualization Container Component', () => { Promise.resolve((samplePPLResponse as unknown) as HttpResponse) ); + // configure({ adapter: new Adapter() }); + const store = createStore(rootReducer); + const editMode = true; const visualizationId = 'panel_viz_9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d'; const savedVisualizationId = 'oiuccXwBYVazWqOO1e06'; @@ -44,21 +50,24 @@ describe('Visualization Container Component', () => { }; const wrapper = mount( - + + + ); 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..da61c2f65 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 @@ -8,6 +8,7 @@ cursor: -webkit-grab; cursor: grab; } + & :active { cursor: -webkit-grabbing; cursor: grabbing; @@ -34,6 +35,43 @@ text-align: center; } +.metricVis { + #metricsEditInline { + /* for ... reasons (?), margin here causes + other rules to activate... providing + a more-correct display. + */ + margin: 0 0; + } + + + #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%; @@ -45,8 +83,7 @@ } .visualization-loading-chart { - margin: 0; - position: absolute; + margin: 122px 0; @extend %center-div; } 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..3d52f9edd 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 @@ -25,15 +25,19 @@ import { EuiToolTip, } from '@elastic/eui'; import React, { useEffect, useMemo, useState } from 'react'; -import _ from 'lodash'; +import { isEmpty } from 'lodash'; +import { useSelector } from 'react-redux'; import { displayVisualization, + fetchVisualizationById, renderCatalogVisualization, renderSavedVisualization, } from '../../helpers/utils'; 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'; /* * Visualization container - This module is a placeholder to add visualizations in react-grid-layout @@ -60,8 +64,11 @@ interface Props { editMode: boolean; visualizationId: string; savedVisualizationId: string; + inputMetaData: object; fromTime: string; toTime: string; + span?: number | string; + resolution?: string; onRefresh: boolean; pplFilterValue: string; usedInNotebooks?: boolean; @@ -70,15 +77,18 @@ interface Props { showFlyout?: (isReplacement?: boolean | undefined, replaceVizId?: string | undefined) => void; removeVisualization?: (visualizationId: string) => void; catalogVisualization?: boolean; - spanParam?: string; + inlineEditor?: JSX.Element; } export const VisualizationContainer = ({ editMode, visualizationId, savedVisualizationId, + inputMetaData, fromTime, toTime, + span, + resolution, onRefresh, pplFilterValue, usedInNotebooks, @@ -87,7 +97,7 @@ export const VisualizationContainer = ({ showFlyout, removeVisualization, catalogVisualization, - spanParam, + inlineEditor, }: Props) => { const [isPopoverOpen, setIsPopoverOpen] = useState(false); const [visualizationTitle, setVisualizationTitle] = useState(''); @@ -103,6 +113,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') @@ -193,11 +204,10 @@ export const VisualizationContainer = ({ , ]; - const showModelPanel = [ + const showPPLQueryPanel = [ { closeActionsMenu(); showModal('catalogModal'); @@ -207,43 +217,61 @@ export const VisualizationContainer = ({ , ]; - if (usedInNotebooks) { - popoverPanel = catalogVisualization ? [showModelPanel] : [popoverPanel[0]]; + if (visualizationMetaData?.subType === PROMQL_METRIC_SUBTYPE) { + popoverPanel = [showPPLQueryPanel]; + } else if (usedInNotebooks) { + popoverPanel = [popoverPanel[0]]; } + const fetchVisualization = async () => { + return savedVisualizationId + ? await fetchVisualizationById(http, savedVisualizationId, setIsError) + : inputMetaData; + }; + const loadVisaulization = async () => { - if (catalogVisualization) - await renderCatalogVisualization({ + const visualization = await fetchVisualization(); + setVisualizationMetaData(visualization); + + if (!visualization && !savedVisualizationId) return; + + if (visualization.subType === PROMQL_METRIC_SUBTYPE) { + renderCatalogVisualization({ + visualization, http, pplService, - catalogSource: savedVisualizationId, + catalogSource: visualizationId, startTime: fromTime, endTime: toTime, + span, + resolution, filterQuery: pplFilterValue, - spanParam, setVisualizationTitle, setVisualizationType, setVisualizationData, setVisualizationMetaData, setIsLoading, setIsError, + queryMetaData, }); - else - await renderSavedVisualization( + } else + await renderSavedVisualization({ + visualization, http, pplService, savedVisualizationId, - fromTime, - toTime, + startTime: fromTime, + endTime: toTime, pplFilterValue, - spanParam, + span, + resolution, setVisualizationTitle, setVisualizationType, setVisualizationData, setVisualizationMetaData, setIsLoading, - setIsError - ); + setIsError, + }); }; const memoisedVisualizationBox = useMemo( @@ -251,7 +279,7 @@ export const VisualizationContainer = ({
{isLoading ? ( - ) : !_.isEmpty(isError) ? ( + ) : !isEmpty(isError) ? (
@@ -281,18 +309,21 @@ export const VisualizationContainer = ({ useEffect(() => { loadVisaulization(); - }, [onRefresh]); + }, [onRefresh, inputMetaData, span, resolution, fromTime, toTime]); + + const metricVisCssClassName = catalogVisualization ? 'metricVis' : ''; return ( <> -
+
+ {inlineEditor}
{memoisedVisualizationBox} diff --git a/public/components/custom_panels/redux/panel_slice.ts b/public/components/custom_panels/redux/panel_slice.ts index 1bad8f452..82492a09e 100644 --- a/public/components/custom_panels/redux/panel_slice.ts +++ b/public/components/custom_panels/redux/panel_slice.ts @@ -4,31 +4,24 @@ */ import { createSelector, createSlice } from '@reduxjs/toolkit'; -import { async, concat, from, Observable, of } from 'rxjs'; -import { map, mergeMap, tap, toArray } from 'rxjs/operators'; -import { forEach, last } from 'lodash'; +import { concat, from, Observable, of } from 'rxjs'; +import { map, mergeMap, toArray } from 'rxjs/operators'; +import { forEach } from 'lodash'; import { + createDemoPanel, CUSTOM_PANELS_API_PREFIX, CUSTOM_PANELS_SAVED_OBJECT_TYPE, - CUSTOM_PANEL_SLICE, - createDemoPanel, + samplePanelName, } from '../../../../common/constants/custom_panels'; import { CustomPanelListType, CustomPanelType, ObservabilityPanelAttrs, PanelType, - VisualizationType, } from '../../../../common/types/custom_panels'; import { coreRefs } from '../../../framework/core_refs'; import { SavedObject, SimpleSavedObject } from '../../../../../../src/core/public'; -import { isNameValid } from '../helpers/utils'; -import { samplePanelName } from '../../../../common/constants/custom_panels'; -import { - addMultipleVisualizations, - addVisualizationPanel, -} from '../helpers/add_visualization_helper'; -import { useToast } from '../../../../public/components/common/toast'; +import { addVisualizationPanel } from '../helpers/add_visualization_helper'; interface InitialState { id: string; @@ -84,8 +77,6 @@ const normalizedPanel = (panel: CustomPanelType): CustomPanelType => ({ export const selectPanelList = (rootState): CustomPanelType[] => rootState.customPanel.panelList; -const {setToast} = useToast(); - /* ** ASYNC DISPATCH FUNCTIONS */ @@ -100,7 +91,12 @@ const fetchObservabilityPanels$ = () => of(coreRefs.http.get(`${CUSTOM_PANELS_API_PREFIX}/panels`)).pipe( mergeMap((res) => res), mergeMap((res) => res.panels as ObservabilityPanelAttrs[]), - map((p: ObservabilityPanelAttrs) => ({ ...p, title: p.name, savedObject: false })) + map((p: ObservabilityPanelAttrs) => ({ + ...p, + title: p.name, + savedObject: false, + type: 'observability-savedObject', + })) ); // Fetches all saved Custom Panels @@ -141,19 +137,25 @@ export const uuidRx = /^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a export const isUuid = (id) => !!id.match(uuidRx); -export const updatePanel = (panel: CustomPanelType, successMsg: string, failureMsg: string) => async (dispatch, getState) => { +export const updatePanel = ( + panel: CustomPanelType, + successMsg: string, + failureMsg: string +) => async (dispatch, getState) => { + const { toasts } = coreRefs; + try { if (isUuid(panel.id)) await updateSavedObjectPanel(panel); else await updateLegacyPanel(panel); if (successMsg) { - setToast(successMsg) + toasts!.add(successMsg); } dispatch(setPanel(panel)); const panelList = getState().customPanel.panelList.map((p) => (p.id === panel.id ? panel : p)); dispatch(setPanelList(panelList)); - } catch (e) { + } catch (e) { if (failureMsg) { - setToast(failureMsg, 'danger') + toasts!.addDanger(failureMsg); } console.error(e); } @@ -172,20 +174,39 @@ export const addVizToPanels = (panels, vizId) => async (dispatch, getState) => { }); }; -export const addMultipleVizToPanels = (panels, vizIds) => async (dispatch, getState) => { - forEach(panels, (oldPanel) => { - const panel = getState().customPanel.panelList.find((p) => p.id === oldPanel.panel.id); +export const addMultipleVizToPanels = async (panels, vizIds) => { + await Promise.all( + panels.map(async ({ panel: { id } }) => { + const soPanel = await savedObjectPanelsClient.get(id); + const oldPanel = savedObjectToCustomPanel(soPanel); - const allVisualizations = panel!.visualizations; + const allVisualizations = oldPanel!.visualizations; - const visualizationsWithNewPanel = addMultipleVisualizations(vizIds, allVisualizations); + let visualizationsWithNewPanel = allVisualizations; - const updatedPanel = { ...panel, visualizations: visualizationsWithNewPanel }; - dispatch(updatePanel(updatedPanel, '', '')); - }); + forEach(vizIds, (vizId) => { + visualizationsWithNewPanel = addVisualizationPanel( + vizId, + undefined, + visualizationsWithNewPanel + ); + }); + + const updatedPanel = { ...oldPanel, visualizations: visualizationsWithNewPanel }; + + if (isUuid(updatedPanel.id)) { + await updateSavedObjectPanel(updatedPanel); + } else { + await updateLegacyPanel(updatedPanel); + } + }) + ); }; -export const replaceVizInPanel = (oldPanel, oldVizId, vizId, newVisualizationTitle) => async (dispatch, getState) => { +export const replaceVizInPanel = (oldPanel, oldVizId, vizId, newVisualizationTitle) => async ( + dispatch, + getState +) => { const panel = getState().customPanel.panelList.find((p) => p.id === oldPanel.id); const allVisualizations = panel!.visualizations; @@ -193,8 +214,14 @@ export const replaceVizInPanel = (oldPanel, oldVizId, vizId, newVisualizationTit const visualizationsWithNewPanel = addVisualizationPanel(vizId, oldVizId, allVisualizations); const updatedPanel = { ...panel, visualizations: visualizationsWithNewPanel }; - - dispatch(updatePanel(updatedPanel, `Visualization ${newVisualizationTitle} successfully added!`, `Error in adding ${newVisualizationTitle} visualization to the panel`)); + + dispatch( + updatePanel( + updatedPanel, + `Visualization ${newVisualizationTitle} successfully added!`, + `Error in adding ${newVisualizationTitle} visualization to the panel` + ) + ); }; const deletePanelSO = (customPanelIdList: string[]) => { @@ -211,6 +238,8 @@ const deleteLegacyPanels = (customPanelIdList: string[]) => { }; export const deletePanels = (panelsToDelete: CustomPanelType[]) => async (dispatch, getState) => { + const { toasts } = coreRefs; + const toastMessage = `Observability Dashboard${ panelsToDelete.length > 1 ? 's' : ' ' + panelsToDelete[0].title } successfully deleted!`; @@ -222,28 +251,27 @@ export const deletePanels = (panelsToDelete: CustomPanelType[]) => async (dispat (p) => !ids.includes(p.id) ); dispatch(setPanelList(panelList)); - setToast(toastMessage); + toasts!.add(toastMessage); } catch (e) { - setToast( - 'Error deleting Observability Dashboards, please make sure you have the correct permission.', - 'danger' + toasts!.addDanger( + 'Error deleting Observability Dashboards, please make sure you have the correct permission.' ); console.error(e); } }; export const createPanel = (panel) => async (dispatch, getState) => { + const { toasts } = coreRefs; try { const newSOPanel = await savedObjectPanelsClient.create(panel); const newPanel = savedObjectToCustomPanel(newSOPanel); const panelList = getState().customPanel.panelList; dispatch(setPanelList([...panelList, newPanel])); - setToast(`Observability Dashboard "${newPanel.title}" successfully created!`); + toasts!.add(`Observability Dashboard "${newPanel.title}" successfully created!`); window.location.replace(`#/${newPanel.id}`); } catch (e) { - setToast( - 'Error occurred while creating Observability Dashboard, please make sure you have the correct permission.', - 'danger' + toasts!.addDanger( + 'Error occurred while creating Observability Dashboard, please make sure you have the correct permission.' ); console.error(e); } @@ -263,6 +291,8 @@ export const createPanelSample = (vizIds) => async (dispatch, getState) => { }; export const clonePanel = (panel, newPanelName) => async (dispatch, getState) => { + const { toasts } = coreRefs; + try { const { id, ...panelCopy } = { ...panel, @@ -277,12 +307,11 @@ export const clonePanel = (panel, newPanelName) => async (dispatch, getState) => const panelList = getState().customPanel.panelList; dispatch(setPanelList([...panelList, newPanel])); dispatch(setPanel(newPanel)); - setToast(`Observability Dashboard "${newPanel.title}" successfully created!`); + toasts!.add(`Observability Dashboard "${newPanel.title}" successfully created!`); window.location.replace(`#/${newPanel.id}`); } catch (e) { - setToast( - 'Error cloning Observability Dashboard, please make sure you have the correct permission.', - 'danger' + toasts!.addDanger( + 'Error cloning Observability Dashboard, please make sure you have the correct permission.' ); console.error(e); } @@ -315,7 +344,13 @@ export const renameCustomPanel = (editedCustomPanelName: string, id: string) => ) => { const panel = getState().customPanel.panelList.find((p) => p.id === id); const updatedPanel = { ...panel, title: editedCustomPanelName }; - dispatch(updatePanel(updatedPanel, `Operational Panel successfully renamed into "${editedCustomPanelName}"`, 'Error renaming Operational Panel, please make sure you have the correct permission.')) + dispatch( + updatePanel( + updatedPanel, + `Operational Panel successfully renamed into "${editedCustomPanelName}"`, + 'Error renaming Operational Panel, please make sure you have the correct permission.' + ) + ); }; /* @@ -323,6 +358,8 @@ export const renameCustomPanel = (editedCustomPanelName: string, id: string) => */ const savedObjectToCustomPanel = (so: SimpleSavedObject): CustomPanelType => ({ id: so.id, + type: so.type, + objectId: so.type + ':' + so.id, ...so.attributes, savedObject: true, }); diff --git a/public/components/event_analytics/explorer/explorer.tsx b/public/components/event_analytics/explorer/explorer.tsx index 1f4806b3c..fd88697b3 100644 --- a/public/components/event_analytics/explorer/explorer.tsx +++ b/public/components/event_analytics/explorer/explorer.tsx @@ -10,15 +10,15 @@ import { EuiFlexItem, EuiLink, EuiLoadingSpinner, + EuiPage, + EuiPageBody, + EuiPageSideBar, EuiPanel, EuiSpacer, + EuiSplitPanel, EuiTabbedContent, EuiTabbedContentTab, EuiText, - EuiPage, - EuiSplitPanel, - EuiPageSideBar, - EuiPageBody, } from '@elastic/eui'; import { FormattedMessage } from '@osd/i18n/react'; import _, { isEmpty, isEqual, reduce } from 'lodash'; @@ -57,6 +57,7 @@ import { import { LIVE_END_TIME, LIVE_OPTIONS, + PPL_METRIC_SUBTYPE, PPL_NEWLINE_REGEX, } from '../../../../common/constants/shared'; import { QueryManager } from '../../../../common/query_manager'; @@ -70,8 +71,8 @@ import { import { buildQuery, getIndexPatternFromRawQuery, - uiSettingsService, getSavingCommonParams, + uiSettingsService, } from '../../../../common/utils'; import { PPLDataFetcher } from '../../../services/data_fetchers/ppl/ppl_data_fetcher'; import { getSavedObjectsClient } from '../../../services/saved_objects/saved_object_client/client_factory'; @@ -91,8 +92,8 @@ import { selectSearchMetaData } from '../../event_analytics/redux/slices/search_ import { getVizContainerProps } from '../../visualizations/charts/helpers'; import { TabContext, useFetchEvents, useFetchPatterns, useFetchVisualizations } from '../hooks'; import { - selectCountDistribution, render as updateCountDistribution, + selectCountDistribution, } from '../redux/slices/count_distribution_slice'; import { selectFields, updateFields } from '../redux/slices/field_slice'; import { selectQueryResult } from '../redux/slices/query_result_slice'; @@ -102,8 +103,8 @@ import { selectExplorerVisualization } from '../redux/slices/visualization_slice import { change as changeVisualizationConfig, change as changeVizConfig, - selectVisualizationConfig, change as updateVizConfig, + selectVisualizationConfig, } from '../redux/slices/viualization_config_slice'; import { formatError, getDefaultVisConfig } from '../utils'; import { getContentTabTitle, getDateRange } from '../utils/utils'; @@ -120,10 +121,12 @@ import { DataSourceSelection } from './datasources/datasources_selection'; import { initialTabId } from '../../../framework/redux/store/shared_state'; import { ObservabilitySideBar } from './sidebar/observability_sidebar'; import { ExplorerSavedObjectLoader } from '../../../services/saved_objects/saved_object_loaders/explorer_saved_object_loader'; +import { processMetricsData } from '../../custom_panels/helpers/utils'; import { DEFAULT_DATA_SOURCE_TYPE, QUERY_LANGUAGE, } from '../../../../common/constants/data_sources'; +import { findMinInterval } from '../../common/query_utils'; export const Explorer = ({ pplService, @@ -206,8 +209,10 @@ export const Explorer = ({ explorerSearchMeta.lang || QUERY_LANGUAGE.SQL ) || {}; const SearchBar = ui?.SearchBar || Search; - const isDefaultDataSourceType = - explorerSearchMeta.datasources?.[0]?.type === DEFAULT_DATA_SOURCE_TYPE; + const isDefaultDataSourceType = [DEFAULT_DATA_SOURCE_TYPE, 'prometheus'].includes( + explorerSearchMeta.datasources?.[0]?.type + ); + const selectedIntervalRef = useRef<{ text: string; value: string; @@ -233,25 +238,7 @@ export const Explorer = ({ liveTailNameRef.current = liveTailName; const findAutoInterval = (start: string = '', end: string = '') => { - const momentStart = dateMath.parse(start)!; - const momentEnd = dateMath.parse(end, { roundUp: true })!; - const diffSeconds = momentEnd.unix() - momentStart.unix(); - let minInterval = 'y'; - - // less than 1 second - if (diffSeconds <= 1) minInterval = 'ms'; - // less than 2 minutes - else if (diffSeconds <= 60 * 2) minInterval = 's'; - // less than 2 hours - else if (diffSeconds <= 3600 * 2) minInterval = 'm'; - // less than 2 days - else if (diffSeconds <= 86400 * 2) minInterval = 'h'; - // less than 1 month - else if (diffSeconds <= 86400 * 31) minInterval = 'd'; - // less than 3 months - else if (diffSeconds <= 86400 * 93) minInterval = 'w'; - // less than 1 year - else if (diffSeconds <= 86400 * 366) minInterval = 'M'; + const minInterval = findMinInterval(start, end); setTimeIntervalOptions([ { text: 'Auto', value: 'auto_' + minInterval }, @@ -608,17 +595,22 @@ export const Explorer = ({ isQueryRunning, ]); + 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 }, }); @@ -740,7 +732,7 @@ export const Explorer = ({ ); } } else { - if (isTabHasObjID && isObjTypeMatchVis) { + if (isTabHasObjID && isObjTypeMatchVis && subType !== PPL_METRIC_SUBTYPE) { soClient = new SaveAsCurrentVisualization( { tabId, history, notifications, showPermissionErrorToast }, { batch, dispatch, changeQuery, updateTabName }, diff --git a/public/components/event_analytics/explorer/save_panel/save_panel.tsx b/public/components/event_analytics/explorer/save_panel/save_panel.tsx index 51c8b34c9..642adb6d4 100644 --- a/public/components/event_analytics/explorer/save_panel/save_panel.tsx +++ b/public/components/event_analytics/explorer/save_panel/save_panel.tsx @@ -20,6 +20,7 @@ import { fetchPanels, selectPanelList, } from '../../../../../public/components/custom_panels/redux/panel_slice'; +import { PPL_METRIC_SUBTYPE } from '../../../../../common/constants/shared'; interface ISavedPanelProps { selectedOptions: any; @@ -64,7 +65,7 @@ export const SavePanel = ({ const onToggleChange = (e: { target: { checked: React.SetStateAction } }) => { setChecked(e.target.checked); if (e.target.checked) { - setSubType('metric'); + setSubType(PPL_METRIC_SUBTYPE); } else { setSubType('visualization'); } diff --git a/public/components/event_analytics/explorer/sidebar/__tests__/__snapshots__/sidebar.test.tsx.snap b/public/components/event_analytics/explorer/sidebar/__tests__/__snapshots__/sidebar.test.tsx.snap index 28c6cd812..fe7ab15c3 100644 --- a/public/components/event_analytics/explorer/sidebar/__tests__/__snapshots__/sidebar.test.tsx.snap +++ b/public/components/event_analytics/explorer/sidebar/__tests__/__snapshots__/sidebar.test.tsx.snap @@ -701,7 +701,6 @@ exports[`Siderbar component Renders sidebar component 1`] = ` "unselectedFields": Array [], } } - tabId="DEFAULT_INDEX_PATTERNS" > @@ -1312,14 +1309,13 @@ exports[`Siderbar component Renders sidebar component 1`] = ` aria-label="inspect" className="dscSidebarField__actionButton" iconType="inspect" - isDisabled={true} onClick={[Function]} size="xs" >
diff --git a/public/components/metrics/top_menu/__tests__/__snapshots__/top_menu.test.tsx.snap b/public/components/metrics/top_menu/__tests__/__snapshots__/top_menu.test.tsx.snap index dc2ff2760..4ed0c0b16 100644 --- a/public/components/metrics/top_menu/__tests__/__snapshots__/top_menu.test.tsx.snap +++ b/public/components/metrics/top_menu/__tests__/__snapshots__/top_menu.test.tsx.snap @@ -1,2279 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Metrics Top Menu Component renders Top Menu Component when disabled in edit mode 1`] = ` - - - -
- -
-
- - } - aria-label="resolutionField" - className="resolutionSelectText" - data-test-subj="metrics__spanValue" - disabled={true} - isInvalid={false} - onChange={[Function]} - prepend="Span Interval" - value={1} - > - - } - fullWidth={false} - prepend="Span Interval" - > -
- - - -
- - - - -
- - -
-
- - - - -
- - - - - -
-
-
-
-
-
-
-
-
-
-
-
- -
- - -
- -
- - } - > -
- - - - - } - closePopover={[Function]} - display="inlineBlock" - hasArrow={true} - isOpen={false} - ownFocus={true} - panelPaddingSize="m" - > -
-
- - - -
-
-
-
-
- } - iconType={false} - isCustom={true} - startDateControl={
} - > -
- -
- - -
-
- -
- - -
- - - } - delay="regular" - position="bottom" - > - - - - - - - - - -
-
-
- - -
- - -
- - Save - - } - closePopover={[Function]} - display="inlineBlock" - hasArrow={true} - isOpen={false} - ownFocus={true} - panelPaddingSize="m" - > -
-
- - - - - -
-
-
-
-
-
- - -
- - -
- -
- - - - - -
-
- -
- - - - - -
-
-
-
- - -`; - -exports[`Metrics Top Menu Component renders Top Menu Component when disabled with no metric visualizations 1`] = ` - - - -
- -
-
- - } - aria-label="resolutionField" - className="resolutionSelectText" - data-test-subj="metrics__spanValue" - disabled={true} - isInvalid={false} - onChange={[Function]} - prepend="Span Interval" - value={1} - > - - } - fullWidth={false} - prepend="Span Interval" - > -
- - - -
- - - - -
- - -
-
- - - - -
- - - - - -
-
-
-
-
-
-
-
-
-
-
-
- -
- - -
- -
- - } - > -
- - - - - } - closePopover={[Function]} - display="inlineBlock" - hasArrow={true} - isOpen={false} - ownFocus={true} - panelPaddingSize="m" - > -
-
- - - -
-
-
-
-
- } - iconType={false} - isCustom={true} - startDateControl={
} - > -
- -
- - -
-
- -
- - -
- - - } - delay="regular" - position="bottom" - > - - - - - - - - - -
-
-
- - -
- - -
- - Save - - } - closePopover={[Function]} - display="inlineBlock" - hasArrow={true} - isOpen={false} - ownFocus={true} - panelPaddingSize="m" - > -
-
- - - - - -
-
-
-
-
-
- - -
- - -
- -
- - - - - -
-
-
-
- - -`; - exports[`Metrics Top Menu Component renders Top Menu Component when enabled 1`] = ` - + - - Save - - } - closePopover={[Function]} - display="inlineBlock" - hasArrow={true} - isOpen={false} - ownFocus={true} - panelPaddingSize="m" - > -
-
+ + + } + closePopover={[Function]} + display="inlineBlock" + hasArrow={true} + isOpen={false} + ownFocus={true} + panelPaddingSize="m" > - - - - - -
-
-
-
-
-
-
- -
- - -
- -
- - - - - + + + + + + + + Save + + + + + + + +
+
+ + +
diff --git a/public/components/metrics/top_menu/__tests__/metrics_export_panel.test.tsx b/public/components/metrics/top_menu/__tests__/metrics_export_panel.test.tsx index ca1759beb..75cce864e 100644 --- a/public/components/metrics/top_menu/__tests__/metrics_export_panel.test.tsx +++ b/public/components/metrics/top_menu/__tests__/metrics_export_panel.test.tsx @@ -8,7 +8,12 @@ import Adapter from 'enzyme-adapter-react-16'; import React from 'react'; import { waitFor } from '@testing-library/react'; import httpClientMock from '../../../../../test/__mocks__/httpClientMock'; -import { sampleSavedMetric, sampleSortedMetricsLayout } from '../../../../../test/metrics_contants'; +import { + sampleAvailableDashboards, + sampleMetricsToExport, + sampleSavedMetric, + sampleSortedMetricsLayout, +} from '../../../../../test/metrics_contants'; import { applyMiddleware, createStore } from '@reduxjs/toolkit'; import { rootReducer } from '../../../../framework/redux/reducers'; import { Provider } from 'react-redux'; @@ -40,12 +45,8 @@ describe('Export Metrics Panel Component', () => { const wrapper = mount( ); diff --git a/public/components/metrics/top_menu/__tests__/top_menu.test.tsx b/public/components/metrics/top_menu/__tests__/top_menu.test.tsx index 17822d22f..bca6805a9 100644 --- a/public/components/metrics/top_menu/__tests__/top_menu.test.tsx +++ b/public/components/metrics/top_menu/__tests__/top_menu.test.tsx @@ -7,171 +7,19 @@ import { configure, mount } from 'enzyme'; import Adapter from 'enzyme-adapter-react-16'; import React from 'react'; import { waitFor } from '@testing-library/react'; -import httpClientMock from '../../../../../test/__mocks__/httpClientMock'; -import { sampleMetric, sampleMetricsVisualizations } from '../../../../../test/metrics_contants'; import { createStore } from '@reduxjs/toolkit'; import { rootReducer } from '../../../../framework/redux/reducers'; import { Provider } from 'react-redux'; -import { HttpResponse } from '../../../../../../../src/core/public'; import { TopMenu } from '../top_menu'; -import { DurationRange } from '@elastic/eui/src/components/date_picker/types'; -import SavedObjects from '../../../../services/saved_objects/event_analytics/saved_objects'; -import { MetricType } from '../../../../../common/types/metrics'; describe('Metrics Top Menu Component', () => { configure({ adapter: new Adapter() }); const store = createStore(rootReducer); it('renders Top Menu Component when enabled', async () => { - httpClientMock.get = jest.fn(() => Promise.resolve((sampleMetric as unknown) as HttpResponse)); - - const http = httpClientMock; - const IsTopPanelDisabled = false; - const startTime = 'now-1d'; - const endTime = 'now'; - const onDatePickerChange = jest.fn(); - const recentlyUsedRanges: DurationRange[] = []; - const editMode = false; - const setEditMode = jest.fn(); - const setEditActionType = jest.fn(); - const panelVisualizations = sampleMetricsVisualizations; - const setPanelVisualizations = jest.fn(); - const resolutionValue = 'h'; - const setResolutionValue = jest.fn(); - const spanValue = 1; - const setSpanValue = jest.fn(); - const resolutionSelectId = 'select_123'; - const savedObjects = new SavedObjects(httpClientMock); - const setToast = jest.fn(); - - const wrapper = mount( - - - - ); - wrapper.update(); - - await waitFor(() => { - expect(wrapper).toMatchSnapshot(); - }); - }); - - it('renders Top Menu Component when disabled with no metric visualizations', async () => { - httpClientMock.get = jest.fn(); - - const http = httpClientMock; - const IsTopPanelDisabled = true; - const startTime = 'now-1d'; - const endTime = 'now'; - const onDatePickerChange = jest.fn(); - const recentlyUsedRanges: DurationRange[] = []; - const editMode = false; - const setEditMode = jest.fn(); - const setEditActionType = jest.fn(); - const panelVisualizations: MetricType[] = []; - const setPanelVisualizations = jest.fn(); - const resolutionValue = 'h'; - const setResolutionValue = jest.fn(); - const spanValue = 1; - const setSpanValue = jest.fn(); - const resolutionSelectId = 'select_123'; - const savedObjects = new SavedObjects(httpClientMock); - const setToast = jest.fn(); - - const wrapper = mount( - - - - ); - wrapper.update(); - - await waitFor(() => { - expect(wrapper).toMatchSnapshot(); - }); - }); - - it('renders Top Menu Component when disabled in edit mode', async () => { - httpClientMock.get = jest.fn(() => Promise.resolve((sampleMetric as unknown) as HttpResponse)); - - const http = httpClientMock; - const IsTopPanelDisabled = true; - const startTime = 'now-1d'; - const endTime = 'now'; - const onDatePickerChange = jest.fn(); - const recentlyUsedRanges: DurationRange[] = []; - const editMode = true; - const setEditMode = jest.fn(); - const setEditActionType = jest.fn(); - const panelVisualizations = sampleMetricsVisualizations; - const setPanelVisualizations = jest.fn(); - const resolutionValue = 'h'; - const setResolutionValue = jest.fn(); - const spanValue = 1; - const setSpanValue = jest.fn(); - const resolutionSelectId = 'select_123'; - const savedObjects = new SavedObjects(httpClientMock); - const setToast = jest.fn(); - const wrapper = mount( - + ); wrapper.update(); diff --git a/public/components/metrics/top_menu/metrics_export.tsx b/public/components/metrics/top_menu/metrics_export.tsx index fc1be965d..bf0b52ef6 100644 --- a/public/components/metrics/top_menu/metrics_export.tsx +++ b/public/components/metrics/top_menu/metrics_export.tsx @@ -17,6 +17,7 @@ import { useSelector } from 'react-redux'; import { max } from 'lodash'; import semver from 'semver'; import { render, unmountComponentAtNode } from 'react-dom'; +import { I18nProvider } from '@osd/i18n/react'; import { MetricsExportPanel } from './metrics_export_panel'; import { OSDSavedVisualizationClient } from '../../../services/saved_objects/saved_object_client/osd_saved_objects/saved_visualization'; import { getSavedObjectsClient } from '../../../services/saved_objects/saved_object_client/client_factory'; @@ -35,7 +36,6 @@ import { updateCatalogVisualizationQuery } from '../../common/query_utils'; import { PROMQL_METRIC_SUBTYPE } from '../../../../common/constants/shared'; import { SavedObjectLoader } from '../../../../../../src/plugins/saved_objects/public'; import { MountPoint } from '../../../../../../src/core/public'; -import { I18nProvider } from '../../../../../../packages/osd-i18n/src/react/provider'; const Savebutton = ({ setIsPanelOpen, diff --git a/public/components/metrics/view/__tests__/__snapshots__/metrics_grid.test.tsx.snap b/public/components/metrics/view/__tests__/__snapshots__/metrics_grid.test.tsx.snap index 1fac9b7f1..8bfdd0f91 100644 --- a/public/components/metrics/view/__tests__/__snapshots__/metrics_grid.test.tsx.snap +++ b/public/components/metrics/view/__tests__/__snapshots__/metrics_grid.test.tsx.snap @@ -12,7 +12,7 @@ exports[`Metrics Grid Component renders Metrics Grid Component 1`] = ` } } > - - - + - -
- -
- - -
-
- -
- -
- -
- - -
- - -
-
-
-
- -
- - } - closePopover={[Function]} - display="inlineBlock" - hasArrow={true} - isOpen={false} - ownFocus={true} - panelPaddingSize="m" - > -
-
- - - -
-
-
-
-
-
-
-
-
- - - - - - - - -
-
-
-
-
-
- + -
- - -
-
- -
- -
- -
- - -
- - -
-
-
-
- -
- - } - closePopover={[Function]} - display="inlineBlock" - hasArrow={true} - isOpen={false} - ownFocus={true} - panelPaddingSize="m" - > -
-
- - - -
-
-
-
-
-
-
-
-
- - - - - - - - -
-
-
-
-
-
- -
- - -
- -
- -
- -
- - -
- prometheus.process_resident_memory_bytes -
-
-
-
-
-
-
- -
- - } - closePopover={[Function]} - display="inlineBlock" - hasArrow={true} - isOpen={false} - ownFocus={true} - panelPaddingSize="m" - > -
-
- - - -
-
-
-
-
-
-
-
-
- - - - - - - - + +
-
- - -
- -
- - - -
+ + + +
+ + + + + + `; diff --git a/public/components/metrics/view/__tests__/metrics_grid.test.tsx b/public/components/metrics/view/__tests__/metrics_grid.test.tsx index c38a34cdf..ddf5949b0 100644 --- a/public/components/metrics/view/__tests__/metrics_grid.test.tsx +++ b/public/components/metrics/view/__tests__/metrics_grid.test.tsx @@ -16,6 +16,7 @@ import { Provider } from 'react-redux'; import PPLService from '../../../../services/requests/ppl'; import httpClientMock from '../../../../../test/__mocks__/httpClientMock'; import { coreRefs } from '../../../../framework/core_refs'; +import { of } from 'rxjs'; describe('Metrics Grid Component', () => { configure({ adapter: new Adapter() }); @@ -48,6 +49,7 @@ describe('Metrics Grid Component', () => { then: () => Promise.resolve(), }) ); + coreRefs.chrome = { getIsNavDrawerLocked$: () => of(false) }; const wrapper = mount( diff --git a/public/components/notebooks/components/paragraph_components/__tests__/__snapshots__/paragraphs.test.tsx.snap b/public/components/notebooks/components/paragraph_components/__tests__/__snapshots__/paragraphs.test.tsx.snap index 0fb456810..bc5be1116 100644 --- a/public/components/notebooks/components/paragraph_components/__tests__/__snapshots__/paragraphs.test.tsx.snap +++ b/public/components/notebooks/components/paragraph_components/__tests__/__snapshots__/paragraphs.test.tsx.snap @@ -275,7 +275,7 @@ exports[` spec renders the component 1`] = ` style="opacity: 1; padding: 15px;" >
Date: Fri, 8 Dec 2023 08:29:45 -0800 Subject: [PATCH 4/8] Cleanup console.logs, unused code Signed-off-by: Peter Fitzgibbons --- .../__tests__/query_utils.test.tsx | 2 - .../visualization_container.scss | 13 +- public/components/metrics/index.tsx | 44 +---- .../components/metrics/sidebar/sidebar.scss | 157 ------------------ .../metrics/top_menu/metrics_export.tsx | 5 - .../components/metrics/view/metrics_grid.tsx | 3 - .../observability_embeddable_component.tsx | 1 - 7 files changed, 6 insertions(+), 219 deletions(-) diff --git a/public/components/common/query_utils/__tests__/query_utils.test.tsx b/public/components/common/query_utils/__tests__/query_utils.test.tsx index 2cd7bdfb5..da81fe446 100644 --- a/public/components/common/query_utils/__tests__/query_utils.test.tsx +++ b/public/components/common/query_utils/__tests__/query_utils.test.tsx @@ -20,7 +20,6 @@ describe('Query Utils', () => { }); test('should parse simple ppl query_range into keywords', () => { - console.log('should parse simple ppl query_range into keywords'); const query = "source = test_catalog.query_range('metric')"; const keywords = parsePromQLIntoKeywords(query); expect(keywords).toEqual({ @@ -32,7 +31,6 @@ describe('Query Utils', () => { }); test('should parse promql into keywords', () => { - console.log('should parse promql into keywords'); const query = "source = test_catalog.query_range('count by(one,two) (metric)')"; const keywords = parsePromQLIntoKeywords(query); expect(keywords).toEqual({ 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 da61c2f65..9b2e60063 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 @@ -51,24 +51,13 @@ } } - // #explorerPlotComponent { - // //overflow: hidden; - // } - // .svg-container { - //height: 425px !important; - margin-top: -25px !important; + margin-top: -25px; } - // .main-svg { - // //margin-top: -10px; - // //height: calc(100% - 10px); - // } - // .main-svg { margin-top: -10px; - //height: calc(100% - 60px); } } diff --git a/public/components/metrics/index.tsx b/public/components/metrics/index.tsx index 8303a4e82..2e7f7a1cf 100644 --- a/public/components/metrics/index.tsx +++ b/public/components/metrics/index.tsx @@ -4,14 +4,13 @@ */ import './index.scss'; -import { EuiGlobalToastList, EuiPage, EuiPageBody, EuiResizableContainer } from '@elastic/eui'; -import React, { ReactChild, useEffect, useState } from 'react'; +import { EuiPage, EuiPageBody, EuiResizableContainer } from '@elastic/eui'; +import React, { useEffect } from 'react'; import { HashRouter, Route, RouteComponentProps, StaticContext } from 'react-router-dom'; -import { ChromeBreadcrumb, Toast } from '../../../../../src/core/public'; +import { ChromeBreadcrumb } from '../../../../../src/core/public'; import { Sidebar } from './sidebar/sidebar'; import PPLService from '../../services/requests/ppl'; import { TopMenu } from './top_menu/top_menu'; -import { MetricType } from '../../../common/types/metrics'; import { MetricsGrid } from './view/metrics_grid'; import SavedObjects from '../../services/saved_objects/event_analytics/saved_objects'; @@ -24,24 +23,6 @@ interface MetricsProps { } export const Home = ({ chrome, parentBreadcrumb }: MetricsProps) => { - // Date picker constants - const [recentlyUsedRanges, setRecentlyUsedRanges] = useState([]); - const [onRefresh, setOnRefresh] = useState(false); - - // Top panel - const [editMode, setEditMode] = useState(false); - const [toasts, setToasts] = useState([]); - const [toastRightSide, setToastRightSide] = useState(true); - - // Metrics constants - const [panelVisualizations, setPanelVisualizations] = useState([]); - - const setToast = (title: string, color = 'success', text?: ReactChild, side?: string) => { - if (!text) text = ''; - setToastRightSide(!side); - setToasts([...toasts, { id: new Date().toISOString(), title, text, color } as Toast]); - }; - useEffect(() => { chrome.setBreadcrumbs([ parentBreadcrumb, @@ -54,14 +35,6 @@ export const Home = ({ chrome, parentBreadcrumb }: MetricsProps) => { return ( <> - { - setToasts(toasts.filter((toast) => toast.id !== removedToast.id)); - }} - side={toastRightSide ? 'right' : 'left'} - toastLifeTimeMs={6000} - /> {
- -
+ +
{(EuiResizablePanel, EuiResizableButton) => ( <> diff --git a/public/components/metrics/sidebar/sidebar.scss b/public/components/metrics/sidebar/sidebar.scss index 3af1086c9..7cadecd53 100644 --- a/public/components/metrics/sidebar/sidebar.scss +++ b/public/components/metrics/sidebar/sidebar.scss @@ -3,163 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -.dscSidebar__container { - padding-left: 0 !important; - padding-right: 0 !important; - background-color: transparent; - border-right-color: transparent; - border-bottom-color: transparent; -} - -.dscIndexPattern__container { - display: flex; - align-items: center; - height: $euiSize * 3; - margin-top: -$euiSizeS; -} - -.dscIndexPattern__triggerButton { - @include euiTitle('xs'); - line-height: $euiSizeXXL; -} - -.dscFieldList { - list-style: none; - margin-bottom: 0; -} - -.dscFieldListHeader { - padding: $euiSizeS $euiSizeS 0 $euiSizeS; - background-color: lightOrDarkTheme(tint($euiColorPrimary, 90%), $euiColorLightShade); -} - -.dscFieldList--popular { - background-color: lightOrDarkTheme(tint($euiColorPrimary, 90%), $euiColorLightShade); -} - -.dscFieldChooser__toggle { - color: $euiColorMediumShade; - margin-left: $euiSizeS !important; -} - -/** - * 1. Only visually hide the action, so that it's still accessible to screen readers. - * 2. When tabbed to, this element needs to be visible for keyboard accessibility. - */ -.dscSidebarItem__action { - opacity: 0; - /* 1 */ - transition: none; - - &:focus { - opacity: 1; - /* 2 */ - } - - font-size: $euiFontSizeXS; - padding: 2px 6px !important; - height: 22px !important; - min-width: auto !important; - - .euiButton__content { - padding: 0 4px; - } -} - -.dscFieldSearch { - padding: $euiSizeS; -} - -.dscFieldSearch__toggleButton { - width: calc(100% - #{$euiSizeS}); - color: $euiColorPrimary; - padding-left: $euiSizeXS; - margin-left: $euiSizeXS; -} - -.dscFieldSearch__filterWrapper { - flex-grow: 0; -} - -.dscFieldSearch__formWrapper { - padding: $euiSizeM; -} - -.dscFieldDetails { - color: $euiTextColor; - margin-bottom: $euiSizeS; -} - -.dscSidebarItem__fieldPopoverPanel { - min-width: 300px; - max-width: 600px; - max-height: 600px; - overflow-y: scroll; -} - -.override_timestamp_loading { - vertical-align: middle; -} - -.explorerFieldSelector { - padding: $euiSizeS; -} - -// #vis__mainContent { -// .explorer__insights, .explorer__configPanel-markdown { -// .explorerFieldSelector, -// .explorer__vizDataConfig { -// padding: $euiSizeS; -// overflow: auto; - -// &__fieldGroups { -// @include euiYScrollWithShadows; - -// overflow-y: auto; -// margin-right: -$euiSizeS; -// padding-right: $euiSizeS; -// margin-top: $euiSizeS; -// } - -// &__fieldGroup { -// margin-top: $euiSizeS; - -// &:first-child { -// margin-top: 0; -// } -// } -// } - -// // .explorerFieldSelector { -// // @include euiYScrollWithShadows; -// // .sidebar-list { -// // height: 100%; -// // } -// // } -// .metricsSideBar { -// @include euiYScrollWithShadows; -// .sidebarHeight{ -// .sidebar-list { -// height: 100%; -// } -// } -// } -// } -// } - -#vis__mainContent .explorer__insights { - min-height: 0; - display: grid; - grid-template-columns: 50% 50%; - height: 100%; -} - -.sidebarHeight { - line-height: normal; - overflow: auto; - max-height: 100vh; -} - .metricsList { line-height: normal; } diff --git a/public/components/metrics/top_menu/metrics_export.tsx b/public/components/metrics/top_menu/metrics_export.tsx index bf0b52ef6..f23f0543d 100644 --- a/public/components/metrics/top_menu/metrics_export.tsx +++ b/public/components/metrics/top_menu/metrics_export.tsx @@ -303,12 +303,8 @@ const MetricsExportPopOver = () => { savedMetrics = await Promise.all( metricsToExport.map(async (metric, index) => { if (metric.savedVisualizationId === undefined) { - console.log('createSavedVisualization', metric); - return createSavedVisualization(metric); } else { - console.log('updateSavedVisualization', metric); - return updateSavedVisualization(metric); } }) @@ -333,7 +329,6 @@ const MetricsExportPopOver = () => { try { if (observabilityDashboards.length > 0) { const savedVisualizationIds = savedMetrics.map((p) => p.objectId); - console.log(savedMetrics); await addMultipleVizToPanels(observabilityDashboards, savedVisualizationIds); } if (osdCoreSelectedDashboards.length > 0) diff --git a/public/components/metrics/view/metrics_grid.tsx b/public/components/metrics/view/metrics_grid.tsx index 16f1c2af8..4fc5d5fd1 100644 --- a/public/components/metrics/view/metrics_grid.tsx +++ b/public/components/metrics/view/metrics_grid.tsx @@ -50,7 +50,6 @@ const visualizationFromMetric = (metric, dateSpanFilter): SavedVisualizationType fillOpacity: 0, lineWidth: 2, }, - layout: { height: 280 }, }, }); @@ -61,7 +60,6 @@ const navigateToEventExplorerVisualization = (savedVisualizationId: string) => { export const InnerGridVisualization = ({ id, idx, dateSpanFilter, metric, refresh }) => { if (!metric) return <>; - console.log('vis', { metric }); return ( { const visualization = props.output.attributes?.savedVisualization; - console.log('ObservabilityEmbeddableComponentInner', visualization); return visualization ? ( Date: Fri, 8 Dec 2023 08:31:50 -0800 Subject: [PATCH 5/8] Simplify PromQL data query. Calculate CSS height of Metric Grid panels based upon attribute-labels legend list. Signed-off-by: Peter Fitzgibbons --- .../application_analytics/helpers/utils.tsx | 3 + .../custom_panels/helpers/utils.tsx | 189 ++++++++---------- public/components/metrics/index.scss | 10 +- .../components/metrics/sidebar/sidebar.scss | 4 + public/components/metrics/sidebar/sidebar.tsx | 6 +- .../components/metrics/view/metrics_grid.scss | 4 +- .../components/metrics/view/metrics_grid.tsx | 8 +- .../saved_object_visualization.tsx | 4 +- 8 files changed, 115 insertions(+), 113 deletions(-) diff --git a/public/components/application_analytics/helpers/utils.tsx b/public/components/application_analytics/helpers/utils.tsx index 8c66cc215..41f0b7048 100644 --- a/public/components/application_analytics/helpers/utils.tsx +++ b/public/components/application_analytics/helpers/utils.tsx @@ -218,6 +218,9 @@ export const calculateAvailability = async ( const visData = await fetchVisualizationById(http, visualizationId, (value: string) => console.error(value) ); + + // resolved mismatched use of user_configs/userConfigs. This fall-back + // silently loads legacy configs. They will be stored in new userConfigs upon save. const userConfigs = visData.userConfigs ? JSON.parse(visData.user_configs || visData.userConfigs) : {}; diff --git a/public/components/custom_panels/helpers/utils.tsx b/public/components/custom_panels/helpers/utils.tsx index 53ac325c8..9abc0d8d7 100644 --- a/public/components/custom_panels/helpers/utils.tsx +++ b/public/components/custom_panels/helpers/utils.tsx @@ -5,7 +5,7 @@ import { ShortDate } from '@elastic/eui'; import { DurationRange } from '@elastic/eui/src/components/date_picker/types'; -import _, { forEach, isEmpty } from 'lodash'; +import _, { forEach, isEmpty, min } from 'lodash'; import { Moment } from 'moment-timezone'; import React from 'react'; import { Layout } from 'react-grid-layout'; @@ -26,19 +26,28 @@ import { ObservabilitySavedVisualization } from '../../../services/saved_objects import { getDefaultVisConfig } from '../../event_analytics/utils'; import { Visualization } from '../../visualizations/visualization'; import { MetricType } from '../../../../common/types/metrics'; -import { convertDateTime } from '../../common/query_utils'; +import { convertDateTime, updateCatalogVisualizationQuery } from '../../common/query_utils'; /* * "Utils" This file contains different reused functions in operational panels * - * isNameValid - Validates string to length > 0 and < 50 - * mergeLayoutAndVisualizations - Function to merge current panel layout into the visualizations list + * checkIndexExists - Function to test if query string includes an index + * checkWhereClauseExists - Function to test if query string includes where clause + * createCatalogVisualizationMetaData - create Visualization metaData from visualization, query details + * displayVisualization - Function to render the visualzation based of its type + * fetchVisualizationById - Fetch visualization from SavedObject store by id * 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 + * isNameValid - Validates string to length > 0 and < 50 * isPPLFilterValid - Validate if the panel PPL query doesn't contain any Index/Time/Field filters - * displayVisualization - Function to render the visualzation based of its type + * mergeLayoutAndVisualizations - Function to merge current panel layout into the visualizations list + * onTimeChange - Function to store recently used time filters and set start and end time. + * parseSavedVisualizations - Transform SavedObject visualization into mapped record object + * prepareMetricsData - Create visualization schema metadata from jsonData scheama list + * prependRecentlyUsedRange - Maintain MRU list of datePicker selected ranges + * processMetricsData - validate and transform jsonData schema into visualization schema metadata + * renderCatalogVisualization - Query OS for visualization Data from PromQL metric schema + * renderSavedVisualization - Query OS for visualization Data from PPL metric schema */ // Name validation 0>Name<=50 @@ -77,7 +86,7 @@ export const mergeLayoutAndVisualizations = ( * Updates the span command interval * Returns -> source = opensearch_dashboards_sample_data_logs | stats avg(bytes) by span(timestamp,1M) */ -export const updateQuerySpanInterval = ( +const updateQuerySpanInterval = ( query: string, timestampField: string, span: number | string = '1', @@ -119,35 +128,6 @@ const queryAccumulator = ( return indexPartOfQuery + timeQueryFilter + pplFilterQuery + filterPartOfQuery; }; -// PPL Service requestor -const pplServiceRequestor = async ( - pplService: PPLService, - finalQuery: string, - type: string, - setVisualizationData: React.Dispatch>, - setIsLoading: React.Dispatch>, - setIsError: React.Dispatch> -) => { - await pplService - .fetch({ query: finalQuery, format: 'jdbc' }) - .then((res) => { - if (res === undefined) - setIsError({ errorMessage: 'Please check the validity of PPL Filter' }); - setVisualizationData(res); - }) - .catch((error: Error) => { - const errorMessage = JSON.parse(error.body.message); - setIsError({ - errorMessage: errorMessage.error.reason || 'Issue in fetching visualization', - errorDetails: errorMessage.error.details, - }); - console.error(error.body); - }) - .finally(() => { - setIsLoading(false); - }); -}; - // Fetched Saved Visualization By Id export const fetchVisualizationById = async ( http: CoreStart['http'], @@ -176,38 +156,25 @@ export const fetchVisualizationById = async ( }; // Get PPL Query Response -export const getQueryResponse = ( +export const getQueryResponse = async ( pplService: PPLService, query: string, type: string, startTime: string, endTime: string, - setVisualizationData: React.Dispatch>, - setIsLoading: React.Dispatch>, - setIsError: React.Dispatch>, filterQuery = '', timestampField = 'timestamp', metricVisualization = false ) => { - setIsLoading(true); - setIsError({} as VizContainerError); + const finalQuery = metricVisualization + ? query + : queryAccumulator(query, timestampField, startTime, endTime, filterQuery); - let finalQuery = ''; - try { - if (!metricVisualization) { - finalQuery = queryAccumulator(query, timestampField, startTime, endTime, filterQuery); - } else { - finalQuery = query; - } - } catch (error) { - const errorMessage = 'Issue in building final query'; - setIsError({ errorMessage }); - console.error(errorMessage, error); - setIsLoading(false); - return; - } + const res = await pplService.fetch({ query: finalQuery, format: 'jdbc' }); + + if (res === undefined) throw new Error('Please check the validity of PPL Filter'); - pplServiceRequestor(pplService, finalQuery, type, setVisualizationData, setIsLoading, setIsError); + return res; }; // Fetches savedVisualization by Id and runs getQueryResponse @@ -263,25 +230,41 @@ export const renderSavedVisualization = async ({ setVisualizationMetaData({ ...visualization, query: updatedVisualizationQuery }); - getQueryResponse( - pplService, - updatedVisualizationQuery, - visualization.type, - startTime, - endTime, - setVisualizationData, - setIsLoading, - setIsError, - filterQuery, - visualization.timeField - ); + try { + const queryData = await getQueryResponse( + pplService, + updatedVisualizationQuery, + visualization.type, + startTime, + endTime, + filterQuery, + visualization.timeField + ); + setVisualizationData(queryData); + } catch (error) { + setIsError({ error }); + } + setIsLoading(false); +}; + +const dynamicLayoutFromQueryData = (queryData) => { + const labelCount = queryData.jsonData.length; + const legendLines = min([labelCount, 10]); + + const height = 230 + legendLines * 30; + const y = -0.35 + -0.15 * legendLines; + return { + height, + legend: { orientation: 'h', x: 0, y }, + }; }; const createCatalogVisualizationMetaData = ( catalogSource: string, visualizationQuery: string, visualizationType: string, - visualizationTimeField: string + visualizationTimeField: string, + queryData: object ) => { return { name: catalogSource, @@ -301,6 +284,9 @@ const createCatalogVisualizationMetaData = ( text: '', tokens: [], }, + userConfigs: { + layout: dynamicLayoutFromQueryData(queryData), + }, }; }; @@ -355,38 +341,37 @@ export const renderCatalogVisualization = async ({ resolution, }); - const visualizationMetaData = createCatalogVisualizationMetaData( - catalogSource, - visualizationQuery, - visualizationType, - visualizationTimeField - ); - - visualizationMetaData.userConfigs = { - layoutConfig: { - height: 280, - legend: { orientation: 'h', y: -0.3 }, - }, - }; - setVisualizationTitle(visualization.name); setVisualizationType(visualizationType); - setVisualizationMetaData(visualizationMetaData); - - getQueryResponse( - pplService, - visualizationQuery, - visualizationType, - startTime, - endTime, - setVisualizationData, - setIsLoading, - setIsError, - filterQuery, - visualizationTimeField, - true - ); + try { + const queryData = await getQueryResponse( + pplService, + visualizationQuery, + visualizationType, + startTime, + endTime, + filterQuery, + visualizationTimeField, + true + ); + setVisualizationData(queryData); + + const visualizationMetaData = createCatalogVisualizationMetaData( + catalogSource, + visualizationQuery, + visualizationType, + visualizationTimeField, + queryData + ); + + console.log('renderCatalogVisualization', { visualizationMetaData }); + setVisualizationMetaData(visualizationMetaData); + } catch (error) { + setIsError({ error }); + } + + setIsLoading(false); }; // Function to store recently used time filters and set start and end time. @@ -550,8 +535,8 @@ export const displayVisualization = (metaData: any, data: any, type: string) => dataConfig: { ...finalDataConfig, }, - layoutConfig: { - ...(metaData.userConfigs?.layoutConfig || {}), + layout: { + ...(metaData.userConfigs?.layout || {}), }, }; diff --git a/public/components/metrics/index.scss b/public/components/metrics/index.scss index abfeba374..905d61b06 100644 --- a/public/components/metrics/index.scss +++ b/public/components/metrics/index.scss @@ -17,4 +17,12 @@ .mainContentTabs .euiResizableContainer { height: calc(100vh - 194px); } - \ No newline at end of file + +.metricsContainer { + height: calc(100vh - 194px); + + section { + line-height: normal; + overflow: auto; + } +} \ No newline at end of file diff --git a/public/components/metrics/sidebar/sidebar.scss b/public/components/metrics/sidebar/sidebar.scss index 7cadecd53..4286170fc 100644 --- a/public/components/metrics/sidebar/sidebar.scss +++ b/public/components/metrics/sidebar/sidebar.scss @@ -7,6 +7,10 @@ line-height: normal; } +section.sidebar { + height: calc(100vh - 240px); +} + .metricsListContainer { margin-left: 15px; margin-right: 5px; diff --git a/public/components/metrics/sidebar/sidebar.tsx b/public/components/metrics/sidebar/sidebar.tsx index 71a92cf42..59fc60506 100644 --- a/public/components/metrics/sidebar/sidebar.tsx +++ b/public/components/metrics/sidebar/sidebar.tsx @@ -60,10 +60,10 @@ export const Sidebar = ({ return ( -
- - + + +
- {visualizationComponents} - +
+ + {visualizationComponents} + +
); }; diff --git a/public/components/visualizations/saved_object_visualization.tsx b/public/components/visualizations/saved_object_visualization.tsx index 220271ad8..f9f4e4f32 100644 --- a/public/components/visualizations/saved_object_visualization.tsx +++ b/public/components/visualizations/saved_object_visualization.tsx @@ -72,8 +72,8 @@ export const SavedObjectVisualization: React.FC = dataConfig: { ...finalDataConfig, }, - layoutConfig: { - ...(userConfigs.layoutConfig || {}), + layout: { + ...userConfigs.layout, }, }; From 074051f7168d81ecb0ef781f8c5050c1c855e0ae Mon Sep 17 00:00:00 2001 From: Peter Fitzgibbons Date: Fri, 8 Dec 2023 10:08:21 -0800 Subject: [PATCH 6/8] Update snapshots Signed-off-by: Peter Fitzgibbons --- .../__snapshots__/utils.test.tsx.snap | 18 +- .../__snapshots__/sidebar.test.tsx.snap | 222 +++++++++--------- .../__snapshots__/metrics_grid.test.tsx.snap | 168 ++++++------- 3 files changed, 206 insertions(+), 202 deletions(-) diff --git a/public/components/custom_panels/helpers/__tests__/__snapshots__/utils.test.tsx.snap b/public/components/custom_panels/helpers/__tests__/__snapshots__/utils.test.tsx.snap index 60c8ec019..a6c47b9fe 100644 --- a/public/components/custom_panels/helpers/__tests__/__snapshots__/utils.test.tsx.snap +++ b/public/components/custom_panels/helpers/__tests__/__snapshots__/utils.test.tsx.snap @@ -147,7 +147,7 @@ exports[`Utils helper functions renders displayVisualization function 1`] = ` ], "span": undefined, }, - "layoutConfig": Object {}, + "layout": Object {}, }, }, "vis": Object { @@ -602,7 +602,7 @@ exports[`Utils helper functions renders displayVisualization function 1`] = ` ], "span": undefined, }, - "layoutConfig": Object {}, + "layout": Object {}, }, }, "vis": Object { @@ -1080,7 +1080,7 @@ exports[`Utils helper functions renders displayVisualization function 1`] = ` ], "span": undefined, }, - "layoutConfig": Object {}, + "layout": Object {}, }, }, "vis": Object { @@ -1761,7 +1761,7 @@ exports[`Utils helper functions renders displayVisualization function 2`] = ` ], }, }, - "layoutConfig": Object {}, + "layout": Object {}, }, }, "vis": Object { @@ -2277,7 +2277,7 @@ exports[`Utils helper functions renders displayVisualization function 2`] = ` ], }, }, - "layoutConfig": Object {}, + "layout": Object {}, }, }, "vis": Object { @@ -2847,7 +2847,7 @@ exports[`Utils helper functions renders displayVisualization function 2`] = ` ], }, }, - "layoutConfig": Object {}, + "layout": Object {}, }, }, "vis": Object { @@ -3584,7 +3584,7 @@ exports[`Utils helper functions renders displayVisualization function 3`] = ` ], "span": undefined, }, - "layoutConfig": Object {}, + "layout": Object {}, }, }, "vis": Object { @@ -4039,7 +4039,7 @@ exports[`Utils helper functions renders displayVisualization function 3`] = ` ], "span": undefined, }, - "layoutConfig": Object {}, + "layout": Object {}, }, }, "vis": Object { @@ -4517,7 +4517,7 @@ exports[`Utils helper functions renders displayVisualization function 3`] = ` ], "span": undefined, }, - "layoutConfig": Object {}, + "layout": Object {}, }, }, "vis": Object { diff --git a/public/components/metrics/sidebar/__tests__/__snapshots__/sidebar.test.tsx.snap b/public/components/metrics/sidebar/__tests__/__snapshots__/sidebar.test.tsx.snap index 8aeeceac5..348bc61a9 100644 --- a/public/components/metrics/sidebar/__tests__/__snapshots__/sidebar.test.tsx.snap +++ b/public/components/metrics/sidebar/__tests__/__snapshots__/sidebar.test.tsx.snap @@ -99,148 +99,148 @@ exports[`Side Bar Component renders Side Bar Component 1`] = ` textComponent={Symbol(react.fragment)} > -
- -
- +
+ + - -
- -
- - -
-
+ + + - - - - -
- - - -
-
-
+ viewBox="0 0 16 16" + width={16} + xmlns="http://www.w3.org/2000/svg" + /> + + + + +
+
- - - -
- -
- -
-
-
- -
- +
+ + + +
+ +
+ + +
+ + +
+ +
- - - - - + - - - -
- +
+ +
-
-
-
-
-
-
-
-
-
+ + + + + + + + +
From 291f88b8b3459dec9fa885c93fcee58a1e8cf1cc Mon Sep 17 00:00:00 2001 From: Peter Fitzgibbons Date: Thu, 14 Dec 2023 05:10:46 -0800 Subject: [PATCH 7/8] Update testing across Metrics module Signed-off-by: Peter Fitzgibbons --- .../__tests__/visualization_helpers.test.tsx | 24 + common/utils/visualization_helpers.ts | 19 + package.json | 1 + .../application_analytics/helpers/utils.tsx | 6 +- .../__tests__/query_utils.test.tsx | 44 +- .../__snapshots__/search.test.tsx.snap | 910 ++++++++++++++++++ .../common/search/__tests__/search.test.tsx | 27 + .../metrics/helpers/__tests__/utils.test.tsx | 2 +- public/components/metrics/helpers/utils.tsx | 12 +- .../slices/__tests__/metric_slice.test.tsx | 207 ++++ .../metrics/redux/slices/metrics_slice.ts | 54 +- .../__snapshots__/sidebar.test.tsx.snap | 618 ++++++------ .../sidebar/__tests__/sidebar.test.tsx | 43 +- public/components/metrics/sidebar/sidebar.tsx | 36 +- .../metrics_export.test.tsx.snap | 125 +++ .../__tests__/metrics_export.test.tsx | 188 ++++ .../__tests__/metrics_export_panel.test.tsx | 2 +- .../metrics/top_menu/metrics_export.tsx | 1 + .../view/__tests__/metrics_grid.test.tsx | 2 +- .../saved_object_visualization.tsx | 3 +- public/services/requests/ppl.ts | 2 + .../explorer_saved_object_loader.ts | 5 +- .../saved_object_loaders/ppl/ppl_loader.ts | 6 +- ...trics_contants.ts => metrics_constants.ts} | 6 + test/setup.jest.ts | 2 +- yarn.lock | 5 + 26 files changed, 1967 insertions(+), 383 deletions(-) create mode 100644 common/utils/__tests__/visualization_helpers.test.tsx create mode 100644 common/utils/visualization_helpers.ts create mode 100644 public/components/common/search/__tests__/__snapshots__/search.test.tsx.snap create mode 100644 public/components/common/search/__tests__/search.test.tsx create mode 100644 public/components/metrics/redux/slices/__tests__/metric_slice.test.tsx create mode 100644 public/components/metrics/top_menu/__tests__/__snapshots__/metrics_export.test.tsx.snap create mode 100644 public/components/metrics/top_menu/__tests__/metrics_export.test.tsx rename test/{metrics_contants.ts => metrics_constants.ts} (97%) diff --git a/common/utils/__tests__/visualization_helpers.test.tsx b/common/utils/__tests__/visualization_helpers.test.tsx new file mode 100644 index 000000000..6c7f9f857 --- /dev/null +++ b/common/utils/__tests__/visualization_helpers.test.tsx @@ -0,0 +1,24 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { getUserConfigFrom } from '../visualization_helpers'; + +describe('Utils helper functions', () => { + describe('getUserConfigFrom', () => { + it('should return empty object from empty input', () => { + expect(getUserConfigFrom(undefined)).toEqual({}); + expect(getUserConfigFrom('')).toEqual({}); + expect(getUserConfigFrom({})).toEqual({}); + }); + it('should get object from user_configs json', () => { + const container = { user_configs: '{ "key": "value" }' }; + expect(getUserConfigFrom(container)).toEqual({ key: 'value' }); + }); + it('should get object from userConfigs', () => { + const container = { userConfigs: '{ "key": "value" }' }; + expect(getUserConfigFrom(container)).toEqual({ key: 'value' }); + }); + }); +}); diff --git a/common/utils/visualization_helpers.ts b/common/utils/visualization_helpers.ts new file mode 100644 index 000000000..8a93cd7af --- /dev/null +++ b/common/utils/visualization_helpers.ts @@ -0,0 +1,19 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { isEmpty, isString } from 'lodash'; + +/* The file contains helper functions for visualizaitons operations + * getUserConfigFrom - returns input objects' user_configs or userConfigs, JSON parsed if necessary + */ + +export const getUserConfigFrom = (container: unknown): object => { + const config = container?.user_configs || container?.userConfigs || {}; + + if (isEmpty(config)) return {}; + + if (isString(config)) return JSON.parse(config); + else return {}; +}; diff --git a/package.json b/package.json index d9ac42f9c..ef7ee224b 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ }, "devDependencies": { "@cypress/skip-test": "^2.6.1", + "@testing-library/user-event": "^14.5.1", "@types/enzyme-adapter-react-16": "^1.0.6", "@types/mime": "^3.0.1", "@types/react-plotly.js": "^2.5.0", diff --git a/public/components/application_analytics/helpers/utils.tsx b/public/components/application_analytics/helpers/utils.tsx index 41f0b7048..67ac27fb7 100644 --- a/public/components/application_analytics/helpers/utils.tsx +++ b/public/components/application_analytics/helpers/utils.tsx @@ -16,6 +16,7 @@ import { NEW_SELECTED_QUERY_TAB, TAB_CREATED_TYPE } from '../../../../common/con import { SPAN_REGEX } from '../../../../common/constants/shared'; import { VisualizationType } from '../../../../common/types/custom_panels'; import { IField } from '../../../../common/types/explorer'; +import { getUserConfigFrom } from '../../../../common/utils/visualization_helpers'; import { preprocessQuery } from '../../common/query_utils'; import { fetchVisualizationById } from '../../../components/custom_panels/helpers/utils'; import { @@ -221,9 +222,8 @@ export const calculateAvailability = async ( // resolved mismatched use of user_configs/userConfigs. This fall-back // silently loads legacy configs. They will be stored in new userConfigs upon save. - const userConfigs = visData.userConfigs - ? JSON.parse(visData.user_configs || visData.userConfigs) - : {}; + const userConfigs = getUserConfigFrom(visData); + // 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/common/query_utils/__tests__/query_utils.test.tsx b/public/components/common/query_utils/__tests__/query_utils.test.tsx index da81fe446..2db7db97d 100644 --- a/public/components/common/query_utils/__tests__/query_utils.test.tsx +++ b/public/components/common/query_utils/__tests__/query_utils.test.tsx @@ -4,7 +4,12 @@ */ import React from 'react'; -import { findMinInterval, parsePromQLIntoKeywords } from '../'; +import { + findMinInterval, + parsePromQLIntoKeywords, + preprocessMetricQuery, + updateCatalogVisualizationQuery, +} from '../'; describe('Query Utils', () => { describe('parsePromQLIntoKeywords', () => { @@ -56,4 +61,41 @@ describe('Query Utils', () => { expect(minInterval).toEqual(span); }); }); + describe('Metric Query processors', () => { + const defaultQueryMetaData = { + catalogSourceName: 'my_catalog', + catalogTableName: 'metricName', + aggregation: 'avg', + attributesGroupBy: [], + start: 'now-1m', + end: 'now', + span: '1', + resolution: 'h', + }; + describe('updateCatalogVisualizationQuery', () => { + it('should build plain promQL series query', () => { + const query = updateCatalogVisualizationQuery(defaultQueryMetaData); + expect(query).toMatch(/avg \(metricName\)/); + }); + it('should build promQL with attributes grouping', () => { + const query = updateCatalogVisualizationQuery({ + ...defaultQueryMetaData, + attributesGroupBy: ['label1', 'label2'], + }); + expect(query).toMatch(/avg by\(label1,label2\) \(metricName\)/); + }); + }); + describe('preprocessMetricQuery', () => { + it('should set timestamps and default resolution', () => { + const [startDate, endDate] = ['2023-11-11', '2023-12-11']; + const [start, end] = [1699660800, 1702252800]; // 2023-11-11 to 2023-12-11 + const query = preprocessMetricQuery({ + metaData: { queryMetaData: defaultQueryMetaData }, + startTime: startDate, + endTime: endDate, + }); + expect(query).toMatch(new RegExp(`, ${start}, ${end}, '1d'`)); + }); + }); + }); }); diff --git a/public/components/common/search/__tests__/__snapshots__/search.test.tsx.snap b/public/components/common/search/__tests__/__snapshots__/search.test.tsx.snap new file mode 100644 index 000000000..b8a95255a --- /dev/null +++ b/public/components/common/search/__tests__/__snapshots__/search.test.tsx.snap @@ -0,0 +1,910 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Explorer Search component renders basic component 1`] = ` + + +
+ +
+ +
+ + PPL + + } + closePopover={[Function]} + display="inlineBlock" + hasArrow={true} + id="smallContextMenuExample" + isOpen={false} + ownFocus={true} + panelPaddingSize="none" + > +
+
+ + + + + +
+
+
+
+
+ +
+ +
+ + +