diff --git a/packages/kbn-securitysolution-grouping/src/hooks/use_get_group_selector.tsx b/packages/kbn-securitysolution-grouping/src/hooks/use_get_group_selector.tsx
index bec790ca8cce0..14f010640ef12 100644
--- a/packages/kbn-securitysolution-grouping/src/hooks/use_get_group_selector.tsx
+++ b/packages/kbn-securitysolution-grouping/src/hooks/use_get_group_selector.tsx
@@ -8,7 +8,7 @@
import type { FieldSpec } from '@kbn/data-views-plugin/common';
import { METRIC_TYPE, UiCounterMetricType } from '@kbn/analytics';
-import React, { useCallback, useEffect } from 'react';
+import React, { useCallback, useEffect, useMemo } from 'react';
import { groupActions, groupByIdSelector } from './state';
import type { GroupOption } from './types';
@@ -64,19 +64,21 @@ export const useGetGroupSelectorStateless = ({
[onGroupChange]
);
- return (
-
- );
+ return useMemo(() => {
+ return (
+
+ );
+ }, [groupingId, fields, maxGroupingLevels, defaultGroupingOptions, onChange]);
};
export const useGetGroupSelector = ({
@@ -198,18 +200,19 @@ export const useGetGroupSelector = ({
}
}, [defaultGroupingOptions, options, selectedGroups, setOptions]);
- return (
-
- );
+ return useMemo(() => {
+ return (
+
+ );
+ }, [groupingId, fields, maxGroupingLevels, onChange, selectedGroups, options]);
};
diff --git a/x-pack/examples/triggers_actions_ui_example/public/plugin.tsx b/x-pack/examples/triggers_actions_ui_example/public/plugin.tsx
index ee5c3229cb713..5077795b620f7 100644
--- a/x-pack/examples/triggers_actions_ui_example/public/plugin.tsx
+++ b/x-pack/examples/triggers_actions_ui_example/public/plugin.tsx
@@ -133,8 +133,11 @@ export class TriggersActionsUiExamplePlugin
id: 'observabilityCases',
columns,
useInternalFlyout,
- getRenderCellValue: () => (props) => {
- const value = props.data.find((d) => d.field === props.columnId)?.value ?? [];
+ getRenderCellValue: (props: {
+ data?: Array<{ field: string; value: string }>;
+ columnId?: string;
+ }) => {
+ const value = props.data?.find((d) => d.field === props.columnId)?.value ?? [];
if (Array.isArray(value)) {
return <>{value.length ? value.join() : '--'}>;
diff --git a/x-pack/plugins/cases/public/components/all_cases/selector_modal/use_cases_add_to_existing_case_modal.tsx b/x-pack/plugins/cases/public/components/all_cases/selector_modal/use_cases_add_to_existing_case_modal.tsx
index 2c739cfe1b984..0fa30647b60ac 100644
--- a/x-pack/plugins/cases/public/components/all_cases/selector_modal/use_cases_add_to_existing_case_modal.tsx
+++ b/x-pack/plugins/cases/public/components/all_cases/selector_modal/use_cases_add_to_existing_case_modal.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { useCallback } from 'react';
+import { useCallback, useMemo } from 'react';
import { useApplication } from '../../../common/lib/kibana/use_application';
import { CaseStatuses } from '../../../../common/types/domain';
import type { AllCasesSelectorModalProps } from '.';
@@ -158,9 +158,11 @@ export const useCasesAddToExistingCaseModal = ({
[closeModal, dispatch, handleOnRowClick, onClose, onCreateCaseClicked]
);
- return {
- open: openModal,
- close: closeModal,
- };
+ return useMemo(() => {
+ return {
+ open: openModal,
+ close: closeModal,
+ };
+ }, [openModal, closeModal]);
};
export type UseCasesAddToExistingCaseModal = typeof useCasesAddToExistingCaseModal;
diff --git a/x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.tsx b/x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.tsx
index e6d5bd2fc2a0b..ea2290bb49633 100644
--- a/x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.tsx
+++ b/x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.tsx
@@ -6,7 +6,7 @@
*/
import type React from 'react';
-import { useCallback } from 'react';
+import { useCallback, useMemo } from 'react';
import type { CaseAttachmentsWithoutOwner } from '../../../types';
import { useCasesToast } from '../../../common/use_cases_toast';
import type { CaseUI } from '../../../containers/types';
@@ -88,10 +88,12 @@ export const useCasesAddToNewCaseFlyout = ({
onClose,
]
);
- return {
- open: openFlyout,
- close: closeFlyout,
- };
+ return useMemo(() => {
+ return {
+ open: openFlyout,
+ close: closeFlyout,
+ };
+ }, [openFlyout, closeFlyout]);
};
export type UseCasesAddToNewCaseFlyout = typeof useCasesAddToNewCaseFlyout;
diff --git a/x-pack/plugins/ml/public/alerting/anomaly_detection_alerts_table/register_alerts_table_configuration.tsx b/x-pack/plugins/ml/public/alerting/anomaly_detection_alerts_table/register_alerts_table_configuration.tsx
index 6c01c056bad99..25ffef0456e42 100644
--- a/x-pack/plugins/ml/public/alerting/anomaly_detection_alerts_table/register_alerts_table_configuration.tsx
+++ b/x-pack/plugins/ml/public/alerting/anomaly_detection_alerts_table/register_alerts_table_configuration.tsx
@@ -129,7 +129,7 @@ export function registerAlertsTableConfiguration(
},
columns,
useInternalFlyout: getAlertFlyout(columns, getAlertFormatters(fieldFormats)),
- getRenderCellValue: getRenderCellValue(fieldFormats),
+ getRenderCellValue,
sort,
useActionsColumn: () => ({
renderCustomActionsRow: (props: RenderCustomActionsRowArgs) => {
diff --git a/x-pack/plugins/ml/public/alerting/anomaly_detection_alerts_table/render_cell_value.tsx b/x-pack/plugins/ml/public/alerting/anomaly_detection_alerts_table/render_cell_value.tsx
index bfc86e390c0e9..afba911e5ddea 100644
--- a/x-pack/plugins/ml/public/alerting/anomaly_detection_alerts_table/render_cell_value.tsx
+++ b/x-pack/plugins/ml/public/alerting/anomaly_detection_alerts_table/render_cell_value.tsx
@@ -6,10 +6,10 @@
*/
import { isEmpty } from 'lodash';
-import React, { type ReactNode } from 'react';
+import React from 'react';
+import type { RenderCellValue } from '@elastic/eui';
import { isDefined } from '@kbn/ml-is-defined';
import { ALERT_DURATION, ALERT_END, ALERT_START } from '@kbn/rule-data-utils';
-import type { GetRenderCellValue } from '@kbn/triggers-actions-ui-plugin/public';
import type { FieldFormatsRegistry } from '@kbn/field-formats-plugin/common';
import { FIELD_FORMAT_IDS } from '@kbn/field-formats-plugin/common';
import { getFormattedSeverityScore, getSeverityColor } from '@kbn/ml-anomaly-utils';
@@ -21,11 +21,6 @@ import {
} from '../../../common/constants/alerts';
import { getFieldFormatterProvider } from '../../application/contexts/kibana/use_field_formatter';
-interface Props {
- columnId: string;
- data: any;
-}
-
export const getMappedNonEcsValue = ({
data,
fieldName,
@@ -54,22 +49,17 @@ const getRenderValue = (mappedNonEcsValue: any) => {
return '—';
};
-export const getRenderCellValue = (fieldFormats: FieldFormatsRegistry): GetRenderCellValue => {
+export const getRenderCellValue: RenderCellValue = ({ columnId, data, fieldFormats }) => {
const alertValueFormatter = getAlertFormatters(fieldFormats);
+ if (!isDefined(data)) return;
- return ({ setFlyoutAlert }) =>
- (props): ReactNode => {
- const { columnId, data } = props as Props;
- if (!isDefined(data)) return;
+ const mappedNonEcsValue = getMappedNonEcsValue({
+ data,
+ fieldName: columnId,
+ });
+ const value = getRenderValue(mappedNonEcsValue);
- const mappedNonEcsValue = getMappedNonEcsValue({
- data,
- fieldName: columnId,
- });
- const value = getRenderValue(mappedNonEcsValue);
-
- return alertValueFormatter(columnId, value);
- };
+ return alertValueFormatter(columnId, value);
};
export function getAlertFormatters(fieldFormats: FieldFormatsRegistry) {
diff --git a/x-pack/plugins/ml/public/application/explorer/alerts/alerts_panel.tsx b/x-pack/plugins/ml/public/application/explorer/alerts/alerts_panel.tsx
index d5a5821aae98a..4c76ebe628f4f 100644
--- a/x-pack/plugins/ml/public/application/explorer/alerts/alerts_panel.tsx
+++ b/x-pack/plugins/ml/public/application/explorer/alerts/alerts_panel.tsx
@@ -33,7 +33,9 @@ export const AlertsPanel: FC = () => {
const [isOpen, setIsOpen] = useState(true);
const [toggleSelected, setToggleSelected] = useState(`alertsSummary`);
-
+ const {
+ services: { fieldFormats },
+ } = useMlKibana();
const { anomalyDetectionAlertsStateService } = useAnomalyExplorerContext();
const countByStatus = useObservable(anomalyDetectionAlertsStateService.countByStatus$);
@@ -48,6 +50,9 @@ export const AlertsPanel: FC = () => {
query: alertsQuery,
showExpandToDetails: true,
showAlertStatusWithFlapping: true,
+ cellContext: {
+ fieldFormats,
+ },
};
const alertsStateTable = triggersActionsUi!.getAlertsStateTable(alertStateProps);
diff --git a/x-pack/plugins/observability_solution/observability/public/components/alerts_table/alerts/get_alerts_page_table_configuration.tsx b/x-pack/plugins/observability_solution/observability/public/components/alerts_table/alerts/get_alerts_page_table_configuration.tsx
index b263a683be35e..cabf1d6d6f34e 100644
--- a/x-pack/plugins/observability_solution/observability/public/components/alerts_table/alerts/get_alerts_page_table_configuration.tsx
+++ b/x-pack/plugins/observability_solution/observability/public/components/alerts_table/alerts/get_alerts_page_table_configuration.tsx
@@ -37,11 +37,7 @@ export const getAlertsPageTableConfiguration = (
id: observabilityFeatureId,
cases: { featureId: casesFeatureId, owner: [observabilityFeatureId] },
columns: getColumns({ showRuleName: true }),
- getRenderCellValue: ({ setFlyoutAlert }) =>
- getRenderCellValue({
- observabilityRuleTypeRegistry,
- setFlyoutAlert,
- }),
+ getRenderCellValue,
sort: [
{
[ALERT_START]: {
diff --git a/x-pack/plugins/observability_solution/observability/public/components/alerts_table/common/render_cell_value.test.tsx b/x-pack/plugins/observability_solution/observability/public/components/alerts_table/common/render_cell_value.test.tsx
index 91c6e8d2c3b4a..d551f90f1097f 100644
--- a/x-pack/plugins/observability_solution/observability/public/components/alerts_table/common/render_cell_value.test.tsx
+++ b/x-pack/plugins/observability_solution/observability/public/components/alerts_table/common/render_cell_value.test.tsx
@@ -7,7 +7,6 @@
import { ALERT_STATUS, ALERT_STATUS_ACTIVE, ALERT_STATUS_RECOVERED } from '@kbn/rule-data-utils';
import type { DeprecatedCellValueElementProps } from '@kbn/timelines-plugin/common';
-import { createObservabilityRuleTypeRegistryMock } from '../../../rules/observability_rule_type_registry_mock';
import { render } from '../../../utils/test_helper';
import { getRenderCellValue } from './render_cell_value';
@@ -16,17 +15,10 @@ interface AlertsTableRow {
}
describe('getRenderCellValue', () => {
- const observabilityRuleTypeRegistryMock = createObservabilityRuleTypeRegistryMock();
-
- const renderCellValue = getRenderCellValue({
- setFlyoutAlert: jest.fn(),
- observabilityRuleTypeRegistry: observabilityRuleTypeRegistryMock,
- });
-
describe('when column is alert status', () => {
it('should return an active indicator when alert status is active', async () => {
const cell = render(
- renderCellValue({
+ getRenderCellValue({
...requiredProperties,
columnId: ALERT_STATUS,
data: makeAlertsTableRow({ alertStatus: ALERT_STATUS_ACTIVE }),
@@ -38,7 +30,7 @@ describe('getRenderCellValue', () => {
it('should return a recovered indicator when alert status is recovered', async () => {
const cell = render(
- renderCellValue({
+ getRenderCellValue({
...requiredProperties,
columnId: ALERT_STATUS,
data: makeAlertsTableRow({ alertStatus: ALERT_STATUS_RECOVERED }),
diff --git a/x-pack/plugins/observability_solution/observability/public/components/alerts_table/common/render_cell_value.tsx b/x-pack/plugins/observability_solution/observability/public/components/alerts_table/common/render_cell_value.tsx
index 5e4091a3080b0..5ab79c6dfa141 100644
--- a/x-pack/plugins/observability_solution/observability/public/components/alerts_table/common/render_cell_value.tsx
+++ b/x-pack/plugins/observability_solution/observability/public/components/alerts_table/common/render_cell_value.tsx
@@ -5,7 +5,6 @@
* 2.0.
*/
import { EuiLink } from '@elastic/eui';
-import { GetRenderCellValue } from '@kbn/triggers-actions-ui-plugin/public';
import React from 'react';
import {
ALERT_DURATION,
@@ -23,12 +22,11 @@ import {
} from '@kbn/rule-data-utils';
import { isEmpty } from 'lodash';
import type { TimelineNonEcsData } from '@kbn/timelines-plugin/common';
-
+import type { ObservabilityRuleTypeRegistry } from '../../..';
import { asDuration } from '../../../../common/utils/formatters';
import { AlertSeverityBadge } from '../../alert_severity_badge';
import { AlertStatusIndicator } from '../../alert_status_indicator';
import { parseAlert } from '../../../pages/alerts/helpers/parse_alert';
-import type { ObservabilityRuleTypeRegistry } from '../../../rules/create_observability_rule_type_registry';
import { CellTooltip } from './cell_tooltip';
import { TimestampTooltip } from './timestamp_tooltip';
@@ -71,63 +69,65 @@ const getRenderValue = (mappedNonEcsValue: any) => {
*/
export const getRenderCellValue = ({
+ columnId,
+ data,
setFlyoutAlert,
observabilityRuleTypeRegistry,
}: {
- setFlyoutAlert: (alertId: string) => void;
- observabilityRuleTypeRegistry: ObservabilityRuleTypeRegistry;
-}): ReturnType => {
- return ({ columnId, data }) => {
- if (!data) return null;
- const mappedNonEcsValue = getMappedNonEcsValue({
- data,
- fieldName: columnId,
- });
- const value = getRenderValue(mappedNonEcsValue);
-
- switch (columnId) {
- case ALERT_STATUS:
- if (value !== ALERT_STATUS_ACTIVE && value !== ALERT_STATUS_RECOVERED) {
- // NOTE: This should only be needed to narrow down the type.
- // Status should be either "active" or "recovered".
- return null;
- }
- return ;
- case TIMESTAMP:
- return ;
- case ALERT_DURATION:
- return asDuration(Number(value));
- case ALERT_SEVERITY:
- return ;
- case ALERT_EVALUATION_VALUE:
- const valuesField = getMappedNonEcsValue({
- data,
- fieldName: ALERT_EVALUATION_VALUES,
- });
- const values = getRenderValue(valuesField);
- return valuesField ? values : value;
- case ALERT_REASON:
- const dataFieldEs = data.reduce((acc, d) => ({ ...acc, [d.field]: d.value }), {});
- const alert = parseAlert(observabilityRuleTypeRegistry)(dataFieldEs);
+ columnId: string;
+ data?: Array<{ field: string; value: any }>;
+ setFlyoutAlert?: (alertId: string) => void;
+ observabilityRuleTypeRegistry?: ObservabilityRuleTypeRegistry;
+}) => {
+ if (!data) return null;
+ const mappedNonEcsValue = getMappedNonEcsValue({
+ data,
+ fieldName: columnId,
+ });
+ const value = getRenderValue(mappedNonEcsValue);
- return (
- setFlyoutAlert && setFlyoutAlert(alert.fields[ALERT_UUID])}
- >
- {alert.reason}
-
- );
- case ALERT_RULE_NAME:
- const ruleCategory = getMappedNonEcsValue({
- data,
- fieldName: ALERT_RULE_CATEGORY,
- });
- const tooltipContent = getRenderValue(ruleCategory);
- return ;
- default:
- return <>{value}>;
- }
- };
+ switch (columnId) {
+ case ALERT_STATUS:
+ if (value !== ALERT_STATUS_ACTIVE && value !== ALERT_STATUS_RECOVERED) {
+ // NOTE: This should only be needed to narrow down the type.
+ // Status should be either "active" or "recovered".
+ return null;
+ }
+ return ;
+ case TIMESTAMP:
+ return ;
+ case ALERT_DURATION:
+ return asDuration(Number(value));
+ case ALERT_SEVERITY:
+ return ;
+ case ALERT_EVALUATION_VALUE:
+ const valuesField = getMappedNonEcsValue({
+ data,
+ fieldName: ALERT_EVALUATION_VALUES,
+ });
+ const values = getRenderValue(valuesField);
+ return valuesField ? values : value;
+ case ALERT_REASON:
+ const dataFieldEs = data.reduce((acc, d) => ({ ...acc, [d.field]: d.value }), {});
+ if (!observabilityRuleTypeRegistry) return <>{value}>;
+ const alert = parseAlert(observabilityRuleTypeRegistry)(dataFieldEs);
+ return (
+ setFlyoutAlert && setFlyoutAlert(alert.fields[ALERT_UUID])}
+ >
+ {alert.reason}
+
+ );
+ case ALERT_RULE_NAME:
+ const ruleCategory = getMappedNonEcsValue({
+ data,
+ fieldName: ALERT_RULE_CATEGORY,
+ });
+ const tooltipContent = getRenderValue(ruleCategory);
+ return ;
+ default:
+ return <>{value}>;
+ }
};
diff --git a/x-pack/plugins/observability_solution/observability/public/components/alerts_table/rule_details/get_rule_details_table_configuration.tsx b/x-pack/plugins/observability_solution/observability/public/components/alerts_table/rule_details/get_rule_details_table_configuration.tsx
index 0ea5cf279e93c..e65bd8f4cfad8 100644
--- a/x-pack/plugins/observability_solution/observability/public/components/alerts_table/rule_details/get_rule_details_table_configuration.tsx
+++ b/x-pack/plugins/observability_solution/observability/public/components/alerts_table/rule_details/get_rule_details_table_configuration.tsx
@@ -38,11 +38,7 @@ export const getRuleDetailsTableConfiguration = (
id: RULE_DETAILS_ALERTS_TABLE_CONFIG_ID,
cases: { featureId: casesFeatureId, owner: [observabilityFeatureId] },
columns: getColumns(),
- getRenderCellValue: ({ setFlyoutAlert }) =>
- getRenderCellValue({
- observabilityRuleTypeRegistry,
- setFlyoutAlert,
- }),
+ getRenderCellValue,
sort: [
{
[ALERT_START]: {
diff --git a/x-pack/plugins/observability_solution/observability/public/components/alerts_table/slo/get_slo_alerts_table_configuration.tsx b/x-pack/plugins/observability_solution/observability/public/components/alerts_table/slo/get_slo_alerts_table_configuration.tsx
index 80c60169253c7..3748047398d36 100644
--- a/x-pack/plugins/observability_solution/observability/public/components/alerts_table/slo/get_slo_alerts_table_configuration.tsx
+++ b/x-pack/plugins/observability_solution/observability/public/components/alerts_table/slo/get_slo_alerts_table_configuration.tsx
@@ -24,11 +24,7 @@ export const getSloAlertsTableConfiguration = (
id: SLO_ALERTS_TABLE_CONFIG_ID,
cases: { featureId: casesFeatureId, owner: [observabilityFeatureId] },
columns,
- getRenderCellValue: ({ setFlyoutAlert }) =>
- getRenderCellValue({
- observabilityRuleTypeRegistry,
- setFlyoutAlert,
- }),
+ getRenderCellValue,
sort: [
{
[ALERT_DURATION]: {
diff --git a/x-pack/plugins/observability_solution/observability/public/pages/alerts/alerts.tsx b/x-pack/plugins/observability_solution/observability/public/pages/alerts/alerts.tsx
index b145dd92c300d..def57b3b4bd1b 100644
--- a/x-pack/plugins/observability_solution/observability/public/pages/alerts/alerts.tsx
+++ b/x-pack/plugins/observability_solution/observability/public/pages/alerts/alerts.tsx
@@ -67,7 +67,7 @@ function InternalAlertsPage() {
},
uiSettings,
} = kibanaServices;
- const { ObservabilityPageTemplate } = usePluginContext();
+ const { ObservabilityPageTemplate, observabilityRuleTypeRegistry } = usePluginContext();
const alertSearchBarStateProps = useAlertSearchBarStateContainer(ALERTS_URL_STORAGE_KEY, {
replace: false,
});
@@ -249,6 +249,7 @@ function InternalAlertsPage() {
query={esQuery}
showAlertStatusWithFlapping
pageSize={ALERTS_PER_PAGE}
+ cellContext={{ observabilityRuleTypeRegistry }}
/>
)}
diff --git a/x-pack/plugins/observability_solution/observability/public/pages/overview/overview.tsx b/x-pack/plugins/observability_solution/observability/public/pages/overview/overview.tsx
index 19c0b0acacf39..d4cfba510fc46 100644
--- a/x-pack/plugins/observability_solution/observability/public/pages/overview/overview.tsx
+++ b/x-pack/plugins/observability_solution/observability/public/pages/overview/overview.tsx
@@ -54,7 +54,7 @@ export function OverviewPage() {
kibanaVersion,
} = useKibana().services;
- const { ObservabilityPageTemplate } = usePluginContext();
+ const { ObservabilityPageTemplate, observabilityRuleTypeRegistry } = usePluginContext();
useBreadcrumbs([
{
@@ -244,6 +244,7 @@ export function OverviewPage() {
pageSize={ALERTS_PER_PAGE}
query={esQuery}
showAlertStatusWithFlapping
+ cellContext={{ observabilityRuleTypeRegistry }}
/>
diff --git a/x-pack/plugins/observability_solution/observability/public/pages/rule_details/components/rule_details_tabs.tsx b/x-pack/plugins/observability_solution/observability/public/pages/rule_details/components/rule_details_tabs.tsx
index f5e514d57a68a..095c5796946fd 100644
--- a/x-pack/plugins/observability_solution/observability/public/pages/rule_details/components/rule_details_tabs.tsx
+++ b/x-pack/plugins/observability_solution/observability/public/pages/rule_details/components/rule_details_tabs.tsx
@@ -19,6 +19,7 @@ import type { AlertConsumers } from '@kbn/rule-data-utils';
import type { Rule } from '@kbn/triggers-actions-ui-plugin/public';
import type { Query, BoolQuery } from '@kbn/es-query';
import { useKibana } from '../../../utils/kibana_react';
+import { usePluginContext } from '../../../hooks/use_plugin_context';
import { ObservabilityAlertSearchbarWithUrlSync } from '../../../components/alert_search_bar/alert_search_bar_with_url_sync';
import { RULE_DETAILS_ALERTS_TABLE_CONFIG_ID } from '../../../constants';
import {
@@ -62,6 +63,7 @@ export function RuleDetailsTabs({
getRuleEventLogList: RuleEventLogList,
},
} = useKibana().services;
+ const { observabilityRuleTypeRegistry } = usePluginContext();
const ruleQuery = useRef([
{ query: `kibana.alert.rule.uuid: ${ruleId}`, language: 'kuery' },
@@ -96,6 +98,7 @@ export function RuleDetailsTabs({
featureIds={featureIds}
query={esQuery}
showAlertStatusWithFlapping
+ cellContext={{ observabilityRuleTypeRegistry }}
/>
)}
diff --git a/x-pack/plugins/security_solution/public/actions/toggle_column/cell_action/toggle_column.ts b/x-pack/plugins/security_solution/public/actions/toggle_column/cell_action/toggle_column.ts
index 2a2926f3f6a27..c9409252277e0 100644
--- a/x-pack/plugins/security_solution/public/actions/toggle_column/cell_action/toggle_column.ts
+++ b/x-pack/plugins/security_solution/public/actions/toggle_column/cell_action/toggle_column.ts
@@ -72,7 +72,7 @@ export const createToggleColumnCellActionFactory = createCellActionFactory(
if (alertTableConfigurationId) {
services.triggersActionsUi.alertsTableConfigurationRegistry
.getActions(alertTableConfigurationId)
- .toggleColumn(field.name);
+ ?.toggleColumn(field.name);
return;
}
diff --git a/x-pack/plugins/security_solution/public/common/components/header_actions/actions.tsx b/x-pack/plugins/security_solution/public/common/components/header_actions/actions.tsx
index 881658c521a8e..7db76c66e53df 100644
--- a/x-pack/plugins/security_solution/public/common/components/header_actions/actions.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/header_actions/actions.tsx
@@ -120,9 +120,9 @@ const ActionsComponent: React.FC = ({
const isDisabled = !useIsInvestigateInResolverActionEnabled(ecsData);
const { setGlobalFullScreen } = useGlobalFullScreen();
const { setTimelineFullScreen } = useTimelineFullScreen();
- const scopedActions = getScopedActions(timelineId);
const handleClick = useCallback(() => {
startTransaction({ name: ALERTS_ACTIONS.OPEN_ANALYZER });
+ const scopedActions = getScopedActions(timelineId);
const dataGridIsFullScreen = document.querySelector('.euiDataGrid--fullScreen');
if (scopedActions) {
@@ -140,7 +140,6 @@ const ActionsComponent: React.FC = ({
}
}, [
startTransaction,
- scopedActions,
timelineId,
dispatch,
ecsData._id,
@@ -176,6 +175,7 @@ const ActionsComponent: React.FC = ({
const openSessionView = useCallback(() => {
const dataGridIsFullScreen = document.querySelector('.euiDataGrid--fullScreen');
startTransaction({ name: ALERTS_ACTIONS.OPEN_SESSION_VIEW });
+ const scopedActions = getScopedActions(timelineId);
if (timelineId === TimelineId.active) {
if (dataGridIsFullScreen) {
@@ -201,7 +201,6 @@ const ActionsComponent: React.FC = ({
setTimelineFullScreen,
dispatch,
setGlobalFullScreen,
- scopedActions,
]);
const { activeStep, isTourShown, incrementStep } = useTourContext();
diff --git a/x-pack/plugins/security_solution/public/common/components/header_actions/header_actions.tsx b/x-pack/plugins/security_solution/public/common/components/header_actions/header_actions.tsx
index 4e9aa8452877c..6088c8587a9fe 100644
--- a/x-pack/plugins/security_solution/public/common/components/header_actions/header_actions.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/header_actions/header_actions.tsx
@@ -5,9 +5,9 @@
* 2.0.
*/
-import React, { useMemo, useCallback } from 'react';
+import React, { useMemo, useCallback, memo } from 'react';
import type { EuiDataGridSorting, EuiDataGridSchemaDetector } from '@elastic/eui';
-import { EuiButtonIcon, EuiCheckbox, EuiToolTip, useDataGridColumnSorting } from '@elastic/eui';
+import { EuiButtonIcon, EuiToolTip, useDataGridColumnSorting, EuiCheckbox } from '@elastic/eui';
import { useDispatch } from 'react-redux';
import styled from 'styled-components';
@@ -68,223 +68,226 @@ const ActionsContainer = styled.div`
const emptySchema = {};
const emptySchemaDetectors: EuiDataGridSchemaDetector[] = [];
-const HeaderActionsComponent: React.FC = ({
- width,
- browserFields,
- columnHeaders,
- isEventViewer = false,
- isSelectAllChecked,
- onSelectAll,
- showEventsSelect,
- showSelectAllCheckbox,
- sort,
- tabType,
- timelineId,
- fieldBrowserOptions,
-}) => {
- const { triggersActionsUi } = useKibana().services;
- const { globalFullScreen, setGlobalFullScreen } = useGlobalFullScreen();
- const { timelineFullScreen, setTimelineFullScreen } = useTimelineFullScreen();
- const dispatch = useDispatch();
-
- const getManageTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []);
- const { defaultColumns } = useDeepEqualSelector((state) => getManageTimeline(state, timelineId));
-
- const toggleFullScreen = useCallback(() => {
- if (timelineId === TimelineId.active) {
- setTimelineFullScreen(!timelineFullScreen);
- } else {
- setGlobalFullScreen(!globalFullScreen);
- }
- }, [
+const HeaderActionsComponent: React.FC = memo(
+ ({
+ width,
+ browserFields,
+ columnHeaders,
+ isEventViewer = false,
+ isSelectAllChecked,
+ onSelectAll,
+ showEventsSelect,
+ showSelectAllCheckbox,
+ sort,
+ tabType,
timelineId,
- setTimelineFullScreen,
- timelineFullScreen,
- setGlobalFullScreen,
- globalFullScreen,
- ]);
-
- const fullScreen = useMemo(
- () =>
- isFullScreen({
- globalFullScreen,
- isActiveTimelines: isActiveTimeline(timelineId),
- timelineFullScreen,
- }),
- [globalFullScreen, timelineFullScreen, timelineId]
- );
- const handleSelectAllChange = useCallback(
- (event: React.ChangeEvent) => {
- onSelectAll({ isSelected: event.currentTarget.checked });
- },
- [onSelectAll]
- );
-
- const onSortColumns = useCallback(
- (cols: EuiDataGridSorting['columns']) =>
- dispatch(
- timelineActions.updateSort({
- id: timelineId,
- sort: cols.map(({ id, direction }) => {
- const columnHeader = columnHeaders.find((ch) => ch.id === id);
- const columnType = columnHeader?.type ?? '';
- const esTypes = columnHeader?.esTypes ?? [];
+ fieldBrowserOptions,
+ }) => {
+ const { triggersActionsUi } = useKibana().services;
+ const { globalFullScreen, setGlobalFullScreen } = useGlobalFullScreen();
+ const { timelineFullScreen, setTimelineFullScreen } = useTimelineFullScreen();
+ const dispatch = useDispatch();
- return {
- columnId: id,
- columnType,
- esTypes,
- sortDirection: direction as SortDirection,
- };
- }),
- })
- ),
- [columnHeaders, dispatch, timelineId]
- );
-
- const sortedColumns = useMemo(
- () => ({
- onSort: onSortColumns,
- columns:
- sort?.map<{ id: string; direction: 'asc' | 'desc' }>(({ columnId, sortDirection }) => ({
- id: columnId,
- direction: sortDirection as 'asc' | 'desc',
- })) ?? [],
- }),
- [onSortColumns, sort]
- );
- const displayValues = useMemo(
- () =>
- columnHeaders?.reduce((acc, ch) => ({ ...acc, [ch.id]: ch.displayAsText ?? ch.id }), {}) ??
- {},
- [columnHeaders]
- );
+ const getManageTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []);
+ const { defaultColumns } = useDeepEqualSelector((state) =>
+ getManageTimeline(state, timelineId)
+ );
- const myColumns = useMemo(
- () =>
- columnHeaders?.map(({ aggregatable, displayAsText, id, type }) => ({
- id,
- isSortable: aggregatable,
- displayAsText,
- schema: type,
- })) ?? [],
- [columnHeaders]
- );
+ const toggleFullScreen = useCallback(() => {
+ if (timelineId === TimelineId.active) {
+ setTimelineFullScreen(!timelineFullScreen);
+ } else {
+ setGlobalFullScreen(!globalFullScreen);
+ }
+ }, [
+ timelineId,
+ setTimelineFullScreen,
+ timelineFullScreen,
+ setGlobalFullScreen,
+ globalFullScreen,
+ ]);
- const onResetColumns = useCallback(() => {
- dispatch(timelineActions.updateColumns({ id: timelineId, columns: defaultColumns }));
- }, [defaultColumns, dispatch, timelineId]);
+ const fullScreen = useMemo(
+ () =>
+ isFullScreen({
+ globalFullScreen,
+ isActiveTimelines: isActiveTimeline(timelineId),
+ timelineFullScreen,
+ }),
+ [globalFullScreen, timelineFullScreen, timelineId]
+ );
+ const handleSelectAllChange = useCallback(
+ (event: React.ChangeEvent) => {
+ onSelectAll({ isSelected: event.currentTarget.checked });
+ },
+ [onSelectAll]
+ );
- const onToggleColumn = useCallback(
- (columnId: string) => {
- if (columnHeaders.some(({ id }) => id === columnId)) {
- dispatch(
- timelineActions.removeColumn({
- columnId,
- id: timelineId,
- })
- );
- } else {
+ const onSortColumns = useCallback(
+ (cols: EuiDataGridSorting['columns']) =>
dispatch(
- timelineActions.upsertColumn({
- column: getColumnHeader(columnId, defaultColumns),
+ timelineActions.updateSort({
id: timelineId,
- index: 1,
+ sort: cols.map(({ id, direction }) => {
+ const columnHeader = columnHeaders.find((ch) => ch.id === id);
+ const columnType = columnHeader?.type ?? '';
+ const esTypes = columnHeader?.esTypes ?? [];
+
+ return {
+ columnId: id,
+ columnType,
+ esTypes,
+ sortDirection: direction as SortDirection,
+ };
+ }),
})
- );
- }
- },
- [columnHeaders, dispatch, timelineId, defaultColumns]
- );
+ ),
+ [columnHeaders, dispatch, timelineId]
+ );
- const ColumnSorting = useDataGridColumnSorting({
- columns: myColumns,
- sorting: sortedColumns,
- schema: emptySchema,
- schemaDetectors: emptySchemaDetectors,
- displayValues,
- });
+ const sortedColumns = useMemo(
+ () => ({
+ onSort: onSortColumns,
+ columns:
+ sort?.map<{ id: string; direction: 'asc' | 'desc' }>(({ columnId, sortDirection }) => ({
+ id: columnId,
+ direction: sortDirection as 'asc' | 'desc',
+ })) ?? [],
+ }),
+ [onSortColumns, sort]
+ );
+ const displayValues = useMemo(
+ () =>
+ columnHeaders?.reduce((acc, ch) => ({ ...acc, [ch.id]: ch.displayAsText ?? ch.id }), {}) ??
+ {},
+ [columnHeaders]
+ );
- return (
-
- {showSelectAllCheckbox && (
-
-
-
-
-
- )}
+ const myColumns = useMemo(
+ () =>
+ columnHeaders?.map(({ aggregatable, displayAsText, id, type }) => ({
+ id,
+ isSortable: aggregatable,
+ displayAsText,
+ schema: type,
+ })) ?? [],
+ [columnHeaders]
+ );
-
-
- {triggersActionsUi.getFieldBrowser({
- browserFields,
- columnIds: columnHeaders.map(({ id }) => id),
- onResetColumns,
- onToggleColumn,
- options: fieldBrowserOptions,
- })}
-
-
+ const onResetColumns = useCallback(() => {
+ dispatch(timelineActions.updateColumns({ id: timelineId, columns: defaultColumns }));
+ }, [defaultColumns, dispatch, timelineId]);
-
-
-
+ const onToggleColumn = useCallback(
+ (columnId: string) => {
+ if (columnHeaders.some(({ id }) => id === columnId)) {
+ dispatch(
+ timelineActions.removeColumn({
+ columnId,
+ id: timelineId,
+ })
+ );
+ } else {
+ dispatch(
+ timelineActions.upsertColumn({
+ column: getColumnHeader(columnId, defaultColumns),
+ id: timelineId,
+ index: 1,
+ })
+ );
+ }
+ },
+ [columnHeaders, dispatch, timelineId, defaultColumns]
+ );
-
-
-
-
-
-
-
- {tabType !== TimelineTabs.eql && (
-
-
-
- {ColumnSorting}
-
-
+ const ColumnSorting = useDataGridColumnSorting({
+ columns: myColumns,
+ sorting: sortedColumns,
+ schema: emptySchema,
+ schemaDetectors: emptySchemaDetectors,
+ displayValues,
+ });
+
+ return (
+
+ {showSelectAllCheckbox && (
+
+
+
+
+
+ )}
+
+
+ {triggersActionsUi.getFieldBrowser({
+ browserFields,
+ columnIds: columnHeaders.map(({ id }) => id),
+ onResetColumns,
+ onToggleColumn,
+ options: fieldBrowserOptions,
+ })}
+
+
+
+
+
- )}
- {showEventsSelect && (
-
+
+
+
- )}
-
- );
-};
+ {tabType !== TimelineTabs.eql && (
+
+
+
+ {ColumnSorting}
+
+
+
+ )}
+
+ {showEventsSelect && (
+
+
+
+
+
+ )}
+
+ );
+ }
+);
HeaderActionsComponent.displayName = 'HeaderActionsComponent';
export const HeaderActions = React.memo(HeaderActionsComponent);
diff --git a/x-pack/plugins/security_solution/public/common/components/toolbar/bulk_actions/alert_bulk_tags.tsx b/x-pack/plugins/security_solution/public/common/components/toolbar/bulk_actions/alert_bulk_tags.tsx
index bdb5c1d2d5f01..67ba6562abc23 100644
--- a/x-pack/plugins/security_solution/public/common/components/toolbar/bulk_actions/alert_bulk_tags.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/toolbar/bulk_actions/alert_bulk_tags.tsx
@@ -8,7 +8,7 @@
import type { EuiSelectableOption } from '@elastic/eui';
import { EuiPopoverTitle, EuiSelectable, EuiButton } from '@elastic/eui';
import type { TimelineItem } from '@kbn/timelines-plugin/common';
-import React, { memo, useCallback, useMemo, useReducer } from 'react';
+import React, { memo, useCallback, useMemo, useReducer, useEffect } from 'react';
import { ALERT_WORKFLOW_TAGS } from '@kbn/rule-data-utils';
import type { EuiSelectableOnChangeEvent } from '@elastic/eui/src/components/selectable/selectable';
import { DEFAULT_ALERT_TAGS_KEY } from '../../../../../common/constants';
@@ -27,6 +27,7 @@ interface BulkAlertTagsPanelComponentProps {
closePopoverMenu: () => void;
onSubmit: SetAlertTagsFunc;
}
+
const BulkAlertTagsPanelComponent: React.FC = ({
alertItems,
refresh,
@@ -47,12 +48,7 @@ const BulkAlertTagsPanelComponent: React.FC =
);
const [{ selectableAlertTags, tagsToAdd, tagsToRemove }, dispatch] = useReducer(
createAlertTagsReducer(),
- {
- ...initialState,
- selectableAlertTags: createInitialTagsState(existingTags, defaultAlertTagOptions),
- tagsToAdd: new Set(),
- tagsToRemove: new Set(),
- }
+ initialState
);
const addAlertTag = useCallback(
@@ -122,6 +118,13 @@ const BulkAlertTagsPanelComponent: React.FC =
[addAlertTag, removeAlertTag, setSelectableAlertTags]
);
+ useEffect(() => {
+ dispatch({
+ type: 'setSelectableAlertTags',
+ value: createInitialTagsState(existingTags, defaultAlertTagOptions),
+ });
+ }, [existingTags, defaultAlertTagOptions]);
+
return (
<>
{
+ return {
+ alertAssigneesItems,
+ alertAssigneesPanels,
+ };
+ }, [alertAssigneesItems, alertAssigneesPanels]);
};
diff --git a/x-pack/plugins/security_solution/public/common/components/toolbar/bulk_actions/use_bulk_alert_tags_items.tsx b/x-pack/plugins/security_solution/public/common/components/toolbar/bulk_actions/use_bulk_alert_tags_items.tsx
index 977cb0bf8b315..c155d39e2a3a5 100644
--- a/x-pack/plugins/security_solution/public/common/components/toolbar/bulk_actions/use_bulk_alert_tags_items.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/toolbar/bulk_actions/use_bulk_alert_tags_items.tsx
@@ -101,8 +101,10 @@ export const useBulkAlertTagsItems = ({ refetch }: UseBulkAlertTagsItemsProps) =
[TitleContent, hasIndexWrite, renderContent]
);
- return {
- alertTagsItems,
- alertTagsPanels,
- };
+ return useMemo(() => {
+ return {
+ alertTagsItems,
+ alertTagsPanels,
+ };
+ }, [alertTagsItems, alertTagsPanels]);
};
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx
index 0ab8ba6a624f1..599c32414d966 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx
@@ -26,6 +26,7 @@ import { useGlobalTime } from '../../../common/containers/use_global_time';
import { useLicense } from '../../../common/hooks/use_license';
import { VIEW_SELECTION } from '../../../../common/constants';
import { DEFAULT_COLUMN_MIN_WIDTH } from '../../../timelines/components/timeline/body/constants';
+import { defaultRowRenderers } from '../../../timelines/components/timeline/body/renderers';
import { eventsDefaultModel } from '../../../common/components/events_viewer/default_model';
import { GraphOverlay } from '../../../timelines/components/graph_overlay';
import {
@@ -255,6 +256,15 @@ export const AlertsTableComponent: FC = ({
[dispatch, tableId, alertTableRefreshHandlerRef, setQuery]
);
+ const cellContext = useMemo(() => {
+ return {
+ rowRenderers: defaultRowRenderers,
+ isDetails: false,
+ truncate: true,
+ isDraggable: false,
+ };
+ }, []);
+
const alertStateProps: AlertsTableStateProps = useMemo(
() => ({
alertsTableConfigurationRegistry: triggersActionsUi.alertsTableConfigurationRegistry,
@@ -269,6 +279,7 @@ export const AlertsTableComponent: FC = ({
columns: finalColumns,
browserFields: finalBrowserFields,
onUpdate: onAlertTableUpdate,
+ cellContext,
runtimeMappings,
toolbarVisibility: {
showColumnSelector: !isEventRenderedView,
@@ -288,6 +299,7 @@ export const AlertsTableComponent: FC = ({
onAlertTableUpdate,
runtimeMappings,
isEventRenderedView,
+ cellContext,
]
);
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_add_bulk_to_timeline.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_add_bulk_to_timeline.tsx
index 28ddfe1e12fc1..4c894918c593e 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_add_bulk_to_timeline.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_add_bulk_to_timeline.tsx
@@ -80,24 +80,28 @@ export const useAddBulkToTimelineAction = ({
const esQueryConfig = useMemo(() => getEsQueryConfig(uiSettings), [uiSettings]);
- const timelineQuerySortField = sort.map(({ columnId, columnType, esTypes, sortDirection }) => ({
- field: columnId,
- direction: sortDirection as Direction,
- esTypes: esTypes ?? [],
- type: columnType,
- }));
+ const timelineQuerySortField = useMemo(() => {
+ return sort.map(({ columnId, columnType, esTypes, sortDirection }) => ({
+ field: columnId,
+ direction: sortDirection as Direction,
+ esTypes: esTypes ?? [],
+ type: columnType,
+ }));
+ }, [sort]);
const combinedFilters = useMemo(() => [...localFilters, ...filters], [localFilters, filters]);
- const combinedQuery = combineQueries({
- config: esQueryConfig,
- dataProviders: [],
- indexPattern,
- filters: combinedFilters,
- kqlQuery: { query: '', language: 'kuery' },
- browserFields,
- kqlMode: 'filter',
- });
+ const combinedQuery = useMemo(() => {
+ return combineQueries({
+ config: esQueryConfig,
+ dataProviders: [],
+ indexPattern,
+ filters: combinedFilters,
+ kqlQuery: { query: '', language: 'kuery' },
+ browserFields,
+ kqlMode: 'filter',
+ });
+ }, [esQueryConfig, indexPattern, combinedFilters, browserFields]);
const filterQuery = useMemo(() => {
if (!combinedQuery) return '';
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_add_to_case_actions.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_add_to_case_actions.tsx
index de3c8782722fa..0fa09d4bf4354 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_add_to_case_actions.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_add_to_case_actions.tsx
@@ -65,7 +65,7 @@ export const useAddToCaseActions = ({
const { activeStep, incrementStep, setStep, isTourShown } = useTourContext();
- const onCaseSuccess = () => {
+ const onCaseSuccess = useCallback(() => {
if (onSuccess) {
onSuccess();
}
@@ -73,7 +73,7 @@ export const useAddToCaseActions = ({
if (refetch) {
refetch();
}
- };
+ }, [onSuccess, refetch]);
const afterCaseCreated = useCallback(async () => {
if (isTourShown(SecurityStepId.alertsCases)) {
@@ -92,17 +92,25 @@ export const useAddToCaseActions = ({
[activeStep, isTourShown]
);
- const createCaseFlyout = casesUi.hooks.useCasesAddToNewCaseFlyout({
- onClose: onMenuItemClick,
- onSuccess: onCaseSuccess,
- afterCaseCreated,
- ...prefillCasesValue,
- });
-
- const selectCaseModal = casesUi.hooks.useCasesAddToExistingCaseModal({
- onClose: onMenuItemClick,
- onSuccess: onCaseSuccess,
- });
+ const createCaseArgs = useMemo(() => {
+ return {
+ onClose: onMenuItemClick,
+ onSuccess: onCaseSuccess,
+ afterCaseCreated,
+ ...prefillCasesValue,
+ };
+ }, [onMenuItemClick, onCaseSuccess, afterCaseCreated, prefillCasesValue]);
+
+ const createCaseFlyout = casesUi.hooks.useCasesAddToNewCaseFlyout(createCaseArgs);
+
+ const selectCaseArgs = useMemo(() => {
+ return {
+ onClose: onMenuItemClick,
+ onSuccess: onCaseSuccess,
+ };
+ }, [onMenuItemClick, onCaseSuccess]);
+
+ const selectCaseModal = casesUi.hooks.useCasesAddToExistingCaseModal(selectCaseArgs);
const handleAddToNewCaseClick = useCallback(() => {
// TODO rename this, this is really `closePopover()`
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_alerts_actions.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_alerts_actions.tsx
index c435333486f4d..7d21c428a33b8 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_alerts_actions.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_alerts_actions.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { useCallback } from 'react';
+import { useCallback, useMemo } from 'react';
import { useDispatch } from 'react-redux';
import type { AlertWorkflowStatus } from '../../../../common/types';
@@ -58,16 +58,22 @@ export const useAlertsActions = ({
[dispatch, scopeId, scopedActions]
);
- const actionItems = useBulkActionItems({
- eventIds: [eventId],
- currentStatus: alertStatus as AlertWorkflowStatus,
- setEventsLoading: localSetEventsLoading,
- setEventsDeleted,
- onUpdateSuccess: onStatusUpdate,
- onUpdateFailure: onStatusUpdate,
- });
+ const eventIds = useMemo(() => [eventId], [eventId]);
- return {
- actionItems: hasIndexWrite ? actionItems : [],
- };
+ const actionItemArgs = useMemo(() => {
+ return {
+ eventIds,
+ currentStatus: alertStatus as AlertWorkflowStatus,
+ setEventsLoading: localSetEventsLoading,
+ setEventsDeleted,
+ onUpdateSuccess: onStatusUpdate,
+ onUpdateFailure: onStatusUpdate,
+ };
+ }, [alertStatus, eventIds, localSetEventsLoading, onStatusUpdate, setEventsDeleted]);
+
+ const actionItems = useBulkActionItems(actionItemArgs);
+
+ return useMemo(() => {
+ return { actionItems: hasIndexWrite ? actionItems : [] };
+ }, [actionItems, hasIndexWrite]);
};
diff --git a/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/fetch_page_context.tsx b/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/fetch_page_context.tsx
index ebd8df15d92ea..45614aad4e96d 100644
--- a/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/fetch_page_context.tsx
+++ b/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/fetch_page_context.tsx
@@ -25,19 +25,21 @@ export const useFetchPageContext: PreFetchPageContext =
alerts,
columns,
}) => {
- const uids = new Set();
- alerts.forEach((alert) => {
- profileUidColumns.forEach((columnId) => {
- if (columns.find((column) => column.id === columnId) != null) {
- const userUids = alert[columnId];
- userUids?.forEach((uid) => uids.add(uid as string));
- }
+ const uids = useMemo(() => {
+ const ids = new Set();
+ alerts.forEach((alert) => {
+ profileUidColumns.forEach((columnId) => {
+ if (columns.find((column) => column.id === columnId) != null) {
+ const userUids = alert[columnId];
+ userUids?.forEach((uid) => ids.add(uid as string));
+ }
+ });
});
- });
+ return ids;
+ }, [alerts, columns]);
const result = useBulkGetUserProfiles({ uids });
- const returnVal = useMemo(
+ return useMemo(
() => ({ profiles: result.data, isLoading: result.isLoading }),
[result.data, result.isLoading]
);
- return returnVal;
};
diff --git a/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/render_cell_value.test.tsx b/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/render_cell_value.test.tsx
index e2e38b2390309..20afb552e045a 100644
--- a/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/render_cell_value.test.tsx
+++ b/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/render_cell_value.test.tsx
@@ -8,18 +8,27 @@
import { mount } from 'enzyme';
import { cloneDeep } from 'lodash/fp';
import React from 'react';
-
+import { TableId } from '@kbn/securitysolution-data-table';
import type { ColumnHeaderOptions } from '../../../../common/types';
import { mockBrowserFields } from '../../../common/containers/source/mock';
import { DragDropContextWrapper } from '../../../common/components/drag_and_drop/drag_drop_context_wrapper';
import { defaultHeaders, mockTimelineData, TestProviders } from '../../../common/mock';
+import { defaultRowRenderers } from '../../../timelines/components/timeline/body/renderers';
import type { TimelineNonEcsData } from '../../../../common/search_strategy/timeline';
import type { CellValueElementProps } from '../../../timelines/components/timeline/cell_rendering';
import { DefaultCellRenderer } from '../../../timelines/components/timeline/cell_rendering/default_cell_renderer';
-
-import { RenderCellValue } from '.';
+import { getRenderCellValueHook } from './render_cell_value';
+import { SourcererScopeName } from '../../../common/store/sourcerer/model';
jest.mock('../../../common/lib/kibana');
+jest.mock('../../../common/containers/sourcerer', () => ({
+ useSourcererDataView: jest.fn().mockReturnValue({
+ browserFields: {},
+ defaultIndex: 'defaultIndex',
+ loading: false,
+ indicesExist: true,
+ }),
+}));
describe('RenderCellValue', () => {
const columnId = '@timestamp';
@@ -49,10 +58,20 @@ describe('RenderCellValue', () => {
colIndex: 0,
setCellProps: jest.fn(),
scopeId,
+ rowRenderers: defaultRowRenderers,
+ asPlainText: false,
+ ecsData: undefined,
+ truncate: undefined,
+ context: undefined,
+ browserFields: {},
};
});
test('it forwards the `CellValueElementProps` to the `DefaultCellRenderer`', () => {
+ const RenderCellValue = getRenderCellValueHook({
+ scopeId: SourcererScopeName.default,
+ tableId: TableId.test,
+ });
const wrapper = mount(
@@ -61,6 +80,6 @@ describe('RenderCellValue', () => {
);
- expect(wrapper.find(DefaultCellRenderer).first().props()).toEqual(props);
+ expect(wrapper.find(DefaultCellRenderer).props()).toEqual(props);
});
});
diff --git a/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/render_cell_value.tsx b/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/render_cell_value.tsx
index 84e14ad725e40..752675857ace2 100644
--- a/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/render_cell_value.tsx
+++ b/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/render_cell_value.tsx
@@ -5,16 +5,15 @@
* 2.0.
*/
-import type { EuiDataGridCellValueElementProps } from '@elastic/eui';
import { EuiIcon, EuiToolTip, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
-import React, { useCallback, useMemo } from 'react';
-import type { GetRenderCellValue } from '@kbn/triggers-actions-ui-plugin/public';
+import type { EuiDataGridCellProps } from '@elastic/eui';
+import React, { useMemo, memo } from 'react';
import { find, getOr } from 'lodash/fp';
import type { TimelineNonEcsData } from '@kbn/timelines-plugin/common';
import { tableDefaults, dataTableSelectors } from '@kbn/securitysolution-data-table';
import type { TableId } from '@kbn/securitysolution-data-table';
import { useLicense } from '../../../common/hooks/use_license';
-import { useShallowEqualSelector } from '../../../common/hooks/use_selector';
+import { useDeepEqualSelector } from '../../../common/hooks/use_selector';
import { defaultRowRenderers } from '../../../timelines/components/timeline/body/renderers';
import type { SourcererScopeName } from '../../../common/store/sourcerer/model';
import { GuidedOnboardingTourStep } from '../../../common/components/guided_onboarding_tour/tour_step';
@@ -25,137 +24,110 @@ import {
} from '../../../common/components/guided_onboarding_tour/tour_config';
import { SIGNAL_RULE_NAME_FIELD_NAME } from '../../../timelines/components/timeline/body/renderers/constants';
import { useSourcererDataView } from '../../../common/containers/sourcerer';
-
-import type { CellValueElementProps } from '../../../timelines/components/timeline/cell_rendering';
import { DefaultCellRenderer } from '../../../timelines/components/timeline/cell_rendering/default_cell_renderer';
import { SUPPRESSED_ALERT_TOOLTIP } from './translations';
import { VIEW_SELECTION } from '../../../../common/constants';
import { getAllFieldsByName } from '../../../common/containers/source';
import { eventRenderedViewColumns, getColumns } from './columns';
-import type { RenderCellValueContext } from './fetch_page_context';
/**
* This implementation of `EuiDataGrid`'s `renderCellValue`
* accepts `EuiDataGridCellValueElementProps`, plus `data`
* from the TGrid
*/
-export const RenderCellValue: React.FC = (
- props
-) => {
- const { columnId, rowIndex, scopeId } = props;
- const isTourAnchor = useMemo(
- () =>
- columnId === SIGNAL_RULE_NAME_FIELD_NAME &&
- isDetectionsAlertsTable(scopeId) &&
- rowIndex === 0 &&
- !props.isDetails,
- [columnId, props.isDetails, rowIndex, scopeId]
- );
-
- // We check both ecsData and data for the suppression count because it could be in either one,
- // depending on where RenderCellValue is being used - when used in cases, data is populated,
- // whereas in the regular security alerts table it's in ecsData
- const ecsSuppressionCount = props.ecsData?.kibana?.alert.suppression?.docs_count?.[0];
- const dataSuppressionCount = find({ field: 'kibana.alert.suppression.docs_count' }, props.data)
- ?.value?.[0] as number | undefined;
- const actualSuppressionCount = ecsSuppressionCount
- ? parseInt(ecsSuppressionCount, 10)
- : dataSuppressionCount;
- const component = (
-
-
-
- );
-
- return columnId === SIGNAL_RULE_NAME_FIELD_NAME &&
- actualSuppressionCount &&
- actualSuppressionCount > 0 ? (
-
-
-
-
-
-
- {component}
-
- ) : (
- component
- );
-};
-
-export const getRenderCellValueHook = ({
- scopeId,
- tableId,
-}: {
- scopeId: SourcererScopeName;
- tableId: TableId;
-}) => {
- const useRenderCellValue: GetRenderCellValue = ({ context }) => {
+export const RenderCellValue: React.FC = memo(
+ function RenderCellValue(props) {
+ const {
+ columnId,
+ rowIndex,
+ scopeId,
+ tableId,
+ header,
+ data,
+ ecsData,
+ linkValues,
+ rowRenderers,
+ isDetails,
+ isExpandable,
+ isDraggable,
+ isExpanded,
+ colIndex,
+ eventId,
+ setCellProps,
+ truncate,
+ context,
+ } = props;
+ const isTourAnchor = useMemo(
+ () =>
+ columnId === SIGNAL_RULE_NAME_FIELD_NAME &&
+ isDetectionsAlertsTable(tableId) &&
+ rowIndex === 0 &&
+ !props.isDetails,
+ [columnId, props.isDetails, rowIndex, tableId]
+ );
const { browserFields } = useSourcererDataView(scopeId);
const browserFieldsByName = useMemo(() => getAllFieldsByName(browserFields), [browserFields]);
const getTable = useMemo(() => dataTableSelectors.getTableByIdSelector(), []);
const license = useLicense();
-
const viewMode =
- useShallowEqualSelector((state) => (getTable(state, tableId) ?? tableDefaults).viewMode) ??
+ useDeepEqualSelector((state) => (getTable(state, tableId) ?? tableDefaults).viewMode) ??
tableDefaults.viewMode;
- const columnHeaders =
- viewMode === VIEW_SELECTION.gridView ? getColumns(license) : eventRenderedViewColumns;
-
- const result = useCallback(
- ({
- columnId,
- colIndex,
- data,
- ecsData,
- eventId,
- header,
- isDetails = false,
- isDraggable = false,
- isExpandable,
- isExpanded,
- rowIndex,
- rowRenderers,
- setCellProps,
- linkValues,
- truncate = true,
- }) => {
- const myHeader = header ?? { id: columnId, ...browserFieldsByName[columnId] };
- /**
- * There is difference between how `triggers actions` fetched data v/s
- * how security solution fetches data via timelineSearchStrategy
- *
- * _id and _index fields are array in timelineSearchStrategy but not in
- * ruleStrategy
- *
- *
- */
-
- const finalData = (data as TimelineNonEcsData[]).map((field) => {
- let localField = field;
- if (['_id', '_index'].includes(field.field)) {
- const newValue = field.value ?? '';
- localField = {
- field: field.field,
- value: Array.isArray(newValue) ? newValue : [newValue],
- };
- }
- return localField;
- });
-
- const colHeader = columnHeaders.find((col) => col.id === columnId);
-
- const localLinkValues = getOr([], colHeader?.linkField ?? '', ecsData);
-
- return (
- {
+ return getColumns(license);
+ }, [license]);
+
+ const columnHeaders = useMemo(() => {
+ return viewMode === VIEW_SELECTION.gridView ? gridColumns : eventRenderedViewColumns;
+ }, [gridColumns, viewMode]);
+
+ /**
+ * There is difference between how `triggers actions` fetched data v/s
+ * how security solution fetches data via timelineSearchStrategy
+ *
+ * _id and _index fields are array in timelineSearchStrategy but not in
+ * ruleStrategy
+ *
+ *
+ */
+
+ const finalData = useMemo(() => {
+ return (data as TimelineNonEcsData[]).map((field) => {
+ if (['_id', '_index'].includes(field.field)) {
+ const newValue = field.value ?? '';
+ return {
+ field: field.field,
+ value: Array.isArray(newValue) ? newValue : [newValue],
+ };
+ } else {
+ return field;
+ }
+ });
+ }, [data]);
+
+ const actualSuppressionCount = useMemo(() => {
+ // We check both ecsData and data for the suppression count because it could be in either one,
+ // depending on where RenderCellValue is being used - when used in cases, data is populated,
+ // whereas in the regular security alerts table it's in ecsData
+ const ecsSuppressionCount = ecsData?.kibana?.alert.suppression?.docs_count?.[0];
+ const dataSuppressionCount = find({ field: 'kibana.alert.suppression.docs_count' }, data)
+ ?.value?.[0] as number | undefined;
+ return ecsSuppressionCount ? parseInt(ecsSuppressionCount, 10) : dataSuppressionCount;
+ }, [ecsData, data]);
+
+ const Renderer = useMemo(() => {
+ const myHeader = header ?? { id: columnId, ...browserFieldsByName[columnId] };
+ const colHeader = columnHeaders.find((col) => col.id === columnId);
+ const localLinkValues = getOr([], colHeader?.linkField ?? '', ecsData);
+ return (
+
+
- );
- },
- [browserFieldsByName, columnHeaders, browserFields, context]
+
+ );
+ }, [
+ isTourAnchor,
+ finalData,
+ browserFieldsByName,
+ header,
+ columnId,
+ ecsData,
+ linkValues,
+ rowRenderers,
+ isDetails,
+ isExpandable,
+ isDraggable,
+ isExpanded,
+ colIndex,
+ eventId,
+ setCellProps,
+ truncate,
+ context,
+ tableId,
+ browserFields,
+ rowIndex,
+ columnHeaders,
+ ]);
+
+ return columnId === SIGNAL_RULE_NAME_FIELD_NAME && actualSuppressionCount ? (
+
+
+
+
+
+
+ {Renderer}
+
+ ) : (
+ <>{Renderer}>
);
- return result;
- };
+ }
+);
+export const getRenderCellValueHook = ({
+ scopeId,
+ tableId,
+}: {
+ scopeId: SourcererScopeName;
+ tableId: TableId;
+}) => {
+ const useRenderCellValue = (props: EuiDataGridCellProps['cellContext']) => {
+ return ;
+ };
return useRenderCellValue;
};
diff --git a/x-pack/plugins/security_solution/public/detections/hooks/trigger_actions_alert_table/use_alert_actions.tsx b/x-pack/plugins/security_solution/public/detections/hooks/trigger_actions_alert_table/use_alert_actions.tsx
index decc2cb159b5c..8a45dfb45db74 100644
--- a/x-pack/plugins/security_solution/public/detections/hooks/trigger_actions_alert_table/use_alert_actions.tsx
+++ b/x-pack/plugins/security_solution/public/detections/hooks/trigger_actions_alert_table/use_alert_actions.tsx
@@ -6,7 +6,7 @@
*/
import type { BulkActionsConfig } from '@kbn/triggers-actions-ui-plugin/public/types';
-import { useCallback } from 'react';
+import { useCallback, useMemo } from 'react';
import type { Filter } from '@kbn/es-query';
import { buildEsQuery } from '@kbn/es-query';
import type { TableId } from '@kbn/securitysolution-data-table';
@@ -174,9 +174,11 @@ export const useBulkAlertActionItems = ({
[getOnAction]
);
- return hasIndexWrite
- ? [FILTER_OPEN, FILTER_CLOSED, FILTER_ACKNOWLEDGED].map((status) =>
- getUpdateAlertStatusAction(status as AlertWorkflowStatus)
- )
- : [];
+ return useMemo(() => {
+ return hasIndexWrite
+ ? [FILTER_OPEN, FILTER_CLOSED, FILTER_ACKNOWLEDGED].map((status) => {
+ return getUpdateAlertStatusAction(status as AlertWorkflowStatus);
+ })
+ : [];
+ }, [getUpdateAlertStatusAction, hasIndexWrite]);
};
diff --git a/x-pack/plugins/security_solution/public/detections/hooks/trigger_actions_alert_table/use_bulk_actions.tsx b/x-pack/plugins/security_solution/public/detections/hooks/trigger_actions_alert_table/use_bulk_actions.tsx
index 0ea04d0df93f2..03e2655ff4047 100644
--- a/x-pack/plugins/security_solution/public/detections/hooks/trigger_actions_alert_table/use_bulk_actions.tsx
+++ b/x-pack/plugins/security_solution/public/detections/hooks/trigger_actions_alert_table/use_bulk_actions.tsx
@@ -10,13 +10,10 @@ import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/type
import type { SerializableRecord } from '@kbn/utility-types';
import { isEqual } from 'lodash';
import type { Filter } from '@kbn/es-query';
-import { useCallback, useMemo } from 'react';
+import { useMemo } from 'react';
import type { TableId } from '@kbn/securitysolution-data-table';
import { useBulkAlertAssigneesItems } from '../../../common/components/toolbar/bulk_actions/use_bulk_alert_assignees_items';
import { useBulkAlertTagsItems } from '../../../common/components/toolbar/bulk_actions/use_bulk_alert_tags_items';
-import type { inputsModel, State } from '../../../common/store';
-import { useShallowEqualSelector } from '../../../common/hooks/use_selector';
-import { inputsSelectors } from '../../../common/store';
import { SourcererScopeName } from '../../../common/store/sourcerer/model';
import { useGlobalTime } from '../../../common/containers/use_global_time';
import { useAddBulkToTimelineAction } from '../../components/alerts_table/timeline_actions/use_add_bulk_to_timeline';
@@ -62,43 +59,56 @@ function getFiltersForDSLQuery(datafeedQuery: QueryDslQueryContainer): Filter[]
export const getBulkActionHook =
(tableId: TableId): AlertsTableConfigurationRegistry['useBulkActions'] =>
- (query) => {
+ (query, refresh) => {
const { from, to } = useGlobalTime();
- const filters = getFiltersForDSLQuery(query);
- const getGlobalQueries = useMemo(() => inputsSelectors.globalQuery(), []);
+ const filters = useMemo(() => {
+ return getFiltersForDSLQuery(query);
+ }, [query]);
+ const assigneeProps = useMemo(() => {
+ return {
+ onAssigneesUpdate: refresh,
+ };
+ }, [refresh]);
- const globalQuery = useShallowEqualSelector((state: State) => getGlobalQueries(state));
+ const { alertAssigneesItems, alertAssigneesPanels } = useBulkAlertAssigneesItems(assigneeProps);
- const refetchGlobalQuery = useCallback(() => {
- globalQuery.forEach((q) => q.refetch && (q.refetch as inputsModel.Refetch)());
- }, [globalQuery]);
+ const timelineActionParams = useMemo(() => {
+ return {
+ localFilters: filters,
+ from,
+ to,
+ scopeId: SourcererScopeName.detections,
+ tableId,
+ };
+ }, [filters, from, to]);
- const timelineAction = useAddBulkToTimelineAction({
- localFilters: filters,
- from,
- to,
- scopeId: SourcererScopeName.detections,
- tableId,
- });
+ const alertActionParams = useMemo(() => {
+ return {
+ scopeId: SourcererScopeName.detections,
+ filters,
+ from,
+ to,
+ tableId,
+ refetch: refresh,
+ };
+ }, [from, to, filters, refresh]);
- const alertActions = useBulkAlertActionItems({
- scopeId: SourcererScopeName.detections,
- filters,
- from,
- to,
- tableId,
- refetch: refetchGlobalQuery,
- });
+ const bulkAlertTagParams = useMemo(() => {
+ return {
+ refetch: refresh,
+ };
+ }, [refresh]);
- const { alertTagsItems, alertTagsPanels } = useBulkAlertTagsItems({
- refetch: refetchGlobalQuery,
- });
+ const timelineAction = useAddBulkToTimelineAction(timelineActionParams);
- const { alertAssigneesItems, alertAssigneesPanels } = useBulkAlertAssigneesItems({
- onAssigneesUpdate: refetchGlobalQuery,
- });
+ const alertActions = useBulkAlertActionItems(alertActionParams);
- const items = [...alertActions, timelineAction, ...alertTagsItems, ...alertAssigneesItems];
+ const { alertTagsItems, alertTagsPanels } = useBulkAlertTagsItems(bulkAlertTagParams);
- return [{ id: 0, items }, ...alertTagsPanels, ...alertAssigneesPanels];
+ const items = useMemo(() => {
+ return [...alertActions, timelineAction, ...alertTagsItems, ...alertAssigneesItems];
+ }, [alertActions, alertTagsItems, timelineAction, alertAssigneesItems]);
+ return useMemo(() => {
+ return [{ id: 0, items }, ...alertTagsPanels, ...alertAssigneesPanels];
+ }, [alertTagsPanels, items, alertAssigneesPanels]);
};
diff --git a/x-pack/plugins/security_solution/public/detections/hooks/trigger_actions_alert_table/use_cell_actions.tsx b/x-pack/plugins/security_solution/public/detections/hooks/trigger_actions_alert_table/use_cell_actions.tsx
index a36678841a904..f1d1de98a0ea3 100644
--- a/x-pack/plugins/security_solution/public/detections/hooks/trigger_actions_alert_table/use_cell_actions.tsx
+++ b/x-pack/plugins/security_solution/public/detections/hooks/trigger_actions_alert_table/use_cell_actions.tsx
@@ -107,10 +107,12 @@ export const getUseCellActionsHook = (tableId: TableId) => {
[cellActions]
);
- return {
- getCellActions,
- visibleCellActions: 3,
- };
+ return useMemo(() => {
+ return {
+ getCellActions,
+ visibleCellActions: 3,
+ };
+ }, [getCellActions]);
};
return useCellActions;
diff --git a/x-pack/plugins/security_solution/public/detections/hooks/trigger_actions_alert_table/use_persistent_controls.tsx b/x-pack/plugins/security_solution/public/detections/hooks/trigger_actions_alert_table/use_persistent_controls.tsx
index 4f4db100f0d05..f662914c870e9 100644
--- a/x-pack/plugins/security_solution/public/detections/hooks/trigger_actions_alert_table/use_persistent_controls.tsx
+++ b/x-pack/plugins/security_solution/public/detections/hooks/trigger_actions_alert_table/use_persistent_controls.tsx
@@ -129,9 +129,7 @@ export const getPersistentControlsHook = (tableId: TableId) => {
[tableView, handleChangeTableView, additionalFiltersComponent, groupSelector]
);
- return {
- right: rightTopMenu,
- };
+ return useMemo(() => ({ right: rightTopMenu }), [rightTopMenu]);
};
return usePersistentControls;
diff --git a/x-pack/plugins/security_solution/public/detections/hooks/trigger_actions_alert_table/use_trigger_actions_browser_fields_options.tsx b/x-pack/plugins/security_solution/public/detections/hooks/trigger_actions_alert_table/use_trigger_actions_browser_fields_options.tsx
index 443dde3fd6ee8..c2140c904c464 100644
--- a/x-pack/plugins/security_solution/public/detections/hooks/trigger_actions_alert_table/use_trigger_actions_browser_fields_options.tsx
+++ b/x-pack/plugins/security_solution/public/detections/hooks/trigger_actions_alert_table/use_trigger_actions_browser_fields_options.tsx
@@ -4,7 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
-
+import { useCallback, useMemo } from 'react';
import type { AlertsTableConfigurationRegistry } from '@kbn/triggers-actions-ui-plugin/public/types';
import { useFieldBrowserOptions } from '../../../timelines/components/fields_browser';
import type { SourcererScopeName } from '../../../common/store/sourcerer/model';
@@ -12,17 +12,26 @@ import type { SourcererScopeName } from '../../../common/store/sourcerer/model';
export const getUseTriggersActionsFieldBrowserOptions = (scopeId: SourcererScopeName) => {
const useTriggersActionsFieldBrowserOptions: AlertsTableConfigurationRegistry['useFieldBrowserOptions'] =
({ onToggleColumn }) => {
- const options = useFieldBrowserOptions({
- sourcererScope: scopeId,
- removeColumn: onToggleColumn,
- upsertColumn: (column) => {
+ const upsertColumn = useCallback(
+ (column) => {
onToggleColumn(column.id);
},
- });
+ [onToggleColumn]
+ );
+ const fieldBrowserArgs = useMemo(() => {
+ return {
+ sourcererScope: scopeId,
+ removeColumn: onToggleColumn,
+ upsertColumn,
+ };
+ }, [upsertColumn, onToggleColumn]);
+ const options = useFieldBrowserOptions(fieldBrowserArgs);
- return {
- createFieldButton: options.createFieldButton,
- };
+ return useMemo(() => {
+ return {
+ createFieldButton: options.createFieldButton,
+ };
+ }, [options.createFieldButton]);
};
return useTriggersActionsFieldBrowserOptions;
diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_update_timeline.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_update_timeline.test.tsx
index 4b6c3c053d0da..10088446c045c 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_update_timeline.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_update_timeline.test.tsx
@@ -5,7 +5,8 @@
* 2.0.
*/
-import { mockTimelineModel } from '../../../common/mock';
+import { renderHook, act } from '@testing-library/react-hooks';
+import { mockTimelineModel, TestProviders } from '../../../common/mock';
import { setTimelineRangeDatePicker as dispatchSetTimelineRangeDatePicker } from '../../../common/store/inputs/actions';
import {
applyKqlFilterQuery as dispatchApplyKqlFilterQuery,
@@ -17,7 +18,6 @@ import {
updateNote as dispatchUpdateNote,
} from '../../../common/store/app/actions';
import { useUpdateTimeline } from './use_update_timeline';
-import type { DispatchUpdateTimeline } from './types';
import type { Note } from '../../../common/lib/note';
import moment from 'moment';
import sinon from 'sinon';
@@ -66,7 +66,6 @@ describe('dispatchUpdateTimeline', () => {
const anchor = '2020-03-27T20:34:51.337Z';
const unix = moment(anchor).valueOf();
let clock: sinon.SinonFakeTimers;
- let timelineDispatch: DispatchUpdateTimeline;
const defaultArgs = {
duplicate: true,
@@ -81,149 +80,196 @@ describe('dispatchUpdateTimeline', () => {
jest.clearAllMocks();
clock = sinon.useFakeTimers(unix);
- timelineDispatch = useUpdateTimeline();
});
afterEach(function () {
clock.restore();
});
- test('it invokes date range picker dispatch', () => {
- timelineDispatch(defaultArgs);
+ it('it invokes date range picker dispatch', async () => {
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() => useUpdateTimeline(), {
+ wrapper: TestProviders,
+ });
+ await waitForNextUpdate();
+ result.current(defaultArgs);
- expect(dispatchSetTimelineRangeDatePicker).toHaveBeenCalledWith({
- from: '2020-03-26T14:35:56.356Z',
- to: '2020-03-26T14:41:56.356Z',
+ expect(dispatchSetTimelineRangeDatePicker).toHaveBeenCalledWith({
+ from: '2020-03-26T14:35:56.356Z',
+ to: '2020-03-26T14:41:56.356Z',
+ });
});
});
- test('it invokes add timeline dispatch', () => {
- timelineDispatch(defaultArgs);
+ it('it invokes add timeline dispatch', async () => {
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() => useUpdateTimeline(), {
+ wrapper: TestProviders,
+ });
+ await waitForNextUpdate();
+ result.current(defaultArgs);
- expect(dispatchAddTimeline).toHaveBeenCalledWith({
- id: TimelineId.active,
- savedTimeline: true,
- timeline: {
- ...mockTimelineModel,
- version: null,
- updated: undefined,
- changed: undefined,
- },
+ expect(dispatchAddTimeline).toHaveBeenCalledWith({
+ id: TimelineId.active,
+ savedTimeline: true,
+ timeline: {
+ ...mockTimelineModel,
+ version: null,
+ updated: undefined,
+ changed: undefined,
+ },
+ });
});
});
- test('it does not invoke kql filter query dispatches if timeline.kqlQuery.filterQuery is null', () => {
- timelineDispatch(defaultArgs);
+ it('it does not invoke kql filter query dispatches if timeline.kqlQuery.filterQuery is null', async () => {
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() => useUpdateTimeline(), {
+ wrapper: TestProviders,
+ });
+ await waitForNextUpdate();
+ result.current(defaultArgs);
- expect(dispatchApplyKqlFilterQuery).not.toHaveBeenCalled();
+ expect(dispatchApplyKqlFilterQuery).not.toHaveBeenCalled();
+ });
});
- test('it does not invoke notes dispatch if duplicate is true', () => {
- timelineDispatch(defaultArgs);
+ it('it does not invoke notes dispatch if duplicate is true', async () => {
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() => useUpdateTimeline(), {
+ wrapper: TestProviders,
+ });
+ await waitForNextUpdate();
+ result.current(defaultArgs);
- expect(dispatchAddNotes).not.toHaveBeenCalled();
+ expect(dispatchAddNotes).not.toHaveBeenCalled();
+ });
});
- test('it does not invoke kql filter query dispatches if timeline.kqlQuery.kuery is null', () => {
- const mockTimeline = {
- ...mockTimelineModel,
- kqlQuery: {
- filterQuery: {
- kuery: null,
- serializedQuery: 'some-serialized-query',
+ it('it does not invoke kql filter query dispatches if timeline.kqlQuery.kuery is null', async () => {
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() => useUpdateTimeline(), {
+ wrapper: TestProviders,
+ });
+ await waitForNextUpdate();
+ const mockTimeline = {
+ ...mockTimelineModel,
+ kqlQuery: {
+ filterQuery: {
+ kuery: null,
+ serializedQuery: 'some-serialized-query',
+ },
},
- },
- };
- timelineDispatch({
- ...defaultArgs,
- timeline: mockTimeline,
- });
+ };
+ result.current({
+ ...defaultArgs,
+ timeline: mockTimeline,
+ });
- expect(dispatchApplyKqlFilterQuery).not.toHaveBeenCalled();
+ expect(dispatchApplyKqlFilterQuery).not.toHaveBeenCalled();
+ });
});
- test('it invokes kql filter query dispatches if timeline.kqlQuery.filterQuery.kuery is not null', () => {
- const mockTimeline = {
- ...mockTimelineModel,
- kqlQuery: {
- filterQuery: {
- kuery: { expression: 'expression', kind: 'kuery' as KueryFilterQueryKind },
- serializedQuery: 'some-serialized-query',
+ it('it invokes kql filter query dispatches if timeline.kqlQuery.filterQuery.kuery is not null', async () => {
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() => useUpdateTimeline(), {
+ wrapper: TestProviders,
+ });
+ await waitForNextUpdate();
+ const mockTimeline = {
+ ...mockTimelineModel,
+ kqlQuery: {
+ filterQuery: {
+ kuery: { expression: 'expression', kind: 'kuery' as KueryFilterQueryKind },
+ serializedQuery: 'some-serialized-query',
+ },
},
- },
- };
- timelineDispatch({
- ...defaultArgs,
- timeline: mockTimeline,
- });
+ };
+ result.current({
+ ...defaultArgs,
+ timeline: mockTimeline,
+ });
- expect(dispatchApplyKqlFilterQuery).toHaveBeenCalledWith({
- id: TimelineId.active,
- filterQuery: {
- kuery: {
- kind: 'kuery',
- expression: 'expression',
+ expect(dispatchApplyKqlFilterQuery).toHaveBeenCalledWith({
+ id: TimelineId.active,
+ filterQuery: {
+ kuery: {
+ kind: 'kuery',
+ expression: 'expression',
+ },
+ serializedQuery: 'some-serialized-query',
},
- serializedQuery: 'some-serialized-query',
- },
+ });
});
});
- test('it invokes dispatchAddNotes if duplicate is false', () => {
- timelineDispatch({
- ...defaultArgs,
- duplicate: false,
- notes: [
- {
- created: 1585233356356,
- updated: 1585233356356,
- noteId: 'note-id',
- note: 'I am a note',
- timelineId: 'abc',
- version: 'testVersion',
- },
- ],
- });
+ it('it invokes dispatchAddNotes if duplicate is false', async () => {
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() => useUpdateTimeline(), {
+ wrapper: TestProviders,
+ });
+ await waitForNextUpdate();
+ result.current({
+ ...defaultArgs,
+ duplicate: false,
+ notes: [
+ {
+ created: 1585233356356,
+ updated: 1585233356356,
+ noteId: 'note-id',
+ note: 'I am a note',
+ timelineId: 'abc',
+ version: 'testVersion',
+ },
+ ],
+ });
- expect(dispatchAddGlobalTimelineNote).not.toHaveBeenCalled();
- expect(dispatchUpdateNote).not.toHaveBeenCalled();
- expect(dispatchAddNotes).toHaveBeenCalledWith({
- notes: [
- {
- created: new Date('2020-03-26T14:35:56.356Z'),
- eventId: null,
- id: 'note-id',
- lastEdit: new Date('2020-03-26T14:35:56.356Z'),
- note: 'I am a note',
- user: 'unknown',
- saveObjectId: 'note-id',
- timelineId: 'abc',
- version: 'testVersion',
- },
- ],
+ expect(dispatchAddGlobalTimelineNote).not.toHaveBeenCalled();
+ expect(dispatchUpdateNote).not.toHaveBeenCalled();
+ expect(dispatchAddNotes).toHaveBeenCalledWith({
+ notes: [
+ {
+ created: new Date('2020-03-26T14:35:56.356Z'),
+ eventId: null,
+ id: 'note-id',
+ lastEdit: new Date('2020-03-26T14:35:56.356Z'),
+ note: 'I am a note',
+ user: 'unknown',
+ saveObjectId: 'note-id',
+ timelineId: 'abc',
+ version: 'testVersion',
+ },
+ ],
+ });
});
});
- test('it invokes dispatch to create a timeline note if duplicate is true and ruleNote exists', () => {
- timelineDispatch({
- ...defaultArgs,
- ruleNote: '# this would be some markdown',
- });
- const expectedNote: Note = {
- created: new Date(anchor),
- id: 'uuidv4()',
- lastEdit: null,
- note: '# this would be some markdown',
- saveObjectId: null,
- user: 'elastic',
- version: null,
- };
-
- expect(dispatchAddNotes).not.toHaveBeenCalled();
- expect(dispatchUpdateNote).toHaveBeenCalledWith({ note: expectedNote });
- expect(dispatchAddGlobalTimelineNote).toHaveBeenLastCalledWith({
- id: TimelineId.active,
- noteId: 'uuidv4()',
+ it('it invokes dispatch to create a timeline note if duplicate is true and ruleNote exists', async () => {
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() => useUpdateTimeline(), {
+ wrapper: TestProviders,
+ });
+ await waitForNextUpdate();
+ result.current({
+ ...defaultArgs,
+ ruleNote: '# this would be some markdown',
+ });
+ const expectedNote: Note = {
+ created: new Date(anchor),
+ id: 'uuidv4()',
+ lastEdit: null,
+ note: '# this would be some markdown',
+ saveObjectId: null,
+ user: 'elastic',
+ version: null,
+ };
+
+ expect(dispatchAddNotes).not.toHaveBeenCalled();
+ expect(dispatchUpdateNote).toHaveBeenCalledWith({ note: expectedNote });
+ expect(dispatchAddGlobalTimelineNote).toHaveBeenLastCalledWith({
+ id: TimelineId.active,
+ noteId: 'uuidv4()',
+ });
});
});
});
diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_update_timeline.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_update_timeline.tsx
index 4d598eb5ba4bb..361d7217fd9ee 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_update_timeline.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_update_timeline.tsx
@@ -5,6 +5,7 @@
* 2.0.
*/
+import { useCallback } from 'react';
import { useDispatch } from 'react-redux';
import { isEmpty } from 'lodash/fp';
import type { Note } from '../../../../common/api/timeline';
@@ -36,102 +37,105 @@ import type { UpdateTimeline } from './types';
export const useUpdateTimeline = () => {
const dispatch = useDispatch();
- return ({
- duplicate,
- id,
- forceNotes = false,
- from,
- notes,
- resolveTimelineConfig,
- timeline,
- to,
- ruleNote,
- ruleAuthor,
- preventSettingQuery,
- }: UpdateTimeline) => {
- let _timeline = timeline;
- if (duplicate) {
- _timeline = { ...timeline, updated: undefined, changed: undefined, version: null };
- }
- if (!isEmpty(_timeline.indexNames)) {
+ return useCallback(
+ ({
+ duplicate,
+ id,
+ forceNotes = false,
+ from,
+ notes,
+ resolveTimelineConfig,
+ timeline,
+ to,
+ ruleNote,
+ ruleAuthor,
+ preventSettingQuery,
+ }: UpdateTimeline) => {
+ let _timeline = timeline;
+ if (duplicate) {
+ _timeline = { ...timeline, updated: undefined, changed: undefined, version: null };
+ }
+ if (!isEmpty(_timeline.indexNames)) {
+ dispatch(
+ sourcererActions.setSelectedDataView({
+ id: SourcererScopeName.timeline,
+ selectedDataViewId: _timeline.dataViewId,
+ selectedPatterns: _timeline.indexNames,
+ })
+ );
+ }
+ if (
+ _timeline.status === TimelineStatus.immutable &&
+ _timeline.timelineType === TimelineType.template
+ ) {
+ dispatch(
+ dispatchSetRelativeRangeDatePicker({
+ id: InputsModelId.timeline,
+ fromStr: 'now-24h',
+ toStr: 'now',
+ from: DEFAULT_FROM_MOMENT.toISOString(),
+ to: DEFAULT_TO_MOMENT.toISOString(),
+ })
+ );
+ } else {
+ dispatch(dispatchSetTimelineRangeDatePicker({ from, to }));
+ }
dispatch(
- sourcererActions.setSelectedDataView({
- id: SourcererScopeName.timeline,
- selectedDataViewId: _timeline.dataViewId,
- selectedPatterns: _timeline.indexNames,
- })
- );
- }
- if (
- _timeline.status === TimelineStatus.immutable &&
- _timeline.timelineType === TimelineType.template
- ) {
- dispatch(
- dispatchSetRelativeRangeDatePicker({
- id: InputsModelId.timeline,
- fromStr: 'now-24h',
- toStr: 'now',
- from: DEFAULT_FROM_MOMENT.toISOString(),
- to: DEFAULT_TO_MOMENT.toISOString(),
- })
- );
- } else {
- dispatch(dispatchSetTimelineRangeDatePicker({ from, to }));
- }
- dispatch(
- dispatchAddTimeline({
- id,
- timeline: _timeline,
- resolveTimelineConfig,
- savedTimeline: duplicate,
- })
- );
- if (
- !preventSettingQuery &&
- _timeline.kqlQuery != null &&
- _timeline.kqlQuery.filterQuery != null &&
- _timeline.kqlQuery.filterQuery.kuery != null &&
- _timeline.kqlQuery.filterQuery.kuery.expression !== ''
- ) {
- dispatch(
- dispatchApplyKqlFilterQuery({
+ dispatchAddTimeline({
id,
- filterQuery: {
- kuery: {
- kind: _timeline.kqlQuery.filterQuery.kuery.kind ?? 'kuery',
- expression: _timeline.kqlQuery.filterQuery.kuery.expression || '',
- },
- serializedQuery: _timeline.kqlQuery.filterQuery.serializedQuery || '',
- },
+ timeline: _timeline,
+ resolveTimelineConfig,
+ savedTimeline: duplicate,
})
);
- }
+ if (
+ !preventSettingQuery &&
+ _timeline.kqlQuery != null &&
+ _timeline.kqlQuery.filterQuery != null &&
+ _timeline.kqlQuery.filterQuery.kuery != null &&
+ _timeline.kqlQuery.filterQuery.kuery.expression !== ''
+ ) {
+ dispatch(
+ dispatchApplyKqlFilterQuery({
+ id,
+ filterQuery: {
+ kuery: {
+ kind: _timeline.kqlQuery.filterQuery.kuery.kind ?? 'kuery',
+ expression: _timeline.kqlQuery.filterQuery.kuery.expression || '',
+ },
+ serializedQuery: _timeline.kqlQuery.filterQuery.serializedQuery || '',
+ },
+ })
+ );
+ }
- if (duplicate && ruleNote != null && !isEmpty(ruleNote)) {
- const newNote = createNote({ newNote: ruleNote, user: ruleAuthor || 'elastic' });
- dispatch(dispatchUpdateNote({ note: newNote }));
- dispatch(dispatchAddGlobalTimelineNote({ noteId: newNote.id, id }));
- }
+ if (duplicate && ruleNote != null && !isEmpty(ruleNote)) {
+ const newNote = createNote({ newNote: ruleNote, user: ruleAuthor || 'elastic' });
+ dispatch(dispatchUpdateNote({ note: newNote }));
+ dispatch(dispatchAddGlobalTimelineNote({ noteId: newNote.id, id }));
+ }
- if (!duplicate || forceNotes) {
- dispatch(
- dispatchAddNotes({
- notes:
- notes != null
- ? notes.map((note: Note) => ({
- created: note.created != null ? new Date(note.created) : new Date(),
- id: note.noteId,
- lastEdit: note.updated != null ? new Date(note.updated) : new Date(),
- note: note.note || '',
- user: note.updatedBy || 'unknown',
- saveObjectId: note.noteId,
- version: note.version,
- eventId: note.eventId ?? null,
- timelineId: note.timelineId ?? null,
- }))
- : [],
- })
- );
- }
- };
+ if (!duplicate || forceNotes) {
+ dispatch(
+ dispatchAddNotes({
+ notes:
+ notes != null
+ ? notes.map((note: Note) => ({
+ created: note.created != null ? new Date(note.created) : new Date(),
+ id: note.noteId,
+ lastEdit: note.updated != null ? new Date(note.updated) : new Date(),
+ note: note.note || '',
+ user: note.updatedBy || 'unknown',
+ saveObjectId: note.noteId,
+ version: note.version,
+ eventId: note.eventId ?? null,
+ timelineId: note.timelineId ?? null,
+ }))
+ : [],
+ })
+ );
+ }
+ },
+ [dispatch]
+ );
};
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap
index 94270d2280cc5..dc31ad380635b 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap
@@ -806,7 +806,11 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = `
"headerCellRender": Object {
"$$typeof": Symbol(react.memo),
"compare": null,
- "type": [Function],
+ "type": Object {
+ "$$typeof": Symbol(react.memo),
+ "compare": null,
+ "type": [Function],
+ },
},
"id": "default-timeline-control-column",
"rowCellRender": Object {
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/alert_table_config_registry.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/alert_table_config_registry.test.ts
index 1f3b2525aec14..2910a6ee6b37f 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/alert_table_config_registry.test.ts
+++ b/x-pack/plugins/triggers_actions_ui/public/application/alert_table_config_registry.test.ts
@@ -112,7 +112,7 @@ describe('update()', () => {
"id": "test-alert-table-config",
}
`);
- expect(alertTableConfigRegistry.getActions('test-alert-table-config').toggleColumn).toEqual(
+ expect(alertTableConfigRegistry.getActions('test-alert-table-config')?.toggleColumn).toEqual(
toggleColumn
);
});
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/alert_table_config_registry.ts b/x-pack/plugins/triggers_actions_ui/public/application/alert_table_config_registry.ts
index 37b2ddbd3b4e0..76e0dc5b39a38 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/alert_table_config_registry.ts
+++ b/x-pack/plugins/triggers_actions_ui/public/application/alert_table_config_registry.ts
@@ -8,16 +8,10 @@
import { i18n } from '@kbn/i18n';
import { noop } from 'lodash';
import { ALERT_TABLE_GENERIC_CONFIG_ID } from './constants';
-import {
- AlertsTableConfigurationRegistry,
- AlertsTableConfigurationRegistryWithActions,
-} from '../types';
+import { AlertsTableConfigurationRegistry } from '../types';
export class AlertTableConfigRegistry {
- private readonly objectTypes: Map<
- string,
- AlertsTableConfigurationRegistry | AlertsTableConfigurationRegistryWithActions
- > = new Map();
+ private readonly objectTypes: Map = new Map();
/**
* Returns if the object type registry has the given type registered
@@ -63,9 +57,9 @@ export class AlertTableConfigRegistry {
return this.objectTypes.get(id)!;
}
- public getActions(id: string): AlertsTableConfigurationRegistryWithActions['actions'] {
+ public getActions(id: string): AlertsTableConfigurationRegistry['actions'] {
return (
- (this.objectTypes.get(id) as AlertsTableConfigurationRegistryWithActions)?.actions ?? {
+ (this.objectTypes.get(id) as AlertsTableConfigurationRegistry)?.actions ?? {
toggleColumn: noop,
}
);
@@ -78,7 +72,7 @@ export class AlertTableConfigRegistry {
/**
* Returns an object type, throw error if not registered
*/
- public update(id: string, objectType: AlertsTableConfigurationRegistryWithActions) {
+ public update(id: string, objectType: AlertsTableConfigurationRegistry) {
if (!this.has(id)) {
throw new Error(
i18n.translate('xpack.triggersActionsUI.typeRegistry.get.missingActionTypeErrorMessage', {
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table.test.tsx
index d264ad4d7b220..9d29e452bf9a3 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table.test.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table.test.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
import React, { useMemo, useReducer } from 'react';
-
+import { identity } from 'lodash';
import { fireEvent, render, screen, within, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { waitForEuiPopoverOpen } from '@elastic/eui/lib/test/rtl';
@@ -16,6 +16,7 @@ import {
ALERT_STATUS,
ALERT_CASE_IDS,
} from '@kbn/rule-data-utils';
+import { FieldFormatsRegistry } from '@kbn/field-formats-plugin/common';
import { AlertsTable } from './alerts_table';
import {
AlertsField,
@@ -40,6 +41,13 @@ import { AlertsTableContext, AlertsTableQueryContext } from './contexts/alerts_t
const mockCaseService = createCasesServiceMock();
+const mockFieldFormatsRegistry = {
+ deserialize: jest.fn().mockImplementation(() => ({
+ id: 'string',
+ convert: jest.fn().mockImplementation(identity),
+ })),
+} as unknown as FieldFormatsRegistry;
+
jest.mock('@kbn/data-plugin/public');
jest.mock('@kbn/kibana-react-plugin/public/ui_settings/use_ui_setting', () => ({
useUiSetting$: jest.fn((value: string) => ['0,0']),
@@ -213,25 +221,6 @@ afterAll(() => {
});
describe('AlertsTable', () => {
- const fetchAlertsData = {
- activePage: 0,
- alerts,
- alertsCount: alerts.length,
- isInitializing: false,
- isLoading: false,
- getInspectQuery: jest.fn().mockImplementation(() => ({ request: {}, response: {} })),
- onPageChange: jest.fn(),
- onSortChange: jest.fn(),
- refresh: jest.fn(),
- sort: [],
- ecsAlertsData,
- oldAlertsData,
- };
-
- const useFetchAlertsData = () => {
- return fetchAlertsData;
- };
-
const alertsTableConfiguration: AlertsTableConfigurationRegistry = {
id: '',
columns,
@@ -266,6 +255,22 @@ describe('AlertsTable', () => {
),
};
},
+ useActionsColumn: () => {
+ return {
+ renderCustomActionsRow: () => (
+
+ {}}
+ size="s"
+ data-test-subj="fake-action"
+ aria-label="fake-action"
+ />
+
+ ),
+ };
+ },
};
const browserFields: BrowserFields = {
@@ -297,19 +302,28 @@ describe('AlertsTable', () => {
columns,
deletedEventIds: [],
disabledCellActions: [],
- pageSize: 1,
pageSizeOptions: [1, 10, 20, 50, 100],
leadingControlColumns: [],
trailingControlColumns: [],
- useFetchAlertsData,
visibleColumns: columns.map((c) => c.id),
'data-test-subj': 'testTable',
- updatedAt: Date.now(),
onToggleColumn: () => {},
onResetColumns: () => {},
onChangeVisibleColumns: () => {},
browserFields,
query: {},
+ pagination: { pageIndex: 0, pageSize: 1 },
+ sort: [],
+ isLoading: false,
+ alerts,
+ oldAlertsData,
+ ecsAlertsData,
+ getInspectQuery: () => ({ request: [], response: [] }),
+ refetch: () => {},
+ alertsCount: alerts.length,
+ onSortChange: jest.fn(),
+ onPageChange: jest.fn(),
+ fieldFormats: mockFieldFormatsRegistry,
};
const defaultBulkActionsState = {
@@ -317,6 +331,7 @@ describe('AlertsTable', () => {
isAllSelected: false,
areAllVisibleRowsSelected: false,
rowCount: 4,
+ updatedAt: Date.now(),
};
const useCaseViewNavigationMock = useCaseViewNavigation as jest.Mock;
@@ -376,19 +391,20 @@ describe('AlertsTable', () => {
skipPointerEventsCheck: true,
});
- expect(fetchAlertsData.onSortChange).toHaveBeenCalledWith([
+ expect(tableProps.onSortChange).toHaveBeenCalledWith([
{ direction: 'asc', id: 'kibana.alert.rule.name' },
]);
});
it('should support pagination', async () => {
- const renderResult = render();
-
+ const renderResult = render(
+
+ );
userEvent.click(renderResult.getByTestId('pagination-button-1'), undefined, {
skipPointerEventsCheck: true,
});
- expect(fetchAlertsData.onPageChange).toHaveBeenCalledWith({ pageIndex: 1, pageSize: 1 });
+ expect(tableProps.onPageChange).toHaveBeenCalledWith({ pageIndex: 1, pageSize: 1 });
});
it('should show when it was updated', () => {
@@ -405,7 +421,7 @@ describe('AlertsTable', () => {
const props = {
...tableProps,
showAlertStatusWithFlapping: true,
- pageSize: alerts.length,
+ pagination: { pageIndex: 0, pageSize: 10 },
alertsTableConfiguration: {
...alertsTableConfiguration,
getRenderCellValue: undefined,
@@ -431,6 +447,7 @@ describe('AlertsTable', () => {
rowCellRender: () => Test cell
,
},
],
+ pagination: { pageIndex: 0, pageSize: 1 },
};
const wrapper = render();
expect(wrapper.queryByTestId('testHeader')).not.toBe(null);
@@ -529,6 +546,10 @@ describe('AlertsTable', () => {
it('should render no action column if there is neither the action nor the expand action config is set', () => {
const customTableProps = {
...tableProps,
+ alertsTableConfiguration: {
+ ...alertsTableConfiguration,
+ useActionsColumn: undefined,
+ },
};
const { queryByTestId } = render();
@@ -544,7 +565,7 @@ describe('AlertsTable', () => {
mockedFn = jest.fn();
customTableProps = {
...tableProps,
- pageSize: 2,
+ pagination: { pageIndex: 0, pageSize: 10 },
alertsTableConfiguration: {
...alertsTableConfiguration,
useActionsColumn: () => {
@@ -623,7 +644,6 @@ describe('AlertsTable', () => {
beforeEach(() => {
customTableProps = {
...tableProps,
- pageSize: 2,
alertsTableConfiguration: {
...alertsTableConfiguration,
useCellActions: mockedUseCellActions,
@@ -692,13 +712,19 @@ describe('AlertsTable', () => {
});
it('should show the cases titles correctly', async () => {
- render();
+ render();
expect(await screen.findByText('Test case')).toBeInTheDocument();
expect(await screen.findByText('Test case 2')).toBeInTheDocument();
});
it('show loading skeleton if it loads cases', async () => {
- render();
+ render(
+
+ );
expect((await screen.findAllByTestId('cases-cell-loading')).length).toBe(4);
});
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table.tsx
index b30d8613c080a..b87633ad22b54 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table.tsx
@@ -18,12 +18,12 @@ import React, {
} from 'react';
import {
EuiDataGrid,
- EuiDataGridCellValueElementProps,
EuiDataGridStyle,
EuiSkeletonText,
EuiDataGridRefProps,
EuiFlexGroup,
EuiDataGridProps,
+ RenderCellValue,
EuiDataGridCellPopoverElementProps,
EuiCodeBlock,
EuiText,
@@ -37,11 +37,15 @@ import { RuleRegistrySearchRequestPagination } from '@kbn/rule-registry-plugin/c
import { FormattedMessage } from '@kbn/i18n-react';
import { css } from '@emotion/react';
import { useSorting, usePagination, useBulkActions, useActionsColumn } from './hooks';
-import { AlertsTableProps, FetchAlertData } from '../../../types';
+import type {
+ AlertsTableProps,
+ FetchAlertData,
+ AlertsTableConfigurationRegistry,
+} from '../../../types';
import { ALERTS_TABLE_CONTROL_COLUMNS_ACTIONS_LABEL } from './translations';
import './alerts_table.scss';
-import { getToolbarVisibility } from './toolbar';
+import { useGetToolbarVisibility } from './toolbar';
import { InspectButtonContainer } from './toolbar/components/inspect';
import { SystemCellId } from './types';
import { SystemCellFactory, systemCells } from './cells';
@@ -61,25 +65,163 @@ const getCellActionsStub = {
disabledCellActions: [],
};
-const basicRenderCellValue = ({
- data,
- columnId,
-}: {
+const fieldBrowserStub = () => ({});
+const stableMappedRowClasses: EuiDataGridStyle['rowClasses'] = {};
+
+const BasicRenderCellValue: React.FC<{
data: Array<{ field: string; value: string[] }>;
- ecsData?: FetchAlertData['ecsAlertsData'][number];
columnId: string;
-}) => {
- const value = data.find((d) => d.field === columnId)?.value ?? [];
+}> = memo(({ data, columnId }) => {
+ const value = (Array.isArray(data) && data.find((d) => d.field === columnId)?.value) ?? [];
if (Array.isArray(value)) {
return <>{value.length ? value.join() : '--'}>;
}
return <>{value}>;
-};
+});
+
+const FullFeaturedRenderCellValue: RenderCellValue = memo((props) => {
+ const {
+ columnId,
+ cases,
+ maintenanceWindows,
+ showAlertStatusWithFlapping,
+ isLoading,
+ isLoadingCases,
+ isLoadingMaintenanceWindows,
+ casesConfig,
+ rowIndex,
+ pagination,
+ RenderCell,
+ ecsData,
+ alerts,
+ } = props;
+ const idx = rowIndex - pagination.pageSize * pagination.pageIndex;
+ const alert = alerts[idx];
+ if (isSystemCell(columnId)) {
+ return (
+
+ );
+ } else if (alert) {
+ // ecsAlert is needed for security solution
+ const ecsAlert = ecsData[idx];
+ const data: Array<{ field: string; value: string[] }> = [];
+ Object.entries(alert ?? {}).forEach(([key, value]) => {
+ data.push({ field: key, value: value as string[] });
+ });
+ if (RenderCell && ecsAlert) {
+ return ;
+ } else {
+ return ;
+ }
+ } else if (isLoading) {
+ return ;
+ }
+ return null;
+});
+
+const ControlColumnHeaderRenderCell = memo(() => {
+ return (
+
+ {ALERTS_TABLE_CONTROL_COLUMNS_ACTIONS_LABEL}
+
+ );
+});
+
+const ControlColumnRowRenderCell: RenderCellValue = memo((props) => {
+ const {
+ visibleRowIndex,
+ alerts,
+ ecsData,
+ setFlyoutAlert,
+ oldAlertsData,
+ id,
+ getSetIsActionLoadingCallback,
+ refresh,
+ clearSelection,
+ renderCustomActionsRow,
+ } = props;
+ if (!ecsData[visibleRowIndex]) {
+ return null;
+ }
+
+ return (
+
+ {renderCustomActionsRow({
+ alert: alerts[visibleRowIndex],
+ ecsAlert: ecsData[visibleRowIndex],
+ nonEcsData: oldAlertsData[visibleRowIndex],
+ rowIndex: visibleRowIndex,
+ setFlyoutAlert,
+ id,
+ cveProps: props,
+ setIsActionLoading: getSetIsActionLoadingCallback(visibleRowIndex),
+ refresh,
+ clearSelection,
+ })}
+
+ );
+});
const isSystemCell = (columnId: string): columnId is SystemCellId => {
return systemCells.includes(columnId as SystemCellId);
};
+const useFieldBrowserOptionsOrDefault = (
+ useFieldBrowserOptions:
+ | NonNullable
+ | (() => undefined),
+ onToggleColumn: (columnId: string) => void
+) => {
+ const args = useMemo(() => ({ onToggleColumn }), [onToggleColumn]);
+ return useFieldBrowserOptions(args);
+};
+
+// Here we force the error callout to be the same height as the cell content
+// so that the error detail gets hidden in the overflow area and only shown in
+// the cell popover
+const errorCalloutStyles = css`
+ height: 1lh;
+`;
+
+/**
+ * An error callout that displays the error stack in a code block
+ */
+const ViewError = ({ error }: { error: Error }) => (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {error.stack}
+ >
+);
+
const Row = styled.div`
display: flex;
min-width: fit-content;
@@ -136,96 +278,74 @@ const CustomGridBody = memo(
}
);
-// Here we force the error callout to be the same height as the cell content
-// so that the error detail gets hidden in the overflow area and only shown in
-// the cell popover
-const errorCalloutStyles = css`
- height: 1lh;
-`;
-
-/**
- * An error callout that displays the error stack in a code block
- */
-const ViewError = ({ error }: { error: Error }) => (
- <>
-
-
-
-
-
-
-
-
-
-
-
-
-
- {error.stack}
- >
-);
-
-const AlertsTable: React.FunctionComponent = (props: AlertsTableProps) => {
+const AlertsTable: React.FunctionComponent = memo((props: AlertsTableProps) => {
const {
visibleColumns,
onToggleColumn,
onResetColumns,
- updatedAt,
browserFields,
onChangeVisibleColumns,
onColumnResize,
showAlertStatusWithFlapping = false,
showInspectButton = false,
- } = props;
-
- const dataGridRef = useRef(null);
- const [activeRowClasses, setActiveRowClasses] = useState<
- NonNullable
- >({});
- const alertsData = props.useFetchAlertsData();
- const {
- activePage,
+ cellContext: passedCellContext,
+ leadingControlColumns: passedControlColumns,
+ trailingControlColumns,
+ alertsTableConfiguration,
+ pagination,
+ columns,
alerts,
- oldAlertsData,
- ecsAlertsData,
alertsCount,
isLoading,
- onPageChange,
+ oldAlertsData,
+ ecsAlertsData,
onSortChange,
+ onPageChange,
sort: sortingFields,
- refresh: alertsRefresh,
+ refetch: alertsRefresh,
getInspectQuery,
- } = alertsData;
+ rowHeightsOptions,
+ dynamicRowHeight,
+ query,
+ featureIds,
+ cases: { data: cases, isLoading: isLoadingCases },
+ maintenanceWindows: { data: maintenanceWindows, isLoading: isLoadingMaintenanceWindows },
+ controls,
+ toolbarVisibility: toolbarVisibilityProp,
+ shouldHighlightRow,
+ fieldFormats,
+ } = props;
+
+ const dataGridRef = useRef(null);
+ const [activeRowClasses, setActiveRowClasses] = useState<
+ NonNullable
+ >({});
const queryClient = useQueryClient({ context: AlertsTableQueryContext });
- const { data: cases, isLoading: isLoadingCases } = props.cases;
- const { data: maintenanceWindows, isLoading: isLoadingMaintenanceWindows } =
- props.maintenanceWindows;
const { sortingColumns, onSort } = useSorting(onSortChange, visibleColumns, sortingFields);
- const {
- renderCustomActionsRow: CustomActionsRow,
- actionsColumnWidth,
- getSetIsActionLoadingCallback,
- } = useActionsColumn({
- options: props.alertsTableConfiguration.useActionsColumn,
- });
- const casesConfig = props.alertsTableConfiguration.cases;
- const renderCellContext = props.alertsTableConfiguration.useFetchPageContext?.({
+ const { renderCustomActionsRow, actionsColumnWidth, getSetIsActionLoadingCallback } =
+ useActionsColumn({
+ options: alertsTableConfiguration.useActionsColumn,
+ });
+
+ const userAssigneeContext = alertsTableConfiguration.useFetchPageContext?.({
alerts,
- columns: props.columns,
+ columns,
});
+ const bulkActionArgs = useMemo(() => {
+ return {
+ alerts,
+ casesConfig: alertsTableConfiguration.cases,
+ query,
+ useBulkActionsConfig: alertsTableConfiguration.useBulkActions,
+ refresh: alertsRefresh,
+ featureIds,
+ };
+ }, [alerts, alertsTableConfiguration, query, alertsRefresh, featureIds]);
+
const {
isBulkActionsColumnActive,
getBulkActionsLeadingControlColumn,
@@ -233,14 +353,7 @@ const AlertsTable: React.FunctionComponent = (props: AlertsTab
bulkActions,
setIsBulkActionsLoading,
clearSelection,
- } = useBulkActions({
- alerts,
- casesConfig,
- query: props.query,
- useBulkActionsConfig: props.alertsTableConfiguration.useBulkActions,
- refresh: alertsRefresh,
- featureIds: props.featureIds,
- });
+ } = useBulkActions(bulkActionArgs);
const refreshData = useCallback(() => {
alertsRefresh();
@@ -255,7 +368,7 @@ const AlertsTable: React.FunctionComponent = (props: AlertsTab
}, [clearSelection, refreshData]);
const {
- pagination,
+ pagination: updatedPagination,
onChangePageSize,
onChangePageIndex,
onPaginateFlyout,
@@ -263,8 +376,8 @@ const AlertsTable: React.FunctionComponent = (props: AlertsTab
setFlyoutAlertIndex,
} = usePagination({
onPageChange,
- pageIndex: activePage,
- pageSize: props.pageSize,
+ pageIndex: pagination.pageIndex,
+ pageSize: pagination.pageSize,
});
// TODO when every solution is using this table, we will be able to simplify it by just passing the alert index
@@ -276,216 +389,149 @@ const AlertsTable: React.FunctionComponent = (props: AlertsTab
[alerts, setFlyoutAlertIndex]
);
- const fieldBrowserOptions = props.alertsTableConfiguration.useFieldBrowserOptions
- ? props.alertsTableConfiguration?.useFieldBrowserOptions({
- onToggleColumn,
- })
- : undefined;
+ const fieldBrowserOptions = useFieldBrowserOptionsOrDefault(
+ alertsTableConfiguration.useFieldBrowserOptions ?? fieldBrowserStub,
+ onToggleColumn
+ );
- const toolbarVisibility = useCallback(() => {
- const { rowSelection } = bulkActionsState;
- return getToolbarVisibility({
+ const toolbarVisibilityArgs = useMemo(() => {
+ return {
bulkActions,
alertsCount,
- rowSelection,
- alerts: alertsData.alerts,
- updatedAt,
+ rowSelection: bulkActionsState.rowSelection,
+ alerts,
isLoading,
columnIds: visibleColumns,
onToggleColumn,
onResetColumns,
browserFields,
- controls: props.controls,
+ controls,
setIsBulkActionsLoading,
clearSelection,
refresh,
fieldBrowserOptions,
getInspectQuery,
showInspectButton,
- toolbarVisibilityProp: props.toolbarVisibility,
- });
+ toolbarVisibilityProp,
+ };
}, [
- bulkActionsState,
bulkActions,
alertsCount,
- alertsData.alerts,
- updatedAt,
+ bulkActionsState,
isLoading,
visibleColumns,
onToggleColumn,
onResetColumns,
browserFields,
- props.controls,
setIsBulkActionsLoading,
clearSelection,
refresh,
fieldBrowserOptions,
getInspectQuery,
showInspectButton,
- props.toolbarVisibility,
- ])();
+ toolbarVisibilityProp,
+ alerts,
+ controls,
+ ]);
- const leadingControlColumns = useMemo(() => {
- let controlColumns = [...props.leadingControlColumns];
+ const toolbarVisibility = useGetToolbarVisibility(toolbarVisibilityArgs);
- if (CustomActionsRow) {
- controlColumns = [
- {
+ const customActionsRow = useMemo(() => {
+ return renderCustomActionsRow
+ ? {
id: 'expandColumn',
width: actionsColumnWidth,
- headerCellRender: () => {
- return (
-
- {ALERTS_TABLE_CONTROL_COLUMNS_ACTIONS_LABEL}
-
- );
- },
- rowCellRender: (cveProps: EuiDataGridCellValueElementProps) => {
- const { visibleRowIndex } = cveProps as EuiDataGridCellValueElementProps & {
- visibleRowIndex: number;
- };
-
- if (!ecsAlertsData[visibleRowIndex]) {
- return null;
- }
-
- return (
-
-
-
- );
- },
- },
- ...controlColumns,
- ];
- }
+ headerCellRender: ControlColumnHeaderRenderCell,
+ rowCellRender: ControlColumnRowRenderCell,
+ }
+ : undefined;
+ }, [renderCustomActionsRow, actionsColumnWidth]);
+ const bulkActionsColumn = useMemo(() => {
+ return isBulkActionsColumnActive ? getBulkActionsLeadingControlColumn() : undefined;
+ }, [isBulkActionsColumnActive, getBulkActionsLeadingControlColumn]);
- if (isBulkActionsColumnActive) {
- controlColumns = [getBulkActionsLeadingControlColumn(), ...controlColumns];
+ const leadingControlColumns = useMemo(() => {
+ const controlColumns = passedControlColumns ?? [];
+ const usedBulkActionsColumn = bulkActionsColumn ? [bulkActionsColumn] : [];
+ const usedCustomActionsRow = customActionsRow ? [customActionsRow] : [];
+ const mergedControlColumns = [
+ ...controlColumns,
+ ...usedBulkActionsColumn,
+ ...usedCustomActionsRow,
+ ];
+ if (mergedControlColumns.length) {
+ return mergedControlColumns;
+ } else {
+ return undefined;
}
+ }, [bulkActionsColumn, customActionsRow, passedControlColumns]);
- return controlColumns;
- }, [
- props.leadingControlColumns,
- props.id,
- CustomActionsRow,
- isBulkActionsColumnActive,
- actionsColumnWidth,
- ecsAlertsData,
- alerts,
- oldAlertsData,
- handleFlyoutAlert,
- getSetIsActionLoadingCallback,
- refresh,
- clearSelection,
- getBulkActionsLeadingControlColumn,
- ]);
-
+ const rowIndex = flyoutAlertIndex + pagination.pageIndex * pagination.pageSize;
useEffect(() => {
// Row classes do not deal with visible row indices, so we need to handle page offset
- const rowIndex = flyoutAlertIndex + pagination.pageIndex * pagination.pageSize;
setActiveRowClasses({
[rowIndex]: 'alertsTableActiveRow',
});
- }, [flyoutAlertIndex, pagination.pageIndex, pagination.pageSize]);
-
- // Update highlighted rows when alerts or pagination changes
- const highlightedRowClasses = useMemo(() => {
- let mappedRowClasses: EuiDataGridStyle['rowClasses'] = {};
- const shouldHighlightRowCheck = props.shouldHighlightRow;
- if (shouldHighlightRowCheck) {
- mappedRowClasses = alerts.reduce>(
- (rowClasses, alert, index) => {
- if (shouldHighlightRowCheck(alert)) {
- rowClasses[index + pagination.pageIndex * pagination.pageSize] =
- 'alertsTableHighlightedRow';
- }
-
- return rowClasses;
- },
- {}
- );
- }
- return mappedRowClasses;
- }, [props.shouldHighlightRow, alerts, pagination.pageIndex, pagination.pageSize]);
+ }, [rowIndex]);
const handleFlyoutClose = useCallback(() => setFlyoutAlertIndex(-1), [setFlyoutAlertIndex]);
- const renderCellValue = useCallback(
- () =>
- props.alertsTableConfiguration?.getRenderCellValue?.({
- setFlyoutAlert: handleFlyoutAlert,
- context: renderCellContext,
- }) ?? basicRenderCellValue,
- [handleFlyoutAlert, props.alertsTableConfiguration, renderCellContext]
- )();
-
- const handleRenderCellValue = useCallback(
- (_props: EuiDataGridCellValueElementProps) => {
- try {
- // https://github.com/elastic/eui/issues/5811
- const idx = _props.rowIndex - pagination.pageSize * pagination.pageIndex;
- const alert = alerts[idx];
- // ecsAlert is needed for security solution
- const ecsAlert = ecsAlertsData[idx];
- if (alert) {
- const data: Array<{ field: string; value: string[] }> = [];
- Object.entries(alert ?? {}).forEach(([key, value]) => {
- data.push({ field: key, value: value as string[] });
- });
- if (isSystemCell(_props.columnId)) {
- return (
-
- );
- }
-
- return renderCellValue({
- ..._props,
- data,
- ecsData: ecsAlert,
- });
- } else if (isLoading) {
- return ;
- }
- return null;
- } catch (e) {
- return ;
- }
- },
- [
+ const RenderCell = useMemo(() => {
+ if (props.alertsTableConfiguration?.getRenderCellValue) {
+ return props.alertsTableConfiguration.getRenderCellValue;
+ } else {
+ return FullFeaturedRenderCellValue;
+ }
+ }, [props.alertsTableConfiguration]);
+
+ const renderCellContext = useMemo(() => {
+ const additionalContext = passedCellContext ? passedCellContext : {};
+ return {
+ ...additionalContext,
+ ...alertsTableConfiguration,
+ ecsData: ecsAlertsData,
+ oldAlertsData,
+ context: userAssigneeContext,
alerts,
- cases,
- casesConfig?.appId,
- ecsAlertsData,
+ browserFields,
+ pagination: updatedPagination,
isLoading,
+ setFlyoutAlert: handleFlyoutAlert,
+ RenderCell,
isLoadingCases,
isLoadingMaintenanceWindows,
+ getSetIsActionLoadingCallback,
+ cases,
maintenanceWindows,
- pagination.pageIndex,
- pagination.pageSize,
- renderCellValue,
showAlertStatusWithFlapping,
- ]
- );
+ refresh,
+ clearSelection,
+ renderCustomActionsRow,
+ fieldFormats,
+ };
+ }, [
+ passedCellContext,
+ alertsTableConfiguration,
+ ecsAlertsData,
+ oldAlertsData,
+ refresh,
+ clearSelection,
+ renderCustomActionsRow,
+ handleFlyoutAlert,
+ RenderCell,
+ browserFields,
+ isLoading,
+ updatedPagination,
+ alerts,
+ isLoadingCases,
+ isLoadingMaintenanceWindows,
+ cases,
+ maintenanceWindows,
+ showAlertStatusWithFlapping,
+ getSetIsActionLoadingCallback,
+ userAssigneeContext,
+ fieldFormats,
+ ]);
const renderCellPopover = useMemo(
() =>
@@ -519,29 +565,36 @@ const AlertsTable: React.FunctionComponent = (props: AlertsTab
const dataGridPagination = useMemo(
() => ({
- ...pagination,
+ pageIndex: updatedPagination.pageIndex,
+ pageSize: updatedPagination.pageSize,
pageSizeOptions: props.pageSizeOptions,
onChangeItemsPerPage: onChangePageSize,
onChangePage: onChangePageIndex,
}),
- [onChangePageIndex, onChangePageSize, pagination, props.pageSizeOptions]
+ [
+ onChangePageIndex,
+ onChangePageSize,
+ updatedPagination.pageIndex,
+ updatedPagination.pageSize,
+ props.pageSizeOptions,
+ ]
);
- const { getCellActions, visibleCellActions, disabledCellActions } = props.alertsTableConfiguration
- ?.useCellActions
- ? props.alertsTableConfiguration?.useCellActions({
- columns: props.columns,
- data: oldAlertsData,
- ecsData: ecsAlertsData,
- dataGridRef,
- pageSize: pagination.pageSize,
- pageIndex: pagination.pageIndex,
- })
- : getCellActionsStub;
+ const { getCellActions, visibleCellActions, disabledCellActions } =
+ alertsTableConfiguration?.useCellActions
+ ? alertsTableConfiguration?.useCellActions({
+ columns,
+ data: oldAlertsData,
+ ecsData: ecsAlertsData,
+ dataGridRef,
+ pageSize: pagination.pageSize,
+ pageIndex: pagination.pageIndex,
+ })
+ : getCellActionsStub;
const columnsWithCellActions = useMemo(() => {
if (getCellActions) {
- return props.columns.map((col, idx) => ({
+ return columns.map((col, idx) => ({
...col,
...(!(disabledCellActions ?? []).includes(col.id)
? {
@@ -551,14 +604,33 @@ const AlertsTable: React.FunctionComponent = (props: AlertsTab
: {}),
}));
}
- return props.columns;
- }, [getCellActions, disabledCellActions, props.columns, visibleCellActions]);
+ return columns;
+ }, [getCellActions, disabledCellActions, columns, visibleCellActions]);
- // Merges the default grid style with the grid style that comes in through props.
- const actualGridStyle = useMemo(() => {
+ // // Update highlighted rows when alerts or pagination changes
+ const highlightedRowClasses = useMemo(() => {
+ if (shouldHighlightRow) {
+ const emptyShouldHighlightRow: EuiDataGridStyle['rowClasses'] = {};
+ return alerts.reduce>(
+ (rowClasses, alert, index) => {
+ if (shouldHighlightRow(alert)) {
+ rowClasses[index + pagination.pageIndex * pagination.pageSize] =
+ 'alertsTableHighlightedRow';
+ }
+
+ return rowClasses;
+ },
+ emptyShouldHighlightRow
+ );
+ } else {
+ return stableMappedRowClasses;
+ }
+ }, [shouldHighlightRow, alerts, pagination.pageIndex, pagination.pageSize]);
+
+ const mergedGridStyle = useMemo(() => {
const propGridStyle: NonNullable = props.gridStyle ?? {};
// Merges default row classes, custom ones and adds the active row class style
- const mergedGridStyle: EuiDataGridStyle = {
+ return {
...DefaultGridStyle,
...propGridStyle,
rowClasses: {
@@ -568,7 +640,11 @@ const AlertsTable: React.FunctionComponent = (props: AlertsTab
...activeRowClasses,
},
};
+ }, [activeRowClasses, highlightedRowClasses, props.gridStyle]);
+ // Merges the default grid style with the grid style that comes in through props.
+ const actualGridStyle = useMemo(() => {
+ const propGridStyle: NonNullable = props.gridStyle ?? {};
// If ANY additional rowClasses have been provided, we need to merge them with our internal ones
if (propGridStyle.rowClasses) {
// Get all row indices with a rowClass.
@@ -593,7 +669,7 @@ const AlertsTable: React.FunctionComponent = (props: AlertsTab
mergedGridStyle.rowClasses = mergedRowClasses;
}
return mergedGridStyle;
- }, [activeRowClasses, highlightedRowClasses, props.gridStyle]);
+ }, [props.gridStyle, mergedGridStyle]);
const renderCustomGridBody = useCallback>(
({ visibleColumns: _visibleColumns, Cell }) => (
@@ -610,6 +686,13 @@ const AlertsTable: React.FunctionComponent = (props: AlertsTab
[actualGridStyle, oldAlertsData, pagination, isLoading, props.gridStyle?.stripes]
);
+ const sortProps = useMemo(() => {
+ return { columns: sortingColumns, onSort };
+ }, [sortingColumns, onSort]);
+
+ const columnVisibility = useMemo(() => {
+ return { visibleColumns, setVisibleColumns: onChangeVisibleColumns };
+ }, [visibleColumns, onChangeVisibleColumns]);
return (
@@ -619,7 +702,7 @@ const AlertsTable: React.FunctionComponent = (props: AlertsTab
alert={alerts[flyoutAlertIndex]}
alertsCount={alertsCount}
onClose={handleFlyoutClose}
- alertsTableConfiguration={props.alertsTableConfiguration}
+ alertsTableConfiguration={alertsTableConfiguration}
flyoutIndex={flyoutAlertIndex + pagination.pageIndex * pagination.pageSize}
onPaginate={onPaginateFlyout}
isLoading={isLoading}
@@ -632,26 +715,29 @@ const AlertsTable: React.FunctionComponent = (props: AlertsTab
aria-label="Alerts table"
data-test-subj="alertsTable"
columns={columnsWithCellActions}
- columnVisibility={{ visibleColumns, setVisibleColumns: onChangeVisibleColumns }}
- trailingControlColumns={props.trailingControlColumns}
+ columnVisibility={columnVisibility}
+ trailingControlColumns={trailingControlColumns}
leadingControlColumns={leadingControlColumns}
rowCount={alertsCount}
- renderCellValue={handleRenderCellValue}
+ renderCellValue={FullFeaturedRenderCellValue}
gridStyle={actualGridStyle}
- sorting={{ columns: sortingColumns, onSort }}
+ sorting={sortProps}
toolbarVisibility={toolbarVisibility}
+ cellContext={renderCellContext}
pagination={dataGridPagination}
- rowHeightsOptions={props.rowHeightsOptions}
+ rowHeightsOptions={rowHeightsOptions}
onColumnResize={onColumnResize}
ref={dataGridRef}
- renderCustomGridBody={props.dynamicRowHeight ? renderCustomGridBody : undefined}
+ renderCustomGridBody={dynamicRowHeight ? renderCustomGridBody : undefined}
renderCellPopover={handleRenderCellPopover}
/>
)}
);
-};
+});
+
+AlertsTable.displayName = 'AlertsTable';
export { AlertsTable };
// eslint-disable-next-line import/no-default-export
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table_state.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table_state.test.tsx
index 0564d2fdcddc6..8f54eaf5c6278 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table_state.test.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table_state.test.tsx
@@ -151,6 +151,8 @@ const alerts = [
[AlertsField.name]: ['five'],
[AlertsField.reason]: ['six'],
[AlertsField.uuid]: ['1047d115-5afd-469e-baf6-f28c2b68db46'],
+ [ALERT_CASE_IDS]: [],
+ [ALERT_MAINTENANCE_WINDOW_IDS]: [],
},
] as unknown as Alerts;
@@ -260,10 +262,6 @@ const getMock = jest.fn().mockImplementation((plugin: string) => {
header: () => <>{'header'}>,
footer: () => <>{'footer'}>,
}),
- getRenderCellValue: () =>
- jest.fn().mockImplementation((props) => {
- return `${props.colIndex}:${props.rowIndex}`;
- }),
useActionsColumn: () => ({
renderCustomActionsRow: ({ setFlyoutAlert }: RenderCustomActionsRowArgs) => {
return (
@@ -326,12 +324,19 @@ const AlertsTableWithLocale: React.FunctionComponent = (p
);
describe('AlertsTableState', () => {
- const tableProps = {
+ const tableProps: AlertsTableStateProps = {
alertsTableConfigurationRegistry: alertsTableConfigurationRegistryMock,
configurationId: PLUGIN_ID,
- id: `test-alerts`,
+ id: PLUGIN_ID,
featureIds: [AlertConsumers.LOGS],
query: {},
+ columns,
+ pagination: {
+ pageIndex: 0,
+ pageSize: 10,
+ onChangePage: jest.fn(),
+ onChangeItemsPerPage: jest.fn(),
+ },
};
const mockCustomProps = (customProps: Partial) => {
@@ -349,6 +354,7 @@ describe('AlertsTableState', () => {
has: hasMock,
get: getMockWithUsePersistentControls,
update: updateMock,
+ getActions: getActionsMock,
} as unknown as AlertTableConfigRegistry;
return {
@@ -454,15 +460,19 @@ describe('AlertsTableState', () => {
const props = mockCustomProps({
cases: { featureId: 'test-feature-id', owner: ['test-owner'] },
- columns: [
- {
- id: AlertsField.name,
- displayAsText: 'Name',
- },
- ],
});
- render();
+ render(
+
+ );
await waitFor(() => {
expect(useBulkGetCasesMock).toHaveBeenCalledWith(['test-id-2'], false);
});
@@ -643,16 +653,17 @@ describe('AlertsTableState', () => {
it('should not fetch maintenance windows if the user does not have permission', async () => {});
it('should not fetch maintenance windows if the column is not visible', async () => {
- const props = mockCustomProps({
- columns: [
- {
- id: AlertsField.name,
- displayAsText: 'Name',
- },
- ],
- });
-
- render();
+ render(
+
+ );
await waitFor(() => {
expect(useBulkGetMaintenanceWindowsMock).toHaveBeenCalledWith(
expect.objectContaining({
@@ -798,6 +809,7 @@ describe('AlertsTableState', () => {
};
beforeEach(() => {
+ jest.clearAllMocks();
hookUseFetchBrowserFieldCapabilities.mockClear();
hookUseFetchBrowserFieldCapabilities.mockImplementation(() => [true, browserFields]);
useBulkGetCasesMock.mockReturnValue({ data: new Map(), isFetching: false });
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table_state.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table_state.tsx
index 1bcd8ceb7d466..70aaf7a304453 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table_state.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table_state.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import React, { useState, useCallback, useRef, useMemo, useReducer, useEffect } from 'react';
+import React, { useState, useCallback, useRef, useMemo, useReducer, useEffect, memo } from 'react';
import { isEmpty } from 'lodash';
import {
EuiDataGridColumn,
@@ -20,11 +20,8 @@ import {
EuiDataGridControlColumn,
} from '@elastic/eui';
import type { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
-import {
- ALERT_CASE_IDS,
- ALERT_MAINTENANCE_WINDOW_IDS,
- ALERT_RULE_UUID,
-} from '@kbn/rule-data-utils';
+import { FieldFormatsRegistry } from '@kbn/field-formats-plugin/common';
+import { ALERT_CASE_IDS, ALERT_MAINTENANCE_WINDOW_IDS } from '@kbn/rule-data-utils';
import type { ValidFeatureId } from '@kbn/rule-data-utils';
import type {
BrowserFields,
@@ -107,7 +104,7 @@ const EmptyConfiguration: AlertsTableConfigurationRegistry = {
id: '',
columns: [],
sort: [],
- getRenderCellValue: () => () => null,
+ getRenderCellValue: () => null,
};
type AlertWithCaseIds = Alert & Required>;
@@ -142,6 +139,19 @@ const isCasesColumnEnabled = (columns: EuiDataGridColumn[]): boolean =>
const isMaintenanceWindowColumnEnabled = (columns: EuiDataGridColumn[]): boolean =>
columns.some(({ id }) => id === ALERT_MAINTENANCE_WINDOW_IDS);
+const stableEmptyArray: string[] = [];
+const defaultPageSizeOptions = [10, 20, 50, 100];
+
+const emptyRowSelection = new Map();
+
+const initialBulkActionsState = {
+ rowSelection: emptyRowSelection,
+ isAllSelected: false,
+ areAllVisibleRowsSelected: false,
+ rowCount: 0,
+ updatedAt: Date.now(),
+};
+
const ErrorBoundaryFallback: FallbackComponent = ({ error }) => {
return (
{
);
};
-const AlertsTableState = (props: AlertsTableStateProps) => {
+const AlertsTableState = memo((props: AlertsTableStateProps) => {
return (
@@ -175,327 +185,369 @@ const AlertsTableState = (props: AlertsTableStateProps) => {
);
-};
+});
+
+AlertsTableState.displayName = 'AlertsTableState';
const DEFAULT_LEADING_CONTROL_COLUMNS: EuiDataGridControlColumn[] = [];
-const AlertsTableStateWithQueryProvider = ({
- alertsTableConfigurationRegistry,
- configurationId,
- id,
- featureIds,
- query,
- pageSize,
- leadingControlColumns = DEFAULT_LEADING_CONTROL_COLUMNS,
- rowHeightsOptions,
- renderCellValue,
- renderCellPopover,
- columns: propColumns,
- gridStyle,
- browserFields: propBrowserFields,
- onUpdate,
- onLoaded,
- runtimeMappings,
- showAlertStatusWithFlapping,
- toolbarVisibility,
- shouldHighlightRow,
- dynamicRowHeight,
- lastReloadRequestTime,
-}: AlertsTableStateProps) => {
- const { cases: casesService } = useKibana<{ cases?: CasesService }>().services;
- const hasAlertsTableConfiguration =
- alertsTableConfigurationRegistry?.has(configurationId) ?? false;
-
- if (!hasAlertsTableConfiguration)
- // eslint-disable-next-line no-console
- console.warn(`Missing Alert Table configuration for configuration ID: ${configurationId}`);
-
- const alertsTableConfiguration = hasAlertsTableConfiguration
- ? alertsTableConfigurationRegistry.get(configurationId)
- : EmptyConfiguration;
-
- const storage = useRef(new Storage(window.localStorage));
- const localStorageAlertsTableConfig = storage.current.get(id) as Partial;
- const persistentControls = alertsTableConfiguration?.usePersistentControls?.();
- const showInspectButton = alertsTableConfiguration?.showInspectButton ?? false;
-
- const columnConfigByClient =
- propColumns && !isEmpty(propColumns) ? propColumns : alertsTableConfiguration?.columns ?? [];
-
- const columnsLocal =
- localStorageAlertsTableConfig &&
- localStorageAlertsTableConfig.columns &&
- !isEmpty(localStorageAlertsTableConfig?.columns)
- ? localStorageAlertsTableConfig?.columns
- : columnConfigByClient;
-
- const getStorageConfig = () => ({
- columns: columnsLocal,
- sort:
- localStorageAlertsTableConfig &&
- localStorageAlertsTableConfig.sort &&
- !isEmpty(localStorageAlertsTableConfig?.sort)
- ? localStorageAlertsTableConfig?.sort
- : alertsTableConfiguration?.sort ?? [],
- visibleColumns:
- localStorageAlertsTableConfig &&
- localStorageAlertsTableConfig.visibleColumns &&
- !isEmpty(localStorageAlertsTableConfig?.visibleColumns)
- ? localStorageAlertsTableConfig?.visibleColumns
- : columnsLocal.map((c) => c.id),
- });
- const storageAlertsTable = useRef(getStorageConfig());
-
- storageAlertsTable.current = getStorageConfig();
-
- const [sort, setSort] = useState(storageAlertsTable.current.sort);
- const [pagination, setPagination] = useState({
- ...DefaultPagination,
- pageSize: pageSize ?? DefaultPagination.pageSize,
- });
-
- const {
- columns,
- browserFields,
- isBrowserFieldDataLoading,
- onToggleColumn,
- onResetColumns,
- visibleColumns,
- onChangeVisibleColumns,
- onColumnResize,
- fields,
- } = useColumns({
- featureIds,
- storageAlertsTable,
- storage,
+const AlertsTableStateWithQueryProvider = memo(
+ ({
+ alertsTableConfigurationRegistry,
+ configurationId,
id,
- defaultColumns: columnConfigByClient,
- initialBrowserFields: propBrowserFields,
- });
-
- const onPageChange = useCallback((_pagination: RuleRegistrySearchRequestPagination) => {
- setPagination(_pagination);
- }, []);
-
- const [
- isLoading,
- {
- alerts,
- oldAlertsData,
- ecsAlertsData,
- isInitializing,
- getInspectQuery,
- refetch: refresh,
- totalAlerts: alertsCount,
- updatedAt,
- },
- ] = useFetchAlerts({
- fields,
featureIds,
query,
- pagination,
- onPageChange,
+ pageSize,
+ leadingControlColumns = DEFAULT_LEADING_CONTROL_COLUMNS,
+ trailingControlColumns,
+ rowHeightsOptions,
+ cellContext,
+ columns: propColumns,
+ gridStyle,
+ browserFields: propBrowserFields,
+ onUpdate,
onLoaded,
runtimeMappings,
- sort,
- skip: false,
- });
-
- const { data: mutedAlerts } = useGetMutedAlerts([
- ...new Set(alerts.map((a) => a[ALERT_RULE_UUID]![0])),
- ]);
-
- useEffect(() => {
- if (hasAlertsTableConfiguration) {
- alertsTableConfigurationRegistry.update(configurationId, {
- ...alertsTableConfiguration,
- actions: { toggleColumn: onToggleColumn },
- });
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [onToggleColumn]);
-
- useEffect(() => {
- if (onUpdate) {
- onUpdate({ isLoading, totalCount: alertsCount, refresh });
- }
- }, [isLoading, alertsCount, onUpdate, refresh]);
- useEffect(() => {
- if (lastReloadRequestTime) {
- refresh();
- }
- }, [lastReloadRequestTime, refresh]);
-
- const caseIds = useMemo(() => getCaseIdsFromAlerts(alerts), [alerts]);
- const maintenanceWindowIds = useMemo(() => getMaintenanceWindowIdsFromAlerts(alerts), [alerts]);
-
- const casesPermissions = casesService?.helpers.canUseCases(
- alertsTableConfiguration?.cases?.owner ?? []
- );
-
- const hasCaseReadPermissions = Boolean(casesPermissions?.read);
- const fetchCases = isCasesColumnEnabled(columns) && hasCaseReadPermissions;
- const fetchMaintenanceWindows = isMaintenanceWindowColumnEnabled(columns);
+ showAlertStatusWithFlapping,
+ toolbarVisibility,
+ shouldHighlightRow,
+ dynamicRowHeight,
+ lastReloadRequestTime,
+ }: AlertsTableStateProps) => {
+ const { cases: casesService, fieldFormats } = useKibana<{
+ cases?: CasesService;
+ fieldFormats: FieldFormatsRegistry;
+ }>().services;
+ const hasAlertsTableConfiguration =
+ alertsTableConfigurationRegistry?.has(configurationId) ?? false;
+
+ if (!hasAlertsTableConfiguration)
+ // eslint-disable-next-line no-console
+ console.warn(`Missing Alert Table configuration for configuration ID: ${configurationId}`);
+
+ const alertsTableConfiguration = hasAlertsTableConfiguration
+ ? alertsTableConfigurationRegistry.get(configurationId)
+ : EmptyConfiguration;
+
+ const storage = useRef(new Storage(window.localStorage));
+ const localStorageAlertsTableConfig = storage.current.get(id) as Partial;
+ const persistentControls = alertsTableConfiguration?.usePersistentControls?.();
+ const showInspectButton = alertsTableConfiguration?.showInspectButton ?? false;
+
+ const columnConfigByClient = useMemo(() => {
+ return propColumns && !isEmpty(propColumns)
+ ? propColumns
+ : alertsTableConfiguration?.columns ?? [];
+ }, [propColumns, alertsTableConfiguration]);
+
+ const columnsLocal =
+ localStorageAlertsTableConfig &&
+ localStorageAlertsTableConfig.columns &&
+ !isEmpty(localStorageAlertsTableConfig?.columns)
+ ? localStorageAlertsTableConfig?.columns
+ : columnConfigByClient;
+
+ const getStorageConfig = useCallback(
+ () => ({
+ columns: columnsLocal,
+ sort:
+ localStorageAlertsTableConfig &&
+ localStorageAlertsTableConfig.sort &&
+ !isEmpty(localStorageAlertsTableConfig?.sort)
+ ? localStorageAlertsTableConfig?.sort
+ : alertsTableConfiguration?.sort ?? [],
+ visibleColumns:
+ localStorageAlertsTableConfig &&
+ localStorageAlertsTableConfig.visibleColumns &&
+ !isEmpty(localStorageAlertsTableConfig?.visibleColumns)
+ ? localStorageAlertsTableConfig?.visibleColumns
+ : columnsLocal.map((c) => c.id),
+ }),
+ [columnsLocal, alertsTableConfiguration?.sort, localStorageAlertsTableConfig]
+ );
+ const storageAlertsTable = useRef(getStorageConfig());
- const { data: cases, isFetching: isLoadingCases } = useBulkGetCases(
- Array.from(caseIds.values()),
- fetchCases
- );
+ storageAlertsTable.current = getStorageConfig();
- const { data: maintenanceWindows, isFetching: isLoadingMaintenanceWindows } =
- useBulkGetMaintenanceWindows({
- ids: Array.from(maintenanceWindowIds.values()),
- canFetchMaintenanceWindows: fetchMaintenanceWindows,
- queryContext: AlertsTableQueryContext,
+ const [sort, setSort] = useState(storageAlertsTable.current.sort);
+ const [pagination, setPagination] = useState({
+ ...DefaultPagination,
+ pageSize: pageSize ?? DefaultPagination.pageSize,
});
- const initialBulkActionsState = useReducer(bulkActionsReducer, {
- rowSelection: new Map(),
- isAllSelected: false,
- areAllVisibleRowsSelected: false,
- rowCount: alerts.length,
- });
-
- const onSortChange = useCallback(
- (_sort: EuiDataGridSorting['columns']) => {
- const newSort = _sort.map((sortItem) => {
- return {
- [sortItem.id]: {
- order: sortItem.direction,
- },
- };
- });
+ const onPageChange = useCallback((_pagination: RuleRegistrySearchRequestPagination) => {
+ setPagination(_pagination);
+ }, []);
- storageAlertsTable.current = {
- ...storageAlertsTable.current,
- sort: newSort,
- };
- storage.current.set(id, storageAlertsTable.current);
- setSort(newSort);
- },
- [id]
- );
-
- const useFetchAlertsData = useCallback(() => {
- return {
- activePage: pagination.pageIndex,
- alerts,
- alertsCount,
- isInitializing,
- isLoading,
- getInspectQuery,
- onPageChange,
- onSortChange,
- refresh,
- sort,
- updatedAt,
- oldAlertsData,
- ecsAlertsData,
- };
- }, [
- alerts,
- alertsCount,
- ecsAlertsData,
- getInspectQuery,
- isInitializing,
- isLoading,
- oldAlertsData,
- onPageChange,
- onSortChange,
- pagination,
- refresh,
- sort,
- updatedAt,
- ]);
-
- const CasesContext = casesService?.ui.getCasesContext();
- const isCasesContextAvailable = casesService && CasesContext;
-
- const memoizedCases = useMemo(
- () => ({
- data: cases ?? new Map(),
- isLoading: isLoadingCases,
- }),
- [cases, isLoadingCases]
- );
-
- const memoizedMaintenanceWindows = useMemo(
- () => ({
- data: maintenanceWindows ?? new Map(),
- isLoading: isLoadingMaintenanceWindows,
- }),
- [maintenanceWindows, isLoadingMaintenanceWindows]
- );
-
- const tableProps: AlertsTableProps = useMemo(
- () => ({
- alertsTableConfiguration,
- cases: memoizedCases,
- maintenanceWindows: memoizedMaintenanceWindows,
+ const {
columns,
- bulkActions: [],
- deletedEventIds: [],
- disabledCellActions: [],
- pageSize: pagination.pageSize,
- pageSizeOptions: [10, 20, 50, 100],
- id,
- leadingControlColumns,
- showAlertStatusWithFlapping,
- trailingControlColumns: [],
- useFetchAlertsData,
- visibleColumns,
- 'data-test-subj': 'internalAlertsState',
- updatedAt,
browserFields,
+ isBrowserFieldDataLoading,
onToggleColumn,
onResetColumns,
+ visibleColumns,
onChangeVisibleColumns,
onColumnResize,
- query,
- rowHeightsOptions,
- renderCellValue,
- renderCellPopover,
- gridStyle,
- controls: persistentControls,
- showInspectButton,
- toolbarVisibility,
- shouldHighlightRow,
- dynamicRowHeight,
+ fields,
+ } = useColumns({
featureIds,
- }),
- [
- alertsTableConfiguration,
- memoizedCases,
- memoizedMaintenanceWindows,
- columns,
- pagination.pageSize,
+ storageAlertsTable,
+ storage,
id,
- leadingControlColumns,
- showAlertStatusWithFlapping,
- useFetchAlertsData,
- visibleColumns,
- updatedAt,
- browserFields,
- onToggleColumn,
- onResetColumns,
- onChangeVisibleColumns,
- onColumnResize,
- query,
- rowHeightsOptions,
- renderCellValue,
- renderCellPopover,
- gridStyle,
- persistentControls,
- showInspectButton,
- toolbarVisibility,
- shouldHighlightRow,
- dynamicRowHeight,
+ defaultColumns: columnConfigByClient,
+ initialBrowserFields: propBrowserFields,
+ });
+
+ const [
+ isLoading,
+ {
+ alerts,
+ oldAlertsData,
+ ecsAlertsData,
+ isInitializing,
+ getInspectQuery,
+ refetch: refresh,
+ totalAlerts: alertsCount,
+ },
+ ] = useFetchAlerts({
+ fields,
featureIds,
- ]
- );
+ query,
+ pagination,
+ onPageChange,
+ onLoaded,
+ runtimeMappings,
+ sort,
+ skip: false,
+ });
+
+ const mutedAlertIds = useMemo(() => {
+ return [...new Set(alerts.map((a) => a['kibana.alert.rule.uuid']![0]))];
+ }, [alerts]);
+
+ const { data: mutedAlerts } = useGetMutedAlerts(mutedAlertIds);
+ const overriddenActions = useMemo(() => {
+ return { toggleColumn: onToggleColumn };
+ }, [onToggleColumn]);
+
+ const configWithToggle = useMemo(() => {
+ return {
+ ...alertsTableConfiguration,
+ actions: overriddenActions,
+ };
+ }, [alertsTableConfiguration, overriddenActions]);
+
+ useEffect(() => {
+ const currentToggle =
+ alertsTableConfigurationRegistry.getActions(configurationId)?.toggleColumn;
+ if (onToggleColumn !== currentToggle) {
+ alertsTableConfigurationRegistry.update(configurationId, configWithToggle);
+ }
+ }, [configurationId, alertsTableConfigurationRegistry, configWithToggle, onToggleColumn]);
+
+ useEffect(() => {
+ if (onUpdate) {
+ onUpdate({ isLoading, totalCount: alertsCount, refresh });
+ }
+ }, [isLoading, alertsCount, onUpdate, refresh]);
+ useEffect(() => {
+ if (lastReloadRequestTime) {
+ refresh();
+ }
+ }, [lastReloadRequestTime, refresh]);
+
+ const caseIds = useMemo(() => getCaseIdsFromAlerts(alerts), [alerts]);
+ const maintenanceWindowIds = useMemo(() => getMaintenanceWindowIdsFromAlerts(alerts), [alerts]);
+
+ const casesPermissions = useMemo(() => {
+ return casesService?.helpers.canUseCases(alertsTableConfiguration?.cases?.owner ?? []);
+ }, [alertsTableConfiguration, casesService]);
+
+ const hasCaseReadPermissions = Boolean(casesPermissions?.read);
+ const fetchCases = isCasesColumnEnabled(columns) && hasCaseReadPermissions;
+ const fetchMaintenanceWindows = isMaintenanceWindowColumnEnabled(columns);
- if (!hasAlertsTableConfiguration) {
- return (
+ const caseIdsForBulk = useMemo(() => {
+ return Array.from(caseIds.values());
+ }, [caseIds]);
+
+ const { data: cases, isFetching: isLoadingCases } = useBulkGetCases(caseIdsForBulk, fetchCases);
+
+ const maintenanceWindowIdsForBulk = useMemo(() => {
+ return {
+ ids: Array.from(maintenanceWindowIds.values()),
+ canFetchMaintenanceWindows: fetchMaintenanceWindows,
+ queryContext: AlertsTableQueryContext,
+ };
+ }, [fetchMaintenanceWindows, maintenanceWindowIds]);
+
+ const { data: maintenanceWindows, isFetching: isLoadingMaintenanceWindows } =
+ useBulkGetMaintenanceWindows(maintenanceWindowIdsForBulk);
+
+ const activeBulkActionsReducer = useReducer(bulkActionsReducer, initialBulkActionsState);
+
+ const onSortChange = useCallback(
+ (_sort: EuiDataGridSorting['columns']) => {
+ const newSort = _sort.map((sortItem) => {
+ return {
+ [sortItem.id]: {
+ order: sortItem.direction,
+ },
+ };
+ });
+
+ storageAlertsTable.current = {
+ ...storageAlertsTable.current,
+ sort: newSort,
+ };
+ storage.current.set(id, storageAlertsTable.current);
+ setSort(newSort);
+ },
+ [id]
+ );
+
+ const CasesContext = useMemo(() => {
+ return casesService?.ui.getCasesContext();
+ }, [casesService?.ui]);
+
+ const isCasesContextAvailable = casesService && CasesContext;
+
+ const memoizedCases = useMemo(
+ () => ({
+ data: cases ?? new Map(),
+ isLoading: isLoadingCases,
+ }),
+ [cases, isLoadingCases]
+ );
+
+ const memoizedMaintenanceWindows = useMemo(
+ () => ({
+ data: maintenanceWindows ?? new Map(),
+ isLoading: isLoadingMaintenanceWindows,
+ }),
+ [maintenanceWindows, isLoadingMaintenanceWindows]
+ );
+
+ const tableProps: AlertsTableProps = useMemo(
+ () => ({
+ alertsTableConfiguration,
+ cases: memoizedCases,
+ maintenanceWindows: memoizedMaintenanceWindows,
+ columns,
+ bulkActions: stableEmptyArray,
+ deletedEventIds: stableEmptyArray,
+ disabledCellActions: stableEmptyArray,
+ pageSizeOptions: defaultPageSizeOptions,
+ id,
+ leadingControlColumns,
+ showAlertStatusWithFlapping,
+ trailingControlColumns,
+ visibleColumns,
+ 'data-test-subj': 'internalAlertsState',
+ browserFields,
+ onToggleColumn,
+ onResetColumns,
+ onChangeVisibleColumns,
+ onColumnResize,
+ query,
+ rowHeightsOptions,
+ cellContext,
+ gridStyle,
+ controls: persistentControls,
+ showInspectButton,
+ toolbarVisibility,
+ shouldHighlightRow,
+ dynamicRowHeight,
+ featureIds,
+ isInitializing,
+ pagination,
+ sort,
+ isLoading,
+ alerts,
+ oldAlertsData,
+ ecsAlertsData,
+ getInspectQuery,
+ refetch: refresh,
+ alertsCount,
+ onSortChange,
+ onPageChange,
+ fieldFormats,
+ }),
+ [
+ alertsTableConfiguration,
+ memoizedCases,
+ memoizedMaintenanceWindows,
+ columns,
+ id,
+ leadingControlColumns,
+ trailingControlColumns,
+ showAlertStatusWithFlapping,
+ visibleColumns,
+ browserFields,
+ onToggleColumn,
+ onResetColumns,
+ onChangeVisibleColumns,
+ onColumnResize,
+ query,
+ rowHeightsOptions,
+ gridStyle,
+ persistentControls,
+ showInspectButton,
+ toolbarVisibility,
+ shouldHighlightRow,
+ dynamicRowHeight,
+ featureIds,
+ cellContext,
+ isInitializing,
+ pagination,
+ sort,
+ isLoading,
+ alerts,
+ oldAlertsData,
+ ecsAlertsData,
+ getInspectQuery,
+ refresh,
+ alertsCount,
+ onSortChange,
+ onPageChange,
+ fieldFormats,
+ ]
+ );
+
+ const alertsTableContext = useMemo(() => {
+ return {
+ mutedAlerts: mutedAlerts ?? {},
+ bulkActions: activeBulkActionsReducer,
+ };
+ }, [activeBulkActionsReducer, mutedAlerts]);
+
+ return hasAlertsTableConfiguration ? (
+
+ {!isLoading && alertsCount === 0 && (
+
+
+
+ )}
+ {(isLoading || isBrowserFieldDataLoading) && (
+
+ )}
+ {alertsCount !== 0 && isCasesContextAvailable && (
+
+
+
+ )}
+ {alertsCount !== 0 && !isCasesContextAvailable && }
+
+ ) : (
);
}
+);
- return (
-
- {!isLoading && alertsCount === 0 && (
-
-
-
- )}
- {(isLoading || isBrowserFieldDataLoading) && (
-
- )}
- {alertsCount !== 0 && isCasesContextAvailable && (
-
-
-
- )}
- {alertsCount !== 0 && !isCasesContextAvailable && }
-
- );
-};
+AlertsTableStateWithQueryProvider.displayName = 'AlertsTableStateWithQueryProvider';
export { AlertsTableState };
// eslint-disable-next-line import/no-default-export
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/bulk_actions/bulk_actions.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/bulk_actions/bulk_actions.test.tsx
index aae92622dbe0a..4a8243eb0269a 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/bulk_actions/bulk_actions.test.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/bulk_actions/bulk_actions.test.tsx
@@ -5,8 +5,9 @@
* 2.0.
*/
import React, { useMemo, useReducer } from 'react';
-
+import { identity } from 'lodash';
import { render, screen, within, fireEvent, waitFor } from '@testing-library/react';
+import { FieldFormatsRegistry } from '@kbn/field-formats-plugin/common';
import { waitForEuiPopoverOpen } from '@elastic/eui/lib/test/rtl';
import { AlertsTable } from '../alerts_table';
import {
@@ -44,6 +45,13 @@ const columns = [
},
];
+const mockFieldFormatsRegistry = {
+ deserialize: jest.fn().mockImplementation(() => ({
+ id: 'string',
+ convert: jest.fn().mockImplementation(identity),
+ })),
+} as unknown as FieldFormatsRegistry;
+
const mockCaseService = createCasesServiceMock();
const mockKibana = jest.fn().mockReturnValue({
@@ -58,6 +66,62 @@ const mockKibana = jest.fn().mockReturnValue({
},
});
+const oldAlertsData = [
+ [
+ {
+ field: AlertsField.name,
+ value: ['one'],
+ },
+ {
+ field: AlertsField.reason,
+ value: ['two'],
+ },
+ ],
+ [
+ {
+ field: AlertsField.name,
+ value: ['three'],
+ },
+ {
+ field: AlertsField.reason,
+ value: ['four'],
+ },
+ ],
+] as FetchAlertData['oldAlertsData'];
+
+const ecsAlertsData = [
+ [
+ {
+ '@timestamp': ['2023-01-28T10:48:49.559Z'],
+ _id: 'SomeId',
+ _index: 'SomeIndex',
+ kibana: {
+ alert: {
+ rule: {
+ name: ['one'],
+ },
+ reason: ['two'],
+ },
+ },
+ },
+ ],
+ [
+ {
+ '@timestamp': ['2023-01-27T10:48:49.559Z'],
+ _id: 'SomeId2',
+ _index: 'SomeIndex',
+ kibana: {
+ alert: {
+ rule: {
+ name: ['three'],
+ },
+ reason: ['four'],
+ },
+ },
+ },
+ ],
+] as FetchAlertData['ecsAlertsData'];
+
jest.mock('@kbn/kibana-react-plugin/public', () => {
const original = jest.requireActual('@kbn/kibana-react-plugin/public');
@@ -166,23 +230,33 @@ describe('AlertsTable.BulkActions', () => {
columns,
deletedEventIds: [],
disabledCellActions: [],
- pageSize: 2,
pageSizeOptions: [2, 4],
leadingControlColumns: [],
trailingControlColumns: [],
- useFetchAlertsData: () => alertsData,
visibleColumns: columns.map((c) => c.id),
'data-test-subj': 'testTable',
- updatedAt: Date.now(),
onToggleColumn: () => {},
onResetColumns: () => {},
onChangeVisibleColumns: () => {},
browserFields: {},
query: {},
+ pagination: { pageIndex: 0, pageSize: 1 },
+ sort: [],
+ isLoading: false,
+ alerts,
+ oldAlertsData,
+ ecsAlertsData,
+ getInspectQuery: () => ({ request: [], response: [] }),
+ refetch: refreshMockFn,
+ alertsCount: alerts.length,
+ onSortChange: () => {},
+ onPageChange: () => {},
+ fieldFormats: mockFieldFormatsRegistry,
};
const tablePropsWithBulkActions = {
...tableProps,
+ pagination: { pageIndex: 0, pageSize: 10 },
alertsTableConfiguration: {
...alertsTableConfiguration,
@@ -239,6 +313,7 @@ describe('AlertsTable.BulkActions', () => {
isAllSelected: false,
areAllVisibleRowsSelected: false,
rowCount: 2,
+ updatedAt: Date.now(),
};
const AlertsTableWithBulkActionsContext: React.FunctionComponent<
@@ -350,6 +425,7 @@ describe('AlertsTable.BulkActions', () => {
rowCount: 1,
rowSelection: new Map([[0, { isLoading: false }]]),
},
+ alerts: newAlertsData.alerts,
alertsTableConfiguration: {
...alertsTableConfiguration,
useBulkActions: () => [
@@ -492,13 +568,14 @@ describe('AlertsTable.BulkActions', () => {
_id: 'alert2',
},
] as unknown as Alerts;
+ const allAlerts = [...alerts, ...secondPageAlerts];
const props = {
...tablePropsWithBulkActions,
- alerts: secondPageAlerts,
+ alerts: allAlerts,
+ alertsCount: allAlerts.length,
useFetchAlertsData: () => {
return {
...alertsData,
- alerts: secondPageAlerts,
alertsCount: secondPageAlerts.length,
activePage: 1,
};
@@ -508,6 +585,7 @@ describe('AlertsTable.BulkActions', () => {
areAllVisibleRowsSelected: true,
rowSelection: new Map([[0, { isLoading: false }]]),
},
+ pagination: { pageIndex: 1, pageSize: 2 },
};
render();
@@ -965,7 +1043,6 @@ describe('AlertsTable.BulkActions', () => {
it('should call refresh function of use fetch alerts when bulk action 3 is clicked', async () => {
const props = {
...tablePropsWithBulkActions,
-
initialBulkActionsState: {
...defaultBulkActionsState,
areAllVisibleRowsSelected: false,
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/bulk_actions/components/column_header.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/bulk_actions/components/column_header.tsx
index 5fcd2d3eb348c..e51c14b710921 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/bulk_actions/components/column_header.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/bulk_actions/components/column_header.tsx
@@ -6,7 +6,7 @@
*/
import { EuiCheckbox } from '@elastic/eui';
-import React, { ChangeEvent, useContext } from 'react';
+import React, { ChangeEvent, useContext, useCallback } from 'react';
import { BulkActionsVerbs } from '../../../../../types';
import { COLUMN_HEADER_ARIA_LABEL } from '../translations';
import { AlertsTableContext } from '../../contexts/alerts_table_context';
@@ -16,18 +16,23 @@ const BulkActionsHeaderComponent: React.FunctionComponent = () => {
bulkActions: [{ isAllSelected, areAllVisibleRowsSelected }, updateSelectedRows],
} = useContext(AlertsTableContext);
+ const onChange = useCallback(
+ (e: ChangeEvent) => {
+ if (e.target.checked) {
+ updateSelectedRows({ action: BulkActionsVerbs.selectCurrentPage });
+ } else {
+ updateSelectedRows({ action: BulkActionsVerbs.clear });
+ }
+ },
+ [updateSelectedRows]
+ );
+
return (
) => {
- if (e.target.checked) {
- updateSelectedRows({ action: BulkActionsVerbs.selectCurrentPage });
- } else {
- updateSelectedRows({ action: BulkActionsVerbs.clear });
- }
- }}
+ onChange={onChange}
data-test-subj="bulk-actions-header"
/>
);
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/bulk_actions/components/row_cell.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/bulk_actions/components/row_cell.tsx
index 4e029f1eaf38a..efa91e90f1a51 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/bulk_actions/components/row_cell.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/bulk_actions/components/row_cell.tsx
@@ -6,7 +6,7 @@
*/
import { EuiCheckbox, EuiLoadingSpinner } from '@elastic/eui';
-import React, { ChangeEvent } from 'react';
+import React, { ChangeEvent, useCallback } from 'react';
import { useContext } from 'react';
import { AlertsTableContext } from '../../contexts/alerts_table_context';
import { BulkActionsVerbs } from '../../../../../types';
@@ -17,7 +17,16 @@ const BulkActionsRowCellComponent = ({ rowIndex }: { rowIndex: number }) => {
} = useContext(AlertsTableContext);
const isChecked = rowSelection.has(rowIndex);
const isLoading = isChecked && rowSelection.get(rowIndex)?.isLoading;
-
+ const onChange = useCallback(
+ (e: ChangeEvent) => {
+ if (e.target.checked) {
+ updateSelectedRows({ action: BulkActionsVerbs.add, rowIndex });
+ } else {
+ updateSelectedRows({ action: BulkActionsVerbs.delete, rowIndex });
+ }
+ },
+ [rowIndex, updateSelectedRows]
+ );
if (isLoading) {
return ;
}
@@ -29,13 +38,7 @@ const BulkActionsRowCellComponent = ({ rowIndex }: { rowIndex: number }) => {
) => {
- if (e.target.checked) {
- updateSelectedRows({ action: BulkActionsVerbs.add, rowIndex });
- } else {
- updateSelectedRows({ action: BulkActionsVerbs.delete, rowIndex });
- }
- }}
+ onChange={onChange}
data-test-subj="bulk-actions-row-cell"
/>
);
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/bulk_actions/get_leading_control_column.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/bulk_actions/get_leading_control_column.tsx
index b249321311ee9..2cf6b678cf2a3 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/bulk_actions/get_leading_control_column.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/bulk_actions/get_leading_control_column.tsx
@@ -5,23 +5,25 @@
* 2.0.
*/
-import React from 'react';
+import React, { memo } from 'react';
import { EuiDataGridCellValueElementProps, EuiDataGridControlColumn } from '@elastic/eui';
import { BulkActionsHeader, BulkActionsRowCell } from './components';
export type GetLeadingControlColumn = () => EuiDataGridControlColumn;
-export const getLeadingControlColumn: GetLeadingControlColumn = (): EuiDataGridControlColumn => ({
- id: 'bulkActions',
- width: 30,
- headerCellRender: () => {
- return ;
- },
- rowCellRender: (cveProps: EuiDataGridCellValueElementProps) => {
+const BulkActionLeadingControlColumnRowCellRender = memo(
+ (cveProps: EuiDataGridCellValueElementProps) => {
const { visibleRowIndex: rowIndex } = cveProps as EuiDataGridCellValueElementProps & {
visibleRowIndex: number;
};
return ;
- },
+ }
+);
+
+export const getLeadingControlColumn: GetLeadingControlColumn = (): EuiDataGridControlColumn => ({
+ id: 'bulkActions',
+ width: 30,
+ headerCellRender: BulkActionsHeader,
+ rowCellRender: BulkActionLeadingControlColumnRowCellRender,
});
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/bulk_actions/reducer.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/bulk_actions/reducer.ts
index db9c8523fb0af..38f79e1eba9e0 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/bulk_actions/reducer.ts
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/bulk_actions/reducer.ts
@@ -35,6 +35,7 @@ export const bulkActionsReducer = (
nextState.isAllSelected = false;
} else if (action === BulkActionsVerbs.rowCountUpdate && rowCount !== undefined) {
nextState.rowCount = rowCount;
+ nextState.updatedAt = Date.now();
} else if (action === BulkActionsVerbs.updateAllLoadingState) {
const nextRowSelection = new Map(
Array.from(rowSelection.keys()).map((idx: number) => [idx, { isLoading }])
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/cases/cell.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/cases/cell.tsx
index 005e6db83d188..9c81da7906313 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/cases/cell.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/cases/cell.tsx
@@ -30,7 +30,7 @@ const CasesCellComponent: React.FC = (props) => {
const { isLoading, alert, cases, caseAppId } = props;
const { navigateToCaseView } = useCaseViewNavigation(caseAppId);
- const caseIds = alert[ALERT_CASE_IDS] ?? [];
+ const caseIds = (alert && alert[ALERT_CASE_IDS]) ?? [];
const validCases = caseIds
.map((id) => cases.get(id))
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/cells/alert_lifecycle_status_cell.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/cells/alert_lifecycle_status_cell.tsx
index 27f5558a13035..c050ac2e4a3e2 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/cells/alert_lifecycle_status_cell.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/cells/alert_lifecycle_status_cell.tsx
@@ -28,10 +28,10 @@ const AlertLifecycleStatusCellComponent: React.FC = (props)
return null;
}
- const alertStatus = alert[ALERT_STATUS] ?? [];
+ const alertStatus = (alert && alert[ALERT_STATUS]) ?? [];
if (Array.isArray(alertStatus) && alertStatus.length) {
- const flapping = alert[ALERT_FLAPPING] ?? [];
+ const flapping = (alert && alert[ALERT_FLAPPING]) ?? [];
return (
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/cells/default_cell.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/cells/default_cell.tsx
index 2ecd96c2e8bcd..d34bc07b90df5 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/cells/default_cell.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/cells/default_cell.tsx
@@ -9,7 +9,7 @@ import React, { memo } from 'react';
import { CellComponentProps } from '../types';
const DefaultCellComponent: React.FC = ({ columnId, alert }) => {
- const value = alert[columnId] ?? [];
+ const value = (alert && alert[columnId]) ?? [];
if (Array.isArray(value)) {
return <>{value.length ? value.join(', ') : '--'}>;
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/cells/render_cell_value.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/cells/render_cell_value.tsx
index 1bd62f120497a..2d77b8a94e46a 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/cells/render_cell_value.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/cells/render_cell_value.tsx
@@ -6,7 +6,7 @@
*/
import { isEmpty } from 'lodash';
-import React, { type ReactNode } from 'react';
+import React from 'react';
import {
ALERT_DURATION,
AlertConsumers,
@@ -22,16 +22,10 @@ import {
FieldFormatParams,
FieldFormatsRegistry,
} from '@kbn/field-formats-plugin/common';
-import { EuiBadge, EuiLink } from '@elastic/eui';
+import { EuiBadge, EuiLink, RenderCellValue } from '@elastic/eui';
import { alertProducersData, observabilityFeatureIds } from '../constants';
-import { GetRenderCellValue } from '../../../../types';
import { useKibana } from '../../../../common/lib/kibana';
-interface Props {
- columnId: string;
- data: any;
-}
-
export const getMappedNonEcsValue = ({
data,
fieldName,
@@ -59,22 +53,17 @@ const getRenderValue = (mappedNonEcsValue: any) => {
return '—';
};
-export const getRenderCellValue = (fieldFormats: FieldFormatsRegistry): GetRenderCellValue => {
+export const getRenderCellValue: RenderCellValue = ({ columnId, data, fieldFormats }) => {
const alertValueFormatter = getAlertFormatters(fieldFormats);
+ if (data == null) return null;
- return () =>
- (props): ReactNode => {
- const { columnId, data } = props as Props;
- if (data == null) return null;
-
- const mappedNonEcsValue = getMappedNonEcsValue({
- data,
- fieldName: columnId,
- });
- const value = getRenderValue(mappedNonEcsValue);
+ const mappedNonEcsValue = getMappedNonEcsValue({
+ data,
+ fieldName: columnId,
+ });
+ const value = getRenderValue(mappedNonEcsValue);
- return alertValueFormatter(columnId, value, data);
- };
+ return alertValueFormatter(columnId, value, data);
};
const defaultParam: Record = {
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/configuration.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/configuration.tsx
index a170e67adc5b5..59e160cb77289 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/configuration.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/configuration.tsx
@@ -159,7 +159,7 @@ export const createGenericAlertsTableConfigurations = (
{
id: ALERT_TABLE_GENERIC_CONFIG_ID,
columns: [firstColumn, ...genericColumns],
- getRenderCellValue: getRenderCellValue(fieldFormats),
+ getRenderCellValue,
useInternalFlyout: getDefaultAlertFlyout(columns, getAlertFormatters(fieldFormats)),
sort,
useActionsColumn,
@@ -167,7 +167,7 @@ export const createGenericAlertsTableConfigurations = (
{
id: ALERT_TABLE_GLOBAL_CONFIG_ID,
columns,
- getRenderCellValue: getRenderCellValue(fieldFormats),
+ getRenderCellValue,
useInternalFlyout: getDefaultAlertFlyout(columns, getAlertFormatters(fieldFormats)),
sort,
useActionsColumn,
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/alert_mute/use_alert_muted_state.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/alert_mute/use_alert_muted_state.ts
index ea2ffa2f21bc9..e59a683d68b91 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/alert_mute/use_alert_muted_state.ts
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/alert_mute/use_alert_muted_state.ts
@@ -10,10 +10,10 @@ import { ALERT_INSTANCE_ID, ALERT_RULE_UUID } from '@kbn/rule-data-utils';
import { AlertsTableContext } from '../../../../..';
import { Alert } from '../../../../../types';
-export const useAlertMutedState = (alert: Alert) => {
+export const useAlertMutedState = (alert?: Alert) => {
const { mutedAlerts } = useContext(AlertsTableContext);
- const alertInstanceId = alert[ALERT_INSTANCE_ID]?.[0];
- const ruleId = alert[ALERT_RULE_UUID]?.[0];
+ const alertInstanceId = alert && alert[ALERT_INSTANCE_ID]?.[0];
+ const ruleId = alert && alert[ALERT_RULE_UUID]?.[0];
return useMemo(() => {
const rule = ruleId ? mutedAlerts[ruleId] : [];
return {
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_actions_column.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_actions_column.ts
index c8cee91b579e9..0378388474d48 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_actions_column.ts
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_actions_column.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { useCallback, useContext } from 'react';
+import { useCallback, useContext, useMemo } from 'react';
import { AlertsTableContext } from '../contexts/alerts_table_context';
import { UseActionsColumnRegistry, BulkActionsVerbs } from '../../../../types';
@@ -20,12 +20,15 @@ export const useActionsColumn = ({ options }: UseActionsColumnProps) => {
bulkActions: [, updateBulkActionsState],
} = useContext(AlertsTableContext);
- const useUserActionsColumn = options
- ? options
- : () => ({
- renderCustomActionsRow: undefined,
- width: undefined,
- });
+ const defaultActionsColum = useCallback(
+ () => ({
+ renderCustomActionsRow: undefined,
+ width: undefined,
+ }),
+ []
+ );
+
+ const useUserActionsColumn = options ? options : defaultActionsColum;
const { renderCustomActionsRow, width: actionsColumnWidth = DEFAULT_ACTIONS_COLUMNS_WIDTH } =
useUserActionsColumn();
@@ -44,9 +47,11 @@ export const useActionsColumn = ({ options }: UseActionsColumnProps) => {
[updateBulkActionsState]
);
- return {
- renderCustomActionsRow,
- actionsColumnWidth,
- getSetIsActionLoadingCallback,
- };
+ return useMemo(() => {
+ return {
+ renderCustomActionsRow,
+ actionsColumnWidth,
+ getSetIsActionLoadingCallback,
+ };
+ }, [renderCustomActionsRow, actionsColumnWidth, getSetIsActionLoadingCallback]);
};
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_bulk_actions.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_bulk_actions.ts
index aec60b89e5e1e..3bc36d1bdd049 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_bulk_actions.ts
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_bulk_actions.ts
@@ -105,8 +105,10 @@ export const useBulkAddToCaseActions = ({
}: UseBulkAddToCaseActionsProps): BulkActionsConfig[] => {
const { cases: casesService } = useKibana<{ cases?: CasesService }>().services;
- const userCasesPermissions = casesService?.helpers.canUseCases(casesConfig?.owner ?? []);
- const CasesContext = casesService?.ui.getCasesContext();
+ const userCasesPermissions = useMemo(() => {
+ return casesService?.helpers.canUseCases(casesConfig?.owner ?? []);
+ }, [casesConfig?.owner, casesService]);
+ const CasesContext = useMemo(() => casesService?.ui.getCasesContext(), [casesService]);
const isCasesContextAvailable = Boolean(casesService && CasesContext);
const onSuccess = useCallback(() => {
@@ -197,50 +199,66 @@ export const useBulkUntrackActions = ({
const { mutateAsync: untrackAlerts } = useBulkUntrackAlerts();
const { mutateAsync: untrackAlertsByQuery } = useBulkUntrackAlertsByQuery();
- // Check if at least one Observability feature is enabled
- if (!application?.capabilities) return [];
- const hasApmPermission = application.capabilities.apm?.['alerting:show'];
- const hasInfrastructurePermission = application.capabilities.infrastructure?.show;
- const hasLogsPermission = application.capabilities.logs?.show;
- const hasUptimePermission = application.capabilities.uptime?.show;
- const hasSloPermission = application.capabilities.slo?.show;
- const hasObservabilityPermission = application.capabilities.observability?.show;
+ const hasApmPermission = application?.capabilities.apm?.['alerting:show'];
+ const hasInfrastructurePermission = application?.capabilities.infrastructure?.show;
+ const hasLogsPermission = application?.capabilities.logs?.show;
+ const hasUptimePermission = application?.capabilities.uptime?.show;
+ const hasSloPermission = application?.capabilities.slo?.show;
+ const hasObservabilityPermission = application?.capabilities.observability?.show;
- if (
- !hasApmPermission &&
- !hasInfrastructurePermission &&
- !hasLogsPermission &&
- !hasUptimePermission &&
- !hasSloPermission &&
- !hasObservabilityPermission
- )
- return [];
-
- return [
- {
- label: MARK_AS_UNTRACKED,
- key: 'mark-as-untracked',
- disableOnQuery: false,
- disabledLabel: MARK_AS_UNTRACKED,
- 'data-test-subj': 'mark-as-untracked',
- onClick: async (alerts?: TimelineItem[]) => {
- if (!alerts) return;
- const alertUuids = alerts.map((alert) => alert._id);
- const indices = alerts.map((alert) => alert._index ?? '');
- try {
- setIsBulkActionsLoading(true);
- if (isAllSelected) {
- await untrackAlertsByQuery({ query, featureIds });
- } else {
- await untrackAlerts({ indices, alertUuids });
+ return useMemo(() => {
+ // Check if at least one Observability feature is enabled
+ if (!application?.capabilities) return [];
+ if (
+ !hasApmPermission &&
+ !hasInfrastructurePermission &&
+ !hasLogsPermission &&
+ !hasUptimePermission &&
+ !hasSloPermission &&
+ !hasObservabilityPermission
+ )
+ return [];
+ return [
+ {
+ label: MARK_AS_UNTRACKED,
+ key: 'mark-as-untracked',
+ disableOnQuery: false,
+ disabledLabel: MARK_AS_UNTRACKED,
+ 'data-test-subj': 'mark-as-untracked',
+ onClick: async (alerts?: TimelineItem[]) => {
+ if (!alerts) return;
+ const alertUuids = alerts.map((alert) => alert._id);
+ const indices = alerts.map((alert) => alert._index ?? '');
+ try {
+ setIsBulkActionsLoading(true);
+ if (isAllSelected) {
+ await untrackAlertsByQuery({ query, featureIds });
+ } else {
+ await untrackAlerts({ indices, alertUuids });
+ }
+ onSuccess();
+ } finally {
+ setIsBulkActionsLoading(false);
}
- onSuccess();
- } finally {
- setIsBulkActionsLoading(false);
- }
+ },
},
- },
- ];
+ ];
+ }, [
+ onSuccess,
+ setIsBulkActionsLoading,
+ untrackAlerts,
+ application?.capabilities,
+ hasApmPermission,
+ hasInfrastructurePermission,
+ hasLogsPermission,
+ hasUptimePermission,
+ hasSloPermission,
+ hasObservabilityPermission,
+ featureIds,
+ query,
+ isAllSelected,
+ untrackAlertsByQuery,
+ ]);
};
export function useBulkActions({
@@ -254,14 +272,17 @@ export function useBulkActions({
const {
bulkActions: [bulkActionsState, updateBulkActionsState],
} = useContext(AlertsTableContext);
- const configBulkActionPanels = useBulkActionsConfig(query);
+ const configBulkActionPanels = useBulkActionsConfig(query, refresh);
const clearSelection = useCallback(() => {
updateBulkActionsState({ action: BulkActionsVerbs.clear });
}, [updateBulkActionsState]);
- const setIsBulkActionsLoading = (isLoading: boolean = true) => {
- updateBulkActionsState({ action: BulkActionsVerbs.updateAllLoadingState, isLoading });
- };
+ const setIsBulkActionsLoading = useCallback(
+ (isLoading: boolean = true) => {
+ updateBulkActionsState({ action: BulkActionsVerbs.updateAllLoadingState, isLoading });
+ },
+ [updateBulkActionsState]
+ );
const caseBulkActions = useBulkAddToCaseActions({ casesConfig, refresh, clearSelection });
const untrackBulkActions = useBulkUntrackActions({
setIsBulkActionsLoading,
@@ -271,32 +292,42 @@ export function useBulkActions({
featureIds,
isAllSelected: bulkActionsState.isAllSelected,
});
-
- const initialItems = [
- ...caseBulkActions,
- // SECURITY SOLUTION WORKAROUND: Disable untrack action for SIEM
- ...(featureIds?.includes('siem') ? [] : untrackBulkActions),
- ];
-
- const bulkActions = initialItems.length
- ? addItemsToInitialPanel({
- panels: configBulkActionPanels,
- items: initialItems,
- })
- : configBulkActionPanels;
+ const initialItems = useMemo(() => {
+ return [...caseBulkActions, ...(featureIds?.includes('siem') ? [] : untrackBulkActions)];
+ }, [caseBulkActions, featureIds, untrackBulkActions]);
+ const bulkActions = useMemo(() => {
+ return initialItems.length
+ ? addItemsToInitialPanel({
+ panels: configBulkActionPanels,
+ items: initialItems,
+ })
+ : configBulkActionPanels;
+ }, [configBulkActionPanels, initialItems]);
const isBulkActionsColumnActive = bulkActions.length !== 0;
useEffect(() => {
- updateBulkActionsState({ action: BulkActionsVerbs.rowCountUpdate, rowCount: alerts.length });
+ updateBulkActionsState({
+ action: BulkActionsVerbs.rowCountUpdate,
+ rowCount: alerts.length,
+ });
}, [alerts, updateBulkActionsState]);
-
- return {
- isBulkActionsColumnActive,
- getBulkActionsLeadingControlColumn,
- bulkActionsState,
+ return useMemo(() => {
+ return {
+ isBulkActionsColumnActive,
+ getBulkActionsLeadingControlColumn,
+ bulkActionsState,
+ bulkActions,
+ setIsBulkActionsLoading,
+ clearSelection,
+ updateBulkActionsState,
+ };
+ }, [
bulkActions,
- setIsBulkActionsLoading,
+ bulkActionsState,
clearSelection,
- };
+ isBulkActionsColumnActive,
+ setIsBulkActionsLoading,
+ updateBulkActionsState,
+ ]);
}
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_fetch_alerts.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_fetch_alerts.test.tsx
index fdc7282f6c817..b3a9ba275e6aa 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_fetch_alerts.test.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_fetch_alerts.test.tsx
@@ -83,7 +83,6 @@ const expectedResponse: FetchAlertResp = {
refetch: expect.anything(),
isInitializing: true,
totalAlerts: -1,
- updatedAt: 0,
oldAlertsData: [],
ecsAlertsData: [],
};
@@ -162,7 +161,6 @@ describe('useFetchAlerts', () => {
],
totalAlerts: 2,
isInitializing: false,
- updatedAt: 1609502400000,
getInspectQuery: expect.anything(),
refetch: expect.anything(),
ecsAlertsData: [
@@ -281,7 +279,6 @@ describe('useFetchAlerts', () => {
refetch: expect.anything(),
isInitializing: true,
totalAlerts: -1,
- updatedAt: 0,
},
]);
});
@@ -300,7 +297,6 @@ describe('useFetchAlerts', () => {
refetch: expect.anything(),
isInitializing: true,
totalAlerts: -1,
- updatedAt: 0,
},
]);
@@ -321,7 +317,6 @@ describe('useFetchAlerts', () => {
refetch: expect.anything(),
isInitializing: true,
totalAlerts: -1,
- updatedAt: 0,
},
]);
});
@@ -432,7 +427,6 @@ describe('useFetchAlerts', () => {
refetch: expect.anything(),
isInitializing: true,
totalAlerts: -1,
- updatedAt: 0,
},
]);
});
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_fetch_alerts.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_fetch_alerts.tsx
index 9b62174661e62..5a0a5efb18c32 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_fetch_alerts.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_fetch_alerts.tsx
@@ -63,7 +63,6 @@ export interface FetchAlertResp {
getInspectQuery: GetInspectQuery;
refetch: Refetch;
totalAlerts: number;
- updatedAt: number;
}
type AlertResponseState = Omit;
@@ -105,7 +104,6 @@ const initialAlertState: AlertStateReducer = {
ecsAlertsData: [],
totalAlerts: -1,
isInitializing: true,
- updatedAt: 0,
},
};
@@ -123,7 +121,6 @@ function alertReducer(state: AlertStateReducer, action: AlertActions) {
totalAlerts: action.totalAlerts,
oldAlertsData: action.oldAlertsData,
ecsAlertsData: action.ecsAlertsData,
- updatedAt: Date.now(),
},
};
case 'resetPagination':
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_pagination.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_pagination.ts
index fb2e0f81b0f0c..e713c1820cb81 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_pagination.ts
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_pagination.ts
@@ -14,7 +14,6 @@ type PaginationProps = RuleRegistrySearchRequestPagination & {
};
export type UsePagination = (props: PaginationProps) => {
- pagination: RuleRegistrySearchRequestPagination;
onChangePageSize: (pageSize: number) => void;
onChangePageIndex: (pageIndex: number) => void;
onPaginateFlyoutNext: () => void;
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/maintenance_windows/cell.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/maintenance_windows/cell.tsx
index 9a654f46560d4..e46807d9b38eb 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/maintenance_windows/cell.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/maintenance_windows/cell.tsx
@@ -71,14 +71,14 @@ export const MaintenanceWindowCell = memo((props: CellComponentProps) => {
const { alert, maintenanceWindows, isLoading } = props;
const validMaintenanceWindows = useMemo(() => {
- const maintenanceWindowIds = alert[ALERT_MAINTENANCE_WINDOW_IDS] || [];
+ const maintenanceWindowIds = (alert && alert[ALERT_MAINTENANCE_WINDOW_IDS]) || [];
return maintenanceWindowIds
.map((id) => maintenanceWindows.get(id))
.filter(isMaintenanceWindowValid);
}, [alert, maintenanceWindows]);
const idsWithoutMaintenanceWindow = useMemo(() => {
- const maintenanceWindowIds = alert[ALERT_MAINTENANCE_WINDOW_IDS] || [];
+ const maintenanceWindowIds = (alert && alert[ALERT_MAINTENANCE_WINDOW_IDS]) || [];
return maintenanceWindowIds.filter((id) => !maintenanceWindows.get(id));
}, [alert, maintenanceWindows]);
@@ -91,7 +91,7 @@ export const MaintenanceWindowCell = memo((props: CellComponentProps) => {
maintenanceWindows={validMaintenanceWindows}
maintenanceWindowIds={idsWithoutMaintenanceWindow}
isLoading={isLoading}
- timestamp={alert[TIMESTAMP]?.[0]}
+ timestamp={alert && alert[TIMESTAMP]?.[0]}
/>
);
});
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/toolbar/components/inspect/index.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/toolbar/components/inspect/index.tsx
index 1a4215a55ca17..2be7ae9b15bcc 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/toolbar/components/inspect/index.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/toolbar/components/inspect/index.tsx
@@ -6,7 +6,7 @@
*/
import { EuiButtonIcon } from '@elastic/eui';
-import React, { useState } from 'react';
+import React, { useState, memo, useCallback } from 'react';
import { GetInspectQuery } from '../../../../../../types';
import { HoverVisibilityContainer } from './hover_visibility_container';
@@ -15,19 +15,19 @@ import { ModalInspectQuery } from './modal';
import * as i18n from './translations';
export const BUTTON_CLASS = 'inspectButtonComponent';
+const VISIBILITY_CLASSES = [BUTTON_CLASS];
interface InspectButtonContainerProps {
hide?: boolean;
children: React.ReactNode;
}
-export const InspectButtonContainer: React.FC = ({
- children,
- hide,
-}) => (
-
- {children}
-
+export const InspectButtonContainer: React.FC = memo(
+ ({ children, hide }) => (
+
+ {children}
+
+ )
);
interface InspectButtonProps {
@@ -43,13 +43,13 @@ const InspectButtonComponent: React.FC = ({
}) => {
const [isShowingModal, setIsShowingModal] = useState(false);
- const onOpenModal = () => {
+ const onOpenModal = useCallback(() => {
setIsShowingModal(true);
- };
+ }, []);
- const onCloseModal = () => {
+ const onCloseModal = useCallback(() => {
setIsShowingModal(false);
- };
+ }, []);
return (
<>
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/toolbar/toolbar_visibility.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/toolbar/toolbar_visibility.tsx
index 48742adcf6ea2..54e2466b524cc 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/toolbar/toolbar_visibility.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/toolbar/toolbar_visibility.tsx
@@ -9,9 +9,10 @@ import {
EuiDataGridToolBarAdditionalControlsOptions,
EuiDataGridToolBarVisibilityOptions,
} from '@elastic/eui';
-import React, { lazy, Suspense } from 'react';
+import React, { lazy, Suspense, memo, useMemo, useContext } from 'react';
import { BrowserFields } from '@kbn/rule-registry-plugin/common';
import { AlertsCount } from './components/alerts_count/alerts_count';
+import { AlertsTableContext } from '../contexts/alerts_table_context';
import type {
Alerts,
BulkActionsPanelConfig,
@@ -26,31 +27,69 @@ import { ALERTS_TABLE_TITLE } from '../translations';
const BulkActionsToolbar = lazy(() => import('../bulk_actions/components/toolbar'));
-const rightControl = ({
- controls,
- updatedAt,
- getInspectQuery,
- showInspectButton,
-}: {
- controls?: EuiDataGridToolBarAdditionalControlsOptions;
- updatedAt: number;
- getInspectQuery: GetInspectQuery;
- showInspectButton: boolean;
-}) => {
- return (
- <>
- {showInspectButton && (
-
- )}
-
- {controls?.right}
- >
- );
-};
+const RightControl = memo(
+ ({
+ controls,
+ getInspectQuery,
+ showInspectButton,
+ }: {
+ controls?: EuiDataGridToolBarAdditionalControlsOptions;
+ getInspectQuery: GetInspectQuery;
+ showInspectButton: boolean;
+ }) => {
+ const {
+ bulkActions: [bulkActionsState],
+ } = useContext(AlertsTableContext);
+ return (
+ <>
+ {showInspectButton && (
+
+ )}
+
+ {controls?.right}
+ >
+ );
+ }
+);
+
+const LeftAppendControl = memo(
+ ({
+ alertsCount,
+ hasBrowserFields,
+ columnIds,
+ browserFields,
+ onResetColumns,
+ onToggleColumn,
+ fieldBrowserOptions,
+ }: {
+ alertsCount: number;
+ columnIds: string[];
+ onToggleColumn: (columnId: string) => void;
+ onResetColumns: () => void;
+ controls?: EuiDataGridToolBarAdditionalControlsOptions;
+ fieldBrowserOptions?: FieldBrowserOptions;
+ hasBrowserFields: boolean;
+ browserFields: BrowserFields;
+ }) => {
+ return (
+ <>
+
+ {hasBrowserFields && (
+
+ )}
+ >
+ );
+ }
+);
-const getDefaultVisibility = ({
+const useGetDefaultVisibility = ({
alertsCount,
- updatedAt,
columnIds,
onToggleColumn,
onResetColumns,
@@ -59,9 +98,9 @@ const getDefaultVisibility = ({
fieldBrowserOptions,
getInspectQuery,
showInspectButton,
+ toolbarVisibilityProp,
}: {
alertsCount: number;
- updatedAt: number;
columnIds: string[];
onToggleColumn: (columnId: string) => void;
onResetColumns: () => void;
@@ -70,44 +109,58 @@ const getDefaultVisibility = ({
fieldBrowserOptions?: FieldBrowserOptions;
getInspectQuery: GetInspectQuery;
showInspectButton: boolean;
+ toolbarVisibilityProp?: EuiDataGridToolBarVisibilityOptions;
}): EuiDataGridToolBarVisibilityOptions => {
- const hasBrowserFields = Object.keys(browserFields).length > 0;
- const additionalControls = {
- right: rightControl({ controls, updatedAt, getInspectQuery, showInspectButton }),
- left: {
- append: (
- <>
-
- {hasBrowserFields && (
- {
+ const hasBrowserFields = Object.keys(browserFields).length > 0;
+ return {
+ additionalControls: {
+ right: (
+
+ ),
+ left: {
+ append: (
+
- )}
- >
- ),
- },
- };
-
- return {
- additionalControls,
- showColumnSelector: {
- allowHide: false,
- },
- showSortSelector: true,
- };
+ ),
+ },
+ },
+ showColumnSelector: {
+ allowHide: false,
+ },
+ showSortSelector: true,
+ };
+ }, [
+ alertsCount,
+ browserFields,
+ columnIds,
+ fieldBrowserOptions,
+ getInspectQuery,
+ onResetColumns,
+ onToggleColumn,
+ showInspectButton,
+ controls,
+ ]);
+ return defaultVisibility;
};
-export const getToolbarVisibility = ({
+export const useGetToolbarVisibility = ({
bulkActions,
alertsCount,
rowSelection,
alerts,
isLoading,
- updatedAt,
columnIds,
onToggleColumn,
onResetColumns,
@@ -126,7 +179,6 @@ export const getToolbarVisibility = ({
rowSelection: RowSelection;
alerts: Alerts;
isLoading: boolean;
- updatedAt: number;
columnIds: string[];
onToggleColumn: (columnId: string) => void;
onResetColumns: () => void;
@@ -141,9 +193,20 @@ export const getToolbarVisibility = ({
toolbarVisibilityProp?: EuiDataGridToolBarVisibilityOptions;
}): EuiDataGridToolBarVisibilityOptions => {
const selectedRowsCount = rowSelection.size;
- const defaultVisibility = getDefaultVisibility({
+ const defaultVisibilityProps = useMemo(() => {
+ return {
+ alertsCount,
+ columnIds,
+ onToggleColumn,
+ onResetColumns,
+ browserFields,
+ controls,
+ fieldBrowserOptions,
+ getInspectQuery,
+ showInspectButton,
+ };
+ }, [
alertsCount,
- updatedAt,
columnIds,
onToggleColumn,
onResetColumns,
@@ -152,41 +215,64 @@ export const getToolbarVisibility = ({
fieldBrowserOptions,
getInspectQuery,
showInspectButton,
- });
- const isBulkActionsActive =
- selectedRowsCount === 0 || selectedRowsCount === undefined || bulkActions.length === 0;
-
- if (isBulkActionsActive)
- return {
- ...defaultVisibility,
- ...(toolbarVisibilityProp ?? {}),
- };
+ ]);
+ const defaultVisibility = useGetDefaultVisibility(defaultVisibilityProps);
+ const options = useMemo(() => {
+ const isBulkActionsActive =
+ selectedRowsCount === 0 || selectedRowsCount === undefined || bulkActions.length === 0;
- const options = {
- showColumnSelector: false,
- showSortSelector: false,
- additionalControls: {
- right: rightControl({ controls, updatedAt, getInspectQuery, showInspectButton }),
- left: {
- append: (
- <>
-
-
-
-
- >
- ),
- },
- },
- ...(toolbarVisibilityProp ?? {}),
- };
+ if (isBulkActionsActive) {
+ return {
+ ...defaultVisibility,
+ ...(toolbarVisibilityProp ?? {}),
+ };
+ } else {
+ return {
+ showColumnSelector: false,
+ showSortSelector: false,
+ additionalControls: {
+ right: (
+
+ ),
+ left: {
+ append: (
+ <>
+
+
+
+
+ >
+ ),
+ },
+ },
+ ...(toolbarVisibilityProp ?? {}),
+ };
+ }
+ }, [
+ alertsCount,
+ bulkActions,
+ defaultVisibility,
+ selectedRowsCount,
+ toolbarVisibilityProp,
+ alerts,
+ clearSelection,
+ refresh,
+ setIsBulkActionsLoading,
+ controls,
+ getInspectQuery,
+ showInspectButton,
+ ]);
return options;
};
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/types.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/types.ts
index 6ee30b24decdc..819cc42bd90e6 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/types.ts
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/types.ts
@@ -23,7 +23,7 @@ export interface Consumer {
export type ServerError = IHttpFetchError;
export interface CellComponentProps {
- alert: Alert;
+ alert?: Alert;
cases: AlertsTableProps['cases']['data'];
maintenanceWindows: AlertsTableProps['maintenanceWindows']['data'];
columnId: SystemCellId;
diff --git a/x-pack/plugins/triggers_actions_ui/public/index.ts b/x-pack/plugins/triggers_actions_ui/public/index.ts
index a23388e24abd8..207a5b56b6558 100644
--- a/x-pack/plugins/triggers_actions_ui/public/index.ts
+++ b/x-pack/plugins/triggers_actions_ui/public/index.ts
@@ -37,7 +37,6 @@ export type {
AlertsTableFlyoutBaseProps,
RuleEventLogListProps,
AlertTableFlyoutComponent,
- GetRenderCellValue,
FieldBrowserOptions,
FieldBrowserProps,
RuleDefinitionProps,
diff --git a/x-pack/plugins/triggers_actions_ui/public/types.ts b/x-pack/plugins/triggers_actions_ui/public/types.ts
index ccaf20e284a82..218b6365457bf 100644
--- a/x-pack/plugins/triggers_actions_ui/public/types.ts
+++ b/x-pack/plugins/triggers_actions_ui/public/types.ts
@@ -13,6 +13,7 @@ import type { ChartsPluginSetup } from '@kbn/charts-plugin/public';
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public';
+import { FieldFormatsRegistry } from '@kbn/field-formats-plugin/common';
import type {
IconType,
RecursivePartial,
@@ -24,6 +25,8 @@ import type {
EuiDataGridToolBarVisibilityOptions,
EuiSuperSelectOption,
EuiDataGridOnColumnResizeHandler,
+ EuiDataGridCellProps,
+ RenderCellValue,
EuiDataGridCellPopoverElementProps,
} from '@elastic/eui';
import type { RuleCreationValidConsumer, ValidFeatureId } from '@kbn/rule-data-utils';
@@ -561,16 +564,14 @@ export type AlertsTableProps = {
// defaultCellActions: TGridCellAction[];
deletedEventIds: string[];
disabledCellActions: string[];
- pageSize: number;
pageSizeOptions: number[];
id?: string;
- leadingControlColumns: EuiDataGridControlColumn[];
+ leadingControlColumns?: EuiDataGridControlColumn[];
showAlertStatusWithFlapping?: boolean;
- trailingControlColumns: EuiDataGridControlColumn[];
- useFetchAlertsData: () => FetchAlertData;
+ trailingControlColumns?: EuiDataGridControlColumn[];
+ cellContext?: EuiDataGridCellProps['cellContext'];
visibleColumns: string[];
'data-test-subj': string;
- updatedAt: number;
browserFields: any;
onToggleColumn: (columnId: string) => void;
onResetColumns: () => void;
@@ -589,7 +590,19 @@ export type AlertsTableProps = {
*/
dynamicRowHeight?: boolean;
featureIds?: ValidFeatureId[];
+ pagination: RuleRegistrySearchRequestPagination;
+ sort: SortCombinations[];
+ isLoading: boolean;
+ alerts: Alerts;
+ oldAlertsData: FetchAlertData['oldAlertsData'];
+ ecsAlertsData: FetchAlertData['ecsAlertsData'];
+ getInspectQuery: GetInspectQuery;
+ refetch: () => void;
+ alertsCount: number;
+ onSortChange: (sort: EuiDataGridSorting['columns']) => void;
+ onPageChange: (pagination: RuleRegistrySearchRequestPagination) => void;
renderCellPopover?: ReturnType;
+ fieldFormats: FieldFormatsRegistry;
} & Partial>;
export type SetFlyoutAlert = (alertId: string) => void;
@@ -599,17 +612,6 @@ export interface TimelineNonEcsData {
value?: string[] | null;
}
-// TODO We need to create generic type between our plugin, right now we have different one because of the old alerts table
-export type GetRenderCellValue = ({
- setFlyoutAlert,
- context,
-}: {
- setFlyoutAlert: SetFlyoutAlert;
- context?: T;
-}) => (
- props: EuiDataGridCellValueElementProps & { data: TimelineNonEcsData[] }
-) => React.ReactNode | JSX.Element;
-
export type GetRenderCellPopover = ({
context,
}: {
@@ -681,7 +683,8 @@ interface ItemsPanelConfig extends PanelConfig {
export type BulkActionsPanelConfig = ItemsPanelConfig | ContentPanelConfig;
export type UseBulkActionsRegistry = (
- query: Pick
+ query: Pick,
+ refresh: () => void
) => BulkActionsPanelConfig[];
export type UseCellActions = (props: {
@@ -750,7 +753,7 @@ export interface AlertsTableConfigurationRegistry {
footer: AlertTableFlyoutComponent;
};
sort?: SortCombinations[];
- getRenderCellValue?: GetRenderCellValue;
+ getRenderCellValue?: RenderCellValue;
getRenderCellPopover?: GetRenderCellPopover;
useActionsColumn?: UseActionsColumnRegistry;
useBulkActions?: UseBulkActionsRegistry;
@@ -762,11 +765,7 @@ export interface AlertsTableConfigurationRegistry {
showInspectButton?: boolean;
ruleTypeIds?: string[];
useFetchPageContext?: PreFetchPageContext;
-}
-
-export interface AlertsTableConfigurationRegistryWithActions
- extends AlertsTableConfigurationRegistry {
- actions: {
+ actions?: {
toggleColumn: (columnId: string) => void;
};
}
@@ -794,6 +793,7 @@ export interface BulkActionsState {
isAllSelected: boolean;
areAllVisibleRowsSelected: boolean;
rowCount: number;
+ updatedAt: number;
}
export type RowSelection = Map;
diff --git a/x-pack/test/observability_functional/apps/observability/pages/alerts/index.ts b/x-pack/test/observability_functional/apps/observability/pages/alerts/index.ts
index c23ffd1ac3c39..36ea9aeedce39 100644
--- a/x-pack/test/observability_functional/apps/observability/pages/alerts/index.ts
+++ b/x-pack/test/observability_functional/apps/observability/pages/alerts/index.ts
@@ -227,6 +227,7 @@ export default ({ getService }: FtrProviderContext) => {
const actionsButton = await observability.alerts.common.getActionsButtonByIndex(0);
await actionsButton.click();
await observability.alerts.common.viewRuleDetailsButtonClick();
+
expect(
await (await find.byCssSelector('[data-test-subj="breadcrumb first"]')).getVisibleText()
).to.eql('Observability');