[Discover][ES|QL] Persist columns sorting inside SO (#180193)

- Closes https://github.com/elastic/kibana/issues/169345

## Summary

This PR allows to persist the configured columns sorting for ES|QL mode
too.

---------

Co-authored-by: Matthias Wilhelm <ankertal@gmail.com>
This commit is contained in:
Julia Rechkunova 2024-04-11 13:22:36 +02:00 committed by GitHub
parent 71788816cd
commit 357b6f8651
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 330 additions and 74 deletions

View file

@ -786,24 +786,21 @@ export const UnifiedDataTable = ({
[sort, visibleColumns]
);
const [inmemorySortingColumns, setInmemorySortingColumns] = useState([]);
const onTableSort = useCallback(
(sortingColumnsData) => {
if (isSortEnabled) {
if (isPlainRecord) {
setInmemorySortingColumns(sortingColumnsData);
} else if (onSort) {
if (onSort) {
onSort(sortingColumnsData.map(({ id, direction }: SortObj) => [id, direction]));
}
}
},
[onSort, isSortEnabled, isPlainRecord, setInmemorySortingColumns]
[onSort, isSortEnabled]
);
const sorting = useMemo(() => {
if (isSortEnabled) {
return {
columns: isPlainRecord ? inmemorySortingColumns : sortingColumns,
columns: sortingColumns,
onSort: onTableSort,
};
}
@ -811,7 +808,7 @@ export const UnifiedDataTable = ({
columns: sortingColumns,
onSort: () => {},
};
}, [isSortEnabled, sortingColumns, isPlainRecord, inmemorySortingColumns, onTableSort]);
}, [isSortEnabled, sortingColumns, onTableSort]);
const canSetExpandedDoc = Boolean(setExpandedDoc && !!renderDocumentView);

View file

@ -145,7 +145,10 @@ function buildEuiGridColumn({
const column: EuiDataGridColumn = {
id: columnName,
schema: getSchemaByKbnType(columnType),
isSortable: isSortEnabled && (isPlainRecord || dataViewField?.sortable === true),
isSortable:
isSortEnabled &&
// TODO: would be great to have something like `sortable` flag for text based columns too
((isPlainRecord && columnName !== '_source') || dataViewField?.sortable === true),
display:
showColumnTokens || headerRowHeight !== 1 ? (
<DataTableColumnHeaderMemoized

View file

@ -18,17 +18,20 @@ describe('getDefaultSort function', function () {
});
test('should return default sort for an data view with timeFieldName', function () {
expect(getDefaultSort(stubDataView, 'desc', false)).toEqual([['@timestamp', 'desc']]);
expect(getDefaultSort(stubDataView, 'asc', false)).toEqual([['@timestamp', 'asc']]);
expect(getDefaultSort(stubDataView, 'desc', false, false)).toEqual([['@timestamp', 'desc']]);
expect(getDefaultSort(stubDataView, 'asc', false, false)).toEqual([['@timestamp', 'asc']]);
expect(getDefaultSort(stubDataView, 'asc', false, true)).toEqual([]);
});
test('should return default sort for an data view without timeFieldName', function () {
expect(getDefaultSort(stubDataViewWithoutTimeField, 'desc', false)).toEqual([]);
expect(getDefaultSort(stubDataViewWithoutTimeField, 'asc', false)).toEqual([]);
expect(getDefaultSort(stubDataViewWithoutTimeField, 'desc', false, false)).toEqual([]);
expect(getDefaultSort(stubDataViewWithoutTimeField, 'asc', false, false)).toEqual([]);
expect(getDefaultSort(stubDataViewWithoutTimeField, 'asc', false, true)).toEqual([]);
});
test('should return empty sort for data view when time column is hidden', function () {
expect(getDefaultSort(stubDataView, 'desc', true)).toEqual([]);
expect(getDefaultSort(stubDataView, 'asc', true)).toEqual([]);
expect(getDefaultSort(stubDataView, 'desc', true, false)).toEqual([]);
expect(getDefaultSort(stubDataView, 'asc', true, false)).toEqual([]);
expect(getDefaultSort(stubDataView, 'asc', true, true)).toEqual([]);
});
});

View file

@ -12,16 +12,21 @@ import { isSortable } from './get_sort';
/**
* use in case the user didn't manually sort.
* the default sort is returned depending of the data view
* the default sort is returned depending on the data view or non for text based queries
*/
export function getDefaultSort(
dataView: DataView | undefined,
defaultSortOrder: string = 'desc',
hidingTimeColumn: boolean = false
hidingTimeColumn: boolean = false,
isTextBasedQueryMode: boolean
): SortOrder[] {
if (isTextBasedQueryMode) {
return [];
}
if (
dataView?.timeFieldName &&
isSortable(dataView.timeFieldName, dataView) &&
isSortable(dataView.timeFieldName, dataView, isTextBasedQueryMode) &&
!hidingTimeColumn
) {
return [[dataView.timeFieldName, defaultSortOrder]];

View file

@ -12,39 +12,51 @@ import {
stubDataViewWithoutTimeField,
} from '@kbn/data-views-plugin/common/data_view.stub';
describe('docTable', function () {
describe('getSort and getSortArray', function () {
describe('getSort function', function () {
test('should be a function', function () {
expect(typeof getSort === 'function').toBeTruthy();
});
test('should return an array of objects', function () {
expect(getSort([['bytes', 'desc']], stubDataView)).toEqual([{ bytes: 'desc' }]);
expect(getSort([['bytes', 'desc']], stubDataViewWithoutTimeField)).toEqual([
expect(getSort([['bytes', 'desc']], stubDataView, false)).toEqual([{ bytes: 'desc' }]);
expect(getSort([['bytes', 'desc']], stubDataView, true)).toEqual([{ bytes: 'desc' }]);
expect(getSort([['bytes', 'desc']], stubDataViewWithoutTimeField, false)).toEqual([
{ bytes: 'desc' },
]);
expect(getSort([['bytes', 'desc']], stubDataViewWithoutTimeField, true)).toEqual([
{ bytes: 'desc' },
]);
});
test('should passthrough arrays of objects', () => {
expect(getSort([{ bytes: 'desc' }], stubDataView)).toEqual([{ bytes: 'desc' }]);
expect(getSort([{ bytes: 'desc' }], stubDataView, false)).toEqual([{ bytes: 'desc' }]);
expect(getSort([{ bytes: 'desc' }], stubDataView, true)).toEqual([{ bytes: 'desc' }]);
});
test('should return an empty array when passed an unsortable field', function () {
expect(getSort([['non-sortable', 'asc']], stubDataView)).toEqual([]);
expect(getSort([['lol_nope', 'asc']], stubDataView)).toEqual([]);
expect(getSort([['non-sortable', 'asc']], stubDataView, false)).toEqual([]);
expect(getSort([['non-sortable', 'asc']], stubDataView, true)).toEqual([
{
'non-sortable': 'asc',
},
]);
expect(getSort([['lol_nope', 'asc']], stubDataView, false)).toEqual([]);
expect(getSort([['non-sortable', 'asc']], stubDataViewWithoutTimeField)).toEqual([]);
expect(getSort([['non-sortable', 'asc']], stubDataViewWithoutTimeField, false)).toEqual([]);
});
test('should return an empty array ', function () {
expect(getSort([], stubDataView)).toEqual([]);
expect(getSort([['foo', 'bar']], stubDataView)).toEqual([]);
expect(getSort([{ foo: 'bar' }], stubDataView)).toEqual([]);
expect(getSort([], stubDataView, false)).toEqual([]);
expect(getSort([['foo', 'bar']], stubDataView, false)).toEqual([]);
expect(getSort([{ foo: 'bar' }], stubDataView, false)).toEqual([]);
expect(getSort([['foo', 'bar']], stubDataView, true)).toEqual([{ foo: 'bar' }]);
expect(getSort([{ foo: 'bar' }], stubDataView, true)).toEqual([{ foo: 'bar' }]);
});
test('should convert a legacy sort to an array of objects', function () {
expect(getSort(['foo', 'desc'], stubDataView)).toEqual([{ foo: 'desc' }]);
expect(getSort(['foo', 'asc'], stubDataView)).toEqual([{ foo: 'asc' }]);
expect(getSort(['foo', 'desc'], stubDataView, false)).toEqual([{ foo: 'desc' }]);
expect(getSort(['foo', 'asc'], stubDataView, false)).toEqual([{ foo: 'asc' }]);
});
});
describe('getSortArray function', function () {
@ -53,22 +65,28 @@ describe('docTable', function () {
});
test('should return an array of arrays for sortable fields', function () {
expect(getSortArray([['bytes', 'desc']], stubDataView)).toEqual([['bytes', 'desc']]);
expect(getSortArray([['bytes', 'desc']], stubDataView, false)).toEqual([['bytes', 'desc']]);
});
test('should return an array of arrays from an array of elasticsearch sort objects', function () {
expect(getSortArray([{ bytes: 'desc' }], stubDataView)).toEqual([['bytes', 'desc']]);
expect(getSortArray([{ bytes: 'desc' }], stubDataView, false)).toEqual([['bytes', 'desc']]);
});
test('should sort by an empty array when an unsortable field is given', function () {
expect(getSortArray([{ 'non-sortable': 'asc' }], stubDataView)).toEqual([]);
expect(getSortArray([{ lol_nope: 'asc' }], stubDataView)).toEqual([]);
expect(getSortArray([{ 'non-sortable': 'asc' }], stubDataViewWithoutTimeField)).toEqual([]);
expect(getSortArray([{ 'non-sortable': 'asc' }], stubDataView, false)).toEqual([]);
expect(getSortArray([{ 'non-sortable': 'asc' }], stubDataView, true)).toEqual([
['non-sortable', 'asc'],
]);
expect(getSortArray([{ lol_nope: 'asc' }], stubDataView, false)).toEqual([]);
expect(
getSortArray([{ 'non-sortable': 'asc' }], stubDataViewWithoutTimeField, false)
).toEqual([]);
});
test('should return an empty array when passed an empty sort array', () => {
expect(getSortArray([], stubDataView)).toEqual([]);
expect(getSortArray([], stubDataViewWithoutTimeField)).toEqual([]);
expect(getSortArray([], stubDataView, false)).toEqual([]);
expect(getSortArray([], stubDataViewWithoutTimeField, false)).toEqual([]);
expect(getSortArray([], stubDataViewWithoutTimeField, true)).toEqual([]);
});
});
});

View file

@ -14,20 +14,36 @@ export type SortPairObj = Record<string, string>;
export type SortPair = SortOrder | SortPairObj;
export type SortInput = SortPair | SortPair[];
export function isSortable(fieldName: string, dataView: DataView): boolean {
export function isSortable(
fieldName: string,
dataView: DataView,
isTextBasedQueryMode: boolean
): boolean {
if (isTextBasedQueryMode) {
// in-memory sorting is used for text-based queries
// would be great to have a way to determine if a text-based column is sortable
return fieldName !== '_source';
}
const field = dataView.getFieldByName(fieldName);
return !!(field && field.sortable);
}
function createSortObject(sortPair: SortInput, dataView: DataView): SortPairObj | undefined {
function createSortObject(
sortPair: SortInput,
dataView: DataView,
isTextBasedQueryMode: boolean
): SortPairObj | undefined {
if (
Array.isArray(sortPair) &&
sortPair.length === 2 &&
isSortable(String(sortPair[0]), dataView)
isSortable(String(sortPair[0]), dataView, isTextBasedQueryMode)
) {
const [field, direction] = sortPair as SortOrder;
return { [field]: direction };
} else if (isPlainObject(sortPair) && isSortable(Object.keys(sortPair)[0], dataView)) {
} else if (
isPlainObject(sortPair) &&
isSortable(Object.keys(sortPair)[0], dataView, isTextBasedQueryMode)
) {
return sortPair as SortPairObj;
}
}
@ -43,16 +59,21 @@ export function isLegacySort(sort: SortPair[] | SortPair): sort is SortPair {
* @param {array} sort two dimensional array [[fieldToSort, directionToSort]]
* or an array of objects [{fieldToSort: directionToSort}]
* @param {object} dataView used for determining default sort
* @param {boolean} isTextBasedQueryMode
* @returns Array<{object}> an array of sort objects
*/
export function getSort(sort: SortPair[] | SortPair, dataView: DataView): SortPairObj[] {
export function getSort(
sort: SortPair[] | SortPair,
dataView: DataView,
isTextBasedQueryMode: boolean
): SortPairObj[] {
if (Array.isArray(sort)) {
if (isLegacySort(sort)) {
// To stay compatible with legacy sort, which just supported a single sort field
return [{ [sort[0]]: sort[1] }];
}
return sort
.map((sortPair: SortPair) => createSortObject(sortPair, dataView))
.map((sortPair: SortPair) => createSortObject(sortPair, dataView, isTextBasedQueryMode))
.filter((sortPairObj) => typeof sortPairObj === 'object') as SortPairObj[];
}
return [];
@ -62,8 +83,12 @@ export function getSort(sort: SortPair[] | SortPair, dataView: DataView): SortPa
* compared to getSort it doesn't return an array of objects, it returns an array of arrays
* [[fieldToSort: directionToSort]]
*/
export function getSortArray(sort: SortInput, dataView: DataView): SortOrder[] {
return getSort(sort, dataView).reduce((acc: SortOrder[], sortPair) => {
export function getSortArray(
sort: SortInput,
dataView: DataView,
isTextBasedQueryMode: boolean
): SortOrder[] {
return getSort(sort, dataView, isTextBasedQueryMode).reduce((acc: SortOrder[], sortPair) => {
const entries = Object.entries(sortPair);
if (entries && entries[0]) {
acc.push(entries[0]);

View file

@ -46,7 +46,7 @@ export function getSortForSearchSource({
}
const { timeFieldName } = dataView;
const sortPairs = getSort(sort, dataView);
const sortPairs = getSort(sort, dataView, false); // text based request is not using search source
const sortForSearchSource = sortPairs.map((sortPair: Record<string, string>) => {
if (timeFieldName && sortPair[timeFieldName]) {

View file

@ -418,7 +418,7 @@ function DiscoverDocumentsComponent({
settings={grid}
onFilter={onAddFilter as DocViewFilterFn}
onSetColumns={onSetColumns}
onSort={!isTextBasedQuery ? onSort : undefined}
onSort={onSort}
onResize={onResizeDataGrid}
useNewFieldsApi={useNewFieldsApi}
configHeaderRowHeight={3}
@ -426,7 +426,7 @@ function DiscoverDocumentsComponent({
onUpdateHeaderRowHeight={onUpdateHeaderRowHeight}
rowHeightState={rowHeight}
onUpdateRowHeight={onUpdateRowHeight}
isSortEnabled={isTextBasedQuery ? Boolean(currentColumns.length) : true}
isSortEnabled={true}
isPlainRecord={isTextBasedQuery}
rowsPerPageState={rowsPerPage ?? getDefaultRowsPerPage(services.uiSettings)}
onUpdateRowsPerPage={onUpdateRowsPerPage}

View file

@ -48,7 +48,7 @@ export function getStateDefaults({
const query = searchSource.getField('query') || data.query.queryString.getDefaultQuery();
const isTextBasedQueryMode = isTextBasedQuery(query);
const sort = getSortArray(savedSearch.sort ?? [], dataView!);
const sort = getSortArray(savedSearch.sort ?? [], dataView!, isTextBasedQueryMode);
const columns = getDefaultColumns(savedSearch, uiSettings);
const chartHidden = getChartHidden(storage, 'discover');
@ -58,7 +58,8 @@ export function getStateDefaults({
? getDefaultSort(
dataView,
uiSettings.get(SORT_DEFAULT_ORDER_SETTING, 'desc'),
uiSettings.get(DOC_HIDE_TIME_COLUMN_SETTING, false)
uiSettings.get(DOC_HIDE_TIME_COLUMN_SETTING, false),
isTextBasedQueryMode
)
: sort,
columns,

View file

@ -37,7 +37,9 @@ export function getDataViewAppState(
);
}
if (query && isOfAggregateQueryType(query)) {
const isTextBasedQueryMode = isOfAggregateQueryType(query);
if (isTextBasedQueryMode) {
columns = [];
}
@ -45,7 +47,7 @@ export function getDataViewAppState(
// filter out sorting by timeField in case it is set. data views without timeField don't
// prepend this field in the table, so in legacy grid you would need to add this column to
// remove sorting
let nextSort = getSortArray(currentSort, nextDataView).filter((value) => {
let nextSort = getSortArray(currentSort, nextDataView, isTextBasedQueryMode).filter((value) => {
return nextDataView.timeFieldName || value[0] !== currentDataView.timeFieldName;
});

View file

@ -57,7 +57,7 @@ export function TableHeader({
sortOrder={
sortOrder.length
? sortOrder
: getDefaultSort(dataView, defaultSortOrder, hideTimeColumn)
: getDefaultSort(dataView, defaultSortOrder, hideTimeColumn, false)
}
onMoveColumn={onMoveColumn}
onRemoveColumn={onRemoveColumn}

View file

@ -389,8 +389,12 @@ export class SavedSearchEmbeddable
}
};
private getSort(sort: SortPair[] | undefined, dataView?: DataView) {
return getSortForEmbeddable(sort, dataView, this.services.uiSettings);
private getSort(
sort: SortPair[] | undefined,
dataView: DataView | undefined,
isTextBasedQueryMode: boolean
) {
return getSortForEmbeddable(sort, dataView, this.services.uiSettings, isTextBasedQueryMode);
}
private initializeSearchEmbeddableProps() {
@ -417,7 +421,7 @@ export class SavedSearchEmbeddable
filters: savedSearch.searchSource.getField('filter') as Filter[],
dataView,
isLoading: false,
sort: this.getSort(savedSearch.sort, dataView),
sort: this.getSort(savedSearch.sort, dataView, this.isTextBasedSearch(savedSearch)),
rows: [],
searchDescription: savedSearch.description,
description: savedSearch.description,
@ -573,7 +577,11 @@ export class SavedSearchEmbeddable
);
searchProps.columns = columnState.columns || [];
searchProps.sort = this.getSort(this.input.sort || savedSearch.sort, searchProps?.dataView);
searchProps.sort = this.getSort(
this.input.sort || savedSearch.sort,
searchProps?.dataView,
this.isTextBasedSearch(savedSearch)
);
searchProps.sharedItemTitle = this.panelTitleInternal;
searchProps.searchTitle = this.panelTitleInternal;
searchProps.rowHeightState = this.input.rowHeight ?? savedSearch.rowHeight;

View file

@ -13,35 +13,56 @@ import {
} from '@kbn/data-views-plugin/common/data_view.stub';
import { uiSettingsMock } from '../../__mocks__/ui_settings';
describe('docTable', function () {
describe('getSortForEmbeddable', function () {
describe('getSortForEmbeddable function', function () {
test('should return an array of arrays for sortable fields', function () {
expect(getSortForEmbeddable([['bytes', 'desc']], stubDataView)).toEqual([['bytes', 'desc']]);
expect(getSortForEmbeddable([['bytes', 'desc']], stubDataView, undefined, false)).toEqual([
['bytes', 'desc'],
]);
expect(getSortForEmbeddable([['bytes', 'desc']], stubDataView, undefined, true)).toEqual([
['bytes', 'desc'],
]);
});
test('should return an array of arrays from an array of elasticsearch sort objects', function () {
expect(getSortForEmbeddable([{ bytes: 'desc' }], stubDataView)).toEqual([['bytes', 'desc']]);
expect(getSortForEmbeddable([{ bytes: 'desc' }], stubDataView, undefined, false)).toEqual([
['bytes', 'desc'],
]);
});
test('should sort by an empty array when an unsortable field is given', function () {
expect(getSortForEmbeddable([{ 'non-sortable': 'asc' }], stubDataView)).toEqual([]);
expect(getSortForEmbeddable([{ lol_nope: 'asc' }], stubDataView)).toEqual([]);
expect(
getSortForEmbeddable([{ 'non-sortable': 'asc' }], stubDataViewWithoutTimeField)
getSortForEmbeddable([{ 'non-sortable': 'asc' }], stubDataView, undefined, false)
).toEqual([]);
expect(
getSortForEmbeddable([{ 'non-sortable': 'asc' }], stubDataView, undefined, true)
).toEqual([['non-sortable', 'asc']]);
expect(getSortForEmbeddable([{ lol_nope: 'asc' }], stubDataView, undefined, false)).toEqual(
[]
);
expect(
getSortForEmbeddable(
[{ 'non-sortable': 'asc' }],
stubDataViewWithoutTimeField,
undefined,
false
)
).toEqual([]);
});
test('should return an empty array when passed an empty sort array', () => {
expect(getSortForEmbeddable([], stubDataView)).toEqual([]);
expect(getSortForEmbeddable([], stubDataViewWithoutTimeField)).toEqual([]);
expect(getSortForEmbeddable([], stubDataView, undefined, false)).toEqual([]);
expect(getSortForEmbeddable([], stubDataView, undefined, true)).toEqual([]);
expect(getSortForEmbeddable([], stubDataViewWithoutTimeField, undefined, false)).toEqual([]);
});
test('should provide fallback results', () => {
expect(getSortForEmbeddable(undefined)).toEqual([]);
expect(getSortForEmbeddable(undefined, stubDataView)).toEqual([]);
expect(getSortForEmbeddable(undefined, stubDataView, uiSettingsMock)).toEqual([
expect(getSortForEmbeddable(undefined, undefined, undefined, false)).toEqual([]);
expect(getSortForEmbeddable(undefined, stubDataView, undefined, false)).toEqual([]);
expect(getSortForEmbeddable(undefined, stubDataView, uiSettingsMock, false)).toEqual([
['@timestamp', 'desc'],
]);
expect(getSortForEmbeddable(undefined, stubDataView, uiSettingsMock, true)).toEqual([]);
});
});
});

View file

@ -16,9 +16,10 @@ import { getDefaultSort, getSortArray, SortInput } from '../../../common/utils/s
* sorting for embeddable, like getSortArray,but returning a default in the case the given sort or dataView is not valid
*/
export function getSortForEmbeddable(
sort?: SortInput,
dataView?: DataView,
uiSettings?: IUiSettingsClient
sort: SortInput | undefined,
dataView: DataView | undefined,
uiSettings: IUiSettingsClient | undefined,
isTextBasedQueryMode: boolean
): SortOrder[] {
if (!sort || !sort.length || !dataView) {
if (!uiSettings) {
@ -26,7 +27,7 @@ export function getSortForEmbeddable(
}
const defaultSortOrder = uiSettings.get(SORT_DEFAULT_ORDER_SETTING, 'desc');
const hidingTimeColumn = uiSettings.get(DOC_HIDE_TIME_COLUMN_SETTING, false);
return getDefaultSort(dataView, defaultSortOrder, hidingTimeColumn);
return getDefaultSort(dataView, defaultSortOrder, hidingTimeColumn, isTextBasedQueryMode);
}
return getSortArray(sort, dataView);
return getSortArray(sort, dataView, isTextBasedQueryMode);
}

View file

@ -19,11 +19,14 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const monacoEditor = getService('monacoEditor');
const security = getService('security');
const retry = getService('retry');
const browser = getService('browser');
const find = getService('find');
const esql = getService('esql');
const dashboardAddPanel = getService('dashboardAddPanel');
const PageObjects = getPageObjects([
'common',
'discover',
'dashboard',
'header',
'timePicker',
'unifiedFieldList',
@ -81,7 +84,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
expect(await testSubjects.exists('discoverQueryHits')).to.be(true);
expect(await testSubjects.exists('discoverAlertsButton')).to.be(true);
expect(await testSubjects.exists('shareTopNavButton')).to.be(true);
expect(await testSubjects.exists('dataGridColumnSortingButton')).to.be(false);
expect(await testSubjects.exists('dataGridColumnSortingButton')).to.be(true);
expect(await testSubjects.exists('docTableExpandToggleColumn')).to.be(true);
expect(await testSubjects.exists('fieldListFiltersFieldTypeFilterToggle')).to.be(true);
await testSubjects.click('field-@message-showDetails');
@ -324,5 +327,174 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await historyItem.findByTestSubject('TextBasedLangEditor-queryHistory-error');
});
});
describe('sorting', () => {
it('should sort correctly', async () => {
const savedSearchName = 'testSorting';
await PageObjects.discover.selectTextBaseLang();
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.discover.waitUntilSearchingHasFinished();
const testQuery = 'from logstash-* | sort @timestamp | limit 100';
await monacoEditor.setCodeEditorValue(testQuery);
await testSubjects.click('querySubmitButton');
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.discover.waitUntilSearchingHasFinished();
await PageObjects.unifiedFieldList.waitUntilSidebarHasLoaded();
await PageObjects.unifiedFieldList.clickFieldListItemAdd('bytes');
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.discover.waitUntilSearchingHasFinished();
await retry.waitFor('first cell contains an initial value', async () => {
const cell = await dataGrid.getCellElement(0, 2);
const text = await cell.getVisibleText();
return text === '1,623';
});
expect(await testSubjects.getVisibleText('dataGridColumnSortingButton')).to.be(
'Sort fields'
);
await dataGrid.clickDocSortDesc('bytes', 'Sort High-Low');
await PageObjects.discover.waitUntilSearchingHasFinished();
await retry.waitFor('first cell contains the highest value', async () => {
const cell = await dataGrid.getCellElement(0, 2);
const text = await cell.getVisibleText();
return text === '483';
});
expect(await testSubjects.getVisibleText('dataGridColumnSortingButton')).to.be(
'Sort fields\n1'
);
await PageObjects.discover.saveSearch(savedSearchName);
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.discover.waitUntilSearchingHasFinished();
await retry.waitFor('first cell contains the same highest value', async () => {
const cell = await dataGrid.getCellElement(0, 2);
const text = await cell.getVisibleText();
return text === '483';
});
await browser.refresh();
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.discover.waitUntilSearchingHasFinished();
await retry.waitFor('first cell contains the same highest value after reload', async () => {
const cell = await dataGrid.getCellElement(0, 2);
const text = await cell.getVisibleText();
return text === '483';
});
await PageObjects.discover.clickNewSearchButton();
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.discover.waitUntilSearchingHasFinished();
await PageObjects.discover.loadSavedSearch(savedSearchName);
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.discover.waitUntilSearchingHasFinished();
await retry.waitFor(
'first cell contains the same highest value after reopening',
async () => {
const cell = await dataGrid.getCellElement(0, 2);
const text = await cell.getVisibleText();
return text === '483';
}
);
await dataGrid.clickDocSortDesc('bytes', 'Sort Low-High');
await PageObjects.discover.waitUntilSearchingHasFinished();
await retry.waitFor('first cell contains the lowest value', async () => {
const cell = await dataGrid.getCellElement(0, 2);
const text = await cell.getVisibleText();
return text === '0';
});
expect(await testSubjects.getVisibleText('dataGridColumnSortingButton')).to.be(
'Sort fields\n1'
);
await PageObjects.unifiedFieldList.clickFieldListItemAdd('extension');
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.discover.waitUntilSearchingHasFinished();
await dataGrid.clickDocSortDesc('extension', 'Sort A-Z');
await retry.waitFor('first cell contains the lowest value for extension', async () => {
const cell = await dataGrid.getCellElement(0, 3);
const text = await cell.getVisibleText();
return text === 'css';
});
expect(await testSubjects.getVisibleText('dataGridColumnSortingButton')).to.be(
'Sort fields\n2'
);
await browser.refresh();
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.discover.waitUntilSearchingHasFinished();
await retry.waitFor('first cell contains the same lowest value after reload', async () => {
const cell = await dataGrid.getCellElement(0, 2);
const text = await cell.getVisibleText();
return text === '0';
});
await retry.waitFor(
'first cell contains the same lowest value for extension after reload',
async () => {
const cell = await dataGrid.getCellElement(0, 3);
const text = await cell.getVisibleText();
return text === 'css';
}
);
await PageObjects.discover.saveSearch(savedSearchName);
await PageObjects.common.navigateToApp('dashboard');
await PageObjects.dashboard.clickNewDashboard();
await PageObjects.timePicker.setDefaultAbsoluteRange();
await dashboardAddPanel.clickOpenAddPanel();
await dashboardAddPanel.addSavedSearch(savedSearchName);
await PageObjects.header.waitUntilLoadingHasFinished();
await retry.waitFor(
'first cell contains the same lowest value as dashboard panel',
async () => {
const cell = await dataGrid.getCellElement(0, 2);
const text = await cell.getVisibleText();
return text === '0';
}
);
await retry.waitFor(
'first cell contains the lowest value for extension as dashboard panel',
async () => {
const cell = await dataGrid.getCellElement(0, 3);
const text = await cell.getVisibleText();
return text === 'css';
}
);
expect(await testSubjects.getVisibleText('dataGridColumnSortingButton')).to.be(
'Sort fields\n2'
);
});
});
});
}

View file

@ -83,7 +83,7 @@ export class DataGridService extends FtrService {
}
private getCellElementSelector(rowIndex: number = 0, columnIndex: number = 0) {
return `[data-test-subj="euiDataGridBody"] [data-test-subj="dataGridRowCell"][data-gridcell-column-index="${columnIndex}"][data-gridcell-row-index="${rowIndex}"]`;
return `[data-test-subj="euiDataGridBody"] [data-test-subj="dataGridRowCell"][data-gridcell-column-index="${columnIndex}"][data-gridcell-visible-row-index="${rowIndex}"]`;
}
/**