mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 01:13:23 -04:00
[ResponseOps][Observability][Alerts] Fix missing alert grouping controls in o11y alerts page (#211160)
## Summary Adds back the additional alerts table toolbar controls to edit the grouping configuration. Adds test cases to check the correctness of the Observability alerts table configurations. ## To verify 1. Create one or more rules that fire alerts in Observability 2. Navigate to Observability > Alerts 3. Verify that the grouping toggle shows and works correctly in the table toolbar (`Group by: ...`)
This commit is contained in:
parent
dca5f18b7e
commit
e5bd422f6e
10 changed files with 223 additions and 78 deletions
|
@ -9,6 +9,7 @@
|
|||
|
||||
import React from 'react';
|
||||
import { EuiButton, EuiCode, EuiCopy, EuiEmptyPrompt } from '@elastic/eui';
|
||||
import { ERROR_PROMPT_TEST_ID } from '../constants';
|
||||
import { FallbackComponent } from './error_boundary';
|
||||
import {
|
||||
ALERTS_TABLE_UNKNOWN_ERROR_COPY_TO_CLIPBOARD_LABEL,
|
||||
|
@ -37,6 +38,7 @@ export const ErrorFallback: FallbackComponent = ({ error }) => {
|
|||
)}
|
||||
</EuiCopy>
|
||||
}
|
||||
data-test-subj={ERROR_PROMPT_TEST_ID}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -137,3 +137,4 @@ export const CELL_ACTIONS_EXPAND_TEST_ID = 'euiDataGridCellExpandButton';
|
|||
export const FIELD_BROWSER_TEST_ID = 'fields-browser-container';
|
||||
export const FIELD_BROWSER_BTN_TEST_ID = 'show-field-browser';
|
||||
export const FIELD_BROWSER_CUSTOM_CREATE_BTN_TEST_ID = 'field-browser-custom-create-btn';
|
||||
export const ERROR_PROMPT_TEST_ID = 'alertsTableErrorPrompt';
|
||||
|
|
|
@ -5,30 +5,21 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { useMemo, useCallback } from 'react';
|
||||
import { type AlertsGroupingProps, useAlertsGroupingState } from '@kbn/alerts-grouping';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useAlertsGroupingState } from '@kbn/alerts-grouping';
|
||||
import { useAlertsDataView } from '@kbn/alerts-ui-shared/src/common/hooks/use_alerts_data_view';
|
||||
import { useGetGroupSelectorStateless } from '@kbn/grouping/src/hooks/use_get_group_selector';
|
||||
import { AlertsByGroupingAgg } from '../types';
|
||||
import { useKibana } from '../../../utils/kibana_react';
|
||||
|
||||
interface GetPersistentControlsParams {
|
||||
interface GroupingToolbarControlsProps {
|
||||
groupingId: string;
|
||||
ruleTypeIds: string[];
|
||||
maxGroupingLevels?: number;
|
||||
services: Pick<
|
||||
AlertsGroupingProps<AlertsByGroupingAgg>['services'],
|
||||
'dataViews' | 'http' | 'notifications'
|
||||
>;
|
||||
}
|
||||
|
||||
export const getPersistentControlsHook =
|
||||
({
|
||||
groupingId,
|
||||
ruleTypeIds,
|
||||
maxGroupingLevels = 3,
|
||||
services: { dataViews, http, notifications },
|
||||
}: GetPersistentControlsParams) =>
|
||||
() => {
|
||||
export const GroupingToolbarControls = React.memo<GroupingToolbarControlsProps>(
|
||||
({ groupingId, ruleTypeIds, maxGroupingLevels = 3 }) => {
|
||||
const { dataViews, http, notifications } = useKibana().services;
|
||||
const { grouping, updateGrouping } = useAlertsGroupingState(groupingId);
|
||||
|
||||
const onGroupChange = useCallback(
|
||||
|
@ -48,7 +39,7 @@ export const getPersistentControlsHook =
|
|||
toasts: notifications.toasts,
|
||||
});
|
||||
|
||||
const groupSelector = useGetGroupSelectorStateless({
|
||||
return useGetGroupSelectorStateless({
|
||||
groupingId,
|
||||
onGroupChange,
|
||||
fields: dataView?.fields ?? [],
|
||||
|
@ -56,10 +47,5 @@ export const getPersistentControlsHook =
|
|||
grouping.options?.filter((option) => !grouping.activeGroups.includes(option.key)) ?? [],
|
||||
maxGroupingLevels,
|
||||
});
|
||||
|
||||
return useMemo(() => {
|
||||
return {
|
||||
right: groupSelector,
|
||||
};
|
||||
}, [groupSelector]);
|
||||
};
|
||||
}
|
||||
);
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { render } from '../../../utils/test_helper';
|
||||
import { alertWithGroupsAndTags } from '../mock/alert';
|
||||
import { useKibana } from '../../../utils/kibana_react';
|
||||
import { kibanaStartMock } from '../../../utils/kibana_react.mock';
|
||||
import { RelatedAlerts } from './related_alerts';
|
||||
import { ObservabilityAlertsTable } from '../../../components/alerts_table/alerts_table_lazy';
|
||||
import {
|
||||
OBSERVABILITY_RULE_TYPE_IDS_WITH_SUPPORTED_STACK_RULE_TYPES,
|
||||
observabilityAlertFeatureIds,
|
||||
} from '../../../../common/constants';
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useParams: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../../utils/kibana_react');
|
||||
|
||||
jest.mock('../../../components/alerts_table/alerts_table_lazy');
|
||||
const mockAlertsTable = jest.mocked(ObservabilityAlertsTable).mockReturnValue(<div />);
|
||||
|
||||
jest.mock('@kbn/alerts-grouping', () => ({
|
||||
AlertsGrouping: jest.fn().mockImplementation(({ children }) => <div>{children([])}</div>),
|
||||
}));
|
||||
|
||||
const useKibanaMock = useKibana as jest.Mock;
|
||||
const mockKibana = () => {
|
||||
useKibanaMock.mockReturnValue({
|
||||
services: {
|
||||
...kibanaStartMock.startContract().services,
|
||||
http: {
|
||||
basePath: {
|
||||
prepend: jest.fn(),
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
describe('Related alerts', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockKibana();
|
||||
});
|
||||
|
||||
it('should pass the correct configuration options to the alerts table', async () => {
|
||||
render(<RelatedAlerts alert={alertWithGroupsAndTags} />);
|
||||
|
||||
expect(mockAlertsTable).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
id: 'xpack.observability.related.alerts.table',
|
||||
ruleTypeIds: OBSERVABILITY_RULE_TYPE_IDS_WITH_SUPPORTED_STACK_RULE_TYPES,
|
||||
consumers: observabilityAlertFeatureIds,
|
||||
initialPageSize: 50,
|
||||
renderAdditionalToolbarControls: expect.any(Function),
|
||||
showInspectButton: true,
|
||||
}),
|
||||
expect.anything()
|
||||
);
|
||||
});
|
||||
});
|
|
@ -27,6 +27,7 @@ import {
|
|||
} from '@kbn/rule-data-utils';
|
||||
import { BoolQuery, Filter, type Query } from '@kbn/es-query';
|
||||
import { AlertsGrouping } from '@kbn/alerts-grouping';
|
||||
import { GroupingToolbarControls } from '../../../components/alerts_table/grouping/grouping_toolbar_controls';
|
||||
import { ObservabilityFields } from '../../../../common/utils/alerting/types';
|
||||
|
||||
import {
|
||||
|
@ -150,6 +151,12 @@ export function InternalRelatedAlerts({ alert }: Props) {
|
|||
consumers={observabilityAlertFeatureIds}
|
||||
query={mergeBoolQueries(esQuery, groupQuery)}
|
||||
initialPageSize={ALERTS_PER_PAGE}
|
||||
renderAdditionalToolbarControls={() => (
|
||||
<GroupingToolbarControls
|
||||
groupingId={RELATED_ALERTS_TABLE_CONFIG_ID}
|
||||
ruleTypeIds={OBSERVABILITY_RULE_TYPE_IDS_WITH_SUPPORTED_STACK_RULE_TYPES}
|
||||
/>
|
||||
)}
|
||||
showInspectButton
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -54,6 +54,7 @@ import { HeaderMenu } from '../overview/components/header_menu/header_menu';
|
|||
import { buildEsQuery } from '../../utils/build_es_query';
|
||||
import { renderRuleStats, RuleStatsState } from './components/rule_stats';
|
||||
import { mergeBoolQueries } from './helpers/merge_bool_queries';
|
||||
import { GroupingToolbarControls } from '../../components/alerts_table/grouping/grouping_toolbar_controls';
|
||||
|
||||
const ALERTS_SEARCH_BAR_ID = 'alerts-search-bar-o11y';
|
||||
const ALERTS_PER_PAGE = 50;
|
||||
|
@ -319,6 +320,12 @@ function InternalAlertsPage() {
|
|||
initialPageSize={ALERTS_PER_PAGE}
|
||||
onUpdate={onUpdate}
|
||||
columns={tableColumns}
|
||||
renderAdditionalToolbarControls={() => (
|
||||
<GroupingToolbarControls
|
||||
groupingId={ALERTS_PAGE_ALERTS_TABLE_CONFIG_ID}
|
||||
ruleTypeIds={OBSERVABILITY_RULE_TYPE_IDS_WITH_SUPPORTED_STACK_RULE_TYPES}
|
||||
/>
|
||||
)}
|
||||
showInspectButton
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -22,6 +22,7 @@ const DATE_WITH_DATA = {
|
|||
const ALERTS_FLYOUT_SELECTOR = 'alertsFlyout';
|
||||
const FILTER_FOR_VALUE_BUTTON_SELECTOR = 'filterForValue';
|
||||
const ALERTS_TABLE_CONTAINER_SELECTOR = 'alertsTable';
|
||||
const ALERTS_TABLE_ERROR_PROMPT_SELECTOR = 'alertsTableErrorPrompt';
|
||||
const ALERTS_TABLE_ACTIONS_MENU_SELECTOR = 'alertsTableActionsMenu';
|
||||
const VIEW_RULE_DETAILS_SELECTOR = 'viewRuleDetails';
|
||||
const VIEW_RULE_DETAILS_FLYOUT_SELECTOR = 'viewRuleDetailsFlyout';
|
||||
|
@ -134,6 +135,10 @@ export function ObservabilityAlertsCommonProvider({
|
|||
return await testSubjects.existOrFail(ALERTS_TABLE_CONTAINER_SELECTOR);
|
||||
};
|
||||
|
||||
const ensureNoTableErrorPrompt = async () => {
|
||||
return await testSubjects.missingOrFail(ALERTS_TABLE_ERROR_PROMPT_SELECTOR);
|
||||
};
|
||||
|
||||
const getNoDataPageOrFail = async () => {
|
||||
return await testSubjects.existOrFail('noDataPage');
|
||||
};
|
||||
|
@ -404,6 +409,7 @@ export function ObservabilityAlertsCommonProvider({
|
|||
getTableCellsInRows,
|
||||
getTableColumnHeaders,
|
||||
getTableOrFail,
|
||||
ensureNoTableErrorPrompt,
|
||||
navigateToTimeWithData,
|
||||
setKibanaTimeZoneToUTC,
|
||||
openAlertsFlyout,
|
||||
|
|
|
@ -16,7 +16,7 @@ export default function ({ loadTestFile }: FtrProviderContext) {
|
|||
loadTestFile(require.resolve('./pages/alerts/pagination'));
|
||||
loadTestFile(require.resolve('./pages/alerts/rule_stats'));
|
||||
loadTestFile(require.resolve('./pages/alerts/state_synchronization'));
|
||||
loadTestFile(require.resolve('./pages/alerts/table_storage'));
|
||||
loadTestFile(require.resolve('./pages/alerts/table_configuration'));
|
||||
loadTestFile(require.resolve('./pages/alerts/custom_threshold_preview_chart'));
|
||||
loadTestFile(require.resolve('./pages/alerts/custom_threshold'));
|
||||
loadTestFile(require.resolve('./pages/cases/case_details'));
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
|
||||
import {
|
||||
ALERT_EVALUATION_VALUE,
|
||||
ALERT_EVALUATION_THRESHOLD,
|
||||
ALERT_DURATION,
|
||||
ALERT_REASON,
|
||||
ALERT_RULE_NAME,
|
||||
ALERT_START,
|
||||
ALERT_STATUS,
|
||||
ALERT_INSTANCE_ID,
|
||||
TAGS,
|
||||
ALERT_STATUS_ACTIVE,
|
||||
} from '@kbn/rule-data-utils';
|
||||
import { FtrProviderContext } from '../../../../ftr_provider_context';
|
||||
|
||||
export default ({ getService, getPageObject }: FtrProviderContext) => {
|
||||
describe('Observability alerts table configuration', function () {
|
||||
this.tags('includeFirefox');
|
||||
|
||||
const observability = getService('observability');
|
||||
const esArchiver = getService('esArchiver');
|
||||
const testSubjects = getService('testSubjects');
|
||||
const dataGrid = getService('dataGrid');
|
||||
const browser = getService('browser');
|
||||
const retry = getService('retry');
|
||||
|
||||
before(async () => {
|
||||
await esArchiver.load('x-pack/test/functional/es_archives/observability/alerts');
|
||||
await esArchiver.load('x-pack/test/functional/es_archives/infra/simple_logs');
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await esArchiver.unload('x-pack/test/functional/es_archives/infra/simple_logs');
|
||||
await esArchiver.unload('x-pack/test/functional/es_archives/observability/alerts');
|
||||
});
|
||||
|
||||
it('renders correctly with a pre-existing persisted configuration', async () => {
|
||||
await observability.alerts.common.navigateWithoutFilter();
|
||||
const LOCAL_STORAGE_KEY = 'xpack.observability.alerts.alert.table';
|
||||
await browser.setLocalStorageItem(
|
||||
LOCAL_STORAGE_KEY,
|
||||
`{"columns":[{"displayAsText":"Alert Status","id":"kibana.alert.status","initialWidth":120,"schema":"string"},{"displayAsText":"Triggered","id":"kibana.alert.start","initialWidth":190,"schema":"datetime"},{"displayAsText":"Duration","id":"kibana.alert.duration.us","initialWidth":70,"schema":"numeric"},{"displayAsText":"Rule name","id":"kibana.alert.rule.name","initialWidth":150,"schema":"string"},{"displayAsText":"Group","id":"kibana.alert.instance.id","initialWidth":100,"schema":"string"},{"displayAsText":"Observed value","id":"kibana.alert.evaluation.value","initialWidth":100,"schema":"conflict"},{"displayAsText":"Threshold","id":"kibana.alert.evaluation.threshold","initialWidth":100,"schema":"numeric"},{"displayAsText":"Tags","id":"tags","initialWidth":150,"schema":"string"},{"displayAsText":"Reason","id":"kibana.alert.reason","schema":"string"}],"sort":[{"kibana.alert.start":{"order":"desc"}}],"visibleColumns":["kibana.alert.status","kibana.alert.start","kibana.alert.duration.us","kibana.alert.rule.name","kibana.alert.instance.id","kibana.alert.evaluation.value","kibana.alert.evaluation.threshold","tags","kibana.alert.reason"]}`
|
||||
);
|
||||
await observability.alerts.common.navigateWithoutFilter();
|
||||
await observability.alerts.common.ensureNoTableErrorPrompt();
|
||||
await browser.removeLocalStorageItem(LOCAL_STORAGE_KEY);
|
||||
});
|
||||
|
||||
it('renders the correct columns', async () => {
|
||||
await observability.alerts.common.navigateToTimeWithData();
|
||||
for (const colId of [
|
||||
ALERT_STATUS,
|
||||
ALERT_START,
|
||||
ALERT_DURATION,
|
||||
ALERT_RULE_NAME,
|
||||
ALERT_INSTANCE_ID,
|
||||
ALERT_EVALUATION_VALUE,
|
||||
ALERT_EVALUATION_THRESHOLD,
|
||||
TAGS,
|
||||
ALERT_REASON,
|
||||
]) {
|
||||
expect(await testSubjects.exists(`dataGridHeaderCell-${colId}`)).to.be(true);
|
||||
}
|
||||
});
|
||||
|
||||
it('renders the group selector', async () => {
|
||||
await observability.alerts.common.navigateToTimeWithData();
|
||||
expect(await testSubjects.exists('group-selector-dropdown')).to.be(true);
|
||||
});
|
||||
|
||||
it('renders the correct alert actions', async () => {
|
||||
await observability.alerts.common.navigateToTimeWithData();
|
||||
await observability.alerts.common.setAlertStatusFilter(ALERT_STATUS_ACTIVE);
|
||||
await testSubjects.click('alertsTableRowActionMore');
|
||||
await retry.waitFor('alert actions popover visible', () =>
|
||||
testSubjects.exists('alertsTableActionsMenu')
|
||||
);
|
||||
for (const action of [
|
||||
'add-to-existing-case-action',
|
||||
'add-to-new-case-action',
|
||||
'viewRuleDetails',
|
||||
'viewAlertDetailsPage',
|
||||
'untrackAlert',
|
||||
'toggle-alert',
|
||||
]) {
|
||||
expect(await testSubjects.exists(action, { allowHidden: true })).to.be(true);
|
||||
}
|
||||
});
|
||||
|
||||
it('remembers column changes', async () => {
|
||||
await observability.alerts.common.navigateToTimeWithData();
|
||||
await dataGrid.clickHideColumn('kibana.alert.duration.us');
|
||||
|
||||
await observability.alerts.common.navigateToTimeWithData();
|
||||
|
||||
const durationColumnExists = await testSubjects.exists(
|
||||
'dataGridHeaderCell-kibana.alert.duration.us'
|
||||
);
|
||||
expect(durationColumnExists).to.be(false);
|
||||
});
|
||||
|
||||
it('remembers sorting changes', async () => {
|
||||
await observability.alerts.common.navigateToTimeWithData();
|
||||
await dataGrid.clickDocSortAsc('kibana.alert.start');
|
||||
|
||||
await observability.alerts.common.navigateToTimeWithData();
|
||||
|
||||
const triggeredColumnHeading = await dataGrid.getHeaderElement('kibana.alert.start');
|
||||
expect(await triggeredColumnHeading.getAttribute('aria-sort')).to.be('ascending');
|
||||
});
|
||||
});
|
||||
};
|
|
@ -1,53 +0,0 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
|
||||
import { FtrProviderContext } from '../../../../ftr_provider_context';
|
||||
|
||||
export default ({ getService, getPageObject }: FtrProviderContext) => {
|
||||
describe('Observability alert table state storage', function () {
|
||||
this.tags('includeFirefox');
|
||||
|
||||
const observability = getService('observability');
|
||||
const esArchiver = getService('esArchiver');
|
||||
const testSubjects = getService('testSubjects');
|
||||
const dataGrid = getService('dataGrid');
|
||||
|
||||
before(async () => {
|
||||
await esArchiver.load('x-pack/test/functional/es_archives/observability/alerts');
|
||||
await esArchiver.load('x-pack/test/functional/es_archives/infra/simple_logs');
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await esArchiver.unload('x-pack/test/functional/es_archives/infra/simple_logs');
|
||||
await esArchiver.unload('x-pack/test/functional/es_archives/observability/alerts');
|
||||
});
|
||||
|
||||
it('remembers column changes', async () => {
|
||||
await observability.alerts.common.navigateToTimeWithData();
|
||||
await dataGrid.clickHideColumn('kibana.alert.duration.us');
|
||||
|
||||
await observability.alerts.common.navigateToTimeWithData();
|
||||
|
||||
const durationColumnExists = await testSubjects.exists(
|
||||
'dataGridHeaderCell-kibana.alert.duration.us'
|
||||
);
|
||||
expect(durationColumnExists).to.be(false);
|
||||
});
|
||||
|
||||
it('remembers sorting changes', async () => {
|
||||
await observability.alerts.common.navigateToTimeWithData();
|
||||
await dataGrid.clickDocSortAsc('kibana.alert.start');
|
||||
|
||||
await observability.alerts.common.navigateToTimeWithData();
|
||||
|
||||
const triggeredColumnHeading = await dataGrid.getHeaderElement('kibana.alert.start');
|
||||
expect(await triggeredColumnHeading.getAttribute('aria-sort')).to.be('ascending');
|
||||
});
|
||||
});
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue