kibana/test/functional/apps/discover/group1/_discover.ts
Davis McPhee 3e1865513d
[Discover] Add resize support to the Discover field list sidebar (#167066)
## Summary

This PR adds resize support to the Discover field list sidebar, which is
persisted to a user's local storage similar to the resizable chart
height.

Additionally it migrates the resizable layout code from Unified
Histogram to a new package called `kbn-resizable-layout` so it can be
shared between Discover and Unified Histogram, as well as act as a new
platform component that other teams can consume to create their own
resizable layouts.


![resize](71b9a0ae-1795-43c8-acb0-e75fe46e2a8a)

Resolves #9531.

### Checklist

- [ ] ~Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)~
- [ ]
~[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials~
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [x] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard accessibility](https://webaim.org/techniques/keyboard/))
- [ ] ~Any UI touched in this PR does not create any new axe failures
(run axe in browser:
[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),
[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))~
- [ ] ~If a plugin configuration key changed, check if it needs to be
allowlisted in the cloud and added to the [docker
list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)~
- [x] This renders correctly on smaller devices using a responsive
layout. (You can test this [in your
browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))
- [x] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)

### For maintainers

- [ ] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
2023-09-27 21:52:25 -03:00

370 lines
16 KiB
TypeScript

/*
* 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 expect from '@kbn/expect';
import { FtrProviderContext } from '../ftr_provider_context';
export default function ({ getService, getPageObjects }: FtrProviderContext) {
const browser = getService('browser');
const log = getService('log');
const retry = getService('retry');
const esArchiver = getService('esArchiver');
const kibanaServer = getService('kibanaServer');
const queryBar = getService('queryBar');
const inspector = getService('inspector');
const testSubjects = getService('testSubjects');
const PageObjects = getPageObjects([
'common',
'discover',
'header',
'timePicker',
'unifiedFieldList',
]);
const defaultSettings = {
defaultIndex: 'logstash-*',
};
describe('discover test', function describeIndexTests() {
before(async function () {
log.debug('load kibana index with default index pattern');
await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover');
// and load a set of makelogs data
await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional');
await kibanaServer.uiSettings.replace(defaultSettings);
await PageObjects.common.navigateToApp('discover');
await PageObjects.timePicker.setDefaultAbsoluteRange();
});
after(async () => {
await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] });
});
describe('query', function () {
const queryName1 = 'Query # 1';
it('should show correct time range string by timepicker', async function () {
const time = await PageObjects.timePicker.getTimeConfig();
expect(time.start).to.be(PageObjects.timePicker.defaultStartTime);
expect(time.end).to.be(PageObjects.timePicker.defaultEndTime);
const rowData = await PageObjects.discover.getDocTableIndex(1);
log.debug('check the newest doc timestamp in UTC (check diff timezone in last test)');
expect(rowData).to.contain('Sep 22, 2015 @ 23:50:13.253');
});
it('save query should show toast message and display query name', async function () {
await PageObjects.discover.saveSearch(queryName1);
const actualQueryNameString = await PageObjects.discover.getCurrentQueryName();
expect(actualQueryNameString).to.be(queryName1);
});
it('load query should show query name', async function () {
await PageObjects.discover.loadSavedSearch(queryName1);
await retry.try(async function () {
expect(await PageObjects.discover.getCurrentQueryName()).to.be(queryName1);
});
});
it('renaming a saved query should modify name in breadcrumb', async function () {
const queryName2 = 'Modified Query # 1';
await PageObjects.discover.loadSavedSearch(queryName1);
await PageObjects.discover.saveSearch(queryName2);
await retry.try(async function () {
expect(await PageObjects.discover.getCurrentQueryName()).to.be(queryName2);
});
});
it('should show the correct hit count', async function () {
const expectedHitCount = '14,004';
await retry.try(async function () {
expect(await PageObjects.discover.getHitCount()).to.be(expectedHitCount);
});
});
it('should show correct time range string in chart', async function () {
const actualTimeString = await PageObjects.discover.getChartTimespan();
const expectedTimeString = `${PageObjects.timePicker.defaultStartTime} - ${PageObjects.timePicker.defaultEndTime} (interval: Auto - 3 hours)`;
expect(actualTimeString).to.be(expectedTimeString);
});
it('should modify the time range when a bar is clicked', async function () {
await PageObjects.timePicker.setDefaultAbsoluteRange();
await PageObjects.discover.clickHistogramBar();
await PageObjects.discover.waitUntilSearchingHasFinished();
const time = await PageObjects.timePicker.getTimeConfig();
expect(time.start).to.be('Sep 21, 2015 @ 12:00:00.000');
expect(time.end).to.be('Sep 21, 2015 @ 15:00:00.000');
await retry.waitForWithTimeout(
'table to contain the right search result',
3000,
async () => {
const rowData = await PageObjects.discover.getDocTableField(1);
log.debug(`The first timestamp value in doc table: ${rowData}`);
return rowData.includes('Sep 21, 2015 @ 14:59:08.840');
}
);
});
// FLAKY: https://github.com/elastic/kibana/issues/146223
it.skip('should show correct initial chart interval of Auto', async function () {
await PageObjects.timePicker.setDefaultAbsoluteRange();
await PageObjects.discover.waitUntilSearchingHasFinished();
const actualInterval = await PageObjects.discover.getChartInterval();
const expectedInterval = 'Auto';
expect(actualInterval).to.be(expectedInterval);
});
it('should not show "no results"', async () => {
const isVisible = await PageObjects.discover.hasNoResults();
expect(isVisible).to.be(false);
});
it('should reload the saved search with persisted query to show the initial hit count', async function () {
await PageObjects.timePicker.setDefaultAbsoluteRange();
await PageObjects.discover.waitUntilSearchingHasFinished();
// apply query some changes
await queryBar.setQuery('test');
await queryBar.submitQuery();
await retry.try(async function () {
expect(await PageObjects.discover.getHitCount()).to.be('22');
});
// reset to persisted state
await queryBar.clearQuery();
await PageObjects.discover.clickResetSavedSearchButton();
const expectedHitCount = '14,004';
await retry.try(async function () {
expect(await queryBar.getQueryString()).to.be('');
expect(await PageObjects.discover.getHitCount()).to.be(expectedHitCount);
});
});
});
describe('query #2, which has an empty time range', () => {
const fromTime = 'Jun 11, 1999 @ 09:22:11.000';
const toTime = 'Jun 12, 1999 @ 11:21:04.000';
before(async () => {
log.debug('setAbsoluteRangeForAnotherQuery');
await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime);
await PageObjects.discover.waitUntilSearchingHasFinished();
});
it('should show "no results"', async () => {
await retry.waitFor('no results screen is displayed', async function () {
const isVisible = await PageObjects.discover.hasNoResults();
return isVisible === true;
});
});
it('should suggest a new time range is picked', async () => {
const isVisible = await PageObjects.discover.hasNoResultsTimepicker();
expect(isVisible).to.be(true);
});
it('should show matches when time range is expanded', async () => {
await PageObjects.discover.expandTimeRangeAsSuggestedInNoResultsMessage();
await PageObjects.discover.waitUntilSearchingHasFinished();
await retry.try(async function () {
expect(await PageObjects.discover.hasNoResults()).to.be(false);
expect(await PageObjects.discover.getHitCountInt()).to.be.above(0);
});
});
});
describe('nested query', () => {
before(async () => {
log.debug('setAbsoluteRangeForAnotherQuery');
await PageObjects.timePicker.setDefaultAbsoluteRange();
await PageObjects.discover.waitUntilSearchingHasFinished();
});
it('should support querying on nested fields', async function () {
await queryBar.setQuery('nestedField:{ child: nestedValue }');
await queryBar.submitQuery();
await retry.try(async function () {
expect(await PageObjects.discover.getHitCount()).to.be('1');
});
});
});
describe('data-shared-item', function () {
it('should have correct data-shared-item title and description', async () => {
const expected = {
title: 'A Saved Search',
description: 'A Saved Search Description',
};
await retry.try(async () => {
await PageObjects.discover.loadSavedSearch(expected.title);
const { title, description } =
await PageObjects.common.getSharedItemTitleAndDescription();
expect(title).to.eql(expected.title);
expect(description).to.eql(expected.description);
});
});
});
describe('time zone switch', () => {
it('should show bars in the correct time zone after switching', async function () {
await kibanaServer.uiSettings.update({ 'dateFormat:tz': 'America/Phoenix' });
await PageObjects.common.navigateToApp('discover');
await PageObjects.header.awaitKibanaChrome();
await PageObjects.timePicker.setDefaultAbsoluteRange();
await queryBar.clearQuery();
log.debug(
'check that the newest doc timestamp is now -7 hours from the UTC time in the first test'
);
const rowData = await PageObjects.discover.getDocTableIndex(1);
expect(rowData.startsWith('Sep 22, 2015 @ 16:50:13.253')).to.be.ok();
});
});
describe('invalid time range in URL', function () {
it('should get the default timerange', async function () {
await PageObjects.common.navigateToUrl('discover', '#/?_g=(time:(from:now-15m,to:null))', {
useActualUrl: true,
});
await PageObjects.header.awaitKibanaChrome();
const time = await PageObjects.timePicker.getTimeConfig();
expect(time.start).to.be('~ 15 minutes ago');
expect(time.end).to.be('now');
});
});
describe('managing fields', function () {
it('should add a field, sort by it, remove it and also sorting by it', async function () {
await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings();
await PageObjects.common.navigateToApp('discover');
await PageObjects.unifiedFieldList.clickFieldListItemAdd('_score');
await PageObjects.discover.clickFieldSort('_score', 'Sort Low-High');
const currentUrlWithScore = await browser.getCurrentUrl();
expect(currentUrlWithScore).to.contain('_score');
await PageObjects.unifiedFieldList.clickFieldListItemRemove('_score');
const currentUrlWithoutScore = await browser.getCurrentUrl();
expect(currentUrlWithoutScore).not.to.contain('_score');
});
it('should add a field with customLabel, sort by it, display it correctly', async function () {
await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings();
await PageObjects.common.navigateToApp('discover');
await PageObjects.unifiedFieldList.clickFieldListItemAdd('referer');
await PageObjects.discover.clickFieldSort('referer', 'Sort A-Z');
expect(await PageObjects.discover.getDocHeader()).to.have.string('Referer custom');
expect(await PageObjects.unifiedFieldList.getAllFieldNames()).to.contain('Referer custom');
const url = await browser.getCurrentUrl();
expect(url).to.contain('referer');
});
});
describe('refresh interval', function () {
it('should refetch when autofresh is enabled', async () => {
const intervalS = 5;
await PageObjects.timePicker.startAutoRefresh(intervalS);
const getRequestTimestamp = async () => {
// check inspector panel request stats for timestamp
await inspector.open();
const requestStats = await inspector.getTableData();
const requestStatsRow = requestStats.filter(
(r) => r && r[0] && r[0].includes('Request timestamp')
);
if (!requestStatsRow || !requestStatsRow[0] || !requestStatsRow[0][1]) {
return '';
}
await inspector.close();
return requestStatsRow[0][1];
};
const requestTimestampBefore = await getRequestTimestamp();
await retry.waitFor('refetch because of refresh interval', async () => {
const requestTimestampAfter = await getRequestTimestamp();
log.debug(
`Timestamp before: ${requestTimestampBefore}, Timestamp after: ${requestTimestampAfter}`
);
return Boolean(requestTimestampAfter) && requestTimestampBefore !== requestTimestampAfter;
});
});
after(async () => {
await inspector.close();
await PageObjects.timePicker.pauseAutoRefresh();
});
});
describe('resizable layout panels', () => {
it('should allow resizing the histogram layout panels', async () => {
const resizeDistance = 100;
const topPanel = await testSubjects.find('unifiedHistogramResizablePanelFixed');
const mainPanel = await testSubjects.find('unifiedHistogramResizablePanelFlex');
const resizeButton = await testSubjects.find('unifiedHistogramResizableButton');
const topPanelSize = (await topPanel.getPosition()).height;
const mainPanelSize = (await mainPanel.getPosition()).height;
await browser.dragAndDrop(
{ location: resizeButton },
{ location: { x: 0, y: resizeDistance } }
);
const newTopPanelSize = (await topPanel.getPosition()).height;
const newMainPanelSize = (await mainPanel.getPosition()).height;
expect(newTopPanelSize).to.be(topPanelSize + resizeDistance);
expect(newMainPanelSize).to.be(mainPanelSize - resizeDistance);
});
it('should allow resizing the sidebar layout panels', async () => {
const resizeDistance = 100;
const leftPanel = await testSubjects.find('discoverLayoutResizablePanelFixed');
const mainPanel = await testSubjects.find('discoverLayoutResizablePanelFlex');
const resizeButton = await testSubjects.find('discoverLayoutResizableButton');
const leftPanelSize = (await leftPanel.getPosition()).width;
const mainPanelSize = (await mainPanel.getPosition()).width;
await browser.dragAndDrop(
{ location: resizeButton },
{ location: { x: resizeDistance, y: 0 } }
);
const newLeftPanelSize = (await leftPanel.getPosition()).width;
const newMainPanelSize = (await mainPanel.getPosition()).width;
expect(newLeftPanelSize).to.be(leftPanelSize + resizeDistance);
expect(newMainPanelSize).to.be(mainPanelSize - resizeDistance);
});
});
describe('URL state', () => {
it('should show a warning and fall back to the default data view when navigating to a URL with an invalid data view ID', async () => {
await PageObjects.common.navigateToApp('discover');
await PageObjects.timePicker.setDefaultAbsoluteRange();
await PageObjects.header.waitUntilLoadingHasFinished();
const dataViewId = await PageObjects.discover.getCurrentDataViewId();
const originalUrl = await browser.getCurrentUrl();
const newUrl = originalUrl.replace(dataViewId, 'invalid-data-view-id');
await browser.get(newUrl);
await PageObjects.header.waitUntilLoadingHasFinished();
await retry.try(async () => {
expect(await browser.getCurrentUrl()).to.be(originalUrl);
expect(await testSubjects.exists('dscDataViewNotFoundShowDefaultWarning')).to.be(true);
});
});
it('should show a warning and fall back to the current data view if the URL is updated to an invalid data view ID', async () => {
await PageObjects.common.navigateToApp('discover');
await PageObjects.timePicker.setDefaultAbsoluteRange();
const originalHash = await browser.execute<[], string>('return window.location.hash');
const dataViewId = await PageObjects.discover.getCurrentDataViewId();
const newHash = originalHash.replace(dataViewId, 'invalid-data-view-id');
await browser.execute(`window.location.hash = "${newHash}"`);
await PageObjects.header.waitUntilLoadingHasFinished();
await retry.try(async () => {
const currentHash = await browser.execute<[], string>('return window.location.hash');
expect(currentHash).to.be(originalHash);
expect(await testSubjects.exists('dscDataViewNotFoundShowSavedWarning')).to.be(true);
});
});
});
});
}