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

[RAC] [TGrid] Implements sorting in the TGrid #107495

Merged
merged 3 commits into from
Aug 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,13 @@ export type ColumnId = string;
/** The specification of a column header */
export type ColumnHeaderOptions = Pick<
EuiDataGridColumn,
'display' | 'displayAsText' | 'id' | 'initialWidth'
| 'actions'
| 'defaultSortDirection'
| 'display'
| 'displayAsText'
| 'id'
| 'initialWidth'
| 'isSortable'
> & {
aggregatable?: boolean;
category?: string;
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
/*
* 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 { mount } from 'enzyme';
import { omit, set } from 'lodash/fp';
import React from 'react';

import { defaultHeaders } from './default_headers';
import { getActionsColumnWidth, getColumnWidthFromType, getColumnHeaders } from './helpers';
import {
DEFAULT_COLUMN_MIN_WIDTH,
DEFAULT_DATE_COLUMN_MIN_WIDTH,
DEFAULT_ACTIONS_COLUMN_WIDTH,
EVENTS_VIEWER_ACTIONS_COLUMN_WIDTH,
SHOW_CHECK_BOXES_COLUMN_WIDTH,
} from '../constants';
import { mockBrowserFields } from '../../../../mock/browser_fields';

window.matchMedia = jest.fn().mockImplementation((query) => {
return {
matches: false,
media: query,
onchange: null,
addListener: jest.fn(),
removeListener: jest.fn(),
};
});

describe('helpers', () => {
describe('getColumnWidthFromType', () => {
test('it returns the expected width for a non-date column', () => {
expect(getColumnWidthFromType('keyword')).toEqual(DEFAULT_COLUMN_MIN_WIDTH);
});

test('it returns the expected width for a date column', () => {
expect(getColumnWidthFromType('date')).toEqual(DEFAULT_DATE_COLUMN_MIN_WIDTH);
});
});

describe('getActionsColumnWidth', () => {
test('returns the default actions column width when isEventViewer is false', () => {
expect(getActionsColumnWidth(false)).toEqual(DEFAULT_ACTIONS_COLUMN_WIDTH);
});

test('returns the default actions column width + checkbox width when isEventViewer is false and showCheckboxes is true', () => {
expect(getActionsColumnWidth(false, true)).toEqual(
DEFAULT_ACTIONS_COLUMN_WIDTH + SHOW_CHECK_BOXES_COLUMN_WIDTH
);
});

test('returns the events viewer actions column width when isEventViewer is true', () => {
expect(getActionsColumnWidth(true)).toEqual(EVENTS_VIEWER_ACTIONS_COLUMN_WIDTH);
});

test('returns the events viewer actions column width + checkbox width when isEventViewer is true and showCheckboxes is true', () => {
expect(getActionsColumnWidth(true, true)).toEqual(
EVENTS_VIEWER_ACTIONS_COLUMN_WIDTH + SHOW_CHECK_BOXES_COLUMN_WIDTH
);
});
});

describe('getColumnHeaders', () => {
// additional properties used by `EuiDataGrid`:
const actions = {
showSortAsc: {
label: 'Sort A-Z',
},
showSortDesc: {
label: 'Sort Z-A',
},
};
const defaultSortDirection = 'desc';
const isSortable = true;

const mockHeader = defaultHeaders.filter((h) =>
['@timestamp', 'source.ip', 'destination.ip'].includes(h.id)
);

describe('display', () => {
const renderedByDisplay = 'I am rendered via a React component: header.display';
const renderedByDisplayAsText = 'I am rendered by header.displayAsText';

test('it renders via `display` when the header has JUST a `display` property (`displayAsText` is undefined)', () => {
const headerWithJustDisplay = mockHeader.map((x) =>
x.id === '@timestamp'
? {
...x,
display: <span>{renderedByDisplay}</span>,
}
: x
);

const wrapper = mount(
<>{getColumnHeaders(headerWithJustDisplay, mockBrowserFields)[0].display}</>
);

expect(wrapper.text()).toEqual(renderedByDisplay);
});

test('it (also) renders via `display` when the header has BOTH a `display` property AND a `displayAsText`', () => {
const headerWithBoth = mockHeader.map((x) =>
x.id === '@timestamp'
? {
...x,
display: <span>{renderedByDisplay}</span>, // this has a higher priority...
displayAsText: renderedByDisplayAsText, // ...so this text won't be rendered
}
: x
);

const wrapper = mount(
<>{getColumnHeaders(headerWithBoth, mockBrowserFields)[0].display}</>
);

expect(wrapper.text()).toEqual(renderedByDisplay);
});

test('it renders via `displayAsText` when the header does NOT have a `display`, BUT it has `displayAsText`', () => {
const headerWithJustDisplayAsText = mockHeader.map((x) =>
x.id === '@timestamp'
? {
...x,
displayAsText: renderedByDisplayAsText, // fallback to rendering via displayAsText
}
: x
);

const wrapper = mount(
<>{getColumnHeaders(headerWithJustDisplayAsText, mockBrowserFields)[0].display}</>
);

expect(wrapper.text()).toEqual(renderedByDisplayAsText);
});

test('it renders `header.id` when the header does NOT have a `display`, AND it does NOT have a `displayAsText`', () => {
const wrapper = mount(<>{getColumnHeaders(mockHeader, mockBrowserFields)[0].display}</>);

expect(wrapper.text()).toEqual('@timestamp'); // fallback to rendering by header.id
});
});

test('it renders the default actions when the header does NOT have custom actions', () => {
expect(getColumnHeaders(mockHeader, mockBrowserFields)[0].actions).toEqual(actions);
});

test('it renders custom actions when `actions` is defined in the header', () => {
const customActions = {
showSortAsc: {
label: 'A custom sort ascending',
},
showSortDesc: {
label: 'A custom sort descending',
},
};

const headerWithCustomActions = mockHeader.map((x) =>
x.id === '@timestamp'
? {
...x,
actions: customActions,
}
: x
);

expect(getColumnHeaders(headerWithCustomActions, mockBrowserFields)[0].actions).toEqual(
customActions
);
});

describe('isSortable', () => {
test("it is sortable, because `@timestamp`'s `aggregatable` BrowserFields property is `true`", () => {
expect(getColumnHeaders(mockHeader, mockBrowserFields)[0].isSortable).toEqual(true);
});

test("it is NOT sortable, when `@timestamp`'s `aggregatable` BrowserFields property is `false`", () => {
const withAggregatableOverride = set(
'base.fields.@timestamp.aggregatable',
false, // override `aggregatable` for `@timestamp`, a date field that is normally aggregatable
mockBrowserFields
);

expect(getColumnHeaders(mockHeader, withAggregatableOverride)[0].isSortable).toEqual(false);
});

test('it is NOT sortable when BrowserFields does not have metadata for the field', () => {
const noBrowserFieldEntry = omit('base', mockBrowserFields); // omit the 'base` category, which contains `@timestamp`

expect(getColumnHeaders(mockHeader, noBrowserFieldEntry)[0].isSortable).toEqual(false);
});
});

test('should return a full object of ColumnHeader from the default header', () => {
const expectedData = [
{
actions,
aggregatable: true,
category: 'base',
columnHeaderType: 'not-filtered',
defaultSortDirection,
description:
'Date/time when the event originated. For log events this is the date/time when the event was generated, and not when it was read. Required field for all events.',
example: '2016-05-23T08:05:34.853Z',
format: '',
id: '@timestamp',
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
isSortable,
name: '@timestamp',
searchable: true,
type: 'date',
initialWidth: 190,
},
{
actions,
aggregatable: true,
category: 'source',
columnHeaderType: 'not-filtered',
defaultSortDirection,
description: 'IP address of the source. Can be one or multiple IPv4 or IPv6 addresses.',
example: '',
format: '',
id: 'source.ip',
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
isSortable,
name: 'source.ip',
searchable: true,
type: 'ip',
initialWidth: 180,
},
{
actions,
aggregatable: true,
category: 'destination',
columnHeaderType: 'not-filtered',
defaultSortDirection,
description:
'IP address of the destination. Can be one or multiple IPv4 or IPv6 addresses.',
example: '',
format: '',
id: 'destination.ip',
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
isSortable,
name: 'destination.ip',
searchable: true,
type: 'ip',
initialWidth: 180,
},
];

// NOTE: the omitted `display` (`React.ReactNode`) property is tested separately above
expect(getColumnHeaders(mockHeader, mockBrowserFields).map(omit('display'))).toEqual(
expectedData
);
});
});
});
Loading