diff --git a/src/platform/packages/shared/response-ops/rule_form/src/hooks/use_rule_form_steps.test.tsx b/src/platform/packages/shared/response-ops/rule_form/src/hooks/use_rule_form_steps.test.tsx index 1bcfcd6f954c5..14ef59bec057e 100644 --- a/src/platform/packages/shared/response-ops/rule_form/src/hooks/use_rule_form_steps.test.tsx +++ b/src/platform/packages/shared/response-ops/rule_form/src/hooks/use_rule_form_steps.test.tsx @@ -14,6 +14,8 @@ import { RULE_FORM_PAGE_RULE_DEFINITION_TITLE, RULE_FORM_PAGE_RULE_ACTIONS_TITLE, RULE_FORM_PAGE_RULE_DETAILS_TITLE, + RULE_FORM_PAGE_RULE_DEFINITION_TITLE_SHORT, + RULE_FORM_PAGE_RULE_DETAILS_TITLE_SHORT, } from '../translations'; import { RuleFormData } from '../types'; import { EuiSteps, EuiStepsHorizontal } from '@elastic/eui'; @@ -145,9 +147,9 @@ describe('useRuleFormHorizontalSteps', () => { render(); - expect(screen.getByText(RULE_FORM_PAGE_RULE_DEFINITION_TITLE)).toBeInTheDocument(); + expect(screen.getByText(RULE_FORM_PAGE_RULE_DEFINITION_TITLE_SHORT)).toBeInTheDocument(); expect(screen.getByText(RULE_FORM_PAGE_RULE_ACTIONS_TITLE)).toBeInTheDocument(); - expect(screen.getByText(RULE_FORM_PAGE_RULE_DETAILS_TITLE)).toBeInTheDocument(); + expect(screen.getByText(RULE_FORM_PAGE_RULE_DETAILS_TITLE_SHORT)).toBeInTheDocument(); }); test('tracks current step successfully', async () => { diff --git a/src/platform/packages/shared/response-ops/rule_form/src/hooks/use_rule_form_steps.tsx b/src/platform/packages/shared/response-ops/rule_form/src/hooks/use_rule_form_steps.tsx index d484f6d8c58c6..74b926a2bc20c 100644 --- a/src/platform/packages/shared/response-ops/rule_form/src/hooks/use_rule_form_steps.tsx +++ b/src/platform/packages/shared/response-ops/rule_form/src/hooks/use_rule_form_steps.tsx @@ -18,6 +18,8 @@ import { RULE_FORM_PAGE_RULE_ACTIONS_TITLE, RULE_FORM_PAGE_RULE_DEFINITION_TITLE, RULE_FORM_PAGE_RULE_DETAILS_TITLE, + RULE_FORM_PAGE_RULE_DEFINITION_TITLE_SHORT, + RULE_FORM_PAGE_RULE_DETAILS_TITLE_SHORT, } from '../translations'; import { hasActionsError, hasActionsParamsErrors, hasParamsErrors } from '../validation'; import { RuleFormStepId } from '../constants'; @@ -27,6 +29,7 @@ interface UseRuleFormStepsOptions { touchedSteps: Record; /* Used to track the current step in horizontal steps, not used for vertical steps */ currentStep?: RuleFormStepId; + shortTitles?: boolean; } /** @@ -69,7 +72,11 @@ const getStepStatus = ({ }; // Create a common hook for both horizontal and vertical steps -const useCommonRuleFormSteps = ({ touchedSteps, currentStep }: UseRuleFormStepsOptions) => { +const useCommonRuleFormSteps = ({ + touchedSteps, + currentStep, + shortTitles, +}: UseRuleFormStepsOptions) => { const { plugins: { application }, baseErrors = {}, @@ -132,7 +139,9 @@ const useCommonRuleFormSteps = ({ touchedSteps, currentStep }: UseRuleFormStepsO const steps = useMemo( () => ({ [RuleFormStepId.DEFINITION]: { - title: RULE_FORM_PAGE_RULE_DEFINITION_TITLE, + title: shortTitles + ? RULE_FORM_PAGE_RULE_DEFINITION_TITLE_SHORT + : RULE_FORM_PAGE_RULE_DEFINITION_TITLE, status: ruleDefinitionStatus, children: , }, @@ -150,7 +159,9 @@ const useCommonRuleFormSteps = ({ touchedSteps, currentStep }: UseRuleFormStepsO } : null, [RuleFormStepId.DETAILS]: { - title: RULE_FORM_PAGE_RULE_DETAILS_TITLE, + title: shortTitles + ? RULE_FORM_PAGE_RULE_DETAILS_TITLE_SHORT + : RULE_FORM_PAGE_RULE_DETAILS_TITLE, status: ruleDetailsStatus, children: ( <> @@ -161,7 +172,7 @@ const useCommonRuleFormSteps = ({ touchedSteps, currentStep }: UseRuleFormStepsO ), }, }), - [ruleDefinitionStatus, canReadConnectors, actionsStatus, ruleDetailsStatus] + [ruleDefinitionStatus, canReadConnectors, actionsStatus, ruleDetailsStatus, shortTitles] ); const stepOrder: RuleFormStepId[] = useMemo( @@ -247,6 +258,7 @@ export const useRuleFormHorizontalSteps: () => RuleFormHorizontalSteps = () => { const { steps, stepOrder } = useCommonRuleFormSteps({ touchedSteps, currentStep, + shortTitles: true, }); // Determine current navigation position diff --git a/src/platform/packages/shared/response-ops/rule_form/src/rule_actions/rule_actions.tsx b/src/platform/packages/shared/response-ops/rule_form/src/rule_actions/rule_actions.tsx index 2479c07edfb5f..168a7d4f5a3c3 100644 --- a/src/platform/packages/shared/response-ops/rule_form/src/rule_actions/rule_actions.tsx +++ b/src/platform/packages/shared/response-ops/rule_form/src/rule_actions/rule_actions.tsx @@ -7,22 +7,41 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import React, { useCallback, useMemo, useState } from 'react'; -import { EuiButton, EuiSpacer, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { v4 as uuidv4 } from 'uuid'; +import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiImage, EuiSpacer, EuiText } from '@elastic/eui'; import { RuleSystemAction } from '@kbn/alerting-types'; import { ActionConnector } from '@kbn/alerts-ui-shared'; -import { ADD_ACTION_TEXT } from '../translations'; -import { RuleActionsConnectorsModal } from './rule_actions_connectors_modal'; -import { useRuleFormDispatch, useRuleFormState } from '../hooks'; +import React, { useCallback, useMemo, useState } from 'react'; +import useEffectOnce from 'react-use/lib/useEffectOnce'; +import { v4 as uuidv4 } from 'uuid'; import { RuleAction, RuleFormParamsErrors } from '../common/types'; import { DEFAULT_FREQUENCY, MULTI_CONSUMER_RULE_TYPE_IDS } from '../constants'; +import { useRuleFormDispatch, useRuleFormState } from '../hooks'; +import { + ADD_ACTION_DESCRIPTION_TEXT, + ADD_ACTION_HEADER, + ADD_ACTION_OPTIONAL_TEXT, + ADD_ACTION_TEXT, +} from '../translations'; +import { getDefaultParams } from '../utils'; +import { RuleActionsConnectorsModal } from './rule_actions_connectors_modal'; import { RuleActionsItem } from './rule_actions_item'; import { RuleActionsSystemActionsItem } from './rule_actions_system_actions_item'; -import { getDefaultParams } from '../utils'; + +const useRuleActionsIllustration = () => { + const [imageData, setImageData] = useState(''); + useEffectOnce(() => { + const fetchImage = async () => { + const image = await import('./rule_actions_illustration.svg'); + setImageData(image.default); + }; + fetchImage(); + }); + return imageData; +}; export const RuleActions = () => { const [isConnectorModalOpen, setIsConnectorModalOpen] = useState(false); + const ruleActionsIllustration = useRuleActionsIllustration(); const { formData: { actions, consumer }, @@ -92,6 +111,8 @@ export const RuleActions = () => { return selectedRuleType.producer; }, [consumer, multiConsumerSelection, selectedRuleType]); + const hasActions = actions.length > 0; + return ( <> @@ -120,15 +141,49 @@ export const RuleActions = () => { ); })} + {!hasActions && ( + + + + + + {ADD_ACTION_HEADER} + + + {ADD_ACTION_OPTIONAL_TEXT} + + + + + {ADD_ACTION_DESCRIPTION_TEXT} + + + + + )} - - {ADD_ACTION_TEXT} - + + + + {ADD_ACTION_TEXT} + + + {isConnectorModalOpen && ( )} diff --git a/src/platform/packages/shared/response-ops/rule_form/src/rule_actions/rule_actions_illustration.svg b/src/platform/packages/shared/response-ops/rule_form/src/rule_actions/rule_actions_illustration.svg new file mode 100644 index 0000000000000..caab2d84dd8f0 --- /dev/null +++ b/src/platform/packages/shared/response-ops/rule_form/src/rule_actions/rule_actions_illustration.svg @@ -0,0 +1,1025 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/platform/packages/shared/response-ops/rule_form/src/rule_definition/rule_definition.tsx b/src/platform/packages/shared/response-ops/rule_form/src/rule_definition/rule_definition.tsx index 5cedd0d117527..70f63642e653b 100644 --- a/src/platform/packages/shared/response-ops/rule_form/src/rule_definition/rule_definition.tsx +++ b/src/platform/packages/shared/response-ops/rule_form/src/rule_definition/rule_definition.tsx @@ -193,20 +193,20 @@ export const RuleDefinition = () => { return ( - + - + {selectedRuleType.name} - + {selectedRuleTypeModel.description} {docsUrl && ( - + ({ + RuleDefinition: () => , +})); + +jest.mock('../rule_actions', () => ({ + RuleActions: () => , +})); + +jest.mock('../rule_details', () => ({ + RuleDetails: () => , +})); + +jest.mock('../hooks/use_rule_form_state', () => ({ + useRuleFormState: jest.fn(), +})); + +jest.mock('../hooks/use_rule_form_dispatch', () => ({ + useRuleFormDispatch: jest.fn(), +})); + +const { useRuleFormState } = jest.requireMock('../hooks/use_rule_form_state'); + +const navigateToUrl = jest.fn(); + +const formDataMock: RuleFormData = { + params: { + aggType: 'count', + termSize: 5, + thresholdComparator: '>', + timeWindowSize: 5, + timeWindowUnit: 'm', + groupBy: 'all', + threshold: [1000], + index: ['.kibana'], + timeField: 'alert.executionStatus.lastExecutionDate', + }, + actions: [], + consumer: 'stackAlerts', + schedule: { interval: '1m' }, + tags: [], + name: 'test', + notifyWhen: 'onActionGroupChange', + alertDelay: { + active: 10, + }, +}; + +const onCancel = jest.fn(); + +useRuleFormState.mockReturnValue({ + plugins: { + application: { + navigateToUrl, + capabilities: { + actions: { + show: true, + save: true, + execute: true, + }, + }, + }, + }, + baseErrors: {}, + paramsErrors: {}, + multiConsumerSelection: 'logs', + formData: formDataMock, + connectors: [], + connectorTypes: [], + aadTemplateFields: [], +}); + +const onSave = jest.fn(); + +describe('ruleFlyout', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + test('renders correctly', () => { + render(); + + expect(screen.getByText(RULE_FORM_PAGE_RULE_DEFINITION_TITLE_SHORT)).toBeInTheDocument(); + expect(screen.getByText(RULE_FORM_PAGE_RULE_ACTIONS_TITLE)).toBeInTheDocument(); + expect(screen.getByText(RULE_FORM_PAGE_RULE_DETAILS_TITLE_SHORT)).toBeInTheDocument(); + + expect(screen.getByTestId('ruleFlyoutFooterCancelButton')).toBeInTheDocument(); + expect(screen.getByTestId('ruleFlyoutFooterNextStepButton')).toBeInTheDocument(); + }); + + test('should navigate back and forth through steps correctly', async () => { + render(); + + fireEvent.click(screen.getByTestId('ruleFlyoutFooterNextStepButton')); + await waitFor(() => + expect(screen.getByTestId('ruleFlyoutFooterPreviousStepButton')).toBeInTheDocument() + ); + fireEvent.click(screen.getByTestId('ruleFlyoutFooterNextStepButton')); + await waitFor(() => + expect(screen.getByTestId('ruleFlyoutFooterSaveButton')).toBeInTheDocument() + ); + fireEvent.click(screen.getByTestId('ruleFlyoutFooterPreviousStepButton')); + await waitFor(() => + expect(screen.getByTestId('ruleFlyoutFooterNextStepButton')).toBeInTheDocument() + ); + }); + + test('should call onSave when save button is pressed', async () => { + render(); + + fireEvent.click(screen.getByTestId('ruleFlyoutFooterNextStepButton')); + await waitFor(() => + expect(screen.getByTestId('ruleFlyoutFooterPreviousStepButton')).toBeInTheDocument() + ); + fireEvent.click(screen.getByTestId('ruleFlyoutFooterNextStepButton')); + await waitFor(() => + expect(screen.getByTestId('ruleFlyoutFooterSaveButton')).toBeInTheDocument() + ); + fireEvent.click(screen.getByTestId('ruleFlyoutFooterSaveButton')); + + expect(onSave).toHaveBeenCalledWith({ + ...formDataMock, + consumer: 'logs', + }); + }); + + test('should call onCancel when the cancel button is clicked', () => { + render(); + + fireEvent.click(screen.getByTestId('ruleFlyoutFooterCancelButton')); + expect(onCancel).toHaveBeenCalled(); + }); +}); diff --git a/src/platform/packages/shared/response-ops/rule_form/src/rule_flyout/rule_flyout.tsx b/src/platform/packages/shared/response-ops/rule_form/src/rule_flyout/rule_flyout.tsx new file mode 100644 index 0000000000000..f1a873302b823 --- /dev/null +++ b/src/platform/packages/shared/response-ops/rule_form/src/rule_flyout/rule_flyout.tsx @@ -0,0 +1,40 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { EuiFlyout, EuiPortal } from '@elastic/eui'; +import React from 'react'; +import type { RuleFormData } from '../types'; +import { RuleFlyoutBody } from './rule_flyout_body'; + +interface RuleFlyoutProps { + isEdit?: boolean; + isSaving?: boolean; + onCancel?: () => void; + onSave: (formData: RuleFormData) => void; +} + +// Wrapper component for the rule flyout. Currently only displays RuleFlyoutBody, but will be extended to conditionally +// display the Show Request UI or the Action Connector UI. These UIs take over the entire flyout, so we need to swap out +// their body elements entirely to avoid adding another EuiFlyout element to the DOM +export const RuleFlyout = ({ onSave, isEdit, isSaving, onCancel = () => {} }: RuleFlyoutProps) => { + return ( + + + + + + ); +}; diff --git a/src/platform/packages/shared/response-ops/rule_form/src/rule_flyout/rule_flyout_body.tsx b/src/platform/packages/shared/response-ops/rule_form/src/rule_flyout/rule_flyout_body.tsx new file mode 100644 index 0000000000000..ec5590b3a587b --- /dev/null +++ b/src/platform/packages/shared/response-ops/rule_form/src/rule_flyout/rule_flyout_body.tsx @@ -0,0 +1,155 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { + EuiFlyoutBody, + EuiFlyoutHeader, + EuiStepsHorizontal, + EuiTitle, + EuiSpacer, + EuiCallOut, +} from '@elastic/eui'; +import { checkActionFormActionTypeEnabled } from '@kbn/alerts-ui-shared'; +import React, { useCallback, useMemo } from 'react'; +import { useRuleFormHorizontalSteps, useRuleFormState } from '../hooks'; +import { + RULE_FLYOUT_HEADER_CREATE_TITLE, + RULE_FLYOUT_HEADER_EDIT_TITLE, + DISABLED_ACTIONS_WARNING_TITLE, +} from '../translations'; +import type { RuleFormData } from '../types'; +import { hasRuleErrors } from '../validation'; +import { RuleFlyoutCreateFooter } from './rule_flyout_create_footer'; +import { RuleFlyoutEditFooter } from './rule_flyout_edit_footer'; +import { RuleFlyoutEditTabs } from './rule_flyout_edit_tabs'; + +interface RuleFlyoutBodyProps { + isEdit?: boolean; + isSaving?: boolean; + onCancel: () => void; + onSave: (formData: RuleFormData) => void; +} + +export const RuleFlyoutBody = ({ + isEdit = false, + isSaving = false, + onCancel, + onSave, +}: RuleFlyoutBodyProps) => { + const { + formData, + multiConsumerSelection, + connectorTypes, + connectors, + baseErrors = {}, + paramsErrors = {}, + actionsErrors = {}, + actionsParamsErrors = {}, + } = useRuleFormState(); + + const hasErrors = useMemo(() => { + const hasBrokenConnectors = formData.actions.some((action) => { + return !connectors.find((connector) => connector.id === action.id); + }); + + if (hasBrokenConnectors) { + return true; + } + + return hasRuleErrors({ + baseErrors, + paramsErrors, + actionsErrors, + actionsParamsErrors, + }); + }, [formData, connectors, baseErrors, paramsErrors, actionsErrors, actionsParamsErrors]); + + const { + steps, + currentStepComponent, + goToNextStep, + goToPreviousStep, + hasNextStep, + hasPreviousStep, + } = useRuleFormHorizontalSteps(); + + const { actions } = formData; + + const onSaveInternal = useCallback(() => { + onSave({ + ...formData, + ...(multiConsumerSelection ? { consumer: multiConsumerSelection } : {}), + }); + }, [onSave, formData, multiConsumerSelection]); + + const hasActionsDisabled = useMemo(() => { + const preconfiguredConnectors = connectors.filter((connector) => connector.isPreconfigured); + return actions.some((action) => { + const actionType = connectorTypes.find(({ id }) => id === action.actionTypeId); + if (!actionType) { + return false; + } + const checkEnabledResult = checkActionFormActionTypeEnabled( + actionType, + preconfiguredConnectors + ); + return !actionType.enabled && !checkEnabledResult.isEnabled; + }); + }, [actions, connectors, connectorTypes]); + + return ( + <> + + + + {isEdit ? RULE_FLYOUT_HEADER_EDIT_TITLE : RULE_FLYOUT_HEADER_CREATE_TITLE} + + + {isEdit && } + + + {!isEdit && } + {hasActionsDisabled && ( + <> + + + > + )} + {currentStepComponent} + + {isEdit ? ( + {} /* TODO */} + isSaving={isSaving} + hasErrors={hasErrors} + /> + ) : ( + {} /* TODO */} + goToNextStep={goToNextStep} + goToPreviousStep={goToPreviousStep} + isSaving={isSaving} + hasNextStep={hasNextStep} + hasPreviousStep={hasPreviousStep} + hasErrors={hasErrors} + /> + )} + > + ); +}; diff --git a/src/platform/packages/shared/response-ops/rule_form/src/rule_flyout/rule_flyout_create_footer.tsx b/src/platform/packages/shared/response-ops/rule_form/src/rule_flyout/rule_flyout_create_footer.tsx new file mode 100644 index 0000000000000..caacacb9240dc --- /dev/null +++ b/src/platform/packages/shared/response-ops/rule_form/src/rule_flyout/rule_flyout_create_footer.tsx @@ -0,0 +1,107 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { + EuiButton, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiFlyoutFooter, +} from '@elastic/eui'; +import React from 'react'; +import { + RULE_FLYOUT_FOOTER_BACK_TEXT, + RULE_FLYOUT_FOOTER_CANCEL_TEXT, + RULE_FLYOUT_FOOTER_CREATE_TEXT, + RULE_FLYOUT_FOOTER_NEXT_TEXT, + RULE_PAGE_FOOTER_SHOW_REQUEST_TEXT, +} from '../translations'; + +export interface RuleFlyoutCreateFooterProps { + isSaving: boolean; + hasErrors: boolean; + onCancel: () => void; + onSave: () => void; + onShowRequest: () => void; + hasNextStep: boolean; + hasPreviousStep: boolean; + goToNextStep: () => void; + goToPreviousStep: () => void; +} +export const RuleFlyoutCreateFooter = ({ + onCancel, + onSave, + onShowRequest, + hasErrors, + isSaving, + hasNextStep, + hasPreviousStep, + goToNextStep, + goToPreviousStep, +}: RuleFlyoutCreateFooterProps) => { + return ( + + + + {hasPreviousStep ? ( + + {RULE_FLYOUT_FOOTER_BACK_TEXT} + + ) : ( + + {RULE_FLYOUT_FOOTER_CANCEL_TEXT} + + )} + + + + + {!hasNextStep && ( + + + {RULE_PAGE_FOOTER_SHOW_REQUEST_TEXT} + + + )} + + {hasNextStep ? ( + + {RULE_FLYOUT_FOOTER_NEXT_TEXT} + + ) : ( + + {RULE_FLYOUT_FOOTER_CREATE_TEXT} + + )} + + + + + + ); +}; diff --git a/src/platform/packages/shared/response-ops/rule_form/src/rule_flyout/rule_flyout_edit_footer.tsx b/src/platform/packages/shared/response-ops/rule_form/src/rule_flyout/rule_flyout_edit_footer.tsx new file mode 100644 index 0000000000000..5cb64b40fb180 --- /dev/null +++ b/src/platform/packages/shared/response-ops/rule_form/src/rule_flyout/rule_flyout_edit_footer.tsx @@ -0,0 +1,77 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { + EuiButton, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiFlyoutFooter, +} from '@elastic/eui'; +import React from 'react'; +import { + RULE_FLYOUT_FOOTER_CANCEL_TEXT, + RULE_FLYOUT_FOOTER_SAVE_TEXT, + RULE_PAGE_FOOTER_SHOW_REQUEST_TEXT, +} from '../translations'; + +export interface RuleFlyoutEditFooterProps { + isSaving: boolean; + hasErrors: boolean; + onCancel: () => void; + onSave: () => void; + onShowRequest: () => void; +} +export const RuleFlyoutEditFooter = ({ + onCancel, + onSave, + onShowRequest, + hasErrors, + isSaving, +}: RuleFlyoutEditFooterProps) => { + return ( + + + + + {RULE_FLYOUT_FOOTER_CANCEL_TEXT} + + + + + + + + {RULE_PAGE_FOOTER_SHOW_REQUEST_TEXT} + + + + + + {RULE_FLYOUT_FOOTER_SAVE_TEXT} + + + + + + + ); +}; diff --git a/src/platform/packages/shared/response-ops/rule_form/src/rule_flyout/rule_flyout_edit_tabs.tsx b/src/platform/packages/shared/response-ops/rule_form/src/rule_flyout/rule_flyout_edit_tabs.tsx new file mode 100644 index 0000000000000..ea560832f9514 --- /dev/null +++ b/src/platform/packages/shared/response-ops/rule_form/src/rule_flyout/rule_flyout_edit_tabs.tsx @@ -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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import React, { useMemo } from 'react'; +import { EuiTabs, EuiTab, useEuiPaddingSize } from '@elastic/eui'; +import { EuiStepHorizontalProps } from '@elastic/eui/src/components/steps/step_horizontal'; + +interface RuleFlyoutEditTabsProps { + steps: Array>; +} + +export const RuleFlyoutEditTabs = ({ steps }: RuleFlyoutEditTabsProps) => { + const bottomMarginOffset = `-${useEuiPaddingSize('l')}`; + + const tabs = useMemo( + () => + steps.map((step, index) => { + return ( + + {step.title} + + ); + }), + [steps] + ); + return ( + + {tabs} + + ); +}; diff --git a/src/platform/packages/shared/response-ops/rule_form/src/rule_form.scss b/src/platform/packages/shared/response-ops/rule_form/src/rule_form.scss new file mode 100644 index 0000000000000..fd905ed8f9d04 --- /dev/null +++ b/src/platform/packages/shared/response-ops/rule_form/src/rule_form.scss @@ -0,0 +1,27 @@ +.ruleForm__container { + container-type: inline-size; +} + +.ruleFormFlyout__container { + container-type: inline-size; +} + +@container (max-width: 768px) { + .euiDescribedFormGroup { + flex-direction: column; + } + .euiDescribedFormGroup > .euiFlexItem { + width: 100%; + } + .ruleDefinitionHeader { + flex-direction: column; + gap: $euiSizeM; + } + .ruleDefinitionHeaderRuleTypeName { + font-size: $euiFontSizeM; + margin-bottom: $euiSizeXS; + } + .ruleDefinitionHeaderRuleTypeDescription, .ruleDefinitionHeaderDocsLink { + font-size: $euiFontSizeS; + } +} \ No newline at end of file diff --git a/src/platform/packages/shared/response-ops/rule_form/src/rule_form.tsx b/src/platform/packages/shared/response-ops/rule_form/src/rule_form.tsx index f34f811a7b488..5b3f43a5bd4ba 100644 --- a/src/platform/packages/shared/response-ops/rule_form/src/rule_form.tsx +++ b/src/platform/packages/shared/response-ops/rule_form/src/rule_form.tsx @@ -18,6 +18,7 @@ import { RULE_FORM_ROUTE_PARAMS_ERROR_TEXT, } from './translations'; import { RuleFormPlugins } from './types'; +import './rule_form.scss'; const queryClient = new QueryClient(); @@ -114,5 +115,9 @@ export const RuleForm = (props: RuleFormProps) => { onSubmit, ]); - return {ruleFormComponent}; + return ( + + {ruleFormComponent} + + ); }; diff --git a/src/platform/packages/shared/response-ops/rule_form/src/translations.ts b/src/platform/packages/shared/response-ops/rule_form/src/translations.ts index 3f34baee68b3c..eebe9dd2157c3 100644 --- a/src/platform/packages/shared/response-ops/rule_form/src/translations.ts +++ b/src/platform/packages/shared/response-ops/rule_form/src/translations.ts @@ -233,6 +233,28 @@ export const ADD_ACTION_TEXT = i18n.translate( } ); +export const ADD_ACTION_HEADER = i18n.translate( + 'responseOpsRuleForm.ruleForm.ruleActions.addActionHeader', + { + defaultMessage: 'Add an action', + } +); + +export const ADD_ACTION_OPTIONAL_TEXT = i18n.translate( + 'responseOpsRuleForm.ruleForm.ruleActions.addActionOptionalText', + { + defaultMessage: 'Optional', + } +); + +export const ADD_ACTION_DESCRIPTION_TEXT = i18n.translate( + 'responseOpsRuleForm.ruleForm.ruleActions.addActionDescriptionText', + { + defaultMessage: + 'Select a connector and configure the actions to be performed when an alert is triggered', + } +); + export const RULE_DETAILS_TITLE = i18n.translate('responseOpsRuleForm.ruleForm.ruleDetails.title', { defaultMessage: 'Rule name and tags', }); @@ -307,6 +329,55 @@ export const RULE_PAGE_FOOTER_SAVE_TEXT = i18n.translate( } ); +export const RULE_FLYOUT_HEADER_CREATE_TITLE = i18n.translate( + 'responseOpsRuleForm.ruleForm.ruleFlyoutHeader.createTitle', + { + defaultMessage: 'Create rule', + } +); + +export const RULE_FLYOUT_HEADER_EDIT_TITLE = i18n.translate( + 'responseOpsRuleForm.ruleForm.ruleFlyoutHeader.editTitle', + { + defaultMessage: 'Edit rule', + } +); + +export const RULE_FLYOUT_FOOTER_CANCEL_TEXT = i18n.translate( + 'responseOpsRuleForm.ruleForm.ruleFlyoutFooter.cancelText', + { + defaultMessage: 'Cancel', + } +); + +export const RULE_FLYOUT_FOOTER_BACK_TEXT = i18n.translate( + 'responseOpsRuleForm.ruleForm.ruleFlyoutFooter.backText', + { + defaultMessage: 'Back', + } +); + +export const RULE_FLYOUT_FOOTER_NEXT_TEXT = i18n.translate( + 'responseOpsRuleForm.ruleForm.ruleFlyoutFooter.nextText', + { + defaultMessage: 'Next', + } +); + +export const RULE_FLYOUT_FOOTER_CREATE_TEXT = i18n.translate( + 'responseOpsRuleForm.ruleForm.ruleFlyoutFooter.createText', + { + defaultMessage: 'Create rule', + } +); + +export const RULE_FLYOUT_FOOTER_SAVE_TEXT = i18n.translate( + 'responseOpsRuleForm.ruleForm.ruleFlyoutFooter.saveText', + { + defaultMessage: 'Save changes', + } +); + export const HEALTH_CHECK_ALERTS_ERROR_TITLE = i18n.translate( 'responseOpsRuleForm.healthCheck.alertsErrorTitle', { @@ -490,6 +561,13 @@ export const RULE_FORM_PAGE_RULE_DEFINITION_TITLE = i18n.translate( } ); +export const RULE_FORM_PAGE_RULE_DEFINITION_TITLE_SHORT = i18n.translate( + 'responseOpsRuleForm.ruleForm.ruleDefinitionTitleShort', + { + defaultMessage: 'Definition', + } +); + export const RULE_FORM_PAGE_RULE_ACTIONS_TITLE = i18n.translate( 'responseOpsRuleForm.ruleForm.ruleActionsTitle', { @@ -518,6 +596,13 @@ export const RULE_FORM_PAGE_RULE_DETAILS_TITLE = i18n.translate( } ); +export const RULE_FORM_PAGE_RULE_DETAILS_TITLE_SHORT = i18n.translate( + 'responseOpsRuleForm.ruleForm.ruleDetailsTitleShort', + { + defaultMessage: 'Details', + } +); + export const RULE_FORM_RETURN_TITLE = i18n.translate('responseOpsRuleForm.ruleForm.returnTitle', { defaultMessage: 'Return', }); diff --git a/src/platform/packages/shared/response-ops/rule_form/tsconfig.json b/src/platform/packages/shared/response-ops/rule_form/tsconfig.json index b43a362905ecd..1e487cf3b6209 100644 --- a/src/platform/packages/shared/response-ops/rule_form/tsconfig.json +++ b/src/platform/packages/shared/response-ops/rule_form/tsconfig.json @@ -2,19 +2,10 @@ "extends": "../../../../../../tsconfig.base.json", "compilerOptions": { "outDir": "target/types", - "types": [ - "jest", - "node", - "react" - ] + "types": ["jest", "node", "react", "@kbn/ambient-ui-types"] }, - "include": [ - "**/*.ts", - "**/*.tsx", - ], - "exclude": [ - "target/**/*" - ], + "include": ["**/*.ts", "**/*.tsx"], + "exclude": ["target/**/*"], "kbn_references": [ "@kbn/alerting-types", "@kbn/i18n", @@ -39,6 +30,6 @@ "@kbn/kibana-react-plugin", "@kbn/core-i18n-browser", "@kbn/core-theme-browser", - "@kbn/core-user-profile-browser", + "@kbn/core-user-profile-browser" ] }
{selectedRuleTypeModel.description}