feat(slo): add group by field selector in forms (#163202)

This commit is contained in:
Kevin Delemme 2023-08-09 17:38:02 -04:00 committed by GitHub
parent a038fb09ca
commit 5d813006a3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 117 additions and 17 deletions

View file

@ -18,6 +18,8 @@ export interface UseFetchIndexPatternFieldsResponse {
export interface Field {
name: string;
type: string;
aggregatable: boolean;
searchable: boolean;
}
export function useFetchIndexPatternFields(

View file

@ -0,0 +1,90 @@
/*
* 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 {
EuiComboBox,
EuiComboBoxOptionOption,
EuiFlexItem,
EuiFormRow,
EuiIconTip,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { ALL_VALUE } from '@kbn/slo-schema';
import React from 'react';
import { Controller, useFormContext } from 'react-hook-form';
import { useFetchIndexPatternFields } from '../../../../hooks/slo/use_fetch_index_pattern_fields';
import { createOptionsFromFields } from '../../helpers/create_options';
import { CreateSLOForm } from '../../types';
interface Props {
index?: string;
}
export function GroupByFieldSelector({ index }: Props) {
const { control, getFieldState } = useFormContext<CreateSLOForm>();
const { isLoading, data: indexFields = [] } = useFetchIndexPatternFields(index);
const groupableFields = indexFields.filter((field) => field.aggregatable);
const label = i18n.translate('xpack.observability.slo.sloEdit.groupBy.placeholder', {
defaultMessage: 'Select an optional field to partition by',
});
return (
<EuiFlexItem>
<EuiFormRow
label={
<span>
{i18n.translate('xpack.observability.slo.sloEdit.groupBy.label', {
defaultMessage: 'Partition by',
})}{' '}
<EuiIconTip
content={i18n.translate('xpack.observability.slo.sloEdit.groupBy.tooltip', {
defaultMessage: 'Create individual SLOs for each value of the selected field.',
})}
position="top"
/>
</span>
}
isInvalid={getFieldState('groupBy').invalid}
>
<Controller
defaultValue={ALL_VALUE}
name="groupBy"
control={control}
rules={{ required: false }}
render={({ field, fieldState }) => (
<EuiComboBox
{...field}
async
placeholder={label}
aria-label={label}
isClearable
isDisabled={!index}
isInvalid={fieldState.invalid}
isLoading={!!index && isLoading}
onChange={(selected: EuiComboBoxOptionOption[]) => {
if (selected.length) {
return field.onChange(selected[0].value);
}
field.onChange(ALL_VALUE);
}}
options={createOptionsFromFields(groupableFields)}
selectedOptions={
!!index &&
!!field.value &&
groupableFields.some((groupableField) => groupableField.name === field.value)
? [{ value: field.value, label: field.value }]
: []
}
singleSelection
/>
)}
/>
</EuiFormRow>
</EuiFlexItem>
);
}

View file

@ -36,7 +36,6 @@ export function QueryBuilder({
useKibana().services;
const { control, getFieldState } = useFormContext<CreateSLOForm>();
const { dataView } = useCreateDataView({ indexPatternString });
return (

View file

@ -20,6 +20,7 @@ import { useFetchIndexPatternFields } from '../../../../hooks/slo/use_fetch_inde
import { createOptionsFromFields } from '../../helpers/create_options';
import { CreateSLOForm } from '../../types';
import { DataPreviewChart } from '../common/data_preview_chart';
import { GroupByFieldSelector } from '../common/group_by_field_selector';
import { QueryBuilder } from '../common/query_builder';
import { IndexSelection } from '../custom_common/index_selection';
@ -81,7 +82,7 @@ export function CustomKqlIndicatorTypeForm() {
? [{ value: field.value, label: field.value }]
: []
}
singleSelection={{ asPlainText: true }}
singleSelection
/>
)}
/>
@ -175,6 +176,8 @@ export function CustomKqlIndicatorTypeForm() {
/>
</EuiFlexItem>
<GroupByFieldSelector index={index} />
<DataPreviewChart />
</EuiFlexGroup>
);

View file

@ -27,15 +27,15 @@ import { DataPreviewChart } from '../common/data_preview_chart';
import { QueryBuilder } from '../common/query_builder';
import { IndexSelection } from '../custom_common/index_selection';
import { MetricIndicator } from './metric_indicator';
import { GroupByFieldSelector } from '../common/group_by_field_selector';
export { NEW_CUSTOM_METRIC } from './metric_indicator';
export function CustomMetricIndicatorTypeForm() {
const { control, watch, getFieldState } = useFormContext<CreateSLOForm>();
const { isLoading, data: indexFields } = useFetchIndexPatternFields(
watch('indicator.params.index')
);
const index = watch('indicator.params.index');
const { isLoading, data: indexFields } = useFetchIndexPatternFields(index);
const timestampFields = (indexFields ?? []).filter((field) => field.type === 'date');
return (
@ -181,6 +181,8 @@ export function CustomMetricIndicatorTypeForm() {
<EuiHorizontalRule margin="none" />
</EuiFlexItem>
<GroupByFieldSelector index={index} />
<DataPreviewChart />
</EuiFlexGroup>
</>

View file

@ -27,6 +27,7 @@ import { DataPreviewChart } from '../common/data_preview_chart';
import { QueryBuilder } from '../common/query_builder';
import { IndexSelection } from '../custom_common/index_selection';
import { HistogramIndicator } from './histogram_indicator';
import { GroupByFieldSelector } from '../common/group_by_field_selector';
export function HistogramIndicatorTypeForm() {
const { control, watch, getFieldState } = useFormContext<CreateSLOForm>();
@ -163,6 +164,9 @@ export function HistogramIndicatorTypeForm() {
<EuiFlexItem>
<EuiHorizontalRule margin="none" />
</EuiFlexItem>
<GroupByFieldSelector index={index} />
<DataPreviewChart />
</EuiFlexGroup>
</>

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import type { CreateSLOInput, SLOWithSummaryResponse, UpdateSLOInput } from '@kbn/slo-schema';
import { CreateSLOInput, SLOWithSummaryResponse, UpdateSLOInput } from '@kbn/slo-schema';
import { toDuration } from '../../../utils/slo/duration';
import { CreateSLOForm } from '../types';

View file

@ -29,7 +29,7 @@ export interface Props {
export function SloBadges({ activeAlerts, isLoading, rules, slo, onClickRuleBadge }: Props) {
return (
<EuiFlexGroup direction="row" responsive={false} gutterSize="s" alignItems="center">
<EuiFlexGroup direction="row" responsive={false} gutterSize="s" alignItems="center" wrap>
{isLoading ? (
<>
<EuiSkeletonRectangle

View file

@ -36,7 +36,7 @@ export function SloSummary({ slo, historicalSummary = [], historicalSummaryLoadi
return (
<EuiFlexGroup direction="row" justifyContent="spaceBetween" gutterSize="l" responsive={false}>
<EuiFlexItem grow={false} style={{ width: 200 }}>
<EuiFlexItem grow={false} style={{ maxWidth: 200 }}>
<EuiFlexGroup
direction="row"
responsive={false}
@ -73,7 +73,7 @@ export function SloSummary({ slo, historicalSummary = [], historicalSummaryLoadi
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem grow={false} style={{ width: 220 }}>
<EuiFlexItem grow={false} style={{ maxWidth: 200 }}>
<EuiFlexGroup
direction="row"
responsive={false}

View file

@ -25,11 +25,13 @@ export class FetchHistoricalSummary {
const sloIds = params.list.map((slo) => slo.sloId);
const sloList = await this.repository.findAllByIds(sloIds);
const list: SLOWithInstanceId[] = params.list.map(({ sloId, instanceId }) => ({
sloId,
instanceId,
slo: sloList.find((slo) => slo.id === sloId)!,
}));
const list: SLOWithInstanceId[] = params.list
.filter(({ sloId }) => sloList.find((slo) => slo.id === sloId))
.map(({ sloId, instanceId }) => ({
sloId,
instanceId,
slo: sloList.find((slo) => slo.id === sloId)!,
}));
const historicalSummary = await this.historicalSummaryClient.fetch(list);

View file

@ -20,8 +20,6 @@ import { DateRange, SLO, Summary } from '../../domain/models';
import { computeSLI, computeSummaryStatus, toErrorBudget } from '../../domain/services';
import { toDateRange } from '../../domain/services/date_range';
// TODO: Change name of this service...
// It does compute a summary but from the rollup data.
export interface SummaryClient {
computeSummary(slo: SLO, instanceId?: string): Promise<Summary>;
}

View file

@ -125,7 +125,7 @@ export class DefaultSummarySearchClient implements SummarySearchClient {
page: pagination.page,
results: finalResults.map((doc) => ({
id: doc._source!.slo.id,
instanceId: doc._source?.slo.instanceId ?? ALL_VALUE,
instanceId: doc._source!.slo.instanceId ?? ALL_VALUE,
summary: {
errorBudget: {
initial: toHighPrecision(doc._source!.errorBudgetInitial),