Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
643d523
Add fields table to stack alerts flyout
umbopepato Dec 7, 2023
fca23b5
Merge branch 'main' into improve-stack-alerts-table-flyout
umbopepato Dec 11, 2023
3e90553
Merge branch 'main' of github.com:elastic/kibana into improve-stack-a…
umbopepato Dec 11, 2023
59a1a56
Simplify stack alert flyout tabs styling
umbopepato Dec 11, 2023
8765e46
Merge branch 'improve-stack-alerts-table-flyout' of github.com:umbope…
umbopepato Dec 11, 2023
37b06ad
Merge branch 'main' of github.com:elastic/kibana into improve-stack-a…
umbopepato Dec 18, 2023
b5e6277
Move alert fields table to alerts-ui-shared package
umbopepato Dec 18, 2023
e59fa58
Remove circular dependency
umbopepato Dec 19, 2023
fb59d79
[CI] Auto-commit changed files from 'node scripts/lint_ts_projects --…
kibanamachine Dec 19, 2023
5fc4de1
Fix wrong translation namespaces after move to package
umbopepato Dec 19, 2023
ada8f90
Merge branch 'improve-stack-alerts-table-flyout' of github.com:umbope…
umbopepato Dec 19, 2023
c8ac956
Merge branch 'main' of github.com:elastic/kibana into improve-stack-a…
umbopepato Dec 19, 2023
ccaa178
Fix wrong translation namespaces after move to package
umbopepato Dec 19, 2023
51b86d2
Merge branch 'improve-stack-alerts-table-flyout' of github.com:umbope…
umbopepato Dec 19, 2023
46191cb
Add alert fields table tests
umbopepato Dec 22, 2023
2af979d
Merge branch 'main' of github.com:elastic/kibana into improve-stack-a…
umbopepato Dec 22, 2023
8359e4e
Merge branch 'main' into improve-stack-alerts-table-flyout
XavierM Dec 22, 2023
21b8d03
Merge branch 'main' into improve-stack-alerts-table-flyout
umbopepato Jan 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions packages/kbn-alerting-types/alert_type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* 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 { TechnicalRuleDataFieldName } from '@kbn/rule-data-utils';

export interface BasicFields {
_id: string;
_index: string;
}

export type Alert = BasicFields & {
[Property in TechnicalRuleDataFieldName]?: string[];
} & {
[x: string]: unknown[];
};
1 change: 1 addition & 0 deletions packages/kbn-alerting-types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@
export * from './builtin_action_groups_types';
export * from './rule_type';
export * from './action_group_types';
export * from './alert_type';
1 change: 1 addition & 0 deletions packages/kbn-alerting-types/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@
"kbn_references": [
"@kbn/i18n",
"@kbn/licensing-plugin",
"@kbn/rule-data-utils"
]
}
2 changes: 2 additions & 0 deletions packages/kbn-alerts-ui-shared/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,5 @@ export * from './src/alerts_search_bar/hooks';
export * from './src/alerts_search_bar/apis';
export { AlertsSearchBar } from './src/alerts_search_bar';
export type { AlertsSearchBarProps } from './src/alerts_search_bar/types';

export * from './src/alert_fields_table';
100 changes: 100 additions & 0 deletions packages/kbn-alerts-ui-shared/src/alert_fields_table/index.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* 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 React from 'react';
import { AlertFieldsTable, AlertFieldsTableProps } from '.';
import { mount, ReactWrapper } from 'enzyme';

