Skip to content

Commit

Permalink
[Response Ops] [Rule Form] Add new flyout to rule form library, respo…
Browse files Browse the repository at this point in the history
…nsive design and illustration to rule form page (elastic#206141)

## Summary

Part of elastic#195211

Adds components for the new rule form flyout, and duplicates some of its
design elements as responsive design on the Rule Page. This PR makes use
of CSS `@container` queries, which EUI doesn't yet support natively.
I've opened elastic/eui#8265 to get native EUI
support for this functionality, but for now we can apply it through
class names and SCSS.

The reason we're using `@container` is so the Rule Form can be
responsive regardless of whether it's bound by the window size (in the
case of the Rule Page) or a container element on a larger screen (in the
Rule Flyout). When responsive design just relies on `@media screen`
queries, we can have a situation where we're trying to render the rule
form in a 500px wide flyout, but because the window is 1920px wide, it
still tries to apply wide screen styling. `@container` instead responds
to the width of an enclosing element, which can either be the body of
the Rule Page, or the width of the Rule Flyout.

### Non-User Facing Changes
- Adds the new rule flyout to `@kbn/response-ops-rule-form`. ***It is
not yet actually user-facing anywhere in the application, this will be
done in a second PR.***
<details>
<summary><h4>Screenshots</h4></summary>
<img width="508" alt="Screenshot 2025-01-08 at 4 29 55 PM"
src="https://github.com/user-attachments/assets/7f03cd3a-5f37-4ac2-9992-ca4951664770"
/>
<img width="502" alt="Screenshot 2025-01-08 at 4 29 59 PM"
src="https://github.com/user-attachments/assets/c0620fc2-83db-4603-90b7-1282e5b0e6ab"
/>
<img width="507" alt="Screenshot 2025-01-08 at 4 30 03 PM"
src="https://github.com/user-attachments/assets/8440d551-46af-49e0-9c92-22d6b3ba1866"
/>
<img width="507" alt="Screenshot 2025-01-08 at 4 30 32 PM"
src="https://github.com/user-attachments/assets/cf7493a7-6e4a-4e55-8027-89e9b36012fc"
/>

</details>

### User-Facing Changes
These changes were added to the existing full page rule form to minimize
the amount of code differences between the flyout and the full page

- Adds some responsive styling to the rule form page to make it look
more similar to the flyout when the browser window is narrow
<details>
<summary>Screenshot</summary>
<img width="783" alt="Screenshot 2025-01-08 at 4 31 50 PM"
src="https://github.com/user-attachments/assets/a3532b92-9f22-4e88-bcc3-e408fc53e64c"
/>

</details>

- Adds the new illustrated "Add an action" empty prompt from the flyout
designs to the existing rule form page
<details>
<summary>Screenshot</summary>
<img width="1299" alt="Screenshot 2025-01-08 at 5 00 55 PM"
src="https://github.com/user-attachments/assets/c4acd50d-9268-4874-b650-ecba532f3e9c"
/>

</details>

### Testing

To test the new flyout, edit
`packages/response-ops/rule_form/src/create_rule_form.tsx` and
`packages/response-ops/rule_form/src/edit_rule_form.tsx` so that they
render `<RuleFlyout>` instead of `<RulePage>`.

<details>
<summary><strong>Use this diff block</strong></summary>

```diff
diff --git a/packages/response-ops/rule_form/src/create_rule_form.tsx b/packages/response-ops/rule_form/src/create_rule_form.tsx
index 2f5e0472dcd..564744b96ec 100644
--- a/packages/response-ops/rule_form/src/create_rule_form.tsx
+++ b/packages/response-ops/rule_form/src/create_rule_form.tsx
@@ -31,6 +31,7 @@ import {
   parseRuleCircuitBreakerErrorMessage,
 } from './utils';
 import { RULE_CREATE_SUCCESS_TEXT, RULE_CREATE_ERROR_TEXT } from './translations';
+import { RuleFlyout } from './rule_flyout';

 export interface CreateRuleFormProps {
   ruleTypeId: string;
@@ -199,7 +200,7 @@ export const CreateRuleForm = (props: CreateRuleFormProps) => {
           }),
         }}
       >
-        <RulePage isEdit={false} isSaving={isSaving} onCancel={onCancel} onSave={onSave} />
+        <RuleFlyout isEdit={false} isSaving={isSaving} onCancel={onCancel} onSave={onSave} />
       </RuleFormStateProvider>
     </div>
   );
diff --git a/packages/response-ops/rule_form/src/edit_rule_form.tsx b/packages/response-ops/rule_form/src/edit_rule_form.tsx
index 392447114ed..41aecd7245a 100644
--- a/packages/response-ops/rule_form/src/edit_rule_form.tsx
+++ b/packages/response-ops/rule_form/src/edit_rule_form.tsx
@@ -26,6 +26,7 @@ import {
 import { RULE_EDIT_ERROR_TEXT, RULE_EDIT_SUCCESS_TEXT } from './translations';
 import { getAvailableRuleTypes, parseRuleCircuitBreakerErrorMessage } from './utils';
 import { DEFAULT_VALID_CONSUMERS, getDefaultFormData } from './constants';
+import { RuleFlyout } from './rule_flyout';

 export interface EditRuleFormProps {
   id: string;
@@ -193,7 +194,7 @@ export const EditRuleForm = (props: EditRuleFormProps) => {
           showMustacheAutocompleteSwitch,
         }}
       >
-        <RulePage isEdit={true} isSaving={isSaving} onSave={onSave} onCancel={onCancel} />
+        <RuleFlyout isEdit={true} isSaving={isSaving} onSave={onSave} onCancel={onCancel} />
       </RuleFormStateProvider>
     </div>
   );
```

