Skip to content

Commit

Permalink
feat(slo): show cardinality info on group by field (#173400)
Browse files Browse the repository at this point in the history
  • Loading branch information
kdelemme authored Dec 18, 2023
1 parent cd02fbd commit 43413ea
Show file tree
Hide file tree
Showing 14 changed files with 214 additions and 23 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* 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 { ALL_VALUE } from '@kbn/slo-schema';
import { useQuery } from '@tanstack/react-query';
import { lastValueFrom } from 'rxjs';
import { useKibana } from '../../utils/kibana_react';

export interface UseFetchIndexPatternFieldsResponse {
isLoading: boolean;
isSuccess: boolean;
isError: boolean;
data?: { cardinality: number; isHighCardinality: boolean };
}

const HIGH_CARDINALITY_THRESHOLD = 1000;

export function useFetchGroupByCardinality(
indexPattern: string,
timestampField: string,
groupBy: string
): UseFetchIndexPatternFieldsResponse {
const { data: dataService } = useKibana().services;

const { isLoading, isError, isSuccess, data } = useQuery({
queryKey: ['fetchGroupByCardinality', indexPattern, timestampField, groupBy],
queryFn: async ({ signal }) => {
try {
const result = await lastValueFrom(
dataService.search.search({
params: {
index: indexPattern,
body: {
query: {
bool: {
filter: [{ range: { [timestampField]: { gte: 'now-24h' } } }],
},
},
aggs: {
groupByCardinality: {
cardinality: {
field: groupBy,
},
},
},
},
},
})
);

// @ts-expect-error Property 'value' does not exist on type 'AggregationsAggregate'
const cardinality = result.rawResponse?.aggregations?.groupByCardinality?.value ?? 0;
return { cardinality, isHighCardinality: cardinality > HIGH_CARDINALITY_THRESHOLD };
} catch (error) {
throw new Error(`Something went wrong. Error: ${error}`);
}
},
retry: false,
refetchOnWindowFocus: false,
enabled:
Boolean(indexPattern) && Boolean(timestampField) && Boolean(groupBy) && groupBy !== ALL_VALUE,
});

return { isLoading, isError, isSuccess, data };
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,15 @@
* 2.0.
*/

import { FieldSpec } from '@kbn/data-views-plugin/common';
import { useQuery } from '@tanstack/react-query';
import { useKibana } from '../../utils/kibana_react';

export interface UseFetchIndexPatternFieldsResponse {
isLoading: boolean;
isSuccess: boolean;
isError: boolean;
data: Field[] | undefined;
}

export interface Field {
name: string;
type: string;
aggregatable: boolean;
searchable: boolean;
data: FieldSpec[] | undefined;
}

export function useFetchIndexPatternFields(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
* 2.0.
*/

import { EuiFlexGroup, EuiFlexItem, EuiIconTip } from '@elastic/eui';
import { EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiIconTip } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { ALL_VALUE } from '@kbn/slo-schema/src/schema/common';
import React, { useEffect } from 'react';
import { useFormContext } from 'react-hook-form';
import { useFetchGroupByCardinality } from '../../../../hooks/slo/use_fetch_group_by_cardinality';
import { useFetchApmIndex } from '../../../../hooks/slo/use_fetch_apm_indices';
import { useFetchIndexPatternFields } from '../../../../hooks/slo/use_fetch_index_pattern_fields';
import { CreateSLOForm } from '../../types';
Expand All @@ -27,9 +28,14 @@ export function ApmAvailabilityIndicatorTypeForm() {
setValue('indicator.params.index', apmIndex);
}
}, [setValue, apmIndex]);
const timestampField = watch('indicator.params.timestampField');
const groupByField = watch('groupBy');

const { isLoading: isIndexFieldsLoading, data: indexFields = [] } =
useFetchIndexPatternFields(apmIndex);

const { isLoading: isGroupByCardinalityLoading, data: groupByCardinality } =
useFetchGroupByCardinality(apmIndex, timestampField, groupByField);
const groupByFields = indexFields.filter((field) => field.aggregatable);

return (
Expand Down Expand Up @@ -158,6 +164,19 @@ export function ApmAvailabilityIndicatorTypeForm() {
isDisabled={!apmIndex}
/>

{!isGroupByCardinalityLoading && !!groupByCardinality && (
<EuiCallOut
size="s"
iconType={groupByCardinality.isHighCardinality ? 'warning' : ''}
color={groupByCardinality.isHighCardinality ? 'warning' : 'primary'}
title={i18n.translate('xpack.observability.slo.sloEdit.groupBy.cardinalityInfo', {
defaultMessage:
"Selected group by field '{groupBy}' will generate at least {card} SLO instances based on the last 24h sample data.",
values: { card: groupByCardinality.cardinality, groupBy: groupByField },
})}
/>
)}

<DataPreviewChart />
</EuiFlexGroup>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,19 @@
* 2.0.
*/

import { EuiFieldNumber, EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiIconTip } from '@elastic/eui';
import {
EuiCallOut,
EuiFieldNumber,
EuiFlexGroup,
EuiFlexItem,
EuiFormRow,
EuiIconTip,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { ALL_VALUE } from '@kbn/slo-schema/src/schema/common';
import React, { useEffect } from 'react';
import { Controller, useFormContext } from 'react-hook-form';
import { useFetchGroupByCardinality } from '../../../../hooks/slo/use_fetch_group_by_cardinality';
import { useFetchApmIndex } from '../../../../hooks/slo/use_fetch_apm_indices';
import { useFetchIndexPatternFields } from '../../../../hooks/slo/use_fetch_index_pattern_fields';
import { CreateSLOForm } from '../../types';
Expand All @@ -28,8 +36,14 @@ export function ApmLatencyIndicatorTypeForm() {
}
}, [setValue, apmIndex]);

const timestampField = watch('indicator.params.timestampField');
const groupByField = watch('groupBy');

const { isLoading: isIndexFieldsLoading, data: indexFields = [] } =
useFetchIndexPatternFields(apmIndex);

const { isLoading: isGroupByCardinalityLoading, data: groupByCardinality } =
useFetchGroupByCardinality(apmIndex, timestampField, groupByField);
const groupByFields = indexFields.filter((field) => field.aggregatable);

return (
Expand Down Expand Up @@ -201,6 +215,19 @@ export function ApmLatencyIndicatorTypeForm() {
isDisabled={!apmIndex}
/>

{!isGroupByCardinalityLoading && !!groupByCardinality && (
<EuiCallOut
size="s"
iconType={groupByCardinality.isHighCardinality ? 'warning' : ''}
color={groupByCardinality.isHighCardinality ? 'warning' : 'primary'}
title={i18n.translate('xpack.observability.slo.sloEdit.groupBy.cardinalityInfo', {
defaultMessage:
"Selected group by field '{groupBy}' will generate at least {card} SLO instances based on the last 24h sample data.",
values: { card: groupByCardinality.cardinality, groupBy: groupByField },
})}
/>
)}

<DataPreviewChart />
</EuiFlexGroup>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@
*/

import { EuiComboBox, EuiComboBoxOptionOption, EuiFlexItem, EuiFormRow } from '@elastic/eui';
import { FieldSpec } from '@kbn/data-views-plugin/common';
import React, { useEffect, useState } from 'react';
import { Controller, useFormContext } from 'react-hook-form';
import { Field } from '../../../../hooks/slo/use_fetch_index_pattern_fields';
import { createOptionsFromFields, Option } from '../../helpers/create_options';
import { CreateSLOForm } from '../../types';

interface Props {
indexFields: Field[];
indexFields: FieldSpec[];
name: 'groupBy' | 'indicator.params.timestampField';
label: React.ReactNode | string;
placeholder: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
* 2.0.
*/

import { EuiFlexGroup, EuiFlexItem, EuiIconTip } from '@elastic/eui';
import { EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiIconTip } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { ALL_VALUE } from '@kbn/slo-schema/src/schema/common';
import React from 'react';
import { useFormContext } from 'react-hook-form';
import { useFetchGroupByCardinality } from '../../../../hooks/slo/use_fetch_group_by_cardinality';
import { useFetchIndexPatternFields } from '../../../../hooks/slo/use_fetch_index_pattern_fields';
import { CreateSLOForm } from '../../types';
import { DataPreviewChart } from '../common/data_preview_chart';
Expand All @@ -20,11 +21,17 @@ import { IndexSelection } from '../custom_common/index_selection';
export function CustomKqlIndicatorTypeForm() {
const { watch } = useFormContext<CreateSLOForm>();
const index = watch('indicator.params.index');
const timestampField = watch('indicator.params.timestampField');
const groupByField = watch('groupBy');

const { isLoading: isIndexFieldsLoading, data: indexFields = [] } =
useFetchIndexPatternFields(index);
const timestampFields = indexFields.filter((field) => field.type === 'date');
const groupByFields = indexFields.filter((field) => field.aggregatable);

const { isLoading: isGroupByCardinalityLoading, data: groupByCardinality } =
useFetchGroupByCardinality(index, timestampField, groupByField);

return (
<EuiFlexGroup direction="column" gutterSize="l">
<EuiFlexGroup direction="row" gutterSize="l">
Expand Down Expand Up @@ -158,6 +165,19 @@ export function CustomKqlIndicatorTypeForm() {
isDisabled={!index}
/>

{!isGroupByCardinalityLoading && !!groupByCardinality && (
<EuiCallOut
size="s"
iconType={groupByCardinality.isHighCardinality ? 'warning' : ''}
color={groupByCardinality.isHighCardinality ? 'warning' : 'primary'}
title={i18n.translate('xpack.observability.slo.sloEdit.groupBy.cardinalityInfo', {
defaultMessage:
"Selected group by field '{groupBy}' will generate at least {card} SLO instances based on the last 24h sample data.",
values: { card: groupByCardinality.cardinality, groupBy: groupByField },
})}
/>
)}

<DataPreviewChart />
</EuiFlexGroup>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

import {
EuiCallOut,
EuiFlexGroup,
EuiFlexItem,
EuiHorizontalRule,
Expand All @@ -18,6 +19,7 @@ import { FormattedMessage } from '@kbn/i18n-react';
import { ALL_VALUE } from '@kbn/slo-schema/src/schema/common';
import React from 'react';
import { useFormContext } from 'react-hook-form';
import { useFetchGroupByCardinality } from '../../../../hooks/slo/use_fetch_group_by_cardinality';
import { useFetchIndexPatternFields } from '../../../../hooks/slo/use_fetch_index_pattern_fields';
import { CreateSLOForm } from '../../types';
import { DataPreviewChart } from '../common/data_preview_chart';
Expand All @@ -33,8 +35,14 @@ const SUPPORTED_METRIC_FIELD_TYPES = ['number', 'histogram'];
export function CustomMetricIndicatorTypeForm() {
const { watch } = useFormContext<CreateSLOForm>();
const index = watch('indicator.params.index');
const timestampField = watch('indicator.params.timestampField');
const groupByField = watch('groupBy');

const { isLoading: isIndexFieldsLoading, data: indexFields = [] } =
useFetchIndexPatternFields(index);
const { isLoading: isGroupByCardinalityLoading, data: groupByCardinality } =
useFetchGroupByCardinality(index, timestampField, groupByField);

const timestampFields = indexFields.filter((field) => field.type === 'date');
const groupByFields = indexFields.filter((field) => field.aggregatable);
const metricFields = indexFields.filter((field) =>
Expand Down Expand Up @@ -175,6 +183,19 @@ export function CustomMetricIndicatorTypeForm() {
isDisabled={!index}
/>

{!isGroupByCardinalityLoading && !!groupByCardinality && (
<EuiCallOut
size="s"
iconType={groupByCardinality.isHighCardinality ? 'warning' : ''}
color={groupByCardinality.isHighCardinality ? 'warning' : 'primary'}
title={i18n.translate('xpack.observability.slo.sloEdit.groupBy.cardinalityInfo', {
defaultMessage:
"Selected group by field '{groupBy}' will generate at least {card} SLO instances based on the last 24h sample data.",
values: { card: groupByCardinality.cardinality, groupBy: groupByField },
})}
/>
)}

<DataPreviewChart />
</EuiFlexGroup>
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ import {
EuiIconTip,
EuiSpacer,
} from '@elastic/eui';
import { FieldSpec } from '@kbn/data-views-plugin/common';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { first, range, xor } from 'lodash';
import React, { useEffect, useState } from 'react';
import { Controller, useFieldArray, useFormContext } from 'react-hook-form';
import { Field } from '../../../../hooks/slo/use_fetch_index_pattern_fields';
import {
aggValueToLabel,
CUSTOM_METRIC_AGGREGATION_OPTIONS,
Expand All @@ -32,7 +32,7 @@ import { QueryBuilder } from '../common/query_builder';

interface MetricIndicatorProps {
type: 'good' | 'total';
metricFields: Field[];
metricFields: FieldSpec[];
isLoadingIndex: boolean;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,17 @@ import {
EuiIconTip,
EuiSpacer,
} from '@elastic/eui';
import { FieldSpec } from '@kbn/data-views-plugin/common';
import { i18n } from '@kbn/i18n';
import React, { Fragment, useEffect, useState } from 'react';
import { Controller, useFormContext } from 'react-hook-form';
import { Field } from '../../../../hooks/slo/use_fetch_index_pattern_fields';
import { createOptionsFromFields, Option } from '../../helpers/create_options';
import { CreateSLOForm } from '../../types';
import { QueryBuilder } from '../common/query_builder';

interface HistogramIndicatorProps {
type: 'good' | 'total';
histogramFields: Field[];
histogramFields: FieldSpec[];
isLoadingIndex: boolean;
}

Expand Down
Loading

0 comments on commit 43413ea

Please sign in to comment.