chore(slo): Improve form field selectors (#167564)

This commit is contained in:
Kevin Delemme 2023-09-29 08:05:35 -04:00 committed by GitHub
parent 8353a7e160
commit f7095d5e50
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 146 additions and 112 deletions

View file

@ -6,7 +6,6 @@
*/
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';
@ -42,7 +41,7 @@ export function IndexFieldSelector({
<EuiFlexItem>
<EuiFormRow label={label} isInvalid={getFieldState(name).invalid}>
<Controller
defaultValue={ALL_VALUE}
defaultValue=""
name={name}
control={control}
rules={{ required: isRequired }}
@ -61,7 +60,7 @@ export function IndexFieldSelector({
return field.onChange(selected[0].value);
}
field.onChange(ALL_VALUE);
field.onChange('');
}}
options={options}
onSearchChange={(searchValue: string) => {

View file

@ -27,6 +27,8 @@ import { MetricIndicator } from './metric_indicator';
export { NEW_CUSTOM_METRIC } from './metric_indicator';
const SUPPORTED_METRIC_FIELD_TYPES = ['number', 'histogram'];
export function CustomMetricIndicatorTypeForm() {
const { watch } = useFormContext<CreateSLOForm>();
const index = watch('indicator.params.index');
@ -34,6 +36,9 @@ export function CustomMetricIndicatorTypeForm() {
useFetchIndexPatternFields(index);
const timestampFields = indexFields.filter((field) => field.type === 'date');
const partitionByFields = indexFields.filter((field) => field.aggregatable);
const metricFields = indexFields.filter((field) =>
SUPPORTED_METRIC_FIELD_TYPES.includes(field.type)
);
return (
<>
@ -115,7 +120,7 @@ export function CustomMetricIndicatorTypeForm() {
<EuiSpacer size="s" />
<MetricIndicator
type="good"
indexFields={indexFields}
metricFields={metricFields}
isLoadingIndex={isIndexFieldsLoading}
/>
</EuiFlexItem>
@ -136,7 +141,7 @@ export function CustomMetricIndicatorTypeForm() {
<EuiSpacer size="s" />
<MetricIndicator
type="total"
indexFields={indexFields}
metricFields={metricFields}
isLoadingIndex={isIndexFieldsLoading}
/>
</EuiFlexItem>

View file

@ -19,16 +19,16 @@ import {
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { first, range, xor } from 'lodash';
import React from 'react';
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 { createOptionsFromFields } from '../../helpers/create_options';
import { createOptionsFromFields, Option } from '../../helpers/create_options';
import { CreateSLOForm } from '../../types';
import { QueryBuilder } from '../common/query_builder';
interface MetricIndicatorProps {
type: 'good' | 'total';
indexFields: Field[];
metricFields: Field[];
isLoadingIndex: boolean;
}
@ -47,51 +47,52 @@ function createEquationFromMetric(names: string[]) {
return names.join(' + ');
}
const SUPPORTED_FIELD_TYPES = ['number', 'histogram'];
const metricLabel = i18n.translate(
'xpack.observability.slo.sloEdit.sliType.customMetric.metricLabel',
{ defaultMessage: 'Metric' }
);
export function MetricIndicator({ type, indexFields, isLoadingIndex }: MetricIndicatorProps) {
const metricLabel = i18n.translate(
'xpack.observability.slo.sloEdit.sliType.customMetric.metricLabel',
{ defaultMessage: 'Metric' }
);
const filterLabel = i18n.translate(
'xpack.observability.slo.sloEdit.sliType.customMetric.filterLabel',
{ defaultMessage: 'Filter' }
);
const filterLabel = i18n.translate(
'xpack.observability.slo.sloEdit.sliType.customMetric.filterLabel',
{ defaultMessage: 'Filter' }
);
const metricTooltip = (
<EuiIconTip
content={i18n.translate(
'xpack.observability.slo.sloEdit.sliType.customMetric.totalMetric.tooltip',
{
defaultMessage: 'This data from this field will be aggregated with the "sum" aggregation.',
}
)}
position="top"
/>
);
const metricTooltip = (
<EuiIconTip
content={i18n.translate(
'xpack.observability.slo.sloEdit.sliType.customMetric.totalMetric.tooltip',
{
defaultMessage:
'This data from this field will be aggregated with the "sum" aggregation.',
}
)}
position="top"
/>
);
const equationLabel = i18n.translate(
'xpack.observability.slo.sloEdit.sliType.customMetric.equationLabel',
{ defaultMessage: 'Equation' }
);
const equationLabel = i18n.translate(
'xpack.observability.slo.sloEdit.sliType.customMetric.equationLabel',
{ defaultMessage: 'Equation' }
);
const equationTooltip = (
<EuiIconTip
content={i18n.translate(
'xpack.observability.slo.sloEdit.sliType.customMetric.totalEquation.tooltip',
{
defaultMessage: 'This supports basic math (A + B / C) and boolean logic (A < B ? A : B).',
}
)}
position="top"
/>
);
const equationTooltip = (
<EuiIconTip
content={i18n.translate(
'xpack.observability.slo.sloEdit.sliType.customMetric.totalEquation.tooltip',
{
defaultMessage: 'This supports basic math (A + B / C) and boolean logic (A < B ? A : B).',
}
)}
position="top"
/>
);
export function MetricIndicator({ type, metricFields, isLoadingIndex }: MetricIndicatorProps) {
const { control, watch, setValue, register, getFieldState } = useFormContext<CreateSLOForm>();
const [options, setOptions] = useState<Option[]>(createOptionsFromFields(metricFields));
const { control, watch, setValue, register } = useFormContext<CreateSLOForm>();
const metricFields = indexFields.filter((field) => SUPPORTED_FIELD_TYPES.includes(field.type));
useEffect(() => {
setOptions(createOptionsFromFields(metricFields));
}, [metricFields]);
const { fields, append, remove } = useFieldArray({
control,
@ -134,6 +135,7 @@ export function MetricIndicator({ type, indexFields, isLoadingIndex }: MetricInd
<EuiFlexItem>
<EuiFormRow
fullWidth
isInvalid={getFieldState(`indicator.params.${type}.metrics.${index}.field`).invalid}
label={
<span>
{metricLabel} {metric.name} {metricTooltip}
@ -163,8 +165,9 @@ export function MetricIndicator({ type, indexFields, isLoadingIndex }: MetricInd
'xpack.observability.slo.sloEdit.sliType.customMetric.metricField.placeholder',
{ defaultMessage: 'Select a metric field' }
)}
isClearable
isInvalid={fieldState.invalid}
isDisabled={!indexPattern}
isDisabled={isLoadingIndex || !indexPattern}
isLoading={!!indexPattern && isLoadingIndex}
onChange={(selected: EuiComboBoxOptionOption[]) => {
if (selected.length) {
@ -184,7 +187,14 @@ export function MetricIndicator({ type, indexFields, isLoadingIndex }: MetricInd
]
: []
}
options={createOptionsFromFields(metricFields)}
onSearchChange={(searchValue: string) => {
setOptions(
createOptionsFromFields(metricFields, ({ value }) =>
value.includes(searchValue)
)
);
}}
options={options}
/>
)}
/>

View file

@ -5,9 +5,6 @@
* 2.0.
*/
import React, { Fragment } from 'react';
import { Controller, useFormContext } from 'react-hook-form';
import { i18n } from '@kbn/i18n';
import {
EuiComboBox,
EuiComboBoxOptionOption,
@ -18,14 +15,17 @@ import {
EuiIconTip,
EuiSpacer,
} from '@elastic/eui';
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';
import { Field } from '../../../../hooks/slo/use_fetch_index_pattern_fields';
import { createOptionsFromFields } from '../../helpers/create_options';
interface HistogramIndicatorProps {
type: 'good' | 'total';
indexFields: Field[];
histogramFields: Field[];
isLoadingIndex: boolean;
}
@ -46,62 +46,70 @@ const AGGREGATIONS = {
const AGGREGATION_OPTIONS = Object.values(AGGREGATIONS);
export function HistogramIndicator({ type, indexFields, isLoadingIndex }: HistogramIndicatorProps) {
const { control, watch } = useFormContext<CreateSLOForm>();
const aggregationTooltip = (
<EuiIconTip
content={i18n.translate(
'xpack.observability.slo.sloEdit.sliType.histogram.aggregationTooltip',
{
defaultMessage:
'The "value count" aggreation will return the total count for the histogram field. Range will return the count from the histogram field that is within the range defined below.',
}
)}
position="top"
/>
);
const fromTooltip = (
<EuiIconTip
content={i18n.translate('xpack.observability.slo.sloEdit.sliType.histogram.fromTooltip', {
defaultMessage: 'The "from" value is inclusive.',
})}
position="top"
/>
);
const toTooltip = (
<EuiIconTip
content={i18n.translate('xpack.observability.slo.sloEdit.sliType.histogram.toTooltip', {
defaultMessage: 'The "to" value is NOT inclusive.',
})}
position="top"
/>
);
const aggregationLabel = i18n.translate(
'xpack.observability.slo.sloEdit.sliType.histogram.aggregationLabel',
{ defaultMessage: 'Aggregation' }
);
const metricLabel = i18n.translate(
'xpack.observability.slo.sloEdit.sliType.histogram.metricLabel',
{ defaultMessage: 'Field' }
);
const toLabel = i18n.translate('xpack.observability.slo.sloEdit.sliType.histogram.toLabel', {
defaultMessage: 'To',
});
const fromLabel = i18n.translate('xpack.observability.slo.sloEdit.sliType.histogram.fromLabel', {
defaultMessage: 'From',
});
export function HistogramIndicator({
type,
histogramFields,
isLoadingIndex,
}: HistogramIndicatorProps) {
const { control, watch, getFieldState } = useFormContext<CreateSLOForm>();
const [options, setOptions] = useState<Option[]>(createOptionsFromFields(histogramFields));
useEffect(() => {
setOptions(createOptionsFromFields(histogramFields));
}, [histogramFields]);
const histogramFields = indexFields.filter((field) => field.type === 'histogram');
const indexPattern = watch('indicator.params.index');
const aggregation = watch(`indicator.params.${type}.aggregation`);
const aggregationTooltip = (
<EuiIconTip
content={i18n.translate(
'xpack.observability.slo.sloEdit.sliType.histogram.aggregationTooltip',
{
defaultMessage:
'The "value count" aggreation will return the total count for the histogram field. Range will return the count from the histogram field that is within the range defined below.',
}
)}
position="top"
/>
);
const fromTooltip = (
<EuiIconTip
content={i18n.translate('xpack.observability.slo.sloEdit.sliType.histogram.fromTooltip', {
defaultMessage: 'The "from" value is inclusive.',
})}
position="top"
/>
);
const toTooltip = (
<EuiIconTip
content={i18n.translate('xpack.observability.slo.sloEdit.sliType.histogram.toTooltip', {
defaultMessage: 'The "to" value is NOT inclusive.',
})}
position="top"
/>
);
const aggregationLabel = i18n.translate(
'xpack.observability.slo.sloEdit.sliType.histogram.aggregationLabel',
{ defaultMessage: 'Aggregation' }
);
const metricLabel = i18n.translate(
'xpack.observability.slo.sloEdit.sliType.histogram.metricLabel',
{ defaultMessage: 'Field' }
);
const toLabel = i18n.translate('xpack.observability.slo.sloEdit.sliType.histogram.toLabel', {
defaultMessage: 'To',
});
const fromLabel = i18n.translate('xpack.observability.slo.sloEdit.sliType.histogram.fromLabel', {
defaultMessage: 'From',
});
return (
<Fragment>
<EuiFlexGroup>
@ -149,7 +157,11 @@ export function HistogramIndicator({ type, indexFields, isLoadingIndex }: Histog
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem>
<EuiFormRow fullWidth label={<span>{metricLabel}</span>}>
<EuiFormRow
fullWidth
isInvalid={getFieldState(`indicator.params.${type}.field`).invalid}
label={<span>{metricLabel}</span>}
>
<Controller
name={`indicator.params.${type}.field`}
defaultValue=""
@ -170,7 +182,7 @@ export function HistogramIndicator({ type, indexFields, isLoadingIndex }: Histog
{ defaultMessage: 'Select a histogram field' }
)}
isInvalid={fieldState.invalid}
isDisabled={!indexPattern}
isDisabled={isLoadingIndex || !indexPattern}
isLoading={!!indexPattern && isLoadingIndex}
onChange={(selected: EuiComboBoxOptionOption[]) => {
if (selected.length) {
@ -190,7 +202,14 @@ export function HistogramIndicator({ type, indexFields, isLoadingIndex }: Histog
]
: []
}
options={createOptionsFromFields(histogramFields)}
onSearchChange={(searchValue: string) => {
setOptions(
createOptionsFromFields(histogramFields, ({ value }) =>
value.includes(searchValue)
)
);
}}
options={options}
/>
)}
/>

View file

@ -31,6 +31,7 @@ export function HistogramIndicatorTypeForm() {
const { isLoading: isIndexFieldsLoading, data: indexFields = [] } =
useFetchIndexPatternFields(index);
const histogramFields = indexFields.filter((field) => field.type === 'histogram');
const timestampFields = indexFields.filter((field) => field.type === 'date');
const partitionByFields = indexFields.filter((field) => field.aggregatable);
@ -109,7 +110,7 @@ export function HistogramIndicatorTypeForm() {
<EuiSpacer size="s" />
<HistogramIndicator
type="good"
indexFields={indexFields}
histogramFields={histogramFields}
isLoadingIndex={isIndexFieldsLoading}
/>
</EuiFlexItem>
@ -128,7 +129,7 @@ export function HistogramIndicatorTypeForm() {
<EuiSpacer size="s" />
<HistogramIndicator
type="total"
indexFields={indexFields}
histogramFields={histogramFields}
isLoadingIndex={isIndexFieldsLoading}
/>
</EuiFlexItem>