diff --git a/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/configurations/lens_attributes.ts b/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/configurations/lens_attributes.ts index 69080c22a13d0..dd98e9879e82a 100644 --- a/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/configurations/lens_attributes.ts +++ b/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/configurations/lens_attributes.ts @@ -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(), }, }; } diff --git a/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/configurations/lens_attributes/single_metric_attributes.ts b/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/configurations/lens_attributes/single_metric_attributes.ts index 1aab4261a5d15..fe206c64dd61c 100644 --- a/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/configurations/lens_attributes/single_metric_attributes.ts +++ b/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/configurations/lens_attributes/single_metric_attributes.ts @@ -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, diff --git a/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/configurations/synthetics/kpi_over_time_config.ts b/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/configurations/synthetics/kpi_over_time_config.ts index 115bb41f6630d..c935d45f9e124 100644 --- a/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/configurations/synthetics/kpi_over_time_config.ts +++ b/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/configurations/synthetics/kpi_over_time_config.ts @@ -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', diff --git a/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/configurations/synthetics/single_metric_config.ts b/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/configurations/synthetics/single_metric_config.ts index 5f74974a81a04..13d509a0919de 100644 --- a/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/configurations/synthetics/single_metric_config.ts +++ b/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/configurations/synthetics/single_metric_config.ts @@ -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', diff --git a/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/configurations/test_data/test_formula_metric_attribute.ts b/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/configurations/test_data/test_formula_metric_attribute.ts index 914ac7174f5e9..a269a7d4c6059 100644 --- a/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/configurations/test_data/test_formula_metric_attribute.ts +++ b/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/configurations/test_data/test_formula_metric_attribute.ts @@ -50,7 +50,7 @@ export const sampleMetricFormulaAttribute = { format: { id: 'percent', params: { - decimals: 1, + decimals: 3, }, }, formula: "1- (count(kql='summary.down > 0') / count())", diff --git a/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/embeddable/embeddable.tsx b/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/embeddable/embeddable.tsx index a0079568803b6..1b3028a0283ed 100644 --- a/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/embeddable/embeddable.tsx +++ b/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/embeddable/embeddable.tsx @@ -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 { diff --git a/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/embeddable/use_embeddable_attributes.ts b/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/embeddable/use_embeddable_attributes.ts index 4b58ce5366516..f1124d2a32a30 100644 --- a/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/embeddable/use_embeddable_attributes.ts +++ b/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/embeddable/use_embeddable_attributes.ts @@ -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, diff --git a/x-pack/plugins/observability_solution/synthetics/common/constants/client_defaults.ts b/x-pack/plugins/observability_solution/synthetics/common/constants/client_defaults.ts index f1098c89b7caa..0f4f2ca9441ad 100644 --- a/x-pack/plugins/observability_solution/synthetics/common/constants/client_defaults.ts +++ b/x-pack/plugins/observability_solution/synthetics/common/constants/client_defaults.ts @@ -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', + ], + }, +}); diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/hooks/use_monitor_filters.test.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/hooks/use_monitor_filters.test.ts new file mode 100644 index 0000000000000..830e2bce119ce --- /dev/null +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/hooks/use_monitor_filters.test.ts @@ -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'] }, + ]); + }); +}); diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/hooks/use_monitor_filters.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/hooks/use_monitor_filters.ts new file mode 100644 index 0000000000000..ed20d021349c0 --- /dev/null +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/hooks/use_monitor_filters.ts @@ -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]; +}; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/hooks/use_monitor_query_filters.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/hooks/use_monitor_query_filters.ts new file mode 100644 index 0000000000000..5ddf208da6115 --- /dev/null +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/hooks/use_monitor_query_filters.ts @@ -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]); +}; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_errors/monitor_async_error.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_errors/monitor_async_error.tsx index ae5a035b76b54..a40bb6e370783 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_errors/monitor_async_error.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_errors/monitor_async_error.tsx @@ -36,7 +36,7 @@ export const MonitorAsyncError = () => { defaultMessage="There was a problem running your monitors for one or more locations:" />
-