mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
feat(slo): add APM availability indicator type form (#151379)
This commit is contained in:
parent
b76ea2f69f
commit
a5113d8001
9 changed files with 252 additions and 19 deletions
3
.github/CODEOWNERS
vendored
3
.github/CODEOWNERS
vendored
|
@ -770,6 +770,9 @@ packages/kbn-yarn-lock-validator @elastic/kibana-operations
|
|||
/x-pack/plugins/observability/public/pages/cases @elastic/actionable-observability
|
||||
/x-pack/plugins/observability/public/pages/rules @elastic/actionable-observability
|
||||
/x-pack/plugins/observability/public/pages/rule_details @elastic/actionable-observability
|
||||
/x-pack/plugins/observability/public/pages/slos @elastic/actionable-observability
|
||||
/x-pack/plugins/observability/public/pages/slo_edit @elastic/actionable-observability
|
||||
/x-pack/plugins/observability/public/pages/slo_details @elastic/actionable-observability
|
||||
/x-pack/test/observability_functional @elastic/actionable-observability
|
||||
|
||||
# keep it below actionable observability
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { ComponentStory } from '@storybook/react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
|
||||
import { KibanaReactStorybookDecorator } from '../../../../utils/kibana_react.storybook_decorator';
|
||||
import {
|
||||
ApmAvailabilityIndicatorTypeForm as Component,
|
||||
Props,
|
||||
} from './apm_availability_indicator_type_form';
|
||||
import { SLO_EDIT_FORM_DEFAULT_VALUES } from '../../constants';
|
||||
|
||||
export default {
|
||||
component: Component,
|
||||
title: 'app/SLO/EditPage/ApmAvailability/Form',
|
||||
decorators: [KibanaReactStorybookDecorator],
|
||||
};
|
||||
|
||||
const Template: ComponentStory<typeof Component> = (props: Props) => {
|
||||
const methods = useForm({ defaultValues: SLO_EDIT_FORM_DEFAULT_VALUES });
|
||||
return (
|
||||
<FormProvider {...methods}>
|
||||
<Component {...props} control={methods.control} />
|
||||
</FormProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const defaultProps = {};
|
||||
|
||||
export const Form = Template.bind({});
|
||||
Form.args = defaultProps;
|
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import {
|
||||
EuiComboBox,
|
||||
EuiComboBoxOptionOption,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiFormLabel,
|
||||
} from '@elastic/eui';
|
||||
import { Control, Controller } from 'react-hook-form';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { CreateSLOInput } from '@kbn/slo-schema';
|
||||
|
||||
import { FieldSelector } from '../common/field_selector';
|
||||
|
||||
export interface Props {
|
||||
control: Control<CreateSLOInput>;
|
||||
}
|
||||
|
||||
export function ApmAvailabilityIndicatorTypeForm({ control }: Props) {
|
||||
return (
|
||||
<EuiFlexGroup direction="column" gutterSize="l">
|
||||
<EuiFlexGroup direction="row" gutterSize="l">
|
||||
<FieldSelector
|
||||
allowAllOption={false}
|
||||
label={i18n.translate('xpack.observability.slos.sloEdit.apmAvailability.serviceName', {
|
||||
defaultMessage: 'Service name',
|
||||
})}
|
||||
placeholder={i18n.translate(
|
||||
'xpack.observability.slos.sloEdit.apmAvailability.serviceName.placeholder',
|
||||
{
|
||||
defaultMessage: 'Select the APM service',
|
||||
}
|
||||
)}
|
||||
fieldName="service.name"
|
||||
name="indicator.params.service"
|
||||
control={control}
|
||||
dataTestSubj="apmAvailabilityServiceSelector"
|
||||
/>
|
||||
<FieldSelector
|
||||
label={i18n.translate(
|
||||
'xpack.observability.slos.sloEdit.apmAvailability.serviceEnvironment',
|
||||
{
|
||||
defaultMessage: 'Service environment',
|
||||
}
|
||||
)}
|
||||
placeholder={i18n.translate(
|
||||
'xpack.observability.slos.sloEdit.apmAvailability.serviceEnvironment.placeholder',
|
||||
{
|
||||
defaultMessage: 'Select the environment',
|
||||
}
|
||||
)}
|
||||
fieldName="service.environment"
|
||||
name="indicator.params.environment"
|
||||
control={control}
|
||||
dataTestSubj="apmAvailabilityEnvironmentSelector"
|
||||
/>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiFlexGroup direction="row" gutterSize="l">
|
||||
<FieldSelector
|
||||
label={i18n.translate(
|
||||
'xpack.observability.slos.sloEdit.apmAvailability.transactionType',
|
||||
{
|
||||
defaultMessage: 'Transaction type',
|
||||
}
|
||||
)}
|
||||
placeholder={i18n.translate(
|
||||
'xpack.observability.slos.sloEdit.apmAvailability.transactionType.placeholder',
|
||||
{
|
||||
defaultMessage: 'Select the transaction type',
|
||||
}
|
||||
)}
|
||||
fieldName="transaction.type"
|
||||
name="indicator.params.transactionType"
|
||||
control={control}
|
||||
dataTestSubj="apmAvailabilityTransactionTypeSelector"
|
||||
/>
|
||||
<FieldSelector
|
||||
label={i18n.translate(
|
||||
'xpack.observability.slos.sloEdit.apmAvailability.transactionName',
|
||||
{
|
||||
defaultMessage: 'Transaction name',
|
||||
}
|
||||
)}
|
||||
placeholder={i18n.translate(
|
||||
'xpack.observability.slos.sloEdit.apmAvailability.transactionName.placeholder',
|
||||
{
|
||||
defaultMessage: 'Select the transaction name',
|
||||
}
|
||||
)}
|
||||
fieldName="transaction.name"
|
||||
name="indicator.params.transactionName"
|
||||
control={control}
|
||||
dataTestSubj="apmAvailabilityTransactionNameSelector"
|
||||
/>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiFlexGroup direction="row" gutterSize="l">
|
||||
<EuiFlexItem>
|
||||
<EuiFormLabel>
|
||||
{i18n.translate('xpack.observability.slos.sloEdit.apmAvailability.goodStatusCodes', {
|
||||
defaultMessage: 'Good status codes',
|
||||
})}
|
||||
</EuiFormLabel>
|
||||
<Controller
|
||||
shouldUnregister={true}
|
||||
name="indicator.params.goodStatusCodes"
|
||||
control={control}
|
||||
defaultValue={['2xx', '3xx', '4xx']}
|
||||
rules={{ required: true }}
|
||||
render={({ field: { ref, ...field }, fieldState }) => (
|
||||
<EuiComboBox
|
||||
{...field}
|
||||
aria-label={i18n.translate(
|
||||
'xpack.observability.slos.sloEdit.apmAvailability.goodStatusCodes.placeholder',
|
||||
{
|
||||
defaultMessage: 'Select the good status codes',
|
||||
}
|
||||
)}
|
||||
placeholder={i18n.translate(
|
||||
'xpack.observability.slos.sloEdit.apmAvailability.goodStatusCodes.placeholder',
|
||||
{
|
||||
defaultMessage: 'Select the good status codes',
|
||||
}
|
||||
)}
|
||||
isInvalid={!!fieldState.error}
|
||||
options={generateStatusCodeOptions()}
|
||||
selectedOptions={generateStatusCodeOptions(field.value)}
|
||||
onChange={(selected: EuiComboBoxOptionOption[]) => {
|
||||
field.onChange(selected.map((opts) => opts.value));
|
||||
}}
|
||||
isClearable={true}
|
||||
data-test-subj="sloEditApmAvailabilityGoodStatusCodesSelector"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem />
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
||||
function generateStatusCodeOptions(codes: string[] = ['2xx', '3xx', '4xx', '5xx']) {
|
||||
return codes.map((code) => ({
|
||||
label: code,
|
||||
value: code,
|
||||
'data-test-subj': `${code}Option`,
|
||||
}));
|
||||
}
|
|
@ -11,7 +11,7 @@ import { Control, Controller } from 'react-hook-form';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import type { CreateSLOInput } from '@kbn/slo-schema';
|
||||
|
||||
import { FieldSelector } from './field_selector';
|
||||
import { FieldSelector } from '../common/field_selector';
|
||||
|
||||
export interface Props {
|
||||
control: Control<CreateSLOInput>;
|
||||
|
|
|
@ -15,7 +15,7 @@ import { SLO_EDIT_FORM_DEFAULT_VALUES } from '../../constants';
|
|||
|
||||
export default {
|
||||
component: Component,
|
||||
title: 'app/SLO/EditPage/ApmLatency/FieldSelector',
|
||||
title: 'app/SLO/EditPage/Common/FieldSelector',
|
||||
decorators: [KibanaReactStorybookDecorator],
|
||||
};
|
||||
|
|
@ -52,7 +52,7 @@ export function FieldSelector({
|
|||
? [
|
||||
{
|
||||
value: '*',
|
||||
label: i18n.translate('xpack.observability.slos.sloEdit.apmFieldSelector.all', {
|
||||
label: i18n.translate('xpack.observability.slos.sloEdit.fieldSelector.all', {
|
||||
defaultMessage: 'All',
|
||||
}),
|
||||
},
|
|
@ -9,6 +9,7 @@ import React, { useEffect } from 'react';
|
|||
import {
|
||||
EuiAvatar,
|
||||
EuiButton,
|
||||
EuiFlexGroup,
|
||||
EuiFormLabel,
|
||||
EuiPanel,
|
||||
EuiSelect,
|
||||
|
@ -36,6 +37,7 @@ import {
|
|||
import { paths } from '../../../config';
|
||||
import { SLI_OPTIONS, SLO_EDIT_FORM_DEFAULT_VALUES } from '../constants';
|
||||
import { ApmLatencyIndicatorTypeForm } from './apm_latency/apm_latency_indicator_type_form';
|
||||
import { ApmAvailabilityIndicatorTypeForm } from './apm_availability/apm_availability_indicator_type_form';
|
||||
|
||||
export interface Props {
|
||||
slo: SLOWithSummaryResponse | undefined;
|
||||
|
@ -110,6 +112,8 @@ export function SloEditForm({ slo }: Props) {
|
|||
return <CustomKqlIndicatorTypeForm control={control} watch={watch} />;
|
||||
case 'sli.apm.transactionDuration':
|
||||
return <ApmLatencyIndicatorTypeForm control={control} />;
|
||||
case 'sli.apm.transactionErrorRate':
|
||||
return <ApmAvailabilityIndicatorTypeForm control={control} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
@ -225,22 +229,34 @@ export function SloEditForm({ slo }: Props) {
|
|||
|
||||
<EuiSpacer size="xl" />
|
||||
|
||||
<EuiButton
|
||||
fill
|
||||
color="primary"
|
||||
data-test-subj="sloFormSubmitButton"
|
||||
onClick={handleSubmit}
|
||||
disabled={!formState.isValid}
|
||||
isLoading={loading && !error}
|
||||
>
|
||||
{isEditMode
|
||||
? i18n.translate('xpack.observability.slos.sloEdit.editSloButton', {
|
||||
defaultMessage: 'Update SLO',
|
||||
})
|
||||
: i18n.translate('xpack.observability.slos.sloEdit.createSloButton', {
|
||||
defaultMessage: 'Create SLO',
|
||||
})}
|
||||
</EuiButton>
|
||||
<EuiFlexGroup direction="row" gutterSize="s">
|
||||
<EuiButton
|
||||
fill
|
||||
color="primary"
|
||||
data-test-subj="sloFormSubmitButton"
|
||||
onClick={handleSubmit}
|
||||
disabled={!formState.isValid}
|
||||
isLoading={loading && !error}
|
||||
>
|
||||
{isEditMode
|
||||
? i18n.translate('xpack.observability.slos.sloEdit.editSloButton', {
|
||||
defaultMessage: 'Update SLO',
|
||||
})
|
||||
: i18n.translate('xpack.observability.slos.sloEdit.createSloButton', {
|
||||
defaultMessage: 'Create SLO',
|
||||
})}
|
||||
</EuiButton>
|
||||
<EuiButton
|
||||
fill
|
||||
color="ghost"
|
||||
data-test-subj="sloFormCancelButton"
|
||||
onClick={() => navigateToUrl(basePath.prepend(paths.observability.slos))}
|
||||
>
|
||||
{i18n.translate('xpack.observability.slos.sloEdit.cancelButton', {
|
||||
defaultMessage: 'Cancel',
|
||||
})}
|
||||
</EuiButton>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiSpacer size="xl" />
|
||||
</EuiPanel>
|
||||
|
|
|
@ -24,6 +24,12 @@ export const SLI_OPTIONS: Array<{
|
|||
defaultMessage: 'APM latency',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 'sli.apm.transactionErrorRate',
|
||||
text: i18n.translate('xpack.observability.slos.sliTypes.apmAvailabilityIndicator', {
|
||||
defaultMessage: 'APM availability',
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
export const BUDGETING_METHOD_OPTIONS: Array<{ value: BudgetingMethod; text: string }> = [
|
||||
|
|
|
@ -45,6 +45,20 @@ export function useSectionFormValidation({ getFieldState, getValues, formState,
|
|||
(field) => !getFieldState(field, formState).invalid
|
||||
);
|
||||
break;
|
||||
case 'sli.apm.transactionErrorRate':
|
||||
isIndicatorSectionValid =
|
||||
(
|
||||
[
|
||||
'indicator.params.service',
|
||||
'indicator.params.environment',
|
||||
'indicator.params.transactionType',
|
||||
'indicator.params.transactionName',
|
||||
] as const
|
||||
).every((field) => !getFieldState(field, formState).invalid && getValues(field) !== '') &&
|
||||
(['indicator.params.index', 'indicator.params.goodStatusCodes'] as const).every(
|
||||
(field) => !getFieldState(field, formState).invalid
|
||||
);
|
||||
break;
|
||||
default:
|
||||
isIndicatorSectionValid = false;
|
||||
break;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue