mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 18:51:07 -04:00
[Attack Discovery][Scheduling] Cases support followup 1 (#225452)
## Summary
Summarize your PR. If it involves visual changes include a screenshot or
gif.
These changes addresses the review comment from my previous PR
36ed6b38c1 (r2150081638)
Initially I broke up rendered component into multiple memoized
sub-sections. Reverting that back and adding tests coverage for the new
functionality - Case actions UI for the Attack Discovery rule type:
* Hidden `group by` component
* Hidden `time window` component
* Hidden `reopen case` component
* Disabled `template selector` component
* Tooltip explaining why we disabled the `template selector` component
This commit is contained in:
parent
4de6f7ca2c
commit
d38801034a
2 changed files with 133 additions and 100 deletions
|
@ -19,6 +19,7 @@ import { useGetAllCaseConfigurations } from '../../../containers/configure/use_g
|
||||||
import { useGetAllCaseConfigurationsResponse } from '../../configure_cases/__mock__';
|
import { useGetAllCaseConfigurationsResponse } from '../../configure_cases/__mock__';
|
||||||
import { templatesConfigurationMock } from '../../../containers/mock';
|
import { templatesConfigurationMock } from '../../../containers/mock';
|
||||||
import * as utils from '../../../containers/configure/utils';
|
import * as utils from '../../../containers/configure/utils';
|
||||||
|
import { ATTACK_DISCOVERY_SCHEDULES_ALERT_TYPE_ID } from '@kbn/elastic-assistant-common';
|
||||||
|
|
||||||
jest.mock('@kbn/alerts-ui-shared/src/common/hooks/use_alerts_data_view');
|
jest.mock('@kbn/alerts-ui-shared/src/common/hooks/use_alerts_data_view');
|
||||||
jest.mock('../../../common/lib/kibana/use_application');
|
jest.mock('../../../common/lib/kibana/use_application');
|
||||||
|
@ -72,6 +73,7 @@ describe('CasesParamsFields renders', () => {
|
||||||
// Workaround for timeout via https://github.com/testing-library/user-event/issues/833#issuecomment-1171452841
|
// Workaround for timeout via https://github.com/testing-library/user-event/issues/833#issuecomment-1171452841
|
||||||
user = userEvent.setup({
|
user = userEvent.setup({
|
||||||
advanceTimers: jest.advanceTimersByTime,
|
advanceTimers: jest.advanceTimersByTime,
|
||||||
|
pointerEventsCheck: 0,
|
||||||
});
|
});
|
||||||
useApplicationMock.mockReturnValueOnce({ appId: 'management' });
|
useApplicationMock.mockReturnValueOnce({ appId: 'management' });
|
||||||
useAlertsDataViewMock.mockReturnValue({
|
useAlertsDataViewMock.mockReturnValue({
|
||||||
|
@ -397,4 +399,67 @@ describe('CasesParamsFields renders', () => {
|
||||||
expect(editAction.mock.calls[0][1].reopenClosedCases).toEqual(true);
|
expect(editAction.mock.calls[0][1].reopenClosedCases).toEqual(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Attack Discovery', () => {
|
||||||
|
it('does not render `group by` component', async () => {
|
||||||
|
const newProps = {
|
||||||
|
...defaultProps,
|
||||||
|
ruleTypeId: ATTACK_DISCOVERY_SCHEDULES_ALERT_TYPE_ID,
|
||||||
|
};
|
||||||
|
render(<CasesParamsFields {...newProps} />);
|
||||||
|
|
||||||
|
expect(screen.queryByTestId('group-by-alert-field-combobox')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not render `time window` component', async () => {
|
||||||
|
const newProps = {
|
||||||
|
...defaultProps,
|
||||||
|
ruleTypeId: ATTACK_DISCOVERY_SCHEDULES_ALERT_TYPE_ID,
|
||||||
|
};
|
||||||
|
render(<CasesParamsFields {...newProps} />);
|
||||||
|
|
||||||
|
expect(screen.queryByTestId('time-window-size-input')).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByTestId('time-window-unit-select')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not render `reopen case` component', async () => {
|
||||||
|
const newProps = {
|
||||||
|
...defaultProps,
|
||||||
|
ruleTypeId: ATTACK_DISCOVERY_SCHEDULES_ALERT_TYPE_ID,
|
||||||
|
};
|
||||||
|
render(<CasesParamsFields {...newProps} />);
|
||||||
|
|
||||||
|
expect(screen.queryByTestId('reopen-case')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders disabled `template selector` component', async () => {
|
||||||
|
const newProps = {
|
||||||
|
...defaultProps,
|
||||||
|
ruleTypeId: ATTACK_DISCOVERY_SCHEDULES_ALERT_TYPE_ID,
|
||||||
|
};
|
||||||
|
render(<CasesParamsFields {...newProps} />);
|
||||||
|
|
||||||
|
const templateSelectorComponent = await screen.findByTestId('create-case-template-select');
|
||||||
|
|
||||||
|
expect(templateSelectorComponent).toBeInTheDocument();
|
||||||
|
expect(templateSelectorComponent).toBeDisabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows attack discovery explanation tooltip', async () => {
|
||||||
|
const newProps = {
|
||||||
|
...defaultProps,
|
||||||
|
ruleTypeId: ATTACK_DISCOVERY_SCHEDULES_ALERT_TYPE_ID,
|
||||||
|
};
|
||||||
|
render(<CasesParamsFields {...newProps} />);
|
||||||
|
|
||||||
|
await user.hover(await screen.findByTestId('create-case-template-select'));
|
||||||
|
|
||||||
|
expect(await screen.findByTestId('case-action-attack-discovery-tooltip')).toBeTruthy();
|
||||||
|
expect(
|
||||||
|
await screen.findByText(
|
||||||
|
'Attack Discovery Schedules fully manage Case actions, automatically filling in all fields for new Cases.'
|
||||||
|
)
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -188,8 +188,26 @@ export const CasesParamsFieldsComponent: React.FunctionComponent<
|
||||||
[editSubActionProperty]
|
[editSubActionProperty]
|
||||||
);
|
);
|
||||||
|
|
||||||
const groupByComponent = useMemo(() => {
|
if (isAttackDiscoveryRuleType) {
|
||||||
return (
|
return (
|
||||||
|
<EuiToolTip
|
||||||
|
data-test-subj="case-action-attack-discovery-tooltip"
|
||||||
|
content={i18n.ATTACK_DISCOVERY_TEMPLATE_TOOLTIP}
|
||||||
|
>
|
||||||
|
<TemplateSelector
|
||||||
|
key={currentConfiguration.id}
|
||||||
|
isLoading={isLoadingCaseConfiguration}
|
||||||
|
templates={[defaultTemplate, ...currentConfiguration.templates]}
|
||||||
|
onTemplateChange={onTemplateChange}
|
||||||
|
initialTemplate={selectedTemplate}
|
||||||
|
isDisabled={true}
|
||||||
|
/>
|
||||||
|
</EuiToolTip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
<EuiFlexGroup>
|
<EuiFlexGroup>
|
||||||
<EuiFlexItem grow={true}>
|
<EuiFlexItem grow={true}>
|
||||||
<EuiFormRow fullWidth label={i18n.GROUP_BY_ALERT} labelAppend={OptionalFieldLabel}>
|
<EuiFormRow fullWidth label={i18n.GROUP_BY_ALERT} labelAppend={OptionalFieldLabel}>
|
||||||
|
@ -207,71 +225,54 @@ export const CasesParamsFieldsComponent: React.FunctionComponent<
|
||||||
</EuiFormRow>
|
</EuiFormRow>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
</EuiFlexGroup>
|
</EuiFlexGroup>
|
||||||
);
|
<EuiSpacer size="m" />
|
||||||
}, [loadingAlertDataViews, onChangeComboBox, options, selectedOptions]);
|
<EuiFormRow
|
||||||
|
fullWidth
|
||||||
const timeWindowComponent = useMemo(() => {
|
id="timeWindow"
|
||||||
return (
|
error={errors.timeWindow as string[]}
|
||||||
<>
|
isInvalid={
|
||||||
<EuiFormRow
|
errors.timeWindow !== undefined &&
|
||||||
fullWidth
|
Number(errors.timeWindow.length) > 0 &&
|
||||||
id="timeWindow"
|
timeWindow !== undefined
|
||||||
error={errors.timeWindow as string[]}
|
}
|
||||||
isInvalid={
|
>
|
||||||
errors.timeWindow !== undefined &&
|
<EuiFlexGroup alignItems="flexEnd" gutterSize="s">
|
||||||
Number(errors.timeWindow.length) > 0 &&
|
<EuiFlexItem grow={4}>
|
||||||
timeWindow !== undefined
|
<EuiFieldNumber
|
||||||
}
|
prepend={i18n.TIME_WINDOW}
|
||||||
>
|
data-test-subj="time-window-size-input"
|
||||||
<EuiFlexGroup alignItems="flexEnd" gutterSize="s">
|
value={timeWindowSize}
|
||||||
<EuiFlexItem grow={4}>
|
min={1}
|
||||||
<EuiFieldNumber
|
step={1}
|
||||||
prepend={i18n.TIME_WINDOW}
|
onChange={(e) => {
|
||||||
data-test-subj="time-window-size-input"
|
handleTimeWindowChange('timeWindowSize', e.target.value);
|
||||||
value={timeWindowSize}
|
}}
|
||||||
min={1}
|
/>
|
||||||
step={1}
|
</EuiFlexItem>
|
||||||
onChange={(e) => {
|
<EuiFlexItem grow={3}>
|
||||||
handleTimeWindowChange('timeWindowSize', e.target.value);
|
<EuiSelect
|
||||||
}}
|
fullWidth
|
||||||
/>
|
data-test-subj="time-window-unit-select"
|
||||||
</EuiFlexItem>
|
value={timeWindowUnit}
|
||||||
<EuiFlexItem grow={3}>
|
onChange={(e) => {
|
||||||
<EuiSelect
|
handleTimeWindowChange('timeWindowUnit', e.target.value);
|
||||||
fullWidth
|
}}
|
||||||
data-test-subj="time-window-unit-select"
|
options={getTimeUnitOptions(timeWindowSize)}
|
||||||
value={timeWindowUnit}
|
/>
|
||||||
onChange={(e) => {
|
</EuiFlexItem>
|
||||||
handleTimeWindowChange('timeWindowUnit', e.target.value);
|
</EuiFlexGroup>
|
||||||
}}
|
</EuiFormRow>
|
||||||
options={getTimeUnitOptions(timeWindowSize)}
|
<EuiSpacer size="s" />
|
||||||
/>
|
{showTimeWindowWarning && (
|
||||||
</EuiFlexItem>
|
<EuiCallOut
|
||||||
</EuiFlexGroup>
|
data-test-subj="show-time-window-warning"
|
||||||
</EuiFormRow>
|
title={i18n.TIME_WINDOW_WARNING}
|
||||||
<EuiSpacer size="s" />
|
color="warning"
|
||||||
{showTimeWindowWarning && (
|
iconType="alert"
|
||||||
<EuiCallOut
|
size="s"
|
||||||
data-test-subj="show-time-window-warning"
|
/>
|
||||||
title={i18n.TIME_WINDOW_WARNING}
|
)}
|
||||||
color="warning"
|
<EuiSpacer size="m" />
|
||||||
iconType="alert"
|
|
||||||
size="s"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}, [
|
|
||||||
errors.timeWindow,
|
|
||||||
handleTimeWindowChange,
|
|
||||||
showTimeWindowWarning,
|
|
||||||
timeWindow,
|
|
||||||
timeWindowSize,
|
|
||||||
timeWindowUnit,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const templateSelectorComponent = useMemo(() => {
|
|
||||||
return (
|
|
||||||
<EuiFlexGroup>
|
<EuiFlexGroup>
|
||||||
<EuiFlexItem grow={true}>
|
<EuiFlexItem grow={true}>
|
||||||
<TemplateSelector
|
<TemplateSelector
|
||||||
|
@ -280,23 +281,10 @@ export const CasesParamsFieldsComponent: React.FunctionComponent<
|
||||||
templates={[defaultTemplate, ...currentConfiguration.templates]}
|
templates={[defaultTemplate, ...currentConfiguration.templates]}
|
||||||
onTemplateChange={onTemplateChange}
|
onTemplateChange={onTemplateChange}
|
||||||
initialTemplate={selectedTemplate}
|
initialTemplate={selectedTemplate}
|
||||||
isDisabled={isAttackDiscoveryRuleType}
|
|
||||||
/>
|
/>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
</EuiFlexGroup>
|
</EuiFlexGroup>
|
||||||
);
|
<EuiSpacer size="m" />
|
||||||
}, [
|
|
||||||
currentConfiguration.id,
|
|
||||||
currentConfiguration.templates,
|
|
||||||
defaultTemplate,
|
|
||||||
isAttackDiscoveryRuleType,
|
|
||||||
isLoadingCaseConfiguration,
|
|
||||||
onTemplateChange,
|
|
||||||
selectedTemplate,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const reopenClosedCasesComponent = useMemo(() => {
|
|
||||||
return (
|
|
||||||
<EuiFlexGroup>
|
<EuiFlexGroup>
|
||||||
<EuiFlexItem>
|
<EuiFlexItem>
|
||||||
<EuiCheckbox
|
<EuiCheckbox
|
||||||
|
@ -310,26 +298,6 @@ export const CasesParamsFieldsComponent: React.FunctionComponent<
|
||||||
/>
|
/>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
</EuiFlexGroup>
|
</EuiFlexGroup>
|
||||||
);
|
|
||||||
}, [editSubActionProperty, index, reopenClosedCases]);
|
|
||||||
|
|
||||||
if (isAttackDiscoveryRuleType) {
|
|
||||||
return (
|
|
||||||
<EuiToolTip content={i18n.ATTACK_DISCOVERY_TEMPLATE_TOOLTIP}>
|
|
||||||
{templateSelectorComponent}
|
|
||||||
</EuiToolTip>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{groupByComponent}
|
|
||||||
<EuiSpacer size="m" />
|
|
||||||
{timeWindowComponent}
|
|
||||||
<EuiSpacer size="m" />
|
|
||||||
{templateSelectorComponent}
|
|
||||||
<EuiSpacer size="m" />
|
|
||||||
{reopenClosedCasesComponent}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue