Skip to content

Commit

Permalink
Manual rule run UX changes + copy (elastic#186830)
Browse files Browse the repository at this point in the history
## Manual rule run UX improvements

- Change copy, mainly Backfill to Manual rule run
- Move Manual run into separate sections
- Not hide Manual run sections with empty state
- Add tooltip for disabled manual rule run when open rule actions
- Add limitations to modal window
- Change auto-refresh to refresh button + add information about last
update
- Fix bug, when you run manual run and it's not showing in the manual
rule run table
- Change order for start finish date in execution log
- Add technical preview to manual rule run section





https://github.com/elastic/kibana/assets/7609147/d6616a87-7259-4ad8-b605-fb8ffb8c6e7e

---------

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Nastasha Solomon <79124755+nastasha-solomon@users.noreply.github.com>
  • Loading branch information
3 people committed Jun 25, 2024
1 parent 0d802cb commit ba12219
Show file tree
Hide file tree
Showing 14 changed files with 245 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -169,11 +169,11 @@ export const getSourceEventTimeRangeColumns = () => [
return backfill ? (
<div>
<div>
<FormattedDate value={backfill.to} fieldName="backfill.to" />
<FormattedDate value={backfill.from} fieldName="backfill.from" />
</div>
<EuiText textAlign="center">{'-'}</EuiText>
<div>
<FormattedDate value={backfill.from} fieldName="backfill.from" />
<FormattedDate value={backfill.to} fieldName="backfill.to" />
</div>
</div>
) : (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ import {
getSourceEventTimeRangeColumns,
} from './execution_log_columns';
import { ExecutionLogSearchBar } from './execution_log_search_bar';
import { RuleBackfillsInfo } from '../../../../rule_gaps/components/rule_backfills_info';

import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features';

const EXECUTION_UUID_FIELD_NAME = 'kibana.alert.rule.execution.uuid';
Expand Down Expand Up @@ -594,9 +594,6 @@ const ExecutionLogTableComponent: React.FC<ExecutionLogTableProps> = ({
itemIdToExpandedRowMap={rows.itemIdToExpandedRowMap}
data-test-subj="executionsTable"
/>

<EuiSpacer size="xl" />
<RuleBackfillsInfo ruleId={ruleId} />
</EuiPanel>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ export const COLUMN_SOURCE_EVENT_TIME_RANGE = i18n.translate(
export const COLUMN_SOURCE_EVENT_TIME_RANGE_TOOLTIP = i18n.translate(
'xpack.securitySolution.detectionEngine.ruleDetails.ruleExecutionLog.sourceEventTimeRangeTooltip',
{
defaultMessage: "Only for manual rule runs. Don't include additional lookback time.",
defaultMessage:
"Only applies to manual rule executions. If the rule has look-back time, it's included in the logged time range.",
}
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ import {
} from '../../../../detections/components/rules/rule_execution_status';
import { ExecutionEventsTable } from '../../../rule_monitoring';
import { ExecutionLogTable } from './execution_log_table/execution_log_table';
import { RuleBackfillsInfo } from '../../../rule_gaps/components/rule_backfills_info';

import * as ruleI18n from '../../../../detections/pages/detection_engine/rules/translations';

Expand Down Expand Up @@ -785,13 +786,17 @@ const RuleDetailsPageComponent: React.FC<DetectionEngineComponentProps> = ({
/>
</Route>
<Route path={`/rules/id/:detailName/:tabName(${RuleDetailTabs.executionResults})`}>
<ExecutionLogTable
ruleId={ruleId}
selectAlertsTab={navigateToAlertsTab}
analytics={analytics}
i18n={i18nStart}
theme={theme}
/>
<>
<ExecutionLogTable
ruleId={ruleId}
selectAlertsTab={navigateToAlertsTab}
analytics={analytics}
i18n={i18nStart}
theme={theme}
/>
<EuiSpacer size="xl" />
<RuleBackfillsInfo ruleId={ruleId} />
</>
</Route>
<Route path={`/rules/id/:detailName/:tabName(${RuleDetailTabs.executionEvents})`}>
<ExecutionEventsTable ruleId={ruleId} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type { UseMutationOptions } from '@tanstack/react-query';
import { useMutation } from '@tanstack/react-query';
import type { ScheduleBackfillProps } from '../../types';
import { scheduleRuleRun } from '../api';
import { useInvalidateFindBackfillQuery } from './use_find_backfills_for_rules';

export const SCHEDULE_RULE_RUN_MUTATION_KEY = [
'POST',
Expand All @@ -18,8 +19,15 @@ export const SCHEDULE_RULE_RUN_MUTATION_KEY = [
export const useScheduleRuleRunMutation = (
options?: UseMutationOptions<unknown, Error, ScheduleBackfillProps>
) => {
const invalidateBackfillQuery = useInvalidateFindBackfillQuery();
return useMutation((scheduleOptions: ScheduleBackfillProps) => scheduleRuleRun(scheduleOptions), {
...options,
onSettled: (...args) => {
invalidateBackfillQuery();
if (options?.onSettled) {
options.onSettled(...args);
}
},
mutationKey: SCHEDULE_RULE_RUN_MUTATION_KEY,
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import {
EuiForm,
EuiFormRow,
useGeneratedHtmlId,
EuiCallOut,
EuiSpacer,
} from '@elastic/eui';
import moment from 'moment';
import React, { useCallback, useMemo, useState } from 'react';
Expand Down Expand Up @@ -118,6 +120,18 @@ const ManualRuleRunModalComponent = ({ onCancel, onConfirm }: ManualRuleRunModal
/>
</EuiFormRow>
</EuiForm>

<EuiSpacer size="m" />

<EuiCallOut
size="s"
iconType="warning"
title={i18n.MANUAL_RULE_RUN_NOTIFIACTIONS_LIMITATIONS}
/>

<EuiSpacer size="m" />

<EuiCallOut size="s" title={i18n.MANUAL_RULE_RUN_ALERT_LIMITATIONS} iconType="iInCircle" />
</EuiConfirmModal>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,18 @@ export const MANUAL_RULE_RUN_START_DATE_OUT_OF_RANGE_ERROR = (maxDaysLookback: n
defaultMessage:
'Manual rule run cannot be scheduled earlier than {maxDaysLookback, plural, =1 {# day} other {# days}} ago',
});

export const MANUAL_RULE_RUN_ALERT_LIMITATIONS = i18n.translate(
'xpack.securitySolution.manualRuleRun.alertLimitations',
{
defaultMessage:
'To view alerts generated by this run, filter the Alerts page by the time range that was selected for this manual rule run.',
}
);

export const MANUAL_RULE_RUN_NOTIFIACTIONS_LIMITATIONS = i18n.translate(
'xpack.securitySolution.manualRuleRun.notificationsLimitations',
{
defaultMessage: 'Rule actions are not performed during manual rule runs.',
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@
*/

import React, { useState } from 'react';
import { EuiAutoRefresh, EuiBasicTable, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import type {
EuiBasicTableColumn,
CriteriaWithPagination,
OnRefreshChangeProps,
import {
EuiButton,
EuiBasicTable,
EuiFlexGroup,
EuiFlexItem,
EuiPanel,
EuiBetaBadge,
} from '@elastic/eui';
import type { EuiBasicTableColumn, CriteriaWithPagination } from '@elastic/eui';
import { useFindBackfillsForRules } from '../../api/hooks/use_find_backfills_for_rules';
import { StopBackfill } from './stop_backfill';
import { BackfillStatusInfo } from './backfill_status';
Expand All @@ -23,31 +26,49 @@ import { useUserData } from '../../../../detections/components/user_info';
import { getBackfillRowsFromResponse } from './utils';
import { HeaderSection } from '../../../../common/components/header_section';
import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features';
import { TableHeaderTooltipCell } from '../../../rule_management_ui/components/rules_table/table_header_tooltip_cell';
import { TECHNICAL_PREVIEW, TECHNICAL_PREVIEW_TOOLTIP } from '../../../../common/translations';
import { useKibana } from '../../../../common/lib/kibana';

const AUTO_REFRESH_INTERVAL = 3000;
const DEFAULT_PAGE_SIZE = 10;

const getBackfillsTableColumns = (hasCRUDPermissions: boolean) => {
const stopAction = {
name: i18n.BACKFILLS_TABLE_COLUMN_ACTION,
render: (item: BackfillRow) => <StopBackfill id={item.id} />,
width: '10%',
};

const columns: Array<EuiBasicTableColumn<BackfillRow>> = [
{
field: 'status',
name: i18n.BACKFILLS_TABLE_COLUMN_STATUS,
name: (
<TableHeaderTooltipCell
title={i18n.BACKFILLS_TABLE_COLUMN_STATUS}
tooltipContent={i18n.BACKFILLS_TABLE_COLUMN_STATUS_TOOLTIP}
/>
),
render: (value: BackfillStatus) => <BackfillStatusInfo status={value} />,
width: '10%',
},
{
field: 'created_at',
name: i18n.BACKFILLS_TABLE_COLUMN_CREATED_AT,
name: (
<TableHeaderTooltipCell
title={i18n.BACKFILLS_TABLE_COLUMN_CREATED_AT}
tooltipContent={i18n.BACKFILLS_TABLE_COLUMN_CREATED_AT_TOOLTIP}
/>
),
render: (value: 'string') => <FormattedDate value={value} fieldName={'created_at'} />,
width: '20%',
},
{
name: i18n.BACKFILLS_TABLE_COLUMN_SOURCE_TIME_RANCE,
name: (
<TableHeaderTooltipCell
title={i18n.BACKFILLS_TABLE_COLUMN_SOURCE_TIME_RANGE}
tooltipContent={i18n.BACKFILLS_TABLE_COLUMN_SOURCE_TIME_RANGE_TOOLTIP}
/>
),
render: (value: BackfillRow) => (
<>
<FormattedDate value={value.start} fieldName={'start'} />
Expand All @@ -60,31 +81,56 @@ const getBackfillsTableColumns = (hasCRUDPermissions: boolean) => {
{
field: 'error',
align: 'right',
name: i18n.BACKFILLS_TABLE_COLUMN_ERROR,
name: (
<TableHeaderTooltipCell
title={i18n.BACKFILLS_TABLE_COLUMN_ERROR}
tooltipContent={i18n.BACKFILLS_TABLE_COLUMN_ERROR_TOOLTIP}
/>
),
'data-test-subj': 'rule-backfills-column-error',
},
{
field: 'pending',
align: 'right',
name: i18n.BACKFILLS_TABLE_COLUMN_PENDING,
name: (
<TableHeaderTooltipCell
title={i18n.BACKFILLS_TABLE_COLUMN_PENDING}
tooltipContent={i18n.BACKFILLS_TABLE_COLUMN_PENDING_TOOLTIP}
/>
),
'data-test-subj': 'rule-backfills-column-pending',
},
{
field: 'running',
align: 'right',
name: i18n.BACKFILLS_TABLE_COLUMN_RUNNING,
name: (
<TableHeaderTooltipCell
title={i18n.BACKFILLS_TABLE_COLUMN_RUNNING}
tooltipContent={i18n.BACKFILLS_TABLE_COLUMN_RUNNING_TOOLTIP}
/>
),
'data-test-subj': 'rule-backfills-column-running',
},
{
field: 'complete',
align: 'right',
name: i18n.BACKFILLS_TABLE_COLUMN_COMPLETED,
name: (
<TableHeaderTooltipCell
title={i18n.BACKFILLS_TABLE_COLUMN_COMPLETED}
tooltipContent={i18n.BACKFILLS_TABLE_COLUMN_COMPLETED_TOOLTIP}
/>
),
'data-test-subj': 'rule-backfills-column-completed',
},
{
field: 'total',
align: 'right',
name: i18n.BACKFILLS_TABLE_COLUMN_TOTAL,
name: (
<TableHeaderTooltipCell
title={i18n.BACKFILLS_TABLE_COLUMN_TOTAL}
tooltipContent={i18n.BACKFILLS_TABLE_COLUMN_TOTAL_TOOLTIP}
/>
),
'data-test-subj': 'rule-backfills-column-total',
},
];
Expand All @@ -98,21 +144,18 @@ const getBackfillsTableColumns = (hasCRUDPermissions: boolean) => {

export const RuleBackfillsInfo = React.memo<{ ruleId: string }>(({ ruleId }) => {
const isManualRuleRunEnabled = useIsExperimentalFeatureEnabled('manualRuleRunEnabled');
const [autoRefreshInterval, setAutoRefreshInterval] = useState(AUTO_REFRESH_INTERVAL);
const [isAutoRefresh, setIsAutoRefresh] = useState(false);
const [pageIndex, setPageIndex] = useState(0);
const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE);
const [{ canUserCRUD }] = useUserData();
const hasCRUDPermissions = hasUserCRUDPermission(canUserCRUD);

const { data, isLoading, isError } = useFindBackfillsForRules(
const { timelines } = useKibana().services;
const { data, isLoading, isError, refetch, dataUpdatedAt } = useFindBackfillsForRules(
{
ruleIds: [ruleId],
page: pageIndex + 1,
perPage: pageSize,
},
{
refetchInterval: isAutoRefresh ? autoRefreshInterval : false,
enabled: isManualRuleRunEnabled,
}
);
Expand All @@ -131,13 +174,6 @@ export const RuleBackfillsInfo = React.memo<{ ruleId: string }>(({ ruleId }) =>
totalItemCount: data?.total ?? 0,
};

if (data?.total === 0) return null;

const handleRefreshChange = ({ isPaused, refreshInterval }: OnRefreshChangeProps) => {
setIsAutoRefresh(!isPaused);
setAutoRefreshInterval(refreshInterval);
};

const handleTableChange: (params: CriteriaWithPagination<BackfillRow>) => void = ({
page,
sort,
Expand All @@ -148,23 +184,39 @@ export const RuleBackfillsInfo = React.memo<{ ruleId: string }>(({ ruleId }) =>
}
};

const handleRefresh = () => {
refetch();
};

return (
<div>
<EuiFlexGroup gutterSize="s" data-test-subj="rule-backfills-info">
<EuiPanel hasBorder>
<EuiFlexGroup alignItems="flexStart" gutterSize="s" data-test-subj="rule-backfills-info">
<EuiFlexItem grow={true}>
<HeaderSection
title={i18n.BACKFILL_TABLE_TITLE}
subtitle={i18n.BACKFILL_TABLE_SUBTITLE}
/>
<EuiFlexGroup gutterSize="s" alignItems="baseline">
<HeaderSection
title={i18n.BACKFILL_TABLE_TITLE}
subtitle={i18n.BACKFILL_TABLE_SUBTITLE}
/>
<EuiBetaBadge label={TECHNICAL_PREVIEW} tooltipContent={TECHNICAL_PREVIEW_TOOLTIP} />
</EuiFlexGroup>
</EuiFlexItem>

<EuiFlexItem grow={false}>
<EuiButton iconType="refresh" fill onClick={handleRefresh}>
{i18n.BACKFILL_TABLE_REFRESH}
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>

<EuiFlexGroup justifyContent="flexEnd">
<EuiFlexItem grow={false}>
<EuiAutoRefresh
isPaused={!isAutoRefresh}
refreshInterval={autoRefreshInterval}
onRefreshChange={handleRefreshChange}
/>
{timelines.getLastUpdated({
showUpdating: isLoading,
updatedAt: dataUpdatedAt,
})}
</EuiFlexItem>
</EuiFlexGroup>

<EuiBasicTable
data-test-subj="rule-backfills-table"
items={backfills}
Expand All @@ -173,9 +225,8 @@ export const RuleBackfillsInfo = React.memo<{ ruleId: string }>(({ ruleId }) =>
error={isError ? 'error' : undefined}
loading={isLoading}
onChange={handleTableChange}
noItemsMessage={'not found'}
/>
</div>
</EuiPanel>
);
});

Expand Down
Loading

0 comments on commit ba12219

Please sign in to comment.