mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 18:51:07 -04:00
feat(slo): enhance search field selectors (#165122)
This commit is contained in:
parent
2fa8eeaa7d
commit
8c8e9741cd
13 changed files with 308 additions and 310 deletions
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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`);
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 d’indicateur",
|
||||
"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 (%)",
|
||||
|
|
|
@ -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(%)",
|
||||
|
|
|
@ -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 (%)",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue