diff --git a/x-pack/plugins/ml/public/application/notifications/components/notifications_list.tsx b/x-pack/plugins/ml/public/application/notifications/components/notifications_list.tsx
index a2a677670e2ea..7b08bcec0331f 100644
--- a/x-pack/plugins/ml/public/application/notifications/components/notifications_list.tsx
+++ b/x-pack/plugins/ml/public/application/notifications/components/notifications_list.tsx
@@ -12,7 +12,7 @@ import { FormattedMessage } from '@kbn/i18n-react';
import {
EuiBadge,
EuiCallOut,
- EuiInMemoryTable,
+ EuiBasicTable,
EuiSearchBar,
EuiSpacer,
IconColor,
@@ -294,6 +294,12 @@ export const NotificationsList: FC = () => {
const newNotificationsCount = Object.values(notificationsCounts).reduce((a, b) => a + b);
+ const itemsPerPage = useMemo(() => {
+ const fromIndex = pagination.pageIndex * pagination.pageSize;
+ const toIndex = fromIndex + pagination.pageSize;
+ return items.slice(fromIndex, toIndex);
+ }, [items, pagination]);
+
return (
<>
@@ -382,12 +388,12 @@ export const NotificationsList: FC = () => {
>
) : null}
-
+
columns={columns}
hasActions={false}
isExpandable={false}
isSelectable={false}
- items={items}
+ items={itemsPerPage}
itemId={'id'}
loading={isLoading}
rowProps={(item) => ({
@@ -397,7 +403,7 @@ export const NotificationsList: FC = () => {
onChange={onTableChange}
sorting={sorting}
data-test-subj={isLoading ? 'mlNotificationsTable loading' : 'mlNotificationsTable loaded'}
- message={
+ noItemsMessage={
number {
- if (sortField === 'timestamp') {
- if (sortDirection === 'asc') {
- return (a, b) => a.timestamp - b.timestamp;
- } else {
- return (a, b) => b.timestamp - a.timestamp;
- }
- } else {
- if (sortDirection === 'asc') {
- return (a, b) => (a[sortField] ?? '').localeCompare(b[sortField]);
- } else {
- return (a, b) => (b[sortField] ?? '').localeCompare(a[sortField]);
- }
+ switch (sortField) {
+ case 'timestamp':
+ if (sortDirection === 'asc') {
+ return (a, b) => a.timestamp - b.timestamp;
+ } else {
+ return (a, b) => b.timestamp - a.timestamp;
+ }
+ case 'level':
+ if (sortDirection === 'asc') {
+ const levelOrder: Record = {
+ error: 0,
+ warning: 1,
+ info: 2,
+ };
+ return (a, b) => levelOrder[b.level] - levelOrder[a.level];
+ } else {
+ const levelOrder: Record = {
+ error: 2,
+ warning: 1,
+ info: 0,
+ };
+ return (a, b) => levelOrder[b.level] - levelOrder[a.level];
+ }
+ default:
+ if (sortDirection === 'asc') {
+ return (a, b) => (a[sortField] ?? '').localeCompare(b[sortField]);
+ } else {
+ return (a, b) => (b[sortField] ?? '').localeCompare(a[sortField]);
+ }
}
}
diff --git a/x-pack/test/functional/apps/ml/short_tests/notifications/notification_list.ts b/x-pack/test/functional/apps/ml/short_tests/notifications/notification_list.ts
index 68a6090bd99e2..eb0f99c97e3c0 100644
--- a/x-pack/test/functional/apps/ml/short_tests/notifications/notification_list.ts
+++ b/x-pack/test/functional/apps/ml/short_tests/notifications/notification_list.ts
@@ -5,10 +5,13 @@
* 2.0.
*/
+import expect from '@kbn/expect';
+import moment from 'moment';
import { FtrProviderContext } from '../../../../ftr_provider_context';
+const timepickerFormat = 'MMM D, YYYY @ HH:mm:ss.SSS';
export default function ({ getService, getPageObjects }: FtrProviderContext) {
- const PageObjects = getPageObjects(['common']);
+ const PageObjects = getPageObjects(['common', 'timePicker']);
const esArchiver = getService('esArchiver');
const ml = getService('ml');
const browser = getService('browser');
@@ -58,6 +61,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await ml.notifications.table.waitForTableToLoad();
await ml.notifications.table.assertRowsNumberPerPage(25);
+ await ml.notifications.table.assertTableSorting('timestamp', 0, 'desc');
});
it('does not show notifications from another space', async () => {
@@ -92,5 +96,25 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await browser.refresh();
await ml.notifications.assertNotificationErrorsCount(0);
});
+
+ it('supports custom sorting for notifications level', async () => {
+ await ml.navigation.navigateToNotifications();
+ await ml.notifications.table.waitForTableToLoad();
+
+ await PageObjects.timePicker.pauseAutoRefresh();
+ const fromTime = moment().subtract(1, 'week').format(timepickerFormat);
+ const toTime = moment().format(timepickerFormat);
+ await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime);
+
+ await ml.notifications.table.waitForTableToLoad();
+
+ await ml.notifications.table.sortByField('level', 1, 'desc');
+ const rowsDesc = await ml.notifications.table.parseTable();
+ expect(rowsDesc[0].level).to.eql('error');
+
+ await ml.notifications.table.sortByField('level', 1, 'asc');
+ const rowsAsc = await ml.notifications.table.parseTable();
+ expect(rowsAsc[0].level).to.eql('info');
+ });
});
}
diff --git a/x-pack/test/functional/services/ml/common_table_service.ts b/x-pack/test/functional/services/ml/common_table_service.ts
index 50a40ab43f35a..063c6095c85b5 100644
--- a/x-pack/test/functional/services/ml/common_table_service.ts
+++ b/x-pack/test/functional/services/ml/common_table_service.ts
@@ -14,6 +14,7 @@ export type MlTableService = ReturnType;
export function MlTableServiceProvider({ getPageObject, getService }: FtrProviderContext) {
const testSubjects = getService('testSubjects');
const commonPage = getPageObject('common');
+ const retry = getService('retry');
const TableService = class {
constructor(
@@ -101,6 +102,42 @@ export function MlTableServiceProvider({ getPageObject, getService }: FtrProvide
`Filtered table should have ${expectedRowCount} row(s) for filter '${queryString}' (got ${rows.length} matching items)`
);
}
+
+ public async assertTableSorting(
+ columnName: string,
+ columnIndex: number,
+ expectedDirection: 'desc' | 'asc'
+ ) {
+ const actualDirection = await this.getCurrentSorting();
+ expect(actualDirection?.direction).to.eql(expectedDirection);
+ expect(actualDirection?.columnName).to.eql(columnName);
+ }
+
+ public async getCurrentSorting(): Promise<
+ { columnName: string; direction: string } | undefined
+ > {
+ const table = await testSubjects.find(`~${this.tableTestSubj}`);
+ const headers = await table.findAllByClassName('euiTableHeaderCell');
+ for (const header of headers) {
+ const ariaSort = await header.getAttribute('aria-sort');
+ if (ariaSort !== 'none') {
+ const columnNameFragments = (await header.getAttribute('data-test-subj')).split('_');
+ const columnName = columnNameFragments.slice(1, columnNameFragments.length - 1).join('_');
+ return { columnName, direction: ariaSort.replace('ending', '') };
+ }
+ }
+ }
+
+ public async sortByField(columnName: string, columnIndex: number, direction: 'desc' | 'asc') {
+ const testSubjString = `tableHeaderCell_${columnName}_${columnIndex}`;
+
+ await retry.tryForTime(5000, async () => {
+ await testSubjects.click(testSubjString);
+ await this.waitForTableToStartLoading();
+ await this.waitForTableToLoad();
+ await this.assertTableSorting(columnName, columnIndex, direction);
+ });
+ }
};
return {