Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ML] Custom sorting by message level on Notifications page #153462

Merged
merged 11 commits into from
Mar 27, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { FormattedMessage } from '@kbn/i18n-react';
import {
EuiBadge,
EuiCallOut,
EuiInMemoryTable,
EuiBasicTable,
EuiSearchBar,
EuiSpacer,
IconColor,
Expand Down Expand Up @@ -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 (
<>
<SavedObjectsWarning onCloseFlyout={fetchNotifications} forceRefresh={isLoading} />
Expand Down Expand Up @@ -382,12 +388,12 @@ export const NotificationsList: FC = () => {
</>
) : null}

<EuiInMemoryTable<NotificationItem>
<EuiBasicTable<NotificationItem>
columns={columns}
hasActions={false}
isExpandable={false}
isSelectable={false}
items={items}
items={itemsPerPage}
itemId={'id'}
loading={isLoading}
rowProps={(item) => ({
Expand All @@ -397,7 +403,7 @@ export const NotificationsList: FC = () => {
onChange={onTableChange}
sorting={sorting}
data-test-subj={isLoading ? 'mlNotificationsTable loading' : 'mlNotificationsTable loaded'}
message={
noItemsMessage={
<FormattedMessage
id="xpack.ml.notifications.noItemsFoundMessage"
defaultMessage="No notifications found"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,18 +154,35 @@ export class NotificationsService {
sortField: keyof NotificationItem,
sortDirection: 'asc' | 'desc'
): (a: NotificationItem, b: NotificationItem) => 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<NotificationSource['level'], number> = {
error: 0,
warning: 1,
info: 2,
};
return (a, b) => levelOrder[b.level] - levelOrder[a.level];
} else {
const levelOrder: Record<NotificationSource['level'], number> = {
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]);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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 () => {
Expand Down Expand Up @@ -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');
});
});
}
37 changes: 37 additions & 0 deletions x-pack/test/functional/services/ml/common_table_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export type MlTableService = ReturnType<typeof MlTableServiceProvider>;
export function MlTableServiceProvider({ getPageObject, getService }: FtrProviderContext) {
const testSubjects = getService('testSubjects');
const commonPage = getPageObject('common');
const retry = getService('retry');

const TableService = class {
constructor(
Expand Down Expand Up @@ -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 {
Expand Down