feat(slo): enhance search field selectors (#165122)

This commit is contained in:
Kevin Delemme 2023-08-31 16:09:02 -04:00 committed by GitHub
parent 2fa8eeaa7d
commit 8c8e9741cd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 308 additions and 310 deletions

View file

@ -9,15 +9,19 @@ import { EuiFlexGroup, EuiFlexItem, EuiIconTip } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
import { useFormContext } from 'react-hook-form';
import { useFetchIndexPatternFields } from '../../../../hooks/slo/use_fetch_index_pattern_fields';
import { CreateSLOForm } from '../../types';
import { FieldSelector } from '../apm_common/field_selector';
import { DataPreviewChart } from '../common/data_preview_chart';
import { GroupByFieldSelector } from '../common/group_by_field_selector';
import { IndexFieldSelector } from '../common/index_field_selector';
import { QueryBuilder } from '../common/query_builder';
export function ApmAvailabilityIndicatorTypeForm() {
const { watch } = useFormContext<CreateSLOForm>();
const index = watch('indicator.params.index');
const { isLoading: isIndexFieldsLoading, data: indexFields = [] } =
useFetchIndexPatternFields(index);
const partitionByFields = indexFields.filter((field) => field.aggregatable);
return (
<EuiFlexGroup direction="column" gutterSize="l">
@ -121,7 +125,28 @@ export function ApmAvailabilityIndicatorTypeForm() {
</EuiFlexItem>
</EuiFlexGroup>
<GroupByFieldSelector index={index} />
<IndexFieldSelector
indexFields={partitionByFields}
name="groupBy"
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>
}
placeholder={i18n.translate('xpack.observability.slo.sloEdit.groupBy.placeholder', {
defaultMessage: 'Select an optional field to partition by',
})}
isLoading={!!index && isIndexFieldsLoading}
isDisabled={!index}
/>
<DataPreviewChart />
</EuiFlexGroup>

View file

@ -9,15 +9,19 @@ import { EuiFieldNumber, EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiIconTip } fro
import { i18n } from '@kbn/i18n';
import React from 'react';
import { Controller, useFormContext } from 'react-hook-form';
import { useFetchIndexPatternFields } from '../../../../hooks/slo/use_fetch_index_pattern_fields';
import { CreateSLOForm } from '../../types';
import { FieldSelector } from '../apm_common/field_selector';
import { DataPreviewChart } from '../common/data_preview_chart';
import { GroupByFieldSelector } from '../common/group_by_field_selector';
import { IndexFieldSelector } from '../common/index_field_selector';
import { QueryBuilder } from '../common/query_builder';
export function ApmLatencyIndicatorTypeForm() {
const { control, watch, getFieldState } = useFormContext<CreateSLOForm>();
const index = watch('indicator.params.index');
const { isLoading: isIndexFieldsLoading, data: indexFields = [] } =
useFetchIndexPatternFields(index);
const partitionByFields = indexFields.filter((field) => field.aggregatable);
return (
<EuiFlexGroup direction="column" gutterSize="l">
@ -164,7 +168,28 @@ export function ApmLatencyIndicatorTypeForm() {
</EuiFlexItem>
</EuiFlexGroup>
<GroupByFieldSelector index={index} />
<IndexFieldSelector
indexFields={partitionByFields}
name="groupBy"
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>
}
placeholder={i18n.translate('xpack.observability.slo.sloEdit.groupBy.placeholder', {
defaultMessage: 'Select an optional field to partition by',
})}
isLoading={!!index && isIndexFieldsLoading}
isDisabled={!index}
/>
<DataPreviewChart />
</EuiFlexGroup>

View file

@ -1,90 +0,0 @@
/*
* 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

@ -0,0 +1,86 @@
/*
* 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 } from '@elastic/eui';
import { ALL_VALUE } from '@kbn/slo-schema';
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[];
name: 'groupBy' | 'indicator.params.timestampField';
label: React.ReactNode | string;
placeholder: string;
isDisabled: boolean;
isLoading: boolean;
isRequired?: boolean;
}
export function IndexFieldSelector({
indexFields,
name,
label,
placeholder,
isDisabled,
isLoading,
isRequired = false,
}: Props) {
const { control, getFieldState } = useFormContext<CreateSLOForm>();
const [options, setOptions] = useState<Option[]>(createOptionsFromFields(indexFields));
useEffect(() => {
setOptions(createOptionsFromFields(indexFields));
}, [indexFields]);
return (
<EuiFlexItem>
<EuiFormRow label={label} isInvalid={getFieldState(name).invalid}>
<Controller
defaultValue={ALL_VALUE}
name={name}
control={control}
rules={{ required: isRequired }}
render={({ field, fieldState }) => (
<EuiComboBox<string>
{...field}
async
placeholder={placeholder}
aria-label={placeholder}
isClearable
isDisabled={isLoading || isDisabled}
isInvalid={fieldState.invalid}
isLoading={isLoading}
onChange={(selected: EuiComboBoxOptionOption[]) => {
if (selected.length) {
return field.onChange(selected[0].value);
}
field.onChange(ALL_VALUE);
}}
options={options}
onSearchChange={(searchValue: string) => {
setOptions(
createOptionsFromFields(indexFields, ({ value }) => value.includes(searchValue))
);
}}
selectedOptions={
!!indexFields &&
!!field.value &&
indexFields.some((indexField) => indexField.name === field.value)
? [{ value: field.value, label: field.value }]
: []
}
singleSelection
/>
)}
/>
</EuiFormRow>
</EuiFlexItem>
);
}

View file

@ -5,31 +5,24 @@
* 2.0.
*/
import {
EuiComboBox,
EuiComboBoxOptionOption,
EuiFlexGroup,
EuiFlexItem,
EuiFormRow,
EuiIconTip,
} from '@elastic/eui';
import { EuiFlexGroup, EuiFlexItem, EuiIconTip } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
import { Controller, useFormContext } from 'react-hook-form';
import { 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';
import { DataPreviewChart } from '../common/data_preview_chart';
import { GroupByFieldSelector } from '../common/group_by_field_selector';
import { IndexFieldSelector } from '../common/index_field_selector';
import { QueryBuilder } from '../common/query_builder';
import { IndexSelection } from '../custom_common/index_selection';
export function CustomKqlIndicatorTypeForm() {
const { control, watch, getFieldState } = useFormContext<CreateSLOForm>();
const { watch } = useFormContext<CreateSLOForm>();
const index = watch('indicator.params.index');
const { isLoading, data: indexFields } = useFetchIndexPatternFields(index);
const timestampFields = (indexFields ?? []).filter((field) => field.type === 'date');
const { isLoading: isIndexFieldsLoading, data: indexFields = [] } =
useFetchIndexPatternFields(index);
const timestampFields = indexFields.filter((field) => field.type === 'date');
const partitionByFields = indexFields.filter((field) => field.aggregatable);
return (
<EuiFlexGroup direction="column" gutterSize="l">
@ -37,56 +30,22 @@ export function CustomKqlIndicatorTypeForm() {
<EuiFlexItem>
<IndexSelection />
</EuiFlexItem>
<EuiFlexItem>
<EuiFormRow
label={i18n.translate(
'xpack.observability.slo.sloEdit.sliType.customKql.timestampField.label',
{ defaultMessage: 'Timestamp field' }
)}
isInvalid={getFieldState('indicator.params.timestampField').invalid}
>
<Controller
name="indicator.params.timestampField"
defaultValue=""
rules={{ required: true }}
control={control}
render={({ field: { ref, ...field }, fieldState }) => (
<EuiComboBox
{...field}
async
placeholder={i18n.translate(
'xpack.observability.slo.sloEdit.sliType.customKql.timestampField.placeholder',
{ defaultMessage: 'Select a timestamp field' }
)}
aria-label={i18n.translate(
'xpack.observability.slo.sloEdit.sliType.customKql.timestampField.placeholder',
{ defaultMessage: 'Select a timestamp field' }
)}
data-test-subj="customKqlIndicatorFormTimestampFieldSelect"
isClearable
isDisabled={!index}
isInvalid={fieldState.invalid}
isLoading={!!index && isLoading}
onChange={(selected: EuiComboBoxOptionOption[]) => {
if (selected.length) {
return field.onChange(selected[0].value);
}
field.onChange('');
}}
options={createOptionsFromFields(timestampFields)}
selectedOptions={
!!index &&
!!field.value &&
timestampFields.some((timestampField) => timestampField.name === field.value)
? [{ value: field.value, label: field.value }]
: []
}
singleSelection
/>
)}
/>
</EuiFormRow>
<EuiFlexItem>
<IndexFieldSelector
indexFields={timestampFields}
name="indicator.params.timestampField"
label={i18n.translate('xpack.observability.slo.sloEdit.timestampField.label', {
defaultMessage: 'Timestamp field',
})}
placeholder={i18n.translate(
'xpack.observability.slo.sloEdit.timestampField.placeholder',
{ defaultMessage: 'Select a timestamp field' }
)}
isLoading={!!index && isIndexFieldsLoading}
isDisabled={!index}
isRequired
/>
</EuiFlexItem>
</EuiFlexGroup>
@ -176,7 +135,28 @@ export function CustomKqlIndicatorTypeForm() {
/>
</EuiFlexItem>
<GroupByFieldSelector index={index} />
<IndexFieldSelector
indexFields={partitionByFields}
name="groupBy"
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>
}
placeholder={i18n.translate('xpack.observability.slo.sloEdit.groupBy.placeholder', {
defaultMessage: 'Select an optional field to partition by',
})}
isLoading={!!index && isIndexFieldsLoading}
isDisabled={!index}
/>
<DataPreviewChart />
</EuiFlexGroup>

View file

@ -6,37 +6,34 @@
*/
import {
EuiComboBox,
EuiComboBoxOptionOption,
EuiFlexGroup,
EuiFlexItem,
EuiFormRow,
EuiHorizontalRule,
EuiIconTip,
EuiSpacer,
EuiTitle,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
import { Controller, useFormContext } from 'react-hook-form';
import { FormattedMessage } from '@kbn/i18n-react';
import React from 'react';
import { 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';
import { DataPreviewChart } from '../common/data_preview_chart';
import { IndexFieldSelector } from '../common/index_field_selector';
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 { watch } = useFormContext<CreateSLOForm>();
const index = watch('indicator.params.index');
const { isLoading, data: indexFields } = useFetchIndexPatternFields(index);
const timestampFields = (indexFields ?? []).filter((field) => field.type === 'date');
const { isLoading: isIndexFieldsLoading, data: indexFields = [] } =
useFetchIndexPatternFields(index);
const timestampFields = indexFields.filter((field) => field.type === 'date');
const partitionByFields = indexFields.filter((field) => field.aggregatable);
return (
<>
@ -55,61 +52,20 @@ export function CustomMetricIndicatorTypeForm() {
<IndexSelection />
</EuiFlexItem>
<EuiFlexItem>
<EuiFormRow
label={i18n.translate(
'xpack.observability.slo.sloEdit.sliType.customMetric.timestampField.label',
{ defaultMessage: 'Timestamp field' }
<IndexFieldSelector
indexFields={timestampFields}
name="indicator.params.timestampField"
label={i18n.translate('xpack.observability.slo.sloEdit.timestampField.label', {
defaultMessage: 'Timestamp field',
})}
placeholder={i18n.translate(
'xpack.observability.slo.sloEdit.timestampField.placeholder',
{ defaultMessage: 'Select a timestamp field' }
)}
isInvalid={getFieldState('indicator.params.timestampField').invalid}
>
<Controller
name="indicator.params.timestampField"
defaultValue=""
rules={{ required: true }}
control={control}
render={({ field: { ref, ...field }, fieldState }) => (
<EuiComboBox
{...field}
async
placeholder={i18n.translate(
'xpack.observability.slo.sloEdit.sliType.customMetric.timestampField.placeholder',
{ defaultMessage: 'Select a timestamp field' }
)}
aria-label={i18n.translate(
'xpack.observability.slo.sloEdit.sliType.customMetric.timestampField.placeholder',
{ defaultMessage: 'Select a timestamp field' }
)}
data-test-subj="customMetricIndicatorFormTimestampFieldSelect"
isClearable
isDisabled={!watch('indicator.params.index')}
isInvalid={fieldState.invalid}
isLoading={!!watch('indicator.params.index') && isLoading}
onChange={(selected: EuiComboBoxOptionOption[]) => {
if (selected.length) {
return field.onChange(selected[0].value);
}
field.onChange('');
}}
options={createOptionsFromFields(timestampFields)}
selectedOptions={
!!watch('indicator.params.index') &&
!!field.value &&
timestampFields.some((timestampField) => timestampField.name === field.value)
? [
{
value: field.value,
label: field.value,
'data-test-subj': `customMetricIndicatorFormTimestampFieldSelectedValue`,
},
]
: []
}
singleSelection={{ asPlainText: true }}
/>
)}
/>
</EuiFormRow>
isLoading={!!index && isIndexFieldsLoading}
isDisabled={!index}
isRequired
/>
</EuiFlexItem>
</EuiFlexGroup>
@ -157,7 +113,11 @@ export function CustomMetricIndicatorTypeForm() {
</h3>
</EuiTitle>
<EuiSpacer size="s" />
<MetricIndicator type="good" indexFields={indexFields} isLoadingIndex={isLoading} />
<MetricIndicator
type="good"
indexFields={indexFields}
isLoadingIndex={isIndexFieldsLoading}
/>
</EuiFlexItem>
<EuiFlexItem>
@ -174,14 +134,39 @@ export function CustomMetricIndicatorTypeForm() {
</h3>
</EuiTitle>
<EuiSpacer size="s" />
<MetricIndicator type="total" indexFields={indexFields} isLoadingIndex={isLoading} />
<MetricIndicator
type="total"
indexFields={indexFields}
isLoadingIndex={isIndexFieldsLoading}
/>
</EuiFlexItem>
<EuiFlexItem>
<EuiHorizontalRule margin="none" />
</EuiFlexItem>
<GroupByFieldSelector index={index} />
<IndexFieldSelector
indexFields={partitionByFields}
name="groupBy"
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>
}
placeholder={i18n.translate('xpack.observability.slo.sloEdit.groupBy.placeholder', {
defaultMessage: 'Select an optional field to partition by',
})}
isLoading={!!index && isIndexFieldsLoading}
isDisabled={!index}
/>
<DataPreviewChart />
</EuiFlexGroup>

View file

@ -28,7 +28,7 @@ import { QueryBuilder } from '../common/query_builder';
interface MetricIndicatorProps {
type: 'good' | 'total';
indexFields: Field[] | undefined;
indexFields: Field[];
isLoadingIndex: boolean;
}
@ -91,9 +91,7 @@ export function MetricIndicator({ type, indexFields, isLoadingIndex }: MetricInd
);
const { control, watch, setValue, register } = useFormContext<CreateSLOForm>();
const metricFields = (indexFields ?? []).filter((field) =>
SUPPORTED_FIELD_TYPES.includes(field.type)
);
const metricFields = indexFields.filter((field) => SUPPORTED_FIELD_TYPES.includes(field.type));
const { fields, append, remove } = useFieldArray({
control,

View file

@ -25,7 +25,7 @@ import { createOptionsFromFields } from '../../helpers/create_options';
interface HistogramIndicatorProps {
type: 'good' | 'total';
indexFields: Field[] | undefined;
indexFields: Field[];
isLoadingIndex: boolean;
}
@ -49,7 +49,7 @@ const AGGREGATION_OPTIONS = Object.values(AGGREGATIONS);
export function HistogramIndicator({ type, indexFields, isLoadingIndex }: HistogramIndicatorProps) {
const { control, watch } = useFormContext<CreateSLOForm>();
const histogramFields = (indexFields ?? []).filter((field) => field.type === 'histogram');
const histogramFields = indexFields.filter((field) => field.type === 'histogram');
const indexPattern = watch('indicator.params.index');
const aggregation = watch(`indicator.params.${type}.aggregation`);

View file

@ -6,35 +6,33 @@
*/
import {
EuiComboBox,
EuiComboBoxOptionOption,
EuiFlexGroup,
EuiFlexItem,
EuiFormRow,
EuiHorizontalRule,
EuiIconTip,
EuiSpacer,
EuiTitle,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
import { Controller, useFormContext } from 'react-hook-form';
import { FormattedMessage } from '@kbn/i18n-react';
import React from 'react';
import { 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';
import { DataPreviewChart } from '../common/data_preview_chart';
import { IndexFieldSelector } from '../common/index_field_selector';
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>();
const { watch } = useFormContext<CreateSLOForm>();
const index = watch('indicator.params.index');
const { isLoading, data: indexFields } = useFetchIndexPatternFields(index);
const timestampFields = (indexFields ?? []).filter((field) => field.type === 'date');
const { isLoading: isIndexFieldsLoading, data: indexFields = [] } =
useFetchIndexPatternFields(index);
const timestampFields = indexFields.filter((field) => field.type === 'date');
const partitionByFields = indexFields.filter((field) => field.aggregatable);
return (
<>
@ -53,55 +51,20 @@ export function HistogramIndicatorTypeForm() {
<IndexSelection />
</EuiFlexItem>
<EuiFlexItem>
<EuiFormRow
label={i18n.translate(
'xpack.observability.slo.sloEdit.sliType.histogram.timestampField.label',
{ defaultMessage: 'Timestamp field' }
<IndexFieldSelector
indexFields={timestampFields}
name="indicator.params.timestampField"
label={i18n.translate('xpack.observability.slo.sloEdit.timestampField.label', {
defaultMessage: 'Timestamp field',
})}
placeholder={i18n.translate(
'xpack.observability.slo.sloEdit.timestampField.placeholder',
{ defaultMessage: 'Select a timestamp field' }
)}
isInvalid={getFieldState('indicator.params.timestampField').invalid}
>
<Controller
name="indicator.params.timestampField"
defaultValue=""
rules={{ required: true }}
control={control}
render={({ field: { ref, ...field }, fieldState }) => (
<EuiComboBox
{...field}
async
placeholder={i18n.translate(
'xpack.observability.slo.sloEdit.sliType.histogram.timestampField.placeholder',
{ defaultMessage: 'Select a timestamp field' }
)}
aria-label={i18n.translate(
'xpack.observability.slo.sloEdit.sliType.histogram.timestampField.placeholder',
{ defaultMessage: 'Select a timestamp field' }
)}
data-test-subj="histogramIndicatorFormTimestampFieldSelect"
isClearable
isDisabled={!index}
isInvalid={fieldState.invalid}
isLoading={!!index && isLoading}
onChange={(selected: EuiComboBoxOptionOption[]) => {
if (selected.length) {
return field.onChange(selected[0].value);
}
field.onChange('');
}}
options={createOptionsFromFields(timestampFields)}
selectedOptions={
!!index &&
!!field.value &&
timestampFields.some((timestampField) => timestampField.name === field.value)
? [{ value: field.value, label: field.value }]
: []
}
singleSelection={{ asPlainText: true }}
/>
)}
/>
</EuiFormRow>
isLoading={!!index && isIndexFieldsLoading}
isDisabled={!index}
isRequired
/>
</EuiFlexItem>
</EuiFlexGroup>
@ -144,7 +107,11 @@ export function HistogramIndicatorTypeForm() {
</h3>
</EuiTitle>
<EuiSpacer size="s" />
<HistogramIndicator type="good" indexFields={indexFields} isLoadingIndex={isLoading} />
<HistogramIndicator
type="good"
indexFields={indexFields}
isLoadingIndex={isIndexFieldsLoading}
/>
</EuiFlexItem>
<EuiFlexItem>
<EuiHorizontalRule margin="none" />
@ -159,13 +126,38 @@ export function HistogramIndicatorTypeForm() {
</h3>
</EuiTitle>
<EuiSpacer size="s" />
<HistogramIndicator type="total" indexFields={indexFields} isLoadingIndex={isLoading} />
<HistogramIndicator
type="total"
indexFields={indexFields}
isLoadingIndex={isIndexFieldsLoading}
/>
</EuiFlexItem>
<EuiFlexItem>
<EuiHorizontalRule margin="none" />
</EuiFlexItem>
<GroupByFieldSelector index={index} />
<IndexFieldSelector
indexFields={partitionByFields}
name="groupBy"
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>
}
placeholder={i18n.translate('xpack.observability.slo.sloEdit.groupBy.placeholder', {
defaultMessage: 'Select an optional field to partition by',
})}
isLoading={!!index && isIndexFieldsLoading}
isDisabled={!index}
/>
<DataPreviewChart />
</EuiFlexGroup>

View file

@ -7,13 +7,22 @@
import { Field } from '../../../hooks/slo/use_fetch_index_pattern_fields';
interface Option {
export interface Option {
label: string;
value: string;
}
export function createOptionsFromFields(fields: Field[]): Option[] {
return fields
export function createOptionsFromFields(
fields: Field[],
filterFn?: (option: Option) => boolean
): Option[] {
const options = fields
.map((field) => ({ label: field.name, value: field.name }))
.sort((a, b) => String(a.label).localeCompare(b.label));
if (filterFn) {
return options.filter(filterFn);
}
return options;
}

View file

@ -27640,8 +27640,6 @@
"xpack.observability.slo.sloEdit.sliType.customKql.goodQuery.tooltip": "Cette requête KQL doit renvoyer un sous-ensemble d'événements considérés comme \"bons\" ou \"réussis\" aux fins du calcul du SLO. La requête doit filtrer les événements en fonction de certains critères pertinents, tels que les codes de statut, les messages d'erreur ou d'autres champs pertinents.",
"xpack.observability.slo.sloEdit.sliType.customKql.goodQueryPlaceholder": "Définir les bons événements",
"xpack.observability.slo.sloEdit.sliType.customKql.queryFilter": "Filtre de requête",
"xpack.observability.slo.sloEdit.sliType.customKql.timestampField.label": "Champ d'horodatage",
"xpack.observability.slo.sloEdit.sliType.customKql.timestampField.placeholder": "Sélectionner un champ d'horodatage",
"xpack.observability.slo.sloEdit.sliType.customKql.totalQuery": "Total de la requête",
"xpack.observability.slo.sloEdit.sliType.customKql.totalQuery.tooltip": "Cette requête KQL doit renvoyer tous les événements pertinents pour le calcul du SLO, y compris les bons et les mauvais événements.",
"xpack.observability.slo.sloEdit.sliType.customKql.totalQueryPlaceholder": "Définir le total d'événements",
@ -27655,8 +27653,6 @@
"xpack.observability.slo.sloEdit.sliType.customMetric.metricField.placeholder": "Sélectionner un champ dindicateur",
"xpack.observability.slo.sloEdit.sliType.customMetric.queryFilter": "Filtre de requête",
"xpack.observability.slo.sloEdit.sliType.customMetric.sumLabel": "Somme de",
"xpack.observability.slo.sloEdit.sliType.customMetric.timestampField.label": "Champ d'horodatage",
"xpack.observability.slo.sloEdit.sliType.customMetric.timestampField.placeholder": "Sélectionner un champ d'horodatage",
"xpack.observability.slo.sloEdit.tags.label": "Balises",
"xpack.observability.slo.sloEdit.tags.placeholder": "Ajouter des balises",
"xpack.observability.slo.sloEdit.targetSlo.label": "Cible/SLO (%)",

View file

@ -27640,8 +27640,6 @@
"xpack.observability.slo.sloEdit.sliType.customKql.goodQuery.tooltip": "このKQLクエリは、SLOを計算する目的で、「良好」または「成功」と見なされるイベントのサブセットを返します。このクエリは、ステータスコード、エラー、メッセージ、または他の関連するフィールドなどの一部の関連する条件に基づいて、イベントをフィルタリングします。",
"xpack.observability.slo.sloEdit.sliType.customKql.goodQueryPlaceholder": "良いイベントを定義",
"xpack.observability.slo.sloEdit.sliType.customKql.queryFilter": "クエリのフィルター",
"xpack.observability.slo.sloEdit.sliType.customKql.timestampField.label": "タイムスタンプフィールド",
"xpack.observability.slo.sloEdit.sliType.customKql.timestampField.placeholder": "タイムスタンプフィールドを選択",
"xpack.observability.slo.sloEdit.sliType.customKql.totalQuery": "合計クエリ",
"xpack.observability.slo.sloEdit.sliType.customKql.totalQuery.tooltip": "このKQLクエリは、良好なイベントと問題があるイベントの両方を含む、SLO計算に関連するすべてのイベントを返します。",
"xpack.observability.slo.sloEdit.sliType.customKql.totalQueryPlaceholder": "合計イベントを定義",
@ -27655,8 +27653,6 @@
"xpack.observability.slo.sloEdit.sliType.customMetric.metricField.placeholder": "メトリックフィールドを選択",
"xpack.observability.slo.sloEdit.sliType.customMetric.queryFilter": "クエリのフィルター",
"xpack.observability.slo.sloEdit.sliType.customMetric.sumLabel": "の合計",
"xpack.observability.slo.sloEdit.sliType.customMetric.timestampField.label": "タイムスタンプフィールド",
"xpack.observability.slo.sloEdit.sliType.customMetric.timestampField.placeholder": "タイムスタンプフィールドを選択",
"xpack.observability.slo.sloEdit.tags.label": "タグ",
"xpack.observability.slo.sloEdit.tags.placeholder": "タグを追加",
"xpack.observability.slo.sloEdit.targetSlo.label": "目標 / SLO%",

View file

@ -27638,8 +27638,6 @@
"xpack.observability.slo.sloEdit.sliType.customKql.goodQuery.tooltip": "此 KQL 查询应返回用于计算 SLO 时被视为“良好”或“成功”的事件的子集。此查询应基于某些相关条件(如状态代码、错误消息或其他相关字段)筛选事件。",
"xpack.observability.slo.sloEdit.sliType.customKql.goodQueryPlaceholder": "定义良好事件",
"xpack.observability.slo.sloEdit.sliType.customKql.queryFilter": "查询筛选",
"xpack.observability.slo.sloEdit.sliType.customKql.timestampField.label": "时间戳字段",
"xpack.observability.slo.sloEdit.sliType.customKql.timestampField.placeholder": "选择时间戳字段",
"xpack.observability.slo.sloEdit.sliType.customKql.totalQuery": "查询总数",
"xpack.observability.slo.sloEdit.sliType.customKql.totalQuery.tooltip": "此 KQL 查询应返回与 SLO 计算相关的所有事件,包括良好和不良事件。",
"xpack.observability.slo.sloEdit.sliType.customKql.totalQueryPlaceholder": "定义事件总数",
@ -27653,8 +27651,6 @@
"xpack.observability.slo.sloEdit.sliType.customMetric.metricField.placeholder": "选择指标字段",
"xpack.observability.slo.sloEdit.sliType.customMetric.queryFilter": "查询筛选",
"xpack.observability.slo.sloEdit.sliType.customMetric.sumLabel": "求和",
"xpack.observability.slo.sloEdit.sliType.customMetric.timestampField.label": "时间戳字段",
"xpack.observability.slo.sloEdit.sliType.customMetric.timestampField.placeholder": "选择时间戳字段",
"xpack.observability.slo.sloEdit.tags.label": "标签",
"xpack.observability.slo.sloEdit.tags.placeholder": "添加标签",
"xpack.observability.slo.sloEdit.targetSlo.label": "目标/SLO (%)",