</details>

### Still Todo

1. Replace the action connector modal with an in-flyout UI as called for
in the [design
spec](https://www.figma.com/design/zetHXnUP0YnDG4YmvPwRb8/Adapt-new-Rule-form-to-work-in-flyout)
2. Add the Show Request UI
3. Replace all instances of the v1 rule flyout with this new one (it's
used heavily in solutions, not in Stack Management)

### Checklist
- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios

(cherry picked from commit 471f948)
  • Loading branch information
Zacqary committed Jan 15, 2025
1 parent 208b795 commit 398062b
Show file tree
Hide file tree
Showing 16 changed files with 1,818 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -145,9 +147,9 @@ describe('useRuleFormHorizontalSteps', () => {

render(<TestComponent />);

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 () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -27,6 +29,7 @@ interface UseRuleFormStepsOptions {
touchedSteps: Record<RuleFormStepId, boolean>;
/* Used to track the current step in horizontal steps, not used for vertical steps */
currentStep?: RuleFormStepId;
shortTitles?: boolean;
}

/**
Expand Down Expand Up @@ -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 = {},
Expand Down Expand Up @@ -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: <RuleDefinition />,
},
Expand All @@ -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: (
<>
Expand All @@ -161,7 +172,7 @@ const useCommonRuleFormSteps = ({ touchedSteps, currentStep }: UseRuleFormStepsO
),
},
}),
[ruleDefinitionStatus, canReadConnectors, actionsStatus, ruleDetailsStatus]
[ruleDefinitionStatus, canReadConnectors, actionsStatus, ruleDetailsStatus, shortTitles]
);

const stepOrder: RuleFormStepId[] = useMemo(
Expand Down Expand Up @@ -247,6 +258,7 @@ export const useRuleFormHorizontalSteps: () => RuleFormHorizontalSteps = () => {
const { steps, stepOrder } = useCommonRuleFormSteps({
touchedSteps,
currentStep,
shortTitles: true,
});

// Determine current navigation position
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<boolean>(false);
const ruleActionsIllustration = useRuleActionsIllustration();

const {
formData: { actions, consumer },
Expand Down Expand Up @@ -92,6 +111,8 @@ export const RuleActions = () => {
return selectedRuleType.producer;
}, [consumer, multiConsumerSelection, selectedRuleType]);

const hasActions = actions.length > 0;

return (
<>
<EuiFlexGroup data-test-subj="ruleActions" direction="column">
Expand Down Expand Up @@ -120,15 +141,49 @@ export const RuleActions = () => {
);
})}
</EuiFlexGroup>
{!hasActions && (
<EuiFlexGroup justifyContent="center">
<EuiFlexGroup
alignItems="center"
direction="column"
gutterSize="m"
style={{ maxWidth: 356 }}
>
<EuiImage
alt="Rule actions illustration"
width={198}
height={180}
url={ruleActionsIllustration}
/>
<EuiFlexItem>
<EuiText textAlign="center">
<h3>{ADD_ACTION_HEADER}</h3>
</EuiText>
<EuiText size="s" textAlign="center" color="subdued">
{ADD_ACTION_OPTIONAL_TEXT}
</EuiText>
</EuiFlexItem>
<EuiFlexItem>
<EuiText size="s" textAlign="center" color="subdued">
{ADD_ACTION_DESCRIPTION_TEXT}
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexGroup>
)}
<EuiSpacer />
<EuiButton
data-test-subj="ruleActionsAddActionButton"
iconType="push"
iconSide="left"
onClick={onModalOpen}
>
{ADD_ACTION_TEXT}
</EuiButton>
<EuiFlexGroup justifyContent={!hasActions ? 'center' : 'flexStart'}>
<EuiFlexItem grow={0}>
<EuiButton
data-test-subj="ruleActionsAddActionButton"
iconType="push"
iconSide="left"
onClick={onModalOpen}
>
{ADD_ACTION_TEXT}
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
{isConnectorModalOpen && (
<RuleActionsConnectorsModal onClose={onModalClose} onSelectConnector={onSelectConnector} />
)}
Expand Down
Loading

0 comments on commit 398062b

Please sign in to comment.