diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx index 6854452fd02a4..bb564214e4fab 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx @@ -125,6 +125,7 @@ export function IndexPatternDataPanel({ id, title: indexPatterns[id].title, timeFieldName: indexPatterns[id].timeFieldName, + fields: indexPatterns[id].fields, })); const dslQuery = buildSafeEsQuery( @@ -197,6 +198,7 @@ export function IndexPatternDataPanel({ charts={charts} onChangeIndexPattern={onChangeIndexPattern} existingFields={state.existingFields} + existenceFetchFailed={state.existenceFetchFailed} /> )} @@ -231,6 +233,7 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ currentIndexPatternId, indexPatternRefs, indexPatterns, + existenceFetchFailed, query, dateRange, filters, @@ -249,6 +252,7 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ onChangeIndexPattern: (newId: string) => void; existingFields: IndexPatternPrivateState['existingFields']; charts: ChartsPluginSetup; + existenceFetchFailed?: boolean; }) { const [localState, setLocalState] = useState({ nameFilter: '', @@ -553,9 +557,15 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ - { - setLocalState((s) => ({ - ...s, - isEmptyAccordionOpen: open, - })); - const displayedFieldLength = - (localState.isAvailableAccordionOpen - ? filteredFieldGroups.availableFields.length - : 0) + (open ? filteredFieldGroups.emptyFields.length : 0); - setPageSize( - Math.max(PAGINATION_SIZE, Math.min(pageSize * 1.5, displayedFieldLength)) - ); - }} - renderCallout={ - - } - /> + {!existenceFetchFailed && ( + { + setLocalState((s) => ({ + ...s, + isEmptyAccordionOpen: open, + })); + const displayedFieldLength = + (localState.isAvailableAccordionOpen + ? filteredFieldGroups.availableFields.length + : 0) + (open ? filteredFieldGroups.emptyFields.length : 0); + setPageSize( + Math.max(PAGINATION_SIZE, Math.min(pageSize * 1.5, displayedFieldLength)) + ); + }} + renderCallout={ + + } + /> + )} diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/fields_accordion.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/fields_accordion.tsx index 7cc049c107b87..1d60b0b5cbbee 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/fields_accordion.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/fields_accordion.tsx @@ -6,12 +6,14 @@ import './datapanel.scss'; import React, { memo, useCallback } from 'react'; +import { i18n } from '@kbn/i18n'; import { EuiText, EuiNotificationBadge, EuiSpacer, EuiAccordion, EuiLoadingSpinner, + EuiIconTip, } from '@elastic/eui'; import { DataPublicPluginStart } from 'src/plugins/data/public'; import { IndexPatternField } from './types'; @@ -44,6 +46,7 @@ export interface FieldsAccordionProps { fieldProps: FieldItemSharedProps; renderCallout: JSX.Element; exists: boolean; + showExistenceFetchError?: boolean; } export const InnerFieldsAccordion = function InnerFieldsAccordion({ @@ -58,6 +61,7 @@ export const InnerFieldsAccordion = function InnerFieldsAccordion({ fieldProps, renderCallout, exists, + showExistenceFetchError, }: FieldsAccordionProps) { const renderField = useCallback( (field: IndexPatternField) => { @@ -78,7 +82,18 @@ export const InnerFieldsAccordion = function InnerFieldsAccordion({ } extraAction={ - hasLoaded ? ( + showExistenceFetchError ? ( + + ) : hasLoaded ? ( {fieldsCount} diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/loader.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/loader.test.ts index 5776691fbcc7f..27904a0f23f16 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/loader.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/loader.test.ts @@ -13,7 +13,7 @@ import { changeLayerIndexPattern, syncExistingFields, } from './loader'; -import { IndexPatternPersistedState, IndexPatternPrivateState } from './types'; +import { IndexPatternPersistedState, IndexPatternPrivateState, IndexPatternField } from './types'; import { documentField } from './document_field'; jest.mock('./operations'); @@ -642,7 +642,11 @@ describe('loader', () => { await syncExistingFields({ dateRange: { fromDate: '1900-01-01', toDate: '2000-01-01' }, fetchJson, - indexPatterns: [{ id: 'a' }, { id: 'b' }, { id: 'c' }], + indexPatterns: [ + { id: 'a', title: 'a', fields: [] }, + { id: 'b', title: 'a', fields: [] }, + { id: 'c', title: 'a', fields: [] }, + ], setState, dslQuery, showNoDataPopover: jest.fn(), @@ -662,6 +666,7 @@ describe('loader', () => { expect(newState).toEqual({ foo: 'bar', isFirstExistenceFetch: false, + existenceFetchFailed: false, existingFields: { a: { a_field_1: true, a_field_2: true }, b: { b_field_1: true, b_field_2: true }, @@ -687,7 +692,11 @@ describe('loader', () => { const args = { dateRange: { fromDate: '1900-01-01', toDate: '2000-01-01' }, fetchJson, - indexPatterns: [{ id: 'a' }, { id: 'b' }, { id: 'c' }], + indexPatterns: [ + { id: 'a', title: 'a', fields: [] }, + { id: 'b', title: 'a', fields: [] }, + { id: 'c', title: 'a', fields: [] }, + ], setState, dslQuery, showNoDataPopover: jest.fn(), @@ -702,5 +711,45 @@ describe('loader', () => { await syncExistingFields({ ...args, isFirstExistenceFetch: true }); expect(showNoDataPopover).not.toHaveBeenCalled(); }); + + it('should set all fields to available and existence error flag if the request fails', async () => { + const setState = jest.fn(); + const fetchJson = (jest.fn((path: string) => { + return new Promise((resolve, reject) => { + reject(new Error()); + }); + }) as unknown) as HttpHandler; + + const args = { + dateRange: { fromDate: '1900-01-01', toDate: '2000-01-01' }, + fetchJson, + indexPatterns: [ + { + id: 'a', + title: 'a', + fields: [{ name: 'field1' }, { name: 'field2' }] as IndexPatternField[], + }, + ], + setState, + dslQuery, + showNoDataPopover: jest.fn(), + currentIndexPatternTitle: 'abc', + isFirstExistenceFetch: false, + }; + + await syncExistingFields(args); + + const [fn] = setState.mock.calls[0]; + const newState = fn({ + foo: 'bar', + existingFields: {}, + }) as IndexPatternPrivateState; + + expect(newState.existenceFetchFailed).toEqual(true); + expect(newState.existingFields.a).toEqual({ + field1: true, + field2: true, + }); + }); }); }); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/loader.ts b/x-pack/plugins/lens/public/indexpattern_datasource/loader.ts index e995c7317b5d8..20e7bec6db131 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/loader.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/loader.ts @@ -246,7 +246,12 @@ export async function syncExistingFields({ showNoDataPopover, }: { dateRange: DateRange; - indexPatterns: Array<{ id: string; timeFieldName?: string | null }>; + indexPatterns: Array<{ + id: string; + title: string; + fields: IndexPatternField[]; + timeFieldName?: string | null; + }>; fetchJson: HttpSetup['post']; setState: SetState; isFirstExistenceFetch: boolean; @@ -254,41 +259,53 @@ export async function syncExistingFields({ dslQuery: object; showNoDataPopover: () => void; }) { - const emptinessInfo = await Promise.all( - indexPatterns.map((pattern) => { - const body: Record = { - dslQuery, - fromDate: dateRange.fromDate, - toDate: dateRange.toDate, - }; - - if (pattern.timeFieldName) { - body.timeFieldName = pattern.timeFieldName; - } + const existenceRequests = indexPatterns.map((pattern) => { + const body: Record = { + dslQuery, + fromDate: dateRange.fromDate, + toDate: dateRange.toDate, + }; - return fetchJson(`${BASE_API_URL}/existing_fields/${pattern.id}`, { - body: JSON.stringify(body), - }) as Promise; - }) - ); + if (pattern.timeFieldName) { + body.timeFieldName = pattern.timeFieldName; + } - if (isFirstExistenceFetch) { - const fieldsCurrentIndexPattern = emptinessInfo.find( - (info) => info.indexPatternTitle === currentIndexPatternTitle - ); - if (fieldsCurrentIndexPattern && fieldsCurrentIndexPattern.existingFieldNames.length === 0) { - showNoDataPopover(); + return fetchJson(`${BASE_API_URL}/existing_fields/${pattern.id}`, { + body: JSON.stringify(body), + }) as Promise; + }); + + try { + const emptinessInfo = await Promise.all(existenceRequests); + if (isFirstExistenceFetch) { + const fieldsCurrentIndexPattern = emptinessInfo.find( + (info) => info.indexPatternTitle === currentIndexPatternTitle + ); + if (fieldsCurrentIndexPattern && fieldsCurrentIndexPattern.existingFieldNames.length === 0) { + showNoDataPopover(); + } } - } - setState((state) => ({ - ...state, - isFirstExistenceFetch: false, - existingFields: emptinessInfo.reduce((acc, info) => { - acc[info.indexPatternTitle] = booleanMap(info.existingFieldNames); - return acc; - }, state.existingFields), - })); + setState((state) => ({ + ...state, + isFirstExistenceFetch: false, + existenceFetchFailed: false, + existingFields: emptinessInfo.reduce((acc, info) => { + acc[info.indexPatternTitle] = booleanMap(info.existingFieldNames); + return acc; + }, state.existingFields), + })); + } catch (e) { + // show all fields as available if fetch failed + setState((state) => ({ + ...state, + existenceFetchFailed: true, + existingFields: indexPatterns.reduce((acc, pattern) => { + acc[pattern.title] = booleanMap(pattern.fields.map((field) => field.name)); + return acc; + }, state.existingFields), + })); + } } function booleanMap(keys: string[]) { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/types.ts b/x-pack/plugins/lens/public/indexpattern_datasource/types.ts index b7beb67196add..2a9b3f452d991 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/types.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/types.ts @@ -52,6 +52,7 @@ export type IndexPatternPrivateState = IndexPatternPersistedState & { */ existingFields: Record>; isFirstExistenceFetch: boolean; + existenceFetchFailed?: boolean; }; export interface IndexPatternRef {