describe('AlertFieldsTable', () => {
const defaultProps = {
alert: {
'kibana.alert.status': ['active'],
'kibana.alert.url': ['ALERT_URL'],
'kibana.alert.evaluation.conditions': [
'Number of matching documents is NOT greater than 1000',
],
'kibana.alert.rule.producer': ['stackAlerts'],
'kibana.alert.reason.text': [
'Document count is 0 in the last 5m in metrics-* data view. Alert when greater than 1000.',
],
'kibana.alert.rule.rule_type_id': ['.es-query'],
'kibana.alert.evaluation.value': ['0'],
'kibana.alert.instance.id': ['query matched'],
'kibana.alert.flapping': [true],
'kibana.alert.rule.name': ['Test rule'],
'event.kind': ['signal'],
'kibana.alert.title': ["rule 'Test rule' recovered"],
'kibana.alert.workflow_status': ['open'],
'kibana.alert.rule.uuid': ['313b0cfb-3455-41da-aff9-cee2bc9637f1'],
'kibana.alert.time_range': [
{
gte: '1703241047013',
},
],
'kibana.alert.flapping_history': [false],
'kibana.alert.reason': [
'Document count is 0 in the last 5m in metrics-* data view. Alert when greater than 1000.',
],
'kibana.alert.rule.consumer': ['infrastructure'],
'kibana.alert.action_group': ['query matched'],
'kibana.alert.rule.category': ['Elasticsearch query'],
'kibana.alert.start': ['2023-12-22T10:30:47.013Z'],
'event.action': ['active'],
'@timestamp': ['2023-12-22T10:32:53.036Z'],
'kibana.alert.duration.us': [126023000],
'kibana.alert.rule.execution.uuid': ['ffcc5773-a4cc-48be-a265-9b16f85f4e62'],
'kibana.alert.uuid': ['93377bf3-d837-425d-b63f-97a8a5ae8054'],
'kibana.space_ids': ['default'],
'kibana.version': ['8.13.0'],
'kibana.alert.evaluation.threshold': [1000],
'kibana.alert.rule.parameters': [
{
searchConfiguration: {
query: {
language: 'kuery',
query: '',
},
index: '8e29356e-9d83-4a89-a79d-7d096104f3f6',
},
timeField: '@timestamp',
searchType: 'searchSource',
timeWindowSize: 5,
timeWindowUnit: 'm',
threshold: [1000],
thresholdComparator: '>',
size: 100,
aggType: 'count',
groupBy: 'all',
termSize: 5,
excludeHitsFromPreviousRun: true,
},
],
'kibana.alert.rule.revision': [0],
_id: '93377bf3-d837-425d-b63f-97a8a5ae8054',
_index: '.internal.alerts-stack.alerts-default-000001',
},
} as unknown as AlertFieldsTableProps;
let wrapper: ReactWrapper;

beforeEach(async () => {
wrapper = mount(<AlertFieldsTable {...defaultProps} />);
});

it('should paginate the results', () => {
expect(wrapper.find('tbody tr')).toHaveLength(25);
wrapper.find(`[data-test-subj="pagination-button-next"]`).last().simulate('click');
expect(wrapper.find('tbody tr')).toHaveLength(8);
});

it('should filter the rows according to the search string', async () => {
wrapper
.find('input[type="search"]')
.simulate('keyup', { target: { value: 'kibana.alert.status' } });
expect(wrapper.find('tbody tr')).toHaveLength(1);
});
});
114 changes: 114 additions & 0 deletions packages/kbn-alerts-ui-shared/src/alert_fields_table/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
Copy link
Copy Markdown
Contributor

@XavierM XavierM Dec 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we have some basic unit test to test for the search and pagination?

* 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 { i18n } from '@kbn/i18n';
import {
EuiInMemoryTable,
EuiTabbedContent,
EuiTabbedContentProps,
useEuiOverflowScroll,
} from '@elastic/eui';
import { css } from '@emotion/react';
import React, { memo, useCallback, useMemo, useState } from 'react';
import { Alert } from '@kbn/alerting-types';
import { euiThemeVars } from '@kbn/ui-theme';

export const search = {
box: {
incremental: true,
placeholder: i18n.translate('alertsUIShared.alertFieldsTable.filter.placeholder', {
defaultMessage: 'Filter by Field, Value, or Description...',
}),
schema: true,
},
};

const columns = [
{
field: 'key',
name: i18n.translate('alertsUIShared.alertFieldsTable.field', {
defaultMessage: 'Field',
}),
width: '30%',
},
{
field: 'value',
name: i18n.translate('alertsUIShared.alertFieldsTable.value', {
defaultMessage: 'Value',
}),
width: '70%',
},
];

export const ScrollableFlyoutTabbedContent = (props: EuiTabbedContentProps) => (
<EuiTabbedContent
css={css`
display: flex;
flex-direction: column;
flex: 1;

& [role='tabpanel'] {
display: flex;
flex: 1 0 0;
${useEuiOverflowScroll('y', true)}
}
`}
{...props}
/>
);

const COUNT_PER_PAGE_OPTIONS = [25, 50, 100];

const useFieldBrowserPagination = () => {
const [pagination, setPagination] = useState<{ pageIndex: number }>({
pageIndex: 0,
});

const onTableChange = useCallback(({ page: { index } }: { page: { index: number } }) => {
setPagination({ pageIndex: index });
}, []);
const paginationTableProp = useMemo(
() => ({
...pagination,
pageSizeOptions: COUNT_PER_PAGE_OPTIONS,
}),
[pagination]
);

return {
onTableChange,
paginationTableProp,
};
};

export interface AlertFieldsTableProps {
alert: Alert;
}

export const AlertFieldsTable = memo(({ alert }: AlertFieldsTableProps) => {
const { onTableChange, paginationTableProp } = useFieldBrowserPagination();
return (
<EuiInMemoryTable
items={Object.entries(alert).map(([key, value]) => ({
key,
value: Array.isArray(value) ? value?.[0] : value,
}))}
itemId="key"
columns={columns}
onTableChange={onTableChange}
pagination={paginationTableProp}
search={search}
css={css`
& .euiTableRow {
font-size: ${euiThemeVars.euiFontSizeXS};
font-family: ${euiThemeVars.euiCodeFontFamily};
}
`}
/>
);
});
1 change: 1 addition & 0 deletions packages/kbn-alerts-ui-shared/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,6 @@
"@kbn/data-views-plugin",
"@kbn/unified-search-plugin",
"@kbn/es-query",
"@kbn/ui-theme",
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*
* 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 { waitFor } from '@testing-library/react';
import { mount } from 'enzyme';
import type { ReactWrapper } from 'enzyme';
import React from 'react';

