mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 18:51:07 -04:00
chore(slo): Improve form field selectors (#167564)
This commit is contained in:
parent
8353a7e160
commit
f7095d5e50
5 changed files with 146 additions and 112 deletions
|
@ -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) => {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue