Skip to content

Commit

Permalink
[RAM] Move alerts filter controls to @kbn/alerts-ui-shared package (e…
Browse files Browse the repository at this point in the history
…lastic#179243)

## Summary

Moves Security's alerts filter controls bar implementation to the
`@kbn/alerts-ui-shared` package for re-use in the Stack alerts (unified)
page and Observability solution (separate PR).

<img width="1339" alt="image"
src="https://github.com/elastic/kibana/assets/18363145/986e984a-0290-4980-859b-9d6e02ef335f">

## To verify

1. Enable the Stack alerts page feature flag:

    ```yaml
    # config/kibana.dev.yml
    
    xpack.trigger_actions_ui.enableExperimental:
      - globalAlertsPage
    ```
2. Create some Stack/O11y rules that fire some alerts
3. Navigate to `Management > Stack management > Alerts`, you should see
the new controls below the KQL bar.
4. Check that the filters apply correctly and reflect on the table
results
5. Check that the filter controls can be customized (`••• > Edit
controls`, add/remove controls, save)
6. Check that the customized configuration persists across reloads
7. Check that the filter controls can be temporarily overridden through
the URL param `filterControls`
8. Check that when the filters are overridden through the URL, a notice
is shown to the user with CTAs to discard/save the temporary filter
configuration
---
1. Navigate to `Security > Alerts > Manage rules`
2. Create one or more security rule that fire alerts
3. Go back to the security alerts page
4. Repeat steps 4-8

Closes elastic#176711

---------

Co-authored-by: Xavier Mouligneau <xavier.mouligneau@elastic.co>
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Jatin Kathuria <jtn.kathuria@gmail.com>
  • Loading branch information
4 people authored Apr 16, 2024
1 parent 6d1a347 commit 448d42b
Show file tree
Hide file tree
Showing 70 changed files with 1,263 additions and 1,079 deletions.
1 change: 1 addition & 0 deletions packages/kbn-alerts-ui-shared/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ export type { AlertsSearchBarProps } from './src/alerts_search_bar/types';
export * from './src/alert_fields_table';

export * from './src/rule_type_modal';
export * from './src/alert_filter_controls/types';
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { ComponentProps } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import type { Filter } from '@kbn/es-query';
import { EuiFlexItem } from '@elastic/eui';
import type { DataViewSpec, DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
import { AlertConsumers } from '@kbn/rule-data-utils';
import { HttpStart } from '@kbn/core-http-browser';
import { NotificationsStart } from '@kbn/core-notifications-browser';
import type { Storage } from '@kbn/kibana-utils-plugin/public';
import { useAlertDataView } from '../..';
import { FilterGroupLoading } from './loading';
import { DEFAULT_CONTROLS } from './constants';
import { FilterGroup } from './filter_group';
import { FilterControlConfig } from './types';

export type AlertFilterControlsProps = Omit<
ComponentProps<typeof FilterGroup>,
'dataViewId' | 'defaultControls' | 'featureIds' | 'Storage'
> & {
/**
* The feature ids used to get the correct alert data view(s)
*/
featureIds?: AlertConsumers[];
/**
* An array of default control configurations
*/
defaultControls?: FilterControlConfig[];
/**
* Specify overrides for the virtual data view created by the filter bar
* (builds upon the alert data view generated by the feature ids)
*/
dataViewSpec?: DataViewSpec;
/**
* The services needed by the filter bar
*/
services: {
http: HttpStart;
notifications: NotificationsStart;
dataViews: DataViewsPublicPluginStart;
storage: typeof Storage;
};
};

/**
* A configurable alert filters bar based on the controls embeddable
*
* @example
*
* <AlertFilterControls
* // Data view configuration
* featureIds={[AlertConsumers.STACK_ALERTS]}
* dataViewSpec={{
* id: 'unified-alerts-dv',
* title: '.alerts-*',
* }}
* spaceId={spaceId}
* // Controls configuration
* controlsUrlState={filterControls}
* defaultControls={DEFAULT_CONTROLS}
* chainingSystem="HIERARCHICAL"
* // Filters state
* filters={filters}
* onFiltersChange={setFilters}
* // Dependencies
* ControlGroupRenderer={ControlGroupRenderer}
* services={{
* http,
* notifications,
* dataViews,
* storage: Storage,
* }}
* />
*/
export const AlertFilterControls = (props: AlertFilterControlsProps) => {
const {
featureIds = [AlertConsumers.STACK_ALERTS],
defaultControls = DEFAULT_CONTROLS,
dataViewSpec,
onFiltersChange,
services: {
http,
notifications: { toasts },
dataViews,
storage,
},
...restFilterItemGroupProps
} = props;
const [loadingPageFilters, setLoadingPageFilters] = useState(true);
const { dataViews: alertDataViews, loading: loadingDataViews } = useAlertDataView({
featureIds,
dataViewsService: dataViews,
http,
toasts,
});

useEffect(() => {
if (!loadingDataViews) {
// If a data view spec is provided, create a new data view
if (dataViewSpec?.id) {
(async () => {
// Creates an adhoc data view starting from the alert data view
// and applying the overrides specified in the dataViewSpec
const spec = {
...(alertDataViews?.[0] ?? {}),
...(dataViewSpec ?? {}),
} as DataViewSpec;
await dataViews.create(spec);
setLoadingPageFilters(false);
})();
} else {
setLoadingPageFilters(false);
}
}

return () => dataViews.clearInstanceCache();
}, [dataViewSpec, alertDataViews, dataViews, loadingDataViews]);

const handleFilterChanges = useCallback(
(newFilters: Filter[]) => {
if (!onFiltersChange) {
return;
}
const updatedFilters = newFilters.map((filter) => {
return {
...filter,
meta: {
...filter.meta,
disabled: false,
},
};
});

onFiltersChange(updatedFilters);
},
[onFiltersChange]
);

if (loadingPageFilters) {
return (
<EuiFlexItem grow={true}>
<FilterGroupLoading />
</EuiFlexItem>
);
}

return (
<FilterGroup
dataViewId={dataViewSpec?.id || null}
onFiltersChange={handleFilterChanges}
featureIds={featureIds}
{...restFilterItemGroupProps}
Storage={storage}
defaultControls={defaultControls}
/>
);
};
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import type { FC } from 'react';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,49 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import type { AddOptionsListControlProps } from '@kbn/controls-plugin/public';
import { ALERT_RULE_NAME, ALERT_STATUS } from '@kbn/rule-data-utils';
import { i18n } from '@kbn/i18n';
import { FilterControlConfig } from './types';

export const DEFAULT_CONTROLS: FilterControlConfig[] = [
{
title: i18n.translate('alertsUIShared.alertFilterControls.defaultControlDisplayNames.status', {
defaultMessage: 'Status',
}),
fieldName: ALERT_STATUS,
selectedOptions: ['active'],
hideActionBar: true,
persist: true,
hideExists: true,
},
{
title: i18n.translate('alertsUIShared.alertFilterControls.defaultControlDisplayNames.rule', {
defaultMessage: 'Rule',
}),
fieldName: ALERT_RULE_NAME,
hideExists: true,
},
{
title: i18n.translate('alertsUIShared.alertFilterControls.defaultControlDisplayNames.group', {
defaultMessage: 'Group',
}),
fieldName: 'kibana.alert.group.value',
},
{
title: i18n.translate('alertsUIShared.alertFilterControls.defaultControlDisplayNames.tags', {
defaultMessage: 'Tags',
}),
fieldName: 'tags',
},
];

export const URL_PARAM_KEY = 'pageFilters';

export const TEST_IDS = {
FILTER_CONTROLS: 'filter-group__items',
Expand All @@ -24,7 +62,6 @@ export const TEST_IDS = {
EDIT: 'filter-group__context--edit',
DISCARD: `filter-group__context--discard`,
},
FILTER_BY_ASSIGNEES_BUTTON: 'filter-popover-button-assignees',
};

export const COMMON_OPTIONS_LIST_CONTROL_INPUTS: Partial<AddOptionsListControlProps> = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { EuiButtonIcon, EuiContextMenuItem, EuiContextMenuPanel, EuiPopover } from '@elastic/eui';
Expand Down
Loading

0 comments on commit 448d42b

Please sign in to comment.