import { getDefaultAlertFlyout } from './default_alerts_flyout';
import { AlertsTableFlyoutBaseProps } from '../../../..';

const columns = [
{
columnHeaderType: 'not-filtered',
displayAsText: 'Alert Status',
id: 'kibana.alert.status',
initialWidth: 110,
},
{
columnHeaderType: 'not-filtered',
displayAsText: 'Last updated',
id: '@timestamp',
initialWidth: 230,
schema: 'datetime',
},
{
columnHeaderType: 'not-filtered',
displayAsText: 'Duration',
id: 'kibana.alert.duration.us',
initialWidth: 116,
},
{
columnHeaderType: 'not-filtered',
displayAsText: 'Reason',
id: 'kibana.alert.reason',
linkField: '*',
},
{
columnHeaderType: 'not-filtered',
displayAsText: 'Maintenance windows',
id: 'kibana.alert.maintenance_window_ids',
schema: 'string',
initialWidth: 180,
},
];

const alert = {
_id: 'dc80788f-f869-4f14-bedb-950186c9d2f8',
_index: '.internal.alerts-stack.alerts-default-000001',
'kibana.alert.status': ['active'],
'kibana.alert.evaluation.conditions': ['Number of matching documents is NOT greater than 1000'],
'kibana.alert.rule.producer': ['stackAlerts'],
'kibana.alert.reason.text': [
'Document count is 0 in the last 5m in metrics-* data view. Alert when greater than 1000.',
],
'kibana.alert.rule.rule_type_id': ['.es-query'],
'kibana.alert.evaluation.value': ['0'],
'kibana.alert.instance.id': ['query matched'],
'kibana.alert.rule.name': ['Test rule'],
'event.kind': ['signal'],
'kibana.alert.title': ["rule 'Test rule' recovered"],
'kibana.alert.workflow_status': ['open'],
'kibana.alert.rule.uuid': ['TEST_RULE_UUID'],
'kibana.alert.reason': [
'Document count is 0 in the last 5m in metrics-* data view. Alert when greater than 1000.',
],
'kibana.alert.rule.consumer': ['infrastructure'],
'kibana.alert.action_group': ['query matched'],
'kibana.alert.rule.category': ['Elasticsearch query'],
'event.action': ['active'],
'@timestamp': ['2023-12-22T09:23:08.244Z'],
'kibana.alert.rule.execution.uuid': ['9ca6fe40-90c0-4e32-9772-025e4de79dd8'],
'kibana.alert.uuid': ['dc80788f-f869-4f14-bedb-950186c9d2f8'],
'kibana.space_ids': ['default'],
'kibana.version': ['8.13.0'],
} as unknown as AlertsTableFlyoutBaseProps['alert'];

const tabsData = [
{ name: 'Overview', subj: 'overviewTab' },
{ name: 'Table', subj: 'tableTab' },
];

describe('DefaultAlertsFlyout', () => {
let wrapper: ReactWrapper;
beforeAll(async () => {
const { body: FlyoutBody } = getDefaultAlertFlyout(columns, (_columnId, value) => value)();
wrapper = mount(<FlyoutBody alert={alert} isLoading={false} />) as ReactWrapper;
await waitFor(() => wrapper.update());
});

describe('tabs', () => {
tabsData.forEach(({ name: tab }) => {
test(`should render the ${tab} tab`, () => {
expect(
wrapper
.find('[data-test-subj="defaultAlertFlyoutTabs"]')
.find('[role="tablist"]')
.containsMatchingElement(<span>{tab}</span>)
).toBeTruthy();
});
});

test('the Overview tab should be selected by default', () => {
expect(
wrapper
.find('[data-test-subj="defaultAlertFlyoutTabs"]')
.find('.euiTab-isSelected')
.first()
.text()
).toEqual('Overview');
});

tabsData.forEach(({ subj, name }) => {
test(`should render the ${name} tab panel`, () => {
wrapper
.find('[data-test-subj="defaultAlertFlyoutTabs"]')
.find('[role="tablist"]')
.find(`[data-test-subj="${subj}"]`)
.first()
.simulate('click');
expect(
wrapper
.find('[data-test-subj="defaultAlertFlyoutTabs"]')
.find('[role="tabpanel"]')
.find(`[data-test-subj="${subj}Panel"]`)
.exists()
).toBeTruthy();
});
});
});
});
Loading