Skip to content

Commit

Permalink
[8.16] [Synthetics] Fix overview page vizs for large number of monito…
Browse files Browse the repository at this point in the history
…rs !! (#199512) (#201080)

# Backport

This will backport the following commits from `main` to `8.16`:
- [[Synthetics] Fix overview page vizs for large number of monitors !!
(#199512)](#199512)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT
[{"author":{"name":"Shahzad","email":"[email protected]"},"sourceCommit":{"committedDate":"2024-11-21T07:27:41Z","message":"[Synthetics]
Fix overview page vizs for large number of monitors !! (#199512)\n\n##
Summary\r\n\r\nFixes #187264
!!\r\n\r\nApply filters directly instead of passing each monitor id
!!\r\n\r\n### Testing\r\n\r\nNo special testing is needed, other than
make sure, alerts/errors vizs\r\ncontinue to work as expected
!!\r\n\r\n<img width=\"1726\"
alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/9c1889a5-4822-442b-97af-c2a4084c4503\">\r\n\r\n---------\r\n\r\nCo-authored-by:
kibanamachine
<[email protected]>","sha":"944e6fa0376702342bb37c3c9893e1574689b211","branchLabelMapping":{"^v9.0.0$":"main","^v8.18.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","ci:project-deploy-observability","Team:obs-ux-management","v8.16.0","backport:version","v8.17.0"],"title":"[Synthetics]
Fix overview page vizs for large number of monitors
!!","number":199512,"url":"https://github.com/elastic/kibana/pull/199512","mergeCommit":{"message":"[Synthetics]
Fix overview page vizs for large number of monitors !! (#199512)\n\n##
Summary\r\n\r\nFixes #187264
!!\r\n\r\nApply filters directly instead of passing each monitor id
!!\r\n\r\n### Testing\r\n\r\nNo special testing is needed, other than
make sure, alerts/errors vizs\r\ncontinue to work as expected
!!\r\n\r\n<img width=\"1726\"
alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/9c1889a5-4822-442b-97af-c2a4084c4503\">\r\n\r\n---------\r\n\r\nCo-authored-by:
kibanamachine
<[email protected]>","sha":"944e6fa0376702342bb37c3c9893e1574689b211"}},"sourceBranch":"main","suggestedTargetBranches":["8.16","8.17"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/199512","number":199512,"mergeCommit":{"message":"[Synthetics]
Fix overview page vizs for large number of monitors !! (#199512)\n\n##
Summary\r\n\r\nFixes #187264
!!\r\n\r\nApply filters directly instead of passing each monitor id
!!\r\n\r\n### Testing\r\n\r\nNo special testing is needed, other than
make sure, alerts/errors vizs\r\ncontinue to work as expected
!!\r\n\r\n<img width=\"1726\"
alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/9c1889a5-4822-442b-97af-c2a4084c4503\">\r\n\r\n---------\r\n\r\nCo-authored-by:
kibanamachine
<[email protected]>","sha":"944e6fa0376702342bb37c3c9893e1574689b211"}},{"branch":"8.16","label":"v8.16.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.17","label":"v8.17.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: Shahzad <[email protected]>
  • Loading branch information
kibanamachine and shahzad31 authored Nov 21, 2024
1 parent ab696dd commit d0a3d5d
Showing 22 changed files with 410 additions and 203 deletions.
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@

import { i18n } from '@kbn/i18n';
import { capitalize } from 'lodash';
import { ExistsFilter, isExistsFilter } from '@kbn/es-query';
import { ExistsFilter, Filter, isExistsFilter } from '@kbn/es-query';
import {
AvgIndexPatternColumn,
CardinalityIndexPatternColumn,
@@ -41,6 +41,7 @@ import type { DataView } from '@kbn/data-views-plugin/common';
import { PersistableFilter } from '@kbn/lens-plugin/common';
import { DataViewSpec } from '@kbn/data-views-plugin/common';
import { LegendSize } from '@kbn/visualizations-plugin/common/constants';
import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
import { urlFiltersToKueryString } from '../utils/stringify_kueries';
import {
FILTER_RECORDS,
@@ -169,17 +170,20 @@ export class LensAttributes {
globalFilter?: { query: string; language: string };
reportType: string;
lensFormulaHelper?: FormulaPublicApi;
dslFilters?: QueryDslQueryContainer[];

constructor(
layerConfigs: LayerConfig[],
reportType: string,
lensFormulaHelper?: FormulaPublicApi
lensFormulaHelper?: FormulaPublicApi,
dslFilters?: QueryDslQueryContainer[]
) {
this.layers = {};
this.seriesReferenceLines = {};
this.reportType = reportType;
this.lensFormulaHelper = lensFormulaHelper;
this.isMultiSeries = layerConfigs.length > 1;
this.dslFilters = dslFilters;

layerConfigs.forEach(({ seriesConfig, operationType }) => {
if (operationType && reportType !== ReportTypes.SINGLE_METRIC) {
@@ -1267,6 +1271,31 @@ export class LensAttributes {
return { internalReferences, adHocDataViews };
}

getFilters(): Filter[] {
const { internalReferences } = this.getReferences();

const dslFilters = this.dslFilters;
if (!dslFilters) {
return [];
}
return dslFilters.map((filter) => {
return {
meta: {
index: internalReferences?.[0].id,
type: 'query_string',
disabled: false,
negate: false,
alias: null,
key: 'query',
},
$state: {
store: 'appState',
},
query: filter,
} as Filter;
});
}

getJSON(
visualizationType: 'lnsXY' | 'lnsLegacyMetric' | 'lnsHeatmap' = 'lnsXY',
lastRefresh?: number
@@ -1290,7 +1319,7 @@ export class LensAttributes {
},
visualization: this.visualization,
query: query || { query: '', language: 'kuery' },
filters: [],
filters: this.getFilters(),
},
};
}
Original file line number Diff line number Diff line change
@@ -10,6 +10,7 @@ import { FormulaPublicApi, MetricState, OperationType } from '@kbn/lens-plugin/p
import type { DataView } from '@kbn/data-views-plugin/common';

import { Query } from '@kbn/es-query';
import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
import { getColorPalette } from '../synthetics/single_metric_config';
import { FORMULA_COLUMN, RECORDS_FIELD } from '../constants';
import { ColumnFilter, MetricOption } from '../../types';
@@ -28,9 +29,10 @@ export class SingleMetricLensAttributes extends LensAttributes {
constructor(
layerConfigs: LayerConfig[],
reportType: string,
lensFormulaHelper: FormulaPublicApi
lensFormulaHelper: FormulaPublicApi,
dslFilters?: QueryDslQueryContainer[]
) {
super(layerConfigs, reportType, lensFormulaHelper);
super(layerConfigs, reportType, lensFormulaHelper, dslFilters);
this.layers = {};
this.reportType = reportType;

@@ -145,7 +147,7 @@ export class SingleMetricLensAttributes extends LensAttributes {
? {
id: 'percent',
params: {
decimals: 1,
decimals: 3,
},
}
: undefined,
Original file line number Diff line number Diff line change
@@ -106,7 +106,7 @@ export function getSyntheticsKPIConfig({ dataView }: ConfigProps): SeriesConfig
label: 'Monitor Errors',
id: 'monitor_errors',
columnType: OPERATION_COLUMN,
field: 'monitor.check_group',
field: 'state.id',
columnFilters: [
{
language: 'kuery',
Original file line number Diff line number Diff line change
@@ -16,8 +16,7 @@ import { ConfigProps, SeriesConfig } from '../../types';
import { FieldLabels, FORMULA_COLUMN, RECORDS_FIELD } from '../constants';
import { buildExistsFilter } from '../utils';

export const FINAL_SUMMARY_KQL =
'summary: * and (summary.final_attempt: true or not summary.final_attempt: *)';
export const FINAL_SUMMARY_KQL = 'summary.final_attempt: true';
export function getSyntheticsSingleMetricConfig({ dataView }: ConfigProps): SeriesConfig {
return {
defaultSeriesType: 'line',
Original file line number Diff line number Diff line change
@@ -50,7 +50,7 @@ export const sampleMetricFormulaAttribute = {
format: {
id: 'percent',
params: {
decimals: 1,
decimals: 3,
},
},
formula: "1- (count(kql='summary.down > 0') / count())",
Original file line number Diff line number Diff line change
@@ -19,6 +19,7 @@ import { ViewMode } from '@kbn/embeddable-plugin/common';
import { observabilityFeatureId } from '@kbn/observability-shared-plugin/public';
import styled from 'styled-components';
import { AnalyticsServiceSetup } from '@kbn/core-analytics-browser';
import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
import { useEBTTelemetry } from '../hooks/use_ebt_telemetry';
import { AllSeries } from '../../../..';
import { AppDataType, ReportViewType } from '../types';
@@ -57,6 +58,7 @@ export interface ExploratoryEmbeddableProps {
lineHeight?: number;
dataTestSubj?: string;
searchSessionId?: string;
dslFilters?: QueryDslQueryContainer[];
}

export interface ExploratoryEmbeddableComponentProps extends ExploratoryEmbeddableProps {
Original file line number Diff line number Diff line change
@@ -21,6 +21,7 @@ export const useEmbeddableAttributes = ({
reportType,
reportConfigMap = {},
lensFormulaHelper,
dslFilters,
}: ExploratoryEmbeddableComponentProps) => {
const spaceId = useKibanaSpace();
const theme = useTheme();
@@ -40,7 +41,8 @@ export const useEmbeddableAttributes = ({
const lensAttributes = new SingleMetricLensAttributes(
layerConfigs,
reportType,
lensFormulaHelper!
lensFormulaHelper!,
dslFilters
);
return lensAttributes?.getJSON('lnsLegacyMetric');
} else if (reportType === ReportTypes.HEATMAP) {
@@ -51,7 +53,12 @@ export const useEmbeddableAttributes = ({
);
return lensAttributes?.getJSON('lnsHeatmap');
} else {
const lensAttributes = new LensAttributes(layerConfigs, reportType, lensFormulaHelper);
const lensAttributes = new LensAttributes(
layerConfigs,
reportType,
lensFormulaHelper,
dslFilters
);
return lensAttributes?.getJSON();
}
} catch (error) {
@@ -60,6 +67,7 @@ export const useEmbeddableAttributes = ({
}, [
attributes,
dataViewState,
dslFilters,
lensFormulaHelper,
reportConfigMap,
reportType,
Original file line number Diff line number Diff line change
@@ -112,3 +112,18 @@ export const getTimeSpanFilter = () => ({
},
},
});

export const getQueryFilters = (query: string) => ({
query_string: {
query: `${query}`,
fields: [
'monitor.name.text',
'tags',
'observer.geo.name',
'observer.name',
'urls',
'hosts',
'monitor.project.id',
],
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { renderHook } from '@testing-library/react-hooks';
import * as spaceHook from '../../../../../hooks/use_kibana_space';
import * as paramHook from '../../../hooks/use_url_params';
import * as redux from 'react-redux';
import { useMonitorFilters } from './use_monitor_filters';
import { WrappedHelper } from '../../../utils/testing';

describe('useMonitorFilters', () => {
beforeEach(() => {
jest.clearAllMocks();
});

const spaceSpy = jest.spyOn(spaceHook, 'useKibanaSpace');
const paramSpy = jest.spyOn(paramHook, 'useGetUrlParams');
const selSPy = jest.spyOn(redux, 'useSelector');

it('should return an empty array when no parameters are provided', () => {
const { result } = renderHook(() => useMonitorFilters({}), { wrapper: WrappedHelper });

expect(result.current).toEqual([]);
});

it('should return filters for allIds and schedules', () => {
spaceSpy.mockReturnValue({} as any);
paramSpy.mockReturnValue({ schedules: 'daily' } as any);
selSPy.mockReturnValue({ status: { allIds: ['id1', 'id2'] } });

const { result } = renderHook(() => useMonitorFilters({}), { wrapper: WrappedHelper });

expect(result.current).toEqual([{ field: 'monitor.id', values: ['id1', 'id2'] }]);
});

it('should return filters for allIds and empty schedules', () => {
spaceSpy.mockReturnValue({} as any);
paramSpy.mockReturnValue({ schedules: [] } as any);
selSPy.mockReturnValue({ status: { allIds: ['id1', 'id2'] } });

const { result } = renderHook(() => useMonitorFilters({}), { wrapper: WrappedHelper });

expect(result.current).toEqual([]);
});

it('should return filters for project IDs', () => {
spaceSpy.mockReturnValue({ space: null } as any);
paramSpy.mockReturnValue({ projects: ['project1', 'project2'] } as any);
selSPy.mockReturnValue({ status: { allIds: [] } });

const { result } = renderHook(() => useMonitorFilters({}), { wrapper: WrappedHelper });

expect(result.current).toEqual([
{ field: 'monitor.project.id', values: ['project1', 'project2'] },
]);
});

it('should return filters for tags and locations', () => {
spaceSpy.mockReturnValue({ space: null } as any);
paramSpy.mockReturnValue({
tags: ['tag1', 'tag2'],
locations: ['location1', 'location2'],
} as any);
selSPy.mockReturnValue({ status: { allIds: [] } });

const { result } = renderHook(() => useMonitorFilters({}), { wrapper: WrappedHelper });

expect(result.current).toEqual([
{ field: 'tags', values: ['tag1', 'tag2'] },
{ field: 'observer.geo.name', values: ['location1', 'location2'] },
]);
});

it('should include space filters for alerts', () => {
spaceSpy.mockReturnValue({ space: { id: 'space1' } } as any);
paramSpy.mockReturnValue({} as any);
selSPy.mockReturnValue({ status: { allIds: [] } });

const { result } = renderHook(() => useMonitorFilters({ forAlerts: true }), {
wrapper: WrappedHelper,
});

expect(result.current).toEqual([{ field: 'kibana.space_ids', values: ['space1'] }]);
});

it('should include space filters for non-alerts', () => {
spaceSpy.mockReturnValue({ space: { id: 'space2' } } as any);
paramSpy.mockReturnValue({} as any);
selSPy.mockReturnValue({ status: { allIds: [] } });

const { result } = renderHook(() => useMonitorFilters({}), { wrapper: WrappedHelper });

expect(result.current).toEqual([{ field: 'meta.space_id', values: ['space2'] }]);
});

it('should handle a combination of parameters', () => {
spaceSpy.mockReturnValue({ space: { id: 'space3' } } as any);
paramSpy.mockReturnValue({
schedules: 'daily',
projects: ['projectA'],
tags: ['tagB'],
locations: ['locationC'],
monitorTypes: 'http',
} as any);
selSPy.mockReturnValue({ status: { allIds: ['id3', 'id4'] } });

const { result } = renderHook(() => useMonitorFilters({ forAlerts: false }), {
wrapper: WrappedHelper,
});

expect(result.current).toEqual([
{ field: 'monitor.id', values: ['id3', 'id4'] },
{ field: 'monitor.project.id', values: ['projectA'] },
{ field: 'monitor.type', values: ['http'] },
{ field: 'tags', values: ['tagB'] },
{ field: 'observer.geo.name', values: ['locationC'] },
{ field: 'meta.space_id', values: ['space3'] },
]);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { UrlFilter } from '@kbn/exploratory-view-plugin/public';
import { useSelector } from 'react-redux';
import { isEmpty } from 'lodash';
import { useGetUrlParams } from '../../../hooks/use_url_params';
import { useKibanaSpace } from '../../../../../hooks/use_kibana_space';
import { selectOverviewStatus } from '../../../state/overview_status';

export const useMonitorFilters = ({ forAlerts }: { forAlerts?: boolean }): UrlFilter[] => {
const { space } = useKibanaSpace();
const { locations, monitorTypes, tags, projects, schedules } = useGetUrlParams();
const { status: overviewStatus } = useSelector(selectOverviewStatus);
const allIds = overviewStatus?.allIds ?? [];

return [
// since schedule isn't available in heartbeat data, in that case we rely on monitor.id
...(allIds?.length && !isEmpty(schedules) ? [{ field: 'monitor.id', values: allIds }] : []),
...(projects?.length ? [{ field: 'monitor.project.id', values: getValues(projects) }] : []),
...(monitorTypes?.length ? [{ field: 'monitor.type', values: getValues(monitorTypes) }] : []),
...(tags?.length ? [{ field: 'tags', values: getValues(tags) }] : []),
...(locations?.length ? [{ field: 'observer.geo.name', values: getValues(locations) }] : []),
...(space
? [{ field: forAlerts ? 'kibana.space_ids' : 'meta.space_id', values: [space.id] }]
: []),
];
};

const getValues = (values: string | string[]): string[] => {
return Array.isArray(values) ? values : [values];
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { useMemo } from 'react';
import { useGetUrlParams } from '../../../hooks';
import { getQueryFilters } from '../../../../../../common/constants/client_defaults';

export const useMonitorQueryFilters = () => {
const { query } = useGetUrlParams();

return useMemo(() => {
return query ? [getQueryFilters(query)] : undefined;
}, [query]);
};
Loading

0 comments on commit d0a3d5d

Please sign in to comment.