Skip to content

Commit 907e43b

Browse files
authored
[Lens] Handle failing existence check (#70718) (#72009)
1 parent 65c5dc8 commit 907e43b

File tree

5 files changed

+170
-75
lines changed

5 files changed

+170
-75
lines changed

x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx

Lines changed: 52 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ export function IndexPatternDataPanel({
125125
id,
126126
title: indexPatterns[id].title,
127127
timeFieldName: indexPatterns[id].timeFieldName,
128+
fields: indexPatterns[id].fields,
128129
}));
129130

130131
const dslQuery = buildSafeEsQuery(
@@ -197,6 +198,7 @@ export function IndexPatternDataPanel({
197198
charts={charts}
198199
onChangeIndexPattern={onChangeIndexPattern}
199200
existingFields={state.existingFields}
201+
existenceFetchFailed={state.existenceFetchFailed}
200202
/>
201203
)}
202204
</>
@@ -231,6 +233,7 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({
231233
currentIndexPatternId,
232234
indexPatternRefs,
233235
indexPatterns,
236+
existenceFetchFailed,
234237
query,
235238
dateRange,
236239
filters,
@@ -249,6 +252,7 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({
249252
onChangeIndexPattern: (newId: string) => void;
250253
existingFields: IndexPatternPrivateState['existingFields'];
251254
charts: ChartsPluginSetup;
255+
existenceFetchFailed?: boolean;
252256
}) {
253257
const [localState, setLocalState] = useState<DataPanelState>({
254258
nameFilter: '',
@@ -553,9 +557,15 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({
553557
<FieldsAccordion
554558
initialIsOpen={localState.isAvailableAccordionOpen}
555559
id="lnsIndexPatternAvailableFields"
556-
label={i18n.translate('xpack.lens.indexPattern.availableFieldsLabel', {
557-
defaultMessage: 'Available fields',
558-
})}
560+
label={
561+
existenceFetchFailed
562+
? i18n.translate('xpack.lens.indexPattern.allFieldsLabel', {
563+
defaultMessage: 'All fields',
564+
})
565+
: i18n.translate('xpack.lens.indexPattern.availableFieldsLabel', {
566+
defaultMessage: 'Available fields',
567+
})
568+
}
559569
exists={true}
560570
hasLoaded={!!hasSyncedExistingFields}
561571
fieldsCount={filteredFieldGroups.availableFields.length}
@@ -576,6 +586,7 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({
576586
Math.max(PAGINATION_SIZE, Math.min(pageSize * 1.5, displayedFieldLength))
577587
);
578588
}}
589+
showExistenceFetchError={existenceFetchFailed}
579590
renderCallout={
580591
<NoFieldsCallout
581592
isAffectedByGlobalFilter={!!filters.length}
@@ -588,42 +599,44 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({
588599
}
589600
/>
590601
<EuiSpacer size="m" />
591-
<FieldsAccordion
592-
initialIsOpen={localState.isEmptyAccordionOpen}
593-
isFiltered={
594-
filteredFieldGroups.emptyFields.length !== fieldGroups.emptyFields.length
595-
}
596-
fieldsCount={filteredFieldGroups.emptyFields.length}
597-
paginatedFields={paginatedEmptyFields}
598-
hasLoaded={!!hasSyncedExistingFields}
599-
exists={false}
600-
fieldProps={fieldProps}
601-
id="lnsIndexPatternEmptyFields"
602-
label={i18n.translate('xpack.lens.indexPattern.emptyFieldsLabel', {
603-
defaultMessage: 'Empty fields',
604-
})}
605-
onToggle={(open) => {
606-
setLocalState((s) => ({
607-
...s,
608-
isEmptyAccordionOpen: open,
609-
}));
610-
const displayedFieldLength =
611-
(localState.isAvailableAccordionOpen
612-
? filteredFieldGroups.availableFields.length
613-
: 0) + (open ? filteredFieldGroups.emptyFields.length : 0);
614-
setPageSize(
615-
Math.max(PAGINATION_SIZE, Math.min(pageSize * 1.5, displayedFieldLength))
616-
);
617-
}}
618-
renderCallout={
619-
<NoFieldsCallout
620-
isAffectedByFieldFilter={
621-
!!(localState.typeFilter.length || localState.nameFilter.length)
622-
}
623-
existFieldsInIndex={!!fieldGroups.emptyFields.length}
624-
/>
625-
}
626-
/>
602+
{!existenceFetchFailed && (
603+
<FieldsAccordion
604+
initialIsOpen={localState.isEmptyAccordionOpen}
605+
isFiltered={
606+
filteredFieldGroups.emptyFields.length !== fieldGroups.emptyFields.length
607+
}
608+
fieldsCount={filteredFieldGroups.emptyFields.length}
609+
paginatedFields={paginatedEmptyFields}
610+
hasLoaded={!!hasSyncedExistingFields}
611+
exists={false}
612+
fieldProps={fieldProps}
613+
id="lnsIndexPatternEmptyFields"
614+
label={i18n.translate('xpack.lens.indexPattern.emptyFieldsLabel', {
615+
defaultMessage: 'Empty fields',
616+
})}
617+
onToggle={(open) => {
618+
setLocalState((s) => ({
619+
...s,
620+
isEmptyAccordionOpen: open,
621+
}));
622+
const displayedFieldLength =
623+
(localState.isAvailableAccordionOpen
624+
? filteredFieldGroups.availableFields.length
625+
: 0) + (open ? filteredFieldGroups.emptyFields.length : 0);
626+
setPageSize(
627+
Math.max(PAGINATION_SIZE, Math.min(pageSize * 1.5, displayedFieldLength))
628+
);
629+
}}
630+
renderCallout={
631+
<NoFieldsCallout
632+
isAffectedByFieldFilter={
633+
!!(localState.typeFilter.length || localState.nameFilter.length)
634+
}
635+
existFieldsInIndex={!!fieldGroups.emptyFields.length}
636+
/>
637+
}
638+
/>
639+
)}
627640
<EuiSpacer size="m" />
628641
</div>
629642
</div>

x-pack/plugins/lens/public/indexpattern_datasource/fields_accordion.tsx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@
66

77
import './datapanel.scss';
88
import React, { memo, useCallback } from 'react';
9+
import { i18n } from '@kbn/i18n';
910
import {
1011
EuiText,
1112
EuiNotificationBadge,
1213
EuiSpacer,
1314
EuiAccordion,
1415
EuiLoadingSpinner,
16+
EuiIconTip,
1517
} from '@elastic/eui';
1618
import { DataPublicPluginStart } from 'src/plugins/data/public';
1719
import { IndexPatternField } from './types';
@@ -44,6 +46,7 @@ export interface FieldsAccordionProps {
4446
fieldProps: FieldItemSharedProps;
4547
renderCallout: JSX.Element;
4648
exists: boolean;
49+
showExistenceFetchError?: boolean;
4750
}
4851

4952
export const InnerFieldsAccordion = function InnerFieldsAccordion({
@@ -58,6 +61,7 @@ export const InnerFieldsAccordion = function InnerFieldsAccordion({
5861
fieldProps,
5962
renderCallout,
6063
exists,
64+
showExistenceFetchError,
6165
}: FieldsAccordionProps) {
6266
const renderField = useCallback(
6367
(field: IndexPatternField) => {
@@ -78,7 +82,18 @@ export const InnerFieldsAccordion = function InnerFieldsAccordion({
7882
</EuiText>
7983
}
8084
extraAction={
81-
hasLoaded ? (
85+
showExistenceFetchError ? (
86+
<EuiIconTip
87+
aria-label={i18n.translate('xpack.lens.indexPattern.existenceErrorAriaLabel', {
88+
defaultMessage: 'Existence fetch failed',
89+
})}
90+
type="alert"
91+
color="warning"
92+
content={i18n.translate('xpack.lens.indexPattern.existenceErrorLabel', {
93+
defaultMessage: "Field information can't be loaded",
94+
})}
95+
/>
96+
) : hasLoaded ? (
8297
<EuiNotificationBadge size="m" color={isFiltered ? 'accent' : 'subdued'}>
8398
{fieldsCount}
8499
</EuiNotificationBadge>

x-pack/plugins/lens/public/indexpattern_datasource/loader.test.ts

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
changeLayerIndexPattern,
1414
syncExistingFields,
1515
} from './loader';
16-
import { IndexPatternPersistedState, IndexPatternPrivateState } from './types';
16+
import { IndexPatternPersistedState, IndexPatternPrivateState, IndexPatternField } from './types';
1717
import { documentField } from './document_field';
1818

1919
jest.mock('./operations');
@@ -642,7 +642,11 @@ describe('loader', () => {
642642
await syncExistingFields({
643643
dateRange: { fromDate: '1900-01-01', toDate: '2000-01-01' },
644644
fetchJson,
645-
indexPatterns: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
645+
indexPatterns: [
646+
{ id: 'a', title: 'a', fields: [] },
647+
{ id: 'b', title: 'a', fields: [] },
648+
{ id: 'c', title: 'a', fields: [] },
649+
],
646650
setState,
647651
dslQuery,
648652
showNoDataPopover: jest.fn(),
@@ -662,6 +666,7 @@ describe('loader', () => {
662666
expect(newState).toEqual({
663667
foo: 'bar',
664668
isFirstExistenceFetch: false,
669+
existenceFetchFailed: false,
665670
existingFields: {
666671
a: { a_field_1: true, a_field_2: true },
667672
b: { b_field_1: true, b_field_2: true },
@@ -687,7 +692,11 @@ describe('loader', () => {
687692
const args = {
688693
dateRange: { fromDate: '1900-01-01', toDate: '2000-01-01' },
689694
fetchJson,
690-
indexPatterns: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
695+
indexPatterns: [
696+
{ id: 'a', title: 'a', fields: [] },
697+
{ id: 'b', title: 'a', fields: [] },
698+
{ id: 'c', title: 'a', fields: [] },
699+
],
691700
setState,
692701
dslQuery,
693702
showNoDataPopover: jest.fn(),
@@ -702,5 +711,45 @@ describe('loader', () => {
702711
await syncExistingFields({ ...args, isFirstExistenceFetch: true });
703712
expect(showNoDataPopover).not.toHaveBeenCalled();
704713
});
714+
715+
it('should set all fields to available and existence error flag if the request fails', async () => {
716+
const setState = jest.fn();
717+
const fetchJson = (jest.fn((path: string) => {
718+
return new Promise((resolve, reject) => {
719+
reject(new Error());
720+
});
721+
}) as unknown) as HttpHandler;
722+
723+
const args = {
724+
dateRange: { fromDate: '1900-01-01', toDate: '2000-01-01' },
725+
fetchJson,
726+
indexPatterns: [
727+
{
728+
id: 'a',
729+
title: 'a',
730+
fields: [{ name: 'field1' }, { name: 'field2' }] as IndexPatternField[],
731+
},
732+
],
733+
setState,
734+
dslQuery,
735+
showNoDataPopover: jest.fn(),
736+
currentIndexPatternTitle: 'abc',
737+
isFirstExistenceFetch: false,
738+
};
739+
740+
await syncExistingFields(args);
741+
742+
const [fn] = setState.mock.calls[0];
743+
const newState = fn({
744+
foo: 'bar',
745+
existingFields: {},
746+
}) as IndexPatternPrivateState;
747+
748+
expect(newState.existenceFetchFailed).toEqual(true);
749+
expect(newState.existingFields.a).toEqual({
750+
field1: true,
751+
field2: true,
752+
});
753+
});
705754
});
706755
});

x-pack/plugins/lens/public/indexpattern_datasource/loader.ts

Lines changed: 49 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -246,49 +246,66 @@ export async function syncExistingFields({
246246
showNoDataPopover,
247247
}: {
248248
dateRange: DateRange;
249-
indexPatterns: Array<{ id: string; timeFieldName?: string | null }>;
249+
indexPatterns: Array<{
250+
id: string;
251+
title: string;
252+
fields: IndexPatternField[];
253+
timeFieldName?: string | null;
254+
}>;
250255
fetchJson: HttpSetup['post'];
251256
setState: SetState;
252257
isFirstExistenceFetch: boolean;
253258
currentIndexPatternTitle: string;
254259
dslQuery: object;
255260
showNoDataPopover: () => void;
256261
}) {
257-
const emptinessInfo = await Promise.all(
258-
indexPatterns.map((pattern) => {
259-
const body: Record<string, string | object> = {
260-
dslQuery,
261-
fromDate: dateRange.fromDate,
262-
toDate: dateRange.toDate,
263-
};
264-
265-
if (pattern.timeFieldName) {
266-
body.timeFieldName = pattern.timeFieldName;
267-
}
262+
const existenceRequests = indexPatterns.map((pattern) => {
263+
const body: Record<string, string | object> = {
264+
dslQuery,
265+
fromDate: dateRange.fromDate,
266+
toDate: dateRange.toDate,
267+
};
268268

269-
return fetchJson(`${BASE_API_URL}/existing_fields/${pattern.id}`, {
270-
body: JSON.stringify(body),
271-
}) as Promise<ExistingFields>;
272-
})
273-
);
269+
if (pattern.timeFieldName) {
270+
body.timeFieldName = pattern.timeFieldName;
271+
}
274272

275-
if (isFirstExistenceFetch) {
276-
const fieldsCurrentIndexPattern = emptinessInfo.find(
277-
(info) => info.indexPatternTitle === currentIndexPatternTitle
278-
);
279-
if (fieldsCurrentIndexPattern && fieldsCurrentIndexPattern.existingFieldNames.length === 0) {
280-
showNoDataPopover();
273+
return fetchJson(`${BASE_API_URL}/existing_fields/${pattern.id}`, {
274+
body: JSON.stringify(body),
275+
}) as Promise<ExistingFields>;
276+
});
277+
278+
try {
279+
const emptinessInfo = await Promise.all(existenceRequests);
280+
if (isFirstExistenceFetch) {
281+
const fieldsCurrentIndexPattern = emptinessInfo.find(
282+
(info) => info.indexPatternTitle === currentIndexPatternTitle
283+
);
284+
if (fieldsCurrentIndexPattern && fieldsCurrentIndexPattern.existingFieldNames.length === 0) {
285+
showNoDataPopover();
286+
}
281287
}
282-
}
283288

284-
setState((state) => ({
285-
...state,
286-
isFirstExistenceFetch: false,
287-
existingFields: emptinessInfo.reduce((acc, info) => {
288-
acc[info.indexPatternTitle] = booleanMap(info.existingFieldNames);
289-
return acc;
290-
}, state.existingFields),
291-
}));
289+
setState((state) => ({
290+
...state,
291+
isFirstExistenceFetch: false,
292+
existenceFetchFailed: false,
293+
existingFields: emptinessInfo.reduce((acc, info) => {
294+
acc[info.indexPatternTitle] = booleanMap(info.existingFieldNames);
295+
return acc;
296+
}, state.existingFields),
297+
}));
298+
} catch (e) {
299+
// show all fields as available if fetch failed
300+
setState((state) => ({
301+
...state,
302+
existenceFetchFailed: true,
303+
existingFields: indexPatterns.reduce((acc, pattern) => {
304+
acc[pattern.title] = booleanMap(pattern.fields.map((field) => field.name));
305+
return acc;
306+
}, state.existingFields),
307+
}));
308+
}
292309
}
293310

294311
function booleanMap(keys: string[]) {

x-pack/plugins/lens/public/indexpattern_datasource/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ export type IndexPatternPrivateState = IndexPatternPersistedState & {
5252
*/
5353
existingFields: Record<string, Record<string, boolean>>;
5454
isFirstExistenceFetch: boolean;
55+
existenceFetchFailed?: boolean;
5556
};
5657

5758
export interface IndexPatternRef {

0 commit comments

Comments
 (0)