From fff2b03d2c4dcd99483b49bbe3899113ca07a7ac Mon Sep 17 00:00:00 2001 From: Dzmitry Tamashevich Date: Tue, 8 Nov 2022 17:39:49 +0300 Subject: [PATCH] [Discover] improve tests --- .../layout/use_discover_histogram.ts | 19 ++++-- .../application/main/utils/fetch_all.test.ts | 30 ++++++--- .../application/main/utils/fetch_all.ts | 37 ++++++----- .../saved_searches/get_saved_searches.test.ts | 2 + .../public/chart/breakdown_field_selector.tsx | 1 + .../unified_histogram/public/chart/chart.tsx | 2 +- .../public/chart/histogram.tsx | 2 +- .../public/chart/use_total_hits.ts | 17 +++-- .../public/layout/layout.tsx | 2 +- .../apps/discover/group1/_discover.ts | 63 ------------------- .../discover/group1/_discover_histogram.ts | 61 ++++++++++++++++++ .../group1/_discover_histogram_breakdown.ts | 61 ++++++++++++++++++ test/functional/apps/discover/group1/index.ts | 1 + test/functional/page_objects/discover_page.ts | 18 ++++++ .../translations/translations/fr-FR.json | 2 - .../translations/translations/ja-JP.json | 2 - .../translations/translations/zh-CN.json | 2 - 17 files changed, 213 insertions(+), 109 deletions(-) create mode 100644 test/functional/apps/discover/group1/_discover_histogram_breakdown.ts diff --git a/src/plugins/discover/public/application/main/components/layout/use_discover_histogram.ts b/src/plugins/discover/public/application/main/components/layout/use_discover_histogram.ts index a21bd5340af2a..958288703e2e7 100644 --- a/src/plugins/discover/public/application/main/components/layout/use_discover_histogram.ts +++ b/src/plugins/discover/public/application/main/components/layout/use_discover_histogram.ts @@ -25,6 +25,7 @@ import type { AppState, GetStateReturn } from '../../services/discover_state'; import { FetchStatus } from '../../../types'; import type { DiscoverSearchSessionManager } from '../../services/discover_search_session'; import type { InspectorAdapters } from '../../hooks/use_inspector'; +import { sendErrorTo } from '../../utils/fetch_all'; export const CHART_HIDDEN_KEY = 'discover:chartHidden'; export const HISTOGRAM_HEIGHT_KEY = 'discover:histogramHeight'; @@ -51,7 +52,7 @@ export const useDiscoverHistogram = ({ inspectorAdapters: InspectorAdapters; searchSessionManager: DiscoverSearchSessionManager; }) => { - const { storage } = useDiscoverServices(); + const { storage, data } = useDiscoverServices(); /** * Visualize @@ -133,10 +134,20 @@ export const useDiscoverHistogram = ({ * Total hits */ + const sendTotalHitsError = useMemo( + () => sendErrorTo(data, savedSearchData$.totalHits$), + [data, savedSearchData$.totalHits$] + ); + const onTotalHitsChange = useCallback( - (status: UnifiedHistogramFetchStatus, totalHits?: number) => { + (status: UnifiedHistogramFetchStatus, result?: number | Error) => { const { fetchStatus, recordRawType } = savedSearchData$.totalHits$.getValue(); + if (result instanceof Error) { + sendTotalHitsError(result); + return; + } + // If we have a partial result already, we don't // want to update the total hits back to loading if (fetchStatus === FetchStatus.PARTIAL && status === UnifiedHistogramFetchStatus.loading) { @@ -145,11 +156,11 @@ export const useDiscoverHistogram = ({ savedSearchData$.totalHits$.next({ fetchStatus: status.toString() as FetchStatus, - result: totalHits, + result, recordRawType, }); }, - [savedSearchData$.totalHits$] + [savedSearchData$.totalHits$, sendTotalHitsError] ); const { fetchStatus: hitsFetchStatus, result: hitsTotal } = useDataState( diff --git a/src/plugins/discover/public/application/main/utils/fetch_all.test.ts b/src/plugins/discover/public/application/main/utils/fetch_all.test.ts index 1c4aa235afea0..43a4fc0cafa71 100644 --- a/src/plugins/discover/public/application/main/utils/fetch_all.test.ts +++ b/src/plugins/discover/public/application/main/utils/fetch_all.test.ts @@ -20,12 +20,12 @@ import { DataDocumentsMsg, DataMainMsg, DataTotalHitsMsg, + RecordRawType, SavedSearchData, } from '../hooks/use_saved_search'; import { fetchDocuments } from './fetch_documents'; import { fetchSql } from './fetch_sql'; -import { fetchTotalHits } from './fetch_total_hits'; import { buildDataTableRecord } from '../../../utils/build_data_record'; import { dataViewMock } from '../../../__mocks__/data_view'; @@ -37,12 +37,7 @@ jest.mock('./fetch_sql', () => ({ fetchSql: jest.fn().mockResolvedValue([]), })); -jest.mock('./fetch_total_hits', () => ({ - fetchTotalHits: jest.fn(), -})); - const mockFetchDocuments = fetchDocuments as unknown as jest.MockedFunction; -const mockFetchTotalHits = fetchTotalHits as unknown as jest.MockedFunction; const mockFetchSQL = fetchSql as unknown as jest.MockedFunction; function subjectCollector(subject: Subject): () => Promise { @@ -88,7 +83,6 @@ describe('test fetchAll', () => { mockFetchDocuments.mockReset().mockResolvedValue([]); mockFetchSQL.mockReset().mockResolvedValue([]); - mockFetchTotalHits.mockReset().mockResolvedValue(42); }); test('changes of fetchStatus when starting with FetchStatus.UNINITIALIZED', async () => { @@ -135,8 +129,12 @@ describe('test fetchAll', () => { const documents = hits.map((hit) => buildDataTableRecord(hit, dataViewMock)); mockFetchDocuments.mockResolvedValue(documents); - mockFetchTotalHits.mockResolvedValue(42); await fetchAll(subjects, searchSource, false, deps); + subjects.totalHits$.next({ + fetchStatus: FetchStatus.COMPLETE, + recordRawType: RecordRawType.DOCUMENT, + result: 42, + }); expect(await collect()).toEqual([ { fetchStatus: FetchStatus.UNINITIALIZED }, { fetchStatus: FetchStatus.LOADING, recordRawType: 'document' }, @@ -149,24 +147,36 @@ describe('test fetchAll', () => { const collect = subjectCollector(subjects.totalHits$); searchSource.getField('index')!.isTimeBased = () => true; await fetchAll(subjects, searchSource, false, deps); + + subjects.totalHits$.next({ + fetchStatus: FetchStatus.COMPLETE, + recordRawType: RecordRawType.DOCUMENT, + result: 32, + }); + expect(await collect()).toEqual([ { fetchStatus: FetchStatus.UNINITIALIZED }, { fetchStatus: FetchStatus.LOADING, recordRawType: 'document' }, { fetchStatus: FetchStatus.PARTIAL, recordRawType: 'document', result: 0 }, // From documents query { fetchStatus: FetchStatus.COMPLETE, recordRawType: 'document', result: 32 }, ]); - expect(mockFetchTotalHits).not.toHaveBeenCalled(); }); test('should only fail totalHits$ query not main$ for error from that query', async () => { const collectTotalHits = subjectCollector(subjects.totalHits$); const collectMain = subjectCollector(subjects.main$); searchSource.getField('index')!.isTimeBased = () => false; - mockFetchTotalHits.mockRejectedValue({ msg: 'Oh noes!' }); const hits = [{ _id: '1', _index: 'logs' }]; const documents = hits.map((hit) => buildDataTableRecord(hit, dataViewMock)); mockFetchDocuments.mockResolvedValue(documents); await fetchAll(subjects, searchSource, false, deps); + + subjects.totalHits$.next({ + fetchStatus: FetchStatus.ERROR, + recordRawType: RecordRawType.DOCUMENT, + error: { msg: 'Oh noes!' } as unknown as Error, + }); + expect(await collectTotalHits()).toEqual([ { fetchStatus: FetchStatus.UNINITIALIZED }, { fetchStatus: FetchStatus.LOADING, recordRawType: 'document' }, diff --git a/src/plugins/discover/public/application/main/utils/fetch_all.ts b/src/plugins/discover/public/application/main/utils/fetch_all.ts index 82cf4712848b3..348013ed547fb 100644 --- a/src/plugins/discover/public/application/main/utils/fetch_all.ts +++ b/src/plugins/discover/public/application/main/utils/fetch_all.ts @@ -43,6 +43,25 @@ export interface FetchDeps { useNewFieldsApi: boolean; } +/** + * Method to create an error handler that will forward the received error + * to the specified subjects. It will ignore AbortErrors and will use the data + * plugin to show a toast for the error (e.g. allowing better insights into shard failures). + */ +export const sendErrorTo = ( + data: DataPublicPluginStart, + ...errorSubjects: Array +) => { + return (error: Error) => { + if (error instanceof Error && error.name === 'AbortError') { + return; + } + + data.search.showError(error); + errorSubjects.forEach((subject) => sendErrorMsg(subject, error)); + }; +}; + /** * This function starts fetching all required queries in Discover. This will be the query to load the individual * documents as well as any other requests that might be required to load the main view. @@ -58,22 +77,6 @@ export function fetchAll( ): Promise { const { initialFetchStatus, appStateContainer, services, useNewFieldsApi, data } = fetchDeps; - /** - * Method to create an error handler that will forward the received error - * to the specified subjects. It will ignore AbortErrors and will use the data - * plugin to show a toast for the error (e.g. allowing better insights into shard failures). - */ - const sendErrorTo = (...errorSubjects: Array) => { - return (error: Error) => { - if (error instanceof Error && error.name === 'AbortError') { - return; - } - - data.search.showError(error); - errorSubjects.forEach((subject) => sendErrorMsg(subject, error)); - }; - }; - try { const dataView = searchSource.getField('index')!; if (reset) { @@ -145,7 +148,7 @@ export function fetchAll( // Only the document query should send its errors to main$, to cause the full Discover app // to get into an error state. The other queries will not cause all of Discover to error out // but their errors will be shown in-place (e.g. of the chart). - .catch(sendErrorTo(dataSubjects.documents$, dataSubjects.main$)); + .catch(sendErrorTo(data, dataSubjects.documents$, dataSubjects.main$)); // Return a promise that will resolve once all the requests have finished or failed return documents.then(() => { diff --git a/src/plugins/saved_search/public/services/saved_searches/get_saved_searches.test.ts b/src/plugins/saved_search/public/services/saved_searches/get_saved_searches.test.ts index ca405537c363d..8e854a54947f8 100644 --- a/src/plugins/saved_search/public/services/saved_searches/get_saved_searches.test.ts +++ b/src/plugins/saved_search/public/services/saved_searches/get_saved_searches.test.ts @@ -100,6 +100,7 @@ describe('getSavedSearch', () => { expect(savedObjectsClient.resolve).toHaveBeenCalled(); expect(savedSearch).toMatchInlineSnapshot(` Object { + "breakdownField": undefined, "columns": Array [ "_source", ], @@ -197,6 +198,7 @@ describe('getSavedSearch', () => { expect(savedObjectsClient.resolve).toHaveBeenCalled(); expect(savedSearch).toMatchInlineSnapshot(` Object { + "breakdownField": undefined, "columns": Array [ "_source", ], diff --git a/src/plugins/unified_histogram/public/chart/breakdown_field_selector.tsx b/src/plugins/unified_histogram/public/chart/breakdown_field_selector.tsx index cfb05ecfa785d..b34192604d24e 100644 --- a/src/plugins/unified_histogram/public/chart/breakdown_field_selector.tsx +++ b/src/plugins/unified_histogram/public/chart/breakdown_field_selector.tsx @@ -52,6 +52,7 @@ export const BreakdownFieldSelector = ({ return ( void; onTimeIntervalChange?: (timeInterval: string) => void; onBreakdownFieldChange?: (breakdownField: DataViewField | undefined) => void; - onTotalHitsChange?: (status: UnifiedHistogramFetchStatus, totalHits?: number) => void; + onTotalHitsChange?: (status: UnifiedHistogramFetchStatus, result?: number | Error) => void; onChartLoad?: (event: UnifiedHistogramChartLoadEvent) => void; } diff --git a/src/plugins/unified_histogram/public/chart/histogram.tsx b/src/plugins/unified_histogram/public/chart/histogram.tsx index 7828bd6389b65..6eaefc40c49cc 100644 --- a/src/plugins/unified_histogram/public/chart/histogram.tsx +++ b/src/plugins/unified_histogram/public/chart/histogram.tsx @@ -40,7 +40,7 @@ export interface HistogramProps { filters: Filter[]; query: Query | AggregateQuery; timeRange: TimeRange; - onTotalHitsChange?: (status: UnifiedHistogramFetchStatus, totalHits?: number) => void; + onTotalHitsChange?: (status: UnifiedHistogramFetchStatus, result?: number | Error) => void; onChartLoad?: (event: UnifiedHistogramChartLoadEvent) => void; } diff --git a/src/plugins/unified_histogram/public/chart/use_total_hits.ts b/src/plugins/unified_histogram/public/chart/use_total_hits.ts index 65308a75190b2..af4fca24dd3d6 100644 --- a/src/plugins/unified_histogram/public/chart/use_total_hits.ts +++ b/src/plugins/unified_histogram/public/chart/use_total_hits.ts @@ -12,7 +12,7 @@ import type { AggregateQuery, Filter, Query, TimeRange } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; import { cloneDeep, isEqual } from 'lodash'; import { MutableRefObject, useEffect, useRef } from 'react'; -import { filter, lastValueFrom, map } from 'rxjs'; +import { catchError, filter, lastValueFrom, map, of } from 'rxjs'; import { UnifiedHistogramFetchStatus, UnifiedHistogramHitsContext, @@ -41,7 +41,7 @@ export const useTotalHits = ({ filters: Filter[]; query: Query | AggregateQuery; timeRange: TimeRange; - onTotalHitsChange?: (status: UnifiedHistogramFetchStatus, totalHits?: number) => void; + onTotalHitsChange?: (status: UnifiedHistogramFetchStatus, result?: number | Error) => void; }) => { const abortController = useRef(); const totalHitsDeps = useRef>(); @@ -150,7 +150,7 @@ const fetchTotalHits = async ({ filters: Filter[]; query: Query | AggregateQuery; timeRange: TimeRange; - onTotalHitsChange?: (status: UnifiedHistogramFetchStatus, totalHits?: number) => void; + onTotalHitsChange?: (status: UnifiedHistogramFetchStatus, result?: number | Error) => void; }) => { abortController.current?.abort(); abortController.current = undefined; @@ -217,10 +217,15 @@ const fetchTotalHits = async ({ }) .pipe( filter((res) => isCompleteResponse(res)), - map((res) => res.rawResponse.hits.total as number) + map((res) => res.rawResponse.hits.total as number), + catchError((error: Error) => of(error)) ); - const totalHits = await lastValueFrom(fetch$); + const result = await lastValueFrom(fetch$); - onTotalHitsChange?.(UnifiedHistogramFetchStatus.complete, totalHits); + const resultStatus = + result instanceof Error + ? UnifiedHistogramFetchStatus.error + : UnifiedHistogramFetchStatus.complete; + onTotalHitsChange?.(resultStatus, result); }; diff --git a/src/plugins/unified_histogram/public/layout/layout.tsx b/src/plugins/unified_histogram/public/layout/layout.tsx index e58271104ee17..2c8f92101b15e 100644 --- a/src/plugins/unified_histogram/public/layout/layout.tsx +++ b/src/plugins/unified_histogram/public/layout/layout.tsx @@ -84,7 +84,7 @@ export interface UnifiedHistogramLayoutProps extends PropsWithChildren * Callback to update the total hits -- should set {@link UnifiedHistogramHitsContext.status} to status * and {@link UnifiedHistogramHitsContext.total} to totalHits */ - onTotalHitsChange?: (status: UnifiedHistogramFetchStatus, totalHits?: number) => void; + onTotalHitsChange?: (status: UnifiedHistogramFetchStatus, result?: number | Error) => void; /** * Called when the histogram loading status changes */ diff --git a/test/functional/apps/discover/group1/_discover.ts b/test/functional/apps/discover/group1/_discover.ts index b67126ff2c8cb..1cba5aa4812d8 100644 --- a/test/functional/apps/discover/group1/_discover.ts +++ b/test/functional/apps/discover/group1/_discover.ts @@ -18,7 +18,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const kibanaServer = getService('kibanaServer'); const queryBar = getService('queryBar'); const inspector = getService('inspector'); - const elasticChart = getService('elasticChart'); const testSubjects = getService('testSubjects'); const PageObjects = getPageObjects(['common', 'discover', 'header', 'timePicker']); @@ -106,48 +105,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ); }); - it('should modify the time range when the histogram is brushed', async function () { - // this is the number of renderings of the histogram needed when new data is fetched - // this needs to be improved - const renderingCountInc = 1; - const prevRenderingCount = await elasticChart.getVisualizationRenderingCount(); - await PageObjects.timePicker.setDefaultAbsoluteRange(); - await PageObjects.discover.waitUntilSearchingHasFinished(); - await retry.waitFor('chart rendering complete', async () => { - const actualCount = await elasticChart.getVisualizationRenderingCount(); - const expectedCount = prevRenderingCount + renderingCountInc; - log.debug( - `renderings before brushing - actual: ${actualCount} expected: ${expectedCount}` - ); - return actualCount === expectedCount; - }); - let prevRowData = ''; - // to make sure the table is already rendered - await retry.try(async () => { - prevRowData = await PageObjects.discover.getDocTableField(1); - log.debug(`The first timestamp value in doc table before brushing: ${prevRowData}`); - }); - - await PageObjects.discover.brushHistogram(); - await PageObjects.discover.waitUntilSearchingHasFinished(); - await retry.waitFor('chart rendering complete after being brushed', async () => { - const actualCount = await elasticChart.getVisualizationRenderingCount(); - const expectedCount = prevRenderingCount + renderingCountInc * 2; - log.debug( - `renderings after brushing - actual: ${actualCount} expected: ${expectedCount}` - ); - return actualCount === expectedCount; - }); - const newDurationHours = await PageObjects.timePicker.getTimeDurationInHours(); - expect(Math.round(newDurationHours)).to.be(26); - - await retry.waitFor('doc table containing the documents of the brushed range', async () => { - const rowData = await PageObjects.discover.getDocTableField(1); - log.debug(`The first timestamp value in doc table after brushing: ${rowData}`); - return prevRowData !== rowData; - }); - }); - it('should show correct initial chart interval of Auto', async function () { await PageObjects.timePicker.setDefaultAbsoluteRange(); await PageObjects.discover.waitUntilSearchingHasFinished(); @@ -265,26 +222,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); - describe('empty query', function () { - it('should update the histogram timerange when the query is resubmitted', async function () { - await kibanaServer.uiSettings.update({ - 'timepicker:timeDefaults': '{ "from": "2015-09-18T19:37:13.000Z", "to": "now"}', - }); - await PageObjects.common.navigateToApp('discover'); - await PageObjects.header.awaitKibanaChrome(); - const initialTimeString = await PageObjects.discover.getChartTimespan(); - await queryBar.submitQuery(); - - await retry.waitFor('chart timespan to have changed', async () => { - const refreshedTimeString = await PageObjects.discover.getChartTimespan(); - log.debug( - `Timestamp before: ${initialTimeString}, Timestamp after: ${refreshedTimeString}` - ); - return refreshedTimeString !== initialTimeString; - }); - }); - }); - 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(); diff --git a/test/functional/apps/discover/group1/_discover_histogram.ts b/test/functional/apps/discover/group1/_discover_histogram.ts index 12effb75cb7f3..99775893c8b40 100644 --- a/test/functional/apps/discover/group1/_discover_histogram.ts +++ b/test/functional/apps/discover/group1/_discover_histogram.ts @@ -23,6 +23,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const browser = getService('browser'); const retry = getService('retry'); + const log = getService('log'); + const queryBar = getService('queryBar'); describe('discover histogram', function describeIndexTests() { before(async () => { @@ -52,6 +54,65 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { } } + it('should modify the time range when the histogram is brushed', async function () { + await PageObjects.common.navigateToApp('discover'); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await PageObjects.timePicker.setDefaultAbsoluteRange(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + // this is the number of renderings of the histogram needed when new data is fetched + const renderingCountInc = 1; + const prevRenderingCount = await elasticChart.getVisualizationRenderingCount(); + await PageObjects.timePicker.setDefaultAbsoluteRange(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await retry.waitFor('chart rendering complete', async () => { + const actualCount = await elasticChart.getVisualizationRenderingCount(); + const expectedCount = prevRenderingCount + renderingCountInc; + log.debug(`renderings before brushing - actual: ${actualCount} expected: ${expectedCount}`); + return actualCount === expectedCount; + }); + let prevRowData = ''; + // to make sure the table is already rendered + await retry.try(async () => { + prevRowData = await PageObjects.discover.getDocTableField(1); + log.debug(`The first timestamp value in doc table before brushing: ${prevRowData}`); + }); + + await PageObjects.discover.brushHistogram(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await retry.waitFor('chart rendering complete after being brushed', async () => { + const actualCount = await elasticChart.getVisualizationRenderingCount(); + const expectedCount = prevRenderingCount + renderingCountInc * 2; + log.debug(`renderings after brushing - actual: ${actualCount} expected: ${expectedCount}`); + return actualCount === expectedCount; + }); + const newDurationHours = await PageObjects.timePicker.getTimeDurationInHours(); + expect(Math.round(newDurationHours)).to.be(26); + + await retry.waitFor('doc table containing the documents of the brushed range', async () => { + const rowData = await PageObjects.discover.getDocTableField(1); + log.debug(`The first timestamp value in doc table after brushing: ${rowData}`); + return prevRowData !== rowData; + }); + }); + + it('should update the histogram timerange when the query is resubmitted', async function () { + await kibanaServer.uiSettings.update({ + 'timepicker:timeDefaults': '{ "from": "2015-09-18T19:37:13.000Z", "to": "now"}', + }); + await PageObjects.common.navigateToApp('discover'); + await PageObjects.header.awaitKibanaChrome(); + const initialTimeString = await PageObjects.discover.getChartTimespan(); + await queryBar.submitQuery(); + + await retry.waitFor('chart timespan to have changed', async () => { + const refreshedTimeString = await PageObjects.discover.getChartTimespan(); + log.debug( + `Timestamp before: ${initialTimeString}, Timestamp after: ${refreshedTimeString}` + ); + return refreshedTimeString !== initialTimeString; + }); + }); + it('should visualize monthly data with different day intervals', async () => { const from = 'Nov 1, 2017 @ 00:00:00.000'; const to = 'Mar 21, 2018 @ 00:00:00.000'; diff --git a/test/functional/apps/discover/group1/_discover_histogram_breakdown.ts b/test/functional/apps/discover/group1/_discover_histogram_breakdown.ts new file mode 100644 index 0000000000000..f83a76c45954b --- /dev/null +++ b/test/functional/apps/discover/group1/_discover_histogram_breakdown.ts @@ -0,0 +1,61 @@ +/* + * 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 esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); + const filterBar = getService('filterBar'); + const PageObjects = getPageObjects(['common', 'discover', 'header', 'timePicker']); + + describe('discover histogram breakdown', function describeIndexTests() { + before(async () => { + await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional'); + await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover'); + await kibanaServer.uiSettings.replace({ defaultIndex: 'logstash-*' }); + await PageObjects.common.navigateToApp('discover'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.timePicker.setDefaultAbsoluteRange(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + }); + + after(async () => { + await kibanaServer.importExport.unload('test/functional/fixtures/kbn_archiver/discover'); + }); + + it('should choose breakdown field', async () => { + await PageObjects.discover.chooseBreakdownField('extension.raw'); + await PageObjects.header.waitUntilLoadingHasFinished(); + const list = await PageObjects.discover.getHistogramLegendList(); + expect(list).to.eql(['png', 'css', 'jpg']); + }); + + it('should add filter using histogram legend values', async () => { + await PageObjects.discover.clickLegendFilter('png', '+'); + await PageObjects.header.waitUntilLoadingHasFinished(); + expect(await filterBar.hasFilter('extension.raw', 'png')).to.be(true); + }); + + it('should save breakdown field in saved search', async () => { + await filterBar.removeFilter('extension.raw'); + await PageObjects.discover.saveSearch('with breakdown'); + + await PageObjects.discover.clickNewSearchButton(); + await PageObjects.header.waitUntilLoadingHasFinished(); + const prevList = await PageObjects.discover.getHistogramLegendList(); + expect(prevList).to.eql([]); + + await PageObjects.discover.loadSavedSearch('with breakdown'); + await PageObjects.header.waitUntilLoadingHasFinished(); + const list = await PageObjects.discover.getHistogramLegendList(); + expect(list).to.eql(['png', 'css', 'jpg']); + }); + }); +} diff --git a/test/functional/apps/discover/group1/index.ts b/test/functional/apps/discover/group1/index.ts index ab6798400b7a2..82fd341ccce04 100644 --- a/test/functional/apps/discover/group1/index.ts +++ b/test/functional/apps/discover/group1/index.ts @@ -23,6 +23,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./_no_data')); loadTestFile(require.resolve('./_discover')); loadTestFile(require.resolve('./_discover_accessibility')); + loadTestFile(require.resolve('./_discover_histogram_breakdown')); loadTestFile(require.resolve('./_discover_histogram')); loadTestFile(require.resolve('./_doc_accessibility')); loadTestFile(require.resolve('./_filter_editor')); diff --git a/test/functional/page_objects/discover_page.ts b/test/functional/page_objects/discover_page.ts index 44a29441d2707..36290cf49190c 100644 --- a/test/functional/page_objects/discover_page.ts +++ b/test/functional/page_objects/discover_page.ts @@ -8,6 +8,7 @@ import expect from '@kbn/expect'; import { FtrService } from '../ftr_provider_context'; +import { WebElementWrapper } from '../services/lib/web_element_wrapper'; export class DiscoverPageObject extends FtrService { private readonly retry = this.ctx.getService('retry'); @@ -25,6 +26,7 @@ export class DiscoverPageObject extends FtrService { private readonly kibanaServer = this.ctx.getService('kibanaServer'); private readonly fieldEditor = this.ctx.getService('fieldEditor'); private readonly queryBar = this.ctx.getService('queryBar'); + private readonly comboBox = this.ctx.getService('comboBox'); private readonly defaultFindTimeout = this.config.get('timeouts.find'); @@ -202,6 +204,22 @@ export class DiscoverPageObject extends FtrService { ); } + public async chooseBreakdownField(field: string) { + await this.comboBox.set('unifiedHistogramBreakdownFieldSelector', field); + } + + public async getHistogramLegendList() { + const unifiedHistogram = await this.testSubjects.find('unifiedHistogramChart'); + const list = await unifiedHistogram.findAllByClassName('echLegendItem__label'); + return Promise.all(list.map((elem: WebElementWrapper) => elem.getVisibleText())); + } + + public async clickLegendFilter(field: string, type: '+' | '-') { + const filterType = type === '+' ? 'filterIn' : 'filterOut'; + await this.testSubjects.click(`legend-${field}`); + await this.testSubjects.click(`legend-${field}-${filterType}`); + } + public async getCurrentQueryName() { return await this.globalNav.getLastBreadcrumb(); } diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index c42c82362e468..4276e29bb9826 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -2219,7 +2219,6 @@ "discover.grid.viewDoc": "Afficher/Masquer les détails de la boîte de dialogue", "discover.gridSampleSize.advancedSettingsLinkLabel": "Paramètres avancés", "discover.helpMenu.appName": "Découverte", - "discover.inspectorRequestDataTitleChart": "Données du graphique", "discover.inspectorRequestDataTitleDocuments": "Documents", "discover.inspectorRequestDescriptionDocument": "Cette requête interroge Elasticsearch afin de récupérer les documents.", "discover.json.codeEditorAriaLabel": "Affichage JSON en lecture seule d’un document Elasticsearch", @@ -2269,7 +2268,6 @@ "discover.sampleData.viewLinkLabel": "Découverte", "discover.savedSearch.savedObjectName": "Recherche enregistrée", "discover.savedSearchEmbeddable.action.viewSavedSearch.displayName": "Ouvrir dans Discover", - "discover.searchingTitle": "Recherche", "discover.selectColumnHeader": "Sélectionner la colonne", "discover.showAllDocuments": "Afficher tous les documents", "discover.showErrorMessageAgain": "Afficher le message d'erreur", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index b3ab75165960f..866f88d818348 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -2215,7 +2215,6 @@ "discover.grid.viewDoc": "詳細ダイアログを切り替え", "discover.gridSampleSize.advancedSettingsLinkLabel": "高度な設定", "discover.helpMenu.appName": "Discover", - "discover.inspectorRequestDataTitleChart": "グラフデータ", "discover.inspectorRequestDataTitleDocuments": "ドキュメント", "discover.inspectorRequestDescriptionDocument": "このリクエストはElasticsearchにクエリをかけ、ドキュメントを取得します。", "discover.json.codeEditorAriaLabel": "Elasticsearch ドキュメントの JSON ビューのみを読み込む", @@ -2265,7 +2264,6 @@ "discover.sampleData.viewLinkLabel": "Discover", "discover.savedSearch.savedObjectName": "保存検索", "discover.savedSearchEmbeddable.action.viewSavedSearch.displayName": "Discoverで開く", - "discover.searchingTitle": "検索中", "discover.selectColumnHeader": "列を選択", "discover.showAllDocuments": "すべてのドキュメントを表示", "discover.showErrorMessageAgain": "エラーメッセージを表示", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 1adc45ea80d44..de482c8782f68 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -2219,7 +2219,6 @@ "discover.grid.viewDoc": "切换具有详情的对话框", "discover.gridSampleSize.advancedSettingsLinkLabel": "高级设置", "discover.helpMenu.appName": "Discover", - "discover.inspectorRequestDataTitleChart": "图表数据", "discover.inspectorRequestDataTitleDocuments": "文档", "discover.inspectorRequestDescriptionDocument": "此请求将查询 Elasticsearch 以获取文档。", "discover.json.codeEditorAriaLabel": "Elasticsearch 文档的只读 JSON 视图", @@ -2269,7 +2268,6 @@ "discover.sampleData.viewLinkLabel": "Discover", "discover.savedSearch.savedObjectName": "已保存搜索", "discover.savedSearchEmbeddable.action.viewSavedSearch.displayName": "在 Discover 中打开", - "discover.searchingTitle": "正在搜索", "discover.selectColumnHeader": "选择列", "discover.showAllDocuments": "显示所有文档", "discover.showErrorMessageAgain": "显示错误消息",