Skip to content

Commit

Permalink
Move rule flyout body to own component
Browse files Browse the repository at this point in the history
  • Loading branch information
Zacqary committed Jan 9, 2025
1 parent d8f9597 commit 7826e79
Show file tree
Hide file tree
Showing 2 changed files with 164 additions and 132 deletions.
141 changes: 9 additions & 132 deletions packages/response-ops/rule_form/src/rule_flyout/rule_flyout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,101 +7,22 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import {
EuiFlyout,
EuiFlyoutBody,
EuiFlyoutHeader,
EuiPortal,
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 { EuiFlyout, EuiPortal } from '@elastic/eui';
import React from 'react';
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';
import { RuleFlyoutBody } from './rule_flyout_body';

export interface RuleFlyoutProps {
interface RuleFlyoutProps {
isEdit?: boolean;
isSaving?: boolean;
onCancel?: () => void;
onSave: (formData: RuleFormData) => void;
}

export const RuleFlyout = (props: RuleFlyoutProps) => {
const { isEdit = false, isSaving = false, onCancel = () => {}, onSave } = props;

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]);

// 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 (
<EuiPortal>
<EuiFlyout
Expand All @@ -112,51 +33,7 @@ export const RuleFlyout = (props: RuleFlyoutProps) => {
maxWidth={500}
className="ruleFormFlyout__container"
>
<EuiFlyoutHeader hasBorder>
<EuiTitle size="s" data-test-subj={isEdit ? 'editRuleFlyoutTitle' : 'addRuleFlyoutTitle'}>
<h3 id="flyoutTitle">
{isEdit ? RULE_FLYOUT_HEADER_EDIT_TITLE : RULE_FLYOUT_HEADER_CREATE_TITLE}
</h3>
</EuiTitle>
{isEdit && <RuleFlyoutEditTabs steps={steps} />}
</EuiFlyoutHeader>
<EuiFlyoutBody>
{!isEdit && <EuiStepsHorizontal size="xs" steps={steps} />}
{hasActionsDisabled && (
<>
<EuiCallOut
size="s"
color="danger"
iconType="error"
data-test-subj="hasActionsDisabled"
title={DISABLED_ACTIONS_WARNING_TITLE}
/>
<EuiSpacer />
</>
)}
{currentStepComponent}
</EuiFlyoutBody>
{isEdit ? (
<RuleFlyoutEditFooter
onCancel={onCancel}
onSave={onSaveInternal}
onShowRequest={() => {} /* TODO */}
isSaving={isSaving}
hasErrors={hasErrors}
/>
) : (
<RuleFlyoutCreateFooter
onCancel={onCancel}
onSave={onSaveInternal}
onShowRequest={() => {} /* TODO */}
goToNextStep={goToNextStep}
goToPreviousStep={goToPreviousStep}
isSaving={isSaving}
hasNextStep={hasNextStep}
hasPreviousStep={hasPreviousStep}
hasErrors={hasErrors}
/>
)}
<RuleFlyoutBody onSave={onSave} onCancel={onCancel} isEdit={isEdit} isSaving={isSaving} />
</EuiFlyout>
</EuiPortal>
);
Expand Down
155 changes: 155 additions & 0 deletions packages/response-ops/rule_form/src/rule_flyout/rule_flyout_body.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<>
<EuiFlyoutHeader hasBorder>
<EuiTitle size="s" data-test-subj={isEdit ? 'editRuleFlyoutTitle' : 'addRuleFlyoutTitle'}>
<h3 id="flyoutTitle">
{isEdit ? RULE_FLYOUT_HEADER_EDIT_TITLE : RULE_FLYOUT_HEADER_CREATE_TITLE}
</h3>
</EuiTitle>
{isEdit && <RuleFlyoutEditTabs steps={steps} />}
</EuiFlyoutHeader>
<EuiFlyoutBody>
{!isEdit && <EuiStepsHorizontal size="xs" steps={steps} />}
{hasActionsDisabled && (
<>
<EuiCallOut
size="s"
color="danger"
iconType="error"
data-test-subj="hasActionsDisabled"
title={DISABLED_ACTIONS_WARNING_TITLE}
/>
<EuiSpacer />
</>
)}
{currentStepComponent}
</EuiFlyoutBody>
{isEdit ? (
<RuleFlyoutEditFooter
onCancel={onCancel}
onSave={onSaveInternal}
onShowRequest={() => {} /* TODO */}
isSaving={isSaving}
hasErrors={hasErrors}
/>
) : (
<RuleFlyoutCreateFooter
onCancel={onCancel}
onSave={onSaveInternal}
onShowRequest={() => {} /* TODO */}
goToNextStep={goToNextStep}
goToPreviousStep={goToPreviousStep}
isSaving={isSaving}
hasNextStep={hasNextStep}
hasPreviousStep={hasPreviousStep}
hasErrors={hasErrors}
/>
)}
</>
);
};

0 comments on commit 7826e79

Please sign in to comment.