diff --git a/src/plugins/discover/public/components/hits_counter/hits_counter.test.tsx b/src/plugins/discover/public/components/hits_counter/hits_counter.test.tsx index 18f9c8b629b3b..e34ad87d403e5 100644 --- a/src/plugins/discover/public/components/hits_counter/hits_counter.test.tsx +++ b/src/plugins/discover/public/components/hits_counter/hits_counter.test.tsx @@ -14,8 +14,20 @@ import { findTestSubject } from '@elastic/eui/lib/test'; import { EuiLoadingSpinner } from '@elastic/eui'; import { BehaviorSubject } from 'rxjs'; import { getDiscoverStateMock } from '../../__mocks__/discover_state.mock'; -import { DataTotalHits$ } from '../../application/main/state_management/discover_data_state_container'; +import { + DataDocuments$, + DataTotalHits$, +} from '../../application/main/state_management/discover_data_state_container'; import { FetchStatus } from '../../application/types'; +import { dataViewMock, esHitsMock } from '@kbn/discover-utils/src/__mocks__'; +import { buildDataTableRecord } from '@kbn/discover-utils'; + +function getDocuments$(count: number = 5) { + return new BehaviorSubject({ + fetchStatus: FetchStatus.COMPLETE, + result: esHitsMock.map((esHit) => buildDataTableRecord(esHit, dataViewMock)).slice(0, count), + }) as DataDocuments$; +} describe('hits counter', function () { it('expect to render the number of hits', function () { @@ -24,6 +36,7 @@ describe('hits counter', function () { fetchStatus: FetchStatus.COMPLETE, result: 1, }) as DataTotalHits$; + stateContainer.dataState.data$.documents$ = getDocuments$(); const component1 = mountWithIntl( ); @@ -45,6 +58,7 @@ describe('hits counter', function () { fetchStatus: FetchStatus.COMPLETE, result: 1899, }) as DataTotalHits$; + stateContainer.dataState.data$.documents$ = getDocuments$(); const component1 = mountWithIntl( ); @@ -64,6 +78,7 @@ describe('hits counter', function () { fetchStatus: FetchStatus.PARTIAL, result: 2, }) as DataTotalHits$; + stateContainer.dataState.data$.documents$ = getDocuments$(); const component = mountWithIntl( ); @@ -76,6 +91,7 @@ describe('hits counter', function () { fetchStatus: FetchStatus.PARTIAL, result: 2, }) as DataTotalHits$; + stateContainer.dataState.data$.documents$ = getDocuments$(); const component = mountWithIntl( ); @@ -89,9 +105,51 @@ describe('hits counter', function () { fetchStatus: FetchStatus.LOADING, result: undefined, }) as DataTotalHits$; + stateContainer.dataState.data$.documents$ = getDocuments$(); const component = mountWithIntl( ); expect(component.isEmptyRender()).toBe(true); }); + + it('should render discoverQueryHitsPartial when status is error', () => { + const stateContainer = getDiscoverStateMock({ isTimeBased: true }); + stateContainer.dataState.data$.totalHits$ = new BehaviorSubject({ + fetchStatus: FetchStatus.ERROR, + result: undefined, + }) as DataTotalHits$; + stateContainer.dataState.data$.documents$ = getDocuments$(3); + const component = mountWithIntl( + + ); + expect(component.find('[data-test-subj="discoverQueryHitsPartial"]').length).toBe(1); + expect(findTestSubject(component, 'discoverQueryTotalHits').text()).toBe('≥3 resultsInfo'); + expect(component.text()).toBe('≥3 resultsInfo'); + + stateContainer.dataState.data$.totalHits$ = new BehaviorSubject({ + fetchStatus: FetchStatus.ERROR, + result: 200, + }) as DataTotalHits$; + stateContainer.dataState.data$.documents$ = getDocuments$(2); + + const component2 = mountWithIntl( + + ); + expect(component2.find('[data-test-subj="discoverQueryHitsPartial"]').length).toBe(1); + expect(findTestSubject(component2, 'discoverQueryTotalHits').text()).toBe('≥200Info'); + expect(component2.text()).toBe(' (≥200Info)'); + + stateContainer.dataState.data$.totalHits$ = new BehaviorSubject({ + fetchStatus: FetchStatus.ERROR, + result: 0, + }) as DataTotalHits$; + stateContainer.dataState.data$.documents$ = getDocuments$(1); + + const component3 = mountWithIntl( + + ); + expect(component3.find('[data-test-subj="discoverQueryHitsPartial"]').length).toBe(1); + expect(findTestSubject(component3, 'discoverQueryTotalHits').text()).toBe('≥1Info'); + expect(component3.text()).toBe(' (≥1Info)'); + }); }); diff --git a/src/plugins/discover/public/components/hits_counter/hits_counter.tsx b/src/plugins/discover/public/components/hits_counter/hits_counter.tsx index fc89183ff864d..203d32ce97f58 100644 --- a/src/plugins/discover/public/components/hits_counter/hits_counter.tsx +++ b/src/plugins/discover/public/components/hits_counter/hits_counter.tsx @@ -8,7 +8,7 @@ */ import React from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiText, EuiLoadingSpinner } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiText, EuiLoadingSpinner, EuiIconTip } from '@elastic/eui'; import { FormattedMessage, FormattedNumber } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import { css } from '@emotion/react'; @@ -29,18 +29,33 @@ export interface HitsCounterProps { export const HitsCounter: React.FC = ({ mode, stateContainer }) => { const totalHits$ = stateContainer.dataState.data$.totalHits$; const totalHitsState = useDataState(totalHits$); - const hitsTotal = totalHitsState.result; + let hitsTotal = totalHitsState.result; const hitsStatus = totalHitsState.fetchStatus; + const documents$ = stateContainer.dataState.data$.documents$; + const documentsState = useDataState(documents$); + const documentsCount = documentsState.result?.length || 0; + if (!hitsTotal && hitsStatus === FetchStatus.LOADING) { return null; } + if ( + hitsStatus === FetchStatus.ERROR && + documentsState.fetchStatus === FetchStatus.COMPLETE && + documentsCount > (hitsTotal ?? 0) + ) { + // if histogram returned partial results and which are less than the fetched documents count => + // override hitsTotal with the fetched documents count + hitsTotal = documentsCount; + } + + const showGreaterOrEqualSign = + hitsStatus === FetchStatus.PARTIAL || hitsStatus === FetchStatus.ERROR; + const formattedHits = ( @@ -55,7 +70,7 @@ export const HitsCounter: React.FC = ({ mode, stateContainer } const element = ( = ({ mode, stateContainer } - {hitsStatus === FetchStatus.PARTIAL && - (mode === HitsCounterMode.standalone ? ( + {showGreaterOrEqualSign ? ( + mode === HitsCounterMode.standalone ? ( = ({ mode, stateContainer } defaultMessage="≥{formattedHits}" values={{ formattedHits }} /> - ))} - {hitsStatus !== FetchStatus.PARTIAL && - (mode === HitsCounterMode.standalone ? ( - - ) : ( - formattedHits - ))} + ) + ) : mode === HitsCounterMode.standalone ? ( + + ) : ( + formattedHits + )} @@ -103,6 +117,19 @@ export const HitsCounter: React.FC = ({ mode, stateContainer } /> )} + {hitsStatus === FetchStatus.ERROR && ( + + + + )} ); diff --git a/src/plugins/unified_histogram/public/chart/histogram.test.tsx b/src/plugins/unified_histogram/public/chart/histogram.test.tsx index 26bdc0c505234..72b5c0cc0b791 100644 --- a/src/plugins/unified_histogram/public/chart/histogram.test.tsx +++ b/src/plugins/unified_histogram/public/chart/histogram.test.tsx @@ -240,7 +240,7 @@ describe('Histogram', () => { onLoad(false, adapters); }); expect(props.onTotalHitsChange).toHaveBeenLastCalledWith( - UnifiedHistogramFetchStatus.complete, + UnifiedHistogramFetchStatus.error, 100 ); expect(props.onChartLoad).toHaveBeenLastCalledWith({ adapters }); diff --git a/src/plugins/unified_histogram/public/chart/histogram.tsx b/src/plugins/unified_histogram/public/chart/histogram.tsx index faa5ddd2b1fc3..e63cf775158aa 100644 --- a/src/plugins/unified_histogram/public/chart/histogram.tsx +++ b/src/plugins/unified_histogram/public/chart/histogram.tsx @@ -130,9 +130,6 @@ export function Histogram({ | undefined; const response = json?.rawResponse; - // The response can have `response?._shards.failed` but we should still be able to show hits number - // TODO: show shards warnings as a badge next to the total hits number - if (requestFailed) { onTotalHitsChange?.(UnifiedHistogramFetchStatus.error, undefined); onChartLoad?.({ adapters: adapters ?? {} }); @@ -142,10 +139,14 @@ export function Histogram({ const adapterTables = adapters?.tables?.tables; const totalHits = computeTotalHits(hasLensSuggestions, adapterTables, isPlainRecord); - onTotalHitsChange?.( - isLoading ? UnifiedHistogramFetchStatus.loading : UnifiedHistogramFetchStatus.complete, - totalHits ?? hits?.total - ); + if (response?._shards?.failed || response?.timed_out) { + onTotalHitsChange?.(UnifiedHistogramFetchStatus.error, totalHits); + } else { + onTotalHitsChange?.( + isLoading ? UnifiedHistogramFetchStatus.loading : UnifiedHistogramFetchStatus.complete, + totalHits ?? hits?.total + ); + } if (response) { const newBucketInterval = buildBucketInterval({ diff --git a/test/functional/apps/discover/ccs_compatibility/_search_errors.ts b/test/functional/apps/discover/ccs_compatibility/_search_errors.ts index 7045e0e7d1a3b..96db6e2f7a347 100644 --- a/test/functional/apps/discover/ccs_compatibility/_search_errors.ts +++ b/test/functional/apps/discover/ccs_compatibility/_search_errors.ts @@ -65,7 +65,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // Ensure documents are still returned for the successful shards await retry.try(async function tryingForTime() { - const hitCount = await discover.getHitCount(); + const hitCount = await discover.getHitCount({ isPartial: true }); expect(hitCount).to.be('9,247'); }); diff --git a/test/functional/page_objects/discover_page.ts b/test/functional/page_objects/discover_page.ts index ab6356075fd81..3fa11ecd39d08 100644 --- a/test/functional/page_objects/discover_page.ts +++ b/test/functional/page_objects/discover_page.ts @@ -338,9 +338,11 @@ export class DiscoverPageObject extends FtrService { return await this.header.waitUntilLoadingHasFinished(); } - public async getHitCount() { + public async getHitCount({ isPartial }: { isPartial?: boolean } = {}) { await this.header.waitUntilLoadingHasFinished(); - return await this.testSubjects.getVisibleText('discoverQueryHits'); + return await this.testSubjects.getVisibleText( + isPartial ? 'discoverQueryHitsPartial' : 'discoverQueryHits' + ); } public async getHitCountInt() {