From 57d814db0b0edee7acc2dedc243cf777b17cdff8 Mon Sep 17 00:00:00 2001 From: dej611 Date: Wed, 27 Jul 2022 17:34:25 +0200 Subject: [PATCH 01/27] Move lens dataViews state into main state --- x-pack/plugins/lens/public/app_plugin/app.tsx | 29 +- .../lens/public/app_plugin/lens_top_nav.tsx | 117 ++--- .../public/app_plugin/show_underlying_data.ts | 4 +- .../plugins/lens/public/app_plugin/types.ts | 2 + .../lens/public/data_views_service/loader.ts | 309 ++++++++++++ .../lens/public/data_views_service/service.ts | 117 +++++ .../buttons/draggable_dimension_button.tsx | 4 + .../buttons/empty_dimension_button.tsx | 4 + .../config_panel/config_panel.tsx | 36 +- .../editor_frame/config_panel/layer_panel.tsx | 123 +++-- .../editor_frame/config_panel/types.ts | 2 + .../editor_frame/data_panel_wrapper.tsx | 63 ++- .../editor_frame/editor_frame.tsx | 8 +- .../editor_frame/expression_helpers.ts | 12 +- .../editor_frame/state_helpers.ts | 264 ++++++++-- .../editor_frame/suggestion_helpers.ts | 20 +- .../editor_frame/suggestion_panel.tsx | 9 +- .../workspace_panel/chart_switch.tsx | 1 + .../workspace_panel/workspace_panel.tsx | 6 +- .../public/editor_frame_service/service.tsx | 30 +- .../lens/public/editor_frame_service/types.ts | 2 +- .../lens/public/embeddable/embeddable.tsx | 4 +- .../public/embeddable/embeddable_factory.ts | 6 +- .../indexpattern_datasource/datapanel.tsx | 131 +++-- .../bucket_nesting_editor.test.tsx | 2 +- .../dimension_panel/dimension_editor.tsx | 22 +- .../dimension_panel/dimension_panel.tsx | 5 +- .../droppable/get_drop_props.ts | 27 +- .../droppable/on_drop_handler.ts | 33 +- .../dimension_panel/field_select.tsx | 13 +- .../dimension_panel/operation_support.ts | 6 +- .../indexpattern_datasource/field_item.tsx | 4 +- .../indexpattern_datasource/field_list.tsx | 167 +++---- .../fields_accordion.tsx | 4 +- .../public/indexpattern_datasource/index.ts | 2 +- .../indexpattern_datasource/indexpattern.tsx | 156 +++--- .../indexpattern_suggestions.ts | 111 +++-- .../layerpanel.test.tsx | 2 +- .../indexpattern_datasource/layerpanel.tsx | 13 +- .../public/indexpattern_datasource/loader.ts | 464 ++++-------------- .../definitions/formula/formula.test.tsx | 3 +- .../definitions/ranges/ranges.test.tsx | 5 +- .../definitions/shared_components/index.tsx | 1 - .../definitions/terms/field_inputs.tsx | 11 +- .../definitions/terms/terms.test.tsx | 3 +- .../operations/layer_helpers.test.ts | 3 +- .../operations/layer_helpers.ts | 4 +- .../operations/operations.ts | 3 +- .../indexpattern_datasource/pure_helpers.ts | 10 +- .../indexpattern_datasource/query_input.tsx | 7 +- .../time_shift_utils.tsx | 9 +- .../indexpattern_datasource/to_expression.ts | 6 +- .../public/indexpattern_datasource/types.ts | 48 +- .../public/indexpattern_datasource/utils.tsx | 8 +- x-pack/plugins/lens/public/plugin.ts | 10 +- .../dataview_picker/dataview_picker.tsx | 93 ++++ .../dataview_picker/helpers.ts | 29 ++ .../dataview_picker/index.ts | 9 + .../drag_drop_bucket/buckets.test.tsx | 79 +++ .../drag_drop_bucket/buckets.tsx | 160 ++++++ .../field_picker/field_picker.tsx | 2 +- .../lens/public/shared_components/index.ts | 6 + .../context_middleware/index.ts | 7 +- .../lens/public/state_management/index.ts | 1 + .../init_middleware/load_initial.ts | 95 +++- .../public/state_management/lens_slice.ts | 99 +++- .../lens/public/state_management/selectors.ts | 5 +- .../lens/public/state_management/types.ts | 17 +- x-pack/plugins/lens/public/types.ts | 141 +++++- x-pack/plugins/lens/public/utils.ts | 93 ++-- .../lens/public/xy_visualization/index.ts | 8 +- .../lens/public/xy_visualization/types.ts | 2 + .../public/xy_visualization/visualization.tsx | 38 +- .../visualization_helpers.tsx | 13 +- 74 files changed, 2324 insertions(+), 1038 deletions(-) create mode 100644 x-pack/plugins/lens/public/data_views_service/loader.ts create mode 100644 x-pack/plugins/lens/public/data_views_service/service.ts create mode 100644 x-pack/plugins/lens/public/shared_components/dataview_picker/dataview_picker.tsx create mode 100644 x-pack/plugins/lens/public/shared_components/dataview_picker/helpers.ts create mode 100644 x-pack/plugins/lens/public/shared_components/dataview_picker/index.ts create mode 100644 x-pack/plugins/lens/public/shared_components/drag_drop_bucket/buckets.test.tsx create mode 100644 x-pack/plugins/lens/public/shared_components/drag_drop_bucket/buckets.tsx diff --git a/x-pack/plugins/lens/public/app_plugin/app.tsx b/x-pack/plugins/lens/public/app_plugin/app.tsx index 4f5027c908b09..93d14d4306fb0 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.tsx @@ -25,11 +25,13 @@ import { LensAppState, DispatchSetState, selectSavedObjectFormat, + updateIndexPatterns, } from '../state_management'; import { SaveModalContainer, runSaveLensVisualization } from './save_modal_container'; import { LensInspector } from '../lens_inspector_service'; import { getEditPath } from '../../common'; import { isLensEqual } from './lens_document_equality'; +import { IndexPatternServiceAPI, createIndexPatternService } from '../data_views_service/service'; export type SaveProps = Omit & { returnToOrigin: boolean; @@ -66,6 +68,7 @@ export function App({ getOriginatingAppName, spaces, http, + notifications, executionContext, // Temporarily required until the 'by value' paradigm is default. dashboardFeatureFlag, @@ -360,6 +363,22 @@ export function App({ ); }, [initialContext]); + const indexPatternService = useMemo( + () => + createIndexPatternService({ + dataViews: lensAppServices.dataViews, + uiSettings: lensAppServices.uiSettings, + core: { http, notifications }, + updateIndexPatterns: (newIndexPatternsState, options) => { + dispatch(updateIndexPatterns(newIndexPatternsState)); + if (options?.applyImmediately) { + dispatch(applyChanges()); + } + }, + }), + [dispatch, http, notifications, lensAppServices] + ); + return ( <>
@@ -381,6 +400,7 @@ export function App({ topNavMenuEntryGenerators={topNavMenuEntryGenerators} initialContext={initialContext} theme$={theme$} + indexPatternService={indexPatternService} /> {getLegacyUrlConflictCallout()} {(!isLoading || persistedDoc) && ( @@ -388,6 +408,7 @@ export function App({ editorFrame={editorFrame} showNoDataPopover={showNoDataPopover} lensInspector={lensInspector} + indexPatternService={indexPatternService} /> )}
@@ -449,13 +470,19 @@ const MemoizedEditorFrameWrapper = React.memo(function EditorFrameWrapper({ editorFrame, showNoDataPopover, lensInspector, + indexPatternService, }: { editorFrame: EditorFrameInstance; lensInspector: LensInspector; showNoDataPopover: () => void; + indexPatternService: IndexPatternServiceAPI; }) { const { EditorFrameContainer } = editorFrame; return ( - + ); }); diff --git a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx index 5dd2368d2b83a..98acf40dedb74 100644 --- a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx +++ b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx @@ -15,7 +15,6 @@ import { tableHasFormulas } from '@kbn/data-plugin/common'; import { exporters, getEsQueryConfig } from '@kbn/data-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/public'; import { useKibana } from '@kbn/kibana-react-plugin/public'; -import type { StateSetter } from '../types'; import { LensAppServices, LensTopNavActions, @@ -29,16 +28,15 @@ import { useLensDispatch, LensAppState, DispatchSetState, - updateDatasourceState, } from '../state_management'; import { getIndexPatternsObjects, getIndexPatternsIds, getResolvedDateRange, - handleIndexPatternChange, refreshIndexPatternsList, } from '../utils'; import { combineQueryAndFilters, getLayerMetaInfo } from './show_underlying_data'; +import { changeIndexPattern } from '../state_management/lens_slice'; function getLensTopNavConfig(options: { showSaveAndReturn: boolean; @@ -219,6 +217,7 @@ export const LensTopNavMenu = ({ topNavMenuEntryGenerators, initialContext, theme$, + indexPatternService, }: LensTopNavMenuProps) => { const { data, @@ -231,14 +230,50 @@ export const LensTopNavMenu = ({ dashboardFeatureFlag, dataViewFieldEditor, dataViewEditor, - dataViews, + dataViews: dataViewsService, } = useKibana().services; + const { + isSaveable, + isLinkedToOriginatingApp, + query, + activeData, + savedQuery, + activeDatasourceId, + datasourceStates, + visualization, + filters, + dataViews, + } = useLensSelector((state) => state.lens); + const dispatch = useLensDispatch(); const dispatchSetState: DispatchSetState = React.useCallback( (state: Partial) => dispatch(setState(state)), [dispatch] ); + const dispatchChangeIndexPattern = React.useCallback( + async (indexPatternId) => { + const newIndexPatterns = await indexPatternService.ensureIndexPattern({ + id: indexPatternId, + cache: dataViews.indexPatterns, + }); + dispatch( + changeIndexPattern({ + dataViews: { indexPatterns: newIndexPatterns }, + datasourceIds: Object.keys(datasourceStates), + visualizationIds: visualization.activeId ? [visualization.activeId] : [], + indexPatternId, + }) + ); + }, + [ + dataViews.indexPatterns, + datasourceStates, + dispatch, + indexPatternService, + visualization.activeId, + ] + ); const [indexPatterns, setIndexPatterns] = useState([]); const [currentIndexPattern, setCurrentIndexPattern] = useState(); @@ -247,18 +282,6 @@ export const LensTopNavMenu = ({ const closeFieldEditor = useRef<() => void | undefined>(); const closeDataViewEditor = useRef<() => void | undefined>(); - const { - isSaveable, - isLinkedToOriginatingApp, - query, - activeData, - savedQuery, - activeDatasourceId, - datasourceStates, - visualization, - filters, - } = useLensSelector((state) => state.lens); - const allLoaded = Object.values(datasourceStates).every(({ isLoading }) => isLoading === false); useEffect(() => { @@ -290,7 +313,7 @@ export const LensTopNavMenu = ({ // Update the cached index patterns if the user made a change to any of them if (hasIndexPatternsChanged) { - getIndexPatternsObjects(indexPatternIds, dataViews).then( + getIndexPatternsObjects(indexPatternIds, dataViewsService).then( ({ indexPatterns: indexPatternObjects, rejectedIds }) => { setIndexPatterns(indexPatternObjects); setRejectedIndexPatterns(rejectedIds); @@ -303,7 +326,7 @@ export const LensTopNavMenu = ({ rejectedIndexPatterns, datasourceMap, indexPatterns, - dataViews, + dataViewsService, ]); useEffect(() => { @@ -366,6 +389,7 @@ export const LensTopNavMenu = ({ datasourceMap[activeDatasourceId], datasourceStates[activeDatasourceId].state, activeData, + dataViews.indexPatterns, data.query.timefilter.timefilter.getTime(), application.capabilities ); @@ -375,6 +399,7 @@ export const LensTopNavMenu = ({ datasourceMap, datasourceStates, activeData, + dataViews.indexPatterns, data.query.timefilter.timefilter, application.capabilities, ]); @@ -604,18 +629,6 @@ export const LensTopNavMenu = ({ }); }, [data.query.filterManager, data.query.queryString, dispatchSetState]); - const setDatasourceState: StateSetter = useMemo(() => { - return (updater) => { - dispatch( - updateDatasourceState({ - updater, - datasourceId: activeDatasourceId!, - clearStagedPreview: true, - }) - ); - }; - }, [activeDatasourceId, dispatch]); - const refreshFieldList = useCallback(async () => { if (currentIndexPattern && currentIndexPattern.id) { refreshIndexPatternsList({ @@ -627,7 +640,8 @@ export const LensTopNavMenu = ({ {} ), indexPatternId: currentIndexPattern.id, - setDatasourceState, + indexPatternService, + indexPatternsCache: dataViews.indexPatterns, }); } // start a new session so all charts are refreshed @@ -637,7 +651,8 @@ export const LensTopNavMenu = ({ data.search.session, datasourceMap, datasourceStates, - setDatasourceState, + indexPatternService, + dataViews.indexPatterns, ]); const editField = useMemo( @@ -679,32 +694,14 @@ export const LensTopNavMenu = ({ closeDataViewEditor.current = dataViewEditor.openEditor({ onSave: async (dataView) => { if (dataView.id) { - handleIndexPatternChange({ - activeDatasources: Object.keys(datasourceStates).reduce( - (acc, datasourceId) => ({ - ...acc, - [datasourceId]: datasourceMap[datasourceId], - }), - {} - ), - datasourceStates, - indexPatternId: dataView.id, - setDatasourceState, - }); + await dispatchChangeIndexPattern(dataView.id); refreshFieldList(); } }, }); } : undefined, - [ - dataViewEditor, - canEditDataView, - datasourceMap, - datasourceStates, - refreshFieldList, - setDatasourceState, - ] + [canEditDataView, dataViewEditor, dispatchChangeIndexPattern, refreshFieldList] ); const dataViewPickerProps = { @@ -721,18 +718,7 @@ export const LensTopNavMenu = ({ (indexPattern) => indexPattern.id === newIndexPatternId ); setCurrentIndexPattern(currentDataView); - handleIndexPatternChange({ - activeDatasources: Object.keys(datasourceStates).reduce( - (acc, datasourceId) => ({ - ...acc, - [datasourceId]: datasourceMap[datasourceId], - }), - {} - ), - datasourceStates, - indexPatternId: newIndexPatternId, - setDatasourceState, - }); + dispatchChangeIndexPattern(newIndexPatternId); }, }; @@ -759,7 +745,8 @@ export const LensTopNavMenu = ({ allLoaded && activeDatasourceId && datasourceMap[activeDatasourceId].isTimeBased( - datasourceStates[activeDatasourceId].state + datasourceStates[activeDatasourceId].state, + dataViews.indexPatterns ) ) } diff --git a/x-pack/plugins/lens/public/app_plugin/show_underlying_data.ts b/x-pack/plugins/lens/public/app_plugin/show_underlying_data.ts index 310e91d3caaad..39d7cda3677c0 100644 --- a/x-pack/plugins/lens/public/app_plugin/show_underlying_data.ts +++ b/x-pack/plugins/lens/public/app_plugin/show_underlying_data.ts @@ -20,7 +20,7 @@ import { RecursiveReadonly } from '@kbn/utility-types'; import { Capabilities } from '@kbn/core/public'; import { partition } from 'lodash'; import { TableInspectorAdapter } from '../editor_frame_service/types'; -import { Datasource } from '../types'; +import { Datasource, IndexPatternMap } from '../types'; /** * Joins a series of queries. @@ -61,6 +61,7 @@ export function getLayerMetaInfo( currentDatasource: Datasource | undefined, datasourceState: unknown, activeData: TableInspectorAdapter | undefined, + indexPatterns: IndexPatternMap, timeRange: TimeRange | undefined, capabilities: RecursiveReadonly<{ navLinks: Capabilities['navLinks']; @@ -93,6 +94,7 @@ export function getLayerMetaInfo( const datasourceAPI = currentDatasource.getPublicAPI({ layerId: firstLayerId, state: datasourceState, + indexPatterns, }); // maybe add also datasourceId validation here? if (datasourceAPI.datasourceId !== 'indexpattern') { diff --git a/x-pack/plugins/lens/public/app_plugin/types.ts b/x-pack/plugins/lens/public/app_plugin/types.ts index abb6cfa6a06a6..fe4df5f26593d 100644 --- a/x-pack/plugins/lens/public/app_plugin/types.ts +++ b/x-pack/plugins/lens/public/app_plugin/types.ts @@ -47,6 +47,7 @@ import type { import type { LensAttributeService } from '../lens_attribute_service'; import type { LensEmbeddableInput } from '../embeddable/embeddable'; import type { LensInspector } from '../lens_inspector_service'; +import { IndexPatternServiceAPI } from '../data_views_service/service'; export interface RedirectToOriginProps { input?: LensEmbeddableInput; @@ -107,6 +108,7 @@ export interface LensTopNavMenuProps { topNavMenuEntryGenerators: LensTopNavMenuEntryGenerator[]; initialContext?: VisualizeFieldContext | VisualizeEditorContext; theme$: Observable; + indexPatternService: IndexPatternServiceAPI; } export interface HistoryLocationState { diff --git a/x-pack/plugins/lens/public/data_views_service/loader.ts b/x-pack/plugins/lens/public/data_views_service/loader.ts new file mode 100644 index 0000000000000..89d1765bc61af --- /dev/null +++ b/x-pack/plugins/lens/public/data_views_service/loader.ts @@ -0,0 +1,309 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { isNestedField } from '@kbn/data-views-plugin/common'; +import type { DataViewsContract, DataView } from '@kbn/data-views-plugin/public'; +import { keyBy } from 'lodash'; +import { HttpSetup } from '@kbn/core/public'; +import { IndexPattern, IndexPatternField, IndexPatternMap, IndexPatternRef } from '../types'; +import { documentField } from '../indexpattern_datasource/document_field'; +import { BASE_API_URL, DateRange, ExistingFields } from '../../common'; +import { DataViewsState } from '../state_management'; + +type ErrorHandler = (err: Error) => void; + +/** + * All these functions will be used by the Embeddable instance too, + * therefore keep all these functions pretty raw here and do not use the IndexPatternService + */ + +export function getFieldByNameFactory(newFields: IndexPatternField[]) { + const fieldsLookup = keyBy(newFields, 'name'); + return (name: string) => fieldsLookup[name]; +} + +export function convertDataViewIntoLensIndexPattern( + dataView: DataView, + restrictionRemapper: (name: string) => string +): IndexPattern { + const newFields = dataView.fields + .filter((field) => !isNestedField(field) && (!!field.aggregatable || !!field.scripted)) + .map((field): IndexPatternField => { + // Convert the getters on the index pattern service into plain JSON + const base = { + name: field.name, + displayName: field.displayName, + type: field.type, + aggregatable: field.aggregatable, + searchable: field.searchable, + meta: dataView.metaFields.includes(field.name), + esTypes: field.esTypes, + scripted: field.scripted, + runtime: Boolean(field.runtimeField), + }; + + // Simplifies tests by hiding optional properties instead of undefined + return base.scripted + ? { + ...base, + lang: field.lang, + script: field.script, + } + : base; + }) + .concat(documentField); + + const { typeMeta, title, name, timeFieldName, fieldFormatMap } = dataView; + if (typeMeta?.aggs) { + const aggs = Object.keys(typeMeta.aggs); + newFields.forEach((field, index) => { + const restrictionsObj: IndexPatternField['aggregationRestrictions'] = {}; + aggs.forEach((agg) => { + const restriction = typeMeta.aggs && typeMeta.aggs[agg] && typeMeta.aggs[agg][field.name]; + if (restriction) { + restrictionsObj[restrictionRemapper(agg)] = restriction; + } + }); + if (Object.keys(restrictionsObj).length) { + newFields[index] = { ...field, aggregationRestrictions: restrictionsObj }; + } + }); + } + + return { + id: dataView.id!, // id exists for sure because we got index patterns by id + title, + name: name ? name : title, + timeFieldName, + fieldFormatMap: + fieldFormatMap && + Object.fromEntries( + Object.entries(fieldFormatMap).map(([id, format]) => [ + id, + 'toJSON' in format ? format.toJSON() : format, + ]) + ), + fields: newFields, + getFieldByName: getFieldByNameFactory(newFields), + hasRestrictions: !!typeMeta?.aggs, + }; +} + +export async function loadIndexPatternRefs( + indexPatternsService: DataViewsContract +): Promise { + const indexPatterns = await indexPatternsService.getIdsWithTitle(); + + return indexPatterns.sort((a, b) => { + return a.title.localeCompare(b.title); + }); +} + +/** + * Map ES agg names with Lens ones + */ +const renameOperationsMapping: Record = { + avg: 'average', + cardinality: 'unique_count', +}; + +function onRestrictionMapping(agg: string): string { + return agg in renameOperationsMapping ? renameOperationsMapping[agg] : agg; +} + +export async function loadIndexPatterns({ + dataViews, + patterns, + notUsedPatterns, + cache, + onIndexPatternRefresh, +}: { + dataViews: DataViewsContract; + patterns: string[]; + notUsedPatterns?: string[]; + cache: Record; + onIndexPatternRefresh?: () => void; +}) { + const missingIds = patterns.filter((id) => !cache[id]); + + if (missingIds.length === 0) { + return cache; + } + + onIndexPatternRefresh?.(); + + const allIndexPatterns = await Promise.allSettled(missingIds.map((id) => dataViews.get(id))); + // ignore rejected indexpatterns here, they're already handled at the app level + let indexPatterns = allIndexPatterns + .filter( + (response): response is PromiseFulfilledResult => response.status === 'fulfilled' + ) + .map((response) => response.value); + + // if all of the used index patterns failed to load, try loading one of not used ones till one succeeds + if (!indexPatterns.length && notUsedPatterns) { + for (const notUsedPattern of notUsedPatterns) { + const resp = await dataViews.get(notUsedPattern).catch((e) => { + // do nothing + }); + if (resp) { + indexPatterns = [resp]; + } + } + } + + const indexPatternsObject = indexPatterns.reduce( + (acc, indexPattern) => ({ + [indexPattern.id!]: convertDataViewIntoLensIndexPattern(indexPattern, onRestrictionMapping), + ...acc, + }), + { ...cache } + ); + + return indexPatternsObject; +} + +export async function loadIndexPattern({ + id, + onError, + dataViews, + cache = {}, +}: { + id: string; + onError: ErrorHandler; + dataViews: DataViewsContract; + cache?: IndexPatternMap; +}) { + const indexPatterns = await loadIndexPatterns({ + dataViews, + cache, + patterns: [id], + }); + + if (indexPatterns[id] == null) { + onError(Error('Missing indexpatterns')); + return; + } + + const newIndexPatterns = { + ...cache, + [id]: indexPatterns[id], + }; + return newIndexPatterns; +} + +async function refreshExistingFields({ + dateRange, + fetchJson, + indexPatternList, + dslQuery, +}: { + dateRange: DateRange; + indexPatternList: IndexPattern[]; + fetchJson: HttpSetup['post']; + dslQuery: object; +}) { + try { + const emptinessInfo = await Promise.all( + indexPatternList.map((pattern) => { + if (pattern.hasRestrictions) { + return { + indexPatternTitle: pattern.title, + existingFieldNames: pattern.fields.map((field) => field.name), + }; + } + const body: Record = { + dslQuery, + fromDate: dateRange.fromDate, + toDate: dateRange.toDate, + }; + + if (pattern.timeFieldName) { + body.timeFieldName = pattern.timeFieldName; + } + + return fetchJson(`${BASE_API_URL}/existing_fields/${pattern.id}`, { + body: JSON.stringify(body), + }) as Promise; + }) + ); + return { result: emptinessInfo, status: 200 }; + } catch (e) { + return { result: undefined, status: e.res?.status as number }; + } +} + +type FieldsPropsFromDataViewsState = Pick< + DataViewsState, + 'existingFields' | 'isFirstExistenceFetch' | 'existenceFetchTimeout' | 'existenceFetchFailed' +>; +export async function syncExistingFields({ + updateIndexPatterns, + isFirstExistenceFetch, + currentIndexPatternTitle, + onNoData, + existingFields, + ...requestOptions +}: { + dateRange: DateRange; + indexPatternList: IndexPattern[]; + existingFields: Record>; + fetchJson: HttpSetup['post']; + updateIndexPatterns: ( + newFieldState: FieldsPropsFromDataViewsState, + options: { applyImmediately: boolean } + ) => void; + isFirstExistenceFetch: boolean; + currentIndexPatternTitle: string; + dslQuery: object; + onNoData?: () => void; +}) { + const { indexPatternList } = requestOptions; + const newExistingFields = { ...existingFields }; + + const { result, status } = await refreshExistingFields(requestOptions); + + if (result) { + if (isFirstExistenceFetch) { + const fieldsCurrentIndexPattern = result.find( + (info) => info.indexPatternTitle === currentIndexPatternTitle + ); + if (fieldsCurrentIndexPattern && fieldsCurrentIndexPattern.existingFieldNames.length === 0) { + onNoData?.(); + } + } + + for (const { indexPatternTitle, existingFieldNames } of result) { + newExistingFields[indexPatternTitle] = booleanMap(existingFieldNames); + } + } else { + for (const { title, fields } of indexPatternList) { + newExistingFields[title] = booleanMap(fields.map((field) => field.name)); + } + } + + updateIndexPatterns( + { + isFirstExistenceFetch: status !== 200, + existingFields: newExistingFields, + ...(result + ? {} + : { + existenceFetchFailed: status !== 418, + existenceFetchTimeout: status === 418, + }), + }, + { applyImmediately: true } + ); +} + +function booleanMap(keys: string[]) { + return keys.reduce((acc, key) => { + acc[key] = true; + return acc; + }, {} as Record); +} diff --git a/x-pack/plugins/lens/public/data_views_service/service.ts b/x-pack/plugins/lens/public/data_views_service/service.ts new file mode 100644 index 0000000000000..047013525266f --- /dev/null +++ b/x-pack/plugins/lens/public/data_views_service/service.ts @@ -0,0 +1,117 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { DataViewsContract } from '@kbn/data-views-plugin/public'; +import type { CoreStart, IUiSettingsClient } from '@kbn/core/public'; +import { i18n } from '@kbn/i18n'; +import type { DateRange } from '../../common'; +import type { IndexPattern, IndexPatternMap, IndexPatternRef } from '../types'; +import { + loadIndexPattern, + loadIndexPatternRefs, + loadIndexPatterns, + syncExistingFields, +} from './loader'; +import type { DataViewsState } from '../state_management'; + +interface IndexPatternServiceProps { + core: Pick; + dataViews: DataViewsContract; + uiSettings: IUiSettingsClient; + updateIndexPatterns: ( + newState: Partial, + options?: { applyImmediately: boolean } + ) => void; +} + +/** + * This service is only available for the full editor version + * and it encapsulate all the indexpattern methods and state + * in a single object. + * NOTE: this is not intended to be used with the Embeddable branch + */ +export interface IndexPatternServiceAPI { + /** + * Loads a list of indexPatterns from a list of id (patterns) + * leveraging existing cache. Eventually fallbacks to unused indexPatterns ( notUsedPatterns ) + * @returns IndexPatternMap + */ + loadIndexPatterns: (args: { + patterns: string[]; + notUsedPatterns?: string[]; + cache: IndexPatternMap; + onIndexPatternRefresh?: () => void; + }) => Promise; + /** + * Load indexPatternRefs with title and ids + */ + loadIndexPatternRefs: (options: { isFullEditor: boolean }) => Promise; + /** + * Ensure an indexPattern is loaded in the cache, usually used in conjuction with a indexPattern change action. + */ + ensureIndexPattern: (args: { + id: string; + cache: IndexPatternMap; + }) => Promise; + /** + * Loads the existingFields map given the current context + */ + refreshExistingFields: (args: { + dateRange: DateRange; + currentIndexPatternTitle: string; + dslQuery: object; + onNoData?: () => void; + existingFields: Record>; + indexPatternList: IndexPattern[]; + isFirstExistenceFetch: boolean; + }) => Promise; + /** + * Retrieves the default indexPattern from the uiSettings + */ + getDefaultIndex: () => string; + + /** + * Update the Lens state cache of indexPatterns + */ + updateIndexPatternsCache: ( + newState: Partial, + options?: { applyImmediately: boolean } + ) => void; +} + +export function createIndexPatternService({ + core, + dataViews, + uiSettings, + updateIndexPatterns, +}: IndexPatternServiceProps): IndexPatternServiceAPI { + const onChangeError = (err: Error) => + core.notifications.toasts.addError(err, { + title: i18n.translate('xpack.lens.indexPattern.dataViewLoadError', { + defaultMessage: 'Error loading data view', + }), + }); + return { + updateIndexPatternsCache: updateIndexPatterns, + loadIndexPatterns: (args) => { + return loadIndexPatterns({ + dataViews, + ...args, + }); + }, + ensureIndexPattern: (args) => loadIndexPattern({ onError: onChangeError, dataViews, ...args }), + refreshExistingFields: (args) => + syncExistingFields({ + updateIndexPatterns, + fetchJson: core.http.post, + ...args, + }), + loadIndexPatternRefs: async ({ isFullEditor }) => + isFullEditor ? loadIndexPatternRefs(dataViews) : [], + getDefaultIndex: () => uiSettings.get('defaultIndex'), + }; +} diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/buttons/draggable_dimension_button.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/buttons/draggable_dimension_button.tsx index 32aba270e846b..cb95c7e9e2c26 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/buttons/draggable_dimension_button.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/buttons/draggable_dimension_button.tsx @@ -13,6 +13,7 @@ import { isOperation, DropType, DatasourceLayers, + IndexPatternMap, } from '../../../../types'; import { getCustomDropTarget, @@ -37,6 +38,7 @@ export function DraggableDimensionButton({ layerDatasource, datasourceLayers, registerNewButtonRef, + indexPatterns, }: { layerId: string; groupIndex: number; @@ -53,6 +55,7 @@ export function DraggableDimensionButton({ accessorIndex: number; columnId: string; registerNewButtonRef: (id: string, instance: HTMLDivElement | null) => void; + indexPatterns: IndexPatternMap; }) { const { dragging } = useContext(DragContext); @@ -73,6 +76,7 @@ export function DraggableDimensionButton({ filterOperations: group.filterOperations, prioritizedOperation: group.prioritizedOperation, }, + indexPatterns, }, sharedDatasource ); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/buttons/empty_dimension_button.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/buttons/empty_dimension_button.tsx index a35366611ae18..30b543bb5f0ae 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/buttons/empty_dimension_button.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/buttons/empty_dimension_button.tsx @@ -18,6 +18,7 @@ import { DropType, DatasourceLayers, isOperation, + IndexPatternMap, } from '../../../../types'; import { getCustomDropTarget, @@ -111,6 +112,7 @@ export function EmptyDimensionButton({ onClick, onDrop, datasourceLayers, + indexPatterns, }: { layerId: string; groupIndex: number; @@ -121,6 +123,7 @@ export function EmptyDimensionButton({ layerDatasource: Datasource; datasourceLayers: DatasourceLayers; state: unknown; + indexPatterns: IndexPatternMap; }) { const { dragging } = useContext(DragContext); const sharedDatasource = @@ -148,6 +151,7 @@ export function EmptyDimensionButton({ prioritizedOperation: group.prioritizedOperation, isNewColumn: true, }, + indexPatterns, }, sharedDatasource ); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx index ce4fbbba70236..72a76a1acd73f 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx @@ -5,13 +5,14 @@ * 2.0. */ -import React, { useMemo, memo } from 'react'; +import React, { useMemo, memo, useCallback } from 'react'; import { EuiForm } from '@elastic/eui'; import { ActionExecutionContext } from '@kbn/ui-actions-plugin/public'; import { UPDATE_FILTER_REFERENCES_ACTION, UPDATE_FILTER_REFERENCES_TRIGGER, } from '@kbn/unified-search-plugin/public'; +import { changeIndexPattern } from '../../../state_management/lens_slice'; import { Visualization } from '../../../types'; import { LayerPanel } from './layer_panel'; import { generateId } from '../../../id_generator'; @@ -49,7 +50,7 @@ export function LayerPanels( activeVisualization: Visualization; } ) { - const { activeVisualization, datasourceMap } = props; + const { activeVisualization, datasourceMap, indexPatternService } = props; const { activeDatasourceId, visualization, datasourceStates } = useLensSelector( (state) => state.lens ); @@ -146,6 +147,35 @@ export function LayerPanels( [dispatchLens] ); + const onChangeIndexPattern = useCallback( + async ({ + indexPatternId, + datasourceId, + visualizationId, + layerId, + }: { + indexPatternId: string; + datasourceId?: string; + visualizationId?: string; + layerId?: string; + }) => { + const indexPatterns = await props.indexPatternService.ensureIndexPattern({ + id: indexPatternId, + cache: props.framePublicAPI.dataViews.indexPatterns, + }); + dispatchLens( + changeIndexPattern({ + indexPatternId, + datasourceIds: datasourceId ? [datasourceId] : [], + visualizationIds: visualizationId ? [visualizationId] : [], + layerId, + dataViews: { indexPatterns }, + }) + ); + }, + [dispatchLens, props.framePublicAPI.dataViews, props.indexPatternService] + ); + return ( {layerIds.map((layerId, layerIndex) => ( @@ -160,6 +190,7 @@ export function LayerPanels( updateVisualization={setVisualizationState} updateDatasource={updateDatasource} updateDatasourceAsync={updateDatasourceAsync} + onChangeIndexPattern={onChangeIndexPattern} updateAll={updateAll} isOnlyLayer={ getRemoveOperation( @@ -212,6 +243,7 @@ export function LayerPanels( removeLayerRef(layerId); }} toggleFullscreen={toggleFullscreen} + indexPatternService={indexPatternService} /> ))} void; toggleFullscreen: () => void; onEmptyDimensionAdd: (columnId: string, group: { groupId: string }) => void; + onChangeIndexPattern: (args: { + indexPatternId: string; + layerId: string; + datasourceId?: string; + visualizationId?: string; + }) => void; + indexPatternService: IndexPatternServiceAPI; } ) { const [activeDimension, setActiveDimension] = useState( @@ -86,6 +94,7 @@ export function LayerPanel( updateAll, updateDatasourceAsync, visualizationState, + onChangeIndexPattern, } = props; const datasourceStates = useLensSelector(selectDatasourceStates); @@ -186,6 +195,7 @@ export function LayerPanel( }, dimensionGroups: groups, dropType, + indexPatterns: framePublicAPI.dataViews.indexPatterns, }) ); } @@ -209,15 +219,15 @@ export function LayerPanel( }; }, [ layerDatasource, - layerDatasourceState, setNextFocusedButtonId, + layerDatasourceState, groups, + updateDatasource, + datasourceId, activeVisualization, + updateVisualization, props.visualizationState, framePublicAPI, - updateVisualization, - datasourceId, - updateDatasource, ]); const isDimensionPanelOpen = Boolean(activeId); @@ -293,6 +303,8 @@ export function LayerPanel( ] ); + const { dataViews } = props.framePublicAPI; + return ( <>
@@ -304,6 +316,12 @@ export function LayerPanel( layerConfigProps={{ ...layerVisualizationConfigProps, setState: props.updateVisualization, + onChangeIndexPattern: (indexPatternId) => + onChangeIndexPattern({ + indexPatternId, + layerId, + visualizationId: activeVisualization.id, + }), }} activeVisualization={activeVisualization} /> @@ -318,45 +336,64 @@ export function LayerPanel( /> + {(layerDatasource || activeVisualization.renderLayerPanel) && } {layerDatasource && ( - <> - - { - const newState = - typeof updater === 'function' ? updater(layerDatasourceState) : updater; - // Look for removed columns - const nextPublicAPI = layerDatasource.getPublicAPI({ - state: newState, + + onChangeIndexPattern({ indexPatternId, layerId, datasourceId }), + setState: (updater: unknown) => { + const newState = + typeof updater === 'function' ? updater(layerDatasourceState) : updater; + // Look for removed columns + const nextPublicAPI = layerDatasource.getPublicAPI({ + state: newState, + layerId, + indexPatterns: dataViews.indexPatterns, + }); + const nextTable = new Set( + nextPublicAPI.getTableSpec().map(({ columnId }) => columnId) + ); + const removed = datasourcePublicAPI + .getTableSpec() + .map(({ columnId }) => columnId) + .filter((columnId) => !nextTable.has(columnId)); + let nextVisState = props.visualizationState; + removed.forEach((columnId) => { + nextVisState = activeVisualization.removeDimension({ layerId, + columnId, + prevState: nextVisState, + frame: framePublicAPI, }); - const nextTable = new Set( - nextPublicAPI.getTableSpec().map(({ columnId }) => columnId) - ); - const removed = datasourcePublicAPI - .getTableSpec() - .map(({ columnId }) => columnId) - .filter((columnId) => !nextTable.has(columnId)); - let nextVisState = props.visualizationState; - removed.forEach((columnId) => { - nextVisState = activeVisualization.removeDimension({ - layerId, - columnId, - prevState: nextVisState, - frame: framePublicAPI, - }); - }); + }); - props.updateAll(datasourceId, newState, nextVisState); - }, - }} - /> - + props.updateAll(datasourceId, newState, nextVisState); + }, + }} + /> + )} + {activeVisualization.renderLayerPanel && ( + + onChangeIndexPattern({ + indexPatternId, + layerId, + visualizationId: activeVisualization.id, + }), + }} + /> )} @@ -441,6 +478,7 @@ export function LayerPanel( onDragStart={() => setHideTooltip(true)} onDragEnd={() => setHideTooltip(false)} onDrop={onDrop} + indexPatterns={dataViews.indexPatterns} >
) : ( @@ -541,6 +583,7 @@ export function LayerPanel( }); }} onDrop={onDrop} + indexPatterns={dataViews.indexPatterns} /> ) : null} @@ -601,6 +644,8 @@ export function LayerPanel( paramEditorCustomProps: activeGroup.paramEditorCustomProps, supportFieldFormat: activeGroup.supportFieldFormat !== false, layerType: activeVisualization.getLayerType(layerId, visualizationState), + indexPatterns: dataViews.indexPatterns, + existingFields: dataViews.existingFields, activeData: layerVisualizationConfigProps.activeData, }} /> diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/types.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/types.ts index bafa5b73a1d71..c4a2d77c30bab 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/types.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/types.ts @@ -6,6 +6,7 @@ */ import { UiActionsStart } from '@kbn/ui-actions-plugin/public'; +import { IndexPatternServiceAPI } from '../../../data_views_service/service'; import { Visualization, @@ -20,6 +21,7 @@ export interface ConfigPanelWrapperProps { datasourceMap: DatasourceMap; visualizationMap: VisualizationMap; core: DatasourceDimensionEditorProps['core']; + indexPatternService: IndexPatternServiceAPI; uiActions: UiActionsStart; } diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/data_panel_wrapper.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/data_panel_wrapper.tsx index 16bc0814a9916..09f8233ef1bca 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/data_panel_wrapper.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/data_panel_wrapper.tsx @@ -7,14 +7,16 @@ import './data_panel_wrapper.scss'; -import React, { useMemo, memo, useContext, useState, useEffect } from 'react'; +import React, { useMemo, memo, useContext, useState, useEffect, useCallback } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiPopover, EuiButtonIcon, EuiContextMenuPanel, EuiContextMenuItem } from '@elastic/eui'; +import { Storage } from '@kbn/kibana-utils-plugin/public'; import { UiActionsStart } from '@kbn/ui-actions-plugin/public'; +import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import { Easteregg } from './easteregg'; import { NativeRenderer } from '../../native_renderer'; import { DragContext, DragDropIdentifier } from '../../drag_drop'; -import { StateSetter, DatasourceDataPanelProps, DatasourceMap } from '../../types'; +import { StateSetter, DatasourceDataPanelProps, DatasourceMap, FramePublicAPI } from '../../types'; import { switchDatasource, useLensDispatch, @@ -26,7 +28,9 @@ import { selectActiveDatasourceId, selectDatasourceStates, } from '../../state_management'; -import { initializeDatasources } from './state_helpers'; +import { initializeSources } from './state_helpers'; +import type { IndexPatternServiceAPI } from '../../data_views_service/service'; +import { changeIndexPattern } from '../../state_management/lens_slice'; interface DataPanelWrapperProps { datasourceMap: DatasourceMap; @@ -34,7 +38,9 @@ interface DataPanelWrapperProps { core: DatasourceDataPanelProps['core']; dropOntoWorkspace: (field: DragDropIdentifier) => void; hasSuggestionForField: (field: DragDropIdentifier) => boolean; - plugins: { uiActions: UiActionsStart }; + plugins: { uiActions: UiActionsStart; dataViews: DataViewsPublicPluginStart }; + indexPatternService: IndexPatternServiceAPI; + frame: FramePublicAPI; } export const DataPanelWrapper = memo((props: DataPanelWrapperProps) => { @@ -64,9 +70,20 @@ export const DataPanelWrapper = memo((props: DataPanelWrapperProps) => { useEffect(() => { if (activeDatasourceId && datasourceStates[activeDatasourceId].state === null) { - initializeDatasources(props.datasourceMap, datasourceStates, undefined, undefined, { - isFullEditor: true, - }).then((result) => { + initializeSources( + { + datasourceMap: props.datasourceMap, + datasourceStates, + dataViews: props.plugins.dataViews, + references: undefined, + initialContext: undefined, + storage: new Storage(localStorage), + defaultIndexPatternId: props.core.uiSettings.get('defaultIndex'), + }, + { + isFullEditor: true, + } + ).then((result) => { const newDatasourceStates = Object.entries(result).reduce( (state, [datasourceId, datasourceState]) => ({ ...state, @@ -80,7 +97,34 @@ export const DataPanelWrapper = memo((props: DataPanelWrapperProps) => { dispatchLens(setState({ datasourceStates: newDatasourceStates })); }); } - }, [datasourceStates, activeDatasourceId, props.datasourceMap, dispatchLens]); + }, [ + datasourceStates, + activeDatasourceId, + props.datasourceMap, + dispatchLens, + props.plugins.dataViews, + props.core.uiSettings, + ]); + + const onChangeIndexPattern = useCallback( + async (indexPatternId: string, datasourceId: string, layerId?: string) => { + // reload the indexpattern + const indexPatterns = await props.indexPatternService.ensureIndexPattern({ + id: indexPatternId, + cache: props.frame.dataViews.indexPatterns, + }); + // now update the state + dispatchLens( + changeIndexPattern({ + dataViews: { indexPatterns }, + datasourceIds: [datasourceId], + indexPatternId, + layerId, + }) + ); + }, + [props.indexPatternService, props.frame.dataViews.indexPatterns, dispatchLens] + ); const datasourceProps: DatasourceDataPanelProps = { ...externalContext, @@ -92,6 +136,9 @@ export const DataPanelWrapper = memo((props: DataPanelWrapperProps) => { dropOntoWorkspace: props.dropOntoWorkspace, hasSuggestionForField: props.hasSuggestionForField, uiActions: props.plugins.uiActions, + onChangeIndexPattern, + indexPatternService: props.indexPatternService, + frame: props.frame, }; const [showDatasourceSwitcher, setDatasourceSwitcher] = useState(false); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx index 0b1847204bd1d..ed8357ea681ef 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx @@ -28,6 +28,7 @@ import { selectVisualization, } from '../../state_management'; import type { LensInspector } from '../../lens_inspector_service'; +import { IndexPatternServiceAPI } from '../../data_views_service/service'; export interface EditorFrameProps { datasourceMap: DatasourceMap; @@ -37,6 +38,7 @@ export interface EditorFrameProps { plugins: EditorFrameStartPlugins; showNoDataPopover: () => void; lensInspector: LensInspector; + indexPatternService: IndexPatternServiceAPI; } export function EditorFrame(props: EditorFrameProps) { @@ -65,7 +67,8 @@ export function EditorFrame(props: EditorFrameProps) { datasourceStates, visualizationMap, datasourceMap[activeDatasourceId], - field + field, + framePublicAPI.dataViews ); }; @@ -96,6 +99,8 @@ export function EditorFrame(props: EditorFrameProps) { showNoDataPopover={props.showNoDataPopover} dropOntoWorkspace={dropOntoWorkspace} hasSuggestionForField={hasSuggestionForField} + indexPatternService={props.indexPatternService} + frame={framePublicAPI} /> } configPanel={ @@ -105,6 +110,7 @@ export function EditorFrame(props: EditorFrameProps) { datasourceMap={datasourceMap} visualizationMap={visualizationMap} framePublicAPI={framePublicAPI} + indexPatternService={props.indexPatternService} uiActions={props.plugins.uiActions} /> ) diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/expression_helpers.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/expression_helpers.ts index 367d156929714..f1caa66ede1a2 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/expression_helpers.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/expression_helpers.ts @@ -7,11 +7,12 @@ import { Ast, fromExpression } from '@kbn/interpreter'; import { DatasourceStates } from '../../state_management'; -import { Visualization, DatasourceMap, DatasourceLayers } from '../../types'; +import { Visualization, DatasourceMap, DatasourceLayers, IndexPatternMap } from '../../types'; export function getDatasourceExpressionsByLayers( datasourceMap: DatasourceMap, - datasourceStates: DatasourceStates + datasourceStates: DatasourceStates, + indexPatterns: IndexPatternMap ): null | Record { const datasourceExpressions: Array<[string, Ast | string]> = []; @@ -24,7 +25,7 @@ export function getDatasourceExpressionsByLayers( const layers = datasource.getLayers(state); layers.forEach((layerId) => { - const result = datasource.toExpression(state, layerId); + const result = datasource.toExpression(state, layerId, indexPatterns); if (result) { datasourceExpressions.push([layerId, result]); } @@ -52,6 +53,7 @@ export function buildExpression({ datasourceLayers, title, description, + indexPatterns, }: { title?: string; description?: string; @@ -60,6 +62,7 @@ export function buildExpression({ datasourceMap: DatasourceMap; datasourceStates: DatasourceStates; datasourceLayers: DatasourceLayers; + indexPatterns: IndexPatternMap; }): Ast | null { if (visualization === null) { return null; @@ -67,7 +70,8 @@ export function buildExpression({ const datasourceExpressionsByLayers = getDatasourceExpressionsByLayers( datasourceMap, - datasourceStates + datasourceStates, + indexPatterns ); const visualizationExpression = visualization.toExpression( diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts index 1790a18ad1248..6017a7125abae 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts @@ -5,15 +5,21 @@ * 2.0. */ -import { SavedObjectReference } from '@kbn/core/public'; +import { IUiSettingsClient, SavedObjectReference } from '@kbn/core/public'; import { Ast } from '@kbn/interpreter'; import memoizeOne from 'memoize-one'; import { VisualizeFieldContext } from '@kbn/ui-actions-plugin/public'; +import { difference } from 'lodash'; +import type { DataViewsContract } from '@kbn/data-views-plugin/public'; +import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import { Datasource, DatasourceLayers, DatasourceMap, FramePublicAPI, + IndexPattern, + IndexPatternMap, + IndexPatternRef, InitializationOptions, Visualization, VisualizationMap, @@ -22,39 +28,186 @@ import { import { buildExpression } from './expression_helpers'; import { Document } from '../../persistence/saved_object_store'; import { getActiveDatasourceIdFromDoc } from '../../utils'; -import { ErrorMessage } from '../types'; +import type { ErrorMessage } from '../types'; import { getMissingCurrentDatasource, getMissingIndexPatterns, getMissingVisualizationTypeError, getUnknownVisualizationTypeError, } from '../error_helper'; -import { DatasourceStates } from '../../state_management'; +import type { DatasourceStates, DataViewsState } from '../../state_management'; +import { readFromStorage } from '../../settings_storage'; +import { loadIndexPatternRefs, loadIndexPatterns } from '../../data_views_service/loader'; -export async function initializeDatasources( - datasourceMap: DatasourceMap, - datasourceStates: DatasourceStates, +function getIndexPatterns( references?: SavedObjectReference[], initialContext?: VisualizeFieldContext | VisualizeEditorContext, - options?: InitializationOptions + initialId?: string ) { - const states: DatasourceStates = {}; - await Promise.all( - Object.entries(datasourceMap).map(([datasourceId, datasource]) => { - if (datasourceStates[datasourceId]) { - return datasource - .initialize( - datasourceStates[datasourceId].state || undefined, - references, - initialContext, - options - ) - .then((datasourceState) => { - states[datasourceId] = { isLoading: false, state: datasourceState }; - }); + const indexPatternIds = []; + if (initialContext) { + if ('isVisualizeAction' in initialContext) { + for (const { indexPatternId } of initialContext.layers) { + indexPatternIds.push(indexPatternId); + } + } else { + indexPatternIds.push(initialContext.indexPatternId); + } + } else { + // use the initialId only when no context is passed over + if (initialId) { + indexPatternIds.push(initialId); + } + } + if (references) { + for (const reference of references) { + if (reference.type === 'index-pattern') { + indexPatternIds.push(reference.id); } - }) + } + } + return [...new Set(indexPatternIds)]; +} + +const getLastUsedIndexPatternId = ( + storage: IStorageWrapper, + indexPatternRefs: IndexPatternRef[] +) => { + const indexPattern = readFromStorage(storage, 'indexPatternId'); + return indexPattern && indexPatternRefs.find((i) => i.id === indexPattern)?.id; +}; + +export async function initializeDataViews( + { + dataViews, + datasourceMap, + datasourceStates, + storage, + defaultIndexPatternId, + references, + initialContext, + }: { + dataViews: DataViewsContract; + datasourceMap: DatasourceMap; + datasourceStates: DatasourceStates; + defaultIndexPatternId: string; + storage: IStorageWrapper; + references?: SavedObjectReference[]; + initialContext?: VisualizeFieldContext | VisualizeEditorContext; + }, + options?: InitializationOptions +) { + const { isFullEditor } = options ?? {}; + // make it explicit or TS will infer never[] and break few lines down + const indexPatternRefs: IndexPatternRef[] = await (isFullEditor + ? loadIndexPatternRefs(dataViews) + : []); + + // if no state is available, use the fallbackId + const lastUsedIndexPatternId = getLastUsedIndexPatternId(storage, indexPatternRefs); + const fallbackId = lastUsedIndexPatternId || defaultIndexPatternId || indexPatternRefs[0]?.id; + const initialId = + !initialContext && + Object.keys(datasourceMap).every((datasourceId) => !datasourceStates[datasourceId]?.state) + ? fallbackId + : undefined; + + const usedIndexPatterns = getIndexPatterns(references, initialContext, initialId); + + // load them + const availableIndexPatterns = new Set(indexPatternRefs.map(({ id }: IndexPatternRef) => id)); + + const notUsedPatterns: string[] = difference([...availableIndexPatterns], usedIndexPatterns); + + const indexPatterns = await loadIndexPatterns({ + dataViews, + patterns: usedIndexPatterns, + notUsedPatterns, + cache: {}, + }); + + return { indexPatternRefs, indexPatterns }; +} + +/** + * This function composes both initializeDataViews & initializeDatasources into a single call + */ +export async function initializeSources( + { + dataViews, + datasourceMap, + datasourceStates, + storage, + defaultIndexPatternId, + references, + initialContext, + }: { + dataViews: DataViewsContract; + datasourceMap: DatasourceMap; + datasourceStates: DatasourceStates; + defaultIndexPatternId: string; + storage: IStorageWrapper; + references?: SavedObjectReference[]; + initialContext?: VisualizeFieldContext | VisualizeEditorContext; + }, + options?: InitializationOptions +) { + const { indexPatternRefs, indexPatterns } = await initializeDataViews( + { + datasourceMap, + datasourceStates, + initialContext, + dataViews, + storage, + defaultIndexPatternId, + references, + }, + { + isFullEditor: true, + } ); + return { + indexPatterns, + indexPatternRefs, + states: initializeDatasources({ + datasourceMap, + datasourceStates, + initialContext, + indexPatternRefs, + indexPatterns, + }), + }; +} + +export function initializeDatasources({ + datasourceMap, + datasourceStates, + indexPatternRefs, + indexPatterns, + references, + initialContext, +}: { + datasourceMap: DatasourceMap; + datasourceStates: DatasourceStates; + indexPatterns: Record; + indexPatternRefs: IndexPatternRef[]; + references?: SavedObjectReference[]; + initialContext?: VisualizeFieldContext | VisualizeEditorContext; +}) { + // init datasources + const states: DatasourceStates = {}; + for (const [datasourceId, datasource] of Object.entries(datasourceMap)) { + if (datasourceStates[datasourceId]) { + const state = datasource.initialize( + datasourceStates[datasourceId].state || undefined, + references, + initialContext, + indexPatternRefs, + indexPatterns + ); + states[datasourceId] = { isLoading: false, state }; + } + } return states; } @@ -74,6 +227,8 @@ export const getDatasourceLayers = memoizeOne(function getDatasourceLayers( datasourceLayers[layer] = datasourceMap[id].getPublicAPI({ state: datasourceState, layerId: layer, + // @TODO + indexPatterns: {}, }); }); }); @@ -83,7 +238,12 @@ export const getDatasourceLayers = memoizeOne(function getDatasourceLayers( export async function persistedStateToExpression( datasourceMap: DatasourceMap, visualizations: VisualizationMap, - doc: Document + doc: Document, + services: { + uiSettings: IUiSettingsClient; + storage: IStorageWrapper; + dataViews: DataViewsContract; + } ): Promise<{ ast: Ast | null; errors: ErrorMessage[] | undefined }> { const { state: { visualization: visualizationState, datasourceStates: persistedDatasourceStates }, @@ -105,18 +265,30 @@ export async function persistedStateToExpression( }; } const visualization = visualizations[visualizationType!]; - const datasourceStates = await initializeDatasources( - datasourceMap, - Object.fromEntries( - Object.entries(persistedDatasourceStates).map(([id, state]) => [ - id, - { isLoading: false, state }, - ]) - ), - references, - undefined, + const datasourceStatesFromSO = Object.fromEntries( + Object.entries(persistedDatasourceStates).map(([id, state]) => [ + id, + { isLoading: false, state }, + ]) + ); + const { indexPatterns, indexPatternRefs } = await initializeDataViews( + { + datasourceMap, + datasourceStates: datasourceStatesFromSO, + references, + dataViews: services.dataViews, + storage: services.storage, + defaultIndexPatternId: services.uiSettings.get('defaultIndex'), + }, { isFullEditor: false } ); + const datasourceStates = initializeDatasources({ + datasourceMap, + datasourceStates: datasourceStatesFromSO, + references, + indexPatterns, + indexPatternRefs, + }); const datasourceLayers = getDatasourceLayers(datasourceStates, datasourceMap); @@ -130,7 +302,8 @@ export async function persistedStateToExpression( const indexPatternValidation = validateRequiredIndexPatterns( datasourceMap[datasourceId], - datasourceStates[datasourceId] + datasourceStates[datasourceId], + indexPatterns ); if (indexPatternValidation) { @@ -145,7 +318,7 @@ export async function persistedStateToExpression( datasourceStates[datasourceId].state, visualization, visualizationState, - { datasourceLayers } + { datasourceLayers, dataViews: { indexPatterns } as DataViewsState } ); return { @@ -157,6 +330,7 @@ export async function persistedStateToExpression( datasourceMap, datasourceStates, datasourceLayers, + indexPatterns, }), errors: validationResult, }; @@ -164,12 +338,13 @@ export async function persistedStateToExpression( export function getMissingIndexPattern( currentDatasource: Datasource | null, - currentDatasourceState: { state: unknown } | null + currentDatasourceState: { state: unknown } | null, + indexPatterns: IndexPatternMap ) { if (currentDatasourceState == null || currentDatasource == null) { return []; } - const missingIds = currentDatasource.checkIntegrity(currentDatasourceState.state); + const missingIds = currentDatasource.checkIntegrity(currentDatasourceState.state, indexPatterns); if (!missingIds.length) { return []; } @@ -178,9 +353,14 @@ export function getMissingIndexPattern( const validateRequiredIndexPatterns = ( currentDatasource: Datasource, - currentDatasourceState: { state: unknown } | null + currentDatasourceState: { state: unknown } | null, + indexPatterns: IndexPatternMap ): ErrorMessage[] | undefined => { - const missingIds = getMissingIndexPattern(currentDatasource, currentDatasourceState); + const missingIds = getMissingIndexPattern( + currentDatasource, + currentDatasourceState, + indexPatterns + ); if (!missingIds.length) { return; @@ -194,14 +374,14 @@ export const validateDatasourceAndVisualization = ( currentDatasourceState: unknown | null, currentVisualization: Visualization | null, currentVisualizationState: unknown | undefined, - frameAPI: Pick + { datasourceLayers, dataViews }: Pick ): ErrorMessage[] | undefined => { const datasourceValidationErrors = currentDatasourceState - ? currentDataSource?.getErrorMessages(currentDatasourceState) + ? currentDataSource?.getErrorMessages(currentDatasourceState, dataViews.indexPatterns) : undefined; const visualizationValidationErrors = currentVisualizationState - ? currentVisualization?.getErrorMessages(currentVisualizationState, frameAPI.datasourceLayers) + ? currentVisualization?.getErrorMessages(currentVisualizationState, datasourceLayers) : undefined; if (datasourceValidationErrors?.length || visualizationValidationErrors?.length) { diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.ts index d67dc2284062e..e9f8445a86819 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.ts @@ -28,6 +28,7 @@ import { DatasourceStates, VisualizationState, applyChanges, + DataViewsState, } from '../../state_management'; /** @@ -48,6 +49,7 @@ export function getSuggestions({ field, visualizeTriggerFieldContext, activeData, + dataViews, mainPalette, }: { datasourceMap: DatasourceMap; @@ -59,6 +61,7 @@ export function getSuggestions({ field?: unknown; visualizeTriggerFieldContext?: VisualizeFieldContext | VisualizeEditorContext; activeData?: Record; + dataViews: DataViewsState; mainPalette?: PaletteOutput; }): Suggestion[] { const datasources = Object.entries(datasourceMap).filter( @@ -91,25 +94,29 @@ export function getSuggestions({ if ('isVisualizeAction' in visualizeTriggerFieldContext) { dataSourceSuggestions = datasource.getDatasourceSuggestionsForVisualizeCharts( datasourceState, - visualizeTriggerFieldContext.layers + visualizeTriggerFieldContext.layers, + dataViews.indexPatterns ); } else { // used for navigating from Discover to Lens dataSourceSuggestions = datasource.getDatasourceSuggestionsForVisualizeField( datasourceState, visualizeTriggerFieldContext.indexPatternId, - visualizeTriggerFieldContext.fieldName + visualizeTriggerFieldContext.fieldName, + dataViews.indexPatterns ); } } else if (field) { dataSourceSuggestions = datasource.getDatasourceSuggestionsForField( datasourceState, field, - (layerId) => isLayerSupportedByVisualization(layerId, [layerTypes.DATA]) // a field dragged to workspace should added to data layer + (layerId) => isLayerSupportedByVisualization(layerId, [layerTypes.DATA]), // a field dragged to workspace should added to data layer + dataViews.indexPatterns ); } else { dataSourceSuggestions = datasource.getDatasourceSuggestionsFromCurrentState( datasourceState, + dataViews.indexPatterns, (layerId) => isLayerSupportedByVisualization(layerId, [layerTypes.DATA]), activeData ); @@ -162,12 +169,14 @@ export function getVisualizeFieldSuggestions({ datasourceStates, visualizationMap, visualizeTriggerFieldContext, + dataViews, }: { datasourceMap: DatasourceMap; datasourceStates: DatasourceStates; visualizationMap: VisualizationMap; subVisualizationId?: string; visualizeTriggerFieldContext?: VisualizeFieldContext | VisualizeEditorContext; + dataViews: DataViewsState; }): Suggestion | undefined { const activeVisualization = visualizationMap?.[Object.keys(visualizationMap)[0]] || null; const suggestions = getSuggestions({ @@ -177,6 +186,7 @@ export function getVisualizeFieldSuggestions({ activeVisualization, visualizationState: undefined, visualizeTriggerFieldContext, + dataViews, }); if (visualizeTriggerFieldContext && 'isVisualizeAction' in visualizeTriggerFieldContext) { @@ -266,7 +276,8 @@ export function getTopSuggestionForField( datasourceStates: DatasourceStates, visualizationMap: Record>, datasource: Datasource, - field: DragDropIdentifier + field: DragDropIdentifier, + dataViews: DataViewsState ) { const hasData = Object.values(datasourceLayers).some( (datasourceLayer) => datasourceLayer.getTableSpec().length > 0 @@ -288,6 +299,7 @@ export function getTopSuggestionForField( visualizationState: visualization.state, field, mainPalette, + dataViews, }); return ( suggestions.find((s) => s.visualizationId === visualization.activeId) || diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx index 67403b93d7b8c..7400aa6dd8fab 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx @@ -209,7 +209,8 @@ export function SuggestionPanel({ const missingIndexPatterns = getMissingIndexPattern( activeDatasourceId ? datasourceMap[activeDatasourceId] : null, - activeDatasourceId ? datasourceStates[activeDatasourceId] : null + activeDatasourceId ? datasourceStates[activeDatasourceId] : null, + frame.dataViews.indexPatterns ); const { suggestions, currentStateExpression, currentStateError } = useMemo(() => { const newSuggestions = missingIndexPatterns.length @@ -223,6 +224,7 @@ export function SuggestionPanel({ : undefined, visualizationState: currentVisualization.state, activeData, + dataViews: frame.dataViews, }) .filter( ({ @@ -240,6 +242,7 @@ export function SuggestionPanel({ visualizationMap[visualizationId], suggestionVisualizationState, { + dataViews: frame.dataViews, datasourceLayers: getDatasourceLayers( suggestionDatasourceId ? { @@ -511,6 +514,7 @@ function getPreviewExpression( updatedLayerApis[layerId] = datasource.getPublicAPI({ layerId, state: datasourceState, + indexPatterns: frame.dataViews.indexPatterns, }); } }); @@ -518,7 +522,8 @@ function getPreviewExpression( const datasourceExpressionsByLayers = getDatasourceExpressionsByLayers( datasources, - datasourceStates + datasourceStates, + frame.dataViews.indexPatterns ); return visualization.toPreviewExpression( diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx index 8c41fc5da8aae..8556d6fdad7eb 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx @@ -496,6 +496,7 @@ function getTopSuggestion( subVisualizationId, activeData: props.framePublicAPI.activeData, mainPalette, + dataViews: props.framePublicAPI.dataViews, }); const suggestions = unfilteredSuggestions.filter((suggestion) => { // don't use extended versions of current data table on switching between visualizations diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx index e6abf77b52206..9d23dd28b3707 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx @@ -189,6 +189,7 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({ datasourceLayers, }; + const { dataViews } = framePublicAPI; const onRender$ = useCallback(() => { if (renderDeps.current) { const datasourceEvents = Object.values(renderDeps.current.datasourceMap).reduce( @@ -242,7 +243,8 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({ const missingIndexPatterns = getMissingIndexPattern( activeDatasourceId ? datasourceMap[activeDatasourceId] : null, - activeDatasourceId ? datasourceStates[activeDatasourceId] : null + activeDatasourceId ? datasourceStates[activeDatasourceId] : null, + dataViews.indexPatterns ); const missingRefsErrors = missingIndexPatterns.length @@ -289,6 +291,7 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({ datasourceMap, datasourceStates, datasourceLayers, + indexPatterns: dataViews.indexPatterns, }); if (ast) { @@ -330,6 +333,7 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({ missingRefsErrors.length, unknownVisError, visualization.activeId, + dataViews.indexPatterns, ]); useEffect(() => { diff --git a/x-pack/plugins/lens/public/editor_frame_service/service.tsx b/x-pack/plugins/lens/public/editor_frame_service/service.tsx index dea3f7c43a4a8..8c7e2378bd354 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/service.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/service.tsx @@ -6,14 +6,23 @@ */ import React from 'react'; -import { CoreStart } from '@kbn/core/public'; +import { CoreStart, IUiSettingsClient } from '@kbn/core/public'; import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; import { ExpressionsSetup, ExpressionsStart } from '@kbn/expressions-plugin/public'; import { EmbeddableSetup, EmbeddableStart } from '@kbn/embeddable-plugin/public'; -import { DataPublicPluginSetup, DataPublicPluginStart } from '@kbn/data-plugin/public'; +import { + DataPublicPluginSetup, + DataPublicPluginStart, + DataViewsContract, +} from '@kbn/data-plugin/public'; +import { UiActionsStart } from '@kbn/ui-actions-plugin/public'; import { ChartsPluginSetup } from '@kbn/charts-plugin/public'; import { DashboardStart } from '@kbn/dashboard-plugin/public'; -import { UiActionsStart } from '@kbn/ui-actions-plugin/public'; +import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; +import { + DataViewsPublicPluginSetup, + DataViewsPublicPluginStart, +} from '@kbn/data-views-plugin/public'; import { Document } from '../persistence/saved_object_store'; import { Datasource, @@ -29,6 +38,7 @@ export interface EditorFrameSetupPlugins { expressions: ExpressionsSetup; charts: ChartsPluginSetup; usageCollection?: UsageCollectionSetup; + dataViews: DataViewsPublicPluginSetup; } export interface EditorFrameStartPlugins { @@ -38,6 +48,13 @@ export interface EditorFrameStartPlugins { dashboard?: DashboardStart; expressions: ExpressionsStart; charts: ChartsPluginSetup; + dataViews: DataViewsPublicPluginStart; +} + +export interface EditorFramePlugins { + dataViews: DataViewsContract; + uiSettings: IUiSettingsClient; + storage: IStorageWrapper; } async function collectAsyncDefinitions( @@ -67,7 +84,7 @@ export class EditorFrameService { * This is an asynchronous process. * @param doc parsed Lens saved object */ - public documentToExpression = async (doc: Document) => { + public documentToExpression = async (doc: Document, services: EditorFramePlugins) => { const [resolvedDatasources, resolvedVisualizations] = await Promise.all([ this.loadDatasources(), this.loadVisualizations(), @@ -75,7 +92,7 @@ export class EditorFrameService { const { persistedStateToExpression } = await import('../async_services'); - return await persistedStateToExpression(resolvedDatasources, resolvedVisualizations, doc); + return persistedStateToExpression(resolvedDatasources, resolvedVisualizations, doc, services); }; public setup(): EditorFrameSetup { @@ -99,7 +116,7 @@ export class EditorFrameService { const { EditorFrame } = await import('../async_services'); return { - EditorFrameContainer: ({ showNoDataPopover, lensInspector }) => { + EditorFrameContainer: ({ showNoDataPopover, lensInspector, indexPatternService }) => { return (
; diff --git a/x-pack/plugins/lens/public/embeddable/embeddable.tsx b/x-pack/plugins/lens/public/embeddable/embeddable.tsx index 69e8d1ee9d18f..de4434a17f05a 100644 --- a/x-pack/plugins/lens/public/embeddable/embeddable.tsx +++ b/x-pack/plugins/lens/public/embeddable/embeddable.tsx @@ -121,7 +121,7 @@ export interface LensEmbeddableDeps { injectFilterReferences: FilterManager['inject']; visualizationMap: VisualizationMap; datasourceMap: DatasourceMap; - indexPatternService: DataViewsContract; + dataViews: DataViewsContract; expressionRenderer: ReactExpressionRendererType; timefilter: TimefilterContract; basePath: IBasePath; @@ -808,7 +808,7 @@ export class Embeddable const { indexPatterns } = await getIndexPatternsObjects( this.savedVis?.references.map(({ id }) => id) || [], - this.deps.indexPatternService + this.deps.dataViews ); this.indexPatterns = uniqBy(indexPatterns, 'id'); diff --git a/x-pack/plugins/lens/public/embeddable/embeddable_factory.ts b/x-pack/plugins/lens/public/embeddable/embeddable_factory.ts index 07c5a5bdf5ef7..1b6effea99341 100644 --- a/x-pack/plugins/lens/public/embeddable/embeddable_factory.ts +++ b/x-pack/plugins/lens/public/embeddable/embeddable_factory.ts @@ -38,7 +38,7 @@ export interface LensEmbeddableStartServices { attributeService: LensAttributeService; capabilities: RecursiveReadonly; expressionRenderer: ReactExpressionRendererType; - indexPatternService: DataViewsContract; + dataViews: DataViewsContract; uiActions?: UiActionsStart; usageCollection?: UsageCollectionSetup; documentToExpression: ( @@ -102,7 +102,7 @@ export class EmbeddableFactory implements EmbeddableFactoryDefinition { uiActions, coreHttp, attributeService, - indexPatternService, + dataViews, capabilities, usageCollection, theme, @@ -117,7 +117,7 @@ export class EmbeddableFactory implements EmbeddableFactoryDefinition { { attributeService, data, - indexPatternService, + dataViews, timefilter, inspector, expressionRenderer, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx index d907c25cbaf14..b46155edf0503 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx @@ -34,21 +34,26 @@ import { IndexPatternFieldEditorStart } from '@kbn/data-view-field-editor-plugin import { VISUALIZE_GEO_FIELD_TRIGGER } from '@kbn/ui-actions-plugin/public'; import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import { ChartsPluginSetup } from '@kbn/charts-plugin/public'; -import type { DatasourceDataPanelProps, DataType, StateSetter } from '../types'; -import { ChildDragDropProvider, DragContextState } from '../drag_drop'; import type { + DatasourceDataPanelProps, + DataType, + FramePublicAPI, IndexPattern, - IndexPatternPrivateState, IndexPatternField, - IndexPatternRef, -} from './types'; -import { loadIndexPatterns, syncExistingFields } from './loader'; -import { fieldExists } from './pure_helpers'; + StateSetter, +} from '../types'; +import { ChildDragDropProvider, DragContextState } from '../drag_drop'; +import type { IndexPatternPrivateState } from './types'; import { Loader } from '../loader'; import { LensFieldIcon } from '../shared_components/field_picker/lens_field_icon'; import { FieldGroups, FieldList } from './field_list'; +import { fieldContainsData, fieldExists } from '../shared_components'; +import { IndexPatternServiceAPI } from '../data_views_service/service'; -export type Props = Omit, 'core'> & { +export type Props = Omit< + DatasourceDataPanelProps, + 'core' | 'onChangeIndexPattern' +> & { data: DataPublicPluginStart; dataViews: DataViewsPublicPluginStart; fieldFormats: FieldFormatsStart; @@ -60,6 +65,9 @@ export type Props = Omit, 'co charts: ChartsPluginSetup; core: CoreStart; indexPatternFieldEditor: IndexPatternFieldEditorStart; + frame: FramePublicAPI; + indexPatternService: IndexPatternServiceAPI; + onIndexPatternRefresh: () => void; }; function sortFields(fieldA: IndexPatternField, fieldB: IndexPatternField) { @@ -134,40 +142,26 @@ export function IndexPatternDataPanel({ dropOntoWorkspace, hasSuggestionForField, uiActions, + indexPatternService, + frame, + onIndexPatternRefresh, }: Props) { - const { indexPatternRefs, indexPatterns, currentIndexPatternId } = state; + const { indexPatterns, indexPatternRefs, existingFields, isFirstExistenceFetch } = + frame.dataViews; + const { currentIndexPatternId } = state; const onChangeIndexPattern = useCallback( (id: string) => changeIndexPattern(id, state, setState), [state, setState, changeIndexPattern] ); - const onUpdateIndexPattern = useCallback( - (indexPattern: IndexPattern) => { - setState((prevState) => ({ - ...prevState, - indexPatterns: { - ...prevState.indexPatterns, - [indexPattern.id]: indexPattern, - }, - })); - }, - [setState] - ); - const indexPatternList = uniq( Object.values(state.layers) .map((l) => l.indexPatternId) .concat(currentIndexPatternId) ) .filter((id) => !!indexPatterns[id]) - .sort((a, b) => a.localeCompare(b)) - .map((id) => ({ - id, - title: indexPatterns[id].title, - timeFieldName: indexPatterns[id].timeFieldName, - fields: indexPatterns[id].fields, - hasRestrictions: indexPatterns[id].hasRestrictions, - })); + .sort() + .map((id) => indexPatterns[id]); const dslQuery = buildSafeEsQuery( indexPatterns[currentIndexPatternId], @@ -180,15 +174,14 @@ export function IndexPatternDataPanel({ <> - syncExistingFields({ + indexPatternService.refreshExistingFields({ dateRange, - setState, - isFirstExistenceFetch: state.isFirstExistenceFetch, currentIndexPatternTitle: indexPatterns[currentIndexPatternId]?.title || '', - showNoDataPopover, - indexPatterns: indexPatternList, - fetchJson: core.http.post, + onNoData: showNoDataPopover, dslQuery, + indexPatternList, + isFirstExistenceFetch, + existingFields, }) } loadDeps={[ @@ -197,7 +190,6 @@ export function IndexPatternDataPanel({ dateRange.fromDate, dateRange.toDate, indexPatternList.map((x) => `${x.title}:${x.timeFieldName}`).join(','), - state.indexPatterns, ]} /> @@ -229,8 +221,6 @@ export function IndexPatternDataPanel({ ) : ( )} @@ -285,42 +274,35 @@ const fieldSearchDescriptionId = htmlId(); export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ currentIndexPatternId, - indexPatternRefs, - indexPatterns, - existenceFetchFailed, - existenceFetchTimeout, query, dateRange, filters, dragDropContext, onChangeIndexPattern, - onUpdateIndexPattern, core, data, dataViews, fieldFormats, indexPatternFieldEditor, - existingFields, charts, dropOntoWorkspace, hasSuggestionForField, uiActions, + indexPatternService, + frame, + onIndexPatternRefresh, }: Omit & { data: DataPublicPluginStart; dataViews: DataViewsPublicPluginStart; fieldFormats: FieldFormatsStart; core: CoreStart; currentIndexPatternId: string; - indexPatternRefs: IndexPatternRef[]; - indexPatterns: Record; dragDropContext: DragContextState; onChangeIndexPattern: (newId: string) => void; - onUpdateIndexPattern: (indexPattern: IndexPattern) => void; - existingFields: IndexPatternPrivateState['existingFields']; charts: ChartsPluginSetup; + frame: FramePublicAPI; indexPatternFieldEditor: IndexPatternFieldEditorStart; - existenceFetchFailed?: boolean; - existenceFetchTimeout?: boolean; + onIndexPatternRefresh: () => void; }) { const [localState, setLocalState] = useState({ nameFilter: '', @@ -330,13 +312,15 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ isEmptyAccordionOpen: false, isMetaAccordionOpen: false, }); + const { existenceFetchFailed, existenceFetchTimeout, indexPatterns, existingFields } = + frame.dataViews; const currentIndexPattern = indexPatterns[currentIndexPatternId]; + const existingFieldsForIndexPattern = existingFields[currentIndexPattern?.title]; const visualizeGeoFieldTrigger = uiActions.getTrigger(VISUALIZE_GEO_FIELD_TRIGGER); const allFields = visualizeGeoFieldTrigger ? currentIndexPattern.fields : currentIndexPattern.fields.filter(({ type }) => type !== 'geo_point' && type !== 'geo_shape'); const clearLocalState = () => setLocalState((s) => ({ ...s, nameFilter: '', typeFilter: [] })); - const hasSyncedExistingFields = existingFields[currentIndexPattern.title]; const availableFieldTypes = uniq(allFields.map(({ type }) => type)).filter( (type) => type in fieldTypeNames ); @@ -349,9 +333,10 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ const unfilteredFieldGroups: FieldGroups = useMemo(() => { const containsData = (field: IndexPatternField) => { const overallField = currentIndexPattern.getFieldByName(field.name); - return ( - overallField && fieldExists(existingFields, currentIndexPattern.title, overallField.name) + overallField && + existingFieldsForIndexPattern && + fieldExists(existingFieldsForIndexPattern, overallField.name) ); }; @@ -463,7 +448,7 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ filters.length, existenceFetchTimeout, currentIndexPattern, - existingFields, + existingFieldsForIndexPattern, ]); const fieldGroups: FieldGroups = useMemo(() => { @@ -490,10 +475,9 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ }, [unfilteredFieldGroups, localState.nameFilter, localState.typeFilter]); const checkFieldExists = useCallback( - (field) => - field.type === 'document' || - fieldExists(existingFields, currentIndexPattern.title, field.name), - [existingFields, currentIndexPattern.title] + (field: IndexPatternField) => + fieldContainsData(field.name, currentIndexPattern, existingFieldsForIndexPattern), + [currentIndexPattern, existingFieldsForIndexPattern] ); const { nameFilter, typeFilter } = localState; @@ -518,15 +502,24 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ }, []); const refreshFieldList = useCallback(async () => { - const newlyMappedIndexPattern = await loadIndexPatterns({ - indexPatternsService: dataViews, - cache: {}, + const newlyMappedIndexPattern = await indexPatternService.loadIndexPatterns({ patterns: [currentIndexPattern.id], + cache: {}, + onIndexPatternRefresh, + }); + indexPatternService.updateIndexPatternsCache({ + ...frame.dataViews.indexPatterns, + [currentIndexPattern.id]: newlyMappedIndexPattern[currentIndexPattern.id], }); - onUpdateIndexPattern(newlyMappedIndexPattern[currentIndexPattern.id]); // start a new session so all charts are refreshed data.search.session.start(); - }, [data, dataViews, currentIndexPattern, onUpdateIndexPattern]); + }, [ + indexPatternService, + currentIndexPattern.id, + onIndexPatternRefresh, + frame.dataViews.indexPatterns, + data.search.session, + ]); const editField = useMemo( () => @@ -709,7 +702,7 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ exists={checkFieldExists} fieldProps={fieldProps} fieldGroups={fieldGroups} - hasSyncedExistingFields={!!hasSyncedExistingFields} + hasSyncedExistingFields={!!existingFieldsForIndexPattern} filter={filter} currentIndexPatternId={currentIndexPatternId} existenceFetchFailed={existenceFetchFailed} diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/bucket_nesting_editor.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/bucket_nesting_editor.test.tsx index fbecfeed0f321..f876536ebba43 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/bucket_nesting_editor.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/bucket_nesting_editor.test.tsx @@ -9,7 +9,7 @@ import { mount } from 'enzyme'; import React from 'react'; import { BucketNestingEditor } from './bucket_nesting_editor'; import { GenericIndexPatternColumn } from '../indexpattern'; -import { IndexPatternField } from '../types'; +import { IndexPatternField } from '../../editor_frame_service/types'; const fieldMap: Record = { a: { displayName: 'a' } as IndexPatternField, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx index e1884808910d1..87f9c64075ef7 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx @@ -36,7 +36,7 @@ import { mergeLayer } from '../state_helpers'; import { hasField } from '../pure_utils'; import { fieldIsInvalid } from '../utils'; import { BucketNestingEditor } from './bucket_nesting_editor'; -import type { IndexPattern, IndexPatternField, IndexPatternLayer } from '../types'; +import type { IndexPatternLayer } from '../types'; import { FormatSelector } from './format_selector'; import { ReferenceEditor } from './reference_editor'; import { TimeScaling } from './time_scaling'; @@ -61,8 +61,8 @@ import { NameInput } from '../../shared_components'; import { ParamEditorProps } from '../operations/definitions'; import { WrappingHelpPopover } from '../help_popover'; import { isColumn } from '../operations/definitions/helpers'; -import { FieldChoiceWithOperationType } from './field_select'; -import { documentField } from '../document_field'; +import type { FieldChoiceWithOperationType } from './field_select'; +import type { IndexPattern, IndexPatternField } from '../../types'; export interface DimensionEditorProps extends IndexPatternDimensionEditorProps { selectedColumn?: GenericIndexPatternColumn; @@ -302,7 +302,7 @@ export function DimensionEditor(props: DimensionEditorProps) { disabledStatus: definition.getDisabledStatus && definition.getDisabledStatus( - state.indexPatterns[state.currentIndexPatternId], + props.indexPatterns[state.currentIndexPatternId], state.layers[layerId], layerType ), @@ -538,7 +538,7 @@ export function DimensionEditor(props: DimensionEditorProps) { setIsCloseable, paramEditorCustomProps, ReferenceEditor, - existingFields: state.existingFields, + existingFields: props.existingFields, ...services, }; @@ -640,7 +640,7 @@ export function DimensionEditor(props: DimensionEditorProps) { }} validation={validation} currentIndexPattern={currentIndexPattern} - existingFields={state.existingFields} + existingFields={props.existingFields} selectionStyle={selectedOperationDefinition.selectionStyle} dateRange={dateRange} labelAppend={selectedOperationDefinition?.getHelpMessage?.({ @@ -666,7 +666,7 @@ export function DimensionEditor(props: DimensionEditorProps) { selectedColumn={selectedColumn as FieldBasedIndexPatternColumn} columnId={columnId} indexPattern={currentIndexPattern} - existingFields={state.existingFields} + existingFields={props.existingFields} operationSupportMatrix={operationSupportMatrix} updateLayer={(newLayer) => { if (temporaryQuickFunction) { @@ -697,7 +697,7 @@ export function DimensionEditor(props: DimensionEditorProps) { const customParamEditor = ParamEditor ? ( <> { dropType?: DropType; source: T; target: DataViewDragDropOperation; + indexPatterns: IndexPatternMap; } export function onDrop(props: DatasourceDimensionDropHandlerProps) { - const { target, source, dropType, state } = props; + const { target, source, dropType, state, indexPatterns } = props; if (isDraggedField(source) && isFieldDropType(dropType)) { return onFieldDrop( @@ -52,9 +54,10 @@ export function onDrop(props: DatasourceDimensionDropHandlerProps ['field_add', 'field_replace', 'field_combine'].includes(dropType); function onFieldDrop(props: DropHandlerProps, shouldAddField?: boolean) { - const { setState, state, source, target, dimensionGroups } = props; + const { setState, state, source, target, dimensionGroups, indexPatterns } = props; const prioritizedOperation = dimensionGroups.find( (g) => g.groupId === target.groupId )?.prioritizedOperation; const layer = state.layers[target.layerId]; - const indexPattern = state.indexPatterns[layer.indexPatternId]; + const indexPattern = indexPatterns[layer.indexPatternId]; const targetColumn = layer.columns[target.columnId]; const newOperation = shouldAddField ? targetColumn.operationType @@ -233,13 +237,20 @@ function onReorder({ } function onMoveIncompatible( - { setState, state, source, dimensionGroups, target }: DropHandlerProps, + { + setState, + state, + source, + dimensionGroups, + target, + indexPatterns, + }: DropHandlerProps, shouldDeleteSource?: boolean ) { const targetLayer = state.layers[target.layerId]; const targetColumn = targetLayer.columns[target.columnId] || null; const sourceLayer = state.layers[source.layerId]; - const indexPattern = state.indexPatterns[sourceLayer.indexPatternId]; + const indexPattern = indexPatterns[sourceLayer.indexPatternId]; const sourceColumn = sourceLayer.columns[source.columnId]; const sourceField = getField(sourceColumn, indexPattern); const newOperation = getNewOperation(sourceField, target.filterOperations, targetColumn); @@ -304,10 +315,11 @@ function onSwapIncompatible({ source, dimensionGroups, target, + indexPatterns, }: DropHandlerProps) { const targetLayer = state.layers[target.layerId]; const sourceLayer = state.layers[source.layerId]; - const indexPattern = state.indexPatterns[targetLayer.indexPatternId]; + const indexPattern = indexPatterns[targetLayer.indexPatternId]; const sourceColumn = sourceLayer.columns[source.columnId]; const targetColumn = targetLayer.columns[target.columnId]; @@ -453,11 +465,12 @@ function onCombine({ source, target, dimensionGroups, + indexPatterns, }: DropHandlerProps) { const targetLayer = state.layers[target.layerId]; const targetColumn = targetLayer.columns[target.columnId]; const targetField = getField(targetColumn, target.dataView); - const indexPattern = state.indexPatterns[targetLayer.indexPatternId]; + const indexPattern = indexPatterns[targetLayer.indexPatternId]; const sourceLayer = state.layers[source.layerId]; const sourceColumn = sourceLayer.columns[source.columnId]; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/field_select.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/field_select.tsx index 6ed035afbc4f3..8aa4175ad3f02 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/field_select.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/field_select.tsx @@ -10,11 +10,11 @@ import { partition } from 'lodash'; import React, { useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiComboBoxOptionOption, EuiComboBoxProps } from '@elastic/eui'; -import { fieldExists } from '../pure_helpers'; import type { OperationType } from '../indexpattern'; import type { OperationSupportMatrix } from './operation_support'; -import type { IndexPattern, IndexPatternPrivateState } from '../types'; import { FieldOption, FieldOptionValue, FieldPicker } from '../../shared_components/field_picker'; +import { fieldContainsData } from '../../shared_components'; +import type { ExistingFieldsMap, IndexPattern } from '../../types'; export type FieldChoiceWithOperationType = FieldOptionValue & { operationType: OperationType; @@ -28,7 +28,7 @@ export interface FieldSelectProps extends EuiComboBoxProps void; onDeleteColumn?: () => void; - existingFields: IndexPatternPrivateState['existingFields']; + existingFields: ExistingFieldsMap[string]; fieldIsInvalid: boolean; markAllFieldsCompatible?: boolean; 'data-test-subj'?: string; @@ -61,9 +61,10 @@ export function FieldSelect({ fields, (field) => currentIndexPattern.getFieldByName(field)?.type === 'document' ); - const containsData = (field: string) => - currentIndexPattern.getFieldByName(field)?.type === 'document' || - fieldExists(existingFields, currentIndexPattern.title, field); + + function containsData(field: string) { + return fieldContainsData(field, currentIndexPattern, existingFields); + } function fieldNamesToOptions(items: string[]) { return items diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/operation_support.ts b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/operation_support.ts index 2f703547219ec..43d9770deb228 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/operation_support.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/operation_support.ts @@ -6,7 +6,7 @@ */ import memoizeOne from 'memoize-one'; -import { DatasourceDimensionDropProps, OperationMetadata } from '../../types'; +import { DatasourceDimensionDropProps, IndexPatternMap, OperationMetadata } from '../../types'; import { OperationType } from '../indexpattern'; import { memoizedGetAvailableOperationsByMetadata, OperationFieldTuple } from '../operations'; import { IndexPatternPrivateState } from '../types'; @@ -20,7 +20,7 @@ export interface OperationSupportMatrix { type Props = Pick< DatasourceDimensionDropProps['target'], 'layerId' | 'columnId' | 'filterOperations' -> & { state: IndexPatternPrivateState }; +> & { state: IndexPatternPrivateState; indexPatterns: IndexPatternMap }; function computeOperationMatrix( operationsByMetadata: Array<{ @@ -67,7 +67,7 @@ const memoizedComputeOperationsMatrix = memoizeOne(computeOperationMatrix); // TODO: the support matrix should be available outside of the dimension panel export const getOperationSupportMatrix = (props: Props): OperationSupportMatrix => { const layerId = props.layerId; - const currentIndexPattern = props.state.indexPatterns[props.state.layers[layerId].indexPatternId]; + const currentIndexPattern = props.indexPatterns[props.state.layers[layerId].indexPatternId]; const operationsByMetadata = memoizedGetAvailableOperationsByMetadata(currentIndexPattern); return memoizedComputeOperationsMatrix(operationsByMetadata, props.filterOperations); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx index 316f9c87c7c29..02ca96e147605 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx @@ -44,9 +44,9 @@ import { KBN_FIELD_TYPES, ES_FIELD_TYPES, getEsQueryConfig } from '@kbn/data-plu import { ChartsPluginSetup } from '@kbn/charts-plugin/public'; import { UiActionsStart } from '@kbn/ui-actions-plugin/public'; import { DragDrop, DragDropIdentifier } from '../drag_drop'; -import { DatasourceDataPanelProps, DataType } from '../types'; +import type { DatasourceDataPanelProps, DataType, IndexPattern, IndexPatternField } from '../types'; import { BucketedAggregation, DOCUMENT_FIELD_NAME, FieldStatsResponse } from '../../common'; -import { IndexPattern, IndexPatternField, DraggedField } from './types'; +import { DraggedField } from './types'; import { LensFieldIcon } from '../shared_components/field_picker/lens_field_icon'; import { VisualizeGeoFieldButton } from './visualize_geo_field_button'; import { getVisualizeGeoFieldMessage } from '../utils'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/field_list.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/field_list.tsx index efaaf86217f06..18da8488db212 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/field_list.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/field_list.tsx @@ -6,15 +6,14 @@ */ import './field_list.scss'; -import { throttle } from 'lodash'; +import { partition, throttle } from 'lodash'; import React, { useState, Fragment, useCallback, useMemo, useEffect } from 'react'; import { EuiSpacer } from '@elastic/eui'; import { UiActionsStart } from '@kbn/ui-actions-plugin/public'; import { FieldItem } from './field_item'; import { NoFieldsCallout } from './no_fields_callout'; -import { IndexPatternField } from './types'; import { FieldItemSharedProps, FieldsAccordion } from './fields_accordion'; -import { DatasourceDataPanelProps } from '../types'; +import type { DatasourceDataPanelProps, IndexPatternField } from '../types'; const PAGINATION_SIZE = 50; export type FieldGroups = Record< @@ -76,13 +75,15 @@ export const FieldList = React.memo(function FieldList({ removeField?: (name: string) => void; uiActions: UiActionsStart; }) { + const [fieldGroupsToShow, fieldFroupsToCollapse] = partition( + Object.entries(fieldGroups), + ([, { showInAccordion }]) => showInAccordion + ); const [pageSize, setPageSize] = useState(PAGINATION_SIZE); const [scrollContainer, setScrollContainer] = useState(undefined); const [accordionState, setAccordionState] = useState>>(() => Object.fromEntries( - Object.entries(fieldGroups) - .filter(([, { showInAccordion }]) => showInAccordion) - .map(([key, { isInitiallyOpen }]) => [key, isInitiallyOpen]) + fieldGroupsToShow.map(([key, { isInitiallyOpen }]) => [key, isInitiallyOpen]) ) ); @@ -116,18 +117,16 @@ export const FieldList = React.memo(function FieldList({ const paginatedFields = useMemo(() => { let remainingItems = pageSize; return Object.fromEntries( - Object.entries(fieldGroups) - .filter(([, { showInAccordion }]) => showInAccordion) - .map(([key, fieldGroup]) => { - if (!accordionState[key] || remainingItems <= 0) { - return [key, []]; - } - const slicedFieldList = fieldGroup.fields.slice(0, remainingItems); - remainingItems = remainingItems - slicedFieldList.length; - return [key, slicedFieldList]; - }) + fieldGroupsToShow.map(([key, fieldGroup]) => { + if (!accordionState[key] || remainingItems <= 0) { + return [key, []]; + } + const slicedFieldList = fieldGroup.fields.slice(0, remainingItems); + remainingItems = remainingItems - slicedFieldList.length; + return [key, slicedFieldList]; + }) ); - }, [pageSize, fieldGroups, accordionState]); + }, [pageSize, fieldGroupsToShow, accordionState]); return (
    - {Object.entries(fieldGroups) - .filter(([, { showInAccordion }]) => !showInAccordion) - .flatMap(([, { fields }]) => - fields.map((field, index) => ( - - )) - )} -
- - {Object.entries(fieldGroups) - .filter(([, { showInAccordion }]) => showInAccordion) - .map(([key, fieldGroup], index) => ( - - + fields.map((field, index) => ( + { - setAccordionState((s) => ({ - ...s, - [key]: open, - })); - const displayedFieldLength = getDisplayedFieldsLength(fieldGroups, { - ...accordionState, - [key]: open, - }); - setPageSize( - Math.max(PAGINATION_SIZE, Math.min(pageSize * 1.5, displayedFieldLength)) - ); - }} - showExistenceFetchError={existenceFetchFailed} - showExistenceFetchTimeout={existenceFetchTimeout} - renderCallout={ - - } + hideDetails={true} + key={field.name} + itemIndex={index} + groupIndex={0} + dropOntoWorkspace={dropOntoWorkspace} + hasSuggestionForField={hasSuggestionForField} uiActions={uiActions} /> - - - ))} + )) + )} + + + {fieldGroupsToShow.map(([key, fieldGroup], index) => ( + + { + setAccordionState((s) => ({ + ...s, + [key]: open, + })); + const displayedFieldLength = getDisplayedFieldsLength(fieldGroups, { + ...accordionState, + [key]: open, + }); + setPageSize( + Math.max(PAGINATION_SIZE, Math.min(pageSize * 1.5, displayedFieldLength)) + ); + }} + showExistenceFetchError={existenceFetchFailed} + showExistenceFetchTimeout={existenceFetchTimeout} + renderCallout={ + + } + uiActions={uiActions} + /> + + + ))}
); 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 33413bf0ba59b..5db910e6d3eff 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/fields_accordion.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/fields_accordion.tsx @@ -22,10 +22,8 @@ import { Filter } from '@kbn/es-query'; import type { Query } from '@kbn/es-query'; import { ChartsPluginSetup } from '@kbn/charts-plugin/public'; import { UiActionsStart } from '@kbn/ui-actions-plugin/public'; -import { IndexPatternField } from './types'; import { FieldItem } from './field_item'; -import { DatasourceDataPanelProps } from '../types'; -import { IndexPattern } from './types'; +import type { DatasourceDataPanelProps, IndexPattern, IndexPatternField } from '../types'; export interface FieldItemSharedProps { core: DatasourceDataPanelProps['core']; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/index.ts index 41f2a4df9bcda..4bf33013b3280 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/index.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/index.ts @@ -17,7 +17,7 @@ import type { UiActionsStart } from '@kbn/ui-actions-plugin/public'; import type { FieldFormatsStart, FieldFormatsSetup } from '@kbn/field-formats-plugin/public'; import type { EditorFrameSetup } from '../types'; -export type { PersistedIndexPatternLayer, IndexPattern, FormulaPublicApi } from './types'; +export type { PersistedIndexPatternLayer, FormulaPublicApi } from './types'; export interface IndexPatternDatasourceSetupPlugins { expressions: ExpressionsSetup; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx index b1f02328242db..7a21f1df08922 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx @@ -31,14 +31,18 @@ import type { InitializationOptions, OperationDescriptor, FramePublicAPI, + IndexPatternField, + IndexPattern, + IndexPatternRef, } from '../types'; import { - loadInitialState, changeIndexPattern, changeLayerIndexPattern, extractReferences, injectReferences, - loadIndexPatterns, + loadInitialState, + onRefreshIndexPattern, + triggerActionOnIndexPatternChange, } from './loader'; import { toExpression } from './to_expression'; import { @@ -67,14 +71,9 @@ import { TermsIndexPatternColumn, } from './operations'; import { getReferenceRoot } from './operations/layer_helpers'; -import { - IndexPatternField, - IndexPatternPrivateState, - IndexPatternPersistedState, - IndexPattern, -} from './types'; +import { IndexPatternPrivateState, IndexPatternPersistedState } from './types'; import { mergeLayer } from './state_helpers'; -import { Datasource, StateSetter, VisualizeEditorContext } from '../types'; +import { Datasource, VisualizeEditorContext } from '../types'; import { deleteColumn, isReferenced } from './operations'; import { GeoFieldWorkspacePanel } from '../editor_frame_service/editor_frame/workspace_panel/geo_field_workspace_panel'; import { DraggingIdentifier } from '../drag_drop'; @@ -137,38 +136,19 @@ export function getIndexPatternDatasource({ uiActions: UiActionsStart; }) { const uiSettings = core.uiSettings; - const onIndexPatternLoadError = (err: Error) => - core.notifications.toasts.addError(err, { - title: i18n.translate('xpack.lens.indexPattern.dataViewLoadError', { - defaultMessage: 'Error loading data view', - }), - }); - - const indexPatternsService = dataViews; - - const handleChangeIndexPattern = ( - id: string, - state: IndexPatternPrivateState, - setState: StateSetter - ) => { - changeIndexPattern({ - id, - state, - setState, - onError: onIndexPatternLoadError, - storage, - indexPatternsService, - }); - }; + + const DATASOURCE_ID = 'indexpattern'; // Not stateful. State is persisted to the frame const indexPatternDatasource: Datasource = { - id: 'indexpattern', + id: DATASOURCE_ID, - async initialize( + initialize( persistedState?: IndexPatternPersistedState, references?: SavedObjectReference[], initialContext?: VisualizeFieldContext | VisualizeEditorContext, + indexPatternRefs?: IndexPatternRef[], + indexPatterns?: Record, options?: InitializationOptions ) { return loadInitialState({ @@ -176,9 +156,9 @@ export function getIndexPatternDatasource({ references, defaultIndexPatternId: core.uiSettings.get('defaultIndex'), storage, - indexPatternsService, initialContext, - options, + indexPatternRefs, + indexPatterns, }); }, @@ -224,8 +204,8 @@ export function getIndexPatternDatasource({ return Object.keys(state.layers); }, - removeColumn({ prevState, layerId, columnId }) { - const indexPattern = prevState.indexPatterns[prevState.layers[layerId]?.indexPatternId]; + removeColumn({ prevState, layerId, columnId, indexPatterns }) { + const indexPattern = indexPatterns[prevState.layers[layerId]?.indexPatternId]; return mergeLayer({ state: prevState, layerId, @@ -237,8 +217,8 @@ export function getIndexPatternDatasource({ }); }, - initializeDimension(state, layerId, { columnId, groupId, staticValue }) { - const indexPattern = state.indexPatterns[state.layers[layerId]?.indexPatternId]; + initializeDimension(state, layerId, indexPatterns, { columnId, groupId, staticValue }) { + const indexPattern = indexPatterns[state.layers[layerId]?.indexPatternId]; if (staticValue == null) { return state; } @@ -259,25 +239,30 @@ export function getIndexPatternDatasource({ }); }, - toExpression: (state, layerId) => toExpression(state, layerId, uiSettings), + toExpression: (state, layerId, indexPatterns) => + toExpression(state, layerId, indexPatterns, uiSettings), renderDataPanel( domElement: Element, props: DatasourceDataPanelProps ) { + const { onChangeIndexPattern, ...otherProps } = props; render( { + onChangeIndexPattern(indexPattern, DATASOURCE_ID); + }} data={data} dataViews={dataViews} fieldFormats={fieldFormats} charts={charts} indexPatternFieldEditor={dataViewFieldEditor} - {...props} + {...otherProps} core={core} uiActions={uiActions} + onIndexPatternRefresh={onRefreshIndexPattern} /> , @@ -317,10 +302,10 @@ export function getIndexPatternDatasource({ return columnLabelMap; }, - isValidColumn: (state: IndexPatternPrivateState, layerId: string, columnId: string) => { + isValidColumn: (state, indexPatterns, layerId, columnId) => { const layer = state.layers[layerId]; - return !isColumnInvalid(layer, columnId, state.indexPatterns[layer.indexPatternId]); + return !isColumnInvalid(layer, columnId, indexPatterns[layer.indexPatternId]); }, renderDimensionTrigger: ( @@ -397,23 +382,20 @@ export function getIndexPatternDatasource({ domElement: Element, props: DatasourceLayerPanelProps ) => { + const { onChangeIndexPattern, ...otherProps } = props; render( { - changeLayerIndexPattern({ + triggerActionOnIndexPatternChange({ indexPatternId, - setState: props.setState, state: props.state, layerId: props.layerId, - onError: onIndexPatternLoadError, - replaceIfPossible: true, - storage, - indexPatternsService, uiActions, }); + onChangeIndexPattern(indexPatternId, DATASOURCE_ID, props.layerId); }} - {...props} + {...otherProps} /> , domElement @@ -456,9 +438,26 @@ export function getIndexPatternDatasource({ }, updateCurrentIndexPatternId: ({ state, indexPatternId, setState }) => { - handleChangeIndexPattern(indexPatternId, state, setState); + setState({ + ...state, + currentIndexPatternId: indexPatternId, + }); }, + onRefreshIndexPattern, + onIndexPatternChange(state, indexPatterns, indexPatternId, layerId) { + if (layerId) { + return changeLayerIndexPattern({ + indexPatternId, + layerId, + state, + replaceIfPossible: true, + storage, + indexPatterns, + }); + } + return changeIndexPattern({ indexPatternId, state, storage, indexPatterns }); + }, getRenderEventCounters(state: IndexPatternPrivateState): string[] { const additionalEvents = { time_shift: false, @@ -489,26 +488,6 @@ export function getIndexPatternDatasource({ ].map((item) => `dimension_${item}`); }, - refreshIndexPatternsList: async ({ indexPatternId, setState }) => { - const newlyMappedIndexPattern = await loadIndexPatterns({ - indexPatternsService: dataViews, - cache: {}, - patterns: [indexPatternId], - }); - const indexPatternRefs = await dataViews.getIdsWithTitle(); - const indexPattern = newlyMappedIndexPattern[indexPatternId]; - setState((s) => { - return { - ...s, - indexPatterns: { - ...s.indexPatterns, - [indexPattern.id]: indexPattern, - }, - indexPatternRefs, - }; - }); - }, - // Reset the temporary invalid state when closing the editor, but don't // update the state if it's not needed updateStateOnCloseDimension: ({ state, layerId }) => { @@ -523,13 +502,13 @@ export function getIndexPatternDatasource({ }); }, - getPublicAPI({ state, layerId }: PublicAPIProps) { + getPublicAPI({ state, layerId, indexPatterns }: PublicAPIProps) { const columnLabelMap = indexPatternDatasource.uniqueLabels(state); const layer = state.layers[layerId]; const visibleColumnIds = layer.columnOrder.filter((colId) => !isReferenced(layer, colId)); return { - datasourceId: 'indexpattern', + datasourceId: DATASOURCE_ID, getTableSpec: () => { // consider also referenced columns in this case // but map fields to the top referencing column @@ -559,7 +538,7 @@ export function getIndexPatternDatasource({ return columnToOperation( layer.columns[columnId], columnLabelMap[columnId], - state.indexPatterns[layer.indexPatternId] + indexPatterns[layer.indexPatternId] ); } } @@ -571,7 +550,7 @@ export function getIndexPatternDatasource({ layer, visibleColumnIds, activeData?.[layerId], - state.indexPatterns[layer.indexPatternId], + indexPatterns[layer.indexPatternId], timeRange ), getVisualDefaults: () => getVisualDefaultsForLayer(layer), @@ -586,12 +565,13 @@ export function getIndexPatternDatasource({ }, }; }, - getDatasourceSuggestionsForField(state, draggedField, filterLayers) { + getDatasourceSuggestionsForField(state, draggedField, filterLayers, indexPatterns) { return isDraggedField(draggedField) ? getDatasourceSuggestionsForField( state, draggedField.indexPatternId, draggedField.field, + indexPatterns, filterLayers ) : []; @@ -600,23 +580,17 @@ export function getIndexPatternDatasource({ getDatasourceSuggestionsForVisualizeField, getDatasourceSuggestionsForVisualizeCharts, - getErrorMessages(state) { + getErrorMessages(state, indexPatterns) { if (!state) { return; } // Forward the indexpattern as well, as it is required by some operationType checks const layerErrors = Object.entries(state.layers) - .filter(([_, layer]) => !!state.indexPatterns[layer.indexPatternId]) + .filter(([_, layer]) => !!indexPatterns[layer.indexPatternId]) .map(([layerId, layer]) => ( - getErrorMessages( - layer, - state.indexPatterns[layer.indexPatternId], - state, - layerId, - core - ) ?? [] + getErrorMessages(layer, indexPatterns[layer.indexPatternId], state, layerId, core) ?? [] ).map((message) => ({ shortMessage: '', // Not displayed currently longMessage: typeof message === 'string' ? message : message.message, @@ -669,13 +643,13 @@ export function getIndexPatternDatasource({ ), ]; }, - checkIntegrity: (state) => { + checkIntegrity: (state, indexPatterns) => { const ids = Object.values(state.layers || {}).map(({ indexPatternId }) => indexPatternId); - return ids.filter((id) => !state.indexPatterns[id]); + return ids.filter((id) => !indexPatterns[id]); }, - isTimeBased: (state) => { + isTimeBased: (state, indexPatterns) => { if (!state) return false; - const { layers, indexPatterns } = state; + const { layers } = state; return ( Boolean(layers) && Object.values(layers).some((layer) => { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts index 720c917dcbc53..319c309cac036 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts @@ -9,7 +9,13 @@ import { flatten, minBy, pick, mapValues, partition } from 'lodash'; import { i18n } from '@kbn/i18n'; import type { VisualizeEditorLayersContext } from '@kbn/visualizations-plugin/public'; import { generateId } from '../id_generator'; -import type { DatasourceSuggestion, TableChangeType } from '../types'; +import type { + DatasourceSuggestion, + IndexPattern, + IndexPatternField, + IndexPatternMap, + TableChangeType, +} from '../types'; import { columnToOperation } from './indexpattern'; import { insertNewColumn, @@ -28,12 +34,7 @@ import { hasTermsWithManyBuckets, } from './operations'; import { hasField } from './pure_utils'; -import type { - IndexPattern, - IndexPatternPrivateState, - IndexPatternLayer, - IndexPatternField, -} from './types'; +import type { IndexPatternPrivateState, IndexPatternLayer } from './types'; import { documentField } from './document_field'; export type IndexPatternSuggestion = DatasourceSuggestion; @@ -100,6 +101,7 @@ export function getDatasourceSuggestionsForField( state: IndexPatternPrivateState, indexPatternId: string, field: IndexPatternField, + indexPatterns: IndexPatternMap, filterLayers?: (layerId: string) => boolean ): IndexPatternSuggestion[] { const layers = Object.keys(state.layers); @@ -113,8 +115,20 @@ export function getDatasourceSuggestionsForField( // This generates a set of suggestions where we add a layer. // A second set of suggestions is generated for visualizations that don't work with layers const newId = generateId(); - return getEmptyLayerSuggestionsForField(state, newId, indexPatternId, field).concat( - getEmptyLayerSuggestionsForField({ ...state, layers: {} }, newId, indexPatternId, field) + return getEmptyLayerSuggestionsForField( + state, + newId, + indexPatternId, + field, + indexPatterns + ).concat( + getEmptyLayerSuggestionsForField( + { ...state, layers: {} }, + newId, + indexPatternId, + field, + indexPatterns + ) ); } else { // The field we're suggesting on matches an existing layer. In this case we find the layer with @@ -125,9 +139,15 @@ export function getDatasourceSuggestionsForField( (layerId) => state.layers[layerId].columnOrder.length ) as string; if (state.layers[mostEmptyLayerId].columnOrder.length === 0) { - return getEmptyLayerSuggestionsForField(state, mostEmptyLayerId, indexPatternId, field); + return getEmptyLayerSuggestionsForField( + state, + mostEmptyLayerId, + indexPatternId, + field, + indexPatterns + ); } else { - return getExistingLayerSuggestionsForField(state, mostEmptyLayerId, field); + return getExistingLayerSuggestionsForField(state, mostEmptyLayerId, field, indexPatterns); } } } @@ -135,24 +155,26 @@ export function getDatasourceSuggestionsForField( // Called when the user navigates from Visualize editor to Lens export function getDatasourceSuggestionsForVisualizeCharts( state: IndexPatternPrivateState, - context: VisualizeEditorLayersContext[] + context: VisualizeEditorLayersContext[], + indexPatterns: IndexPatternMap ): IndexPatternSuggestion[] { const layers = Object.keys(state.layers); const layerIds = layers.filter( (id) => state.layers[id].indexPatternId === context[0].indexPatternId ); if (layerIds.length !== 0) return []; - return getEmptyLayersSuggestionsForVisualizeCharts(state, context); + return getEmptyLayersSuggestionsForVisualizeCharts(state, context, indexPatterns); } function getEmptyLayersSuggestionsForVisualizeCharts( state: IndexPatternPrivateState, - context: VisualizeEditorLayersContext[] + context: VisualizeEditorLayersContext[], + indexPatterns: IndexPatternMap ): IndexPatternSuggestion[] { const suggestions: IndexPatternSuggestion[] = []; for (let layerIdx = 0; layerIdx < context.length; layerIdx++) { const layer = context[layerIdx]; - const indexPattern = state.indexPatterns[layer.indexPatternId]; + const indexPattern = indexPatterns[layer.indexPatternId]; if (!indexPattern) return []; const newId = generateId(); @@ -228,18 +250,31 @@ function createNewTimeseriesLayerWithMetricAggregationFromVizEditor( export function getDatasourceSuggestionsForVisualizeField( state: IndexPatternPrivateState, indexPatternId: string, - fieldName: string + fieldName: string, + indexPatterns: IndexPatternMap ): IndexPatternSuggestion[] { const layers = Object.keys(state.layers); const layerIds = layers.filter((id) => state.layers[id].indexPatternId === indexPatternId); // Identify the field by the indexPatternId and the fieldName - const indexPattern = state.indexPatterns[indexPatternId]; + const indexPattern = indexPatterns[indexPatternId]; const field = indexPattern.getFieldByName(fieldName); if (layerIds.length !== 0 || !field) return []; const newId = generateId(); - return getEmptyLayerSuggestionsForField(state, newId, indexPatternId, field).concat( - getEmptyLayerSuggestionsForField({ ...state, layers: {} }, newId, indexPatternId, field) + return getEmptyLayerSuggestionsForField( + state, + newId, + indexPatternId, + field, + indexPatterns + ).concat( + getEmptyLayerSuggestionsForField( + { ...state, layers: {} }, + newId, + indexPatternId, + field, + indexPatterns + ) ); } @@ -255,10 +290,11 @@ function getBucketOperation(field: IndexPatternField) { function getExistingLayerSuggestionsForField( state: IndexPatternPrivateState, layerId: string, - field: IndexPatternField + field: IndexPatternField, + indexPatterns: IndexPatternMap ) { const layer = state.layers[layerId]; - const indexPattern = state.indexPatterns[layer.indexPatternId]; + const indexPattern = indexPatterns[layer.indexPatternId]; const operations = getOperationTypesForField(field); const usableAsBucketOperation = getBucketOperation(field); const fieldInUse = Object.values(layer.columns).some( @@ -369,9 +405,10 @@ function getEmptyLayerSuggestionsForField( state: IndexPatternPrivateState, layerId: string, indexPatternId: string, - field: IndexPatternField + field: IndexPatternField, + indexPatterns: IndexPatternMap ): IndexPatternSuggestion[] { - const indexPattern = state.indexPatterns[indexPatternId]; + const indexPattern = indexPatterns[indexPatternId]; let newLayer: IndexPatternLayer | undefined; const bucketOperation = getBucketOperation(field); if (bucketOperation) { @@ -447,6 +484,7 @@ function createNewLayerWithMetricAggregation( export function getDatasourceSuggestionsFromCurrentState( state: IndexPatternPrivateState, + indexPatterns: IndexPatternMap, filterLayers: (layerId: string) => boolean = () => true ): Array> { const layers = Object.entries(state.layers || {}).filter(([layerId]) => filterLayers(layerId)); @@ -467,7 +505,7 @@ export function getDatasourceSuggestionsFromCurrentState( }) : i18n.translate('xpack.lens.indexPatternSuggestion.removeLayerLabel', { defaultMessage: 'Show only {indexPatternTitle}', - values: { indexPatternTitle: state.indexPatterns[layer.indexPatternId].title }, + values: { indexPatternTitle: indexPatterns[layer.indexPatternId].title }, }); return buildSuggestion({ @@ -495,7 +533,7 @@ export function getDatasourceSuggestionsFromCurrentState( layers .filter(([_id, layer]) => layer.columnOrder.length && layer.indexPatternId) .map(([layerId, layer]) => { - const indexPattern = state.indexPatterns[layer.indexPatternId]; + const indexPattern = indexPatterns[layer.indexPatternId]; const [buckets, metrics, references] = getExistingColumnGroups(layer); const timeDimension = layer.columnOrder.find( (columnId) => @@ -522,7 +560,9 @@ export function getDatasourceSuggestionsFromCurrentState( if (!references.length && metrics.length && buckets.length === 0) { if (timeField && buckets.length < 1 && !hasTermsWithManyBuckets(layer)) { // suggest current metric over time if there is a default time field - suggestions.push(createSuggestionWithDefaultDateHistogram(state, layerId, timeField)); + suggestions.push( + createSuggestionWithDefaultDateHistogram(state, layerId, timeField, indexPatterns) + ); } if (indexPattern) { suggestions.push(...createAlternativeMetricSuggestions(indexPattern, layerId, state)); @@ -541,11 +581,13 @@ export function getDatasourceSuggestionsFromCurrentState( ) { // suggest current configuration over time if there is a default time field // and no time dimension yet - suggestions.push(createSuggestionWithDefaultDateHistogram(state, layerId, timeField)); + suggestions.push( + createSuggestionWithDefaultDateHistogram(state, layerId, timeField, indexPatterns) + ); } if (buckets.length === 2) { - suggestions.push(createChangedNestingSuggestion(state, layerId)); + suggestions.push(createChangedNestingSuggestion(state, layerId, indexPatterns)); } } return suggestions; @@ -553,11 +595,15 @@ export function getDatasourceSuggestionsFromCurrentState( ); } -function createChangedNestingSuggestion(state: IndexPatternPrivateState, layerId: string) { +function createChangedNestingSuggestion( + state: IndexPatternPrivateState, + layerId: string, + indexPatterns: IndexPatternMap +) { const layer = state.layers[layerId]; const [firstBucket, secondBucket, ...rest] = layer.columnOrder; const updatedLayer = { ...layer, columnOrder: [secondBucket, firstBucket, ...rest] }; - const indexPattern = state.indexPatterns[state.currentIndexPatternId]; + const indexPattern = indexPatterns[state.currentIndexPatternId]; const firstBucketColumn = layer.columns[firstBucket]; const firstBucketLabel = (hasField(firstBucketColumn) && @@ -670,10 +716,11 @@ function createAlternativeMetricSuggestions( function createSuggestionWithDefaultDateHistogram( state: IndexPatternPrivateState, layerId: string, - timeField: IndexPatternField + timeField: IndexPatternField, + indexPatterns: IndexPatternMap ) { const layer = state.layers[layerId]; - const indexPattern = state.indexPatterns[layer.indexPatternId]; + const indexPattern = indexPatterns[layer.indexPatternId]; return buildSuggestion({ state, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/layerpanel.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/layerpanel.test.tsx index d0c8e1ffafd69..3d366408595b0 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/layerpanel.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/layerpanel.test.tsx @@ -12,7 +12,7 @@ import { shallowWithIntl as shallow } from '@kbn/test-jest-helpers'; import { ShallowWrapper } from 'enzyme'; import { EuiSelectable } from '@elastic/eui'; import { DataViewsList } from '@kbn/unified-search-plugin/public'; -import { ChangeIndexPattern } from './change_indexpattern'; +import { ChangeIndexPattern } from '../shared_components/dataview_picker/dataview_picker'; import { getFieldByNameFactory } from './pure_helpers'; import { TermsIndexPatternColumn } from './operations'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/layerpanel.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/layerpanel.tsx index 4fd7b920e124b..9824f70eeddfc 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/layerpanel.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/layerpanel.tsx @@ -10,7 +10,7 @@ import { I18nProvider } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import { DatasourceLayerPanelProps } from '../types'; import { IndexPatternPrivateState } from './types'; -import { ChangeIndexPattern } from './change_indexpattern'; +import { ChangeIndexPattern } from '../shared_components/dataview_picker/dataview_picker'; export interface IndexPatternLayerPanelProps extends DatasourceLayerPanelProps { @@ -18,10 +18,15 @@ export interface IndexPatternLayerPanelProps onChangeIndexPattern: (newId: string) => void; } -export function LayerPanel({ state, layerId, onChangeIndexPattern }: IndexPatternLayerPanelProps) { +export function LayerPanel({ + state, + layerId, + onChangeIndexPattern, + dataViews, +}: IndexPatternLayerPanelProps) { const layer = state.layers[layerId]; - const indexPattern = state.indexPatterns[layer.indexPatternId]; + const indexPattern = dataViews.indexPatterns[layer.indexPatternId]; const notFoundTitleLabel = i18n.translate('xpack.lens.layerPanel.missingDataView', { defaultMessage: 'Data view not found', }); @@ -37,7 +42,7 @@ export function LayerPanel({ state, layerId, onChangeIndexPattern }: IndexPatter fontWeight: 'normal', }} indexPatternId={layer.indexPatternId} - indexPatternRefs={state.indexPatternRefs} + indexPatternRefs={dataViews.indexPatternRefs} isMissingCurrent={!indexPattern} onChangeIndexPattern={onChangeIndexPattern} /> diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/loader.ts b/x-pack/plugins/lens/public/indexpattern_datasource/loader.ts index d3affb5b32d8c..204e576140411 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/loader.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/loader.ts @@ -7,9 +7,7 @@ import { uniq, mapValues, difference } from 'lodash'; import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; -import type { HttpSetup, SavedObjectReference } from '@kbn/core/public'; -import type { DataViewsContract, DataView } from '@kbn/data-views-plugin/public'; -import { isNestedField } from '@kbn/data-views-plugin/common'; +import type { SavedObjectReference } from '@kbn/core/public'; import { UPDATE_FILTER_REFERENCES_ACTION, UPDATE_FILTER_REFERENCES_TRIGGER, @@ -19,148 +17,18 @@ import { UiActionsStart, VisualizeFieldContext, } from '@kbn/ui-actions-plugin/public'; -import type { - DatasourceDataPanelProps, - InitializationOptions, - VisualizeEditorContext, -} from '../types'; -import { - IndexPattern, - IndexPatternRef, - IndexPatternPersistedState, - IndexPatternPrivateState, - IndexPatternField, - IndexPatternLayer, -} from './types'; +import type { VisualizeEditorContext } from '../types'; +import { IndexPatternPersistedState, IndexPatternPrivateState, IndexPatternLayer } from './types'; -import { updateLayerIndexPattern, translateToOperationName } from './operations'; -import { DateRange, ExistingFields } from '../../common/types'; -import { BASE_API_URL } from '../../common'; -import { documentField } from './document_field'; +import { memoizedGetAvailableOperationsByMetadata, updateLayerIndexPattern } from './operations'; import { readFromStorage, writeToStorage } from '../settings_storage'; -import { getFieldByNameFactory } from './pure_helpers'; -import { memoizedGetAvailableOperationsByMetadata } from './operations'; - -type SetState = DatasourceDataPanelProps['setState']; -type IndexPatternsService = Pick; -type ErrorHandler = (err: Error) => void; - -export function convertDataViewIntoLensIndexPattern(dataView: DataView): IndexPattern { - const newFields = dataView.fields - .filter((field) => !isNestedField(field) && (!!field.aggregatable || !!field.scripted)) - .map((field): IndexPatternField => { - // Convert the getters on the index pattern service into plain JSON - const base = { - name: field.name, - displayName: field.displayName, - type: field.type, - aggregatable: field.aggregatable, - searchable: field.searchable, - meta: dataView.metaFields.includes(field.name), - esTypes: field.esTypes, - scripted: field.scripted, - runtime: Boolean(field.runtimeField), - }; - - // Simplifies tests by hiding optional properties instead of undefined - return base.scripted - ? { - ...base, - lang: field.lang, - script: field.script, - } - : base; - }) - .concat(documentField); - - const { typeMeta, title, name, timeFieldName, fieldFormatMap } = dataView; - if (typeMeta?.aggs) { - const aggs = Object.keys(typeMeta.aggs); - newFields.forEach((field, index) => { - const restrictionsObj: IndexPatternField['aggregationRestrictions'] = {}; - aggs.forEach((agg) => { - const restriction = typeMeta.aggs && typeMeta.aggs[agg] && typeMeta.aggs[agg][field.name]; - if (restriction) { - restrictionsObj[translateToOperationName(agg)] = restriction; - } - }); - if (Object.keys(restrictionsObj).length) { - newFields[index] = { ...field, aggregationRestrictions: restrictionsObj }; - } - }); - } - - return { - id: dataView.id!, // id exists for sure because we got index patterns by id - title, - name: name ? name : title, - timeFieldName, - fieldFormatMap: - fieldFormatMap && - Object.fromEntries( - Object.entries(fieldFormatMap).map(([id, format]) => [ - id, - // @ts-expect-error FIXME Property 'toJSON' does not exist on type 'SerializedFieldFormat' - 'toJSON' in format ? format.toJSON() : format, - ]) - ), - fields: newFields, - getFieldByName: getFieldByNameFactory(newFields, false), - hasRestrictions: !!typeMeta?.aggs, - }; -} - -export async function loadIndexPatterns({ - indexPatternsService, - patterns, - notUsedPatterns, - cache, -}: { - indexPatternsService: IndexPatternsService; - patterns: string[]; - notUsedPatterns?: string[]; - cache: Record; -}) { - const missingIds = patterns.filter((id) => !cache[id]); - - if (missingIds.length === 0) { - return cache; - } +import type { IndexPattern, IndexPatternRef } from '../types'; +export function onRefreshIndexPattern() { if (memoizedGetAvailableOperationsByMetadata.cache.clear) { // clear operations meta data cache because index pattern reference may change memoizedGetAvailableOperationsByMetadata.cache.clear(); } - - const allIndexPatterns = await Promise.allSettled( - missingIds.map((id) => indexPatternsService.get(id)) - ); - // ignore rejected indexpatterns here, they're already handled at the app level - let indexPatterns = allIndexPatterns - .filter( - (response): response is PromiseFulfilledResult => response.status === 'fulfilled' - ) - .map((response) => response.value); - - // if all of the used index patterns failed to load, try loading one of not used ones till one succeeds - for (let i = 0; notUsedPatterns && i < notUsedPatterns?.length && !indexPatterns.length; i++) { - const resp = await indexPatternsService.get(notUsedPatterns[i]).catch((e) => { - // do nothing - }); - if (resp) { - indexPatterns = [resp]; - } - } - - const indexPatternsObject = indexPatterns.reduce( - (acc, indexPattern) => ({ - [indexPattern.id!]: convertDataViewIntoLensIndexPattern(indexPattern), - ...acc, - }), - { ...cache } - ); - - return indexPatternsObject; } const getLastUsedIndexPatternId = ( @@ -209,42 +77,43 @@ export function injectReferences( }; } -export async function loadInitialState({ +export function createStateFromPersisted({ persistedState, references, - defaultIndexPatternId, - storage, - indexPatternsService, - initialContext, - options, }: { persistedState?: IndexPatternPersistedState; references?: SavedObjectReference[]; +}) { + return persistedState && references ? injectReferences(persistedState, references) : undefined; +} + +export function getUsedIndexPatterns({ + state, + indexPatternRefs, + storage, + initialContext, + defaultIndexPatternId, +}: { + state?: { + layers: Record; + }; defaultIndexPatternId?: string; storage: IStorageWrapper; - indexPatternsService: IndexPatternsService; initialContext?: VisualizeFieldContext | VisualizeEditorContext; - options?: InitializationOptions; -}): Promise { - const { isFullEditor } = options ?? {}; - // make it explicit or TS will infer never[] and break few lines down - const indexPatternRefs: IndexPatternRef[] = await (isFullEditor - ? loadIndexPatternRefs(indexPatternsService) - : []); - + indexPatternRefs: IndexPatternRef[]; +}) { const lastUsedIndexPatternId = getLastUsedIndexPatternId(storage, indexPatternRefs); const fallbackId = lastUsedIndexPatternId || defaultIndexPatternId || indexPatternRefs[0]?.id; const indexPatternIds = []; - if (initialContext && 'isVisualizeAction' in initialContext) { - for (let layerIdx = 0; layerIdx < initialContext.layers.length; layerIdx++) { - const layerContext = initialContext.layers[layerIdx]; - indexPatternIds.push(layerContext.indexPatternId); + if (initialContext) { + if ('isVisualizeAction' in initialContext) { + for (const { indexPatternId } of initialContext.layers) { + indexPatternIds.push(indexPatternId); + } + } else { + indexPatternIds.push(initialContext.indexPatternId); } - } else if (initialContext) { - indexPatternIds.push(initialContext.indexPatternId); } - const state = - persistedState && references ? injectReferences(persistedState, references) : undefined; const usedPatterns = ( initialContext ? indexPatternIds @@ -253,30 +122,50 @@ export async function loadInitialState({ // take out the undefined from the list .filter(Boolean); - const notUsedPatterns: string[] = difference( - uniq(indexPatternRefs.map(({ id }) => id)), - usedPatterns - ); + return { + usedPatterns, + allIndexPatternIds: indexPatternIds, + }; +} + +export function loadInitialState({ + persistedState, + references, + defaultIndexPatternId, + storage, + initialContext, + indexPatternRefs = [], + indexPatterns = {}, +}: { + persistedState?: IndexPatternPersistedState; + references?: SavedObjectReference[]; + defaultIndexPatternId?: string; + storage: IStorageWrapper; + initialContext?: VisualizeFieldContext | VisualizeEditorContext; + indexPatternRefs?: IndexPatternRef[]; + indexPatterns?: Record; +}): IndexPatternPrivateState { + const state = createStateFromPersisted({ persistedState, references }); + const { usedPatterns, allIndexPatternIds: indexPatternIds } = getUsedIndexPatterns({ + state, + defaultIndexPatternId, + storage, + initialContext, + indexPatternRefs, + }); const availableIndexPatterns = new Set(indexPatternRefs.map(({ id }: IndexPatternRef) => id)); - const indexPatterns = await loadIndexPatterns({ - indexPatternsService, - cache: {}, - patterns: usedPatterns, - notUsedPatterns, - }); + const notUsedPatterns: string[] = difference([...availableIndexPatterns], usedPatterns); // Priority list: // * start with the indexPattern in context // * then fallback to the used ones // * then as last resort use a first one from not used refs - const availableIndexPatternIds = [...indexPatternIds, ...usedPatterns, ...notUsedPatterns].filter( + const currentIndexPatternId = [...indexPatternIds, ...usedPatterns, ...notUsedPatterns].find( (id) => id != null && availableIndexPatterns.has(id) && indexPatterns[id] ); - const currentIndexPatternId = availableIndexPatternIds[0]; - if (currentIndexPatternId) { setLastUsedIndexPatternId(storage, currentIndexPatternId); } @@ -285,78 +174,41 @@ export async function loadInitialState({ layers: {}, ...state, currentIndexPatternId, - indexPatternRefs, - indexPatterns, - existingFields: {}, - isFirstExistenceFetch: true, }; } -export async function changeIndexPattern({ - id, +export function changeIndexPattern({ + indexPatternId, state, - setState, - onError, storage, - indexPatternsService, + indexPatterns, }: { - id: string; + indexPatternId: string; state: IndexPatternPrivateState; - setState: SetState; - onError: ErrorHandler; storage: IStorageWrapper; - indexPatternsService: IndexPatternsService; + indexPatterns: Record; }) { - const indexPatterns = await loadIndexPatterns({ - indexPatternsService, - cache: state.indexPatterns, - patterns: [id], - }); - - if (indexPatterns[id] == null) { - return onError(Error('Missing indexpatterns')); - } - - try { - setState( - (s) => ({ - ...s, - layers: isSingleEmptyLayer(state.layers) - ? mapValues(state.layers, (layer) => updateLayerIndexPattern(layer, indexPatterns[id])) - : state.layers, - indexPatterns: { - ...s.indexPatterns, - [id]: indexPatterns[id], - }, - currentIndexPatternId: id, - }), - { applyImmediately: true } - ); - setLastUsedIndexPatternId(storage, id); - } catch (err) { - onError(err); - } + setLastUsedIndexPatternId(storage, indexPatternId); + return { + ...state, + layers: isSingleEmptyLayer(state.layers) + ? mapValues(state.layers, (layer) => + updateLayerIndexPattern(layer, indexPatterns[indexPatternId]) + ) + : state.layers, + currentIndexPatternId: indexPatternId, + }; } -export async function changeLayerIndexPattern({ - indexPatternId, - layerId, +export function triggerActionOnIndexPatternChange({ state, - setState, - onError, - replaceIfPossible, - storage, - indexPatternsService, + layerId, uiActions, + indexPatternId, }: { indexPatternId: string; layerId: string; state: IndexPatternPrivateState; - setState: SetState; - onError: ErrorHandler; - replaceIfPossible?: boolean; - storage: IStorageWrapper; - indexPatternsService: IndexPatternsService; uiActions: UiActionsStart; }) { const fromDataView = state.layers[layerId].indexPatternId; @@ -372,144 +224,32 @@ export async function changeLayerIndexPattern({ defaultDataView: toDataView, usedDataViews: Object.values(Object.values(state.layers).map((layer) => layer.indexPatternId)), } as ActionExecutionContext); - - const indexPatterns = await loadIndexPatterns({ - indexPatternsService, - cache: state.indexPatterns, - patterns: [indexPatternId], - }); - if (indexPatterns[indexPatternId] == null) { - return onError(Error('Missing indexpatterns')); - } - - try { - setState((s) => ({ - ...s, - layers: { - ...s.layers, - [layerId]: updateLayerIndexPattern(s.layers[layerId], indexPatterns[indexPatternId]), - }, - indexPatterns: { - ...s.indexPatterns, - [indexPatternId]: indexPatterns[indexPatternId], - }, - currentIndexPatternId: replaceIfPossible ? indexPatternId : s.currentIndexPatternId, - })); - setLastUsedIndexPatternId(storage, indexPatternId); - } catch (err) { - onError(err); - } -} - -async function loadIndexPatternRefs( - indexPatternsService: IndexPatternsService -): Promise { - const indexPatterns = await indexPatternsService.getIdsWithTitle(); - - return indexPatterns.sort((a, b) => { - return a.title.localeCompare(b.title); - }); } -export async function syncExistingFields({ +export function changeLayerIndexPattern({ + indexPatternId, indexPatterns, - dateRange, - fetchJson, - setState, - isFirstExistenceFetch, - currentIndexPatternTitle, - dslQuery, - showNoDataPopover, + layerId, + state, + replaceIfPossible, + storage, }: { - dateRange: DateRange; - indexPatterns: Array<{ - id: string; - title: string; - fields: IndexPatternField[]; - timeFieldName?: string | null; - hasRestrictions: boolean; - }>; - fetchJson: HttpSetup['post']; - setState: SetState; - isFirstExistenceFetch: boolean; - currentIndexPatternTitle: string; - dslQuery: object; - showNoDataPopover: () => void; + indexPatternId: string; + layerId: string; + state: IndexPatternPrivateState; + replaceIfPossible?: boolean; + storage: IStorageWrapper; + indexPatterns: Record; }) { - const existenceRequests = indexPatterns.map((pattern) => { - if (pattern.hasRestrictions) { - return { - indexPatternTitle: pattern.title, - existingFieldNames: pattern.fields.map((field) => field.name), - }; - } - const body: Record = { - dslQuery, - fromDate: dateRange.fromDate, - toDate: dateRange.toDate, - }; - - if (pattern.timeFieldName) { - body.timeFieldName = pattern.timeFieldName; - } - - 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, - existenceFetchFailed: false, - existenceFetchTimeout: false, - existingFields: emptinessInfo.reduce( - (acc, info) => { - acc[info.indexPatternTitle] = booleanMap(info.existingFieldNames); - return acc; - }, - { ...state.existingFields } - ), - }), - { applyImmediately: true } - ); - } catch (e) { - // show all fields as available if fetch failed or timed out - setState( - (state) => ({ - ...state, - existenceFetchFailed: e.res?.status !== 408, - existenceFetchTimeout: e.res?.status === 408, - existingFields: indexPatterns.reduce( - (acc, pattern) => { - acc[pattern.title] = booleanMap(pattern.fields.map((field) => field.name)); - return acc; - }, - { ...state.existingFields } - ), - }), - { applyImmediately: true } - ); - } -} - -function booleanMap(keys: string[]) { - return keys.reduce((acc, key) => { - acc[key] = true; - return acc; - }, {} as Record); + setLastUsedIndexPatternId(storage, indexPatternId); + return { + ...state, + layers: { + ...state.layers, + [layerId]: updateLayerIndexPattern(state.layers[layerId], indexPatterns[indexPatternId]), + }, + currentIndexPatternId: replaceIfPossible ? indexPatternId : state.currentIndexPatternId, + }; } function isSingleEmptyLayer(layerMap: IndexPatternPrivateState['layers']) { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula.test.tsx index 1e16c27253d9b..4a8c6d437f2ac 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula.test.tsx @@ -9,7 +9,8 @@ import { createMockedIndexPattern } from '../../../mocks'; import { formulaOperation, GenericOperationDefinition, GenericIndexPatternColumn } from '..'; import { FormulaIndexPatternColumn } from './formula'; import { insertOrReplaceFormulaColumn } from './parse'; -import type { IndexPattern, IndexPatternField, IndexPatternLayer } from '../../../types'; +import type { IndexPatternLayer } from '../../../types'; +import { IndexPattern, IndexPatternField } from '../../../../editor_frame_service/types'; import { tinymathFunctions } from './util'; import { TermsIndexPatternColumn } from '../terms'; import { MovingAverageIndexPatternColumn } from '../calculations'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.test.tsx index 9548d9473e656..e0c1c4b3b84cc 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.test.tsx @@ -14,7 +14,7 @@ import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks'; -import type { IndexPatternLayer, IndexPattern } from '../../../types'; +import type { IndexPatternLayer } from '../../../types'; import { rangeOperation } from '..'; import { RangeIndexPatternColumn } from './ranges'; import { @@ -25,8 +25,9 @@ import { SLICES, } from './constants'; import { RangePopover } from './advanced_editor'; -import { DragDropBuckets } from '../shared_components'; +import { DragDropBuckets } from '../../../../shared_components'; import { getFieldByNameFactory } from '../../../pure_helpers'; +import { IndexPattern } from '../../../../editor_frame_service/types'; // mocking random id generator function jest.mock('@elastic/eui', () => { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/shared_components/index.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/shared_components/index.tsx index 47cc121be095b..49d913655af45 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/shared_components/index.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/shared_components/index.tsx @@ -6,5 +6,4 @@ */ export * from './label_input'; -export * from './buckets'; export * from './form_row'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/field_inputs.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/field_inputs.tsx index 6381b2843cf2c..a6b734d373849 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/field_inputs.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/field_inputs.tsx @@ -15,13 +15,18 @@ import { htmlIdGenerator, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { DragDropBuckets, NewBucketButton } from '../shared_components/buckets'; -import { TooltipWrapper, useDebouncedValue } from '../../../../shared_components'; +import { + DragDropBuckets, + NewBucketButton, + TooltipWrapper, + useDebouncedValue, +} from '../../../../shared_components'; import { FieldSelect } from '../../../dimension_panel/field_select'; import type { TermsIndexPatternColumn } from './types'; -import type { IndexPattern, IndexPatternPrivateState } from '../../../types'; +import type { IndexPatternPrivateState } from '../../../types'; import type { OperationSupportMatrix } from '../../../dimension_panel'; import { supportedTypes } from './constants'; +import type { IndexPattern } from '../../../../editor_frame_service/types'; const generateId = htmlIdGenerator(); export const MAX_MULTI_FIELDS_SIZE = 3; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx index 424bdfd002522..f01ccebdabcaf 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx @@ -28,12 +28,13 @@ import { LastValueIndexPatternColumn, operationDefinitionMap, } from '..'; -import { IndexPattern, IndexPatternLayer, IndexPatternPrivateState } from '../../../types'; +import { IndexPatternLayer, IndexPatternPrivateState } from '../../../types'; import { FrameDatasourceAPI } from '../../../../types'; import { DateHistogramIndexPatternColumn } from '../date_histogram'; import { getOperationSupportMatrix } from '../../../dimension_panel/operation_support'; import { FieldSelect } from '../../../dimension_panel/field_select'; import { ReferenceEditor } from '../../../dimension_panel/reference_editor'; +import { IndexPattern } from '../../../../editor_frame_service/types'; import { cloneDeep } from 'lodash'; import { IncludeExcludeRow } from './include_exclude_options'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts index c89ec6ae02199..cfbc5d4455fd2 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts @@ -23,7 +23,7 @@ import { operationDefinitionMap, OperationType } from '.'; import { TermsIndexPatternColumn } from './definitions/terms'; import { DateHistogramIndexPatternColumn } from './definitions/date_histogram'; import { AvgIndexPatternColumn } from './definitions/metrics'; -import type { IndexPattern, IndexPatternLayer, IndexPatternPrivateState } from '../types'; +import type { IndexPatternLayer, IndexPatternPrivateState } from '../types'; import { documentField } from '../document_field'; import { getFieldByNameFactory } from '../pure_helpers'; import { generateId } from '../../id_generator'; @@ -38,6 +38,7 @@ import { } from './definitions'; import { TinymathAST } from '@kbn/tinymath'; import { CoreStart } from '@kbn/core/public'; +import { IndexPattern } from '../../editor_frame_service/types'; jest.mock('.'); jest.mock('../../id_generator'); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts index a1edd6132d22a..be3dd8702e20d 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts @@ -13,6 +13,8 @@ import type { VisualizeEditorLayersContext } from '@kbn/visualizations-plugin/pu import type { DatasourceFixAction, FrameDatasourceAPI, + IndexPattern, + IndexPatternField, OperationMetadata, VisualizationDimensionGroupConfig, } from '../../types'; @@ -27,8 +29,6 @@ import { } from './definitions'; import type { DataViewDragDropOperation, - IndexPattern, - IndexPatternField, IndexPatternLayer, IndexPatternPrivateState, } from '../types'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.ts index 396bf78f82db6..b30c91a7f1084 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.ts @@ -6,7 +6,7 @@ */ import { memoize } from 'lodash'; -import { OperationMetadata } from '../../types'; +import type { IndexPattern, IndexPatternField, OperationMetadata } from '../../types'; import { operationDefinitionMap, operationDefinitions, @@ -15,7 +15,6 @@ import { renameOperationsMapping, BaseIndexPatternColumn, } from './definitions'; -import { IndexPattern, IndexPatternField } from '../types'; import { documentField } from '../document_field'; import { hasField } from '../pure_utils'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/pure_helpers.ts b/x-pack/plugins/lens/public/indexpattern_datasource/pure_helpers.ts index 6c81634fb4c09..ba3ba9bbb824f 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/pure_helpers.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/pure_helpers.ts @@ -6,17 +6,9 @@ */ import { keyBy } from 'lodash'; -import { IndexPatternField, IndexPatternPrivateState } from './types'; +import { IndexPatternField } from '../types'; import { documentField } from './document_field'; -export function fieldExists( - existingFields: IndexPatternPrivateState['existingFields'], - indexPatternTitle: string, - fieldName: string -) { - return existingFields[indexPatternTitle] && existingFields[indexPatternTitle][fieldName]; -} - export function getFieldByNameFactory( newFields: IndexPatternField[], addRecordsField: boolean = true diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/query_input.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/query_input.tsx index 6d69760d51d87..583f6b28996c9 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/query_input.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/query_input.tsx @@ -20,6 +20,7 @@ export const QueryInput = ({ onSubmit, disableAutoFocus, ['data-test-subj']: dataTestSubj, + placeholder, }: { value: Query; onChange: (input: Query) => void; @@ -28,6 +29,7 @@ export const QueryInput = ({ onSubmit: () => void; disableAutoFocus?: boolean; 'data-test-subj'?: string; + placeholder?: string; }) => { const { inputValue, handleInputChange } = useDebouncedValue({ value, onChange }); @@ -51,7 +53,8 @@ export const QueryInput = ({ } }} placeholder={ - inputValue.language === 'kuery' + placeholder ?? + (inputValue.language === 'kuery' ? i18n.translate('xpack.lens.indexPattern.filters.queryPlaceholderKql', { defaultMessage: '{example}', values: { example: 'method : "GET" or status : "404"' }, @@ -59,7 +62,7 @@ export const QueryInput = ({ : i18n.translate('xpack.lens.indexPattern.filters.queryPlaceholderLucene', { defaultMessage: '{example}', values: { example: 'method:GET OR status:404' }, - }) + })) } languageSwitcherPopoverAnchorPosition="rightDown" /> diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/time_shift_utils.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/time_shift_utils.tsx index b5c6acccac543..7dbad00c627a6 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/time_shift_utils.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/time_shift_utils.tsx @@ -13,13 +13,12 @@ import type { DatatableUtilitiesService } from '@kbn/data-plugin/common'; import { Datatable } from '@kbn/expressions-plugin/common'; import { search } from '@kbn/data-plugin/public'; import { parseTimeShift } from '@kbn/data-plugin/common'; -import { - IndexPattern, +import type { GenericIndexPatternColumn, IndexPatternLayer, IndexPatternPrivateState, } from './types'; -import { FramePublicAPI } from '../types'; +import type { FramePublicAPI, IndexPattern } from '../types'; export const timeShiftOptions = [ { @@ -187,12 +186,12 @@ export function getDisallowedPreviousShiftMessage( export function getStateTimeShiftWarningMessages( datatableUtilities: DatatableUtilitiesService, state: IndexPatternPrivateState, - { activeData }: FramePublicAPI + { activeData, dataViews }: FramePublicAPI ) { if (!state) return; const warningMessages: React.ReactNode[] = []; Object.entries(state.layers).forEach(([layerId, layer]) => { - const layerIndexPattern = state.indexPatterns[layer.indexPatternId]; + const layerIndexPattern = dataViews.indexPatterns[layer.indexPatternId]; if (!layerIndexPattern) { return; } diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/to_expression.ts b/x-pack/plugins/lens/public/indexpattern_datasource/to_expression.ts index c23cd07f866f3..e6113bf150970 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/to_expression.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/to_expression.ts @@ -22,10 +22,11 @@ import { } from '@kbn/expressions-plugin/public'; import { GenericIndexPatternColumn } from './indexpattern'; import { operationDefinitionMap } from './operations'; -import { IndexPattern, IndexPatternPrivateState, IndexPatternLayer } from './types'; +import { IndexPatternPrivateState, IndexPatternLayer } from './types'; import { DateHistogramIndexPatternColumn, RangeIndexPatternColumn } from './operations/definitions'; import { FormattedIndexPatternColumn } from './operations/definitions/column_types'; import { isColumnFormatted, isColumnOfType } from './operations/definitions/helpers'; +import type { IndexPattern, IndexPatternMap } from '../types'; export type OriginalColumn = { id: string } & GenericIndexPatternColumn; @@ -408,12 +409,13 @@ function sortedReferences(columns: Array; - meta?: boolean; - runtime?: boolean; -}; - export interface IndexPatternLayer { columnOrder: string[]; columns: Record; @@ -86,26 +66,20 @@ export type PersistedIndexPatternLayer = Omit; - indexPatternRefs: IndexPatternRef[]; - indexPatterns: Record; + // indexPatternRefs: IndexPatternRef[]; + // indexPatterns: Record; - /** - * indexPatternId -> fieldName -> boolean - */ - existingFields: Record>; - isFirstExistenceFetch: boolean; - existenceFetchFailed?: boolean; - existenceFetchTimeout?: boolean; + // /** + // * indexPatternId -> fieldName -> boolean + // */ + // existingFields: Record>; + // isFirstExistenceFetch: boolean; + // existenceFetchFailed?: boolean; + // existenceFetchTimeout?: boolean; isDimensionClosePrevented?: boolean; } -export interface IndexPatternRef { - id: string; - title: string; - name?: string; -} - export interface DataViewDragDropOperation extends DragDropOperation { dataView: IndexPattern; column?: GenericIndexPatternColumn; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/utils.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/utils.tsx index 85837b1010355..d67023a1a24be 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/utils.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/utils.tsx @@ -16,8 +16,8 @@ import { EuiLink, EuiTextColor, EuiButton, EuiSpacer } from '@elastic/eui'; import type { DatatableColumn } from '@kbn/expressions-plugin/common'; import { groupBy, escape } from 'lodash'; import type { Query } from '@kbn/data-plugin/common'; -import type { FramePublicAPI, StateSetter } from '../types'; -import type { IndexPattern, IndexPatternLayer, IndexPatternPrivateState } from './types'; +import type { FramePublicAPI, IndexPattern, StateSetter } from '../types'; +import type { IndexPatternLayer, IndexPatternPrivateState } from './types'; import type { ReferenceBasedIndexPatternColumn } from './operations/definitions/column_types'; import { @@ -162,7 +162,7 @@ const accuracyModeEnabledWarning = (columnName: string, docLink: string) => ( export function getPrecisionErrorWarningMessages( datatableUtilities: DatatableUtilitiesService, state: IndexPatternPrivateState, - { activeData }: FramePublicAPI, + { activeData, dataViews }: FramePublicAPI, docLinks: DocLinksStart, setState: StateSetter ) { @@ -181,7 +181,7 @@ export function getPrecisionErrorWarningMessages( const currentLayer = state.layers[layerId]; const currentColumn = currentLayer?.columns[column.id]; if (currentLayer && currentColumn && datatableUtilities.hasPrecisionError(column)) { - const indexPattern = state.indexPatterns[currentLayer.indexPatternId]; + const indexPattern = dataViews.indexPatterns[currentLayer.indexPatternId]; // currentColumnIsTerms is mostly a type guard. If there's a precision error, // we already know that we're dealing with a terms-based operation (at least for now). const currentColumnIsTerms = isColumnOfType( diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts index c66d538ed0511..b3c59056e63c6 100644 --- a/x-pack/plugins/lens/public/plugin.ts +++ b/x-pack/plugins/lens/public/plugin.ts @@ -12,6 +12,7 @@ import type { UsageCollectionSetup, UsageCollectionStart, } from '@kbn/usage-collection-plugin/public'; +import { Storage } from '@kbn/kibana-utils-plugin/public'; import type { DataPublicPluginSetup, DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { EmbeddableSetup, EmbeddableStart } from '@kbn/embeddable-plugin/public'; import { CONTEXT_MENU_TRIGGER } from '@kbn/embeddable-plugin/public'; @@ -279,11 +280,16 @@ export class LensPlugin { data: plugins.data, timefilter: plugins.data.query.timefilter.timefilter, expressionRenderer: plugins.expressions.ReactExpressionRenderer, - documentToExpression: this.editorFrameService!.documentToExpression, + documentToExpression: (doc) => + this.editorFrameService!.documentToExpression(doc, { + dataViews: plugins.dataViews, + storage: new Storage(localStorage), + uiSettings: core.uiSettings, + }), injectFilterReferences: data.query.filterManager.inject.bind(data.query.filterManager), visualizationMap, datasourceMap, - indexPatternService: plugins.dataViews, + dataViews: plugins.dataViews, uiActions: plugins.uiActions, usageCollection, inspector: plugins.inspector, diff --git a/x-pack/plugins/lens/public/shared_components/dataview_picker/dataview_picker.tsx b/x-pack/plugins/lens/public/shared_components/dataview_picker/dataview_picker.tsx new file mode 100644 index 0000000000000..b66a5f1d76545 --- /dev/null +++ b/x-pack/plugins/lens/public/shared_components/dataview_picker/dataview_picker.tsx @@ -0,0 +1,93 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import React, { useState } from 'react'; +import { EuiPopover, EuiPopoverTitle, EuiSelectableProps } from '@elastic/eui'; +import { ToolbarButton, ToolbarButtonProps } from '@kbn/kibana-react-plugin/public'; +import { DataViewsList } from '@kbn/unified-search-plugin/public'; +import { IndexPatternRef } from '../../types'; + +export type ChangeIndexPatternTriggerProps = ToolbarButtonProps & { + label: string; + title?: string; +}; + +export function ChangeIndexPattern({ + indexPatternRefs, + isMissingCurrent, + indexPatternId, + onChangeIndexPattern, + trigger, + selectableProps, +}: { + trigger: ChangeIndexPatternTriggerProps; + indexPatternRefs: IndexPatternRef[]; + isMissingCurrent?: boolean; + onChangeIndexPattern: (newId: string) => void; + indexPatternId?: string; + selectableProps?: EuiSelectableProps; +}) { + const [isPopoverOpen, setPopoverIsOpen] = useState(false); + + // be careful to only add color with a value, otherwise it will fallbacks to "primary" + const colorProp = isMissingCurrent + ? { + color: 'danger' as const, + } + : {}; + + const createTrigger = function () { + const { label, title, ...rest } = trigger; + return ( + setPopoverIsOpen(!isPopoverOpen)} + fullWidth + {...colorProp} + {...rest} + > + {label} + + ); + }; + + return ( + <> + setPopoverIsOpen(false)} + display="block" + panelPaddingSize="none" + ownFocus + > +
+ + {i18n.translate('xpack.lens.indexPattern.changeDataViewTitle', { + defaultMessage: 'Data view', + })} + + + { + onChangeIndexPattern(newId); + setPopoverIsOpen(false); + }} + currentDataViewId={indexPatternId} + selectableProps={selectableProps} + /> +
+
+ + ); +} diff --git a/x-pack/plugins/lens/public/shared_components/dataview_picker/helpers.ts b/x-pack/plugins/lens/public/shared_components/dataview_picker/helpers.ts new file mode 100644 index 0000000000000..4f53930fa4973 --- /dev/null +++ b/x-pack/plugins/lens/public/shared_components/dataview_picker/helpers.ts @@ -0,0 +1,29 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { IndexPattern } from '../../types'; + +/** + * Checks if the provided field contains data (works for meta field) + */ +export function fieldContainsData( + field: string, + indexPattern: IndexPattern, + existingFields: Record +) { + return ( + indexPattern.getFieldByName(field)?.type === 'document' || fieldExists(existingFields, field) + ); +} + +/** + * Performs an existence check on the existingFields data structure for the provided field. + * Does not work for meta fields. + */ +export function fieldExists(existingFields: Record, fieldName: string) { + return existingFields[fieldName]; +} diff --git a/x-pack/plugins/lens/public/shared_components/dataview_picker/index.ts b/x-pack/plugins/lens/public/shared_components/dataview_picker/index.ts new file mode 100644 index 0000000000000..4de03b2f8b92c --- /dev/null +++ b/x-pack/plugins/lens/public/shared_components/dataview_picker/index.ts @@ -0,0 +1,9 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { ChangeIndexPattern } from './dataview_picker'; +export { fieldExists, fieldContainsData } from './helpers'; diff --git a/x-pack/plugins/lens/public/shared_components/drag_drop_bucket/buckets.test.tsx b/x-pack/plugins/lens/public/shared_components/drag_drop_bucket/buckets.test.tsx new file mode 100644 index 0000000000000..aba0cbfc40a6e --- /dev/null +++ b/x-pack/plugins/lens/public/shared_components/drag_drop_bucket/buckets.test.tsx @@ -0,0 +1,79 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { mount, shallow } from 'enzyme'; +import { act } from 'react-dom/test-utils'; +import { EuiIcon } from '@elastic/eui'; +import { DragDropBuckets, DraggableBucketContainer } from './buckets'; + +jest.mock('@elastic/eui', () => { + const original = jest.requireActual('@elastic/eui'); + return { + ...original, + EuiDragDropContext: 'eui-drag-drop-context', + EuiDroppable: 'eui-droppable', + EuiDraggable: (props: any) => props.children(), // eslint-disable-line @typescript-eslint/no-explicit-any + }; +}); + +describe('buckets shared components', () => { + describe('DragDropBuckets', () => { + it('should call onDragEnd when dragging ended with reordered items', () => { + const items = [
first
,
second
,
third
]; + const defaultProps = { + items, + onDragStart: jest.fn(), + onDragEnd: jest.fn(), + droppableId: 'TEST_ID', + children: items, + }; + const instance = shallow(); + act(() => { + // simulate dragging ending + instance.props().onDragEnd({ source: { index: 0 }, destination: { index: 1 } }); + }); + + expect(defaultProps.onDragEnd).toHaveBeenCalledWith([ +
second
, +
first
, +
third
, + ]); + }); + }); + describe('DraggableBucketContainer', () => { + const defaultProps = { + isInvalid: false, + invalidMessage: 'invalid', + onRemoveClick: jest.fn(), + removeTitle: 'remove', + children:
popover
, + id: '0', + idx: 0, + }; + it('should render valid component', () => { + const instance = mount(); + const popover = instance.find('[data-test-subj="popover"]'); + expect(popover).toHaveLength(1); + }); + it('should render invalid component', () => { + const instance = mount(); + const iconProps = instance.find(EuiIcon).first().props(); + expect(iconProps.color).toEqual('danger'); + expect(iconProps.type).toEqual('alert'); + expect(iconProps.title).toEqual('invalid'); + }); + it('should call onRemoveClick when remove icon is clicked', () => { + const instance = mount(); + const removeIcon = instance + .find('[data-test-subj="lns-customBucketContainer-remove"]') + .first(); + removeIcon.simulate('click'); + expect(defaultProps.onRemoveClick).toHaveBeenCalled(); + }); + }); +}); diff --git a/x-pack/plugins/lens/public/shared_components/drag_drop_bucket/buckets.tsx b/x-pack/plugins/lens/public/shared_components/drag_drop_bucket/buckets.tsx new file mode 100644 index 0000000000000..0f4ba342348cf --- /dev/null +++ b/x-pack/plugins/lens/public/shared_components/drag_drop_bucket/buckets.tsx @@ -0,0 +1,160 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiPanel, + EuiButtonIcon, + EuiIcon, + EuiDragDropContext, + euiDragDropReorder, + EuiDraggable, + EuiDroppable, + EuiButtonEmpty, +} from '@elastic/eui'; + +export const NewBucketButton = ({ + label, + onClick, + ['data-test-subj']: dataTestSubj, + isDisabled, +}: { + label: string; + onClick: () => void; + 'data-test-subj'?: string; + isDisabled?: boolean; +}) => ( + + {label} + +); + +interface BucketContainerProps { + isInvalid?: boolean; + invalidMessage: string; + onRemoveClick: () => void; + removeTitle: string; + isNotRemovable?: boolean; + children: React.ReactNode; + dataTestSubj?: string; +} + +const BucketContainer = ({ + isInvalid, + invalidMessage, + onRemoveClick, + removeTitle, + children, + dataTestSubj, + isNotRemovable, +}: BucketContainerProps) => { + return ( + + + {/* Empty for spacing */} + + + + {children} + + + + + + ); +}; + +export const DraggableBucketContainer = ({ + id, + idx, + children, + ...bucketContainerProps +}: { + id: string; + idx: number; + children: React.ReactNode; +} & BucketContainerProps) => { + return ( + + {(provided) => {children}} + + ); +}; + +interface DraggableLocation { + droppableId: string; + index: number; +} + +export const DragDropBuckets = ({ + items, + onDragStart, + onDragEnd, + droppableId, + children, +}: { + items: any; // eslint-disable-line @typescript-eslint/no-explicit-any + onDragStart: () => void; + onDragEnd: (items: any) => void; // eslint-disable-line @typescript-eslint/no-explicit-any + droppableId: string; + children: React.ReactElement[]; +}) => { + const handleDragEnd = ({ + source, + destination, + }: { + source?: DraggableLocation; + destination?: DraggableLocation; + }) => { + if (source && destination) { + const newItems = euiDragDropReorder(items, source.index, destination.index); + onDragEnd(newItems); + } + }; + return ( + + + {children} + + + ); +}; diff --git a/x-pack/plugins/lens/public/shared_components/field_picker/field_picker.tsx b/x-pack/plugins/lens/public/shared_components/field_picker/field_picker.tsx index bde920446d468..eb33bb41e0373 100644 --- a/x-pack/plugins/lens/public/shared_components/field_picker/field_picker.tsx +++ b/x-pack/plugins/lens/public/shared_components/field_picker/field_picker.tsx @@ -30,7 +30,7 @@ const DEFAULT_COMBOBOX_WIDTH = 305; const COMBOBOX_PADDINGS = 90; const DEFAULT_FONT = '14px Inter'; -export function FieldPicker({ +export function FieldPicker({ selectedOptions, options, onChoose, diff --git a/x-pack/plugins/lens/public/shared_components/index.ts b/x-pack/plugins/lens/public/shared_components/index.ts index e3a9af00ad005..a4e6bf046b36d 100644 --- a/x-pack/plugins/lens/public/shared_components/index.ts +++ b/x-pack/plugins/lens/public/shared_components/index.ts @@ -11,6 +11,12 @@ export { LegendSettingsPopover } from './legend_settings_popover'; export { PalettePicker } from './palette_picker'; export { FieldPicker, LensFieldIcon, TruncatedLabel } from './field_picker'; export type { FieldOption, FieldOptionValue } from './field_picker'; +export { ChangeIndexPattern, fieldExists, fieldContainsData } from './dataview_picker'; +export { + NewBucketButton, + DraggableBucketContainer, + DragDropBuckets, +} from './drag_drop_bucket/buckets'; export { RangeInputField } from './range_input_field'; export { BucketAxisBoundsControl, diff --git a/x-pack/plugins/lens/public/state_management/context_middleware/index.ts b/x-pack/plugins/lens/public/state_management/context_middleware/index.ts index 90cd4ba27723e..7d1e6a0b48fba 100644 --- a/x-pack/plugins/lens/public/state_management/context_middleware/index.ts +++ b/x-pack/plugins/lens/public/state_management/context_middleware/index.ts @@ -23,11 +23,14 @@ import { onActiveDataChange } from '../lens_slice'; import { DatasourceMap } from '../../types'; function isTimeBased(state: LensState, datasourceMap: DatasourceMap) { - const { activeDatasourceId, datasourceStates } = state.lens; + const { activeDatasourceId, datasourceStates, dataViews } = state.lens; return Boolean( activeDatasourceId && datasourceStates[activeDatasourceId] && - datasourceMap[activeDatasourceId].isTimeBased?.(datasourceStates[activeDatasourceId].state) + datasourceMap[activeDatasourceId].isTimeBased?.( + datasourceStates[activeDatasourceId].state, + dataViews.indexPatterns + ) ); } diff --git a/x-pack/plugins/lens/public/state_management/index.ts b/x-pack/plugins/lens/public/state_management/index.ts index 7b9c345ff89f6..e41247557aa0a 100644 --- a/x-pack/plugins/lens/public/state_management/index.ts +++ b/x-pack/plugins/lens/public/state_management/index.ts @@ -33,6 +33,7 @@ export const { rollbackSuggestion, submitSuggestion, switchDatasource, + updateIndexPatterns, setToggleFullscreen, initEmpty, editVisualizationAction, diff --git a/x-pack/plugins/lens/public/state_management/init_middleware/load_initial.ts b/x-pack/plugins/lens/public/state_management/init_middleware/load_initial.ts index e25c57ac129c9..5f978ca9f3a58 100644 --- a/x-pack/plugins/lens/public/state_management/init_middleware/load_initial.ts +++ b/x-pack/plugins/lens/public/state_management/init_middleware/load_initial.ts @@ -12,8 +12,8 @@ import { setState, initEmpty, LensStoreDeps } from '..'; import { disableAutoApply, getPreloadedState } from '../lens_slice'; import { SharingSavedObjectProps } from '../../types'; import { LensEmbeddableInput, LensByReferenceInput } from '../../embeddable/embeddable'; -import { getInitialDatasourceId } from '../../utils'; -import { initializeDatasources } from '../../editor_frame_service/editor_frame'; +import { getInitialDatasourceId, getInitialDataViewsObject } from '../../utils'; +import { initializeSources } from '../../editor_frame_service/editor_frame'; import { LensAppServices } from '../../app_plugin/types'; import { getEditPath, getFullPath, LENS_EMBEDDABLE_TYPE } from '../../../common/constants'; import { Document } from '../../persistence'; @@ -102,21 +102,37 @@ export function loadInitial( getPreloadedState(storeDeps); const { attributeService, notifications, data, dashboardFeatureFlag } = lensServices; const { lens } = store.getState(); + + const loaderSharedArgs = { + dataViews: lensServices.dataViews, + storage: lensServices.storage, + defaultIndexPatternId: lensServices.uiSettings.get('defaultIndex'), + }; + if ( !initialInput || (attributeService.inputIsRefType(initialInput) && initialInput.savedObjectId === lens.persistedDoc?.savedObjectId) ) { - return initializeDatasources(datasourceMap, lens.datasourceStates, undefined, initialContext, { - isFullEditor: true, - }) - .then((result) => { + return initializeSources( + { + datasourceMap, + datasourceStates: lens.datasourceStates, + initialContext, + ...loaderSharedArgs, + }, + { + isFullEditor: true, + } + ) + .then(({ states, indexPatterns, indexPatternRefs }) => { store.dispatch( initEmpty({ newState: { ...emptyState, + dataViews: getInitialDataViewsObject(indexPatterns, indexPatternRefs), searchSessionId: data.search.session.getSessionId() || data.search.session.start(), - datasourceStates: Object.entries(result).reduce( + datasourceStates: Object.entries(states).reduce( (state, [datasourceId, datasourceState]) => ({ ...state, [datasourceId]: { @@ -141,6 +157,40 @@ export function loadInitial( }); redirectCallback(); }); + // return initializeDatasources(datasourceMap, lens.datasourceStates, undefined, initialContext, { + // isFullEditor: true, + // }) + // .then((result) => { + // store.dispatch( + // initEmpty({ + // newState: { + // ...emptyState, + // searchSessionId: data.search.session.getSessionId() || data.search.session.start(), + // datasourceStates: Object.entries(result).reduce( + // (state, [datasourceId, datasourceState]) => ({ + // ...state, + // [datasourceId]: { + // ...datasourceState, + // isLoading: false, + // }, + // }), + // {} + // ), + // isLoading: false, + // }, + // initialContext, + // }) + // ); + // if (autoApplyDisabled) { + // store.dispatch(disableAutoApply()); + // } + // }) + // .catch((e: { message: string }) => { + // notifications.toasts.addDanger({ + // title: e.message, + // }); + // redirectCallback(); + // }); } getPersisted({ initialInput, lensServices, history }) @@ -171,16 +221,28 @@ export function loadInitial( // Don't overwrite any pinned filters data.query.filterManager.setAppFilters(filters); - initializeDatasources( - datasourceMap, - docDatasourceStates, - doc.references, - initialContext, + // initializeDatasources( + // datasourceMap, + // docDatasourceStates, + // doc.references, + // initialContext, + // { + // isFullEditor: true, + // } + // ) + initializeSources( { - isFullEditor: true, - } + datasourceMap, + datasourceStates: docDatasourceStates, + references: doc.references, + initialContext, + dataViews: lensServices.dataViews, + storage: lensServices.storage, + defaultIndexPatternId: lensServices.uiSettings.get('defaultIndex'), + }, + { isFullEditor: true } ) - .then((result) => { + .then(({ states, indexPatterns, indexPatternRefs }) => { const currentSessionId = data.search.session.getSessionId(); store.dispatch( setState({ @@ -201,7 +263,8 @@ export function loadInitial( activeId: doc.visualizationType, state: doc.state.visualization, }, - datasourceStates: Object.entries(result).reduce( + dataViews: getInitialDataViewsObject(indexPatterns, indexPatternRefs), + datasourceStates: Object.entries(states).reduce( (state, [datasourceId, datasourceState]) => ({ ...state, [datasourceId]: { diff --git a/x-pack/plugins/lens/public/state_management/lens_slice.ts b/x-pack/plugins/lens/public/state_management/lens_slice.ts index 2a71cd9aaab48..6de39cb5d9681 100644 --- a/x-pack/plugins/lens/public/state_management/lens_slice.ts +++ b/x-pack/plugins/lens/public/state_management/lens_slice.ts @@ -15,7 +15,7 @@ import { getDatasourceLayers } from '../editor_frame_service/editor_frame'; import { TableInspectorAdapter } from '../editor_frame_service/types'; import type { VisualizeEditorContext, Suggestion } from '../types'; import { getInitialDatasourceId, getResolvedDateRange, getRemoveOperation } from '../utils'; -import { LensAppState, LensStoreDeps, VisualizationState } from './types'; +import { DataViewsState, LensAppState, LensStoreDeps, VisualizationState } from './types'; import { Datasource, Visualization } from '../types'; import { generateId } from '../id_generator'; import type { LayerType } from '../../common/types'; @@ -39,6 +39,12 @@ export const initialState: LensAppState = { state: null, activeId: null, }, + dataViews: { + indexPatternRefs: [], + indexPatterns: {}, + existingFields: {}, + isFirstExistenceFetch: false, + }, }; export const getPreloadedState = ({ @@ -163,6 +169,17 @@ export const setLayerDefaultDimension = createAction<{ groupId: string; }>('lens/setLayerDefaultDimension'); +export const updateIndexPatterns = createAction>( + 'lens/updateIndexPatterns' +); +export const changeIndexPattern = createAction<{ + visualizationIds?: string[]; + datasourceIds?: string[]; + indexPatternId: string; + layerId?: string; + dataViews: Partial; +}>('lens/changeIndexPattern'); + export const lensActions = { setState, onActiveDataChange, @@ -188,6 +205,8 @@ export const lensActions = { removeOrClearLayer, addLayer, setLayerDefaultDimension, + updateIndexPatterns, + changeIndexPattern, }; export const makeLensReducer = (storeDeps: LensStoreDeps) => { @@ -282,6 +301,68 @@ export const makeLensReducer = (storeDeps: LensStoreDeps) => { ? activeVisualization.clearLayer(state.visualization.state, layerId) : activeVisualization.removeLayer(state.visualization.state, layerId); }, + [changeIndexPattern.type]: ( + state, + { + payload, + }: { + payload: { + visualizationIds?: string; + datasourceIds?: string; + layerId?: string; + indexPatternId: string; + dataViews: Pick; + }; + } + ) => { + const { visualizationIds, datasourceIds, layerId, indexPatternId, dataViews } = payload; + const newState: Partial = { dataViews: { ...state.dataViews, ...dataViews } }; + if (visualizationIds?.length) { + for (const visualizationId of visualizationIds) { + const activeVisualization = + visualizationId && + state.visualization.activeId !== visualizationId && + visualizationMap[visualizationId]; + if (activeVisualization && layerId && activeVisualization?.onIndexPatternChange) { + newState.visualization = { + ...state.visualization, + state: activeVisualization.onIndexPatternChange( + state.visualization.state, + layerId, + indexPatternId + ), + }; + } + } + } + if (datasourceIds?.length) { + newState.datasourceStates = { ...state.datasourceStates }; + for (const datasourceId of datasourceIds) { + const activeDatasource = datasourceId && datasourceMap[datasourceId]; + if (activeDatasource && activeDatasource?.onIndexPatternChange) { + newState.datasourceStates = { + ...newState.datasourceStates, + [datasourceId]: { + isLoading: false, + state: activeDatasource.onIndexPatternChange( + newState.datasourceStates[datasourceId].state, + dataViews.indexPatterns, + indexPatternId, + layerId + ), + }, + }; + } + } + } + return { ...state, ...newState }; + }, + [updateIndexPatterns.type]: (state, { payload }: { payload: Partial }) => { + return { + ...state, + dataViews: { ...state.dataViews, ...payload }, + }; + }, [updateDatasourceState.type]: ( state, { @@ -449,6 +530,7 @@ export const makeLensReducer = (storeDeps: LensStoreDeps) => { datasourceStates: newState.datasourceStates, visualizationMap, visualizeTriggerFieldContext: payload.initialContext, + dataViews: newState.dataViews, }); if (suggestion) { return { @@ -635,6 +717,7 @@ export const makeLensReducer = (storeDeps: LensStoreDeps) => { : undefined, datasourceLayers: getDatasourceLayers(state.datasourceStates, datasourceMap), dateRange: current(state.resolvedDateRange), + dataViews: current(state.dataViews), }; const activeDatasource = datasourceMap[state.activeDatasourceId]; @@ -694,6 +777,7 @@ export const makeLensReducer = (storeDeps: LensStoreDeps) => { : undefined, datasourceLayers: getDatasourceLayers(state.datasourceStates, datasourceMap), dateRange: current(state.resolvedDateRange), + dataViews: current(state.dataViews), }, activeVisualization, activeDatasource, @@ -751,10 +835,15 @@ function addInitialValueIfAvailable({ if (!noDatasource && activeDatasource?.initializeDimension) { return { - activeDatasourceState: activeDatasource.initializeDimension(datasourceState, layerId, { - ...info, - columnId: columnId || info.columnId, - }), + activeDatasourceState: activeDatasource.initializeDimension( + datasourceState, + layerId, + framePublicAPI.dataViews.indexPatterns, + { + ...info, + columnId: columnId || info.columnId, + } + ), activeVisualizationState, }; } else { diff --git a/x-pack/plugins/lens/public/state_management/selectors.ts b/x-pack/plugins/lens/public/state_management/selectors.ts index 9217c66863c5c..a7e7a55e39592 100644 --- a/x-pack/plugins/lens/public/state_management/selectors.ts +++ b/x-pack/plugins/lens/public/state_management/selectors.ts @@ -27,6 +27,7 @@ export const selectChangesApplied = (state: LensState) => export const selectDatasourceStates = (state: LensState) => state.lens.datasourceStates; export const selectActiveDatasourceId = (state: LensState) => state.lens.activeDatasourceId; export const selectActiveData = (state: LensState) => state.lens.activeData; +export const selectDataViews = (state: LensState) => state.lens.dataViews; export const selectIsFullscreenDatasource = (state: LensState) => Boolean(state.lens.isFullscreenDatasource); @@ -165,12 +166,14 @@ export const selectFramePublicAPI = createSelector( selectActiveData, selectInjectedDependencies as SelectInjectedDependenciesFunction, selectResolvedDateRange, + selectDataViews, ], - (datasourceStates, activeData, datasourceMap, dateRange) => { + (datasourceStates, activeData, datasourceMap, dateRange, dataViews) => { return { datasourceLayers: getDatasourceLayers(datasourceStates, datasourceMap), activeData, dateRange, + dataViews, }; } ); diff --git a/x-pack/plugins/lens/public/state_management/types.ts b/x-pack/plugins/lens/public/state_management/types.ts index 4c6f4adc59ff5..7b461338261fc 100644 --- a/x-pack/plugins/lens/public/state_management/types.ts +++ b/x-pack/plugins/lens/public/state_management/types.ts @@ -11,7 +11,7 @@ import { Filter, Query } from '@kbn/es-query'; import { SavedQuery } from '@kbn/data-plugin/public'; import { Document } from '../persistence'; -import { TableInspectorAdapter } from '../editor_frame_service/types'; +import type { TableInspectorAdapter } from '../editor_frame_service/types'; import { DateRange } from '../../common'; import { LensAppServices } from '../app_plugin/types'; import { @@ -19,13 +19,24 @@ import { VisualizationMap, SharingSavedObjectProps, VisualizeEditorContext, + IndexPattern, + IndexPatternRef, } from '../types'; export interface VisualizationState { activeId: string | null; state: unknown; } -export type DatasourceStates = Record; +export interface DataViewsState { + indexPatternRefs: IndexPatternRef[]; + indexPatterns: Record; + existingFields: Record>; + isFirstExistenceFetch: boolean; + existenceFetchFailed?: boolean; + existenceFetchTimeout?: boolean; +} + +export type DatasourceStates = Record; export interface PreviewState { visualization: VisualizationState; datasourceStates: DatasourceStates; @@ -53,6 +64,8 @@ export interface LensAppState extends EditorFrameState { searchSessionId: string; resolvedDateRange: DateRange; sharingSavedObjectProps?: Omit; + // Dataview/Indexpattern management has moved in here from datasource + dataViews: DataViewsState; } export type DispatchSetState = (state: Partial) => { diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index 215c0ef273cbb..192d076063941 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -4,13 +4,13 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { Ast } from '@kbn/interpreter'; +import type { Ast } from '@kbn/interpreter'; import type { IconType } from '@elastic/eui/src/components/icon/icon'; import type { CoreSetup, SavedObjectReference, ResolvedSimpleSavedObject } from '@kbn/core/public'; import type { PaletteOutput } from '@kbn/coloring'; import type { TopNavMenuData } from '@kbn/navigation-plugin/public'; import type { MutableRefObject } from 'react'; -import { Filter, TimeRange } from '@kbn/es-query'; +import type { Filter, TimeRange } from '@kbn/es-query'; import type { ExpressionAstExpression, ExpressionRendererEvent, @@ -24,8 +24,11 @@ import type { RowClickContext, VisualizeFieldContext, } from '@kbn/ui-actions-plugin/public'; -import { ClickTriggerEvent, BrushTriggerEvent } from '@kbn/charts-plugin/public'; -import { DraggingIdentifier, DragDropIdentifier, DragContextState } from './drag_drop'; +import type { ClickTriggerEvent, BrushTriggerEvent } from '@kbn/charts-plugin/public'; +import type { IndexPatternAggRestrictions } from '@kbn/data-plugin/public'; +import type { FieldSpec } from '@kbn/data-views-plugin/common'; +import type { FieldFormatParams } from '@kbn/field-formats-plugin/common'; +import type { DraggingIdentifier, DragDropIdentifier, DragContextState } from './drag_drop'; import type { DateRange, LayerType, SortingHint } from '../common'; import type { LensSortActionData, @@ -41,21 +44,57 @@ import { LENS_EDIT_PAGESIZE_ACTION, } from './datatable_visualization/components/constants'; import type { LensInspector } from './lens_inspector_service'; +import { DataViewsState } from './state_management/types'; +import { IndexPatternServiceAPI } from './data_views_service/service'; + +export interface IndexPatternRef { + id: string; + title: string; + name?: string; +} + +export interface IndexPattern { + id: string; + fields: IndexPatternField[]; + getFieldByName(name: string): IndexPatternField | undefined; + title: string; + name?: string; + timeFieldName?: string; + fieldFormatMap?: Record< + string, + { + id: string; + params: FieldFormatParams; + } + >; + hasRestrictions: boolean; +} + +export type IndexPatternField = FieldSpec & { + displayName: string; + aggregationRestrictions?: Partial; + meta?: boolean; + runtime?: boolean; +}; export type ErrorCallback = (e: { message: string }) => void; export interface PublicAPIProps { state: T; layerId: string; + indexPatterns: IndexPatternMap; } export interface EditorFrameProps { showNoDataPopover: () => void; lensInspector: LensInspector; + indexPatternService: IndexPatternServiceAPI; } export type VisualizationMap = Record; export type DatasourceMap = Record; +export type IndexPatternMap = Record; +export type ExistingFieldsMap = Record>; export interface EditorFrameInstance { EditorFrameContainer: (props: EditorFrameProps) => React.ReactElement; @@ -206,6 +245,7 @@ export interface GetDropPropsArgs { prioritizedOperation?: string; isNewColumn?: boolean; }; + indexPatterns: IndexPatternMap; } /** @@ -221,8 +261,10 @@ export interface Datasource { state?: P, savedObjectReferences?: SavedObjectReference[], initialContext?: VisualizeFieldContext | VisualizeEditorContext, + indexPatternRefs?: IndexPatternRef[], + indexPatterns?: IndexPatternMap, options?: InitializationOptions - ) => Promise; + ) => T; // Given the current state, which parts should be saved? getPersistableState: (state: T) => { state: P; savedObjectReferences: SavedObjectReference[] }; @@ -232,10 +274,16 @@ export interface Datasource { removeLayer: (state: T, layerId: string) => T; clearLayer: (state: T, layerId: string) => T; getLayers: (state: T) => string[]; - removeColumn: (props: { prevState: T; layerId: string; columnId: string }) => T; + removeColumn: (props: { + prevState: T; + layerId: string; + columnId: string; + indexPatterns: IndexPatternMap; + }) => T; initializeDimension?: ( state: T, layerId: string, + indexPatterns: IndexPatternMap, value: { columnId: string; groupId: string; @@ -283,33 +331,50 @@ export interface Datasource { state: T; setState: StateSetter; }) => void; + onIndexPatternChange?: ( + state: T, + indexPatterns: IndexPatternMap, + indexPatternId: string, + layerId?: string + ) => T; - refreshIndexPatternsList?: (props: { indexPatternId: string; setState: StateSetter }) => void; + onRefreshIndexPattern: () => void; - toExpression: (state: T, layerId: string) => ExpressionAstExpression | string | null; + toExpression: ( + state: T, + layerId: string, + indexPatterns: IndexPatternMap + ) => ExpressionAstExpression | string | null; getDatasourceSuggestionsForField: ( state: T, field: unknown, - filterFn: (layerId: string) => boolean + filterFn: (layerId: string) => boolean, + indexPatterns: IndexPatternMap ) => Array>; getDatasourceSuggestionsForVisualizeCharts: ( state: T, - context: VisualizeEditorLayersContext[] + context: VisualizeEditorLayersContext[], + indexPatterns: IndexPatternMap ) => Array>; getDatasourceSuggestionsForVisualizeField: ( state: T, indexPatternId: string, - fieldName: string + fieldName: string, + indexPatterns: IndexPatternMap ) => Array>; getDatasourceSuggestionsFromCurrentState: ( state: T, + indexPatterns: IndexPatternMap, filterFn?: (layerId: string) => boolean, activeData?: Record ) => Array>; getPublicAPI: (props: PublicAPIProps) => DatasourcePublicAPI; - getErrorMessages: (state: T) => + getErrorMessages: ( + state: T, + indexPatterns: Record + ) => | Array<{ shortMessage: string; longMessage: React.ReactNode; @@ -323,7 +388,7 @@ export interface Datasource { /** * Check the internal state integrity and returns a list of missing references */ - checkIntegrity: (state: T) => string[]; + checkIntegrity: (state: T, indexPatterns: IndexPatternMap) => string[]; /** * The frame calls this function to display warnings about visualization */ @@ -335,11 +400,16 @@ export interface Datasource { /** * Checks if the visualization created is time based, for example date histogram */ - isTimeBased: (state: T) => boolean; + isTimeBased: (state: T, indexPatterns: IndexPatternMap) => boolean; /** * Given the current state layer and a columnId will verify if the column configuration has errors */ - isValidColumn: (state: T, layerId: string, columnId: string) => boolean; + isValidColumn: ( + state: T, + indexPatterns: IndexPatternMap, + layerId: string, + columnId: string + ) => boolean; /** * Are these datasources equivalent? */ @@ -414,7 +484,10 @@ export interface DatasourceDataPanelProps { filters: Filter[]; dropOntoWorkspace: (field: DragDropIdentifier) => void; hasSuggestionForField: (field: DragDropIdentifier) => boolean; + onChangeIndexPattern: (indexPatternId: string, datasourceId: string, layerId?: string) => void; uiActions: UiActionsStart; + indexPatternService: IndexPatternServiceAPI; + frame: FramePublicAPI; } interface SharedDimensionProps { @@ -437,6 +510,8 @@ export type DatasourceDimensionProps = SharedDimensionProps & { onRemove?: (accessor: string) => void; state: T; activeData?: Record; + indexPatterns: IndexPatternMap; + existingFields: Record>; hideTooltip?: boolean; invalid?: boolean; invalidMessage?: string; @@ -473,6 +548,8 @@ export interface DatasourceLayerPanelProps { state: T; setState: StateSetter; activeData?: Record; + dataViews: DataViewsState; + onChangeIndexPattern: (indexPatternId: string, datasourceId: string, layerId?: string) => void; } export interface DragDropOperation { @@ -506,6 +583,7 @@ export interface DatasourceDimensionDropProps { export type DatasourceDimensionDropHandlerProps = DatasourceDimensionDropProps & { source: DragDropIdentifier; dropType: DropType; + indexPatterns: IndexPatternMap; }; export type FieldOnlyDataType = @@ -562,6 +640,11 @@ export interface VisualizationConfigProps { export type VisualizationLayerWidgetProps = VisualizationConfigProps & { setState: (newState: T) => void; + onChangeIndexPattern: (indexPatternId: string, layerId: string) => void; +}; + +export type VisualizationLayerHeaderContentProps = VisualizationLayerWidgetProps & { + defaultIndexPatternId: string; }; export interface VisualizationToolbarProps { @@ -727,6 +810,7 @@ export interface FramePublicAPI { * If accessing, make sure to check whether expected columns actually exist. */ activeData?: Record; + dataViews: DataViewsState; } export interface FrameDatasourceAPI extends FramePublicAPI { @@ -786,6 +870,15 @@ export interface Visualization { */ initialize: (addNewLayer: () => string, state?: T, mainPalette?: PaletteOutput) => T; + /** + * Retrieve the used indexpatterns in the visualization + */ + getUsedIndexPatterns?: ( + state?: T, + indexPatternRefs?: IndexPatternRef[], + savedObjectReferences?: SavedObjectReference[] + ) => { usedPatterns: string[] }; + getMainPalette?: (state: T) => undefined | PaletteOutput; /** @@ -816,7 +909,7 @@ export interface Visualization { /** Optional, if the visualization supports multiple layers */ removeLayer?: (state: T, layerId: string) => T; /** Track added layers in internal state */ - appendLayer?: (state: T, layerId: string, type: LayerType) => T; + appendLayer?: (state: T, layerId: string, type: LayerType, indexPatternId?: string) => T; /** Retrieve a list of supported layer types with initialization data */ getSupportedLayers: ( @@ -847,13 +940,22 @@ export interface Visualization { }; /** - * Header rendered as layer title This can be used for both static and dynamic content lioke + * Header rendered as layer title. This can be used for both static and dynamic content like * for extra configurability, such as for switch chart type */ renderLayerHeader?: ( domElement: Element, props: VisualizationLayerWidgetProps ) => ((cleanupElement: Element) => void) | void; + + /** + * Layer panel content rendered. This can be used to render a custom content below the title, + * like a custom dataview switch + */ + renderLayerPanel?: ( + domElement: Element, + props: VisualizationLayerWidgetProps + ) => ((cleanupElement: Element) => void) | void; /** * Toolbar rendered above the visualization. This is meant to be used to provide chart-level * settings for the visualization. @@ -971,6 +1073,11 @@ export interface Visualization { */ onEditAction?: (state: T, event: LensEditEvent) => T; + /** + * Some visualization track indexPattern changes (i.e. annotations) + * This method makes it aware of the change and produces a new updated state + */ + onIndexPatternChange?: (state: T, indexPatternId: string, layerId?: string) => T; /** * Gets custom display options for showing the visualization. */ diff --git a/x-pack/plugins/lens/public/utils.ts b/x-pack/plugins/lens/public/utils.ts index 4b0f24ce05835..13ff13b6ab93b 100644 --- a/x-pack/plugins/lens/public/utils.ts +++ b/x-pack/plugins/lens/public/utils.ts @@ -14,8 +14,15 @@ import type { DataView, DataViewsContract } from '@kbn/data-views-plugin/public' import type { DatatableUtilitiesService } from '@kbn/data-plugin/common'; import { BrushTriggerEvent, ClickTriggerEvent } from '@kbn/charts-plugin/public'; import type { Document } from './persistence/saved_object_store'; -import type { Datasource, DatasourceMap, Visualization, StateSetter } from './types'; +import type { + Datasource, + DatasourceMap, + Visualization, + IndexPatternMap, + IndexPatternRef, +} from './types'; import type { DatasourceStates, VisualizationState } from './state_management'; +import { IndexPatternServiceAPI } from './data_views_service/service'; export function getVisualizeGeoFieldMessage(fieldType: string) { return i18n.translate('xpack.lens.visualizeGeoFieldMessage', { @@ -58,43 +65,69 @@ export const getInitialDatasourceId = (datasourceMap: DatasourceMap, doc?: Docum return (doc && getActiveDatasourceIdFromDoc(doc)) || Object.keys(datasourceMap)[0] || null; }; -export function handleIndexPatternChange({ - activeDatasources, - datasourceStates, - indexPatternId, - setDatasourceState, -}: { - activeDatasources: Record; - datasourceStates: DatasourceStates; - indexPatternId: string; - setDatasourceState: StateSetter; -}): void { - Object.entries(activeDatasources).forEach(([id, datasource]) => { - datasource?.updateCurrentIndexPatternId?.({ - state: datasourceStates[id].state, - indexPatternId, - setState: setDatasourceState, - }); - }); +export function getInitialDataViewsObject( + indexPatterns: IndexPatternMap, + indexPatternRefs: IndexPatternRef[] +) { + return { + indexPatterns, + indexPatternRefs, + existingFields: {}, + isFirstExistenceFetch: true, + }; } -export function refreshIndexPatternsList({ +export async function refreshIndexPatternsList({ activeDatasources, + indexPatternService, indexPatternId, - setDatasourceState, + indexPatternsCache, }: { + indexPatternService: IndexPatternServiceAPI; activeDatasources: Record; indexPatternId: string; - setDatasourceState: StateSetter; -}): void { - Object.entries(activeDatasources).forEach(([id, datasource]) => { - datasource?.refreshIndexPatternsList?.({ - indexPatternId, - setState: setDatasourceState, - }); + indexPatternsCache: IndexPatternMap; +}) { + // collect all the onRefreshIndex callbacks from datasources + const onRefreshCallbacks = Object.values(activeDatasources) + .map((datasource) => datasource?.onRefreshIndexPattern) + .filter(Boolean); + + const [newlyMappedIndexPattern, indexPatternRefs] = await Promise.all([ + indexPatternService.loadIndexPatterns({ + cache: {}, + patterns: [indexPatternId], + onIndexPatternRefresh: () => onRefreshCallbacks.forEach((fn) => fn()), + }), + indexPatternService.loadIndexPatternRefs({ isFullEditor: true }), + ]); + const indexPattern = newlyMappedIndexPattern[indexPatternId]; + indexPatternService.updateIndexPatternsCache({ + indexPatterns: { + ...indexPatternsCache, + [indexPatternId]: indexPattern, + }, + indexPatternRefs, }); } +// export function refreshIndexPatternsList({ +// activeDatasources, +// indexPatternId, +// setDatasourceState, +// }: { +// activeDatasources: Record; +// indexPatternId: string; +// setDatasourceState: StateSetter; +// }): void { +// Object.entries(activeDatasources).forEach(([id, datasource]) => { +// datasource?.refreshIndexPatternsList?.({ +// indexPatternId, +// setState: setDatasourceState, +// }); +// }); +// } + export function getIndexPatternsIds({ activeDatasources, datasourceStates, @@ -121,9 +154,9 @@ export function getIndexPatternsIds({ export async function getIndexPatternsObjects( ids: string[], - indexPatternsService: DataViewsContract + dataViews: DataViewsContract ): Promise<{ indexPatterns: DataView[]; rejectedIds: string[] }> { - const responses = await Promise.allSettled(ids.map((id) => indexPatternsService.get(id))); + const responses = await Promise.allSettled(ids.map((id) => dataViews.get(id))); const fullfilled = responses.filter( (response): response is PromiseFulfilledResult => response.status === 'fulfilled' ); diff --git a/x-pack/plugins/lens/public/xy_visualization/index.ts b/x-pack/plugins/lens/public/xy_visualization/index.ts index a7de5374c92d1..30c58d2a728b7 100644 --- a/x-pack/plugins/lens/public/xy_visualization/index.ts +++ b/x-pack/plugins/lens/public/xy_visualization/index.ts @@ -6,6 +6,7 @@ */ import type { CoreSetup } from '@kbn/core/public'; +import { Storage } from '@kbn/kibana-utils-plugin/public'; import { EventAnnotationPluginSetup } from '@kbn/event-annotation-plugin/public'; import type { ExpressionsSetup } from '@kbn/expressions-plugin/public'; import type { ChartsPluginSetup } from '@kbn/charts-plugin/public'; @@ -29,12 +30,15 @@ export class XyVisualization { ) { editorFrame.registerVisualization(async () => { const { getXyVisualization } = await import('../async_services'); - const [, { charts, data, fieldFormats, eventAnnotation }] = await core.getStartServices(); + const [coreStart, { data, charts, fieldFormats, eventAnnotation }] = + await core.getStartServices(); const palettes = await charts.palettes.getPalettes(); const eventAnnotationService = await eventAnnotation.getService(); const useLegacyTimeAxis = core.uiSettings.get(LEGACY_TIME_AXIS); return getXyVisualization({ - datatableUtilities: data.datatableUtilities, + core: coreStart, + data, + storage: new Storage(localStorage), paletteService: palettes, eventAnnotationService, fieldFormats, diff --git a/x-pack/plugins/lens/public/xy_visualization/types.ts b/x-pack/plugins/lens/public/xy_visualization/types.ts index 58c4cfb7e26c4..a763053d72475 100644 --- a/x-pack/plugins/lens/public/xy_visualization/types.ts +++ b/x-pack/plugins/lens/public/xy_visualization/types.ts @@ -113,6 +113,8 @@ export interface XYAnnotationLayerConfig { layerId: string; layerType: 'annotations'; annotations: EventAnnotationConfig[]; + hide?: boolean; + indexPatternId: string; simpleView?: boolean; } diff --git a/x-pack/plugins/lens/public/xy_visualization/visualization.tsx b/x-pack/plugins/lens/public/xy_visualization/visualization.tsx index 2eb15c96afe95..aedbcbb648957 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visualization.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/visualization.tsx @@ -11,13 +11,14 @@ import { Position } from '@elastic/charts'; import { FormattedMessage, I18nProvider } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import type { PaletteRegistry } from '@kbn/coloring'; -import type { DatatableUtilitiesService } from '@kbn/data-plugin/common'; import { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; -import { ThemeServiceStart } from '@kbn/core/public'; +import { CoreStart, ThemeServiceStart } from '@kbn/core/public'; import { EventAnnotationServiceType } from '@kbn/event-annotation-plugin/public'; import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; import { VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public'; import { FillStyle } from '@kbn/expression-xy-plugin/common'; +import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import { getSuggestions } from './xy_suggestions'; import { XyToolbar } from './xy_config_panel'; import { DimensionEditor } from './xy_config_panel/dimension_editor'; @@ -79,14 +80,18 @@ import { defaultAnnotationLabel } from './annotations/helpers'; import { onDropForVisualization } from '../editor_frame_service/editor_frame/config_panel/buttons/drop_targets_utils'; export const getXyVisualization = ({ - datatableUtilities, + core, + storage, + data, paletteService, fieldFormats, useLegacyTimeAxis, kibanaTheme, eventAnnotationService, }: { - datatableUtilities: DatatableUtilitiesService; + core: CoreStart; + storage: IStorageWrapper; + data: DataPublicPluginStart; paletteService: PaletteRegistry; eventAnnotationService: EventAnnotationServiceType; fieldFormats: FieldFormatsStart; @@ -116,7 +121,7 @@ export const getXyVisualization = ({ }; }, - appendLayer(state, layerId, layerType) { + appendLayer(state, layerId, layerType, indexPatternId) { const firstUsedSeriesType = getDataLayers(state.layers)?.[0]?.seriesType; return { ...state, @@ -126,6 +131,7 @@ export const getXyVisualization = ({ seriesType: firstUsedSeriesType || state.preferredSeriesType, layerId, layerType, + indexPatternId: indexPatternId ?? core.uiSettings.get('defaultIndex'), }), ], }; @@ -137,7 +143,11 @@ export const getXyVisualization = ({ layers: state.layers.map((l) => l.layerId !== layerId ? l - : newLayerState({ seriesType: state.preferredSeriesType, layerId }) + : newLayerState({ + seriesType: state.preferredSeriesType, + layerId, + indexPatternId: core.uiSettings.get('defaultIndex'), + }) ), }; }, @@ -189,6 +199,20 @@ export const getXyVisualization = ({ ]; }, + onIndexPatternChange(state, indexPatternId, layerId) { + const layerIndex = state.layers.findIndex((l) => l.layerId === layerId); + const layer = state.layers[layerIndex]; + if (!layer || !isAnnotationsLayer(layer)) { + return state; + } + const newLayers = [...state.layers]; + newLayers[layerIndex] = { ...layer, indexPatternId }; + return { + ...state, + layers: newLayers, + }; + }, + getConfiguration({ state, frame, layerId }) { const layer = state.layers.find((l) => l.layerId === layerId); if (!layer) { @@ -520,7 +544,7 @@ export const getXyVisualization = ({ renderDimensionEditor(domElement, props) { const allProps = { ...props, - datatableUtilities, + datatableUtilities: data.datatableUtilities, formatFactory: fieldFormats.deserialize, paletteService, }; diff --git a/x-pack/plugins/lens/public/xy_visualization/visualization_helpers.tsx b/x-pack/plugins/lens/public/xy_visualization/visualization_helpers.tsx index e89edab464bfd..fb364d7bd09f7 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visualization_helpers.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/visualization_helpers.tsx @@ -277,10 +277,17 @@ const newLayerFn = { layerType: layerTypes.REFERENCELINE, accessors: [], }), - [layerTypes.ANNOTATIONS]: ({ layerId }: { layerId: string }): XYAnnotationLayerConfig => ({ + [layerTypes.ANNOTATIONS]: ({ + layerId, + indexPatternId, + }: { + layerId: string; + indexPatternId: string; + }): XYAnnotationLayerConfig => ({ layerId, layerType: layerTypes.ANNOTATIONS, annotations: [], + indexPatternId, }), }; @@ -288,12 +295,14 @@ export function newLayerState({ layerId, layerType = layerTypes.DATA, seriesType, + indexPatternId, }: { layerId: string; layerType?: LayerType; seriesType: SeriesType; + indexPatternId: string; }) { - return newLayerFn[layerType]({ layerId, seriesType }); + return newLayerFn[layerType]({ layerId, seriesType, indexPatternId }); } export function getLayersByType(state: State, byType?: string) { From 751c2a2fcedfe145433869be200dc29dc1d5ddae Mon Sep 17 00:00:00 2001 From: dej611 Date: Wed, 27 Jul 2022 18:05:11 +0200 Subject: [PATCH 02/27] :fire: Remove some old cruft from the code --- .../lens/public/app_plugin/lens_top_nav.tsx | 2 +- .../init_middleware/load_initial.ts | 43 ------------------- x-pack/plugins/lens/public/types.ts | 9 ---- x-pack/plugins/lens/public/utils.ts | 17 -------- 4 files changed, 1 insertion(+), 70 deletions(-) diff --git a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx index 98acf40dedb74..12c8c76306246 100644 --- a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx +++ b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx @@ -694,7 +694,7 @@ export const LensTopNavMenu = ({ closeDataViewEditor.current = dataViewEditor.openEditor({ onSave: async (dataView) => { if (dataView.id) { - await dispatchChangeIndexPattern(dataView.id); + dispatchChangeIndexPattern(dataView.id); refreshFieldList(); } }, diff --git a/x-pack/plugins/lens/public/state_management/init_middleware/load_initial.ts b/x-pack/plugins/lens/public/state_management/init_middleware/load_initial.ts index 5f978ca9f3a58..1e39fa55f4b04 100644 --- a/x-pack/plugins/lens/public/state_management/init_middleware/load_initial.ts +++ b/x-pack/plugins/lens/public/state_management/init_middleware/load_initial.ts @@ -157,40 +157,6 @@ export function loadInitial( }); redirectCallback(); }); - // return initializeDatasources(datasourceMap, lens.datasourceStates, undefined, initialContext, { - // isFullEditor: true, - // }) - // .then((result) => { - // store.dispatch( - // initEmpty({ - // newState: { - // ...emptyState, - // searchSessionId: data.search.session.getSessionId() || data.search.session.start(), - // datasourceStates: Object.entries(result).reduce( - // (state, [datasourceId, datasourceState]) => ({ - // ...state, - // [datasourceId]: { - // ...datasourceState, - // isLoading: false, - // }, - // }), - // {} - // ), - // isLoading: false, - // }, - // initialContext, - // }) - // ); - // if (autoApplyDisabled) { - // store.dispatch(disableAutoApply()); - // } - // }) - // .catch((e: { message: string }) => { - // notifications.toasts.addDanger({ - // title: e.message, - // }); - // redirectCallback(); - // }); } getPersisted({ initialInput, lensServices, history }) @@ -221,15 +187,6 @@ export function loadInitial( // Don't overwrite any pinned filters data.query.filterManager.setAppFilters(filters); - // initializeDatasources( - // datasourceMap, - // docDatasourceStates, - // doc.references, - // initialContext, - // { - // isFullEditor: true, - // } - // ) initializeSources( { datasourceMap, diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index 192d076063941..66f0a54b1f2da 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -870,15 +870,6 @@ export interface Visualization { */ initialize: (addNewLayer: () => string, state?: T, mainPalette?: PaletteOutput) => T; - /** - * Retrieve the used indexpatterns in the visualization - */ - getUsedIndexPatterns?: ( - state?: T, - indexPatternRefs?: IndexPatternRef[], - savedObjectReferences?: SavedObjectReference[] - ) => { usedPatterns: string[] }; - getMainPalette?: (state: T) => undefined | PaletteOutput; /** diff --git a/x-pack/plugins/lens/public/utils.ts b/x-pack/plugins/lens/public/utils.ts index 13ff13b6ab93b..2c848758b0975 100644 --- a/x-pack/plugins/lens/public/utils.ts +++ b/x-pack/plugins/lens/public/utils.ts @@ -111,23 +111,6 @@ export async function refreshIndexPatternsList({ }); } -// export function refreshIndexPatternsList({ -// activeDatasources, -// indexPatternId, -// setDatasourceState, -// }: { -// activeDatasources: Record; -// indexPatternId: string; -// setDatasourceState: StateSetter; -// }): void { -// Object.entries(activeDatasources).forEach(([id, datasource]) => { -// datasource?.refreshIndexPatternsList?.({ -// indexPatternId, -// setState: setDatasourceState, -// }); -// }); -// } - export function getIndexPatternsIds({ activeDatasources, datasourceStates, From c7a9ce8c88ae70e661f4a08e11aa9e500108f1e3 Mon Sep 17 00:00:00 2001 From: dej611 Date: Wed, 27 Jul 2022 18:57:22 +0200 Subject: [PATCH 03/27] :bug: Fix dataViews layer change --- .../editor_frame/config_panel/layer_panel.tsx | 28 -------- .../indexpattern_datasource/datapanel.tsx | 14 +--- .../public/state_management/lens_slice.ts | 64 +++++++++++++------ x-pack/plugins/lens/public/types.ts | 1 - 4 files changed, 47 insertions(+), 60 deletions(-) diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx index 393aabdb04e74..915a1e05e6ec6 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx @@ -347,34 +347,6 @@ export function LayerPanel( dataViews, onChangeIndexPattern: (indexPatternId) => onChangeIndexPattern({ indexPatternId, layerId, datasourceId }), - setState: (updater: unknown) => { - const newState = - typeof updater === 'function' ? updater(layerDatasourceState) : updater; - // Look for removed columns - const nextPublicAPI = layerDatasource.getPublicAPI({ - state: newState, - layerId, - indexPatterns: dataViews.indexPatterns, - }); - const nextTable = new Set( - nextPublicAPI.getTableSpec().map(({ columnId }) => columnId) - ); - const removed = datasourcePublicAPI - .getTableSpec() - .map(({ columnId }) => columnId) - .filter((columnId) => !nextTable.has(columnId)); - let nextVisState = props.visualizationState; - removed.forEach((columnId) => { - nextVisState = activeVisualization.removeDimension({ - layerId, - columnId, - prevState: nextVisState, - frame: framePublicAPI, - }); - }); - - props.updateAll(datasourceId, newState, nextVisState); - }, }} /> )} diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx index b46155edf0503..17f065c4f05f5 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx @@ -40,7 +40,6 @@ import type { FramePublicAPI, IndexPattern, IndexPatternField, - StateSetter, } from '../types'; import { ChildDragDropProvider, DragContextState } from '../drag_drop'; import type { IndexPatternPrivateState } from './types'; @@ -57,11 +56,7 @@ export type Props = Omit< data: DataPublicPluginStart; dataViews: DataViewsPublicPluginStart; fieldFormats: FieldFormatsStart; - changeIndexPattern: ( - id: string, - state: IndexPatternPrivateState, - setState: StateSetter - ) => void; + changeIndexPattern: (id: string) => void; charts: ChartsPluginSetup; core: CoreStart; indexPatternFieldEditor: IndexPatternFieldEditorStart; @@ -125,7 +120,6 @@ function buildSafeEsQuery( } export function IndexPatternDataPanel({ - setState, state, dragDropContext, core, @@ -149,10 +143,6 @@ export function IndexPatternDataPanel({ const { indexPatterns, indexPatternRefs, existingFields, isFirstExistenceFetch } = frame.dataViews; const { currentIndexPatternId } = state; - const onChangeIndexPattern = useCallback( - (id: string) => changeIndexPattern(id, state, setState), - [state, setState, changeIndexPattern] - ); const indexPatternList = uniq( Object.values(state.layers) @@ -231,7 +221,7 @@ export function IndexPatternDataPanel({ fieldFormats={fieldFormats} charts={charts} indexPatternFieldEditor={indexPatternFieldEditor} - onChangeIndexPattern={onChangeIndexPattern} + onChangeIndexPattern={changeIndexPattern} dropOntoWorkspace={dropOntoWorkspace} hasSuggestionForField={hasSuggestionForField} uiActions={uiActions} diff --git a/x-pack/plugins/lens/public/state_management/lens_slice.ts b/x-pack/plugins/lens/public/state_management/lens_slice.ts index 6de39cb5d9681..e4050d09c3ed8 100644 --- a/x-pack/plugins/lens/public/state_management/lens_slice.ts +++ b/x-pack/plugins/lens/public/state_management/lens_slice.ts @@ -13,7 +13,7 @@ import { History } from 'history'; import { LensEmbeddableInput } from '..'; import { getDatasourceLayers } from '../editor_frame_service/editor_frame'; import { TableInspectorAdapter } from '../editor_frame_service/types'; -import type { VisualizeEditorContext, Suggestion } from '../types'; +import type { VisualizeEditorContext, Suggestion, DatasourceMap } from '../types'; import { getInitialDatasourceId, getResolvedDateRange, getRemoveOperation } from '../utils'; import { DataViewsState, LensAppState, LensStoreDeps, VisualizationState } from './types'; import { Datasource, Visualization } from '../types'; @@ -337,6 +337,9 @@ export const makeLensReducer = (storeDeps: LensStoreDeps) => { } if (datasourceIds?.length) { newState.datasourceStates = { ...state.datasourceStates }; + const frame = createFrameAPI(state, datasourceMap); + const datasourceLayers = frame.datasourceLayers; + for (const datasourceId of datasourceIds) { const activeDatasource = datasourceId && datasourceMap[datasourceId]; if (activeDatasource && activeDatasource?.onIndexPatternChange) { @@ -352,9 +355,38 @@ export const makeLensReducer = (storeDeps: LensStoreDeps) => { ), }, }; + // Update the visualization columns + if (layerId && state.visualization.activeId) { + const nextPublicAPI = activeDatasource.getPublicAPI({ + state: newState.datasourceStates[datasourceId].state, + layerId, + indexPatterns: dataViews.indexPatterns, + }); + const nextTable = new Set( + nextPublicAPI.getTableSpec().map(({ columnId }) => columnId) + ); + const removed = datasourceLayers[layerId] + .getTableSpec() + .map(({ columnId }) => columnId) + .filter((columnId) => !nextTable.has(columnId)); + const nextVisState = (newState.visualization || state.visualization).state; + const activeVisualization = visualizationMap[state.visualization.activeId]; + removed.forEach((columnId) => { + newState.visualization = { + ...state.visualization, + state: activeVisualization.removeDimension({ + layerId, + columnId, + prevState: nextVisState, + frame, + }), + }; + }); + } } } } + return { ...state, ...newState }; }, [updateIndexPatterns.type]: (state, { payload }: { payload: Partial }) => { @@ -710,15 +742,7 @@ export const makeLensReducer = (storeDeps: LensStoreDeps) => { layerType ); - const framePublicAPI = { - // any better idea to avoid `as`? - activeData: state.activeData - ? (current(state.activeData) as TableInspectorAdapter) - : undefined, - datasourceLayers: getDatasourceLayers(state.datasourceStates, datasourceMap), - dateRange: current(state.resolvedDateRange), - dataViews: current(state.dataViews), - }; + const framePublicAPI = createFrameAPI(state, datasourceMap); const activeDatasource = datasourceMap[state.activeDatasourceId]; const { noDatasource } = @@ -770,15 +794,7 @@ export const makeLensReducer = (storeDeps: LensStoreDeps) => { const { activeDatasourceState, activeVisualizationState } = addInitialValueIfAvailable({ datasourceState: state.datasourceStates[state.activeDatasourceId].state, visualizationState: state.visualization.state, - framePublicAPI: { - // any better idea to avoid `as`? - activeData: state.activeData - ? (current(state.activeData) as TableInspectorAdapter) - : undefined, - datasourceLayers: getDatasourceLayers(state.datasourceStates, datasourceMap), - dateRange: current(state.resolvedDateRange), - dataViews: current(state.dataViews), - }, + framePublicAPI: createFrameAPI(state, datasourceMap), activeVisualization, activeDatasource, layerId, @@ -793,6 +809,16 @@ export const makeLensReducer = (storeDeps: LensStoreDeps) => { }); }; +function createFrameAPI(state: LensAppState, datasourceMap: DatasourceMap) { + return { + // any better idea to avoid `as`? + activeData: state.activeData ? (current(state.activeData) as TableInspectorAdapter) : undefined, + datasourceLayers: getDatasourceLayers(state.datasourceStates, datasourceMap), + dateRange: current(state.resolvedDateRange), + dataViews: current(state.dataViews), + }; +} + function addInitialValueIfAvailable({ visualizationState, datasourceState, diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index 66f0a54b1f2da..5141834d14e80 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -546,7 +546,6 @@ export type DatasourceDimensionTriggerProps = DatasourceDimensionProps; export interface DatasourceLayerPanelProps { layerId: string; state: T; - setState: StateSetter; activeData?: Record; dataViews: DataViewsState; onChangeIndexPattern: (indexPatternId: string, datasourceId: string, layerId?: string) => void; From 79566af6d33fdcfbe1a13d63986c3839a8abc52e Mon Sep 17 00:00:00 2001 From: dej611 Date: Wed, 27 Jul 2022 19:23:48 +0200 Subject: [PATCH 04/27] :bug: Fix datasourceLayers refs --- .../editor_frame/state_helpers.ts | 8 ++++---- .../editor_frame/suggestion_panel.tsx | 3 ++- .../definitions/filters/filter_popover.tsx | 2 +- .../lens/public/state_management/lens_slice.ts | 16 ++++++++++++---- .../lens/public/state_management/selectors.ts | 10 ++++++++-- 5 files changed, 27 insertions(+), 12 deletions(-) diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts index 6017a7125abae..b4b6a79975f71 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts @@ -213,7 +213,8 @@ export function initializeDatasources({ export const getDatasourceLayers = memoizeOne(function getDatasourceLayers( datasourceStates: DatasourceStates, - datasourceMap: DatasourceMap + datasourceMap: DatasourceMap, + indexPatterns: DataViewsState['indexPatterns'] ) { const datasourceLayers: DatasourceLayers = {}; Object.keys(datasourceMap) @@ -227,8 +228,7 @@ export const getDatasourceLayers = memoizeOne(function getDatasourceLayers( datasourceLayers[layer] = datasourceMap[id].getPublicAPI({ state: datasourceState, layerId: layer, - // @TODO - indexPatterns: {}, + indexPatterns, }); }); }); @@ -290,7 +290,7 @@ export async function persistedStateToExpression( indexPatternRefs, }); - const datasourceLayers = getDatasourceLayers(datasourceStates, datasourceMap); + const datasourceLayers = getDatasourceLayers(datasourceStates, datasourceMap, indexPatterns); const datasourceId = getActiveDatasourceIdFromDoc(doc); if (datasourceId == null) { diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx index 7400aa6dd8fab..24e65e75ec090 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx @@ -252,7 +252,8 @@ export function SuggestionPanel({ }, } : {}, - datasourceMap + datasourceMap, + frame.dataViews.indexPatterns ), } ) == null diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filter_popover.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filter_popover.tsx index 7166da0a3c0df..c4ab33c36f1f1 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filter_popover.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filter_popover.tsx @@ -10,8 +10,8 @@ import './filter_popover.scss'; import React from 'react'; import { EuiPopover, EuiSpacer } from '@elastic/eui'; import type { Query } from '@kbn/es-query'; +import { IndexPattern } from '../../../../types'; import { FilterValue, defaultLabel, isQueryValid } from '.'; -import { IndexPattern } from '../../../types'; import { LabelInput } from '../shared_components'; import { QueryInput } from '../../../query_input'; diff --git a/x-pack/plugins/lens/public/state_management/lens_slice.ts b/x-pack/plugins/lens/public/state_management/lens_slice.ts index e4050d09c3ed8..9db4806d6885f 100644 --- a/x-pack/plugins/lens/public/state_management/lens_slice.ts +++ b/x-pack/plugins/lens/public/state_management/lens_slice.ts @@ -337,7 +337,7 @@ export const makeLensReducer = (storeDeps: LensStoreDeps) => { } if (datasourceIds?.length) { newState.datasourceStates = { ...state.datasourceStates }; - const frame = createFrameAPI(state, datasourceMap); + const frame = createFrameAPI(state, datasourceMap, newState.dataViews); const datasourceLayers = frame.datasourceLayers; for (const datasourceId of datasourceIds) { @@ -809,13 +809,21 @@ export const makeLensReducer = (storeDeps: LensStoreDeps) => { }); }; -function createFrameAPI(state: LensAppState, datasourceMap: DatasourceMap) { +function createFrameAPI( + state: LensAppState, + datasourceMap: DatasourceMap, + dataViews: DataViewsState = current(state.dataViews) +) { return { // any better idea to avoid `as`? activeData: state.activeData ? (current(state.activeData) as TableInspectorAdapter) : undefined, - datasourceLayers: getDatasourceLayers(state.datasourceStates, datasourceMap), + datasourceLayers: getDatasourceLayers( + state.datasourceStates, + datasourceMap, + dataViews.indexPatterns + ), dateRange: current(state.resolvedDateRange), - dataViews: current(state.dataViews), + dataViews, }; } diff --git a/x-pack/plugins/lens/public/state_management/selectors.ts b/x-pack/plugins/lens/public/state_management/selectors.ts index a7e7a55e39592..f1f53197978fa 100644 --- a/x-pack/plugins/lens/public/state_management/selectors.ts +++ b/x-pack/plugins/lens/public/state_management/selectors.ts @@ -156,8 +156,10 @@ export const selectDatasourceLayers = createSelector( [ selectDatasourceStates, selectInjectedDependencies as SelectInjectedDependenciesFunction, + selectDataViews, ], - (datasourceStates, datasourceMap) => getDatasourceLayers(datasourceStates, datasourceMap) + (datasourceStates, datasourceMap, dataViews) => + getDatasourceLayers(datasourceStates, datasourceMap, dataViews.indexPatterns) ); export const selectFramePublicAPI = createSelector( @@ -170,7 +172,11 @@ export const selectFramePublicAPI = createSelector( ], (datasourceStates, activeData, datasourceMap, dateRange, dataViews) => { return { - datasourceLayers: getDatasourceLayers(datasourceStates, datasourceMap), + datasourceLayers: getDatasourceLayers( + datasourceStates, + datasourceMap, + dataViews.indexPatterns + ), activeData, dateRange, dataViews, From 4822b3674d209d1ef2f8e44926e0dc6ce9536196 Mon Sep 17 00:00:00 2001 From: dej611 Date: Wed, 27 Jul 2022 19:51:16 +0200 Subject: [PATCH 05/27] :fire: Remove more old cruft --- .../lens/public/indexpattern_datasource/types.ts | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/types.ts b/x-pack/plugins/lens/public/indexpattern_datasource/types.ts index 06eedb2848363..8c7cef5d59a11 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/types.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/types.ts @@ -66,17 +66,6 @@ export type PersistedIndexPatternLayer = Omit; - // indexPatternRefs: IndexPatternRef[]; - // indexPatterns: Record; - - // /** - // * indexPatternId -> fieldName -> boolean - // */ - // existingFields: Record>; - // isFirstExistenceFetch: boolean; - // existenceFetchFailed?: boolean; - // existenceFetchTimeout?: boolean; - isDimensionClosePrevented?: boolean; } From 220aa39d7508c11dc67290c2d85760478d0a73f3 Mon Sep 17 00:00:00 2001 From: dej611 Date: Thu, 28 Jul 2022 11:21:48 +0200 Subject: [PATCH 06/27] :bug: Fix bug when loading SO --- .../public/editor_frame_service/editor_frame/state_helpers.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts index b4b6a79975f71..f9136af24dde8 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts @@ -175,6 +175,7 @@ export async function initializeSources( initialContext, indexPatternRefs, indexPatterns, + references, }), }; } From 759f19ad89d6ba0989fa3ba9cc69282d6f78ad00 Mon Sep 17 00:00:00 2001 From: dej611 Date: Thu, 28 Jul 2022 11:24:14 +0200 Subject: [PATCH 07/27] :bug: Fix initial existence flag --- x-pack/plugins/lens/public/state_management/lens_slice.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/lens/public/state_management/lens_slice.ts b/x-pack/plugins/lens/public/state_management/lens_slice.ts index 9db4806d6885f..7bee1b25e2ae6 100644 --- a/x-pack/plugins/lens/public/state_management/lens_slice.ts +++ b/x-pack/plugins/lens/public/state_management/lens_slice.ts @@ -43,7 +43,7 @@ export const initialState: LensAppState = { indexPatternRefs: [], indexPatterns: {}, existingFields: {}, - isFirstExistenceFetch: false, + isFirstExistenceFetch: true, }, }; From 7f590daa5d306d972cd1ec9e843dbca84fe4036b Mon Sep 17 00:00:00 2001 From: dej611 Date: Thu, 28 Jul 2022 14:51:42 +0200 Subject: [PATCH 08/27] :label: Fix type issues --- .../lens/public/data_views_service/loader.ts | 2 +- .../lens/public/embeddable/embeddable.tsx | 28 ++- .../change_indexpattern.tsx | 2 +- .../dimension_panel/bucket_nesting_editor.tsx | 3 +- .../dimension_panel/dimension_editor.tsx | 1 + .../dimension_panel/droppable/mocks.ts | 6 +- .../dimension_panel/field_input.tsx | 2 +- .../dimension_panel/reference_editor.tsx | 12 +- .../dimension_panel/time_shift.tsx | 5 +- .../indexpattern_datasource/document_field.ts | 2 +- .../indexpattern_datasource/indexpattern.tsx | 4 +- .../definitions/calculations/utils.ts | 3 +- .../operations/definitions/count.tsx | 2 +- .../definitions/filters/filters.tsx | 4 +- .../formula/editor/formula_help.tsx | 2 +- .../formula/editor/math_completion.ts | 2 +- .../definitions/formula/formula.tsx | 2 +- .../definitions/formula/formula_public_api.ts | 5 +- .../operations/definitions/formula/math.tsx | 2 +- .../operations/definitions/formula/parse.ts | 3 +- .../definitions/formula/validation.ts | 3 +- .../operations/definitions/helpers.tsx | 3 +- .../operations/definitions/last_value.tsx | 2 +- .../operations/definitions/ranges/ranges.tsx | 2 +- .../definitions/shared_components/index.tsx | 2 +- .../operations/definitions/static_value.tsx | 2 +- .../definitions/terms/field_inputs.tsx | 17 +- .../operations/definitions/terms/helpers.ts | 6 +- .../operations/definitions/terms/index.tsx | 2 +- .../public/indexpattern_datasource/types.ts | 2 +- .../drag_drop_bucket/buckets.test.tsx | 79 --------- .../drag_drop_bucket/buckets.tsx | 160 ------------------ .../lens/public/shared_components/index.ts | 5 - x-pack/plugins/lens/public/types.ts | 3 +- 34 files changed, 76 insertions(+), 304 deletions(-) delete mode 100644 x-pack/plugins/lens/public/shared_components/drag_drop_bucket/buckets.test.tsx delete mode 100644 x-pack/plugins/lens/public/shared_components/drag_drop_bucket/buckets.tsx diff --git a/x-pack/plugins/lens/public/data_views_service/loader.ts b/x-pack/plugins/lens/public/data_views_service/loader.ts index 89d1765bc61af..ac1a894ed5809 100644 --- a/x-pack/plugins/lens/public/data_views_service/loader.ts +++ b/x-pack/plugins/lens/public/data_views_service/loader.ts @@ -28,7 +28,7 @@ export function getFieldByNameFactory(newFields: IndexPatternField[]) { export function convertDataViewIntoLensIndexPattern( dataView: DataView, - restrictionRemapper: (name: string) => string + restrictionRemapper: (name: string) => string = onRestrictionMapping ): IndexPattern { const newFields = dataView.fields .filter((field) => !isNestedField(field) && (!!field.aggregatable || !!field.scripted)) diff --git a/x-pack/plugins/lens/public/embeddable/embeddable.tsx b/x-pack/plugins/lens/public/embeddable/embeddable.tsx index de4434a17f05a..75787ba08402f 100644 --- a/x-pack/plugins/lens/public/embeddable/embeddable.tsx +++ b/x-pack/plugins/lens/public/embeddable/embeddable.tsx @@ -66,6 +66,7 @@ import { Visualization, DatasourceMap, Datasource, + IndexPatternMap, } from '../types'; import { getEditPath, DOC_TYPE } from '../../common'; @@ -75,6 +76,7 @@ import { getLensInspectorService, LensInspector } from '../lens_inspector_servic import { SharingSavedObjectProps, VisualizationDisplayOptions } from '../types'; import { getActiveDatasourceIdFromDoc, getIndexPatternsObjects, inferTimeField } from '../utils'; import { getLayerMetaInfo, combineQueryAndFilters } from '../app_plugin/show_underlying_data'; +import { convertDataViewIntoLensIndexPattern } from '../data_views_service/loader'; export type LensSavedObjectAttributes = Omit; @@ -169,6 +171,7 @@ function getViewUnderlyingDataArgs({ filters, timeRange, esQueryConfig, + indexPatternsCache, }: { activeDatasource: Datasource; activeDatasourceState: unknown; @@ -179,11 +182,13 @@ function getViewUnderlyingDataArgs({ filters: Filter[]; timeRange: TimeRange; esQueryConfig: EsQueryConfig; + indexPatternsCache: IndexPatternMap; }) { const { error, meta } = getLayerMetaInfo( activeDatasource, activeDatasourceState, activeData, + indexPatternsCache, timeRange, capabilities ); @@ -749,7 +754,7 @@ export class Embeddable private async loadViewUnderlyingDataArgs(): Promise { const mergedSearchContext = this.getMergedSearchContext(); - if (!this.activeDataInfo.activeData || !mergedSearchContext.timeRange) { + if (!this.activeDataInfo.activeData || !mergedSearchContext.timeRange || !this.savedVis) { return false; } @@ -761,12 +766,22 @@ export class Embeddable this.activeDataInfo.activeDatasource = this.deps.datasourceMap[activeDatasourceId]; const docDatasourceState = this.savedVis?.state.datasourceStates[activeDatasourceId]; + const indexPatternsCache = this.indexPatterns.reduce( + (acc, indexPattern) => ({ + [indexPattern.id!]: convertDataViewIntoLensIndexPattern(indexPattern), + ...acc, + }), + {} + ); + if (!this.activeDataInfo.activeDatasourceState) { - this.activeDataInfo.activeDatasourceState = - await this.activeDataInfo.activeDatasource.initialize( - docDatasourceState, - this.savedVis?.references - ); + this.activeDataInfo.activeDatasourceState = this.activeDataInfo.activeDatasource.initialize( + docDatasourceState, + this.savedVis?.references, + undefined, + undefined, + indexPatternsCache + ); } const viewUnderlyingDataArgs = getViewUnderlyingDataArgs({ @@ -779,6 +794,7 @@ export class Embeddable filters: mergedSearchContext.filters || [], timeRange: mergedSearchContext.timeRange, esQueryConfig: getEsQueryConfig(this.deps.uiSettings), + indexPatternsCache, }); const loaded = typeof viewUnderlyingDataArgs !== 'undefined'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/change_indexpattern.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/change_indexpattern.tsx index 256f452c2ab2a..75ebbfdeb27a4 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/change_indexpattern.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/change_indexpattern.tsx @@ -10,7 +10,7 @@ import React, { useState } from 'react'; import { EuiPopover, EuiPopoverTitle, EuiSelectableProps } from '@elastic/eui'; import { ToolbarButton, ToolbarButtonProps } from '@kbn/kibana-react-plugin/public'; import { DataViewsList } from '@kbn/unified-search-plugin/public'; -import { IndexPatternRef } from './types'; +import type { IndexPatternRef } from '../types'; export type ChangeIndexPatternTriggerProps = ToolbarButtonProps & { label: string; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/bucket_nesting_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/bucket_nesting_editor.tsx index 46ed51f35d9b0..31a4b91706dfa 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/bucket_nesting_editor.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/bucket_nesting_editor.tsx @@ -8,9 +8,10 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { EuiFormRow, EuiSwitch, EuiSelect } from '@elastic/eui'; -import { IndexPatternLayer, IndexPatternField } from '../types'; +import { IndexPatternLayer } from '../types'; import { hasField } from '../pure_utils'; import { GenericIndexPatternColumn } from '../operations'; +import { IndexPatternField } from '../../types'; function nestColumn(columnOrder: string[], outer: string, inner: string) { const result = columnOrder.filter((c) => c !== inner); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx index 87f9c64075ef7..71d71f51fb34d 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx @@ -63,6 +63,7 @@ import { WrappingHelpPopover } from '../help_popover'; import { isColumn } from '../operations/definitions/helpers'; import type { FieldChoiceWithOperationType } from './field_select'; import type { IndexPattern, IndexPatternField } from '../../types'; +import { documentField } from '../document_field'; export interface DimensionEditorProps extends IndexPatternDimensionEditorProps { selectedColumn?: GenericIndexPatternColumn; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable/mocks.ts b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable/mocks.ts index 40121cf99f546..19dfaf3ac7c20 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable/mocks.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable/mocks.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { IndexPattern, IndexPatternLayer } from '../../types'; +import { IndexPatternLayer } from '../../types'; import { documentField } from '../../document_field'; -import { OperationMetadata } from '../../../types'; +import { IndexPatternMap, OperationMetadata } from '../../../types'; import { DateHistogramIndexPatternColumn, GenericIndexPatternColumn, @@ -17,7 +17,7 @@ import { import { getFieldByNameFactory } from '../../pure_helpers'; jest.mock('../../../id_generator'); -export const mockDataViews = (): Record => { +export const mockDataViews = (): IndexPatternMap => { const fields = [ { name: 'timestamp', diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/field_input.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/field_input.tsx index ad3aa97b2a0ea..ec471b70de614 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/field_input.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/field_input.tsx @@ -62,7 +62,7 @@ export function FieldInput({ ; isInline?: boolean; - existingFields: IndexPatternPrivateState['existingFields']; + existingFields: ExistingFieldsMap; dateRange: DateRange; labelAppend?: EuiFormRowProps['labelAppend']; isFullscreen: boolean; @@ -305,7 +305,7 @@ export const ReferenceEditor = (props: ReferenceEditorProps) => { , - options?: InitializationOptions + indexPatterns?: Record ) { return loadInitialState({ persistedState, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/utils.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/utils.ts index 5bae2f5a1865f..f85bf0b194e2e 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/utils.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/utils.ts @@ -8,9 +8,10 @@ import { i18n } from '@kbn/i18n'; import type { AstFunction } from '@kbn/interpreter'; import memoizeOne from 'memoize-one'; +import type { IndexPattern } from '../../../../types'; import { LayerType, layerTypes } from '../../../../../common'; import type { TimeScaleUnit } from '../../../../../common/expressions'; -import type { IndexPattern, IndexPatternLayer } from '../../../types'; +import type { IndexPatternLayer } from '../../../types'; import { adjustTimeScaleLabelSuffix } from '../../time_scale_utils'; import type { ReferenceBasedIndexPatternColumn } from '../column_types'; import { getManagedColumnsFrom, isColumnValidAsReference } from '../../layer_helpers'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/count.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/count.tsx index d3c0b9700379c..9bf4522705f9f 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/count.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/count.tsx @@ -14,7 +14,7 @@ import { buildExpressionFunction } from '@kbn/expressions-plugin/public'; import { TimeScaleUnit } from '../../../../common/expressions'; import { OperationDefinition, ParamEditorProps } from '.'; import { FieldBasedIndexPatternColumn, ValueFormatConfig } from './column_types'; -import { IndexPatternField } from '../../types'; +import type { IndexPatternField } from '../../../types'; import { getInvalidFieldMessage, getFilter, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filters.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filters.tsx index 3890494be8b46..cee188b6f483a 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filters.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filters.tsx @@ -15,12 +15,12 @@ import type { Query } from '@kbn/es-query'; import type { AggFunctionsMapping } from '@kbn/data-plugin/public'; import { queryFilterToAst } from '@kbn/data-plugin/common'; import { buildExpressionFunction } from '@kbn/expressions-plugin/public'; +import { IndexPattern } from '../../../../types'; import { updateColumnParam } from '../../layer_helpers'; import type { OperationDefinition } from '..'; import type { BaseIndexPatternColumn } from '../column_types'; import { FilterPopover } from './filter_popover'; -import type { IndexPattern } from '../../../types'; -import { NewBucketButton, DragDropBuckets, DraggableBucketContainer } from '../shared_components'; +import { DragDropBuckets, DraggableBucketContainer, NewBucketButton } from '../shared_components'; const generateId = htmlIdGenerator(); const OPERATION_NAME = 'filters'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/editor/formula_help.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/editor/formula_help.tsx index bdc774f9d15f3..c6fa7e56edb02 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/editor/formula_help.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/editor/formula_help.tsx @@ -21,7 +21,7 @@ import { EuiSpacer, } from '@elastic/eui'; import { Markdown } from '@kbn/kibana-react-plugin/public'; -import { IndexPattern } from '../../../../types'; +import type { IndexPattern } from '../../../../../types'; import { tinymathFunctions } from '../util'; import { getPossibleFunctions } from './math_completion'; import { hasFunctionFieldArgument } from '../validation'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/editor/math_completion.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/editor/math_completion.ts index 95833a4508fdf..c3cf81b0e92b3 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/editor/math_completion.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/editor/math_completion.ts @@ -22,7 +22,7 @@ import type { } from '@kbn/unified-search-plugin/public'; import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import { parseTimeShift } from '@kbn/data-plugin/common'; -import { IndexPattern } from '../../../../types'; +import type { IndexPattern } from '../../../../../types'; import { memoizedGetAvailableOperationsByMetadata } from '../../../operations'; import { tinymathFunctions, groupArgsByType, unquotedStringRegex } from '../util'; import type { GenericOperationDefinition } from '../..'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula.tsx index 72aace21479ac..c3562fb649665 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula.tsx @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import type { BaseIndexPatternColumn, OperationDefinition } from '..'; import type { ReferenceBasedIndexPatternColumn } from '../column_types'; -import type { IndexPattern } from '../../../types'; +import type { IndexPattern } from '../../../../types'; import { runASTValidation, tryToParse } from './validation'; import { WrappedFormulaEditor } from './editor'; import { insertOrReplaceFormulaColumn } from './parse'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula_public_api.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula_public_api.ts index 8f431afa129e0..4085ec931d3a6 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula_public_api.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula_public_api.ts @@ -6,10 +6,11 @@ */ import type { DataView } from '@kbn/data-views-plugin/public'; -import type { IndexPattern, PersistedIndexPatternLayer } from '../../../types'; +import { convertDataViewIntoLensIndexPattern } from '../../../../data_views_service/loader'; +import type { IndexPattern } from '../../../../types'; +import type { PersistedIndexPatternLayer } from '../../../types'; import { insertOrReplaceFormulaColumn } from './parse'; -import { convertDataViewIntoLensIndexPattern } from '../../../loader'; /** @public **/ export interface FormulaPublicApi { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/math.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/math.tsx index d7f25275f63a2..b05c9f7c0fd21 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/math.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/math.tsx @@ -8,7 +8,7 @@ import type { TinymathAST } from '@kbn/tinymath'; import { OperationDefinition } from '..'; import { ValueFormatConfig, ReferenceBasedIndexPatternColumn } from '../column_types'; -import { IndexPattern } from '../../../types'; +import { IndexPattern } from '../../../../types'; export interface MathIndexPatternColumn extends ReferenceBasedIndexPatternColumn { operationType: 'math'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/parse.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/parse.ts index 63e0935a3425b..5c9a277070936 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/parse.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/parse.ts @@ -8,13 +8,14 @@ import { i18n } from '@kbn/i18n'; import { isObject } from 'lodash'; import type { TinymathAST, TinymathVariable, TinymathLocation } from '@kbn/tinymath'; +import type { IndexPattern } from '../../../../types'; import { OperationDefinition, GenericOperationDefinition, GenericIndexPatternColumn, operationDefinitionMap, } from '..'; -import type { IndexPattern, IndexPatternLayer } from '../../../types'; +import type { IndexPatternLayer } from '../../../types'; import { mathOperation } from './math'; import { documentField } from '../../../document_field'; import { runASTValidation, shouldHaveFieldArgument, tryToParse } from './validation'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/validation.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/validation.ts index 28e015e4fc0b5..788d00f1c14bf 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/validation.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/validation.ts @@ -27,7 +27,8 @@ import type { GenericIndexPatternColumn, GenericOperationDefinition, } from '..'; -import type { IndexPattern, IndexPatternLayer } from '../../../types'; +import type { IndexPatternLayer } from '../../../types'; +import type { IndexPattern } from '../../../../types'; import type { TinymathNodeTypes } from './types'; interface ValidationErrors { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/helpers.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/helpers.tsx index 4ca172df112e5..868d5e2557e49 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/helpers.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/helpers.tsx @@ -6,13 +6,14 @@ */ import { i18n } from '@kbn/i18n'; +import type { IndexPattern, IndexPatternField } from '../../../types'; import { GenericIndexPatternColumn, operationDefinitionMap } from '.'; import { FieldBasedIndexPatternColumn, FormattedIndexPatternColumn, ReferenceBasedIndexPatternColumn, } from './column_types'; -import { IndexPattern, IndexPatternField, IndexPatternLayer } from '../../types'; +import type { IndexPatternLayer } from '../../types'; import { hasField } from '../../pure_utils'; export function getInvalidFieldMessage( diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.tsx index 0af5ed4428ef7..ec140d0a7e597 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.tsx @@ -19,7 +19,7 @@ import { AggFunctionsMapping } from '@kbn/data-plugin/public'; import { buildExpressionFunction } from '@kbn/expressions-plugin/public'; import { OperationDefinition } from '.'; import { FieldBasedIndexPatternColumn, ValueFormatConfig } from './column_types'; -import { IndexPatternField, IndexPattern } from '../../types'; +import type { IndexPatternField, IndexPattern } from '../../../types'; import { DataType } from '../../../types'; import { getFormatFromPreviousColumn, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx index 06db3221bde34..b1d023a189be9 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx @@ -17,7 +17,7 @@ import { FieldBasedIndexPatternColumn } from '../column_types'; import { updateColumnParam } from '../../layer_helpers'; import { supportedFormats } from '../../../../../common/expressions/format_column/supported_formats'; import { MODES, AUTO_BARS, DEFAULT_INTERVAL, MIN_HISTOGRAM_BARS, SLICES } from './constants'; -import { IndexPattern, IndexPatternField } from '../../../types'; +import { IndexPattern, IndexPatternField } from '../../../../types'; import { getInvalidFieldMessage, isValidNumber } from '../helpers'; type RangeType = Omit; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/shared_components/index.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/shared_components/index.tsx index 49d913655af45..1dbd20d8767d0 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/shared_components/index.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/shared_components/index.tsx @@ -4,6 +4,6 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +export * from './buckets'; export * from './label_input'; export * from './form_row'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/static_value.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/static_value.tsx index bb9b36a3d097b..de6e662aead13 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/static_value.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/static_value.tsx @@ -13,7 +13,7 @@ import { GenericIndexPatternColumn, ValueFormatConfig, } from './column_types'; -import type { IndexPattern } from '../../types'; +import type { IndexPattern } from '../../../types'; import { useDebouncedValue } from '../../../shared_components'; import { getFormatFromPreviousColumn, isValidNumber } from './helpers'; import { getColumnOrder } from '../layer_helpers'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/field_inputs.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/field_inputs.tsx index a6b734d373849..28e267b01bcd7 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/field_inputs.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/field_inputs.tsx @@ -15,18 +15,13 @@ import { htmlIdGenerator, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { - DragDropBuckets, - NewBucketButton, - TooltipWrapper, - useDebouncedValue, -} from '../../../../shared_components'; +import { ExistingFieldsMap, IndexPattern } from '../../../../types'; +import { TooltipWrapper, useDebouncedValue } from '../../../../shared_components'; import { FieldSelect } from '../../../dimension_panel/field_select'; import type { TermsIndexPatternColumn } from './types'; -import type { IndexPatternPrivateState } from '../../../types'; import type { OperationSupportMatrix } from '../../../dimension_panel'; import { supportedTypes } from './constants'; -import type { IndexPattern } from '../../../../editor_frame_service/types'; +import { DragDropBuckets, NewBucketButton } from '../shared_components'; const generateId = htmlIdGenerator(); export const MAX_MULTI_FIELDS_SIZE = 3; @@ -34,7 +29,7 @@ export const MAX_MULTI_FIELDS_SIZE = 3; export interface FieldInputsProps { column: TermsIndexPatternColumn; indexPattern: IndexPattern; - existingFields: IndexPatternPrivateState['existingFields']; + existingFields: ExistingFieldsMap; invalidFields?: string[]; operationSupportMatrix: Pick; onChange: (newValues: string[]) => void; @@ -100,7 +95,7 @@ export function FieldInputs({ { - const original = jest.requireActual('@elastic/eui'); - return { - ...original, - EuiDragDropContext: 'eui-drag-drop-context', - EuiDroppable: 'eui-droppable', - EuiDraggable: (props: any) => props.children(), // eslint-disable-line @typescript-eslint/no-explicit-any - }; -}); - -describe('buckets shared components', () => { - describe('DragDropBuckets', () => { - it('should call onDragEnd when dragging ended with reordered items', () => { - const items = [
first
,
second
,
third
]; - const defaultProps = { - items, - onDragStart: jest.fn(), - onDragEnd: jest.fn(), - droppableId: 'TEST_ID', - children: items, - }; - const instance = shallow(); - act(() => { - // simulate dragging ending - instance.props().onDragEnd({ source: { index: 0 }, destination: { index: 1 } }); - }); - - expect(defaultProps.onDragEnd).toHaveBeenCalledWith([ -
second
, -
first
, -
third
, - ]); - }); - }); - describe('DraggableBucketContainer', () => { - const defaultProps = { - isInvalid: false, - invalidMessage: 'invalid', - onRemoveClick: jest.fn(), - removeTitle: 'remove', - children:
popover
, - id: '0', - idx: 0, - }; - it('should render valid component', () => { - const instance = mount(); - const popover = instance.find('[data-test-subj="popover"]'); - expect(popover).toHaveLength(1); - }); - it('should render invalid component', () => { - const instance = mount(); - const iconProps = instance.find(EuiIcon).first().props(); - expect(iconProps.color).toEqual('danger'); - expect(iconProps.type).toEqual('alert'); - expect(iconProps.title).toEqual('invalid'); - }); - it('should call onRemoveClick when remove icon is clicked', () => { - const instance = mount(); - const removeIcon = instance - .find('[data-test-subj="lns-customBucketContainer-remove"]') - .first(); - removeIcon.simulate('click'); - expect(defaultProps.onRemoveClick).toHaveBeenCalled(); - }); - }); -}); diff --git a/x-pack/plugins/lens/public/shared_components/drag_drop_bucket/buckets.tsx b/x-pack/plugins/lens/public/shared_components/drag_drop_bucket/buckets.tsx deleted file mode 100644 index 0f4ba342348cf..0000000000000 --- a/x-pack/plugins/lens/public/shared_components/drag_drop_bucket/buckets.tsx +++ /dev/null @@ -1,160 +0,0 @@ -/* - * 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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { i18n } from '@kbn/i18n'; -import { - EuiFlexGroup, - EuiFlexItem, - EuiPanel, - EuiButtonIcon, - EuiIcon, - EuiDragDropContext, - euiDragDropReorder, - EuiDraggable, - EuiDroppable, - EuiButtonEmpty, -} from '@elastic/eui'; - -export const NewBucketButton = ({ - label, - onClick, - ['data-test-subj']: dataTestSubj, - isDisabled, -}: { - label: string; - onClick: () => void; - 'data-test-subj'?: string; - isDisabled?: boolean; -}) => ( - - {label} - -); - -interface BucketContainerProps { - isInvalid?: boolean; - invalidMessage: string; - onRemoveClick: () => void; - removeTitle: string; - isNotRemovable?: boolean; - children: React.ReactNode; - dataTestSubj?: string; -} - -const BucketContainer = ({ - isInvalid, - invalidMessage, - onRemoveClick, - removeTitle, - children, - dataTestSubj, - isNotRemovable, -}: BucketContainerProps) => { - return ( - - - {/* Empty for spacing */} - - - - {children} - - - - - - ); -}; - -export const DraggableBucketContainer = ({ - id, - idx, - children, - ...bucketContainerProps -}: { - id: string; - idx: number; - children: React.ReactNode; -} & BucketContainerProps) => { - return ( - - {(provided) => {children}} - - ); -}; - -interface DraggableLocation { - droppableId: string; - index: number; -} - -export const DragDropBuckets = ({ - items, - onDragStart, - onDragEnd, - droppableId, - children, -}: { - items: any; // eslint-disable-line @typescript-eslint/no-explicit-any - onDragStart: () => void; - onDragEnd: (items: any) => void; // eslint-disable-line @typescript-eslint/no-explicit-any - droppableId: string; - children: React.ReactElement[]; -}) => { - const handleDragEnd = ({ - source, - destination, - }: { - source?: DraggableLocation; - destination?: DraggableLocation; - }) => { - if (source && destination) { - const newItems = euiDragDropReorder(items, source.index, destination.index); - onDragEnd(newItems); - } - }; - return ( - - - {children} - - - ); -}; diff --git a/x-pack/plugins/lens/public/shared_components/index.ts b/x-pack/plugins/lens/public/shared_components/index.ts index a4e6bf046b36d..c22f071ceede3 100644 --- a/x-pack/plugins/lens/public/shared_components/index.ts +++ b/x-pack/plugins/lens/public/shared_components/index.ts @@ -12,11 +12,6 @@ export { PalettePicker } from './palette_picker'; export { FieldPicker, LensFieldIcon, TruncatedLabel } from './field_picker'; export type { FieldOption, FieldOptionValue } from './field_picker'; export { ChangeIndexPattern, fieldExists, fieldContainsData } from './dataview_picker'; -export { - NewBucketButton, - DraggableBucketContainer, - DragDropBuckets, -} from './drag_drop_bucket/buckets'; export { RangeInputField } from './range_input_field'; export { BucketAxisBoundsControl, diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index 5141834d14e80..163174abe6125 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -262,8 +262,7 @@ export interface Datasource { savedObjectReferences?: SavedObjectReference[], initialContext?: VisualizeFieldContext | VisualizeEditorContext, indexPatternRefs?: IndexPatternRef[], - indexPatterns?: IndexPatternMap, - options?: InitializationOptions + indexPatterns?: IndexPatternMap ) => T; // Given the current state, which parts should be saved? From 12b91fc7e97f5658542326d62085bd5c265f7f29 Mon Sep 17 00:00:00 2001 From: dej611 Date: Thu, 28 Jul 2022 18:14:35 +0200 Subject: [PATCH 09/27] :label: Fix types and tests --- .../__snapshots__/app.test.tsx.snap | 8 + .../app_plugin/show_underlying_data.test.ts | 11 +- .../buttons/drop_targets_utils.test.tsx | 5 + .../config_panel/config_panel.test.tsx | 2 + .../config_panel/layer_panel.test.tsx | 3 + .../config_panel/layer_settings.test.tsx | 1 + .../editor_frame/data_panel_wrapper.test.tsx | 8 +- .../editor_frame/editor_frame.test.tsx | 4 + .../editor_frame/suggestion_helpers.test.ts | 27 +- .../public/embeddable/embeddable.test.tsx | 42 +- .../datapanel.test.tsx | 96 ++- .../bucket_nesting_editor.test.tsx | 2 +- .../dimension_panel/dimension_panel.test.tsx | 113 ++- .../droppable/get_drop_props.test.ts | 12 +- .../droppable/on_drop_handler.test.ts | 36 +- .../dimension_panel/field_input.test.tsx | 7 +- .../dimension_panel/filtering.tsx | 3 +- .../field_item.test.tsx | 2 +- .../fields_accordion.test.tsx | 2 +- .../indexpattern.test.ts | 279 +++---- .../indexpattern_suggestions.test.tsx | 774 ++++++++++-------- .../layerpanel.test.tsx | 71 +- .../public/indexpattern_datasource/mocks.ts | 2 +- .../operations/definitions.test.ts | 3 +- .../definitions/date_histogram.test.tsx | 3 +- .../formula/editor/math_completion.test.ts | 3 +- .../definitions/formula/formula.test.tsx | 2 +- .../formula/formula_public_api.test.ts | 2 +- .../operations/definitions/index.ts | 15 +- .../definitions/last_value.test.tsx | 3 +- .../definitions/percentile.test.tsx | 3 +- .../definitions/percentile_ranks.test.tsx | 3 +- .../definitions/ranges/ranges.test.tsx | 4 +- .../definitions/static_value.test.tsx | 3 +- .../definitions/terms/terms.test.tsx | 9 +- .../operations/layer_helpers.test.ts | 2 +- .../pure_helpers.test.ts | 16 - .../public/mocks/data_views_service_mock.ts | 19 + .../lens/public/mocks/datasource_mock.ts | 17 +- x-pack/plugins/lens/public/mocks/index.ts | 12 + .../plugins/lens/public/mocks/store_mocks.tsx | 6 + .../xy_visualization/to_expression.test.ts | 12 +- .../lens/public/xy_visualization/types.ts | 1 - .../xy_visualization/visualization.test.ts | 10 +- .../public/xy_visualization/visualization.tsx | 2 +- .../visualization_helpers.tsx | 1 - .../xy_visualization/xy_suggestions.test.ts | 9 +- 47 files changed, 921 insertions(+), 749 deletions(-) delete mode 100644 x-pack/plugins/lens/public/indexpattern_datasource/pure_helpers.test.ts create mode 100644 x-pack/plugins/lens/public/mocks/data_views_service_mock.ts diff --git a/x-pack/plugins/lens/public/app_plugin/__snapshots__/app.test.tsx.snap b/x-pack/plugins/lens/public/app_plugin/__snapshots__/app.test.tsx.snap index ca14bc908eb80..bfdc82ff3b7c4 100644 --- a/x-pack/plugins/lens/public/app_plugin/__snapshots__/app.test.tsx.snap +++ b/x-pack/plugins/lens/public/app_plugin/__snapshots__/app.test.tsx.snap @@ -4,6 +4,14 @@ exports[`Lens App renders the editor frame 1`] = ` Array [ Array [ Object { + "indexPatternService": Object { + "ensureIndexPattern": [Function], + "getDefaultIndex": [Function], + "loadIndexPatternRefs": [Function], + "loadIndexPatterns": [Function], + "refreshExistingFields": [Function], + "updateIndexPatternsCache": [Function], + }, "lensInspector": Object { "adapters": Object { "expression": ExpressionsInspectorAdapter { diff --git a/x-pack/plugins/lens/public/app_plugin/show_underlying_data.test.ts b/x-pack/plugins/lens/public/app_plugin/show_underlying_data.test.ts index 99bc4fa0f9717..8a8c7bbe1f973 100644 --- a/x-pack/plugins/lens/public/app_plugin/show_underlying_data.test.ts +++ b/x-pack/plugins/lens/public/app_plugin/show_underlying_data.test.ts @@ -21,6 +21,7 @@ describe('getLayerMetaInfo', () => { createMockDatasource('testDatasource'), {}, undefined, + {}, undefined, capabilities ).error @@ -36,6 +37,7 @@ describe('getLayerMetaInfo', () => { datatable1: { type: 'datatable', columns: [], rows: [] }, datatable2: { type: 'datatable', columns: [], rows: [] }, }, + {}, undefined, capabilities ).error @@ -43,7 +45,7 @@ describe('getLayerMetaInfo', () => { }); it('should return error in case of missing activeDatasource', () => { - expect(getLayerMetaInfo(undefined, {}, undefined, undefined, capabilities).error).toBe( + expect(getLayerMetaInfo(undefined, {}, undefined, {}, undefined, capabilities).error).toBe( 'Visualization has no data available to show' ); }); @@ -54,6 +56,7 @@ describe('getLayerMetaInfo', () => { createMockDatasource('testDatasource'), undefined, {}, + {}, undefined, capabilities ).error @@ -81,7 +84,7 @@ describe('getLayerMetaInfo', () => { }; mockDatasource.getPublicAPI.mockReturnValue(updatedPublicAPI); expect( - getLayerMetaInfo(createMockDatasource('testDatasource'), {}, {}, undefined, capabilities) + getLayerMetaInfo(createMockDatasource('testDatasource'), {}, {}, {}, undefined, capabilities) .error ).toBe('Visualization has no data available to show'); }); @@ -105,6 +108,7 @@ describe('getLayerMetaInfo', () => { { datatable1: { type: 'datatable', columns: [], rows: [] }, }, + {}, undefined, capabilities ).error @@ -120,6 +124,7 @@ describe('getLayerMetaInfo', () => { { datatable1: { type: 'datatable', columns: [], rows: [] }, }, + {}, undefined, { navLinks: { discover: false }, @@ -134,6 +139,7 @@ describe('getLayerMetaInfo', () => { { datatable1: { type: 'datatable', columns: [], rows: [] }, }, + {}, undefined, { navLinks: { discover: true }, @@ -167,6 +173,7 @@ describe('getLayerMetaInfo', () => { { datatable1: { type: 'datatable', columns: [], rows: [] }, }, + {}, undefined, capabilities ); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/buttons/drop_targets_utils.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/buttons/drop_targets_utils.test.tsx index dd5ec847fb5b5..17907ac19c4bc 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/buttons/drop_targets_utils.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/buttons/drop_targets_utils.test.tsx @@ -27,6 +27,7 @@ describe('getDropProps', () => { id: 'annotationColumn2', humanData: { label: 'Event' }, }, + indexPatterns: {}, }, mockDatasource ); @@ -50,6 +51,7 @@ describe('getDropProps', () => { id: 'annotationColumn2', humanData: { label: 'Event' }, }, + indexPatterns: {}, }) ).toEqual({ dropTypes: ['reorder'] }); }); @@ -71,6 +73,7 @@ describe('getDropProps', () => { id: 'annotationColumn2', humanData: { label: 'Event' }, }, + indexPatterns: {}, }) ).toEqual({ dropTypes: ['duplicate_compatible'] }); }); @@ -91,6 +94,7 @@ describe('getDropProps', () => { id: 'annotationColumn2', humanData: { label: 'Event' }, }, + indexPatterns: {}, }) ).toEqual({ dropTypes: ['replace_compatible', 'replace_duplicate_compatible', 'swap_compatible'], @@ -114,6 +118,7 @@ describe('getDropProps', () => { id: 'annotationColumn2', humanData: { label: 'Event' }, }, + indexPatterns: {}, }) ).toEqual({ dropTypes: ['move_compatible', 'duplicate_compatible'], diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.test.tsx index a13b6b480a900..ef461b7d9d302 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.test.tsx @@ -25,6 +25,7 @@ import { mountWithProvider } from '../../../mocks'; import { LayerType, layerTypes } from '../../../../common'; import { ReactWrapper } from 'enzyme'; import { addLayer } from '../../../state_management'; +import { createIndexPatternServiceMock } from '../../../mocks/data_views_service_mock'; jest.mock('../../../id_generator'); @@ -107,6 +108,7 @@ describe('ConfigPanel', () => { state: 'state', }, }, + indexPatternService: createIndexPatternServiceMock(), visualizationState: 'state', updateVisualization: jest.fn(), updateDatasource: jest.fn(), diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx index 49a2bec8cda7c..0440700c7ed8b 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx @@ -20,6 +20,7 @@ import { DatasourceMock, mountWithProvider, } from '../../../mocks'; +import { createIndexPatternServiceMock } from '../../../mocks/data_views_service_mock'; jest.mock('../../../id_generator'); @@ -96,6 +97,8 @@ describe('LayerPanel', () => { isFullscreen: false, toggleFullscreen: jest.fn(), onEmptyDimensionAdd: jest.fn(), + onChangeIndexPattern: jest.fn(), + indexPatternService: createIndexPatternServiceMock(), }; } diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_settings.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_settings.test.tsx index 04c430143a3c8..3daea87dad39d 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_settings.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_settings.test.tsx @@ -28,6 +28,7 @@ describe('LayerSettings', () => { dateRange: { fromDate: 'now-7d', toDate: 'now' }, activeData: frame.activeData, setState: jest.fn(), + onChangeIndexPattern: jest.fn(), }, }; } diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/data_panel_wrapper.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/data_panel_wrapper.test.tsx index cd7d325d5830d..63e4f88d6d234 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/data_panel_wrapper.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/data_panel_wrapper.test.tsx @@ -10,9 +10,11 @@ import { DataPanelWrapper } from './data_panel_wrapper'; import { Datasource, DatasourceDataPanelProps } from '../../types'; import { DragDropIdentifier } from '../../drag_drop'; import { UiActionsStart } from '@kbn/ui-actions-plugin/public'; -import { mockStoreDeps, mountWithProvider } from '../../mocks'; +import { createMockFramePublicAPI, mockStoreDeps, mountWithProvider } from '../../mocks'; import { disableAutoApply } from '../../state_management/lens_slice'; import { selectTriggerApplyChanges } from '../../state_management'; +import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; +import { createIndexPatternServiceMock } from '../../mocks/data_views_service_mock'; describe('Data Panel Wrapper', () => { describe('Datasource data panel properties', () => { @@ -34,7 +36,9 @@ describe('Data Panel Wrapper', () => { core={{} as DatasourceDataPanelProps['core']} dropOntoWorkspace={(field: DragDropIdentifier) => {}} hasSuggestionForField={(field: DragDropIdentifier) => true} - plugins={{ uiActions: {} as UiActionsStart }} + plugins={{ uiActions: {} as UiActionsStart, dataViews: {} as DataViewsPublicPluginStart }} + indexPatternService={createIndexPatternServiceMock()} + frame={createMockFramePublicAPI()} />, { preloadedState: { diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx index 047d8b4deffaa..035011b7d4a68 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx @@ -49,6 +49,8 @@ import { mockDataPlugin, mountWithProvider } from '../../mocks'; import { setState } from '../../state_management'; import { getLensInspectorService } from '../../lens_inspector_service'; import { toExpression } from '@kbn/interpreter'; +import { createIndexPatternServiceMock } from '../../mocks/data_views_service_mock'; +import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; function generateSuggestion(state = {}): DatasourceSuggestion { return { @@ -80,10 +82,12 @@ function getDefaultProps() { data: mockDataPlugin(), expressions: expressionsPluginMock.createStartContract(), charts: chartPluginMock.createStartContract(), + dataViews: dataViewPluginMocks.createStartContract(), }, palettes: chartPluginMock.createPaletteRegistry(), lensInspector: getLensInspectorService(inspectorPluginMock.createStartContract()), showNoDataPopover: jest.fn(), + indexPatternService: createIndexPatternServiceMock(), }; return defaultProps; } diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.test.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.test.ts index a265f534627b6..b739091ddff3b 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.test.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.test.ts @@ -7,14 +7,19 @@ import type { PaletteOutput } from '@kbn/coloring'; import { getSuggestions, getTopSuggestionForField } from './suggestion_helpers'; -import { createMockVisualization, createMockDatasource, DatasourceMock } from '../../mocks'; +import { + createMockVisualization, + createMockDatasource, + DatasourceMock, + createMockFramePublicAPI, +} from '../../mocks'; import { TableSuggestion, DatasourceSuggestion, Visualization, VisualizeEditorContext, } from '../../types'; -import { DatasourceStates } from '../../state_management'; +import { DatasourceStates, DataViewsState } from '../../state_management'; const generateSuggestion = (state = {}, layerId: string = 'first'): DatasourceSuggestion => ({ state, @@ -29,6 +34,7 @@ const generateSuggestion = (state = {}, layerId: string = 'first'): DatasourceSu let datasourceMap: Record; let datasourceStates: DatasourceStates; +let dataViews: DataViewsState; beforeEach(() => { datasourceMap = { @@ -41,6 +47,8 @@ beforeEach(() => { state: {}, }, }; + + dataViews = createMockFramePublicAPI().dataViews; }); describe('suggestion helpers', () => { @@ -69,6 +77,7 @@ describe('suggestion helpers', () => { visualizationState: {}, datasourceMap, datasourceStates, + dataViews, }); expect(suggestions).toHaveLength(1); expect(suggestions[0].visualizationState).toBe(suggestedState); @@ -116,6 +125,7 @@ describe('suggestion helpers', () => { visualizationState: {}, datasourceMap, datasourceStates, + dataViews, }); expect(suggestions).toHaveLength(3); }); @@ -133,6 +143,7 @@ describe('suggestion helpers', () => { datasourceMap, datasourceStates, field: droppedField, + dataViews, }); expect(datasourceMap.mock.getDatasourceSuggestionsForField).toHaveBeenCalledWith( datasourceStates.mock.state, @@ -168,6 +179,7 @@ describe('suggestion helpers', () => { datasourceMap: multiDatasourceMap, datasourceStates: multiDatasourceStates, field: droppedField, + dataViews, }); expect(multiDatasourceMap.mock.getDatasourceSuggestionsForField).toHaveBeenCalledWith( multiDatasourceStates.mock.state, @@ -201,6 +213,7 @@ describe('suggestion helpers', () => { indexPatternId: '1', fieldName: 'test', }, + dataViews, }); expect(datasourceMap.mock.getDatasourceSuggestionsForVisualizeField).toHaveBeenCalledWith( datasourceStates.mock.state, @@ -240,6 +253,7 @@ describe('suggestion helpers', () => { datasourceMap: multiDatasourceMap, datasourceStates: multiDatasourceStates, visualizeTriggerFieldContext: visualizeTriggerField, + dataViews, }); expect(multiDatasourceMap.mock.getDatasourceSuggestionsForVisualizeField).toHaveBeenCalledWith( multiDatasourceStates.mock.state, @@ -320,6 +334,7 @@ describe('suggestion helpers', () => { datasourceMap, datasourceStates, visualizeTriggerFieldContext: triggerContext, + dataViews, }); expect(datasourceMap.mock.getDatasourceSuggestionsForVisualizeCharts).toHaveBeenCalledWith( datasourceStates.mock.state, @@ -402,6 +417,7 @@ describe('suggestion helpers', () => { datasourceMap: multiDatasourceMap, datasourceStates: multiDatasourceStates, visualizeTriggerFieldContext: triggerContext, + dataViews, }); expect(multiDatasourceMap.mock.getDatasourceSuggestionsForVisualizeCharts).toHaveBeenCalledWith( datasourceStates.mock.state, @@ -458,6 +474,7 @@ describe('suggestion helpers', () => { visualizationState: {}, datasourceMap, datasourceStates, + dataViews, }); expect(suggestions[0].score).toBe(0.8); expect(suggestions[1].score).toBe(0.6); @@ -493,6 +510,7 @@ describe('suggestion helpers', () => { visualizationState: {}, datasourceMap, datasourceStates, + dataViews, }); expect(mockVisualization1.getSuggestions.mock.calls[0][0].table).toEqual(table1); expect(mockVisualization1.getSuggestions.mock.calls[1][0].table).toEqual(table2); @@ -553,6 +571,7 @@ describe('suggestion helpers', () => { visualizationState: {}, datasourceMap, datasourceStates, + dataViews, }); expect(suggestions[0].datasourceState).toBe(tableState1); expect(suggestions[0].datasourceId).toBe('mock'); @@ -582,6 +601,7 @@ describe('suggestion helpers', () => { visualizationState: {}, datasourceMap, datasourceStates, + dataViews, }); expect(mockVisualization1.getSuggestions).toHaveBeenCalledWith( expect.objectContaining({ @@ -614,6 +634,7 @@ describe('suggestion helpers', () => { datasourceMap, datasourceStates, mainPalette, + dataViews, }); expect(mockVisualization1.getSuggestions).toHaveBeenCalledWith( expect.objectContaining({ @@ -647,6 +668,7 @@ describe('suggestion helpers', () => { visualizationState: {}, datasourceMap, datasourceStates, + dataViews, }); expect(mockVisualization1.getMainPalette).toHaveBeenCalledWith({}); expect(mockVisualization2.getSuggestions).toHaveBeenCalledWith( @@ -716,6 +738,7 @@ describe('suggestion helpers', () => { { testVis: mockVisualization1 }, datasourceMap.mock, { id: 'myfield', humanData: { label: 'myfieldLabel' } }, + dataViews, ]; }); diff --git a/x-pack/plugins/lens/public/embeddable/embeddable.test.tsx b/x-pack/plugins/lens/public/embeddable/embeddable.test.tsx index 6ee840d2f605f..9ff0d5a664ad3 100644 --- a/x-pack/plugins/lens/public/embeddable/embeddable.test.tsx +++ b/x-pack/plugins/lens/public/embeddable/embeddable.test.tsx @@ -144,7 +144,7 @@ describe('embeddable', () => { data: dataMock, expressionRenderer, basePath, - indexPatternService: {} as DataViewsContract, + dataViews: {} as DataViewsContract, capabilities: { canSaveDashboards: true, canSaveVisualizations: true, @@ -196,7 +196,7 @@ describe('embeddable', () => { uiSettings: { get: () => undefined } as unknown as IUiSettingsClient, expressionRenderer, basePath, - indexPatternService: {} as DataViewsContract, + dataViews: {} as DataViewsContract, inspector: inspectorPluginMock.createStartContract(), capabilities: { canSaveDashboards: true, @@ -249,7 +249,7 @@ describe('embeddable', () => { expressionRenderer, basePath, inspector: inspectorPluginMock.createStartContract(), - indexPatternService: {} as DataViewsContract, + dataViews: {} as DataViewsContract, capabilities: { canSaveDashboards: true, canSaveVisualizations: true, @@ -311,7 +311,7 @@ describe('embeddable', () => { inspector: inspectorPluginMock.createStartContract(), expressionRenderer, basePath, - indexPatternService: {} as DataViewsContract, + dataViews: {} as DataViewsContract, spaces: spacesPluginStart, capabilities: { canSaveDashboards: true, @@ -362,7 +362,7 @@ describe('embeddable', () => { expressionRenderer, basePath, inspector: inspectorPluginMock.createStartContract(), - indexPatternService: { + dataViews: { get: (id: string) => Promise.resolve({ id, isTimeBased: jest.fn(() => true) }), } as unknown as DataViewsContract, capabilities: { @@ -413,7 +413,7 @@ describe('embeddable', () => { expressionRenderer, basePath, inspector: inspectorPluginMock.createStartContract(), - indexPatternService: { + dataViews: { get: (id: string) => Promise.resolve({ id, isTimeBased: () => false }), } as unknown as DataViewsContract, capabilities: { @@ -462,7 +462,7 @@ describe('embeddable', () => { expressionRenderer, basePath, inspector: inspectorPluginMock.createStartContract(), - indexPatternService: {} as DataViewsContract, + dataViews: {} as DataViewsContract, capabilities: { canSaveDashboards: true, canSaveVisualizations: true, @@ -515,7 +515,7 @@ describe('embeddable', () => { expressionRenderer, basePath, inspector: inspectorPluginMock.createStartContract(), - indexPatternService: {} as DataViewsContract, + dataViews: {} as DataViewsContract, capabilities: { canSaveDashboards: true, canSaveVisualizations: true, @@ -572,7 +572,7 @@ describe('embeddable', () => { expressionRenderer, basePath, inspector: inspectorPluginMock.createStartContract(), - indexPatternService: {} as DataViewsContract, + dataViews: {} as DataViewsContract, capabilities: { canSaveDashboards: true, canSaveVisualizations: true, @@ -627,7 +627,7 @@ describe('embeddable', () => { expressionRenderer, basePath, inspector: inspectorPluginMock.createStartContract(), - indexPatternService: {} as DataViewsContract, + dataViews: {} as DataViewsContract, capabilities: { canSaveDashboards: true, canSaveVisualizations: true, @@ -689,7 +689,7 @@ describe('embeddable', () => { expressionRenderer, basePath, inspector: inspectorPluginMock.createStartContract(), - indexPatternService: {} as DataViewsContract, + dataViews: {} as DataViewsContract, capabilities: { canSaveDashboards: true, canSaveVisualizations: true, @@ -752,7 +752,7 @@ describe('embeddable', () => { expressionRenderer, basePath, inspector: inspectorPluginMock.createStartContract(), - indexPatternService: {} as DataViewsContract, + dataViews: {} as DataViewsContract, capabilities: { canSaveDashboards: true, canSaveVisualizations: true, @@ -818,7 +818,7 @@ describe('embeddable', () => { expressionRenderer, basePath, inspector: inspectorPluginMock.createStartContract(), - indexPatternService: { get: jest.fn() } as unknown as DataViewsContract, + dataViews: { get: jest.fn() } as unknown as DataViewsContract, capabilities: { canSaveDashboards: true, canSaveVisualizations: true, @@ -869,7 +869,7 @@ describe('embeddable', () => { expressionRenderer, basePath, inspector: inspectorPluginMock.createStartContract(), - indexPatternService: {} as DataViewsContract, + dataViews: {} as DataViewsContract, capabilities: { canSaveDashboards: true, canSaveVisualizations: true, @@ -922,7 +922,7 @@ describe('embeddable', () => { expressionRenderer, basePath, inspector: inspectorPluginMock.createStartContract(), - indexPatternService: {} as DataViewsContract, + dataViews: {} as DataViewsContract, capabilities: { canSaveDashboards: true, canSaveVisualizations: true, @@ -972,7 +972,7 @@ describe('embeddable', () => { expressionRenderer, basePath, inspector: inspectorPluginMock.createStartContract(), - indexPatternService: {} as DataViewsContract, + dataViews: {} as DataViewsContract, capabilities: { canSaveDashboards: true, canSaveVisualizations: true, @@ -1037,7 +1037,7 @@ describe('embeddable', () => { expressionRenderer, basePath, inspector: inspectorPluginMock.createStartContract(), - indexPatternService: {} as DataViewsContract, + dataViews: {} as DataViewsContract, capabilities: { canSaveDashboards: true, canSaveVisualizations: true, @@ -1121,7 +1121,7 @@ describe('embeddable', () => { expressionRenderer, basePath, inspector: inspectorPluginMock.createStartContract(), - indexPatternService: {} as DataViewsContract, + dataViews: {} as DataViewsContract, capabilities: { canSaveDashboards: true, canSaveVisualizations: true, @@ -1180,7 +1180,7 @@ describe('embeddable', () => { expressionRenderer, basePath, inspector: inspectorPluginMock.createStartContract(), - indexPatternService: {} as DataViewsContract, + dataViews: {} as DataViewsContract, capabilities: { canSaveDashboards: true, canSaveVisualizations: true, @@ -1236,7 +1236,7 @@ describe('embeddable', () => { expressionRenderer, basePath, inspector: inspectorPluginMock.createStartContract(), - indexPatternService: {} as DataViewsContract, + dataViews: {} as DataViewsContract, capabilities: { canSaveDashboards: true, canSaveVisualizations: true, @@ -1313,7 +1313,7 @@ describe('embeddable', () => { expressionRenderer, basePath, inspector: inspectorPluginMock.createStartContract(), - indexPatternService: {} as DataViewsContract, + dataViews: {} as DataViewsContract, capabilities: { canSaveDashboards: true, canSaveVisualizations: true, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx index 2bd1601eefc85..0578c3ac7a47a 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx @@ -27,6 +27,9 @@ import { getFieldByNameFactory } from './pure_helpers'; import { uiActionsPluginMock } from '@kbn/ui-actions-plugin/public/mocks'; import { TermsIndexPatternColumn } from './operations'; import { DOCUMENT_FIELD_NAME } from '../../common'; +import { createIndexPatternServiceMock } from '../mocks/data_views_service_mock'; +import { createMockFramePublicAPI } from '../mocks'; +import { DataViewsState } from '../state_management'; const fieldsOne = [ { @@ -153,8 +156,6 @@ const fieldsThree = [ ]; const initialState: IndexPatternPrivateState = { - indexPatternRefs: [], - existingFields: {}, currentIndexPatternId: '1', layers: { first: { @@ -212,7 +213,11 @@ const initialState: IndexPatternPrivateState = { }, }, }, - indexPatterns: { +}; + +function getFrameAPIMock({ indexPatterns, existingFields, ...rest }: Partial = {}) { + const frameAPI = createMockFramePublicAPI(); + const defaultIndexPatterns = { '1': { id: '1', title: 'idx1', @@ -237,9 +242,18 @@ const initialState: IndexPatternPrivateState = { fields: fieldsThree, getFieldByName: getFieldByNameFactory(fieldsThree), }, - }, - isFirstExistenceFetch: false, -}; + }; + return { + ...frameAPI, + dataViews: { + ...frameAPI.dataViews, + indexPatterns: indexPatterns ?? defaultIndexPatterns, + existingFields: existingFields ?? {}, + isFirstExistenceFetch: false, + ...rest, + }, + }; +} const dslQuery = { bool: { must: [], filter: [], should: [], must_not: [] } }; @@ -255,17 +269,14 @@ describe('IndexPattern Data Panel', () => { beforeEach(() => { core = coreMock.createStart(); defaultProps = { - indexPatternRefs: [], - existingFields: {}, data: dataPluginMock.createStartContract(), dataViews: dataViewPluginMocks.createStartContract(), fieldFormats: fieldFormatsServiceMock.createStartContract(), indexPatternFieldEditor: indexPatternFieldEditorPluginMock.createStartContract(), - onUpdateIndexPattern: jest.fn(), + onChangeIndexPattern: jest.fn(), + onIndexPatternRefresh: jest.fn(), dragDropContext: createMockedDragDropContext(), currentIndexPatternId: '1', - indexPatterns: initialState.indexPatterns, - onChangeIndexPattern: jest.fn(), core, dateRange: { fromDate: 'now-7d', @@ -278,6 +289,8 @@ describe('IndexPattern Data Panel', () => { dropOntoWorkspace: jest.fn(), hasSuggestionForField: jest.fn(() => false), uiActions: uiActionsPluginMock.createStartContract(), + indexPatternService: createIndexPatternServiceMock(), + frame: getFrameAPIMock(), }; }); @@ -313,7 +326,6 @@ describe('IndexPattern Data Panel', () => { state={{ ...initialState, currentIndexPatternId: '', - indexPatterns: {}, }} setState={jest.fn()} dragDropContext={{ @@ -321,6 +333,7 @@ describe('IndexPattern Data Panel', () => { dragging: { id: '1', humanData: { label: 'Label' } }, }} changeIndexPattern={jest.fn()} + frame={createMockFramePublicAPI()} /> ); expect(wrapper.find('[data-test-subj="indexPattern-no-indexpatterns"]')).toHaveLength(1); @@ -623,12 +636,14 @@ describe('IndexPattern Data Panel', () => { beforeEach(() => { props = { ...defaultProps, - existingFields: { - idx1: { - bytes: true, - memory: true, + frame: getFrameAPIMock({ + existingFields: { + idx1: { + bytes: true, + memory: true, + }, }, - }, + }), }; }); it('should list all supported fields in the pattern sorted alphabetically in groups', async () => { @@ -658,22 +673,24 @@ describe('IndexPattern Data Panel', () => { const wrapper = mountWithIntl( ); wrapper @@ -692,7 +709,10 @@ describe('IndexPattern Data Panel', () => { it('should display NoFieldsCallout when all fields are empty', async () => { const wrapper = mountWithIntl( - + ); expect(wrapper.find(NoFieldsCallout).length).toEqual(2); expect( @@ -726,7 +746,10 @@ describe('IndexPattern Data Panel', () => { it('should not allow field details when error', () => { const wrapper = mountWithIntl( - + ); expect(wrapper.find(FieldList).prop('fieldGroups')).toEqual( @@ -738,7 +761,10 @@ describe('IndexPattern Data Panel', () => { it('should allow field details when timeout', () => { const wrapper = mountWithIntl( - + ); expect(wrapper.find(FieldList).prop('fieldGroups')).toEqual( diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/bucket_nesting_editor.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/bucket_nesting_editor.test.tsx index f876536ebba43..248cc0f78756e 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/bucket_nesting_editor.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/bucket_nesting_editor.test.tsx @@ -9,7 +9,7 @@ import { mount } from 'enzyme'; import React from 'react'; import { BucketNestingEditor } from './bucket_nesting_editor'; import { GenericIndexPatternColumn } from '../indexpattern'; -import { IndexPatternField } from '../../editor_frame_service/types'; +import { IndexPatternField } from '../../types'; const fieldMap: Record = { a: { displayName: 'a' } as IndexPatternField, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx index 291ccb1ede76b..a23f35f4cd53a 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx @@ -157,18 +157,7 @@ describe('IndexPatternDimensionEditorPanel', () => { beforeEach(() => { state = { - indexPatternRefs: [], - indexPatterns: expectedIndexPatterns, currentIndexPatternId: '1', - isFirstExistenceFetch: false, - existingFields: { - 'my-fake-index-pattern': { - timestamp: true, - bytes: true, - memory: true, - source: true, - }, - }, layers: { first: { indexPatternId: '1', @@ -202,6 +191,15 @@ describe('IndexPatternDimensionEditorPanel', () => { }); defaultProps = { + indexPatterns: expectedIndexPatterns, + existingFields: { + 'my-fake-index-pattern': { + timestamp: true, + bytes: true, + memory: true, + source: true, + }, + }, state, setState, dateRange: { fromDate: 'now-1d', toDate: 'now' }, @@ -1309,13 +1307,10 @@ describe('IndexPatternDimensionEditorPanel', () => { wrapper = mount( @@ -1636,34 +1631,31 @@ describe('IndexPatternDimensionEditorPanel', () => { }); it('should select operation directly if only one field is possible', () => { - const initialState = { - ...state, - indexPatterns: { - 1: { - ...state.indexPatterns['1'], - fields: state.indexPatterns['1'].fields.filter((field) => field.name !== 'memory'), - }, - }, - }; - wrapper = mount( field.name !== 'memory' + ), + }, + }} /> ); wrapper.find('button[data-test-subj="lns-indexPatternDimension-average"]').simulate('click'); expect(setState.mock.calls[0]).toEqual([expect.any(Function), { isDimensionComplete: true }]); - expect(setState.mock.calls[0][0](initialState)).toEqual({ - ...initialState, + expect(setState.mock.calls[0][0](state)).toEqual({ + ...state, layers: { first: { - ...initialState.layers.first, + ...state.layers.first, columns: { - ...initialState.layers.first.columns, + ...state.layers.first.columns, col2: expect.objectContaining({ sourceField: 'bytes', operationType: 'average', @@ -2071,35 +2063,38 @@ describe('IndexPatternDimensionEditorPanel', () => { sourceField: 'bytes', }, }), - indexPatterns: { - 1: { - id: '1', - title: 'my-fake-index-pattern', - hasRestrictions: false, - fields: [ - { - name: 'bytes', - displayName: 'bytes', - type: 'number', - aggregatable: true, - searchable: true, - }, - ], - getFieldByName: getFieldByNameFactory([ - { - name: 'bytes', - displayName: 'bytes', - type: 'number', - aggregatable: true, - searchable: true, - }, - ]), - }, - }, }; wrapper = mount( - + ); expect(wrapper.find('[data-test-subj="lns-indexPatternDimension-differences"]')).toHaveLength( diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable/get_drop_props.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable/get_drop_props.test.ts index f48e4d897a637..2f4d8a0121aa1 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable/get_drop_props.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable/get_drop_props.test.ts @@ -18,19 +18,9 @@ import { import { generateId } from '../../../id_generator'; const getDefaultProps = () => ({ + indexPatterns: mockDataViews(), state: { - indexPatternRefs: [], - indexPatterns: mockDataViews(), currentIndexPatternId: 'first', - isFirstExistenceFetch: false, - existingFields: { - first: { - timestamp: true, - bytes: true, - memory: true, - source: true, - }, - }, layers: { first: mockedLayers.doubleColumnLayer(), second: mockedLayers.emptyLayer() }, }, target: mockedDndOperations.notFiltering, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable/on_drop_handler.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable/on_drop_handler.test.ts index 12acf46c58380..3ae1672d9e464 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable/on_drop_handler.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable/on_drop_handler.test.ts @@ -69,18 +69,7 @@ describe('IndexPatternDimensionEditorPanel: onDrop', () => { beforeEach(() => { state = { - indexPatternRefs: [], - indexPatterns: mockDataViews(), currentIndexPatternId: 'first', - isFirstExistenceFetch: false, - existingFields: { - first: { - timestamp: true, - bytes: true, - memory: true, - source: true, - }, - }, layers: { first: mockedLayers.singleColumnLayer(), second: mockedLayers.emptyLayer(), @@ -96,6 +85,7 @@ describe('IndexPatternDimensionEditorPanel: onDrop', () => { state, setState, dimensionGroups: [], + indexPatterns: mockDataViews(), }; jest.clearAllMocks(); @@ -1506,19 +1496,9 @@ describe('IndexPatternDimensionEditorPanel: onDrop', () => { setState = jest.fn(); props = { + indexPatterns: mockDataViews(), state: { - indexPatternRefs: [], - indexPatterns: mockDataViews(), currentIndexPatternId: 'first', - isFirstExistenceFetch: false, - existingFields: { - first: { - timestamp: true, - bytes: true, - memory: true, - source: true, - }, - }, layers: { first: mockedLayers.singleColumnLayer(), second: mockedLayers.multipleColumnsLayer('col2', 'col3', 'col4', 'col5'), @@ -2062,6 +2042,7 @@ describe('IndexPatternDimensionEditorPanel: onDrop', () => { setState: jest.fn(), dropType: 'move_compatible', + indexPatterns: mockDataViews(), state: { layers: { first: { @@ -2114,18 +2095,7 @@ describe('IndexPatternDimensionEditorPanel: onDrop', () => { columnOrder: ['second', 'secondX0'], }, }, - indexPatternRefs: [], - indexPatterns: mockDataViews(), currentIndexPatternId: 'first', - isFirstExistenceFetch: false, - existingFields: { - first: { - timestamp: true, - bytes: true, - memory: true, - source: true, - }, - }, }, source: { columnId: 'firstColumn', diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/field_input.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/field_input.test.tsx index 730342ae694e8..8cdbc396cd9ab 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/field_input.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/field_input.test.tsx @@ -120,14 +120,13 @@ function getDefaultOperationSupportMatrix( return getOperationSupportMatrix({ state: { layers: { layer1: layer }, - indexPatterns: { - [defaultProps.indexPattern.id]: defaultProps.indexPattern, - }, - existingFields, } as unknown as IndexPatternPrivateState, layerId: 'layer1', filterOperations: () => true, columnId, + indexPatterns: { + [defaultProps.indexPattern.id]: defaultProps.indexPattern, + }, }); } diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/filtering.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/filtering.tsx index 80e369526c8e4..5117e935c59d6 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/filtering.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/filtering.tsx @@ -21,8 +21,9 @@ import type { Query } from '@kbn/es-query'; import { GenericIndexPatternColumn, operationDefinitionMap } from '../operations'; import { validateQuery } from '../operations/definitions/filters'; import { QueryInput } from '../query_input'; -import type { IndexPattern, IndexPatternLayer } from '../types'; +import type { IndexPatternLayer } from '../types'; import { useDebouncedValue } from '../../shared_components'; +import type { IndexPattern } from '../../types'; const filterByLabel = i18n.translate('xpack.lens.indexPattern.filterBy.label', { defaultMessage: 'Filter by', diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.test.tsx index 11d3d9c6a7871..3c3b81cd126df 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.test.tsx @@ -13,7 +13,7 @@ import { InnerFieldItem, FieldItemProps } from './field_item'; import { coreMock } from '@kbn/core/public/mocks'; import { mountWithIntl } from '@kbn/test-jest-helpers'; import { fieldFormatsServiceMock } from '@kbn/field-formats-plugin/public/mocks'; -import { IndexPattern } from './types'; +import { IndexPattern } from '../types'; import { chartPluginMock } from '@kbn/charts-plugin/public/mocks'; import { documentField } from './document_field'; import { uiActionsPluginMock } from '@kbn/ui-actions-plugin/public/mocks'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/fields_accordion.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/fields_accordion.test.tsx index cc79941d2cdfe..90be3a9f6e470 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/fields_accordion.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/fields_accordion.test.tsx @@ -10,7 +10,7 @@ import { EuiLoadingSpinner, EuiNotificationBadge } from '@elastic/eui'; import { coreMock } from '@kbn/core/public/mocks'; import { mountWithIntl, shallowWithIntl } from '@kbn/test-jest-helpers'; import { fieldFormatsServiceMock } from '@kbn/field-formats-plugin/public/mocks'; -import { IndexPattern } from './types'; +import { IndexPattern } from '../types'; import { FieldItem } from './field_item'; import { FieldsAccordion, FieldsAccordionProps, FieldItemSharedProps } from './fields_accordion'; import { chartPluginMock } from '@kbn/charts-plugin/public/mocks'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts index 8693f8492aebe..218d68a45de0d 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts @@ -171,16 +171,7 @@ type DataViewBaseState = Omit< 'indexPatternRefs' | 'indexPatterns' | 'existingFields' | 'isFirstExistenceFetch' >; -function enrichBaseState(baseState: DataViewBaseState): IndexPatternPrivateState { - return { - currentIndexPatternId: baseState.currentIndexPatternId, - layers: baseState.layers, - indexPatterns: expectedIndexPatterns, - indexPatternRefs: [], - existingFields: {}, - isFirstExistenceFetch: false, - }; -} +const indexPatterns = expectedIndexPatterns; describe('IndexPattern Data Source', () => { let baseState: Omit< @@ -275,9 +266,7 @@ describe('IndexPattern Data Source', () => { describe('#getPersistedState', () => { it('should persist from saved state', async () => { - const state = enrichBaseState(baseState); - - expect(indexPatternDatasource.getPersistableState(state)).toEqual({ + expect(indexPatternDatasource.getPersistableState(baseState)).toEqual({ state: { layers: { first: { @@ -310,8 +299,8 @@ describe('IndexPattern Data Source', () => { describe('#toExpression', () => { it('should generate an empty expression when no columns are selected', async () => { - const state = await indexPatternDatasource.initialize(); - expect(indexPatternDatasource.toExpression(state, 'first')).toEqual(null); + const state = indexPatternDatasource.initialize(); + expect(indexPatternDatasource.toExpression(state, 'first', indexPatterns)).toEqual(null); }); it('should create a table when there is a formula without aggs', async () => { @@ -334,8 +323,7 @@ describe('IndexPattern Data Source', () => { }, }, }; - const state = enrichBaseState(queryBaseState); - expect(indexPatternDatasource.toExpression(state, 'first')).toEqual({ + expect(indexPatternDatasource.toExpression(queryBaseState, 'first', indexPatterns)).toEqual({ chain: [ { function: 'createTable', @@ -382,9 +370,8 @@ describe('IndexPattern Data Source', () => { }, }; - const state = enrichBaseState(queryBaseState); - - expect(indexPatternDatasource.toExpression(state, 'first')).toMatchInlineSnapshot(` + expect(indexPatternDatasource.toExpression(queryBaseState, 'first', indexPatterns)) + .toMatchInlineSnapshot(` Object { "chain": Array [ Object { @@ -558,9 +545,11 @@ describe('IndexPattern Data Source', () => { }, }; - const state = enrichBaseState(queryBaseState); - - const ast = indexPatternDatasource.toExpression(state, 'first') as Ast; + const ast = indexPatternDatasource.toExpression( + queryBaseState, + 'first', + indexPatterns + ) as Ast; expect(ast.chain[1].arguments.timeFields).toEqual(['timestamp', 'another_datefield']); }); @@ -595,9 +584,11 @@ describe('IndexPattern Data Source', () => { }, }; - const state = enrichBaseState(queryBaseState); - - const ast = indexPatternDatasource.toExpression(state, 'first') as Ast; + const ast = indexPatternDatasource.toExpression( + queryBaseState, + 'first', + indexPatterns + ) as Ast; expect((ast.chain[1].arguments.aggs[1] as Ast).chain[0].arguments.timeShift).toEqual(['1d']); }); @@ -644,9 +635,11 @@ describe('IndexPattern Data Source', () => { }, }; - const state = enrichBaseState(queryBaseState); - - const ast = indexPatternDatasource.toExpression(state, 'first') as Ast; + const ast = indexPatternDatasource.toExpression( + queryBaseState, + 'first', + indexPatterns + ) as Ast; expect(ast.chain[1].arguments.aggs[0]).toMatchInlineSnapshot(` Object { "chain": Array [ @@ -770,9 +763,11 @@ describe('IndexPattern Data Source', () => { }, }; - const state = enrichBaseState(queryBaseState); - - const ast = indexPatternDatasource.toExpression(state, 'first') as Ast; + const ast = indexPatternDatasource.toExpression( + queryBaseState, + 'first', + indexPatterns + ) as Ast; const timeScaleCalls = ast.chain.filter((fn) => fn.function === 'lens_time_scale'); const formatCalls = ast.chain.filter((fn) => fn.function === 'lens_format_column'); expect(timeScaleCalls).toHaveLength(1); @@ -855,9 +850,11 @@ describe('IndexPattern Data Source', () => { }, }; - const state = enrichBaseState(queryBaseState); - - const ast = indexPatternDatasource.toExpression(state, 'first') as Ast; + const ast = indexPatternDatasource.toExpression( + queryBaseState, + 'first', + indexPatterns + ) as Ast; const formatIndex = ast.chain.findIndex((fn) => fn.function === 'lens_format_column'); const calculationIndex = ast.chain.findIndex((fn) => fn.function === 'moving_average'); expect(calculationIndex).toBeLessThan(formatIndex); @@ -905,8 +902,11 @@ describe('IndexPattern Data Source', () => { }, }; - const state = enrichBaseState(queryBaseState); - const ast = indexPatternDatasource.toExpression(state, 'first') as Ast; + const ast = indexPatternDatasource.toExpression( + queryBaseState, + 'first', + indexPatterns + ) as Ast; expect(ast.chain[1].arguments.metricsAtAllLevels).toEqual([false]); expect(JSON.parse(ast.chain[2].arguments.idMap[0] as string)).toEqual({ 'col-0-0': [expect.objectContaining({ id: 'bucket1' })], @@ -945,9 +945,11 @@ describe('IndexPattern Data Source', () => { }, }; - const state = enrichBaseState(queryBaseState); - - const ast = indexPatternDatasource.toExpression(state, 'first') as Ast; + const ast = indexPatternDatasource.toExpression( + queryBaseState, + 'first', + indexPatterns + ) as Ast; expect(ast.chain[1].arguments.timeFields).toEqual(['timestamp']); expect(ast.chain[1].arguments.timeFields).not.toContain('timefield'); }); @@ -1001,11 +1003,9 @@ describe('IndexPattern Data Source', () => { }, }; - const state = enrichBaseState(queryBaseState); - const optimizeMock = jest.spyOn(operationDefinitionMap.percentile, 'optimizeEsAggs'); - indexPatternDatasource.toExpression(state, 'first'); + indexPatternDatasource.toExpression(queryBaseState, 'first', indexPatterns); expect(operationDefinitionMap.percentile.optimizeEsAggs).toHaveBeenCalledTimes(1); @@ -1066,8 +1066,6 @@ describe('IndexPattern Data Source', () => { }, }; - const state = enrichBaseState(queryBaseState); - const optimizeMock = jest .spyOn(operationDefinitionMap.percentile, 'optimizeEsAggs') .mockImplementation((aggs, esAggsIdMap) => { @@ -1075,7 +1073,11 @@ describe('IndexPattern Data Source', () => { return { aggs: aggs.reverse(), esAggsIdMap }; }); - const ast = indexPatternDatasource.toExpression(state, 'first') as Ast; + const ast = indexPatternDatasource.toExpression( + queryBaseState, + 'first', + indexPatterns + ) as Ast; expect(operationDefinitionMap.percentile.optimizeEsAggs).toHaveBeenCalledTimes(1); @@ -1126,9 +1128,11 @@ describe('IndexPattern Data Source', () => { }, }; - const state = enrichBaseState(queryBaseState); - - const ast = indexPatternDatasource.toExpression(state, 'first') as Ast; + const ast = indexPatternDatasource.toExpression( + queryBaseState, + 'first', + indexPatterns + ) as Ast; // @ts-expect-error we can't isolate just the reference type expect(operationDefinitionMap.testReference.toExpression).toHaveBeenCalled(); expect(ast.chain[3]).toEqual('mock'); @@ -1161,9 +1165,11 @@ describe('IndexPattern Data Source', () => { }, }; - const state = enrichBaseState(queryBaseState); - - const ast = indexPatternDatasource.toExpression(state, 'first') as Ast; + const ast = indexPatternDatasource.toExpression( + queryBaseState, + 'first', + indexPatterns + ) as Ast; expect(JSON.parse(ast.chain[2].arguments.idMap[0] as string)).toEqual({ 'col-0-0': [ @@ -1250,9 +1256,11 @@ describe('IndexPattern Data Source', () => { }, }; - const state = enrichBaseState(queryBaseState); - - const ast = indexPatternDatasource.toExpression(state, 'first') as Ast; + const ast = indexPatternDatasource.toExpression( + queryBaseState, + 'first', + indexPatterns + ) as Ast; const chainLength = ast.chain.length; expect(ast.chain[chainLength - 2].arguments.name).toEqual(['math']); expect(ast.chain[chainLength - 1].arguments.id).toEqual(['formula']); @@ -1333,10 +1341,6 @@ describe('IndexPattern Data Source', () => { it('should list the current layers', () => { expect( indexPatternDatasource.getLayers({ - indexPatternRefs: [], - existingFields: {}, - isFirstExistenceFetch: false, - indexPatterns: expectedIndexPatterns, layers: { first: { indexPatternId: '1', @@ -1359,10 +1363,10 @@ describe('IndexPattern Data Source', () => { let publicAPI: DatasourcePublicAPI; beforeEach(async () => { - const initialState = enrichBaseState(baseState); publicAPI = indexPatternDatasource.getPublicAPI({ - state: initialState, + state: baseState, layerId: 'first', + indexPatterns, }); }); @@ -1378,7 +1382,7 @@ describe('IndexPattern Data Source', () => { it('should skip columns that are being referenced', () => { publicAPI = indexPatternDatasource.getPublicAPI({ state: { - ...enrichBaseState(baseState), + ...baseState, layers: { first: { indexPatternId: '1', @@ -1407,6 +1411,7 @@ describe('IndexPattern Data Source', () => { }, }, layerId: 'first', + indexPatterns, }); expect(publicAPI.getTableSpec()).toEqual([expect.objectContaining({ columnId: 'col2' })]); @@ -1415,7 +1420,7 @@ describe('IndexPattern Data Source', () => { it('should collect all fields (also from referenced columns)', () => { publicAPI = indexPatternDatasource.getPublicAPI({ state: { - ...enrichBaseState(baseState), + ...baseState, layers: { first: { indexPatternId: '1', @@ -1444,6 +1449,7 @@ describe('IndexPattern Data Source', () => { }, }, layerId: 'first', + indexPatterns, }); // The cumulative sum column has no field, but it references a sum column (hidden) which has it // The getTableSpec() should walk the reference tree and assign all fields to the root column @@ -1453,7 +1459,7 @@ describe('IndexPattern Data Source', () => { it('should collect and organize fields per visible column', () => { publicAPI = indexPatternDatasource.getPublicAPI({ state: { - ...enrichBaseState(baseState), + ...baseState, layers: { first: { indexPatternId: '1', @@ -1494,6 +1500,7 @@ describe('IndexPattern Data Source', () => { }, }, layerId: 'first', + indexPatterns, }); // col1 is skipped as referenced but its field gets inherited by col2 @@ -1522,7 +1529,7 @@ describe('IndexPattern Data Source', () => { it('should return null for referenced columns', () => { publicAPI = indexPatternDatasource.getPublicAPI({ state: { - ...enrichBaseState(baseState), + ...baseState, layers: { first: { indexPatternId: '1', @@ -1551,6 +1558,7 @@ describe('IndexPattern Data Source', () => { }, }, layerId: 'first', + indexPatterns, }); expect(publicAPI.getOperationForColumnId('col1')).toEqual(null); }); @@ -1566,7 +1574,7 @@ describe('IndexPattern Data Source', () => { it('should return all filters in metrics, grouped by language', () => { publicAPI = indexPatternDatasource.getPublicAPI({ state: { - ...enrichBaseState(baseState), + ...baseState, layers: { first: { indexPatternId: '1', @@ -1595,6 +1603,7 @@ describe('IndexPattern Data Source', () => { }, }, layerId: 'first', + indexPatterns, }); expect(publicAPI.getFilters()).toEqual({ enabled: { @@ -1607,7 +1616,7 @@ describe('IndexPattern Data Source', () => { it('should ignore empty filtered metrics', () => { publicAPI = indexPatternDatasource.getPublicAPI({ state: { - ...enrichBaseState(baseState), + ...baseState, layers: { first: { indexPatternId: '1', @@ -1627,6 +1636,7 @@ describe('IndexPattern Data Source', () => { }, }, layerId: 'first', + indexPatterns, }); expect(publicAPI.getFilters()).toEqual({ enabled: { kuery: [], lucene: [] }, @@ -1636,7 +1646,7 @@ describe('IndexPattern Data Source', () => { it('shuold collect top values fields as kuery existence filters if no data is provided', () => { publicAPI = indexPatternDatasource.getPublicAPI({ state: { - ...enrichBaseState(baseState), + ...baseState, layers: { first: { indexPatternId: '1', @@ -1672,6 +1682,7 @@ describe('IndexPattern Data Source', () => { }, }, layerId: 'first', + indexPatterns, }); expect(publicAPI.getFilters()).toEqual({ enabled: { @@ -1690,7 +1701,7 @@ describe('IndexPattern Data Source', () => { it('shuold collect top values fields and terms as kuery filters if data is provided', () => { publicAPI = indexPatternDatasource.getPublicAPI({ state: { - ...enrichBaseState(baseState), + ...baseState, layers: { first: { indexPatternId: '1', @@ -1726,6 +1737,7 @@ describe('IndexPattern Data Source', () => { }, }, layerId: 'first', + indexPatterns, }); const data = { first: { @@ -1760,7 +1772,7 @@ describe('IndexPattern Data Source', () => { it('shuold collect top values fields and terms and carefully handle empty string values', () => { publicAPI = indexPatternDatasource.getPublicAPI({ state: { - ...enrichBaseState(baseState), + ...baseState, layers: { first: { indexPatternId: '1', @@ -1796,6 +1808,7 @@ describe('IndexPattern Data Source', () => { }, }, layerId: 'first', + indexPatterns, }); const data = { first: { @@ -1830,7 +1843,7 @@ describe('IndexPattern Data Source', () => { it('should ignore top values fields if other/missing option is enabled', () => { publicAPI = indexPatternDatasource.getPublicAPI({ state: { - ...enrichBaseState(baseState), + ...baseState, layers: { first: { indexPatternId: '1', @@ -1867,6 +1880,7 @@ describe('IndexPattern Data Source', () => { }, }, layerId: 'first', + indexPatterns, }); expect(publicAPI.getFilters()).toEqual({ enabled: { kuery: [], lucene: [] }, @@ -1876,7 +1890,7 @@ describe('IndexPattern Data Source', () => { it('should collect custom ranges as kuery filters', () => { publicAPI = indexPatternDatasource.getPublicAPI({ state: { - ...enrichBaseState(baseState), + ...baseState, layers: { first: { indexPatternId: '1', @@ -1912,6 +1926,7 @@ describe('IndexPattern Data Source', () => { }, }, layerId: 'first', + indexPatterns, }); expect(publicAPI.getFilters()).toEqual({ enabled: { @@ -1930,7 +1945,7 @@ describe('IndexPattern Data Source', () => { it('should collect custom ranges as kuery filters as partial', () => { publicAPI = indexPatternDatasource.getPublicAPI({ state: { - ...enrichBaseState(baseState), + ...baseState, layers: { first: { indexPatternId: '1', @@ -1974,6 +1989,7 @@ describe('IndexPattern Data Source', () => { }, }, layerId: 'first', + indexPatterns, }); expect(publicAPI.getFilters()).toEqual({ enabled: { @@ -1989,7 +2005,7 @@ describe('IndexPattern Data Source', () => { it('should collect filters within filters operation grouped by language', () => { publicAPI = indexPatternDatasource.getPublicAPI({ state: { - ...enrichBaseState(baseState), + ...baseState, layers: { first: { indexPatternId: '1', @@ -2035,6 +2051,7 @@ describe('IndexPattern Data Source', () => { }, }, layerId: 'first', + indexPatterns, }); expect(publicAPI.getFilters()).toEqual({ enabled: { @@ -2059,7 +2076,7 @@ describe('IndexPattern Data Source', () => { it('should ignore filtered metrics if at least one metric is unfiltered', () => { publicAPI = indexPatternDatasource.getPublicAPI({ state: { - ...enrichBaseState(baseState), + ...baseState, layers: { first: { indexPatternId: '1', @@ -2087,6 +2104,7 @@ describe('IndexPattern Data Source', () => { }, }, layerId: 'first', + indexPatterns, }); expect(publicAPI.getFilters()).toEqual({ enabled: { kuery: [], lucene: [] }, @@ -2096,7 +2114,7 @@ describe('IndexPattern Data Source', () => { it('should ignore filtered metrics if at least one metric is unfiltered in formula', () => { publicAPI = indexPatternDatasource.getPublicAPI({ state: { - ...enrichBaseState(baseState), + ...baseState, layers: { first: { indexPatternId: '1', @@ -2159,6 +2177,7 @@ describe('IndexPattern Data Source', () => { }, }, layerId: 'first', + indexPatterns, }); expect(publicAPI.getFilters()).toEqual({ enabled: { kuery: [], lucene: [] }, @@ -2168,7 +2187,7 @@ describe('IndexPattern Data Source', () => { it('should support complete scenarios', () => { publicAPI = indexPatternDatasource.getPublicAPI({ state: { - ...enrichBaseState(baseState), + ...baseState, layers: { first: { indexPatternId: '1', @@ -2225,6 +2244,7 @@ describe('IndexPattern Data Source', () => { }, }, layerId: 'first', + indexPatterns, }); expect(publicAPI.getFilters()).toEqual({ enabled: { @@ -2254,7 +2274,7 @@ describe('IndexPattern Data Source', () => { it('should avoid duplicate filters when formula has a global filter', () => { publicAPI = indexPatternDatasource.getPublicAPI({ state: { - ...enrichBaseState(baseState), + ...baseState, layers: { first: { indexPatternId: '1', @@ -2319,6 +2339,7 @@ describe('IndexPattern Data Source', () => { }, }, layerId: 'first', + indexPatterns, }); expect(publicAPI.getFilters()).toEqual({ enabled: { @@ -2360,10 +2381,6 @@ describe('IndexPattern Data Source', () => { (getErrorMessages as jest.Mock).mockClear(); (getErrorMessages as jest.Mock).mockReturnValueOnce(['error 1', 'error 2']); const state: IndexPatternPrivateState = { - indexPatternRefs: [], - existingFields: {}, - isFirstExistenceFetch: false, - indexPatterns: expectedIndexPatterns, layers: { first: { indexPatternId: '1', @@ -2373,7 +2390,7 @@ describe('IndexPattern Data Source', () => { }, currentIndexPatternId: '1', }; - expect(indexPatternDatasource.getErrorMessages(state)).toEqual([ + expect(indexPatternDatasource.getErrorMessages(state, indexPatterns)).toEqual([ { longMessage: 'error 1', shortMessage: '' }, { longMessage: 'error 2', shortMessage: '' }, ]); @@ -2384,10 +2401,6 @@ describe('IndexPattern Data Source', () => { (getErrorMessages as jest.Mock).mockClear(); (getErrorMessages as jest.Mock).mockReturnValueOnce(['error 1', 'error 2']); const state: IndexPatternPrivateState = { - indexPatternRefs: [], - existingFields: {}, - isFirstExistenceFetch: false, - indexPatterns: expectedIndexPatterns, layers: { first: { indexPatternId: '1', @@ -2402,7 +2415,7 @@ describe('IndexPattern Data Source', () => { }, currentIndexPatternId: '1', }; - expect(indexPatternDatasource.getErrorMessages(state)).toEqual([ + expect(indexPatternDatasource.getErrorMessages(state, indexPatterns)).toEqual([ { longMessage: 'Layer 1 error: error 1', shortMessage: '' }, { longMessage: 'Layer 1 error: error 2', shortMessage: '' }, ]); @@ -2431,10 +2444,6 @@ describe('IndexPattern Data Source', () => { }; state = { - indexPatternRefs: [], - existingFields: {}, - isFirstExistenceFetch: false, - indexPatterns: expectedIndexPatterns, layers: { first: { indexPatternId: '1', @@ -2570,10 +2579,6 @@ describe('IndexPattern Data Source', () => { (getErrorMessages as jest.Mock).mockReturnValueOnce(['error 1', 'error 2']); state = { - indexPatternRefs: [], - existingFields: {}, - isFirstExistenceFetch: false, - indexPatterns: expectedIndexPatterns, layers: { first: { indexPatternId: '1', @@ -2589,7 +2594,7 @@ describe('IndexPattern Data Source', () => { currentIndexPatternId: '1', }; - expect(indexPatternDatasource.getErrorMessages(state)).toEqual([ + expect(indexPatternDatasource.getErrorMessages(state, indexPatterns)).toEqual([ { longMessage: 'Layer 1 error: error 1', shortMessage: '' }, { longMessage: 'Layer 1 error: error 2', shortMessage: '' }, ]); @@ -2602,10 +2607,6 @@ describe('IndexPattern Data Source', () => { expect( indexPatternDatasource.updateStateOnCloseDimension!({ state: { - indexPatternRefs: [], - existingFields: {}, - isFirstExistenceFetch: false, - indexPatterns: expectedIndexPatterns, layers: { first: { indexPatternId: '1', @@ -2632,10 +2633,6 @@ describe('IndexPattern Data Source', () => { it('should clear all incomplete columns', () => { const state = { - indexPatternRefs: [], - existingFields: {}, - isFirstExistenceFetch: false, - indexPatterns: expectedIndexPatterns, layers: { first: { indexPatternId: '1', @@ -2670,7 +2667,7 @@ describe('IndexPattern Data Source', () => { }); describe('#isTimeBased', () => { it('should return true if date histogram exists in any layer', () => { - let state = enrichBaseState({ + const state = { currentIndexPatternId: '1', layers: { first: { @@ -2722,18 +2719,16 @@ describe('IndexPattern Data Source', () => { }, }, }, - }); - state = { - ...state, - indexPatterns: { - ...state.indexPatterns, - '1': { ...state.indexPatterns['1'], timeFieldName: undefined }, - }, - }; - expect(indexPatternDatasource.isTimeBased(state)).toEqual(true); + } as IndexPatternPrivateState; + expect( + indexPatternDatasource.isTimeBased(state, { + ...indexPatterns, + '1': { ...indexPatterns['1'], timeFieldName: undefined }, + }) + ).toEqual(true); }); it('should return false if date histogram exists but is detached from global time range in every layer', () => { - let state = enrichBaseState({ + const state = { currentIndexPatternId: '1', layers: { first: { @@ -2786,18 +2781,16 @@ describe('IndexPattern Data Source', () => { }, }, }, - }); - state = { - ...state, - indexPatterns: { - ...state.indexPatterns, - '1': { ...state.indexPatterns['1'], timeFieldName: undefined }, - }, - }; - expect(indexPatternDatasource.isTimeBased(state)).toEqual(false); + } as IndexPatternPrivateState; + expect( + indexPatternDatasource.isTimeBased(state, { + ...indexPatterns, + '1': { ...indexPatterns['1'], timeFieldName: undefined }, + }) + ).toEqual(false); }); it('should return false if date histogram does not exist in any layer', () => { - let state = enrichBaseState({ + const state = { currentIndexPatternId: '1', layers: { first: { @@ -2814,18 +2807,16 @@ describe('IndexPattern Data Source', () => { }, }, }, - }); - state = { - ...state, - indexPatterns: { - ...state.indexPatterns, - '1': { ...state.indexPatterns['1'], timeFieldName: undefined }, - }, - }; - expect(indexPatternDatasource.isTimeBased(state)).toEqual(false); + } as IndexPatternPrivateState; + expect( + indexPatternDatasource.isTimeBased(state, { + ...indexPatterns, + '1': { ...indexPatterns['1'], timeFieldName: undefined }, + }) + ).toEqual(false); }); it('should return true if the index pattern is time based even if date histogram does not exist in any layer', () => { - const state = enrichBaseState({ + const state = { currentIndexPatternId: '1', layers: { first: { @@ -2842,14 +2833,14 @@ describe('IndexPattern Data Source', () => { }, }, }, - }); - expect(indexPatternDatasource.isTimeBased(state)).toEqual(true); + } as IndexPatternPrivateState; + expect(indexPatternDatasource.isTimeBased(state, indexPatterns)).toEqual(true); }); }); describe('#initializeDimension', () => { it('should return the same state if no static value is passed', () => { - const state = enrichBaseState({ + const state = { currentIndexPatternId: '1', layers: { first: { @@ -2866,9 +2857,9 @@ describe('IndexPattern Data Source', () => { }, }, }, - }); + } as IndexPatternPrivateState; expect( - indexPatternDatasource.initializeDimension!(state, 'first', { + indexPatternDatasource.initializeDimension!(state, 'first', indexPatterns, { columnId: 'newStatic', groupId: 'a', }) @@ -2876,7 +2867,7 @@ describe('IndexPattern Data Source', () => { }); it('should add a new static value column if a static value is passed', () => { - const state = enrichBaseState({ + const state = { currentIndexPatternId: '1', layers: { first: { @@ -2893,9 +2884,9 @@ describe('IndexPattern Data Source', () => { }, }, }, - }); + } as IndexPatternPrivateState; expect( - indexPatternDatasource.initializeDimension!(state, 'first', { + indexPatternDatasource.initializeDimension!(state, 'first', indexPatterns, { columnId: 'newStatic', groupId: 'a', staticValue: 0, // use a falsy value to check also this corner case diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx index f96a6dbd3340e..90f59f5470e37 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx @@ -163,9 +163,6 @@ const expectedIndexPatterns = { function testInitialState(): IndexPatternPrivateState { return { currentIndexPatternId: '1', - indexPatternRefs: [], - existingFields: {}, - indexPatterns: expectedIndexPatterns, layers: { first: { indexPatternId: '1', @@ -189,7 +186,6 @@ function testInitialState(): IndexPatternPrivateState { }, }, }, - isFirstExistenceFetch: false, }; } @@ -223,13 +219,18 @@ describe('IndexPattern Data Source suggestions', () => { } it('should apply a bucketed aggregation for a string field, using metric for sorting', () => { - const suggestions = getDatasourceSuggestionsForField(stateWithoutLayer(), '1', { - name: 'source', - displayName: 'source', - type: 'string', - aggregatable: true, - searchable: true, - }); + const suggestions = getDatasourceSuggestionsForField( + stateWithoutLayer(), + '1', + { + name: 'source', + displayName: 'source', + type: 'string', + aggregatable: true, + searchable: true, + }, + expectedIndexPatterns + ); expect(suggestions).toContainEqual( expect.objectContaining({ @@ -272,13 +273,18 @@ describe('IndexPattern Data Source suggestions', () => { }); it('should apply a bucketed aggregation for a date field', () => { - const suggestions = getDatasourceSuggestionsForField(stateWithoutLayer(), '1', { - name: 'timestamp', - displayName: 'timestampLabel', - type: 'date', - aggregatable: true, - searchable: true, - }); + const suggestions = getDatasourceSuggestionsForField( + stateWithoutLayer(), + '1', + { + name: 'timestamp', + displayName: 'timestampLabel', + type: 'date', + aggregatable: true, + searchable: true, + }, + expectedIndexPatterns + ); expect(suggestions).toContainEqual( expect.objectContaining({ @@ -317,13 +323,18 @@ describe('IndexPattern Data Source suggestions', () => { }); it('should select a metric for a number field', () => { - const suggestions = getDatasourceSuggestionsForField(stateWithoutLayer(), '1', { - name: 'bytes', - displayName: 'bytes', - type: 'number', - aggregatable: true, - searchable: true, - }); + const suggestions = getDatasourceSuggestionsForField( + stateWithoutLayer(), + '1', + { + name: 'bytes', + displayName: 'bytes', + type: 'number', + aggregatable: true, + searchable: true, + }, + expectedIndexPatterns + ); expect(suggestions).toContainEqual( expect.objectContaining({ @@ -364,35 +375,7 @@ describe('IndexPattern Data Source suggestions', () => { it('should make a metric suggestion for a number field if there is no time field', async () => { const state: IndexPatternPrivateState = { - indexPatternRefs: [], - existingFields: {}, currentIndexPatternId: '1', - isFirstExistenceFetch: false, - indexPatterns: { - 1: { - id: '1', - title: 'no timefield', - hasRestrictions: false, - fields: [ - { - name: 'bytes', - displayName: 'bytes', - type: 'number', - aggregatable: true, - searchable: true, - }, - ], - getFieldByName: getFieldByNameFactory([ - { - name: 'bytes', - displayName: 'bytes', - type: 'number', - aggregatable: true, - searchable: true, - }, - ]), - }, - }, layers: { first: { indexPatternId: '1', @@ -402,13 +385,44 @@ describe('IndexPattern Data Source suggestions', () => { }, }; - const suggestions = getDatasourceSuggestionsForField(state, '1', { - name: 'bytes', - displayName: 'bytes', - type: 'number', - aggregatable: true, - searchable: true, - }); + const indexPatterns = { + 1: { + id: '1', + title: 'no timefield', + hasRestrictions: false, + fields: [ + { + name: 'bytes', + displayName: 'bytes', + type: 'number', + aggregatable: true, + searchable: true, + }, + ], + getFieldByName: getFieldByNameFactory([ + { + name: 'bytes', + displayName: 'bytes', + type: 'number', + aggregatable: true, + searchable: true, + }, + ]), + }, + }; + + const suggestions = getDatasourceSuggestionsForField( + state, + '1', + { + name: 'bytes', + displayName: 'bytes', + type: 'number', + aggregatable: true, + searchable: true, + }, + indexPatterns + ); expect(suggestions).toContainEqual( expect.objectContaining({ @@ -446,13 +460,18 @@ describe('IndexPattern Data Source suggestions', () => { } it('should apply a bucketed aggregation for a string field, using metric for sorting', () => { - const suggestions = getDatasourceSuggestionsForField(stateWithEmptyLayer(), '1', { - name: 'source', - displayName: 'source', - type: 'string', - aggregatable: true, - searchable: true, - }); + const suggestions = getDatasourceSuggestionsForField( + stateWithEmptyLayer(), + '1', + { + name: 'source', + displayName: 'source', + type: 'string', + aggregatable: true, + searchable: true, + }, + expectedIndexPatterns + ); expect(suggestions).toContainEqual( expect.objectContaining({ @@ -495,13 +514,18 @@ describe('IndexPattern Data Source suggestions', () => { }); it('should apply a bucketed aggregation for a date field', () => { - const suggestions = getDatasourceSuggestionsForField(stateWithEmptyLayer(), '1', { - name: 'timestamp', - displayName: 'timestampLabel', - type: 'date', - aggregatable: true, - searchable: true, - }); + const suggestions = getDatasourceSuggestionsForField( + stateWithEmptyLayer(), + '1', + { + name: 'timestamp', + displayName: 'timestampLabel', + type: 'date', + aggregatable: true, + searchable: true, + }, + expectedIndexPatterns + ); expect(suggestions).toContainEqual( expect.objectContaining({ @@ -540,13 +564,18 @@ describe('IndexPattern Data Source suggestions', () => { }); it('should select a metric for a number field', () => { - const suggestions = getDatasourceSuggestionsForField(stateWithEmptyLayer(), '1', { - name: 'bytes', - displayName: 'bytes', - type: 'number', - aggregatable: true, - searchable: true, - }); + const suggestions = getDatasourceSuggestionsForField( + stateWithEmptyLayer(), + '1', + { + name: 'bytes', + displayName: 'bytes', + type: 'number', + aggregatable: true, + searchable: true, + }, + expectedIndexPatterns + ); expect(suggestions).toContainEqual( expect.objectContaining({ @@ -587,36 +616,7 @@ describe('IndexPattern Data Source suggestions', () => { it('should make a metric suggestion for a number field if there is no time field', async () => { const state: IndexPatternPrivateState = { - indexPatternRefs: [], - existingFields: {}, currentIndexPatternId: '1', - isFirstExistenceFetch: false, - indexPatterns: { - 1: { - id: '1', - title: 'no timefield', - hasRestrictions: false, - fields: [ - { - name: 'bytes', - displayName: 'bytes', - type: 'number', - aggregatable: true, - searchable: true, - }, - ], - - getFieldByName: getFieldByNameFactory([ - { - name: 'bytes', - displayName: 'bytes', - type: 'number', - aggregatable: true, - searchable: true, - }, - ]), - }, - }, layers: { previousLayer: { indexPatternId: '1', @@ -626,13 +626,45 @@ describe('IndexPattern Data Source suggestions', () => { }, }; - const suggestions = getDatasourceSuggestionsForField(state, '1', { - name: 'bytes', - displayName: 'bytes', - type: 'number', - aggregatable: true, - searchable: true, - }); + const indexPatterns = { + 1: { + id: '1', + title: 'no timefield', + hasRestrictions: false, + fields: [ + { + name: 'bytes', + displayName: 'bytes', + type: 'number', + aggregatable: true, + searchable: true, + }, + ], + + getFieldByName: getFieldByNameFactory([ + { + name: 'bytes', + displayName: 'bytes', + type: 'number', + aggregatable: true, + searchable: true, + }, + ]), + }, + }; + + const suggestions = getDatasourceSuggestionsForField( + state, + '1', + { + name: 'bytes', + displayName: 'bytes', + type: 'number', + aggregatable: true, + searchable: true, + }, + indexPatterns + ); expect(suggestions).toContainEqual( expect.objectContaining({ @@ -654,13 +686,18 @@ describe('IndexPattern Data Source suggestions', () => { }); it('creates a new layer and replaces layer if no match is found', () => { - const suggestions = getDatasourceSuggestionsForField(stateWithEmptyLayer(), '2', { - name: 'source', - displayName: 'source', - type: 'string', - aggregatable: true, - searchable: true, - }); + const suggestions = getDatasourceSuggestionsForField( + stateWithEmptyLayer(), + '2', + { + name: 'source', + displayName: 'source', + type: 'string', + aggregatable: true, + searchable: true, + }, + expectedIndexPatterns + ); expect(suggestions).toContainEqual( expect.objectContaining({ @@ -794,7 +831,8 @@ describe('IndexPattern Data Source suggestions', () => { type: 'date', aggregatable: true, searchable: true, - } + }, + expectedIndexPatterns ); expect(suggestions).toContainEqual( @@ -821,13 +859,18 @@ describe('IndexPattern Data Source suggestions', () => { it('puts a date histogram column after the last bucket column on date field', () => { (generateId as jest.Mock).mockReturnValue('newid'); const initialState = stateWithNonEmptyTables(); - const suggestions = getDatasourceSuggestionsForField(initialState, '1', { - name: 'timestamp', - displayName: 'timestampLabel', - type: 'date', - aggregatable: true, - searchable: true, - }); + const suggestions = getDatasourceSuggestionsForField( + initialState, + '1', + { + name: 'timestamp', + displayName: 'timestampLabel', + type: 'date', + aggregatable: true, + searchable: true, + }, + expectedIndexPatterns + ); expect(suggestions).toContainEqual( expect.objectContaining({ state: expect.objectContaining({ @@ -867,13 +910,18 @@ describe('IndexPattern Data Source suggestions', () => { }); it('does not use the same field for bucketing multiple times', () => { - const suggestions = getDatasourceSuggestionsForField(stateWithNonEmptyTables(), '1', { - name: 'source', - displayName: 'source', - type: 'string', - aggregatable: true, - searchable: true, - }); + const suggestions = getDatasourceSuggestionsForField( + stateWithNonEmptyTables(), + '1', + { + name: 'source', + displayName: 'source', + type: 'string', + aggregatable: true, + searchable: true, + }, + expectedIndexPatterns + ); expect(suggestions).toHaveLength(0); }); @@ -881,13 +929,18 @@ describe('IndexPattern Data Source suggestions', () => { it('appends a terms column with default size on string field', () => { (generateId as jest.Mock).mockReturnValue('newid'); const initialState = stateWithNonEmptyTables(); - const suggestions = getDatasourceSuggestionsForField(initialState, '1', { - name: 'dest', - displayName: 'dest', - type: 'string', - aggregatable: true, - searchable: true, - }); + const suggestions = getDatasourceSuggestionsForField( + initialState, + '1', + { + name: 'dest', + displayName: 'dest', + type: 'string', + aggregatable: true, + searchable: true, + }, + expectedIndexPatterns + ); expect(suggestions).toContainEqual( expect.objectContaining({ state: expect.objectContaining({ @@ -913,13 +966,18 @@ describe('IndexPattern Data Source suggestions', () => { it('suggests both replacing and adding metric if only one other metric is set', () => { (generateId as jest.Mock).mockReturnValue('newid'); const initialState = stateWithNonEmptyTables(); - const suggestions = getDatasourceSuggestionsForField(initialState, '1', { - name: 'memory', - displayName: 'memory', - type: 'number', - aggregatable: true, - searchable: true, - }); + const suggestions = getDatasourceSuggestionsForField( + initialState, + '1', + { + name: 'memory', + displayName: 'memory', + type: 'number', + aggregatable: true, + searchable: true, + }, + expectedIndexPatterns + ); expect(suggestions).toContainEqual( expect.objectContaining({ state: expect.objectContaining({ @@ -988,13 +1046,18 @@ describe('IndexPattern Data Source suggestions', () => { }, }, }; - const suggestions = getDatasourceSuggestionsForField(modifiedState, '1', { - name: 'memory', - displayName: 'memory', - type: 'number', - aggregatable: true, - searchable: true, - }); + const suggestions = getDatasourceSuggestionsForField( + modifiedState, + '1', + { + name: 'memory', + displayName: 'memory', + type: 'number', + aggregatable: true, + searchable: true, + }, + expectedIndexPatterns + ); expect(suggestions).toContainEqual( expect.objectContaining({ @@ -1019,26 +1082,36 @@ describe('IndexPattern Data Source suggestions', () => { it('skips duplicates when the field is already in use', () => { const initialState = stateWithNonEmptyTables(); - const suggestions = getDatasourceSuggestionsForField(initialState, '1', { - name: 'bytes', - displayName: 'bytes', - type: 'number', - aggregatable: true, - searchable: true, - }); + const suggestions = getDatasourceSuggestionsForField( + initialState, + '1', + { + name: 'bytes', + displayName: 'bytes', + type: 'number', + aggregatable: true, + searchable: true, + }, + expectedIndexPatterns + ); expect(suggestions).not.toContain(expect.objectContaining({ changeType: 'extended' })); }); it('skips metric only suggestion when the field is already in use', () => { const initialState = stateWithNonEmptyTables(); - const suggestions = getDatasourceSuggestionsForField(initialState, '1', { - name: 'bytes', - displayName: 'bytes', - type: 'number', - aggregatable: true, - searchable: true, - }); + const suggestions = getDatasourceSuggestionsForField( + initialState, + '1', + { + name: 'bytes', + displayName: 'bytes', + type: 'number', + aggregatable: true, + searchable: true, + }, + expectedIndexPatterns + ); expect( suggestions.some( @@ -1070,7 +1143,12 @@ describe('IndexPattern Data Source suggestions', () => { }, }, }; - const suggestions = getDatasourceSuggestionsForField(modifiedState, '1', documentField); + const suggestions = getDatasourceSuggestionsForField( + modifiedState, + '1', + documentField, + expectedIndexPatterns + ); expect(suggestions).not.toContain(expect.objectContaining({ changeType: 'extended' })); }); @@ -1114,7 +1192,7 @@ describe('IndexPattern Data Source suggestions', () => { }, }; const suggestions = getSuggestionSubset( - getDatasourceSuggestionsForField(modifiedState, '1', documentField) + getDatasourceSuggestionsForField(modifiedState, '1', documentField, expectedIndexPatterns) ); expect(suggestions).toContainEqual( expect.objectContaining({ @@ -1174,7 +1252,7 @@ describe('IndexPattern Data Source suggestions', () => { }, }; const suggestions = getSuggestionSubset( - getDatasourceSuggestionsForField(modifiedState, '1', documentField) + getDatasourceSuggestionsForField(modifiedState, '1', documentField, expectedIndexPatterns) ); expect(suggestions).toContainEqual( expect.objectContaining({ @@ -1261,6 +1339,7 @@ describe('IndexPattern Data Source suggestions', () => { modifiedState, '1', documentField, + expectedIndexPatterns, (layerId) => layerId !== 'referenceLineLayer' ) ); @@ -1323,21 +1402,26 @@ describe('IndexPattern Data Source suggestions', () => { it('suggests on the layer that matches by indexPatternId', () => { const initialState = stateWithCurrentIndexPattern(); - const suggestions = getDatasourceSuggestionsForField(initialState, '2', { - name: 'timestamp', - displayName: 'timestampLabel', - type: 'date', - aggregatable: true, - searchable: true, - aggregationRestrictions: { - date_histogram: { - agg: 'date_histogram', - fixed_interval: '1d', - delay: '7d', - time_zone: 'UTC', + const suggestions = getDatasourceSuggestionsForField( + initialState, + '2', + { + name: 'timestamp', + displayName: 'timestampLabel', + type: 'date', + aggregatable: true, + searchable: true, + aggregationRestrictions: { + date_histogram: { + agg: 'date_histogram', + fixed_interval: '1d', + delay: '7d', + time_zone: 'UTC', + }, }, }, - }); + expectedIndexPatterns + ); expect(suggestions).toContainEqual( expect.objectContaining({ @@ -1378,13 +1462,18 @@ describe('IndexPattern Data Source suggestions', () => { it('suggests on the layer with the fewest columns that matches by indexPatternId', () => { const initialState = stateWithCurrentIndexPattern(); - const suggestions = getDatasourceSuggestionsForField(initialState, '1', { - name: 'timestamp', - displayName: 'timestampLabel', - type: 'date', - aggregatable: true, - searchable: true, - }); + const suggestions = getDatasourceSuggestionsForField( + initialState, + '1', + { + name: 'timestamp', + displayName: 'timestampLabel', + type: 'date', + aggregatable: true, + searchable: true, + }, + expectedIndexPatterns + ); expect(suggestions).toContainEqual( expect.objectContaining({ @@ -1450,14 +1539,19 @@ describe('IndexPattern Data Source suggestions', () => { ]; const suggestions = getDatasourceSuggestionsForVisualizeCharts( stateWithoutLayer(), - updatedContext + updatedContext, + expectedIndexPatterns ); expect(suggestions).toStrictEqual([]); }); it('should apply a count metric, with a timeseries bucket', () => { - const suggestions = getDatasourceSuggestionsForVisualizeCharts(stateWithoutLayer(), context); + const suggestions = getDatasourceSuggestionsForVisualizeCharts( + stateWithoutLayer(), + context, + expectedIndexPatterns + ); expect(suggestions).toContainEqual( expect.objectContaining({ @@ -1505,7 +1599,8 @@ describe('IndexPattern Data Source suggestions', () => { ]; const suggestions = getDatasourceSuggestionsForVisualizeCharts( stateWithoutLayer(), - updatedContext + updatedContext, + expectedIndexPatterns ); expect(suggestions).toContainEqual( @@ -1555,7 +1650,8 @@ describe('IndexPattern Data Source suggestions', () => { ]; const suggestions = getDatasourceSuggestionsForVisualizeCharts( stateWithoutLayer(), - updatedContext + updatedContext, + expectedIndexPatterns ); expect(suggestions).toContainEqual( @@ -1621,7 +1717,8 @@ describe('IndexPattern Data Source suggestions', () => { ]; const suggestions = getDatasourceSuggestionsForVisualizeCharts( stateWithoutLayer(), - updatedContext + updatedContext, + expectedIndexPatterns ); expect(suggestions).toContainEqual( @@ -1703,7 +1800,8 @@ describe('IndexPattern Data Source suggestions', () => { ]; const suggestions = getDatasourceSuggestionsForVisualizeCharts( stateWithoutLayer(), - updatedContext + updatedContext, + expectedIndexPatterns ); expect(suggestions).toContainEqual( @@ -1787,7 +1885,8 @@ describe('IndexPattern Data Source suggestions', () => { ]; const suggestions = getDatasourceSuggestionsForVisualizeCharts( stateWithoutLayer(), - updatedContext + updatedContext, + expectedIndexPatterns ); expect(suggestions).toContainEqual( @@ -1856,7 +1955,8 @@ describe('IndexPattern Data Source suggestions', () => { ]; const suggestions = getDatasourceSuggestionsForVisualizeCharts( stateWithoutLayer(), - updatedContext + updatedContext, + expectedIndexPatterns ); expect(suggestions).toContainEqual( @@ -1906,7 +2006,8 @@ describe('IndexPattern Data Source suggestions', () => { const suggestions = getDatasourceSuggestionsForVisualizeField( stateWithoutLayer(), '1', - 'field_not_exist' + 'field_not_exist', + expectedIndexPatterns ); expect(suggestions).toEqual([]); @@ -1916,7 +2017,8 @@ describe('IndexPattern Data Source suggestions', () => { const suggestions = getDatasourceSuggestionsForVisualizeField( stateWithoutLayer(), '1', - 'source' + 'source', + expectedIndexPatterns ); expect(suggestions).toContainEqual( @@ -1961,20 +2063,19 @@ describe('IndexPattern Data Source suggestions', () => { describe('#getDatasourceSuggestionsFromCurrentState', () => { it('returns no suggestions if there are no columns', () => { expect( - getDatasourceSuggestionsFromCurrentState({ - isFirstExistenceFetch: false, - indexPatternRefs: [], - existingFields: {}, - indexPatterns: expectedIndexPatterns, - layers: { - first: { - indexPatternId: '1', - columnOrder: [], - columns: {}, + getDatasourceSuggestionsFromCurrentState( + { + layers: { + first: { + indexPatternId: '1', + columnOrder: [], + columns: {}, + }, }, + currentIndexPatternId: '1', }, - currentIndexPatternId: '1', - }) + expectedIndexPatterns + ) ).toEqual([]); }); @@ -2008,7 +2109,7 @@ describe('IndexPattern Data Source suggestions', () => { }, }; - const result = getDatasourceSuggestionsFromCurrentState(state); + const result = getDatasourceSuggestionsFromCurrentState(state, expectedIndexPatterns); expect(result).toContainEqual( expect.objectContaining({ @@ -2094,7 +2195,9 @@ describe('IndexPattern Data Source suggestions', () => { }, }; - expect(getSuggestionSubset(getDatasourceSuggestionsFromCurrentState(state))).toContainEqual( + expect( + getSuggestionSubset(getDatasourceSuggestionsFromCurrentState(state, expectedIndexPatterns)) + ).toContainEqual( expect.objectContaining({ table: { isMultiRow: true, @@ -2167,7 +2270,9 @@ describe('IndexPattern Data Source suggestions', () => { }, }; - expect(getSuggestionSubset(getDatasourceSuggestionsFromCurrentState(state))).toContainEqual( + expect( + getSuggestionSubset(getDatasourceSuggestionsFromCurrentState(state, expectedIndexPatterns)) + ).toContainEqual( expect.objectContaining({ table: { isMultiRow: true, @@ -2268,7 +2373,11 @@ describe('IndexPattern Data Source suggestions', () => { expect( getSuggestionSubset( - getDatasourceSuggestionsFromCurrentState(state, (layerId) => layerId !== 'referenceLine') + getDatasourceSuggestionsFromCurrentState( + state, + expectedIndexPatterns, + (layerId) => layerId !== 'referenceLine' + ) ) ).toContainEqual( expect.objectContaining({ @@ -2352,7 +2461,9 @@ describe('IndexPattern Data Source suggestions', () => { }, }; - expect(getDatasourceSuggestionsFromCurrentState(state)).not.toContainEqual( + expect( + getDatasourceSuggestionsFromCurrentState(state, expectedIndexPatterns) + ).not.toContainEqual( expect.objectContaining({ table: { isMultiRow: true, @@ -2398,7 +2509,9 @@ describe('IndexPattern Data Source suggestions', () => { }, }; - expect(getSuggestionSubset(getDatasourceSuggestionsFromCurrentState(state))).toContainEqual( + expect( + getSuggestionSubset(getDatasourceSuggestionsFromCurrentState(state, expectedIndexPatterns)) + ).toContainEqual( expect.objectContaining({ table: { changeType: 'extended', @@ -2467,9 +2580,8 @@ describe('IndexPattern Data Source suggestions', () => { }, }, }; - const suggestions = getDatasourceSuggestionsFromCurrentState({ - ...state, - indexPatterns: { 1: { ...state.indexPatterns['1'], timeFieldName: undefined } }, + const suggestions = getDatasourceSuggestionsFromCurrentState(state, { + 1: { ...expectedIndexPatterns['1'], timeFieldName: undefined }, }); suggestions.forEach((suggestion) => expect(suggestion.table.columns.length).toBe(1)); }); @@ -2477,9 +2589,8 @@ describe('IndexPattern Data Source suggestions', () => { it("should not propose an over time suggestion if there's a top values aggregation with an high size", () => { const initialState = testInitialState(); (initialState.layers.first.columns.col1 as TermsIndexPatternColumn).params!.size = 6; - const suggestions = getDatasourceSuggestionsFromCurrentState({ - ...initialState, - indexPatterns: { 1: { ...initialState.indexPatterns['1'], timeFieldName: undefined } }, + const suggestions = getDatasourceSuggestionsFromCurrentState(initialState, { + 1: { ...expectedIndexPatterns['1'], timeFieldName: undefined }, }); suggestions.forEach((suggestion) => expect(suggestion.table.columns.length).toBe(1)); }); @@ -2522,9 +2633,8 @@ describe('IndexPattern Data Source suggestions', () => { }, }, }; - const suggestions = getDatasourceSuggestionsFromCurrentState({ - ...state, - indexPatterns: { 1: { ...state.indexPatterns['1'], timeFieldName: undefined } }, + const suggestions = getDatasourceSuggestionsFromCurrentState(state, { + 1: { ...expectedIndexPatterns['1'], timeFieldName: undefined }, }); suggestions.forEach((suggestion) => { const firstBucket = suggestion.table.columns.find(({ columnId }) => columnId === 'col1'); @@ -2572,19 +2682,7 @@ describe('IndexPattern Data Source suggestions', () => { }, ]; const state: IndexPatternPrivateState = { - indexPatternRefs: [], - existingFields: {}, currentIndexPatternId: '1', - indexPatterns: { - 1: { - id: '1', - title: 'my-fake-index-pattern', - hasRestrictions: false, - fields, - getFieldByName: getFieldByNameFactory(fields), - }, - }, - isFirstExistenceFetch: false, layers: { first: { ...initialState.layers.first, @@ -2655,7 +2753,15 @@ describe('IndexPattern Data Source suggestions', () => { }, }; - const suggestions = getDatasourceSuggestionsFromCurrentState(state); + const suggestions = getDatasourceSuggestionsFromCurrentState(state, { + 1: { + id: '1', + title: 'my-fake-index-pattern', + hasRestrictions: false, + fields, + getFieldByName: getFieldByNameFactory(fields), + }, + }); // 3 bucket cols, 2 metric cols isTableWithBucketColumns(suggestions[0], ['col1', 'col2', 'col3', 'col4', 'col5'], 3); @@ -2681,50 +2787,7 @@ describe('IndexPattern Data Source suggestions', () => { it('returns an only metric version of a given table, but does not include current state as reduced', () => { const initialState = testInitialState(); const state: IndexPatternPrivateState = { - indexPatternRefs: [], - existingFields: {}, currentIndexPatternId: '1', - indexPatterns: { - 1: { - id: '1', - title: 'my-fake-index-pattern', - hasRestrictions: false, - fields: [ - { - name: 'field1', - displayName: 'field1', - type: 'number', - aggregatable: true, - searchable: true, - }, - { - name: 'field2', - displayName: 'field2', - type: 'date', - aggregatable: true, - searchable: true, - }, - ], - - getFieldByName: getFieldByNameFactory([ - { - name: 'field1', - displayName: 'field1', - type: 'number', - aggregatable: true, - searchable: true, - }, - { - name: 'field2', - displayName: 'field2', - type: 'date', - aggregatable: true, - searchable: true, - }, - ]), - }, - }, - isFirstExistenceFetch: false, layers: { first: { ...initialState.layers.first, @@ -2754,7 +2817,50 @@ describe('IndexPattern Data Source suggestions', () => { }, }; - const suggestions = getSuggestionSubset(getDatasourceSuggestionsFromCurrentState(state)); + const indexPatterns = { + 1: { + id: '1', + title: 'my-fake-index-pattern', + hasRestrictions: false, + fields: [ + { + name: 'field1', + displayName: 'field1', + type: 'number', + aggregatable: true, + searchable: true, + }, + { + name: 'field2', + displayName: 'field2', + type: 'date', + aggregatable: true, + searchable: true, + }, + ], + + getFieldByName: getFieldByNameFactory([ + { + name: 'field1', + displayName: 'field1', + type: 'number', + aggregatable: true, + searchable: true, + }, + { + name: 'field2', + displayName: 'field2', + type: 'date', + aggregatable: true, + searchable: true, + }, + ]), + }, + }; + + const suggestions = getSuggestionSubset( + getDatasourceSuggestionsFromCurrentState(state, indexPatterns) + ); expect(suggestions).not.toContainEqual( expect.objectContaining({ table: expect.objectContaining({ @@ -2787,35 +2893,7 @@ describe('IndexPattern Data Source suggestions', () => { it('returns an alternative metric for an only-metric table', () => { const initialState = testInitialState(); const state: IndexPatternPrivateState = { - indexPatternRefs: [], - existingFields: {}, currentIndexPatternId: '1', - indexPatterns: { - 1: { - id: '1', - title: 'my-fake-index-pattern', - hasRestrictions: false, - fields: [ - { - name: 'field1', - displayName: 'field1', - type: 'number', - aggregatable: true, - searchable: true, - }, - ], - getFieldByName: getFieldByNameFactory([ - { - name: 'field1', - displayName: 'field1', - type: 'number', - aggregatable: true, - searchable: true, - }, - ]), - }, - }, - isFirstExistenceFetch: false, layers: { first: { ...initialState.layers.first, @@ -2834,7 +2912,35 @@ describe('IndexPattern Data Source suggestions', () => { }, }; - const suggestions = getSuggestionSubset(getDatasourceSuggestionsFromCurrentState(state)); + const indexPatterns = { + 1: { + id: '1', + title: 'my-fake-index-pattern', + hasRestrictions: false, + fields: [ + { + name: 'field1', + displayName: 'field1', + type: 'number', + aggregatable: true, + searchable: true, + }, + ], + getFieldByName: getFieldByNameFactory([ + { + name: 'field1', + displayName: 'field1', + type: 'number', + aggregatable: true, + searchable: true, + }, + ]), + }, + }; + + const suggestions = getSuggestionSubset( + getDatasourceSuggestionsFromCurrentState(state, indexPatterns) + ); expect(suggestions).toContainEqual( expect.objectContaining({ table: expect.objectContaining({ @@ -2851,11 +2957,7 @@ describe('IndexPattern Data Source suggestions', () => { it('contains a reordering suggestion when there are exactly 2 buckets', () => { const initialState = testInitialState(); const state: IndexPatternPrivateState = { - indexPatternRefs: [], - existingFields: {}, currentIndexPatternId: '1', - indexPatterns: expectedIndexPatterns, - isFirstExistenceFetch: false, layers: { first: { ...initialState.layers.first, @@ -2894,7 +2996,7 @@ describe('IndexPattern Data Source suggestions', () => { }, }; - const suggestions = getDatasourceSuggestionsFromCurrentState(state); + const suggestions = getDatasourceSuggestionsFromCurrentState(state, expectedIndexPatterns); expect(suggestions).toContainEqual( expect.objectContaining({ table: expect.objectContaining({ @@ -2907,11 +3009,7 @@ describe('IndexPattern Data Source suggestions', () => { it('will generate suggestions even if there are errors from missing fields', () => { const initialState = testInitialState(); const state: IndexPatternPrivateState = { - indexPatternRefs: [], - existingFields: {}, currentIndexPatternId: '1', - indexPatterns: expectedIndexPatterns, - isFirstExistenceFetch: false, layers: { first: { ...initialState.layers.first, @@ -2932,7 +3030,9 @@ describe('IndexPattern Data Source suggestions', () => { }, }; - const suggestions = getSuggestionSubset(getDatasourceSuggestionsFromCurrentState(state)); + const suggestions = getSuggestionSubset( + getDatasourceSuggestionsFromCurrentState(state, expectedIndexPatterns) + ); expect(suggestions).toContainEqual( expect.objectContaining({ table: { @@ -3006,7 +3106,9 @@ describe('IndexPattern Data Source suggestions', () => { }, }; - const result = getSuggestionSubset(getDatasourceSuggestionsFromCurrentState(state)); + const result = getSuggestionSubset( + getDatasourceSuggestionsFromCurrentState(state, expectedIndexPatterns) + ); expect(result).toContainEqual( expect.objectContaining({ @@ -3091,7 +3193,9 @@ describe('IndexPattern Data Source suggestions', () => { }, }; - const result = getSuggestionSubset(getDatasourceSuggestionsFromCurrentState(state)); + const result = getSuggestionSubset( + getDatasourceSuggestionsFromCurrentState(state, expectedIndexPatterns) + ); expect(result).toContainEqual( expect.objectContaining({ @@ -3235,7 +3339,7 @@ describe('IndexPattern Data Source suggestions', () => { }, }; - const result = getDatasourceSuggestionsFromCurrentState(state); + const result = getDatasourceSuggestionsFromCurrentState(state, expectedIndexPatterns); // only generate suggestions for top level metrics + only first metric with all buckets expect( @@ -3316,7 +3420,7 @@ describe('IndexPattern Data Source suggestions', () => { }, }; - const result = getDatasourceSuggestionsFromCurrentState(state); + const result = getDatasourceSuggestionsFromCurrentState(state, expectedIndexPatterns); // only generate suggestions for top level metrics expect( @@ -3366,7 +3470,7 @@ describe('IndexPattern Data Source suggestions', () => { }, }; - const result = getDatasourceSuggestionsFromCurrentState(state); + const result = getDatasourceSuggestionsFromCurrentState(state, expectedIndexPatterns); expect( result.filter((suggestion) => suggestion.table.changeType === 'unchanged').length diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/layerpanel.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/layerpanel.test.tsx index 3d366408595b0..d481e77bd69a8 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/layerpanel.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/layerpanel.test.tsx @@ -136,14 +136,7 @@ const fieldsThree = [ ]; const initialState: IndexPatternPrivateState = { - indexPatternRefs: [ - { id: '1', title: 'my-fake-index-pattern' }, - { id: '2', title: 'my-fake-restricted-pattern' }, - { id: '3', title: 'my-compatible-pattern' }, - ], - existingFields: {}, currentIndexPatternId: '1', - isFirstExistenceFetch: false, layers: { first: { indexPatternId: '1', @@ -173,32 +166,6 @@ const initialState: IndexPatternPrivateState = { }, }, }, - indexPatterns: { - '1': { - id: '1', - title: 'my-fake-index-pattern', - timeFieldName: 'timestamp', - hasRestrictions: false, - fields: fieldsOne, - getFieldByName: getFieldByNameFactory(fieldsOne), - }, - '2': { - id: '2', - title: 'my-fake-restricted-pattern', - hasRestrictions: true, - timeFieldName: 'timestamp', - fields: fieldsTwo, - getFieldByName: getFieldByNameFactory(fieldsTwo), - }, - '3': { - id: '3', - title: 'my-compatible-pattern', - timeFieldName: 'timestamp', - hasRestrictions: false, - fields: fieldsThree, - getFieldByName: getFieldByNameFactory(fieldsThree), - }, - }, }; describe('Layer Data Panel', () => { let defaultProps: IndexPatternLayerPanelProps; @@ -207,8 +174,42 @@ describe('Layer Data Panel', () => { defaultProps = { layerId: 'first', state: initialState, - setState: jest.fn(), - onChangeIndexPattern: jest.fn(async () => {}), + onChangeIndexPattern: jest.fn(), + dataViews: { + indexPatternRefs: [ + { id: '1', title: 'my-fake-index-pattern' }, + { id: '2', title: 'my-fake-restricted-pattern' }, + { id: '3', title: 'my-compatible-pattern' }, + ], + existingFields: {}, + isFirstExistenceFetch: false, + indexPatterns: { + '1': { + id: '1', + title: 'my-fake-index-pattern', + timeFieldName: 'timestamp', + hasRestrictions: false, + fields: fieldsOne, + getFieldByName: getFieldByNameFactory(fieldsOne), + }, + '2': { + id: '2', + title: 'my-fake-restricted-pattern', + hasRestrictions: true, + timeFieldName: 'timestamp', + fields: fieldsTwo, + getFieldByName: getFieldByNameFactory(fieldsTwo), + }, + '3': { + id: '3', + title: 'my-compatible-pattern', + timeFieldName: 'timestamp', + hasRestrictions: false, + fields: fieldsThree, + getFieldByName: getFieldByNameFactory(fieldsThree), + }, + }, + }, }; }); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/mocks.ts b/x-pack/plugins/lens/public/indexpattern_datasource/mocks.ts index 079a866676f04..d76e6723c1be9 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/mocks.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/mocks.ts @@ -7,7 +7,7 @@ import { DragContextState } from '../drag_drop'; import { getFieldByNameFactory } from './pure_helpers'; -import type { IndexPattern, IndexPatternField } from './types'; +import type { IndexPattern, IndexPatternField } from '../types'; export const createMockedIndexPatternWithoutType = ( typeToFilter: IndexPatternField['type'] diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions.test.ts index dae677663d289..d8e8af06b485a 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions.test.ts @@ -18,7 +18,8 @@ import { } from './definitions'; import { getFieldByNameFactory } from '../pure_helpers'; import { documentField } from '../document_field'; -import { IndexPattern, IndexPatternLayer, IndexPatternField } from '../types'; +import { IndexPatternLayer } from '../types'; +import { IndexPattern, IndexPatternField } from '../../types'; import { GenericIndexPatternColumn } from '.'; import { DateHistogramIndexPatternColumn } from './definitions/date_histogram'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.test.tsx index 73cb0a37ad563..ddcb072cf7666 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.test.tsx @@ -17,7 +17,8 @@ import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import { UI_SETTINGS } from '@kbn/data-plugin/public'; import { dataPluginMock, getCalculateAutoTimeExpression } from '@kbn/data-plugin/public/mocks'; import { createMockedIndexPattern } from '../../mocks'; -import type { IndexPatternLayer, IndexPattern } from '../../types'; +import type { IndexPatternLayer } from '../../types'; +import type { IndexPattern } from '../../../types'; import { getFieldByNameFactory } from '../../pure_helpers'; import { act } from 'react-dom/test-utils'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/editor/math_completion.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/editor/math_completion.test.ts index 8a59636d09952..d90a021a7cb12 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/editor/math_completion.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/editor/math_completion.test.ts @@ -11,8 +11,7 @@ import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; import { createMockedIndexPattern } from '../../../../mocks'; import { GenericOperationDefinition } from '../..'; -import type { IndexPatternField } from '../../../../types'; -import type { OperationMetadata } from '../../../../../types'; +import type { OperationMetadata, IndexPatternField } from '../../../../../types'; import { tinymathFunctions } from '../util'; import { getSignatureHelp, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula.test.tsx index 4a8c6d437f2ac..e68bb0d3142ef 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula.test.tsx @@ -10,7 +10,7 @@ import { formulaOperation, GenericOperationDefinition, GenericIndexPatternColumn import { FormulaIndexPatternColumn } from './formula'; import { insertOrReplaceFormulaColumn } from './parse'; import type { IndexPatternLayer } from '../../../types'; -import { IndexPattern, IndexPatternField } from '../../../../editor_frame_service/types'; +import { IndexPattern, IndexPatternField } from '../../../../types'; import { tinymathFunctions } from './util'; import { TermsIndexPatternColumn } from '../terms'; import { MovingAverageIndexPatternColumn } from '../calculations'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula_public_api.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula_public_api.test.ts index d1ee2023e1d1c..fec643772cc76 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula_public_api.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula_public_api.test.ts @@ -5,12 +5,12 @@ * 2.0. */ -import { convertDataViewIntoLensIndexPattern } from '../../../loader'; import { insertOrReplaceFormulaColumn } from './parse'; import { createFormulaPublicApi, FormulaPublicApi } from './formula_public_api'; import type { DataView } from '@kbn/data-views-plugin/public'; import type { DateHistogramIndexPatternColumn, PersistedIndexPatternLayer } from '../../../types'; +import { convertDataViewIntoLensIndexPattern } from '../../../../data_views_service/loader'; jest.mock('./parse', () => ({ insertOrReplaceFormulaColumn: jest.fn().mockReturnValue({}), diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts index 2cbe6f5e0c827..c06c595a7bff2 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts @@ -48,19 +48,20 @@ import { countOperation } from './count'; import { mathOperation, formulaOperation } from './formula'; import { staticValueOperation } from './static_value'; import { lastValueOperation } from './last_value'; -import { FrameDatasourceAPI, OperationMetadata, ParamEditorCustomProps } from '../../../types'; +import { + FrameDatasourceAPI, + IndexPattern, + IndexPatternField, + OperationMetadata, + ParamEditorCustomProps, +} from '../../../types'; import type { BaseIndexPatternColumn, IncompleteColumn, GenericIndexPatternColumn, ReferenceBasedIndexPatternColumn, } from './column_types'; -import { - DataViewDragDropOperation, - IndexPattern, - IndexPatternField, - IndexPatternLayer, -} from '../../types'; +import { DataViewDragDropOperation, IndexPatternLayer } from '../../types'; import { DateRange, LayerType } from '../../../../common'; import { rangeOperation } from './ranges'; import { IndexPatternDimensionEditorProps, OperationSupportMatrix } from '../../dimension_panel'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.test.tsx index f2248fcdf36c9..ad7c9f107b7d0 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.test.tsx @@ -16,7 +16,8 @@ import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; import { createMockedIndexPattern } from '../../mocks'; import { LastValueIndexPatternColumn } from './last_value'; import { lastValueOperation } from '.'; -import type { IndexPattern, IndexPatternLayer } from '../../types'; +import type { IndexPatternLayer } from '../../types'; +import type { IndexPattern } from '../../../types'; import { TermsIndexPatternColumn } from './terms'; import { EuiSwitch, EuiSwitchEvent } from '@elastic/eui'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.test.tsx index a359ae0b89820..a0723c9d55e23 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.test.tsx @@ -17,7 +17,7 @@ import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; import { createMockedIndexPattern } from '../../mocks'; import { percentileOperation } from '.'; -import { IndexPattern, IndexPatternLayer } from '../../types'; +import { IndexPatternLayer } from '../../types'; import { PercentileIndexPatternColumn } from './percentile'; import { TermsIndexPatternColumn } from './terms'; import { @@ -26,6 +26,7 @@ import { ExpressionAstExpressionBuilder, } from '@kbn/expressions-plugin/public'; import type { OriginalColumn } from '../../to_expression'; +import { IndexPattern } from '../../../types'; jest.mock('lodash', () => { const original = jest.requireActual('lodash'); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile_ranks.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile_ranks.test.tsx index 62c0bbd45be6c..96766a2e95d3d 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile_ranks.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile_ranks.test.tsx @@ -17,9 +17,10 @@ import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; import { createMockedIndexPattern } from '../../mocks'; import { percentileRanksOperation } from '.'; -import { IndexPattern, IndexPatternLayer } from '../../types'; +import { IndexPatternLayer } from '../../types'; import type { PercentileRanksIndexPatternColumn } from './percentile_ranks'; import { TermsIndexPatternColumn } from './terms'; +import { IndexPattern } from '../../../types'; jest.mock('lodash', () => { const original = jest.requireActual('lodash'); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.test.tsx index e0c1c4b3b84cc..ba93999e6c6f2 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.test.tsx @@ -25,9 +25,9 @@ import { SLICES, } from './constants'; import { RangePopover } from './advanced_editor'; -import { DragDropBuckets } from '../../../../shared_components'; +import { DragDropBuckets } from '../shared_components'; import { getFieldByNameFactory } from '../../../pure_helpers'; -import { IndexPattern } from '../../../../editor_frame_service/types'; +import { IndexPattern } from '../../../../types'; // mocking random id generator function jest.mock('@elastic/eui', () => { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/static_value.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/static_value.test.tsx index ea870e68f563b..66a4549bad393 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/static_value.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/static_value.test.tsx @@ -16,7 +16,8 @@ import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; import { createMockedIndexPattern } from '../../mocks'; import { staticValueOperation } from '.'; -import { IndexPattern, IndexPatternLayer } from '../../types'; +import { IndexPatternLayer } from '../../types'; +import { IndexPattern } from '../../../types'; import { StaticValueIndexPatternColumn } from './static_value'; import { TermsIndexPatternColumn } from './terms'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx index f01ccebdabcaf..8d28e9a76274c 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx @@ -34,7 +34,7 @@ import { DateHistogramIndexPatternColumn } from '../date_histogram'; import { getOperationSupportMatrix } from '../../../dimension_panel/operation_support'; import { FieldSelect } from '../../../dimension_panel/field_select'; import { ReferenceEditor } from '../../../dimension_panel/reference_editor'; -import { IndexPattern } from '../../../../editor_frame_service/types'; +import { IndexPattern } from '../../../../types'; import { cloneDeep } from 'lodash'; import { IncludeExcludeRow } from './include_exclude_options'; @@ -1174,14 +1174,13 @@ describe('terms', () => { return getOperationSupportMatrix({ state: { layers: { layer1: layer }, - indexPatterns: { - [defaultProps.indexPattern.id]: defaultProps.indexPattern, - }, - existingFields, } as unknown as IndexPatternPrivateState, layerId: 'layer1', filterOperations: () => true, columnId, + indexPatterns: { + [defaultProps.indexPattern.id]: defaultProps.indexPattern, + }, }); } diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts index cfbc5d4455fd2..613c41ea74194 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts @@ -38,7 +38,7 @@ import { } from './definitions'; import { TinymathAST } from '@kbn/tinymath'; import { CoreStart } from '@kbn/core/public'; -import { IndexPattern } from '../../editor_frame_service/types'; +import { IndexPattern } from '../../types'; jest.mock('.'); jest.mock('../../id_generator'); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/pure_helpers.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/pure_helpers.test.ts deleted file mode 100644 index 90673189b4a83..0000000000000 --- a/x-pack/plugins/lens/public/indexpattern_datasource/pure_helpers.test.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * 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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { fieldExists } from './pure_helpers'; - -describe('fieldExists', () => { - it('returns whether or not a field exists', () => { - expect(fieldExists({ a: { b: true } }, 'a', 'b')).toBeTruthy(); - expect(fieldExists({ a: { b: true } }, 'a', 'c')).toBeFalsy(); - expect(fieldExists({ a: { b: true } }, 'b', 'b')).toBeFalsy(); - }); -}); diff --git a/x-pack/plugins/lens/public/mocks/data_views_service_mock.ts b/x-pack/plugins/lens/public/mocks/data_views_service_mock.ts new file mode 100644 index 0000000000000..4c2e77078dd28 --- /dev/null +++ b/x-pack/plugins/lens/public/mocks/data_views_service_mock.ts @@ -0,0 +1,19 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { IndexPatternServiceAPI } from '../data_views_service/service'; + +export function createIndexPatternServiceMock(): IndexPatternServiceAPI { + return { + loadIndexPatterns: jest.fn(), + loadIndexPatternRefs: jest.fn(), + ensureIndexPattern: jest.fn(), + refreshExistingFields: jest.fn(), + getDefaultIndex: jest.fn(), + updateIndexPatternsCache: jest.fn(), + }; +} diff --git a/x-pack/plugins/lens/public/mocks/datasource_mock.ts b/x-pack/plugins/lens/public/mocks/datasource_mock.ts index 90bb1ea9f5cab..cf8370efdece9 100644 --- a/x-pack/plugins/lens/public/mocks/datasource_mock.ts +++ b/x-pack/plugins/lens/public/mocks/datasource_mock.ts @@ -25,10 +25,12 @@ export function createMockDatasource(id: string): DatasourceMock { return { id: 'testDatasource', clearLayer: jest.fn((state, _layerId) => state), - getDatasourceSuggestionsForField: jest.fn((_state, _item, filterFn) => []), - getDatasourceSuggestionsForVisualizeField: jest.fn((_state, _indexpatternId, _fieldName) => []), - getDatasourceSuggestionsForVisualizeCharts: jest.fn((_state, _context) => []), - getDatasourceSuggestionsFromCurrentState: jest.fn((_state) => []), + getDatasourceSuggestionsForField: jest.fn((_state, _item, filterFn, _indexPatterns) => []), + getDatasourceSuggestionsForVisualizeField: jest.fn( + (_state, _indexpatternId, _fieldName, _indexPatterns) => [] + ), + getDatasourceSuggestionsForVisualizeCharts: jest.fn((_state, _context, _indexPatterns) => []), + getDatasourceSuggestionsFromCurrentState: jest.fn((_state, _indexPatterns) => []), getPersistableState: jest.fn((x) => ({ state: x, savedObjectReferences: [{ type: 'index-pattern', id: 'mockip', name: 'mockip' }], @@ -39,7 +41,7 @@ export function createMockDatasource(id: string): DatasourceMock { renderDataPanel: jest.fn(), renderLayerPanel: jest.fn(), getCurrentIndexPatternId: jest.fn(), - toExpression: jest.fn((_frame, _state) => null), + toExpression: jest.fn((_frame, _state, _indexPatterns) => null), insertLayer: jest.fn((_state, _newLayerId) => ({})), removeLayer: jest.fn((_state, _layerId) => {}), removeColumn: jest.fn((props) => {}), @@ -53,12 +55,13 @@ export function createMockDatasource(id: string): DatasourceMock { // this is an additional property which doesn't exist on real datasources // but can be used to validate whether specific API mock functions are called publicAPIMock, - getErrorMessages: jest.fn((_state) => undefined), - checkIntegrity: jest.fn((_state) => []), + getErrorMessages: jest.fn((_state, _indexPatterns) => undefined), + checkIntegrity: jest.fn((_state, _indexPatterns) => []), isTimeBased: jest.fn(), isValidColumn: jest.fn(), isEqual: jest.fn(), getUsedDataView: jest.fn(), + onRefreshIndexPattern: jest.fn(), }; } diff --git a/x-pack/plugins/lens/public/mocks/index.ts b/x-pack/plugins/lens/public/mocks/index.ts index 58ef8ce05c613..0d90e719ebade 100644 --- a/x-pack/plugins/lens/public/mocks/index.ts +++ b/x-pack/plugins/lens/public/mocks/index.ts @@ -32,6 +32,12 @@ export type FrameMock = jest.Mocked; export const createMockFramePublicAPI = (): FrameMock => ({ datasourceLayers: {}, dateRange: { fromDate: '2022-03-17T08:25:00.000Z', toDate: '2022-04-17T08:25:00.000Z' }, + dataViews: { + indexPatterns: {}, + indexPatternRefs: [], + isFirstExistenceFetch: true, + existingFields: {}, + }, }); export type FrameDatasourceMock = jest.Mocked; @@ -41,4 +47,10 @@ export const createMockFrameDatasourceAPI = (): FrameDatasourceMock => ({ dateRange: { fromDate: '2022-03-17T08:25:00.000Z', toDate: '2022-04-17T08:25:00.000Z' }, query: { query: '', language: 'lucene' }, filters: [], + dataViews: { + indexPatterns: {}, + indexPatternRefs: [], + isFirstExistenceFetch: true, + existingFields: {}, + }, }); diff --git a/x-pack/plugins/lens/public/mocks/store_mocks.tsx b/x-pack/plugins/lens/public/mocks/store_mocks.tsx index 2e6bc86a9d33e..5fab4602b0483 100644 --- a/x-pack/plugins/lens/public/mocks/store_mocks.tsx +++ b/x-pack/plugins/lens/public/mocks/store_mocks.tsx @@ -58,6 +58,12 @@ export const defaultState = { activeId: 'testVis', }, datasourceStates: mockDatasourceStates(), + dataViews: { + indexPatterns: {}, + indexPatternRefs: [], + existingFields: {}, + isFirstExistenceFetch: false, + }, }; export function makeLensStore({ diff --git a/x-pack/plugins/lens/public/xy_visualization/to_expression.test.ts b/x-pack/plugins/lens/public/xy_visualization/to_expression.test.ts index f0a2b9f28623d..b97b3e0679df4 100644 --- a/x-pack/plugins/lens/public/xy_visualization/to_expression.test.ts +++ b/x-pack/plugins/lens/public/xy_visualization/to_expression.test.ts @@ -8,7 +8,6 @@ import { Ast, fromExpression } from '@kbn/interpreter'; import { Position } from '@elastic/charts'; import { chartPluginMock } from '@kbn/charts-plugin/public/mocks'; -import { createDatatableUtilitiesMock } from '@kbn/data-plugin/common/mocks'; import { getXyVisualization } from './xy_visualization'; import { OperationDescriptor } from '../types'; import { createMockDatasource, createMockFramePublicAPI } from '../mocks'; @@ -16,17 +15,21 @@ import { layerTypes } from '../../common'; import { fieldFormatsServiceMock } from '@kbn/field-formats-plugin/public/mocks'; import { eventAnnotationServiceMock } from '@kbn/event-annotation-plugin/public/mocks'; import { defaultReferenceLineColor } from './color_assignment'; -import { themeServiceMock } from '@kbn/core/public/mocks'; +import { coreMock, themeServiceMock } from '@kbn/core/public/mocks'; import { LegendSize } from '@kbn/visualizations-plugin/common'; +import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; +import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; describe('#toExpression', () => { const xyVisualization = getXyVisualization({ - datatableUtilities: createDatatableUtilitiesMock(), paletteService: chartPluginMock.createPaletteRegistry(), fieldFormats: fieldFormatsServiceMock.createStartContract(), kibanaTheme: themeServiceMock.createStartContract(), useLegacyTimeAxis: false, eventAnnotationService: eventAnnotationServiceMock, + core: coreMock.createStart(), + storage: {} as IStorageWrapper, + data: dataPluginMock.createStartContract(), }); let mockDatasource: ReturnType; let frame: ReturnType; @@ -54,7 +57,8 @@ describe('#toExpression', () => { const datasourceExpression = mockDatasource.toExpression( frame.datasourceLayers.first, - 'first' + 'first', + frame.dataViews.indexPatterns ) ?? { type: 'expression', chain: [], diff --git a/x-pack/plugins/lens/public/xy_visualization/types.ts b/x-pack/plugins/lens/public/xy_visualization/types.ts index a763053d72475..c5712938da5d0 100644 --- a/x-pack/plugins/lens/public/xy_visualization/types.ts +++ b/x-pack/plugins/lens/public/xy_visualization/types.ts @@ -114,7 +114,6 @@ export interface XYAnnotationLayerConfig { layerType: 'annotations'; annotations: EventAnnotationConfig[]; hide?: boolean; - indexPatternId: string; simpleView?: boolean; } diff --git a/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts b/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts index d16cbd86df3cf..24df7ad742666 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts +++ b/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts @@ -17,7 +17,6 @@ import type { XYReferenceLineLayerConfig, SeriesType, } from './types'; -import { createDatatableUtilitiesMock } from '@kbn/data-plugin/common/mocks'; import { layerTypes } from '../../common'; import { createMockDatasource, createMockFramePublicAPI } from '../mocks'; import { LensIconChartBar } from '../assets/chart_bar'; @@ -25,9 +24,11 @@ import type { VisualizeEditorLayersContext } from '@kbn/visualizations-plugin/pu import { chartPluginMock } from '@kbn/charts-plugin/public/mocks'; import { fieldFormatsServiceMock } from '@kbn/field-formats-plugin/public/mocks'; import { Datatable } from '@kbn/expressions-plugin/common'; -import { themeServiceMock } from '@kbn/core/public/mocks'; +import { coreMock, themeServiceMock } from '@kbn/core/public/mocks'; import { eventAnnotationServiceMock } from '@kbn/event-annotation-plugin/public/mocks'; import { EventAnnotationConfig } from '@kbn/event-annotation-plugin/common'; +import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; +import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; const exampleAnnotation: EventAnnotationConfig = { id: 'an1', @@ -69,12 +70,14 @@ const paletteServiceMock = chartPluginMock.createPaletteRegistry(); const fieldFormatsMock = fieldFormatsServiceMock.createStartContract(); const xyVisualization = getXyVisualization({ - datatableUtilities: createDatatableUtilitiesMock(), paletteService: paletteServiceMock, fieldFormats: fieldFormatsMock, useLegacyTimeAxis: false, kibanaTheme: themeServiceMock.createStartContract(), eventAnnotationService: eventAnnotationServiceMock, + core: coreMock.createStart(), + storage: {} as IStorageWrapper, + data: dataPluginMock.createStartContract(), }); describe('xy_visualization', () => { @@ -335,6 +338,7 @@ describe('xy_visualization', () => { ]); frame = { + ...frame, datasourceLayers: { first: mockDatasource.publicAPIMock, }, diff --git a/x-pack/plugins/lens/public/xy_visualization/visualization.tsx b/x-pack/plugins/lens/public/xy_visualization/visualization.tsx index aedbcbb648957..4a318965265c6 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visualization.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/visualization.tsx @@ -206,7 +206,7 @@ export const getXyVisualization = ({ return state; } const newLayers = [...state.layers]; - newLayers[layerIndex] = { ...layer, indexPatternId }; + newLayers[layerIndex] = { ...layer }; return { ...state, layers: newLayers, diff --git a/x-pack/plugins/lens/public/xy_visualization/visualization_helpers.tsx b/x-pack/plugins/lens/public/xy_visualization/visualization_helpers.tsx index fb364d7bd09f7..34d9bbd4adc8c 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visualization_helpers.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/visualization_helpers.tsx @@ -287,7 +287,6 @@ const newLayerFn = { layerId, layerType: layerTypes.ANNOTATIONS, annotations: [], - indexPatternId, }), }; diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.test.ts b/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.test.ts index 14750dd34a127..6ea8b84abf936 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.test.ts +++ b/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.test.ts @@ -17,22 +17,25 @@ import { import { generateId } from '../id_generator'; import { getXyVisualization } from './xy_visualization'; import { chartPluginMock } from '@kbn/charts-plugin/public/mocks'; -import { createDatatableUtilitiesMock } from '@kbn/data-plugin/common/mocks'; import { eventAnnotationServiceMock } from '@kbn/event-annotation-plugin/public/mocks'; import type { PaletteOutput } from '@kbn/coloring'; import { layerTypes } from '../../common'; import { fieldFormatsServiceMock } from '@kbn/field-formats-plugin/public/mocks'; -import { themeServiceMock } from '@kbn/core/public/mocks'; +import { coreMock, themeServiceMock } from '@kbn/core/public/mocks'; +import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; +import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; jest.mock('../id_generator'); const xyVisualization = getXyVisualization({ - datatableUtilities: createDatatableUtilitiesMock(), paletteService: chartPluginMock.createPaletteRegistry(), fieldFormats: fieldFormatsServiceMock.createStartContract(), useLegacyTimeAxis: false, kibanaTheme: themeServiceMock.createStartContract(), eventAnnotationService: eventAnnotationServiceMock, + core: coreMock.createStart(), + storage: {} as IStorageWrapper, + data: dataPluginMock.createStartContract(), }); describe('xy_suggestions', () => { From 2ee6f6d9890cbfcfe66c1b1874d0771b579305c8 Mon Sep 17 00:00:00 2001 From: dej611 Date: Fri, 29 Jul 2022 17:12:46 +0200 Subject: [PATCH 10/27] :label: Fix types issues --- x-pack/plugins/lens/public/app_plugin/app.tsx | 2 +- .../lens/public/app_plugin/mounter.tsx | 2 +- .../plugins/lens/public/app_plugin/types.ts | 2 +- .../editor_frame/config_panel/layer_panel.tsx | 2 +- .../editor_frame/config_panel/types.ts | 2 +- .../editor_frame/data_panel_wrapper.tsx | 2 +- .../editor_frame/editor_frame.test.tsx | 4 +- .../editor_frame/editor_frame.tsx | 2 +- .../editor_frame/state_helpers.ts | 2 +- .../lens/public/embeddable/embeddable.tsx | 2 +- .../__mocks__/loader.ts | 22 +- .../indexpattern_datasource/datapanel.tsx | 2 +- .../indexpattern_datasource/loader.test.ts | 913 +++--------------- .../public/indexpattern_datasource/loader.ts | 4 +- .../formula/formula_public_api.test.ts | 2 +- .../definitions/formula/formula_public_api.ts | 2 +- .../indexpattern_service/loader.test.ts | 445 +++++++++ .../loader.ts | 18 +- .../lens/public/indexpattern_service/mocks.ts | 217 +++++ .../service.ts | 5 +- .../public/mocks/data_views_service_mock.ts | 2 +- .../plugins/lens/public/mocks/store_mocks.tsx | 2 +- .../public/state_management/lens_slice.ts | 8 +- x-pack/plugins/lens/public/types.ts | 2 +- x-pack/plugins/lens/public/utils.ts | 2 +- .../annotations/helpers.test.ts | 8 +- 26 files changed, 831 insertions(+), 845 deletions(-) create mode 100644 x-pack/plugins/lens/public/indexpattern_service/loader.test.ts rename x-pack/plugins/lens/public/{data_views_service => indexpattern_service}/loader.ts (94%) create mode 100644 x-pack/plugins/lens/public/indexpattern_service/mocks.ts rename x-pack/plugins/lens/public/{data_views_service => indexpattern_service}/service.ts (96%) diff --git a/x-pack/plugins/lens/public/app_plugin/app.tsx b/x-pack/plugins/lens/public/app_plugin/app.tsx index 93d14d4306fb0..7ff105adf43f7 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.tsx @@ -31,7 +31,7 @@ import { SaveModalContainer, runSaveLensVisualization } from './save_modal_conta import { LensInspector } from '../lens_inspector_service'; import { getEditPath } from '../../common'; import { isLensEqual } from './lens_document_equality'; -import { IndexPatternServiceAPI, createIndexPatternService } from '../data_views_service/service'; +import { IndexPatternServiceAPI, createIndexPatternService } from '../indexpattern_service/service'; export type SaveProps = Omit & { returnToOrigin: boolean; diff --git a/x-pack/plugins/lens/public/app_plugin/mounter.tsx b/x-pack/plugins/lens/public/app_plugin/mounter.tsx index 17e2ae3e0b2ed..4fcd426378e49 100644 --- a/x-pack/plugins/lens/public/app_plugin/mounter.tsx +++ b/x-pack/plugins/lens/public/app_plugin/mounter.tsx @@ -223,7 +223,7 @@ export async function mountApp( }; const lensStore: LensRootStore = makeConfigureStore(storeDeps, { lens: getPreloadedState(storeDeps) as LensAppState, - } as PreloadedState); + } as unknown as PreloadedState); const EditorRenderer = React.memo( (props: { id?: string; history: History; editByValue?: boolean }) => { diff --git a/x-pack/plugins/lens/public/app_plugin/types.ts b/x-pack/plugins/lens/public/app_plugin/types.ts index fe4df5f26593d..6a2718fc49820 100644 --- a/x-pack/plugins/lens/public/app_plugin/types.ts +++ b/x-pack/plugins/lens/public/app_plugin/types.ts @@ -47,7 +47,7 @@ import type { import type { LensAttributeService } from '../lens_attribute_service'; import type { LensEmbeddableInput } from '../embeddable/embeddable'; import type { LensInspector } from '../lens_inspector_service'; -import { IndexPatternServiceAPI } from '../data_views_service/service'; +import { IndexPatternServiceAPI } from '../indexpattern_service/service'; export interface RedirectToOriginProps { input?: LensEmbeddableInput; diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx index 915a1e05e6ec6..ea33bbc2ac91a 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx @@ -18,7 +18,7 @@ import { EuiIconTip, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { IndexPatternServiceAPI } from '../../../data_views_service/service'; +import { IndexPatternServiceAPI } from '../../../indexpattern_service/service'; import { NativeRenderer } from '../../../native_renderer'; import { StateSetter, diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/types.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/types.ts index c4a2d77c30bab..3e1458d42c438 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/types.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/types.ts @@ -6,7 +6,7 @@ */ import { UiActionsStart } from '@kbn/ui-actions-plugin/public'; -import { IndexPatternServiceAPI } from '../../../data_views_service/service'; +import { IndexPatternServiceAPI } from '../../../indexpattern_service/service'; import { Visualization, diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/data_panel_wrapper.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/data_panel_wrapper.tsx index 09f8233ef1bca..4cabda4cc62df 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/data_panel_wrapper.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/data_panel_wrapper.tsx @@ -29,7 +29,7 @@ import { selectDatasourceStates, } from '../../state_management'; import { initializeSources } from './state_helpers'; -import type { IndexPatternServiceAPI } from '../../data_views_service/service'; +import type { IndexPatternServiceAPI } from '../../indexpattern_service/service'; import { changeIndexPattern } from '../../state_management/lens_slice'; interface DataPanelWrapperProps { diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx index 035011b7d4a68..9c5b7740cacff 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx @@ -390,7 +390,7 @@ describe('editor_frame', () => { describe('datasource public api communication', () => { it('should give access to the datasource state in the datasource factory function', async () => { const datasourceState = {}; - mockDatasource.initialize.mockResolvedValue(datasourceState); + mockDatasource.initialize.mockReturnValue(datasourceState); mockDatasource.getLayers.mockReturnValue(['first']); const props = { @@ -503,7 +503,7 @@ describe('editor_frame', () => { it('should call datasource render with new state on switch', async () => { const initialState = {}; - mockDatasource2.initialize.mockResolvedValue(initialState); + mockDatasource2.initialize.mockReturnValue(initialState); instance.find('button[data-test-subj="datasource-switch"]').simulate('click'); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx index ed8357ea681ef..b381245994863 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx @@ -28,7 +28,7 @@ import { selectVisualization, } from '../../state_management'; import type { LensInspector } from '../../lens_inspector_service'; -import { IndexPatternServiceAPI } from '../../data_views_service/service'; +import { IndexPatternServiceAPI } from '../../indexpattern_service/service'; export interface EditorFrameProps { datasourceMap: DatasourceMap; diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts index f9136af24dde8..0a42917bb86ab 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts @@ -37,7 +37,7 @@ import { } from '../error_helper'; import type { DatasourceStates, DataViewsState } from '../../state_management'; import { readFromStorage } from '../../settings_storage'; -import { loadIndexPatternRefs, loadIndexPatterns } from '../../data_views_service/loader'; +import { loadIndexPatternRefs, loadIndexPatterns } from '../../indexpattern_service/loader'; function getIndexPatterns( references?: SavedObjectReference[], diff --git a/x-pack/plugins/lens/public/embeddable/embeddable.tsx b/x-pack/plugins/lens/public/embeddable/embeddable.tsx index 75787ba08402f..98dddaa0441bd 100644 --- a/x-pack/plugins/lens/public/embeddable/embeddable.tsx +++ b/x-pack/plugins/lens/public/embeddable/embeddable.tsx @@ -76,7 +76,7 @@ import { getLensInspectorService, LensInspector } from '../lens_inspector_servic import { SharingSavedObjectProps, VisualizationDisplayOptions } from '../types'; import { getActiveDatasourceIdFromDoc, getIndexPatternsObjects, inferTimeField } from '../utils'; import { getLayerMetaInfo, combineQueryAndFilters } from '../app_plugin/show_underlying_data'; -import { convertDataViewIntoLensIndexPattern } from '../data_views_service/loader'; +import { convertDataViewIntoLensIndexPattern } from '../indexpattern_service/loader'; export type LensSavedObjectAttributes = Omit; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/__mocks__/loader.ts b/x-pack/plugins/lens/public/indexpattern_datasource/__mocks__/loader.ts index 072087e0de65c..1273d596576c4 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/__mocks__/loader.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/__mocks__/loader.ts @@ -10,17 +10,9 @@ import { IndexPatternPrivateState } from '../types'; export function loadInitialState() { const indexPattern = createMockedIndexPattern(); - const restricted = createMockedRestrictedIndexPattern(); const result: IndexPatternPrivateState = { currentIndexPatternId: indexPattern.id, - indexPatternRefs: [], - existingFields: {}, - indexPatterns: { - [indexPattern.id]: indexPattern, - [restricted.id]: restricted, - }, layers: {}, - isFirstExistenceFetch: false, }; return result; } @@ -30,3 +22,17 @@ const originalLoader = jest.requireActual('../loader'); export const extractReferences = originalLoader.extractReferences; export const injectReferences = originalLoader.injectReferences; + +export function loadInitialDataViews() { + const indexPattern = createMockedIndexPattern(); + const restricted = createMockedRestrictedIndexPattern(); + return { + indexPatternRefs: [], + existingFields: {}, + indexPatterns: { + [indexPattern.id]: indexPattern, + [restricted.id]: restricted, + }, + isFirstExistenceFetch: false, + }; +} diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx index 17f065c4f05f5..8bac417fb53ae 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx @@ -47,7 +47,7 @@ import { Loader } from '../loader'; import { LensFieldIcon } from '../shared_components/field_picker/lens_field_icon'; import { FieldGroups, FieldList } from './field_list'; import { fieldContainsData, fieldExists } from '../shared_components'; -import { IndexPatternServiceAPI } from '../data_views_service/service'; +import { IndexPatternServiceAPI } from '../indexpattern_service/service'; export type Props = Omit< DatasourceDataPanelProps, 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 b3806e5c55b15..c31f9167e2b24 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/loader.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/loader.test.ts @@ -5,30 +5,16 @@ * 2.0. */ -import { HttpHandler } from '@kbn/core/public'; -import { last } from 'lodash'; import { loadInitialState, - loadIndexPatterns, changeIndexPattern, changeLayerIndexPattern, - syncExistingFields, extractReferences, injectReferences, } from './loader'; -import { DataViewsContract } from '@kbn/data-views-plugin/public'; -import { uiActionsPluginMock } from '@kbn/ui-actions-plugin/public/mocks'; -import { createHttpFetchError } from '@kbn/core-http-browser-mocks'; -import { - IndexPatternPersistedState, - IndexPatternPrivateState, - IndexPatternField, - IndexPattern, -} from './types'; -import { createMockedRestrictedIndexPattern, createMockedIndexPattern } from './mocks'; -import { documentField } from './document_field'; -import { DateHistogramIndexPatternColumn } from './operations'; -import { UiActionsStart } from '@kbn/ui-actions-plugin/public'; +import { IndexPatternPersistedState, IndexPatternPrivateState } from './types'; +import { DateHistogramIndexPatternColumn, TermsIndexPatternColumn } from './operations'; +import { sampleIndexPatterns } from '../indexpattern_service/mocks'; const createMockStorage = (lastData?: Record) => { return { @@ -39,351 +25,29 @@ const createMockStorage = (lastData?: Record) => { }; }; -const indexPattern1 = { - id: '1', - title: 'my-fake-index-pattern', - timeFieldName: 'timestamp', - hasRestrictions: false, - fields: [ - { - name: 'timestamp', - displayName: 'timestampLabel', - type: 'date', - aggregatable: true, - searchable: true, - }, - { - name: 'start_date', - displayName: 'start_date', - type: 'date', - aggregatable: true, - searchable: true, - }, - { - name: 'bytes', - displayName: 'bytes', - type: 'number', - aggregatable: true, - searchable: true, - }, - { - name: 'memory', - displayName: 'memory', - type: 'number', - aggregatable: true, - searchable: true, - }, - { - name: 'source', - displayName: 'source', - type: 'string', - aggregatable: true, - searchable: true, - esTypes: ['keyword'], - }, - { - name: 'unsupported', - displayName: 'unsupported', - type: 'geo', - aggregatable: true, - searchable: true, - }, - { - name: 'dest', - displayName: 'dest', - type: 'string', - aggregatable: true, - searchable: true, - esTypes: ['keyword'], - }, - { - name: 'geo.src', - displayName: 'geo.src', - type: 'string', - aggregatable: true, - searchable: true, - esTypes: ['keyword'], - }, - { - name: 'scripted', - displayName: 'Scripted', - type: 'string', - searchable: true, - aggregatable: true, - scripted: true, - lang: 'painless', - script: '1234', - }, - documentField, - ], -} as unknown as IndexPattern; - -const sampleIndexPatternsFromService = { - '1': createMockedIndexPattern(), - '2': createMockedRestrictedIndexPattern(), -}; - -const indexPattern2 = { - id: '2', - title: 'my-fake-restricted-pattern', - timeFieldName: 'timestamp', - hasRestrictions: true, - fieldFormatMap: { bytes: { id: 'bytes', params: { pattern: '0.0' } } }, - fields: [ - { - name: 'timestamp', - displayName: 'timestampLabel', - type: 'date', - aggregatable: true, - searchable: true, - aggregationRestrictions: { - date_histogram: { - agg: 'date_histogram', - fixed_interval: '1d', - delay: '7d', - time_zone: 'UTC', - }, - }, - }, - { - name: 'bytes', - displayName: 'bytes', - type: 'number', - aggregatable: true, - searchable: true, - aggregationRestrictions: { - // Ignored in the UI - histogram: { - agg: 'histogram', - interval: 1000, - }, - average: { - agg: 'avg', - }, - max: { - agg: 'max', - }, - min: { - agg: 'min', - }, - sum: { - agg: 'sum', - }, - }, - }, - { - name: 'source', - displayName: 'source', - type: 'string', - aggregatable: true, - searchable: true, - scripted: true, - lang: 'painless', - script: '1234', - aggregationRestrictions: { - terms: { - agg: 'terms', - }, - }, - }, - documentField, - ], -} as unknown as IndexPattern; - -const sampleIndexPatterns = { - '1': indexPattern1, - '2': indexPattern2, -}; - -function mockIndexPatternsService() { - return { - get: jest.fn(async (id: '1' | '2') => { - const result = { ...sampleIndexPatternsFromService[id], metaFields: [] }; - if (!result.fields) { - result.fields = []; - } - return result; - }), - getIdsWithTitle: jest.fn(async () => { - return [ - { - id: sampleIndexPatterns[1].id, - title: sampleIndexPatterns[1].title, - }, - { - id: sampleIndexPatterns[2].id, - title: sampleIndexPatterns[2].title, - }, - ]; - }), - } as unknown as Pick; -} +const indexPatternRefs = [ + { + id: sampleIndexPatterns[1].id, + title: sampleIndexPatterns[1].title, + }, + { + id: sampleIndexPatterns[2].id, + title: sampleIndexPatterns[2].title, + }, +]; describe('loader', () => { - describe('loadIndexPatterns', () => { - it('should not load index patterns that are already loaded', async () => { - const cache = await loadIndexPatterns({ - cache: sampleIndexPatterns, - patterns: ['1', '2'], - indexPatternsService: { - get: jest.fn(() => - Promise.reject('mockIndexPatternService.get should not have been called') - ), - getIdsWithTitle: jest.fn(), - } as unknown as Pick, - }); - - expect(cache).toEqual(sampleIndexPatterns); - }); - - it('should load index patterns that are not loaded', async () => { - const cache = await loadIndexPatterns({ - cache: { - '2': sampleIndexPatterns['2'], - }, - patterns: ['1', '2'], - indexPatternsService: mockIndexPatternsService(), - }); - - expect(cache).toMatchObject(sampleIndexPatterns); - }); - - it('should allow scripted, but not full text fields', async () => { - const cache = await loadIndexPatterns({ - cache: {}, - patterns: ['1', '2'], - indexPatternsService: mockIndexPatternsService(), - }); - - expect(cache).toMatchObject(sampleIndexPatterns); - }); - - it('should apply field restrictions from typeMeta', async () => { - const cache = await loadIndexPatterns({ - cache: {}, - patterns: ['foo'], - indexPatternsService: { - get: jest.fn(async () => ({ - id: 'foo', - title: 'Foo index', - metaFields: [], - typeMeta: { - aggs: { - date_histogram: { - timestamp: { - agg: 'date_histogram', - fixed_interval: 'm', - }, - }, - sum: { - bytes: { - agg: 'sum', - }, - }, - }, - }, - fields: [ - { - name: 'timestamp', - displayName: 'timestampLabel', - type: 'date', - aggregatable: true, - searchable: true, - }, - { - name: 'bytes', - displayName: 'bytes', - type: 'number', - aggregatable: true, - searchable: true, - }, - ], - })), - getIdsWithTitle: jest.fn(async () => ({ - id: 'foo', - title: 'Foo index', - })), - } as unknown as Pick, - }); - - expect(cache.foo.getFieldByName('bytes')!.aggregationRestrictions).toEqual({ - sum: { agg: 'sum' }, - }); - expect(cache.foo.getFieldByName('timestamp')!.aggregationRestrictions).toEqual({ - date_histogram: { agg: 'date_histogram', fixed_interval: 'm' }, - }); - }); - - it('should map meta flag', async () => { - const cache = await loadIndexPatterns({ - cache: {}, - patterns: ['foo'], - indexPatternsService: { - get: jest.fn(async () => ({ - id: 'foo', - title: 'Foo index', - metaFields: ['timestamp'], - typeMeta: { - aggs: { - date_histogram: { - timestamp: { - agg: 'date_histogram', - fixed_interval: 'm', - }, - }, - sum: { - bytes: { - agg: 'sum', - }, - }, - }, - }, - fields: [ - { - name: 'timestamp', - displayName: 'timestampLabel', - type: 'date', - aggregatable: true, - searchable: true, - }, - { - name: 'bytes', - displayName: 'bytes', - type: 'number', - aggregatable: true, - searchable: true, - }, - ], - })), - getIdsWithTitle: jest.fn(async () => ({ - id: 'foo', - title: 'Foo index', - })), - } as unknown as Pick, - }); - - expect(cache.foo.getFieldByName('timestamp')!.meta).toEqual(true); - }); - }); - describe('loadInitialState', () => { it('should load a default state', async () => { const storage = createMockStorage(); const state = await loadInitialState({ - indexPatternsService: mockIndexPatternsService(), + indexPatterns: sampleIndexPatterns, + indexPatternRefs, storage, - options: { isFullEditor: true }, }); expect(state).toMatchObject({ currentIndexPatternId: '1', - indexPatternRefs: [ - { id: '1', title: sampleIndexPatterns['1'].title }, - { id: '2', title: sampleIndexPatterns['2'].title }, - ], - indexPatterns: { - '1': sampleIndexPatterns['1'], - }, layers: {}, }); expect(storage.set).toHaveBeenCalledWith('lens-settings', { @@ -393,41 +57,30 @@ describe('loader', () => { it('should load a default state without loading the indexPatterns when embedded', async () => { const storage = createMockStorage(); - const indexPatternsService = mockIndexPatternsService(); const state = await loadInitialState({ - indexPatternsService, storage, - options: { isFullEditor: false }, + indexPatterns: {}, + indexPatternRefs: [], }); expect(state).toMatchObject({ currentIndexPatternId: undefined, - indexPatternRefs: [], - indexPatterns: {}, layers: {}, }); expect(storage.set).not.toHaveBeenCalled(); - expect(indexPatternsService.getIdsWithTitle).not.toHaveBeenCalled(); }); it('should load a default state when lastUsedIndexPatternId is not found in indexPatternRefs', async () => { const storage = createMockStorage({ indexPatternId: 'c' }); const state = await loadInitialState({ - indexPatternsService: mockIndexPatternsService(), storage, - options: { isFullEditor: true }, + indexPatternRefs, + indexPatterns: sampleIndexPatterns, }); expect(state).toMatchObject({ currentIndexPatternId: '1', - indexPatternRefs: [ - { id: '1', title: sampleIndexPatterns['1'].title }, - { id: '2', title: sampleIndexPatterns['2'].title }, - ], - indexPatterns: { - '1': sampleIndexPatterns['1'], - }, layers: {}, }); expect(storage.set).toHaveBeenCalledWith('lens-settings', { @@ -437,20 +90,13 @@ describe('loader', () => { it('should load lastUsedIndexPatternId if in localStorage', async () => { const state = await loadInitialState({ - indexPatternsService: mockIndexPatternsService(), storage: createMockStorage({ indexPatternId: '2' }), - options: { isFullEditor: true }, + indexPatternRefs, + indexPatterns: sampleIndexPatterns, }); expect(state).toMatchObject({ currentIndexPatternId: '2', - indexPatternRefs: [ - { id: '1', title: sampleIndexPatterns['1'].title }, - { id: '2', title: sampleIndexPatterns['2'].title }, - ], - indexPatterns: { - '2': sampleIndexPatterns['2'], - }, layers: {}, }); }); @@ -459,20 +105,13 @@ describe('loader', () => { const storage = createMockStorage(); const state = await loadInitialState({ defaultIndexPatternId: '2', - indexPatternsService: mockIndexPatternsService(), storage, - options: { isFullEditor: true }, + indexPatternRefs, + indexPatterns: sampleIndexPatterns, }); expect(state).toMatchObject({ currentIndexPatternId: '2', - indexPatternRefs: [ - { id: '1', title: sampleIndexPatterns['1'].title }, - { id: '2', title: sampleIndexPatterns['2'].title }, - ], - indexPatterns: { - '2': sampleIndexPatterns['2'], - }, layers: {}, }); expect(storage.set).toHaveBeenCalledWith('lens-settings', { @@ -483,24 +122,17 @@ describe('loader', () => { it('should use the indexPatternId of the visualize trigger field, if provided', async () => { const storage = createMockStorage(); const state = await loadInitialState({ - indexPatternsService: mockIndexPatternsService(), storage, initialContext: { indexPatternId: '1', fieldName: '', }, - options: { isFullEditor: true }, + indexPatternRefs, + indexPatterns: sampleIndexPatterns, }); expect(state).toMatchObject({ currentIndexPatternId: '1', - indexPatternRefs: [ - { id: '1', title: sampleIndexPatterns['1'].title }, - { id: '2', title: sampleIndexPatterns['2'].title }, - ], - indexPatterns: { - '1': sampleIndexPatterns['1'], - }, layers: {}, }); expect(storage.set).toHaveBeenCalledWith('lens-settings', { @@ -511,7 +143,6 @@ describe('loader', () => { it('should use the indexPatternId of the visualize trigger chart context, if provided', async () => { const storage = createMockStorage(); const state = await loadInitialState({ - indexPatternsService: mockIndexPatternsService(), storage, initialContext: { layers: [ @@ -541,18 +172,12 @@ describe('loader', () => { savedObjectId: '', isVisualizeAction: true, }, - options: { isFullEditor: true }, + indexPatternRefs, + indexPatterns: sampleIndexPatterns, }); expect(state).toMatchObject({ currentIndexPatternId: '1', - indexPatternRefs: [ - { id: '1', title: sampleIndexPatterns['1'].title }, - { id: '2', title: sampleIndexPatterns['2'].title }, - ], - indexPatterns: { - '1': sampleIndexPatterns['1'], - }, layers: {}, }); expect(storage.set).toHaveBeenCalledWith('lens-settings', { @@ -594,17 +219,15 @@ describe('loader', () => { { name: 'indexpattern-datasource-layer-layerb', id: '2', type: 'index-pattern' }, { name: 'another-reference', id: 'c', type: 'index-pattern' }, ], - indexPatternsService: mockIndexPatternsService(), + indexPatternRefs: [], + indexPatterns: { + '2': sampleIndexPatterns['2'], + }, storage, - options: { isFullEditor: false }, }); expect(state).toMatchObject({ currentIndexPatternId: undefined, - indexPatternRefs: [], - indexPatterns: { - '2': sampleIndexPatterns['2'], - }, layers: { layerb: { ...savedState.layers.layerb, indexPatternId: '2' } }, }); expect(storage.set).not.toHaveBeenCalled(); @@ -644,111 +267,28 @@ describe('loader', () => { { name: 'indexpattern-datasource-layer-layerb', id: '2', type: 'index-pattern' }, { name: 'another-reference', id: 'c', type: 'index-pattern' }, ], - indexPatternsService: mockIndexPatternsService(), - storage, - options: { isFullEditor: true }, - }); - expect(state).toMatchObject({ - currentIndexPatternId: '2', - indexPatternRefs: [ - { id: '1', title: sampleIndexPatterns['1'].title }, - { id: '2', title: sampleIndexPatterns['2'].title }, - ], + indexPatternRefs, indexPatterns: { '2': sampleIndexPatterns['2'], }, - layers: { layerb: { ...savedState.layers.layerb, indexPatternId: '2' } }, - }); - - expect(storage.set).toHaveBeenCalledWith('lens-settings', { - indexPatternId: '2', - }); - }); - - it('should default to the first loaded index pattern if could not load any used one or one from the storage', async () => { - function mockIndexPatternsServiceWithConflict() { - return { - get: jest.fn(async (id: '1' | '2' | 'conflictId') => { - if (id === 'conflictId') { - return Promise.reject(new Error('Oh noes conflict boom')); - } - const result = { ...sampleIndexPatternsFromService[id], metaFields: [] }; - if (!result.fields) { - result.fields = []; - } - return result; - }), - getIdsWithTitle: jest.fn(async () => { - return [ - { - id: sampleIndexPatterns[1].id, - title: sampleIndexPatterns[1].title, - }, - { - id: sampleIndexPatterns[2].id, - title: sampleIndexPatterns[2].title, - }, - { - id: 'conflictId', - title: 'conflictId title', - }, - ]; - }), - } as unknown as Pick; - } - const savedState: IndexPatternPersistedState = { - layers: { - layerb: { - columnOrder: ['col1', 'col2'], - columns: { - col1: { - dataType: 'date', - isBucketed: true, - label: 'My date', - operationType: 'date_histogram', - params: { - interval: 'm', - }, - sourceField: 'timestamp', - } as DateHistogramIndexPatternColumn, - col2: { - dataType: 'number', - isBucketed: false, - label: 'Sum of bytes', - operationType: 'sum', - sourceField: 'bytes', - }, - }, - }, - }, - }; - const storage = createMockStorage({ indexPatternId: 'conflictId' }); - const state = await loadInitialState({ - persistedState: savedState, - references: [ - { name: 'indexpattern-datasource-layer-layerb', id: 'conflictId', type: 'index-pattern' }, - ], - indexPatternsService: mockIndexPatternsServiceWithConflict(), storage, - options: { isFullEditor: true }, }); expect(state).toMatchObject({ - currentIndexPatternId: '1', + currentIndexPatternId: '2', indexPatternRefs: [ - { id: 'conflictId', title: 'conflictId title' }, { id: '1', title: sampleIndexPatterns['1'].title }, { id: '2', title: sampleIndexPatterns['2'].title }, ], indexPatterns: { - '1': sampleIndexPatterns['1'], + '2': sampleIndexPatterns['2'], }, - layers: { layerb: { ...savedState.layers.layerb, indexPatternId: 'conflictId' } }, + layers: { layerb: { ...savedState.layers.layerb, indexPatternId: '2' } }, }); expect(storage.set).toHaveBeenCalledWith('lens-settings', { - indexPatternId: '1', + indexPatternId: '2', }); }); }); @@ -756,9 +296,6 @@ describe('loader', () => { describe('saved object references', () => { const state: IndexPatternPrivateState = { currentIndexPatternId: 'b', - indexPatternRefs: [], - indexPatterns: {}, - existingFields: {}, layers: { a: { indexPatternId: 'id-index-pattern-a', @@ -787,7 +324,6 @@ describe('loader', () => { }, }, }, - isFirstExistenceFetch: false, }; it('should create a reference for each layer and for current index pattern', () => { @@ -815,95 +351,111 @@ describe('loader', () => { }); describe('changeIndexPattern', () => { - it('loads the index pattern and then sets it as current', async () => { - const setState = jest.fn(); + it('sets the given indexpattern as current', () => { const state: IndexPatternPrivateState = { currentIndexPatternId: '2', - indexPatternRefs: [], - indexPatterns: {}, - existingFields: {}, layers: {}, - isFirstExistenceFetch: false, }; const storage = createMockStorage({ indexPatternId: '2' }); - await changeIndexPattern({ + const newState = changeIndexPattern({ + indexPatternId: '1', state, - setState, - id: '1', - indexPatternsService: mockIndexPatternsService(), - onError: jest.fn(), storage, + indexPatterns: { + '1': sampleIndexPatterns['1'], + }, }); - expect(setState).toHaveBeenCalledTimes(1); - const [fn, options] = setState.mock.calls[0]; - expect(options).toEqual({ applyImmediately: true }); - expect(fn(state)).toMatchObject({ + expect(newState).toMatchObject({ currentIndexPatternId: '1', - indexPatterns: { - '1': { - ...sampleIndexPatterns['1'], - fields: [...sampleIndexPatterns['1'].fields], - }, - }, }); expect(storage.set).toHaveBeenCalledWith('lens-settings', { indexPatternId: '1', }); }); - it('handles errors', async () => { - const setState = jest.fn(); - const onError = jest.fn(); - const err = new Error('NOPE!'); + it('should update an empty layer on indexpattern change', () => { const state: IndexPatternPrivateState = { currentIndexPatternId: '2', - indexPatternRefs: [], - existingFields: {}, - indexPatterns: {}, - layers: {}, - isFirstExistenceFetch: false, + layers: { layerId: { columnOrder: [], columns: {}, indexPatternId: '2' } }, }; - const storage = createMockStorage({ indexPatternId: '2' }); - await changeIndexPattern({ + const newState = changeIndexPattern({ + indexPatternId: '1', state, - setState, - id: '1', - indexPatternsService: { - get: jest.fn(async () => { - throw err; - }), - getIdsWithTitle: jest.fn(), - }, - onError, storage, + indexPatterns: sampleIndexPatterns, }); - expect(setState).not.toHaveBeenCalled(); - expect(storage.set).not.toHaveBeenCalled(); - expect(onError).toHaveBeenCalledWith(Error('Missing indexpatterns')); + expect(newState.layers.layerId).toEqual({ + columnOrder: [], + columns: {}, + indexPatternId: '1', + }); }); - }); - describe('changeLayerIndexPattern', () => { - let uiActions: UiActionsStart; + it('should keep layer indexpattern on change if not empty', () => { + const state: IndexPatternPrivateState = { + currentIndexPatternId: '2', + layers: { + layerId: { + columnOrder: ['col1'], + columns: { + col1: { + dataType: 'string', + isBucketed: true, + label: '', + operationType: 'terms', + sourceField: 'bytes', + params: { + orderBy: { type: 'alphabetical' }, + orderDirection: 'asc', + size: 3, + }, + } as TermsIndexPatternColumn, + }, + indexPatternId: '2', + }, + }, + }; + const storage = createMockStorage({ indexPatternId: '2' }); - beforeEach(() => { - uiActions = uiActionsPluginMock.createStartContract(); + const newState = changeIndexPattern({ + indexPatternId: '1', + state, + storage, + indexPatterns: sampleIndexPatterns, + }); + + expect(newState.layers).toEqual({ + layerId: { + columnOrder: ['col1'], + columns: { + col1: { + dataType: 'string', + isBucketed: true, + label: '', + operationType: 'terms', + sourceField: 'bytes', + params: { + orderBy: { type: 'alphabetical' }, + orderDirection: 'asc', + size: 3, + }, + }, + }, + indexPatternId: '2', + }, + }); }); + }); + describe('changeLayerIndexPattern', () => { it('loads the index pattern and then changes the specified layer', async () => { - const setState = jest.fn(); const state: IndexPatternPrivateState = { - currentIndexPatternId: '2', - indexPatternRefs: [], - existingFields: {}, - indexPatterns: { - '1': sampleIndexPatterns['1'], - }, + currentIndexPatternId: '1', layers: { l0: { columnOrder: ['col1'], @@ -927,29 +479,20 @@ describe('loader', () => { indexPatternId: '1', }, }, - isFirstExistenceFetch: false, }; const storage = createMockStorage({ indexPatternId: '1' }); - await changeLayerIndexPattern({ + const newState = changeLayerIndexPattern({ state, - setState, indexPatternId: '2', layerId: 'l1', - indexPatternsService: mockIndexPatternsService(), - onError: jest.fn(), storage, - uiActions, + indexPatterns: sampleIndexPatterns, }); - expect(setState).toHaveBeenCalledTimes(1); - expect(setState.mock.calls[0][0](state)).toMatchObject({ + expect(newState).toMatchObject({ currentIndexPatternId: '2', - indexPatterns: { - 1: sampleIndexPatterns['1'], - 2: sampleIndexPatterns['2'], - }, layers: { l0: { columnOrder: ['col1'], @@ -978,239 +521,5 @@ describe('loader', () => { indexPatternId: '2', }); }); - - it('handles errors', async () => { - const setState = jest.fn(); - const onError = jest.fn(); - const err = new Error('NOPE!'); - const state: IndexPatternPrivateState = { - currentIndexPatternId: '2', - indexPatternRefs: [], - existingFields: {}, - indexPatterns: { - '1': sampleIndexPatterns['1'], - }, - layers: { - l0: { - columnOrder: ['col1'], - columns: {}, - indexPatternId: '1', - }, - }, - isFirstExistenceFetch: false, - }; - - const storage = createMockStorage({ indexPatternId: '2' }); - - await changeLayerIndexPattern({ - state, - setState, - indexPatternId: '2', - layerId: 'l0', - indexPatternsService: { - get: jest.fn(async () => { - throw err; - }), - getIdsWithTitle: jest.fn(), - }, - onError, - storage, - uiActions, - }); - - expect(setState).not.toHaveBeenCalled(); - expect(storage.set).not.toHaveBeenCalled(); - expect(onError).toHaveBeenCalledWith(Error('Missing indexpatterns')); - }); - }); - - describe('syncExistingFields', () => { - const dslQuery = { - bool: { - must: [], - filter: [{ match_all: {} }], - should: [], - must_not: [], - }, - }; - - it('should call once for each index pattern', async () => { - const setState = jest.fn(); - const fetchJson = jest.fn((path: string) => { - const indexPatternTitle = last(path.split('/')); - return { - indexPatternTitle, - existingFieldNames: ['field_1', 'field_2'].map( - (fieldName) => `ip${indexPatternTitle}_${fieldName}` - ), - }; - }) as unknown as HttpHandler; - - await syncExistingFields({ - dateRange: { fromDate: '1900-01-01', toDate: '2000-01-01' }, - fetchJson, - indexPatterns: [ - { id: '1', title: '1', fields: [], hasRestrictions: false }, - { id: '2', title: '1', fields: [], hasRestrictions: false }, - { id: '3', title: '1', fields: [], hasRestrictions: false }, - ], - setState, - dslQuery, - showNoDataPopover: jest.fn(), - currentIndexPatternTitle: 'abc', - isFirstExistenceFetch: false, - }); - - expect(fetchJson).toHaveBeenCalledTimes(3); - expect(setState).toHaveBeenCalledTimes(1); - - const [fn, options] = setState.mock.calls[0]; - expect(options).toEqual({ applyImmediately: true }); - const newState = fn({ - foo: 'bar', - existingFields: {}, - }); - - expect(newState).toEqual({ - foo: 'bar', - isFirstExistenceFetch: false, - existenceFetchFailed: false, - existenceFetchTimeout: false, - existingFields: { - '1': { ip1_field_1: true, ip1_field_2: true }, - '2': { ip2_field_1: true, ip2_field_2: true }, - '3': { ip3_field_1: true, ip3_field_2: true }, - }, - }); - }); - - it('should call showNoDataPopover callback if current index pattern returns no fields', async () => { - const setState = jest.fn(); - const showNoDataPopover = jest.fn(); - const fetchJson = jest.fn((path: string) => { - const indexPatternTitle = last(path.split('/')); - return { - indexPatternTitle, - existingFieldNames: - indexPatternTitle === '1' - ? ['field_1', 'field_2'].map((fieldName) => `${indexPatternTitle}_${fieldName}`) - : [], - }; - }) as unknown as HttpHandler; - - const args = { - dateRange: { fromDate: '1900-01-01', toDate: '2000-01-01' }, - fetchJson, - indexPatterns: [ - { id: '1', title: '1', fields: [], hasRestrictions: false }, - { id: '2', title: '1', fields: [], hasRestrictions: false }, - { id: 'c', title: '1', fields: [], hasRestrictions: false }, - ], - setState, - dslQuery, - showNoDataPopover: jest.fn(), - currentIndexPatternTitle: 'abc', - isFirstExistenceFetch: false, - }; - - await syncExistingFields(args); - - expect(showNoDataPopover).not.toHaveBeenCalled(); - - 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: '1', - title: '1', - hasRestrictions: false, - fields: [{ name: 'field1' }, { name: 'field2' }] as IndexPatternField[], - }, - ], - setState, - dslQuery, - showNoDataPopover: jest.fn(), - currentIndexPatternTitle: 'abc', - isFirstExistenceFetch: false, - }; - - await syncExistingFields(args); - - const [fn, options] = setState.mock.calls[0]; - expect(options).toEqual({ applyImmediately: true }); - const newState = fn({ - foo: 'bar', - existingFields: {}, - }) as IndexPatternPrivateState; - - expect(newState.existenceFetchFailed).toEqual(true); - expect(newState.existenceFetchTimeout).toEqual(false); - expect(newState.existingFields['1']).toEqual({ - field1: true, - field2: true, - }); - }); - - it('should set all fields to available and existence error flag if the request times out', async () => { - const setState = jest.fn(); - const fetchJson = jest.fn((path: string) => { - return new Promise((resolve, reject) => { - const error = createHttpFetchError( - 'timeout', - 'error', - {} as Request, - { status: 408 } as Response - ); - reject(error); - }); - }) as unknown as HttpHandler; - - const args = { - dateRange: { fromDate: '1900-01-01', toDate: '2000-01-01' }, - fetchJson, - indexPatterns: [ - { - id: '1', - title: '1', - hasRestrictions: false, - fields: [{ name: 'field1' }, { name: 'field2' }] as IndexPatternField[], - }, - ], - setState, - dslQuery, - showNoDataPopover: jest.fn(), - currentIndexPatternTitle: 'abc', - isFirstExistenceFetch: false, - }; - - await syncExistingFields(args); - - const [fn, options] = setState.mock.calls[0]; - expect(options).toEqual({ applyImmediately: true }); - const newState = fn({ - foo: 'bar', - existingFields: {}, - }) as IndexPatternPrivateState; - - expect(newState.existenceFetchFailed).toEqual(false); - expect(newState.existenceFetchTimeout).toEqual(true); - expect(newState.existingFields['1']).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 204e576140411..f7a6912390027 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/loader.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/loader.ts @@ -77,7 +77,7 @@ export function injectReferences( }; } -export function createStateFromPersisted({ +function createStateFromPersisted({ persistedState, references, }: { @@ -87,7 +87,7 @@ export function createStateFromPersisted({ return persistedState && references ? injectReferences(persistedState, references) : undefined; } -export function getUsedIndexPatterns({ +function getUsedIndexPatterns({ state, indexPatternRefs, storage, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula_public_api.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula_public_api.test.ts index fec643772cc76..0582d5fcf7693 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula_public_api.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula_public_api.test.ts @@ -10,7 +10,7 @@ import { createFormulaPublicApi, FormulaPublicApi } from './formula_public_api'; import type { DataView } from '@kbn/data-views-plugin/public'; import type { DateHistogramIndexPatternColumn, PersistedIndexPatternLayer } from '../../../types'; -import { convertDataViewIntoLensIndexPattern } from '../../../../data_views_service/loader'; +import { convertDataViewIntoLensIndexPattern } from '../../../../indexpattern_service/loader'; jest.mock('./parse', () => ({ insertOrReplaceFormulaColumn: jest.fn().mockReturnValue({}), diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula_public_api.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula_public_api.ts index 4085ec931d3a6..f5080de84d67d 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula_public_api.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula_public_api.ts @@ -6,7 +6,7 @@ */ import type { DataView } from '@kbn/data-views-plugin/public'; -import { convertDataViewIntoLensIndexPattern } from '../../../../data_views_service/loader'; +import { convertDataViewIntoLensIndexPattern } from '../../../../indexpattern_service/loader'; import type { IndexPattern } from '../../../../types'; import type { PersistedIndexPatternLayer } from '../../../types'; diff --git a/x-pack/plugins/lens/public/indexpattern_service/loader.test.ts b/x-pack/plugins/lens/public/indexpattern_service/loader.test.ts new file mode 100644 index 0000000000000..e43b79c82cfbf --- /dev/null +++ b/x-pack/plugins/lens/public/indexpattern_service/loader.test.ts @@ -0,0 +1,445 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { HttpHandler } from '@kbn/core/public'; +import { last } from 'lodash'; +import { DataViewsContract } from '@kbn/data-views-plugin/public'; +import { createHttpFetchError } from '@kbn/core-http-browser-mocks'; +import { IndexPattern, IndexPatternField } from '../types'; +import { + ensureIndexPattern, + loadIndexPatternRefs, + loadIndexPatterns, + syncExistingFields, +} from './loader'; +import { sampleIndexPatterns, mockDataViewsService } from './mocks'; +import { documentField } from '../indexpattern_datasource/document_field'; + +describe('loader', () => { + describe('loadIndexPatternRefs', () => { + it('should return a list of sorted indexpattern refs', async () => { + const refs = await loadIndexPatternRefs(mockDataViewsService()); + expect(refs[0].title < refs[1].title).toBeTruthy(); + }); + }); + + describe('loadIndexPatterns', () => { + it('should not load index patterns that are already loaded', async () => { + const dataViewsService = mockDataViewsService(); + dataViewsService.get = jest.fn(() => + Promise.reject('mockIndexPatternService.get should not have been called') + ); + + const cache = await loadIndexPatterns({ + cache: sampleIndexPatterns, + patterns: ['1', '2'], + dataViews: dataViewsService, + }); + + expect(cache).toEqual(sampleIndexPatterns); + }); + + it('should load index patterns that are not loaded', async () => { + const cache = await loadIndexPatterns({ + cache: { + '2': sampleIndexPatterns['2'], + }, + patterns: ['1', '2'], + dataViews: mockDataViewsService(), + }); + + expect(cache).toMatchObject(sampleIndexPatterns); + }); + + it('should allow scripted, but not full text fields', async () => { + const cache = await loadIndexPatterns({ + cache: {}, + patterns: ['1', '2'], + dataViews: mockDataViewsService(), + }); + + expect(cache).toMatchObject(sampleIndexPatterns); + }); + + it('should apply field restrictions from typeMeta', async () => { + const cache = await loadIndexPatterns({ + cache: {}, + patterns: ['foo'], + dataViews: { + get: jest.fn(async () => ({ + id: 'foo', + title: 'Foo index', + metaFields: [], + typeMeta: { + aggs: { + date_histogram: { + timestamp: { + agg: 'date_histogram', + fixed_interval: 'm', + }, + }, + sum: { + bytes: { + agg: 'sum', + }, + }, + }, + }, + fields: [ + { + name: 'timestamp', + displayName: 'timestampLabel', + type: 'date', + aggregatable: true, + searchable: true, + }, + { + name: 'bytes', + displayName: 'bytes', + type: 'number', + aggregatable: true, + searchable: true, + }, + ], + })), + getIdsWithTitle: jest.fn(async () => ({ + id: 'foo', + title: 'Foo index', + })), + } as unknown as Pick, + }); + + expect(cache.foo.getFieldByName('bytes')!.aggregationRestrictions).toEqual({ + sum: { agg: 'sum' }, + }); + expect(cache.foo.getFieldByName('timestamp')!.aggregationRestrictions).toEqual({ + date_histogram: { agg: 'date_histogram', fixed_interval: 'm' }, + }); + }); + + it('should map meta flag', async () => { + const cache = await loadIndexPatterns({ + cache: {}, + patterns: ['foo'], + dataViews: { + get: jest.fn(async () => ({ + id: 'foo', + title: 'Foo index', + metaFields: ['timestamp'], + typeMeta: { + aggs: { + date_histogram: { + timestamp: { + agg: 'date_histogram', + fixed_interval: 'm', + }, + }, + sum: { + bytes: { + agg: 'sum', + }, + }, + }, + }, + fields: [ + { + name: 'timestamp', + displayName: 'timestampLabel', + type: 'date', + aggregatable: true, + searchable: true, + }, + { + name: 'bytes', + displayName: 'bytes', + type: 'number', + aggregatable: true, + searchable: true, + }, + ], + })), + getIdsWithTitle: jest.fn(async () => ({ + id: 'foo', + title: 'Foo index', + })), + } as unknown as Pick, + }); + + expect(cache.foo.getFieldByName('timestamp')!.meta).toEqual(true); + }); + + it('should call the refresh callback when loading new indexpatterns', async () => { + const onIndexPatternRefresh = jest.fn(); + await loadIndexPatterns({ + cache: { + '2': sampleIndexPatterns['2'], + }, + patterns: ['1', '2'], + dataViews: mockDataViewsService(), + onIndexPatternRefresh, + }); + + expect(onIndexPatternRefresh).toHaveBeenCalled(); + }); + + it('should not call the refresh callback when using the cache', async () => { + const onIndexPatternRefresh = jest.fn(); + await loadIndexPatterns({ + cache: sampleIndexPatterns, + patterns: ['1', '2'], + dataViews: mockDataViewsService(), + onIndexPatternRefresh, + }); + + expect(onIndexPatternRefresh).not.toHaveBeenCalled(); + }); + + it('should load one of the not used indexpatterns if all used ones are not available', async () => { + const dataViewsService = { + get: jest.fn(async (id: string) => { + if (id === '3') { + return { + id: '3', + title: 'my-fake-index-pattern', + timeFieldName: 'timestamp', + hasRestrictions: false, + fields: [], + }; + } + return Promise.reject(); + }), + getIdsWithTitle: jest.fn(), + } as unknown as Pick; + const cache = await loadIndexPatterns({ + cache: {}, + patterns: ['1', '2'], + notUsedPatterns: ['3'], + dataViews: dataViewsService, + }); + + expect(cache).toEqual({ + 3: expect.objectContaining({ + id: '3', + title: 'my-fake-index-pattern', + timeFieldName: 'timestamp', + hasRestrictions: false, + fields: [documentField], + }), + }); + }); + }); + + describe('ensureIndexPattern', () => { + it('should throw if the requested indexPattern cannot be loaded', async () => { + const err = Error('NOPE!'); + const onError = jest.fn(); + const cache = await ensureIndexPattern({ + id: '3', + dataViews: { + get: jest.fn(async () => { + throw err; + }), + getIdsWithTitle: jest.fn(), + } as unknown as Pick, + onError, + }); + + expect(cache).toBeUndefined(); + expect(onError).toHaveBeenCalledWith(Error('Missing indexpatterns')); + }); + + it('should ensure the requested indexpattern is loaded into the cache', async () => { + const onError = jest.fn(); + const cache = await ensureIndexPattern({ + id: '2', + dataViews: mockDataViewsService(), + onError, + }); + expect(cache).toMatchObject({ 2: sampleIndexPatterns['2'] }); + expect(onError).not.toHaveBeenCalled(); + }); + }); + + describe('syncExistingFields', () => { + const dslQuery = { + bool: { + must: [], + filter: [{ match_all: {} }], + should: [], + must_not: [], + }, + }; + + function getIndexPatternList() { + return [ + { id: '1', title: '1', fields: [], hasRestrictions: false }, + { id: '2', title: '1', fields: [], hasRestrictions: false }, + { id: '3', title: '1', fields: [], hasRestrictions: false }, + ] as unknown as IndexPattern[]; + } + + it('should call once for each index pattern', async () => { + const updateIndexPatterns = jest.fn(); + const fetchJson = jest.fn((path: string) => { + const indexPatternTitle = last(path.split('/')); + return { + indexPatternTitle, + existingFieldNames: ['field_1', 'field_2'].map( + (fieldName) => `ip${indexPatternTitle}_${fieldName}` + ), + }; + }) as unknown as HttpHandler; + + await syncExistingFields({ + dateRange: { fromDate: '1900-01-01', toDate: '2000-01-01' }, + fetchJson, + indexPatternList: getIndexPatternList(), + updateIndexPatterns, + dslQuery, + onNoData: jest.fn(), + currentIndexPatternTitle: 'abc', + isFirstExistenceFetch: false, + existingFields: {}, + }); + + expect(fetchJson).toHaveBeenCalledTimes(3); + expect(updateIndexPatterns).toHaveBeenCalledTimes(1); + + const [newState, options] = updateIndexPatterns.mock.calls[0]; + expect(options).toEqual({ applyImmediately: true }); + + expect(newState).toEqual({ + isFirstExistenceFetch: false, + existingFields: { + '1': { ip1_field_1: true, ip1_field_2: true }, + '2': { ip2_field_1: true, ip2_field_2: true }, + '3': { ip3_field_1: true, ip3_field_2: true }, + }, + }); + }); + + it('should call onNoData callback if current index pattern returns no fields', async () => { + const updateIndexPatterns = jest.fn(); + const onNoData = jest.fn(); + const fetchJson = jest.fn((path: string) => { + const indexPatternTitle = last(path.split('/')); + return { + indexPatternTitle, + existingFieldNames: + indexPatternTitle === '1' + ? ['field_1', 'field_2'].map((fieldName) => `${indexPatternTitle}_${fieldName}`) + : [], + }; + }) as unknown as HttpHandler; + + const args = { + dateRange: { fromDate: '1900-01-01', toDate: '2000-01-01' }, + fetchJson, + indexPatternList: getIndexPatternList(), + updateIndexPatterns, + dslQuery, + onNoData, + currentIndexPatternTitle: 'abc', + isFirstExistenceFetch: false, + existingFields: {}, + }; + + await syncExistingFields(args); + + expect(onNoData).not.toHaveBeenCalled(); + + await syncExistingFields({ ...args, isFirstExistenceFetch: true }); + expect(onNoData).not.toHaveBeenCalled(); + }); + + it('should set all fields to available and existence error flag if the request fails', async () => { + const updateIndexPatterns = 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, + indexPatternList: [ + { + id: '1', + title: '1', + hasRestrictions: false, + fields: [{ name: 'field1' }, { name: 'field2' }] as IndexPatternField[], + }, + ] as IndexPattern[], + updateIndexPatterns, + dslQuery, + onNoData: jest.fn(), + currentIndexPatternTitle: 'abc', + isFirstExistenceFetch: false, + existingFields: {}, + }; + + await syncExistingFields(args); + + const [newState, options] = updateIndexPatterns.mock.calls[0]; + expect(options).toEqual({ applyImmediately: true }); + + expect(newState.existenceFetchFailed).toEqual(true); + expect(newState.existenceFetchTimeout).toEqual(false); + expect(newState.existingFields['1']).toEqual({ + field1: true, + field2: true, + }); + }); + + it('should set all fields to available and existence error flag if the request times out', async () => { + const updateIndexPatterns = jest.fn(); + const fetchJson = jest.fn((path: string) => { + return new Promise((resolve, reject) => { + const error = createHttpFetchError( + 'timeout', + 'error', + {} as Request, + { status: 408 } as Response + ); + reject(error); + }); + }) as unknown as HttpHandler; + + const args = { + dateRange: { fromDate: '1900-01-01', toDate: '2000-01-01' }, + fetchJson, + indexPatternList: [ + { + id: '1', + title: '1', + hasRestrictions: false, + fields: [{ name: 'field1' }, { name: 'field2' }] as IndexPatternField[], + }, + ] as IndexPattern[], + updateIndexPatterns, + dslQuery, + onNoData: jest.fn(), + currentIndexPatternTitle: 'abc', + isFirstExistenceFetch: false, + existingFields: {}, + }; + + await syncExistingFields(args); + + const [newState, options] = updateIndexPatterns.mock.calls[0]; + expect(options).toEqual({ applyImmediately: true }); + + expect(newState.existenceFetchFailed).toEqual(false); + expect(newState.existenceFetchTimeout).toEqual(true); + expect(newState.existingFields['1']).toEqual({ + field1: true, + field2: true, + }); + }); + }); +}); diff --git a/x-pack/plugins/lens/public/data_views_service/loader.ts b/x-pack/plugins/lens/public/indexpattern_service/loader.ts similarity index 94% rename from x-pack/plugins/lens/public/data_views_service/loader.ts rename to x-pack/plugins/lens/public/indexpattern_service/loader.ts index ac1a894ed5809..a45c6ecf5cda7 100644 --- a/x-pack/plugins/lens/public/data_views_service/loader.ts +++ b/x-pack/plugins/lens/public/indexpattern_service/loader.ts @@ -15,6 +15,7 @@ import { BASE_API_URL, DateRange, ExistingFields } from '../../common'; import { DataViewsState } from '../state_management'; type ErrorHandler = (err: Error) => void; +type MinimalDataViewsContract = Pick; /** * All these functions will be used by the Embeddable instance too, @@ -84,6 +85,7 @@ export function convertDataViewIntoLensIndexPattern( Object.fromEntries( Object.entries(fieldFormatMap).map(([id, format]) => [ id, + // @ts-ignore 'toJSON' in format ? format.toJSON() : format, ]) ), @@ -94,7 +96,7 @@ export function convertDataViewIntoLensIndexPattern( } export async function loadIndexPatternRefs( - indexPatternsService: DataViewsContract + indexPatternsService: MinimalDataViewsContract ): Promise { const indexPatterns = await indexPatternsService.getIdsWithTitle(); @@ -122,7 +124,7 @@ export async function loadIndexPatterns({ cache, onIndexPatternRefresh, }: { - dataViews: DataViewsContract; + dataViews: MinimalDataViewsContract; patterns: string[]; notUsedPatterns?: string[]; cache: Record; @@ -167,7 +169,7 @@ export async function loadIndexPatterns({ return indexPatternsObject; } -export async function loadIndexPattern({ +export async function ensureIndexPattern({ id, onError, dataViews, @@ -175,7 +177,7 @@ export async function loadIndexPattern({ }: { id: string; onError: ErrorHandler; - dataViews: DataViewsContract; + dataViews: MinimalDataViewsContract; cache?: IndexPatternMap; }) { const indexPatterns = await loadIndexPatterns({ @@ -288,13 +290,13 @@ export async function syncExistingFields({ updateIndexPatterns( { - isFirstExistenceFetch: status !== 200, existingFields: newExistingFields, ...(result - ? {} + ? { isFirstExistenceFetch: status !== 200 } : { - existenceFetchFailed: status !== 418, - existenceFetchTimeout: status === 418, + isFirstExistenceFetch, + existenceFetchFailed: status !== 408, + existenceFetchTimeout: status === 408, }), }, { applyImmediately: true } diff --git a/x-pack/plugins/lens/public/indexpattern_service/mocks.ts b/x-pack/plugins/lens/public/indexpattern_service/mocks.ts new file mode 100644 index 0000000000000..4429d735057b3 --- /dev/null +++ b/x-pack/plugins/lens/public/indexpattern_service/mocks.ts @@ -0,0 +1,217 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { DataViewsContract } from '@kbn/data-views-plugin/common'; +import { documentField } from '../indexpattern_datasource/document_field'; +import { + createMockedIndexPattern, + createMockedRestrictedIndexPattern, +} from '../indexpattern_datasource/mocks'; +import { IndexPattern } from '../types'; + +export function loadInitialDataViews() { + const indexPattern = createMockedIndexPattern(); + const restricted = createMockedRestrictedIndexPattern(); + return { + indexPatternRefs: [], + existingFields: {}, + indexPatterns: { + [indexPattern.id]: indexPattern, + [restricted.id]: restricted, + }, + isFirstExistenceFetch: false, + }; +} + +export const createMockStorage = (lastData?: Record) => { + return { + get: jest.fn().mockImplementation(() => lastData), + set: jest.fn(), + remove: jest.fn(), + clear: jest.fn(), + }; +}; + +const indexPattern1 = { + id: '1', + title: 'my-fake-index-pattern', + timeFieldName: 'timestamp', + hasRestrictions: false, + fields: [ + { + name: 'timestamp', + displayName: 'timestampLabel', + type: 'date', + aggregatable: true, + searchable: true, + }, + { + name: 'start_date', + displayName: 'start_date', + type: 'date', + aggregatable: true, + searchable: true, + }, + { + name: 'bytes', + displayName: 'bytes', + type: 'number', + aggregatable: true, + searchable: true, + }, + { + name: 'memory', + displayName: 'memory', + type: 'number', + aggregatable: true, + searchable: true, + }, + { + name: 'source', + displayName: 'source', + type: 'string', + aggregatable: true, + searchable: true, + esTypes: ['keyword'], + }, + { + name: 'unsupported', + displayName: 'unsupported', + type: 'geo', + aggregatable: true, + searchable: true, + }, + { + name: 'dest', + displayName: 'dest', + type: 'string', + aggregatable: true, + searchable: true, + esTypes: ['keyword'], + }, + { + name: 'geo.src', + displayName: 'geo.src', + type: 'string', + aggregatable: true, + searchable: true, + esTypes: ['keyword'], + }, + { + name: 'scripted', + displayName: 'Scripted', + type: 'string', + searchable: true, + aggregatable: true, + scripted: true, + lang: 'painless', + script: '1234', + }, + documentField, + ], +} as unknown as IndexPattern; + +const sampleIndexPatternsFromService = { + '1': createMockedIndexPattern(), + '2': createMockedRestrictedIndexPattern(), +}; + +const indexPattern2 = { + id: '2', + title: 'my-fake-restricted-pattern', + timeFieldName: 'timestamp', + hasRestrictions: true, + fieldFormatMap: { bytes: { id: 'bytes', params: { pattern: '0.0' } } }, + fields: [ + { + name: 'timestamp', + displayName: 'timestampLabel', + type: 'date', + aggregatable: true, + searchable: true, + aggregationRestrictions: { + date_histogram: { + agg: 'date_histogram', + fixed_interval: '1d', + delay: '7d', + time_zone: 'UTC', + }, + }, + }, + { + name: 'bytes', + displayName: 'bytes', + type: 'number', + aggregatable: true, + searchable: true, + aggregationRestrictions: { + // Ignored in the UI + histogram: { + agg: 'histogram', + interval: 1000, + }, + average: { + agg: 'avg', + }, + max: { + agg: 'max', + }, + min: { + agg: 'min', + }, + sum: { + agg: 'sum', + }, + }, + }, + { + name: 'source', + displayName: 'source', + type: 'string', + aggregatable: true, + searchable: true, + scripted: true, + lang: 'painless', + script: '1234', + aggregationRestrictions: { + terms: { + agg: 'terms', + }, + }, + }, + documentField, + ], +} as unknown as IndexPattern; + +export const sampleIndexPatterns = { + '1': indexPattern1, + '2': indexPattern2, +}; + +export function mockDataViewsService() { + return { + get: jest.fn(async (id: '1' | '2') => { + const result = { ...sampleIndexPatternsFromService[id], metaFields: [] }; + if (!result.fields) { + result.fields = []; + } + return result; + }), + getIdsWithTitle: jest.fn(async () => { + return [ + { + id: sampleIndexPatterns[1].id, + title: sampleIndexPatterns[1].title, + }, + { + id: sampleIndexPatterns[2].id, + title: sampleIndexPatterns[2].title, + }, + ]; + }), + } as unknown as Pick; +} diff --git a/x-pack/plugins/lens/public/data_views_service/service.ts b/x-pack/plugins/lens/public/indexpattern_service/service.ts similarity index 96% rename from x-pack/plugins/lens/public/data_views_service/service.ts rename to x-pack/plugins/lens/public/indexpattern_service/service.ts index 047013525266f..1a27be7e00fb8 100644 --- a/x-pack/plugins/lens/public/data_views_service/service.ts +++ b/x-pack/plugins/lens/public/indexpattern_service/service.ts @@ -11,7 +11,7 @@ import { i18n } from '@kbn/i18n'; import type { DateRange } from '../../common'; import type { IndexPattern, IndexPatternMap, IndexPatternRef } from '../types'; import { - loadIndexPattern, + ensureIndexPattern, loadIndexPatternRefs, loadIndexPatterns, syncExistingFields, @@ -103,7 +103,8 @@ export function createIndexPatternService({ ...args, }); }, - ensureIndexPattern: (args) => loadIndexPattern({ onError: onChangeError, dataViews, ...args }), + ensureIndexPattern: (args) => + ensureIndexPattern({ onError: onChangeError, dataViews, ...args }), refreshExistingFields: (args) => syncExistingFields({ updateIndexPatterns, diff --git a/x-pack/plugins/lens/public/mocks/data_views_service_mock.ts b/x-pack/plugins/lens/public/mocks/data_views_service_mock.ts index 4c2e77078dd28..41abd1a0e8f8d 100644 --- a/x-pack/plugins/lens/public/mocks/data_views_service_mock.ts +++ b/x-pack/plugins/lens/public/mocks/data_views_service_mock.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { IndexPatternServiceAPI } from '../data_views_service/service'; +import type { IndexPatternServiceAPI } from '../indexpattern_service/service'; export function createIndexPatternServiceMock(): IndexPatternServiceAPI { return { diff --git a/x-pack/plugins/lens/public/mocks/store_mocks.tsx b/x-pack/plugins/lens/public/mocks/store_mocks.tsx index 5fab4602b0483..e63a79693cadb 100644 --- a/x-pack/plugins/lens/public/mocks/store_mocks.tsx +++ b/x-pack/plugins/lens/public/mocks/store_mocks.tsx @@ -84,7 +84,7 @@ export function makeLensStore({ resolvedDateRange: getResolvedDateRange(data.query.timefilter.timefilter), ...preloadedState, }, - } as PreloadedState); + } as unknown as PreloadedState); const origDispatch = store.dispatch; store.dispatch = jest.fn(dispatch || origDispatch); diff --git a/x-pack/plugins/lens/public/state_management/lens_slice.ts b/x-pack/plugins/lens/public/state_management/lens_slice.ts index 7bee1b25e2ae6..9b32f91e7b139 100644 --- a/x-pack/plugins/lens/public/state_management/lens_slice.ts +++ b/x-pack/plugins/lens/public/state_management/lens_slice.ts @@ -15,13 +15,13 @@ import { getDatasourceLayers } from '../editor_frame_service/editor_frame'; import { TableInspectorAdapter } from '../editor_frame_service/types'; import type { VisualizeEditorContext, Suggestion, DatasourceMap } from '../types'; import { getInitialDatasourceId, getResolvedDateRange, getRemoveOperation } from '../utils'; -import { DataViewsState, LensAppState, LensStoreDeps, VisualizationState } from './types'; -import { Datasource, Visualization } from '../types'; +import type { DataViewsState, LensAppState, LensStoreDeps, VisualizationState } from './types'; +import type { Datasource, Visualization } from '../types'; import { generateId } from '../id_generator'; import type { LayerType } from '../../common/types'; import { getLayerType } from '../editor_frame_service/editor_frame/config_panel/add_layer'; import { getVisualizeFieldSuggestions } from '../editor_frame_service/editor_frame/suggestion_helpers'; -import { FramePublicAPI, LensEditContextMapping, LensEditEvent } from '../types'; +import type { FramePublicAPI, LensEditContextMapping, LensEditEvent } from '../types'; export const initialState: LensAppState = { persistedDoc: undefined, @@ -80,7 +80,7 @@ export const getPreloadedState = ({ activeDatasourceId: initialDatasourceId, datasourceStates, visualization: { - state: null as unknown, + state: null, activeId: Object.keys(visualizationMap)[0] || null, }, }; diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index 163174abe6125..d72061e1aba4b 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -45,7 +45,7 @@ import { } from './datatable_visualization/components/constants'; import type { LensInspector } from './lens_inspector_service'; import { DataViewsState } from './state_management/types'; -import { IndexPatternServiceAPI } from './data_views_service/service'; +import { IndexPatternServiceAPI } from './indexpattern_service/service'; export interface IndexPatternRef { id: string; diff --git a/x-pack/plugins/lens/public/utils.ts b/x-pack/plugins/lens/public/utils.ts index 2c848758b0975..6ebf1cb7d3f83 100644 --- a/x-pack/plugins/lens/public/utils.ts +++ b/x-pack/plugins/lens/public/utils.ts @@ -22,7 +22,7 @@ import type { IndexPatternRef, } from './types'; import type { DatasourceStates, VisualizationState } from './state_management'; -import { IndexPatternServiceAPI } from './data_views_service/service'; +import { IndexPatternServiceAPI } from './indexpattern_service/service'; export function getVisualizeGeoFieldMessage(fieldType: string) { return i18n.translate('xpack.lens.visualizeGeoFieldMessage', { diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.test.ts b/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.test.ts index 1e02f929e90ce..e259e1a675a47 100644 --- a/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.test.ts +++ b/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.test.ts @@ -8,9 +8,15 @@ import { FramePublicAPI } from '../../types'; import { getStaticDate } from './helpers'; -const frame = { +const frame: FramePublicAPI = { datasourceLayers: {}, dateRange: { fromDate: '2022-02-01T00:00:00.000Z', toDate: '2022-04-20T00:00:00.000Z' }, + dataViews: { + indexPatterns: {}, + indexPatternRefs: [], + existingFields: {}, + isFirstExistenceFetch: true, + }, }; describe('annotations helpers', () => { From ce7bddd233d76fb137a086789a6a9c6fdaf3d3b4 Mon Sep 17 00:00:00 2001 From: dej611 Date: Fri, 29 Jul 2022 20:00:38 +0200 Subject: [PATCH 11/27] :white_check_mark: Fix more tests --- .../config_panel/config_panel.test.tsx | 17 ++++--- .../editor_frame/data_panel_wrapper.tsx | 12 +++-- .../editor_frame/editor_frame.test.tsx | 14 +++++- .../editor_frame/suggestion_helpers.test.ts | 33 +++++++++----- .../indexpattern_datasource/loader.test.ts | 44 ++++++++----------- .../indexpattern_datasource/utils.test.tsx | 12 ++--- .../indexpattern_service/loader.test.ts | 6 +-- .../public/indexpattern_service/loader.ts | 4 +- .../lens/public/indexpattern_service/mocks.ts | 4 ++ 9 files changed, 92 insertions(+), 54 deletions(-) diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.test.tsx index ef461b7d9d302..5c1b5c4d9d8a6 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.test.tsx @@ -356,11 +356,16 @@ describe('ConfigPanel', () => { await clickToAddLayer(instance); expect(lensStore.dispatch).toHaveBeenCalledTimes(1); - expect(datasourceMap.testDatasource.initializeDimension).toHaveBeenCalledWith({}, 'newId', { - columnId: 'myColumn', - groupId: 'testGroup', - staticValue: 100, - }); + expect(datasourceMap.testDatasource.initializeDimension).toHaveBeenCalledWith( + {}, + 'newId', + frame.dataViews.indexPatterns, + { + columnId: 'myColumn', + groupId: 'testGroup', + staticValue: 100, + } + ); }); it('should add an initial dimension value when clicking on the empty dimension button', async () => { @@ -390,6 +395,7 @@ describe('ConfigPanel', () => { expect(datasourceMap.testDatasource.initializeDimension).toHaveBeenCalledWith( 'state', 'first', + frame.dataViews.indexPatterns, { groupId: 'a', columnId: 'newId', @@ -447,6 +453,7 @@ describe('ConfigPanel', () => { a: expect.anything(), }, dateRange: expect.anything(), + dataViews: expect.anything(), }, groupId: 'a', layerId: 'newId', diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/data_panel_wrapper.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/data_panel_wrapper.tsx index 4cabda4cc62df..553a61ba2c1c5 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/data_panel_wrapper.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/data_panel_wrapper.tsx @@ -31,6 +31,7 @@ import { import { initializeSources } from './state_helpers'; import type { IndexPatternServiceAPI } from '../../indexpattern_service/service'; import { changeIndexPattern } from '../../state_management/lens_slice'; +import { getInitialDataViewsObject } from '../../utils'; interface DataPanelWrapperProps { datasourceMap: DatasourceMap; @@ -83,8 +84,8 @@ export const DataPanelWrapper = memo((props: DataPanelWrapperProps) => { { isFullEditor: true, } - ).then((result) => { - const newDatasourceStates = Object.entries(result).reduce( + ).then(({ states, indexPatterns, indexPatternRefs }) => { + const newDatasourceStates = Object.entries(states).reduce( (state, [datasourceId, datasourceState]) => ({ ...state, [datasourceId]: { @@ -94,7 +95,12 @@ export const DataPanelWrapper = memo((props: DataPanelWrapperProps) => { }), {} ); - dispatchLens(setState({ datasourceStates: newDatasourceStates })); + dispatchLens( + setState({ + datasourceStates: newDatasourceStates, + dataViews: getInitialDataViewsObject(indexPatterns, indexPatternRefs), + }) + ); }); } }, [ diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx index 9c5b7740cacff..8d4cbade98e41 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx @@ -65,6 +65,17 @@ function generateSuggestion(state = {}): DatasourceSuggestion { }; } +function wrapDataViewsContract() { + const dataViewsContract = dataViewPluginMocks.createStartContract(); + return { + ...dataViewsContract, + getIdsWithTitle: jest.fn(async () => [ + { id: '1', title: 'IndexPatternTitle' }, + { id: '2', title: 'OtherIndexPatternTitle' }, + ]), + }; +} + function getDefaultProps() { const defaultProps = { store: { @@ -82,7 +93,7 @@ function getDefaultProps() { data: mockDataPlugin(), expressions: expressionsPluginMock.createStartContract(), charts: chartPluginMock.createStartContract(), - dataViews: dataViewPluginMocks.createStartContract(), + dataViews: wrapDataViewsContract(), }, palettes: chartPluginMock.createPaletteRegistry(), lensInspector: getLensInspectorService(inspectorPluginMock.createStartContract()), @@ -418,6 +429,7 @@ describe('editor_frame', () => { expect(mockDatasource.getPublicAPI).toHaveBeenCalledWith({ state: datasourceState, layerId: 'first', + indexPatterns: {}, }); }); }); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.test.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.test.ts index b739091ddff3b..5cc62012a5094 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.test.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.test.ts @@ -148,7 +148,8 @@ describe('suggestion helpers', () => { expect(datasourceMap.mock.getDatasourceSuggestionsForField).toHaveBeenCalledWith( datasourceStates.mock.state, droppedField, - expect.any(Function) + expect.any(Function), + dataViews.indexPatterns ); }); @@ -184,12 +185,14 @@ describe('suggestion helpers', () => { expect(multiDatasourceMap.mock.getDatasourceSuggestionsForField).toHaveBeenCalledWith( multiDatasourceStates.mock.state, droppedField, - expect.any(Function) + expect.any(Function), + dataViews.indexPatterns ); expect(multiDatasourceMap.mock2.getDatasourceSuggestionsForField).toHaveBeenCalledWith( multiDatasourceStates.mock2.state, droppedField, - expect.any(Function) + expect.any(Function), + dataViews.indexPatterns ); expect(multiDatasourceMap.mock3.getDatasourceSuggestionsForField).not.toHaveBeenCalled(); }); @@ -218,7 +221,8 @@ describe('suggestion helpers', () => { expect(datasourceMap.mock.getDatasourceSuggestionsForVisualizeField).toHaveBeenCalledWith( datasourceStates.mock.state, '1', - 'test' + 'test', + dataViews.indexPatterns ); }); @@ -258,12 +262,14 @@ describe('suggestion helpers', () => { expect(multiDatasourceMap.mock.getDatasourceSuggestionsForVisualizeField).toHaveBeenCalledWith( multiDatasourceStates.mock.state, '1', - 'test' + 'test', + dataViews.indexPatterns ); expect(multiDatasourceMap.mock2.getDatasourceSuggestionsForVisualizeField).toHaveBeenCalledWith( multiDatasourceStates.mock2.state, '1', - 'test' + 'test', + dataViews.indexPatterns ); expect( multiDatasourceMap.mock3.getDatasourceSuggestionsForVisualizeField @@ -338,7 +344,8 @@ describe('suggestion helpers', () => { }); expect(datasourceMap.mock.getDatasourceSuggestionsForVisualizeCharts).toHaveBeenCalledWith( datasourceStates.mock.state, - triggerContext.layers + triggerContext.layers, + dataViews.indexPatterns ); }); @@ -421,12 +428,17 @@ describe('suggestion helpers', () => { }); expect(multiDatasourceMap.mock.getDatasourceSuggestionsForVisualizeCharts).toHaveBeenCalledWith( datasourceStates.mock.state, - triggerContext.layers + triggerContext.layers, + dataViews.indexPatterns ); expect( multiDatasourceMap.mock2.getDatasourceSuggestionsForVisualizeCharts - ).toHaveBeenCalledWith(multiDatasourceStates.mock2.state, triggerContext.layers); + ).toHaveBeenCalledWith( + multiDatasourceStates.mock2.state, + triggerContext.layers, + dataViews.indexPatterns + ); expect( multiDatasourceMap.mock3.getDatasourceSuggestionsForVisualizeCharts ).not.toHaveBeenCalled(); @@ -753,7 +765,8 @@ describe('suggestion helpers', () => { label: 'myfieldLabel', }, }, - expect.any(Function) + expect.any(Function), + dataViews.indexPatterns ); }); 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 c31f9167e2b24..3a85a317eec0f 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/loader.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/loader.test.ts @@ -38,9 +38,9 @@ const indexPatternRefs = [ describe('loader', () => { describe('loadInitialState', () => { - it('should load a default state', async () => { + it('should load a default state', () => { const storage = createMockStorage(); - const state = await loadInitialState({ + const state = loadInitialState({ indexPatterns: sampleIndexPatterns, indexPatternRefs, storage, @@ -55,9 +55,9 @@ describe('loader', () => { }); }); - it('should load a default state without loading the indexPatterns when embedded', async () => { + it('should load a default state without loading the indexPatterns when embedded', () => { const storage = createMockStorage(); - const state = await loadInitialState({ + const state = loadInitialState({ storage, indexPatterns: {}, indexPatternRefs: [], @@ -71,9 +71,9 @@ describe('loader', () => { expect(storage.set).not.toHaveBeenCalled(); }); - it('should load a default state when lastUsedIndexPatternId is not found in indexPatternRefs', async () => { + it('should load a default state when lastUsedIndexPatternId is not found in indexPatternRefs', () => { const storage = createMockStorage({ indexPatternId: 'c' }); - const state = await loadInitialState({ + const state = loadInitialState({ storage, indexPatternRefs, indexPatterns: sampleIndexPatterns, @@ -88,8 +88,8 @@ describe('loader', () => { }); }); - it('should load lastUsedIndexPatternId if in localStorage', async () => { - const state = await loadInitialState({ + it('should load lastUsedIndexPatternId if in localStorage', () => { + const state = loadInitialState({ storage: createMockStorage({ indexPatternId: '2' }), indexPatternRefs, indexPatterns: sampleIndexPatterns, @@ -101,9 +101,9 @@ describe('loader', () => { }); }); - it('should use the default index pattern id, if provided', async () => { + it('should use the default index pattern id, if provided', () => { const storage = createMockStorage(); - const state = await loadInitialState({ + const state = loadInitialState({ defaultIndexPatternId: '2', storage, indexPatternRefs, @@ -119,9 +119,9 @@ describe('loader', () => { }); }); - it('should use the indexPatternId of the visualize trigger field, if provided', async () => { + it('should use the indexPatternId of the visualize trigger field, if provided', () => { const storage = createMockStorage(); - const state = await loadInitialState({ + const state = loadInitialState({ storage, initialContext: { indexPatternId: '1', @@ -140,9 +140,9 @@ describe('loader', () => { }); }); - it('should use the indexPatternId of the visualize trigger chart context, if provided', async () => { + it('should use the indexPatternId of the visualize trigger chart context, if provided', () => { const storage = createMockStorage(); - const state = await loadInitialState({ + const state = loadInitialState({ storage, initialContext: { layers: [ @@ -185,7 +185,7 @@ describe('loader', () => { }); }); - it('should initialize all the embeddable references without local storage', async () => { + it('should initialize all the embeddable references without local storage', () => { const savedState: IndexPatternPersistedState = { layers: { layerb: { @@ -213,7 +213,7 @@ describe('loader', () => { }, }; const storage = createMockStorage({}); - const state = await loadInitialState({ + const state = loadInitialState({ persistedState: savedState, references: [ { name: 'indexpattern-datasource-layer-layerb', id: '2', type: 'index-pattern' }, @@ -233,7 +233,7 @@ describe('loader', () => { expect(storage.set).not.toHaveBeenCalled(); }); - it('should initialize from saved state', async () => { + it('should initialize from saved state', () => { const savedState: IndexPatternPersistedState = { layers: { layerb: { @@ -261,7 +261,7 @@ describe('loader', () => { }, }; const storage = createMockStorage({ indexPatternId: '1' }); - const state = await loadInitialState({ + const state = loadInitialState({ persistedState: savedState, references: [ { name: 'indexpattern-datasource-layer-layerb', id: '2', type: 'index-pattern' }, @@ -277,13 +277,6 @@ describe('loader', () => { expect(state).toMatchObject({ currentIndexPatternId: '2', - indexPatternRefs: [ - { id: '1', title: sampleIndexPatterns['1'].title }, - { id: '2', title: sampleIndexPatterns['2'].title }, - ], - indexPatterns: { - '2': sampleIndexPatterns['2'], - }, layers: { layerb: { ...savedState.layers.layerb, indexPatternId: '2' } }, }); @@ -489,6 +482,7 @@ describe('loader', () => { layerId: 'l1', storage, indexPatterns: sampleIndexPatterns, + replaceIfPossible: true, }); expect(newState).toMatchObject({ diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/utils.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/utils.test.tsx index 79b87628bd213..3bfcd51fb3907 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/utils.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/utils.test.tsx @@ -41,11 +41,6 @@ describe('indexpattern_datasource utils', () => { }, }, }, - indexPatterns: { - one: { - getFieldByName: (x: string) => ({ name: x, displayName: x }), - }, - }, } as unknown as IndexPatternPrivateState; framePublicAPI = { activeData: { @@ -62,6 +57,13 @@ describe('indexpattern_datasource utils', () => { ], }, }, + dataViews: { + indexPatterns: { + one: { + getFieldByName: (x: string) => ({ name: x, displayName: x }), + }, + }, + }, } as unknown as FramePublicAPI; docLinks = { diff --git a/x-pack/plugins/lens/public/indexpattern_service/loader.test.ts b/x-pack/plugins/lens/public/indexpattern_service/loader.test.ts index e43b79c82cfbf..610236ea12d05 100644 --- a/x-pack/plugins/lens/public/indexpattern_service/loader.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_service/loader.test.ts @@ -52,7 +52,7 @@ describe('loader', () => { dataViews: mockDataViewsService(), }); - expect(cache).toMatchObject(sampleIndexPatterns); + expect(cache).toEqual(sampleIndexPatterns); }); it('should allow scripted, but not full text fields', async () => { @@ -62,7 +62,7 @@ describe('loader', () => { dataViews: mockDataViewsService(), }); - expect(cache).toMatchObject(sampleIndexPatterns); + expect(cache).toEqual(sampleIndexPatterns); }); it('should apply field restrictions from typeMeta', async () => { @@ -259,7 +259,7 @@ describe('loader', () => { dataViews: mockDataViewsService(), onError, }); - expect(cache).toMatchObject({ 2: sampleIndexPatterns['2'] }); + expect(cache).toEqual({ 2: expect.anything() }); expect(onError).not.toHaveBeenCalled(); }); }); diff --git a/x-pack/plugins/lens/public/indexpattern_service/loader.ts b/x-pack/plugins/lens/public/indexpattern_service/loader.ts index a45c6ecf5cda7..45dd7eb9db8f3 100644 --- a/x-pack/plugins/lens/public/indexpattern_service/loader.ts +++ b/x-pack/plugins/lens/public/indexpattern_service/loader.ts @@ -96,9 +96,9 @@ export function convertDataViewIntoLensIndexPattern( } export async function loadIndexPatternRefs( - indexPatternsService: MinimalDataViewsContract + dataViews: MinimalDataViewsContract ): Promise { - const indexPatterns = await indexPatternsService.getIdsWithTitle(); + const indexPatterns = await dataViews.getIdsWithTitle(); return indexPatterns.sort((a, b) => { return a.title.localeCompare(b.title); diff --git a/x-pack/plugins/lens/public/indexpattern_service/mocks.ts b/x-pack/plugins/lens/public/indexpattern_service/mocks.ts index 4429d735057b3..a96236e781984 100644 --- a/x-pack/plugins/lens/public/indexpattern_service/mocks.ts +++ b/x-pack/plugins/lens/public/indexpattern_service/mocks.ts @@ -12,6 +12,7 @@ import { createMockedRestrictedIndexPattern, } from '../indexpattern_datasource/mocks'; import { IndexPattern } from '../types'; +import { getFieldByNameFactory } from './loader'; export function loadInitialDataViews() { const indexPattern = createMockedIndexPattern(); @@ -113,7 +114,9 @@ const indexPattern1 = { }, documentField, ], + getFieldByName: jest.fn(), } as unknown as IndexPattern; +indexPattern1.getFieldByName = getFieldByNameFactory(indexPattern1.fields); const sampleIndexPatternsFromService = { '1': createMockedIndexPattern(), @@ -186,6 +189,7 @@ const indexPattern2 = { documentField, ], } as unknown as IndexPattern; +indexPattern2.getFieldByName = getFieldByNameFactory(indexPattern2.fields); export const sampleIndexPatterns = { '1': indexPattern1, From 80647befcf7bf4e821e4e8692291df24229e2b02 Mon Sep 17 00:00:00 2001 From: dej611 Date: Wed, 3 Aug 2022 11:59:59 +0200 Subject: [PATCH 12/27] :white_check_mark: Fix with new dataViews structure --- .../indexpattern.test.ts | 55 ++++++++----------- 1 file changed, 24 insertions(+), 31 deletions(-) diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts index 218d68a45de0d..e91aa1b286bec 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts @@ -38,6 +38,7 @@ import { import { createMockedFullReference } from './operations/mocks'; import { cloneDeep } from 'lodash'; import { DatatableColumn } from '@kbn/expressions-plugin/common'; +import { createMockFramePublicAPI } from '../mocks'; jest.mock('./loader'); jest.mock('../id_generator'); @@ -166,18 +167,10 @@ const expectedIndexPatterns = { }, }; -type DataViewBaseState = Omit< - IndexPatternPrivateState, - 'indexPatternRefs' | 'indexPatterns' | 'existingFields' | 'isFirstExistenceFetch' ->; - const indexPatterns = expectedIndexPatterns; describe('IndexPattern Data Source', () => { - let baseState: Omit< - IndexPatternPrivateState, - 'indexPatternRefs' | 'indexPatterns' | 'existingFields' | 'isFirstExistenceFetch' - >; + let baseState: IndexPatternPrivateState; let indexPatternDatasource: Datasource; beforeEach(() => { @@ -304,7 +297,7 @@ describe('IndexPattern Data Source', () => { }); it('should create a table when there is a formula without aggs', async () => { - const queryBaseState: DataViewBaseState = { + const queryBaseState: IndexPatternPrivateState = { currentIndexPatternId: '1', layers: { first: { @@ -341,7 +334,7 @@ describe('IndexPattern Data Source', () => { }); it('should generate an expression for an aggregated query', async () => { - const queryBaseState: DataViewBaseState = { + const queryBaseState: IndexPatternPrivateState = { currentIndexPatternId: '1', layers: { first: { @@ -495,7 +488,7 @@ describe('IndexPattern Data Source', () => { }); it('should put all time fields used in date_histograms to the esaggs timeFields parameter if not ignoring global time range', async () => { - const queryBaseState: DataViewBaseState = { + const queryBaseState: IndexPatternPrivateState = { currentIndexPatternId: '1', layers: { first: { @@ -554,7 +547,7 @@ describe('IndexPattern Data Source', () => { }); it('should pass time shift parameter to metric agg functions', async () => { - const queryBaseState: DataViewBaseState = { + const queryBaseState: IndexPatternPrivateState = { currentIndexPatternId: '1', layers: { first: { @@ -593,7 +586,7 @@ describe('IndexPattern Data Source', () => { }); it('should wrap filtered metrics in filtered metric aggregation', async () => { - const queryBaseState: DataViewBaseState = { + const queryBaseState: IndexPatternPrivateState = { currentIndexPatternId: '1', layers: { first: { @@ -725,7 +718,7 @@ describe('IndexPattern Data Source', () => { }); it('should add time_scale and format function if time scale is set and supported', async () => { - const queryBaseState: DataViewBaseState = { + const queryBaseState: IndexPatternPrivateState = { currentIndexPatternId: '1', layers: { first: { @@ -810,7 +803,7 @@ describe('IndexPattern Data Source', () => { }); it('should put column formatters after calculated columns', async () => { - const queryBaseState: DataViewBaseState = { + const queryBaseState: IndexPatternPrivateState = { currentIndexPatternId: '1', layers: { first: { @@ -861,7 +854,7 @@ describe('IndexPattern Data Source', () => { }); it('should rename the output from esaggs when using flat query', () => { - const queryBaseState: DataViewBaseState = { + const queryBaseState: IndexPatternPrivateState = { currentIndexPatternId: '1', layers: { first: { @@ -916,7 +909,7 @@ describe('IndexPattern Data Source', () => { }); it('should not put date fields used outside date_histograms to the esaggs timeFields parameter', async () => { - const queryBaseState: DataViewBaseState = { + const queryBaseState: IndexPatternPrivateState = { currentIndexPatternId: '1', layers: { first: { @@ -955,7 +948,7 @@ describe('IndexPattern Data Source', () => { }); it('should call optimizeEsAggs once per operation for which it is available', () => { - const queryBaseState: DataViewBaseState = { + const queryBaseState: IndexPatternPrivateState = { currentIndexPatternId: '1', layers: { first: { @@ -1013,7 +1006,7 @@ describe('IndexPattern Data Source', () => { }); it('should update anticipated esAggs column IDs based on the order of the optimized agg expression builders', () => { - const queryBaseState: DataViewBaseState = { + const queryBaseState: IndexPatternPrivateState = { currentIndexPatternId: '1', layers: { first: { @@ -1102,7 +1095,7 @@ describe('IndexPattern Data Source', () => { }); it('should collect expression references and append them', async () => { - const queryBaseState: DataViewBaseState = { + const queryBaseState: IndexPatternPrivateState = { currentIndexPatternId: '1', layers: { first: { @@ -1139,7 +1132,7 @@ describe('IndexPattern Data Source', () => { }); it('should keep correct column mapping keys with reference columns present', async () => { - const queryBaseState: DataViewBaseState = { + const queryBaseState: IndexPatternPrivateState = { currentIndexPatternId: '1', layers: { first: { @@ -1182,7 +1175,7 @@ describe('IndexPattern Data Source', () => { it('should topologically sort references', () => { // This is a real example of count() + count() - const queryBaseState: DataViewBaseState = { + const queryBaseState: IndexPatternPrivateState = { currentIndexPatternId: '1', layers: { first: { @@ -1271,9 +1264,6 @@ describe('IndexPattern Data Source', () => { describe('#insertLayer', () => { it('should insert an empty layer into the previous state', () => { const state = { - indexPatternRefs: [], - existingFields: {}, - indexPatterns: expectedIndexPatterns, layers: { first: { indexPatternId: '1', @@ -1287,7 +1277,6 @@ describe('IndexPattern Data Source', () => { }, }, currentIndexPatternId: '1', - isFirstExistenceFetch: false, }; expect(indexPatternDatasource.insertLayer(state, 'newLayer')).toEqual({ ...state, @@ -1306,10 +1295,6 @@ describe('IndexPattern Data Source', () => { describe('#removeLayer', () => { it('should remove a layer', () => { const state = { - indexPatternRefs: [], - existingFields: {}, - isFirstExistenceFetch: false, - indexPatterns: expectedIndexPatterns, layers: { first: { indexPatternId: '1', @@ -2539,6 +2524,14 @@ describe('IndexPattern Data Source', () => { ], }, }, + dataViews: { + ...createMockFramePublicAPI().dataViews, + indexPatterns: expectedIndexPatterns, + indexPatternRefs: Object.values(expectedIndexPatterns).map(({ id, title }) => ({ + id, + title, + })), + }, } as unknown as FramePublicAPI; }); From 63bf020d331aa200000fa31c90fc41dd2dba8d53 Mon Sep 17 00:00:00 2001 From: dej611 Date: Wed, 3 Aug 2022 17:26:49 +0200 Subject: [PATCH 13/27] :white_check_mark: Fix more test mocks --- .../operations/definitions/formula/formula_public_api.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula_public_api.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula_public_api.test.ts index 0582d5fcf7693..3286696308d51 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula_public_api.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula_public_api.test.ts @@ -16,7 +16,7 @@ jest.mock('./parse', () => ({ insertOrReplaceFormulaColumn: jest.fn().mockReturnValue({}), })); -jest.mock('../../../loader', () => ({ +jest.mock('../../../../indexpattern_service/loader', () => ({ convertDataViewIntoLensIndexPattern: jest.fn((v) => v), })); From 3a7f9fbc6a4a0089c78ca69d9bd597877e221a1d Mon Sep 17 00:00:00 2001 From: dej611 Date: Wed, 3 Aug 2022 20:05:32 +0200 Subject: [PATCH 14/27] :white_check_mark: More tests fixed --- .../datapanel.test.tsx | 94 ++++++++----------- .../indexpattern_datasource/datapanel.tsx | 10 +- .../dimension_panel/dimension_panel.test.tsx | 11 +-- .../dimension_panel/field_input.test.tsx | 26 ++--- .../definitions/terms/terms.test.tsx | 2 +- 5 files changed, 63 insertions(+), 80 deletions(-) diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx index 0578c3ac7a47a..a9d68644df689 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx @@ -10,7 +10,7 @@ import ReactDOM from 'react-dom'; import { createMockedDragDropContext } from './mocks'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; -import { InnerIndexPatternDataPanel, IndexPatternDataPanel, MemoizedDataPanel } from './datapanel'; +import { InnerIndexPatternDataPanel, IndexPatternDataPanel } from './datapanel'; import { FieldList } from './field_list'; import { FieldItem } from './field_item'; import { NoFieldsCallout } from './no_fields_callout'; @@ -30,6 +30,7 @@ import { DOCUMENT_FIELD_NAME } from '../../common'; import { createIndexPatternServiceMock } from '../mocks/data_views_service_mock'; import { createMockFramePublicAPI } from '../mocks'; import { DataViewsState } from '../state_management'; +import { ExistingFieldsMap, FramePublicAPI, IndexPattern } from '../types'; const fieldsOne = [ { @@ -155,6 +156,18 @@ const fieldsThree = [ documentField, ]; +function getExistingFields(indexPatterns: Record) { + const existingFields: ExistingFieldsMap = {}; + for (const { title, fields } of Object.values(indexPatterns)) { + const fieldsMap: Record = {}; + for (const { displayName, name } of fields) { + fieldsMap[displayName ?? name] = true; + } + existingFields[title] = fieldsMap; + } + return existingFields; +} + const initialState: IndexPatternPrivateState = { currentIndexPatternId: '1', layers: { @@ -248,7 +261,7 @@ function getFrameAPIMock({ indexPatterns, existingFields, ...rest }: Partial { dataViews: dataViewPluginMocks.createStartContract(), fieldFormats: fieldFormatsServiceMock.createStartContract(), indexPatternFieldEditor: indexPatternFieldEditorPluginMock.createStartContract(), - onChangeIndexPattern: jest.fn(), onIndexPatternRefresh: jest.fn(), dragDropContext: createMockedDragDropContext(), currentIndexPatternId: '1', @@ -294,31 +306,6 @@ describe('IndexPattern Data Panel', () => { }; }); - it('should call change index pattern callback', async () => { - const setStateSpy = jest.fn(); - const state = { - ...initialState, - layers: { first: { indexPatternId: '1', columnOrder: [], columns: {} } }, - }; - const changeIndexPattern = jest.fn(); - const wrapper = shallowWithIntl( - - ); - - wrapper.find(MemoizedDataPanel).prop('onChangeIndexPattern')!('2'); - - expect(changeIndexPattern).toHaveBeenCalledWith('2', state, setStateSpy); - }); - it('should render a warning if there are no index patterns', () => { const wrapper = shallowWithIntl( { ...createMockedDragDropContext(), dragging: { id: '1', humanData: { label: 'Label' } }, }} - changeIndexPattern={jest.fn()} frame={createMockFramePublicAPI()} /> ); @@ -341,7 +327,6 @@ describe('IndexPattern Data Panel', () => { describe('loading existence data', () => { function testProps() { - const setState = jest.fn(); core.http.post.mockImplementation(async (path) => { const parts = (path as unknown as string).split('/'); const indexPatternTitle = parts[parts.length - 1]; @@ -354,36 +339,39 @@ describe('IndexPattern Data Panel', () => { }); return { ...defaultProps, - changeIndexPattern: jest.fn(), - setState, + setState: jest.fn(), dragDropContext: { ...createMockedDragDropContext(), dragging: { id: '1', humanData: { label: 'Label' } }, }, dateRange: { fromDate: '2019-01-01', toDate: '2020-01-01' }, - state: { - indexPatternRefs: [], - existingFields: {}, - isFirstExistenceFetch: false, - currentIndexPatternId: 'a', - indexPatterns: { - a: { - id: 'a', - title: 'aaa', - timeFieldName: 'atime', - fields: [], - getFieldByName: getFieldByNameFactory([]), - hasRestrictions: false, - }, - b: { - id: 'b', - title: 'bbb', - timeFieldName: 'btime', - fields: [], - getFieldByName: getFieldByNameFactory([]), - hasRestrictions: false, + frame: { + dataViews: { + indexPatternRefs: [], + existingFields: {}, + isFirstExistenceFetch: false, + indexPatterns: { + a: { + id: 'a', + title: 'aaa', + timeFieldName: 'atime', + fields: [], + getFieldByName: getFieldByNameFactory([]), + hasRestrictions: false, + }, + b: { + id: 'b', + title: 'bbb', + timeFieldName: 'btime', + fields: [], + getFieldByName: getFieldByNameFactory([]), + hasRestrictions: false, + }, }, }, + } as unknown as FramePublicAPI, + state: { + currentIndexPatternId: 'a', layers: { 1: { indexPatternId: 'a', diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx index 8bac417fb53ae..5a5b6c5f79397 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx @@ -56,7 +56,6 @@ export type Props = Omit< data: DataPublicPluginStart; dataViews: DataViewsPublicPluginStart; fieldFormats: FieldFormatsStart; - changeIndexPattern: (id: string) => void; charts: ChartsPluginSetup; core: CoreStart; indexPatternFieldEditor: IndexPatternFieldEditorStart; @@ -129,7 +128,6 @@ export function IndexPatternDataPanel({ query, filters, dateRange, - changeIndexPattern, charts, indexPatternFieldEditor, showNoDataPopover, @@ -221,7 +219,6 @@ export function IndexPatternDataPanel({ fieldFormats={fieldFormats} charts={charts} indexPatternFieldEditor={indexPatternFieldEditor} - onChangeIndexPattern={changeIndexPattern} dropOntoWorkspace={dropOntoWorkspace} hasSuggestionForField={hasSuggestionForField} uiActions={uiActions} @@ -268,7 +265,6 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ dateRange, filters, dragDropContext, - onChangeIndexPattern, core, data, dataViews, @@ -281,14 +277,16 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ indexPatternService, frame, onIndexPatternRefresh, -}: Omit & { +}: Omit< + DatasourceDataPanelProps, + 'state' | 'setState' | 'showNoDataPopover' | 'core' | 'onChangeIndexPattern' +> & { data: DataPublicPluginStart; dataViews: DataViewsPublicPluginStart; fieldFormats: FieldFormatsStart; core: CoreStart; currentIndexPatternId: string; dragDropContext: DragContextState; - onChangeIndexPattern: (newId: string) => void; charts: ChartsPluginSetup; frame: FramePublicAPI; indexPatternFieldEditor: IndexPatternFieldEditorStart; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx index a23f35f4cd53a..d42da13729665 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx @@ -333,13 +333,10 @@ describe('IndexPatternDimensionEditorPanel', () => { it('should hide fields that have no data', () => { const props = { ...defaultProps, - state: { - ...defaultProps.state, - existingFields: { - 'my-fake-index-pattern': { - timestamp: true, - source: true, - }, + existingFields: { + 'my-fake-index-pattern': { + timestamp: true, + source: true, }, }, }; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/field_input.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/field_input.test.tsx index 8cdbc396cd9ab..b7ab501f34d16 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/field_input.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/field_input.test.tsx @@ -130,13 +130,13 @@ function getDefaultOperationSupportMatrix( }); } -function getExistingFields(layer: IndexPatternLayer) { +function getExistingFields() { const fields: Record = {}; for (const field of defaultProps.indexPattern.fields) { fields[field.name] = true; } return { - [layer.indexPatternId]: fields, + [defaultProps.indexPattern.title]: fields, }; } @@ -144,7 +144,7 @@ describe('FieldInput', () => { it('should render a field select box', () => { const updateLayerSpy = jest.fn(); const layer = getLayer(); - const existingFields = getExistingFields(layer); + const existingFields = getExistingFields(); const operationSupportMatrix = getDefaultOperationSupportMatrix(layer, 'col1', existingFields); const instance = mount( { it('should render an error message when incomplete operation is on', () => { const updateLayerSpy = jest.fn(); const layer = getLayer(); - const existingFields = getExistingFields(layer); + const existingFields = getExistingFields(); const operationSupportMatrix = getDefaultOperationSupportMatrix(layer, 'col1', existingFields); const instance = mount( { (_, col: ReferenceBasedIndexPatternColumn) => { const updateLayerSpy = jest.fn(); const layer = getLayer(col); - const existingFields = getExistingFields(layer); + const existingFields = getExistingFields(); const operationSupportMatrix = getDefaultOperationSupportMatrix( layer, 'col1', @@ -234,7 +234,7 @@ describe('FieldInput', () => { (_, col: ReferenceBasedIndexPatternColumn) => { const updateLayerSpy = jest.fn(); const layer = getLayer(col); - const existingFields = getExistingFields(layer); + const existingFields = getExistingFields(); const operationSupportMatrix = getDefaultOperationSupportMatrix( layer, 'col1', @@ -269,7 +269,7 @@ describe('FieldInput', () => { it('should render an error message for invalid fields', () => { const updateLayerSpy = jest.fn(); const layer = getLayer(); - const existingFields = getExistingFields(layer); + const existingFields = getExistingFields(); const operationSupportMatrix = getDefaultOperationSupportMatrix(layer, 'col1', existingFields); const instance = mount( { it('should render a help message when passed and no errors are found', () => { const updateLayerSpy = jest.fn(); const layer = getLayer(); - const existingFields = getExistingFields(layer); + const existingFields = getExistingFields(); const operationSupportMatrix = getDefaultOperationSupportMatrix(layer, 'col1', existingFields); const instance = mount( { it('should prioritize errors over help messages', () => { const updateLayerSpy = jest.fn(); const layer = getLayer(); - const existingFields = getExistingFields(layer); + const existingFields = getExistingFields(); const operationSupportMatrix = getDefaultOperationSupportMatrix(layer, 'col1', existingFields); const instance = mount( { it('should update the layer on field selection', () => { const updateLayerSpy = jest.fn(); const layer = getLayer(); - const existingFields = getExistingFields(layer); + const existingFields = getExistingFields(); const operationSupportMatrix = getDefaultOperationSupportMatrix(layer, 'col1', existingFields); const instance = mount( { it('should not trigger when the same selected field is selected again', () => { const updateLayerSpy = jest.fn(); const layer = getLayer(); - const existingFields = getExistingFields(layer); + const existingFields = getExistingFields(); const operationSupportMatrix = getDefaultOperationSupportMatrix(layer, 'col1', existingFields); const instance = mount( { it('should prioritize incomplete fields over selected column field to display', () => { const updateLayerSpy = jest.fn(); const layer = getLayer(); - const existingFields = getExistingFields(layer); + const existingFields = getExistingFields(); const operationSupportMatrix = getDefaultOperationSupportMatrix(layer, 'col1', existingFields); const instance = mount( { const updateLayerSpy = jest.fn(); const onDeleteColumn = jest.fn(); const layer = getLayer(); - const existingFields = getExistingFields(layer); + const existingFields = getExistingFields(); const operationSupportMatrix = getDefaultOperationSupportMatrix(layer, 'col1', existingFields); const instance = mount( { fields[field.name] = true; } return { - [layer.indexPatternId]: fields, + [defaultProps.indexPattern.title]: fields, }; } From 2fc1f0f13e327317dd2f6eb184833eb763fb7347 Mon Sep 17 00:00:00 2001 From: dej611 Date: Thu, 4 Aug 2022 09:55:18 +0200 Subject: [PATCH 15/27] :fire: Removed unused prop --- .../lens/public/indexpattern_datasource/indexpattern.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx index 803efd672073e..ee887459bb2c5 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx @@ -249,9 +249,6 @@ export function getIndexPatternDatasource({ { - onChangeIndexPattern(indexPattern, DATASOURCE_ID); - }} data={data} dataViews={dataViews} fieldFormats={fieldFormats} From e9abe5a987bd74e14ea0945b7079b21f0bc648a5 Mon Sep 17 00:00:00 2001 From: dej611 Date: Thu, 4 Aug 2022 15:42:23 +0200 Subject: [PATCH 16/27] :white_check_mark: Down to single broken test suite --- x-pack/plugins/lens/public/app_plugin/app.test.tsx | 2 +- .../editor_frame/state_helpers.ts | 4 +--- .../lens/public/indexpattern_service/loader.test.ts | 12 +----------- .../lens/public/indexpattern_service/mocks.ts | 1 - .../lens/public/mocks/data_views_service_mock.ts | 12 ++++++------ x-pack/plugins/lens/public/mocks/datasource_mock.ts | 2 +- x-pack/plugins/lens/public/mocks/services_mock.tsx | 3 ++- .../__snapshots__/load_initial.test.tsx.snap | 6 ++++++ .../state_management/init_middleware/load_initial.ts | 5 ++--- .../public/state_management/load_initial.test.tsx | 10 ++++------ 10 files changed, 24 insertions(+), 33 deletions(-) diff --git a/x-pack/plugins/lens/public/app_plugin/app.test.tsx b/x-pack/plugins/lens/public/app_plugin/app.test.tsx index 54e1c5eda5d58..a2e247cc427c8 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.test.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.test.tsx @@ -477,7 +477,7 @@ describe('Lens App', () => { expect(services.navigation.ui.TopNavMenu).toHaveBeenCalledWith( expect.objectContaining({ query: 'fake query', - indexPatterns: [{ id: 'mockip', isTimeBased: expect.any(Function) }], + indexPatterns: [{ id: 'mockip', isTimeBased: expect.any(Function), fields: [] }], }), {} ); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts index 0a42917bb86ab..91ff19de95f33 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts @@ -162,9 +162,7 @@ export async function initializeSources( defaultIndexPatternId, references, }, - { - isFullEditor: true, - } + options ); return { indexPatterns, diff --git a/x-pack/plugins/lens/public/indexpattern_service/loader.test.ts b/x-pack/plugins/lens/public/indexpattern_service/loader.test.ts index 610236ea12d05..912028fba81a2 100644 --- a/x-pack/plugins/lens/public/indexpattern_service/loader.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_service/loader.test.ts @@ -52,17 +52,7 @@ describe('loader', () => { dataViews: mockDataViewsService(), }); - expect(cache).toEqual(sampleIndexPatterns); - }); - - it('should allow scripted, but not full text fields', async () => { - const cache = await loadIndexPatterns({ - cache: {}, - patterns: ['1', '2'], - dataViews: mockDataViewsService(), - }); - - expect(cache).toEqual(sampleIndexPatterns); + expect(Object.keys(cache)).toEqual(['1', '2']); }); it('should apply field restrictions from typeMeta', async () => { diff --git a/x-pack/plugins/lens/public/indexpattern_service/mocks.ts b/x-pack/plugins/lens/public/indexpattern_service/mocks.ts index a96236e781984..6eb7156a91cff 100644 --- a/x-pack/plugins/lens/public/indexpattern_service/mocks.ts +++ b/x-pack/plugins/lens/public/indexpattern_service/mocks.ts @@ -114,7 +114,6 @@ const indexPattern1 = { }, documentField, ], - getFieldByName: jest.fn(), } as unknown as IndexPattern; indexPattern1.getFieldByName = getFieldByNameFactory(indexPattern1.fields); diff --git a/x-pack/plugins/lens/public/mocks/data_views_service_mock.ts b/x-pack/plugins/lens/public/mocks/data_views_service_mock.ts index 41abd1a0e8f8d..798217e9fb673 100644 --- a/x-pack/plugins/lens/public/mocks/data_views_service_mock.ts +++ b/x-pack/plugins/lens/public/mocks/data_views_service_mock.ts @@ -9,11 +9,11 @@ import type { IndexPatternServiceAPI } from '../indexpattern_service/service'; export function createIndexPatternServiceMock(): IndexPatternServiceAPI { return { - loadIndexPatterns: jest.fn(), - loadIndexPatternRefs: jest.fn(), - ensureIndexPattern: jest.fn(), - refreshExistingFields: jest.fn(), - getDefaultIndex: jest.fn(), - updateIndexPatternsCache: jest.fn(), + loadIndexPatterns: jest.fn(async () => ({})), + loadIndexPatternRefs: jest.fn(async () => []), + ensureIndexPattern: jest.fn(async () => ({})), + refreshExistingFields: jest.fn(async () => {}), + getDefaultIndex: jest.fn(() => 'fake-index'), + updateIndexPatternsCache: jest.fn(async () => {}), }; } diff --git a/x-pack/plugins/lens/public/mocks/datasource_mock.ts b/x-pack/plugins/lens/public/mocks/datasource_mock.ts index cf8370efdece9..8111270cc5d12 100644 --- a/x-pack/plugins/lens/public/mocks/datasource_mock.ts +++ b/x-pack/plugins/lens/public/mocks/datasource_mock.ts @@ -37,7 +37,7 @@ export function createMockDatasource(id: string): DatasourceMock { })), getRenderEventCounters: jest.fn((_state) => []), getPublicAPI: jest.fn().mockReturnValue(publicAPIMock), - initialize: jest.fn((_state?) => Promise.resolve()), + initialize: jest.fn((_state?) => {}), renderDataPanel: jest.fn(), renderLayerPanel: jest.fn(), getCurrentIndexPatternId: jest.fn(), diff --git a/x-pack/plugins/lens/public/mocks/services_mock.tsx b/x-pack/plugins/lens/public/mocks/services_mock.tsx index 800ec3dee25b1..e9d0c7cd14f28 100644 --- a/x-pack/plugins/lens/public/mocks/services_mock.tsx +++ b/x-pack/plugins/lens/public/mocks/services_mock.tsx @@ -86,9 +86,10 @@ export function makeDefaultServices( const dataViewsMock = dataViewPluginMocks.createStartContract(); dataViewsMock.get.mockImplementation( jest.fn((id) => - Promise.resolve({ id, isTimeBased: () => true }) + Promise.resolve({ id, isTimeBased: () => true, fields: [] }) ) as unknown as DataViewsPublicPluginStart['get'] ); + dataViewsMock.getIdsWithTitle.mockImplementation(jest.fn(async () => [])); const navigationStartMock = navigationPluginMock.createStartContract(); diff --git a/x-pack/plugins/lens/public/state_management/__snapshots__/load_initial.test.tsx.snap b/x-pack/plugins/lens/public/state_management/__snapshots__/load_initial.test.tsx.snap index 7f70508dc423f..a6759521f562e 100644 --- a/x-pack/plugins/lens/public/state_management/__snapshots__/load_initial.test.tsx.snap +++ b/x-pack/plugins/lens/public/state_management/__snapshots__/load_initial.test.tsx.snap @@ -4,6 +4,12 @@ exports[`Initializing the store should initialize all datasources with state fro Object { "lens": Object { "activeDatasourceId": "testDatasource", + "dataViews": Object { + "existingFields": Object {}, + "indexPatternRefs": Array [], + "indexPatterns": Object {}, + "isFirstExistenceFetch": true, + }, "datasourceStates": Object { "testDatasource": Object { "isLoading": false, diff --git a/x-pack/plugins/lens/public/state_management/init_middleware/load_initial.ts b/x-pack/plugins/lens/public/state_management/init_middleware/load_initial.ts index 1e39fa55f4b04..5ee4c82d7ce5f 100644 --- a/x-pack/plugins/lens/public/state_management/init_middleware/load_initial.ts +++ b/x-pack/plugins/lens/public/state_management/init_middleware/load_initial.ts @@ -159,7 +159,7 @@ export function loadInitial( }); } - getPersisted({ initialInput, lensServices, history }) + return getPersisted({ initialInput, lensServices, history }) .then( (persisted) => { if (persisted) { @@ -186,8 +186,7 @@ export function loadInitial( const filters = data.query.filterManager.inject(doc.state.filters, doc.references); // Don't overwrite any pinned filters data.query.filterManager.setAppFilters(filters); - - initializeSources( + return initializeSources( { datasourceMap, datasourceStates: docDatasourceStates, diff --git a/x-pack/plugins/lens/public/state_management/load_initial.test.tsx b/x-pack/plugins/lens/public/state_management/load_initial.test.tsx index 9ae27a9c0073e..2d8ce9405f12f 100644 --- a/x-pack/plugins/lens/public/state_management/load_initial.test.tsx +++ b/x-pack/plugins/lens/public/state_management/load_initial.test.tsx @@ -120,17 +120,15 @@ describe('Initializing the store', () => { datasource1State, [], undefined, - { - isFullEditor: true, - } + [], + {} ); expect(datasourceMap.testDatasource2.initialize).toHaveBeenCalledWith( datasource2State, [], undefined, - { - isFullEditor: true, - } + [], + {} ); expect(datasourceMap.testDatasource3.initialize).not.toHaveBeenCalled(); expect(store.getState()).toMatchSnapshot(); From 13a669ec4899286633794dcf88b0debca9cea7be Mon Sep 17 00:00:00 2001 From: dej611 Date: Thu, 4 Aug 2022 19:45:37 +0200 Subject: [PATCH 17/27] :label: Fix type issue --- .../public/indexpattern_datasource/dimension_panel/window.tsx | 3 ++- .../lens/public/indexpattern_datasource/window_utils.tsx | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/window.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/window.tsx index c0d561d694274..00799af410ee0 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/window.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/window.tsx @@ -17,7 +17,8 @@ import { GenericIndexPatternColumn, operationDefinitionMap, } from '../operations'; -import { IndexPattern, IndexPatternLayer } from '../types'; +import type { IndexPatternLayer } from '../types'; +import type { IndexPattern } from '../../types'; import { windowOptions } from '../window_utils'; export function setWindow(columnId: string, layer: IndexPatternLayer, window: string | undefined) { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/window_utils.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/window_utils.tsx index 14cc09fcdec8b..26b1feaa45eaa 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/window_utils.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/window_utils.tsx @@ -6,7 +6,8 @@ */ import { i18n } from '@kbn/i18n'; -import { IndexPattern, IndexPatternLayer } from './types'; +import type { IndexPatternLayer } from './types'; +import type { IndexPattern } from '../types'; export const windowOptions = [ { From f340ff57323f433b5f4204d131f69d791601f66d Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Wed, 10 Aug 2022 12:35:48 +0300 Subject: [PATCH 18/27] Persistable state change --- .../data/common/query/filters/persistable_state.test.ts | 2 +- src/plugins/data/common/query/filters/persistable_state.ts | 3 ++- src/plugins/data/common/query/persistable_state.test.ts | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/plugins/data/common/query/filters/persistable_state.test.ts b/src/plugins/data/common/query/filters/persistable_state.test.ts index 9ce3e2536a70a..392b029d8c7c2 100644 --- a/src/plugins/data/common/query/filters/persistable_state.test.ts +++ b/src/plugins/data/common/query/filters/persistable_state.test.ts @@ -26,7 +26,7 @@ describe('filter manager persistable state tests', () => { const updatedFilters = inject(filters, [ { type: DATA_VIEW_SAVED_OBJECT_TYPE, name: 'test123', id: '123' }, ]); - expect(updatedFilters[0]).toHaveProperty('meta.index', undefined); + expect(updatedFilters[0]).toHaveProperty('meta.index', 'test'); }); }); diff --git a/src/plugins/data/common/query/filters/persistable_state.ts b/src/plugins/data/common/query/filters/persistable_state.ts index a309573fb9df2..a2696723fbab7 100644 --- a/src/plugins/data/common/query/filters/persistable_state.ts +++ b/src/plugins/data/common/query/filters/persistable_state.ts @@ -46,7 +46,8 @@ export const inject = (filters: Filter[], references: SavedObjectReference[]) => ...filter, meta: { ...filter.meta, - index: reference && reference.id, + // if no reference has been found, keep the current "index" property (used for adhoc data views) + index: reference ? reference.id : filter.meta.index, }, }; }); diff --git a/src/plugins/data/common/query/persistable_state.test.ts b/src/plugins/data/common/query/persistable_state.test.ts index 2fcfce910ebb8..f44e5276ca7fe 100644 --- a/src/plugins/data/common/query/persistable_state.test.ts +++ b/src/plugins/data/common/query/persistable_state.test.ts @@ -41,7 +41,7 @@ describe('query service persistable state tests', () => { const updatedQueryState = inject(queryState, [ { type: DATA_VIEW_SAVED_OBJECT_TYPE, name: 'test123', id: '123' }, ]); - expect(updatedQueryState.filters[0]).toHaveProperty('meta.index', undefined); + expect(updatedQueryState.filters[0]).toHaveProperty('meta.index', 'test'); }); }); From 70000f0c2643a98df839ccd3e9acbbe351116d10 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Wed, 10 Aug 2022 12:57:25 +0300 Subject: [PATCH 19/27] Fix types --- x-pack/plugins/lens/public/embeddable/embeddable.test.tsx | 2 +- x-pack/plugins/lens/public/state_management/lens_slice.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/lens/public/embeddable/embeddable.test.tsx b/x-pack/plugins/lens/public/embeddable/embeddable.test.tsx index a6fc6a79f16e3..8dcd91dca4184 100644 --- a/x-pack/plugins/lens/public/embeddable/embeddable.test.tsx +++ b/x-pack/plugins/lens/public/embeddable/embeddable.test.tsx @@ -195,7 +195,7 @@ describe('embeddable', () => { data: dataMock, expressionRenderer, basePath, - indexPatternService: {} as DataViewsContract, + dataViews: {} as DataViewsContract, capabilities: { canSaveDashboards: true, canSaveVisualizations: true, diff --git a/x-pack/plugins/lens/public/state_management/lens_slice.ts b/x-pack/plugins/lens/public/state_management/lens_slice.ts index 9b32f91e7b139..b15bdc701c300 100644 --- a/x-pack/plugins/lens/public/state_management/lens_slice.ts +++ b/x-pack/plugins/lens/public/state_management/lens_slice.ts @@ -366,12 +366,12 @@ export const makeLensReducer = (storeDeps: LensStoreDeps) => { nextPublicAPI.getTableSpec().map(({ columnId }) => columnId) ); const removed = datasourceLayers[layerId] - .getTableSpec() + ?.getTableSpec() .map(({ columnId }) => columnId) .filter((columnId) => !nextTable.has(columnId)); const nextVisState = (newState.visualization || state.visualization).state; const activeVisualization = visualizationMap[state.visualization.activeId]; - removed.forEach((columnId) => { + removed?.forEach((columnId) => { newState.visualization = { ...state.visualization, state: activeVisualization.removeDimension({ From 7c77bf17ada20937f3e94a3e7a5489a02105f4d6 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Thu, 11 Aug 2022 19:53:37 +0300 Subject: [PATCH 20/27] Adds support for adhoc dataviews --- .../lens/public/app_plugin/lens_top_nav.tsx | 47 +++++++++++++++---- .../editor_frame/state_helpers.ts | 29 ++++++++++-- .../lens/public/embeddable/embeddable.tsx | 17 ++++++- .../indexpattern_datasource/indexpattern.tsx | 15 ++++-- .../public/indexpattern_datasource/loader.ts | 41 ++++++++++++---- .../operations/layer_helpers.ts | 7 +++ .../public/indexpattern_datasource/types.ts | 4 +- .../public/indexpattern_service/loader.ts | 13 ++++- .../lens/public/state_management/selectors.ts | 12 ++++- x-pack/plugins/lens/public/types.ts | 5 +- 10 files changed, 158 insertions(+), 32 deletions(-) diff --git a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx index 12c8c76306246..97256eb397130 100644 --- a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx +++ b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx @@ -252,14 +252,38 @@ export const LensTopNavMenu = ({ [dispatch] ); const dispatchChangeIndexPattern = React.useCallback( - async (indexPatternId) => { - const newIndexPatterns = await indexPatternService.ensureIndexPattern({ - id: indexPatternId, - cache: dataViews.indexPatterns, - }); + async (dataViewOrId, isAdHoc?) => { + const indexPatternId = typeof dataViewOrId === 'string' ? dataViewOrId : dataViewOrId.id; + const [newIndexPatternRefs, newIndexPatterns] = await Promise.all([ + // Reload refs in case it's a new indexPattern created on the spot + dataViews.indexPatternRefs[indexPatternId] + ? dataViews.indexPatternRefs + : indexPatternService.loadIndexPatternRefs({ + isFullEditor: true, + }), + indexPatternService.ensureIndexPattern({ + id: indexPatternId, + cache: dataViews.indexPatterns, + }), + ]); + let indexPatternRefsEnhanced = newIndexPatternRefs; + if (isAdHoc) { + indexPatternRefsEnhanced = [ + ...indexPatternRefsEnhanced, + { + title: dataViewOrId.title, + name: dataViewOrId.name, + id: indexPatternId, + adHoc: true, + }, + ]; + } dispatch( changeIndexPattern({ - dataViews: { indexPatterns: newIndexPatterns }, + dataViews: { + indexPatterns: newIndexPatterns, + indexPatternRefs: indexPatternRefsEnhanced, + }, datasourceIds: Object.keys(datasourceStates), visualizationIds: visualization.activeId ? [visualization.activeId] : [], indexPatternId, @@ -267,6 +291,7 @@ export const LensTopNavMenu = ({ ); }, [ + dataViews.indexPatternRefs, dataViews.indexPatterns, datasourceStates, dispatch, @@ -694,14 +719,20 @@ export const LensTopNavMenu = ({ closeDataViewEditor.current = dataViewEditor.openEditor({ onSave: async (dataView) => { if (dataView.id) { - dispatchChangeIndexPattern(dataView.id); + dispatchChangeIndexPattern(dataView, !dataView.isPersisted()); + setCurrentIndexPattern(dataView); + if (!dataView.isPersisted()) { + // add the ad-hoc dataview on the indexPatterns list + setIndexPatterns([...indexPatterns, dataView]); + } refreshFieldList(); } }, + allowAdHocDataView: true, }); } : undefined, - [canEditDataView, dataViewEditor, dispatchChangeIndexPattern, refreshFieldList] + [canEditDataView, dataViewEditor, dispatchChangeIndexPattern, indexPatterns, refreshFieldList] ); const dataViewPickerProps = { diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts index 91ff19de95f33..8e703472b9c33 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts @@ -42,7 +42,8 @@ import { loadIndexPatternRefs, loadIndexPatterns } from '../../indexpattern_serv function getIndexPatterns( references?: SavedObjectReference[], initialContext?: VisualizeFieldContext | VisualizeEditorContext, - initialId?: string + initialId?: string, + adHocDataviews?: string[] ) { const indexPatternIds = []; if (initialContext) { @@ -66,6 +67,9 @@ function getIndexPatterns( } } } + if (adHocDataviews) { + indexPatternIds.push(...adHocDataviews); + } return [...new Set(indexPatternIds)]; } @@ -111,8 +115,26 @@ export async function initializeDataViews( Object.keys(datasourceMap).every((datasourceId) => !datasourceStates[datasourceId]?.state) ? fallbackId : undefined; - - const usedIndexPatterns = getIndexPatterns(references, initialContext, initialId); + const adHocDataviewsIds: string[] = []; + let adHocDataviews; + Object.keys(datasourceMap).forEach((datasourceId) => { + const datasource = datasourceMap[datasourceId]; + const datasourceState = datasourceStates[datasourceId]?.state; + const adHocSpecs = datasource?.getAdHocIndexSpecs?.(datasourceState); + if (adHocSpecs) { + const dataViewsIds: string[] = Object.keys(adHocSpecs); + if (dataViewsIds.length) { + adHocDataviewsIds.push(...dataViewsIds); + adHocDataviews = Object.values(adHocSpecs); + } + } + }); + const usedIndexPatterns = getIndexPatterns( + references, + initialContext, + initialId, + adHocDataviewsIds + ); // load them const availableIndexPatterns = new Set(indexPatternRefs.map(({ id }: IndexPatternRef) => id)); @@ -124,6 +146,7 @@ export async function initializeDataViews( patterns: usedIndexPatterns, notUsedPatterns, cache: {}, + adHocDataviews, }); return { indexPatternRefs, indexPatterns }; diff --git a/x-pack/plugins/lens/public/embeddable/embeddable.tsx b/x-pack/plugins/lens/public/embeddable/embeddable.tsx index 238ae272b1169..6335581775b5a 100644 --- a/x-pack/plugins/lens/public/embeddable/embeddable.tsx +++ b/x-pack/plugins/lens/public/embeddable/embeddable.tsx @@ -764,8 +764,23 @@ export class Embeddable this.activeDataInfo.activeDatasource = this.deps.datasourceMap[activeDatasourceId]; const docDatasourceState = this.savedVis?.state.datasourceStates[activeDatasourceId]; + const adHocIndexPatterns = + this.activeDataInfo.activeDatasource?.getAdHocIndexSpecs?.(docDatasourceState); + + const adHocDataviews: DataView[] = []; + + if (adHocIndexPatterns) { + const adHocSpecs = Object.values(adHocIndexPatterns); + if (adHocSpecs?.length) { + for (const addHocDataView of adHocSpecs) { + const d = await this.deps.dataViews.create(addHocDataView); + adHocDataviews.push(d); + } + } + } + const allIndexPatterns = [...this.indexPatterns, ...adHocDataviews]; - const indexPatternsCache = this.indexPatterns.reduce( + const indexPatternsCache = allIndexPatterns.reduce( (acc, indexPattern) => ({ [indexPattern.id!]: convertDataViewIntoLensIndexPattern(indexPattern), ...acc, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx index a2ab9fd238215..2855b1976c537 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx @@ -202,6 +202,10 @@ export function getIndexPatternDatasource({ return Object.keys(state.layers); }, + getAdHocIndexSpecs(state: IndexPatternPersistedState) { + return state?.adHocIndexPatterns; + }, + removeColumn({ prevState, layerId, columnId, indexPatterns }) { const indexPattern = indexPatterns[prevState.layers[layerId]?.indexPatternId]; return mergeLayer({ @@ -540,15 +544,18 @@ export function getIndexPatternDatasource({ } return null; }, - getSourceId: () => layer.indexPatternId, - getFilters: (activeData: FramePublicAPI['activeData'], timeRange?: TimeRange) => - getFiltersInLayer( + getSourceId: () => { + return layer.adHocSpec?.id || layer.indexPatternId; + }, + getFilters: (activeData: FramePublicAPI['activeData'], timeRange?: TimeRange) => { + return getFiltersInLayer( layer, visibleColumnIds, activeData?.[layerId], indexPatterns[layer.indexPatternId], timeRange - ), + ); + }, getVisualDefaults: () => getVisualDefaultsForLayer(layer), getMaxPossibleNumValues: (columnId) => { if (layer && layer.columns[columnId]) { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/loader.ts b/x-pack/plugins/lens/public/indexpattern_datasource/loader.ts index f7a6912390027..4fb2b67712e2f 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/loader.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/loader.ts @@ -49,16 +49,25 @@ function getLayerReferenceName(layerId: string) { export function extractReferences({ layers }: IndexPatternPrivateState) { const savedObjectReferences: SavedObjectReference[] = []; - const persistableLayers: Record> = {}; + const persistableState: IndexPatternPersistedState = { + layers: {}, + adHocIndexPatterns: {}, + }; Object.entries(layers).forEach(([layerId, { indexPatternId, ...persistableLayer }]) => { - savedObjectReferences.push({ - type: 'index-pattern', - id: indexPatternId, - name: getLayerReferenceName(layerId), - }); - persistableLayers[layerId] = persistableLayer; + persistableState.layers[layerId] = persistableLayer; + if (persistableLayer.adHocSpec) { + if (!persistableState.adHocIndexPatterns![indexPatternId]) { + persistableState.adHocIndexPatterns![indexPatternId] = persistableLayer.adHocSpec!; + } + } else { + savedObjectReferences.push({ + type: 'index-pattern', + id: indexPatternId, + name: getLayerReferenceName(layerId), + }); + } }); - return { savedObjectReferences, state: { layers: persistableLayers } }; + return { savedObjectReferences, state: persistableState }; } export function injectReferences( @@ -69,11 +78,14 @@ export function injectReferences( Object.entries(state.layers).forEach(([layerId, persistedLayer]) => { layers[layerId] = { ...persistedLayer, - indexPatternId: references.find(({ name }) => name === getLayerReferenceName(layerId))!.id, + indexPatternId: + persistedLayer.adHocSpec?.id || + references.find(({ name }) => name === getLayerReferenceName(layerId))!.id, }; }); return { layers, + adHocIndexPatterns: state.adHocIndexPatterns, }; } @@ -117,7 +129,11 @@ function getUsedIndexPatterns({ const usedPatterns = ( initialContext ? indexPatternIds - : uniq(state ? Object.values(state.layers).map((l) => l.indexPatternId) : [fallbackId]) + : uniq( + state + ? Object.values(state.layers).map((l) => l.adHocSpec?.id ?? l.indexPatternId) + : [fallbackId] + ) ) // take out the undefined from the list .filter(Boolean); @@ -154,6 +170,11 @@ export function loadInitialState({ indexPatternRefs, }); + if (persistedState?.adHocIndexPatterns) { + Object.entries(persistedState?.adHocIndexPatterns).forEach(([id, { name, title }]) => { + indexPatternRefs.push({ id, name, title: title || '', adHoc: true }); + }); + } const availableIndexPatterns = new Set(indexPatternRefs.map(({ id }: IndexPatternRef) => id)); const notUsedPatterns: string[] = difference([...availableIndexPatterns], usedPatterns); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts index 3d079584e32f9..fc6d521fa8a45 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts @@ -200,6 +200,12 @@ export function insertNewColumn({ if (layer.columns[columnId]) { throw new Error(`Can't insert a column with an ID that is already in use`); } + if (indexPattern.spec) { + layer = { + ...layer, + adHocSpec: indexPattern.spec, + }; + } const baseOptions = { indexPattern, @@ -1356,6 +1362,7 @@ export function updateLayerIndexPattern( return { ...layer, indexPatternId: newIndexPattern.id, + adHocSpec: newIndexPattern?.spec, columns: newColumns, columnOrder: newColumnOrder, }; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/types.ts b/x-pack/plugins/lens/public/indexpattern_datasource/types.ts index 46a9ffaafb566..68facca974f5b 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/types.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/types.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import type { DataViewSpec } from '@kbn/data-views-plugin/common'; import type { DragDropIdentifier } from '../drag_drop/providers'; import type { IncompleteColumn, GenericIndexPatternColumn } from './operations'; import type { DragDropOperation } from '../types'; @@ -55,10 +55,12 @@ export interface IndexPatternLayer { indexPatternId: string; // Partial columns represent the temporary invalid states incompleteColumns?: Record; + adHocSpec?: DataViewSpec; } export interface IndexPatternPersistedState { layers: Record>; + adHocIndexPatterns?: Record; } export type PersistedIndexPatternLayer = Omit; diff --git a/x-pack/plugins/lens/public/indexpattern_service/loader.ts b/x-pack/plugins/lens/public/indexpattern_service/loader.ts index 45dd7eb9db8f3..e5ddd485065fb 100644 --- a/x-pack/plugins/lens/public/indexpattern_service/loader.ts +++ b/x-pack/plugins/lens/public/indexpattern_service/loader.ts @@ -6,7 +6,7 @@ */ import { isNestedField } from '@kbn/data-views-plugin/common'; -import type { DataViewsContract, DataView } from '@kbn/data-views-plugin/public'; +import type { DataViewsContract, DataView, DataViewSpec } from '@kbn/data-views-plugin/public'; import { keyBy } from 'lodash'; import { HttpSetup } from '@kbn/core/public'; import { IndexPattern, IndexPatternField, IndexPatternMap, IndexPatternRef } from '../types'; @@ -15,7 +15,7 @@ import { BASE_API_URL, DateRange, ExistingFields } from '../../common'; import { DataViewsState } from '../state_management'; type ErrorHandler = (err: Error) => void; -type MinimalDataViewsContract = Pick; +type MinimalDataViewsContract = Pick; /** * All these functions will be used by the Embeddable instance too, @@ -92,6 +92,7 @@ export function convertDataViewIntoLensIndexPattern( fields: newFields, getFieldByName: getFieldByNameFactory(newFields), hasRestrictions: !!typeMeta?.aggs, + spec: dataView.isPersisted() ? undefined : dataView.toSpec(false), }; } @@ -122,12 +123,14 @@ export async function loadIndexPatterns({ patterns, notUsedPatterns, cache, + adHocDataviews, onIndexPatternRefresh, }: { dataViews: MinimalDataViewsContract; patterns: string[]; notUsedPatterns?: string[]; cache: Record; + adHocDataviews?: DataViewSpec[]; onIndexPatternRefresh?: () => void; }) { const missingIds = patterns.filter((id) => !cache[id]); @@ -157,6 +160,12 @@ export async function loadIndexPatterns({ } } } + if (adHocDataviews?.length) { + for (const addHocDataView of adHocDataviews) { + const d = await dataViews.create(addHocDataView); + indexPatterns.push(d); + } + } const indexPatternsObject = indexPatterns.reduce( (acc, indexPattern) => ({ diff --git a/x-pack/plugins/lens/public/state_management/selectors.ts b/x-pack/plugins/lens/public/state_management/selectors.ts index f1f53197978fa..3354d4f8e7f61 100644 --- a/x-pack/plugins/lens/public/state_management/selectors.ts +++ b/x-pack/plugins/lens/public/state_management/selectors.ts @@ -113,8 +113,16 @@ export const selectSavedObjectFormat = createSelector( references.push(...savedObjectReferences); }); + const adHocFilters = filters + .filter((f) => !references.some((r) => r.type === 'index-pattern' && r.id === f.meta.index)) + .map((f) => ({ ...f, meta: { ...f.meta, value: undefined } })); + + const referencedFilters = filters.filter((f) => + references.some((r) => r.type === 'index-pattern' && r.id === f.meta.index) + ); + const { state: persistableFilters, references: filterReferences } = - extractFilterReferences(filters); + extractFilterReferences(referencedFilters); references.push(...filterReferences); @@ -128,7 +136,7 @@ export const selectSavedObjectFormat = createSelector( state: { visualization: visualization.state, query, - filters: persistableFilters, + filters: [...persistableFilters, ...adHocFilters], datasourceStates: persistibleDatasourceStates, }, }; diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index 39c0d3c2a30fe..f1fe6cf8fb2ca 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -26,7 +26,7 @@ import type { } from '@kbn/ui-actions-plugin/public'; import type { ClickTriggerEvent, BrushTriggerEvent } from '@kbn/charts-plugin/public'; import type { IndexPatternAggRestrictions } from '@kbn/data-plugin/public'; -import type { FieldSpec } from '@kbn/data-views-plugin/common'; +import type { FieldSpec, DataViewSpec } from '@kbn/data-views-plugin/common'; import type { FieldFormatParams } from '@kbn/field-formats-plugin/common'; import type { DraggingIdentifier, DragDropIdentifier, DragContextState } from './drag_drop'; import type { DateRange, LayerType, SortingHint } from '../common'; @@ -51,6 +51,7 @@ export interface IndexPatternRef { id: string; title: string; name?: string; + adHoc?: boolean; } export interface IndexPattern { @@ -68,6 +69,7 @@ export interface IndexPattern { } >; hasRestrictions: boolean; + spec?: DataViewSpec; } export type IndexPatternField = FieldSpec & { @@ -273,6 +275,7 @@ export interface Datasource { removeLayer: (state: T, layerId: string) => T; clearLayer: (state: T, layerId: string) => T; getLayers: (state: T) => string[]; + getAdHocIndexSpecs?: (state: P) => Record | undefined; removeColumn: (props: { prevState: T; layerId: string; From b43a72a4ff6c6fb0f9811e4ed910ebc2d1ad6593 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Fri, 12 Aug 2022 09:37:48 +0300 Subject: [PATCH 21/27] Fix types and unit test --- .../lens/public/indexpattern_datasource/mocks.ts | 2 ++ .../lens/public/indexpattern_service/loader.test.ts | 13 +++++++++---- .../lens/public/indexpattern_service/mocks.ts | 11 +++++++++-- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/mocks.ts b/x-pack/plugins/lens/public/indexpattern_datasource/mocks.ts index d76e6723c1be9..2a1f93f5b31ca 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/mocks.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/mocks.ts @@ -101,6 +101,7 @@ export const createMockedIndexPattern = (): IndexPattern => { hasRestrictions: false, fields, getFieldByName: getFieldByNameFactory(fields), + spec: undefined, }; }; @@ -140,6 +141,7 @@ export const createMockedRestrictedIndexPattern = () => { fieldFormatMap: { bytes: { id: 'bytes', params: { pattern: '0.0' } } }, fields, getFieldByName: getFieldByNameFactory(fields), + spec: undefined, typeMeta: { params: { rollup_index: 'my-fake-index-pattern', diff --git a/x-pack/plugins/lens/public/indexpattern_service/loader.test.ts b/x-pack/plugins/lens/public/indexpattern_service/loader.test.ts index 912028fba81a2..dc9edff3c31b7 100644 --- a/x-pack/plugins/lens/public/indexpattern_service/loader.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_service/loader.test.ts @@ -64,6 +64,7 @@ describe('loader', () => { id: 'foo', title: 'Foo index', metaFields: [], + isPersisted: () => true, typeMeta: { aggs: { date_histogram: { @@ -100,7 +101,8 @@ describe('loader', () => { id: 'foo', title: 'Foo index', })), - } as unknown as Pick, + create: jest.fn(), + } as unknown as Pick, }); expect(cache.foo.getFieldByName('bytes')!.aggregationRestrictions).toEqual({ @@ -120,6 +122,7 @@ describe('loader', () => { id: 'foo', title: 'Foo index', metaFields: ['timestamp'], + isPersisted: () => true, typeMeta: { aggs: { date_histogram: { @@ -156,7 +159,8 @@ describe('loader', () => { id: 'foo', title: 'Foo index', })), - } as unknown as Pick, + create: jest.fn(), + } as unknown as Pick, }); expect(cache.foo.getFieldByName('timestamp')!.meta).toEqual(true); @@ -198,12 +202,13 @@ describe('loader', () => { timeFieldName: 'timestamp', hasRestrictions: false, fields: [], + isPersisted: () => true, }; } return Promise.reject(); }), getIdsWithTitle: jest.fn(), - } as unknown as Pick; + } as unknown as Pick; const cache = await loadIndexPatterns({ cache: {}, patterns: ['1', '2'], @@ -234,7 +239,7 @@ describe('loader', () => { throw err; }), getIdsWithTitle: jest.fn(), - } as unknown as Pick, + } as unknown as Pick, onError, }); diff --git a/x-pack/plugins/lens/public/indexpattern_service/mocks.ts b/x-pack/plugins/lens/public/indexpattern_service/mocks.ts index 6eb7156a91cff..048333786fdd6 100644 --- a/x-pack/plugins/lens/public/indexpattern_service/mocks.ts +++ b/x-pack/plugins/lens/public/indexpattern_service/mocks.ts @@ -42,6 +42,7 @@ const indexPattern1 = { title: 'my-fake-index-pattern', timeFieldName: 'timestamp', hasRestrictions: false, + isPersisted: () => true, fields: [ { name: 'timestamp', @@ -127,6 +128,7 @@ const indexPattern2 = { title: 'my-fake-restricted-pattern', timeFieldName: 'timestamp', hasRestrictions: true, + isPersisted: () => true, fieldFormatMap: { bytes: { id: 'bytes', params: { pattern: '0.0' } } }, fields: [ { @@ -198,7 +200,11 @@ export const sampleIndexPatterns = { export function mockDataViewsService() { return { get: jest.fn(async (id: '1' | '2') => { - const result = { ...sampleIndexPatternsFromService[id], metaFields: [] }; + const result = { + ...sampleIndexPatternsFromService[id], + metaFields: [], + isPersisted: () => true, + }; if (!result.fields) { result.fields = []; } @@ -216,5 +222,6 @@ export function mockDataViewsService() { }, ]; }), - } as unknown as Pick; + create: jest.fn(), + } as unknown as Pick; } From 5ca14704719ae4fe4c3e5d626d411cd5b7a6b711 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Fri, 12 Aug 2022 10:18:54 +0300 Subject: [PATCH 22/27] Fixes field statistics --- .../indexpattern_datasource/field_item.tsx | 1 + .../operations/definitions/terms/helpers.ts | 1 + .../lens/public/indexpattern_service/loader.ts | 4 ++++ .../lens/server/routes/existing_fields.ts | 16 +++++++++++++--- x-pack/plugins/lens/server/routes/field_stats.ts | 7 +++++-- 5 files changed, 24 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx index 02ca96e147605..33cbe250f0f12 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx @@ -165,6 +165,7 @@ export const InnerFieldItem = function InnerFieldItem(props: FieldItemProps) { fromDate: dateRange.fromDate, toDate: dateRange.toDate, fieldName: field.name, + spec: indexPattern.spec, }), }) .then((results) => { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/helpers.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/helpers.ts index a290c2d239a1e..629a9aba257d2 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/helpers.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/helpers.ts @@ -147,6 +147,7 @@ export function getDisallowedTermsMessage( fromDate: frame.dateRange.fromDate, toDate: frame.dateRange.toDate, size: currentColumn.params.size, + spec: indexPattern.spec, }), } ); diff --git a/x-pack/plugins/lens/public/indexpattern_service/loader.ts b/x-pack/plugins/lens/public/indexpattern_service/loader.ts index e5ddd485065fb..52e025dcf616f 100644 --- a/x-pack/plugins/lens/public/indexpattern_service/loader.ts +++ b/x-pack/plugins/lens/public/indexpattern_service/loader.ts @@ -237,6 +237,10 @@ async function refreshExistingFields({ body.timeFieldName = pattern.timeFieldName; } + if (pattern.spec) { + body.spec = pattern.spec; + } + return fetchJson(`${BASE_API_URL}/existing_fields/${pattern.id}`, { body: JSON.stringify(body), }) as Promise; diff --git a/x-pack/plugins/lens/server/routes/existing_fields.ts b/x-pack/plugins/lens/server/routes/existing_fields.ts index 8de7aa009b54a..4183791bc3f05 100644 --- a/x-pack/plugins/lens/server/routes/existing_fields.ts +++ b/x-pack/plugins/lens/server/routes/existing_fields.ts @@ -12,7 +12,7 @@ import { schema } from '@kbn/config-schema'; import { RequestHandlerContext, ElasticsearchClient } from '@kbn/core/server'; import { CoreSetup, Logger } from '@kbn/core/server'; import { RuntimeField } from '@kbn/data-views-plugin/common'; -import { DataViewsService, DataView, FieldSpec } from '@kbn/data-views-plugin/common'; +import { DataViewsService, DataView, FieldSpec, DataViewSpec } from '@kbn/data-views-plugin/common'; import { UI_SETTINGS } from '@kbn/data-plugin/server'; import { BASE_API_URL } from '../../common'; import { FIELD_EXISTENCE_SETTING } from '../ui_settings'; @@ -51,6 +51,7 @@ export async function existingFieldsRoute(setup: CoreSetup, fromDate: schema.maybe(schema.string()), toDate: schema.maybe(schema.string()), timeFieldName: schema.maybe(schema.string()), + spec: schema.object({}, { unknowns: 'allow' }), }), }, }, @@ -110,6 +111,7 @@ async function fetchFieldExistence({ fromDate, toDate, timeFieldName, + spec, includeFrozen, useSampling, }: { @@ -120,6 +122,7 @@ async function fetchFieldExistence({ fromDate?: string; toDate?: string; timeFieldName?: string; + spec?: DataViewSpec; includeFrozen: boolean; useSampling: boolean; }) { @@ -132,13 +135,16 @@ async function fetchFieldExistence({ fromDate, toDate, timeFieldName, + spec, includeFrozen, }); } const uiSettingsClient = (await context.core).uiSettings.client; const metaFields: string[] = await uiSettingsClient.get(UI_SETTINGS.META_FIELDS); - const dataView = await dataViewsService.get(indexPatternId); + const dataView = spec + ? await dataViewsService.create(spec) + : await dataViewsService.get(indexPatternId); const allFields = buildFieldList(dataView, metaFields); const existingFieldList = await dataViewsService.getFieldsForIndexPattern(dataView, { // filled in by data views service @@ -159,6 +165,7 @@ async function legacyFetchFieldExistenceSampling({ fromDate, toDate, timeFieldName, + spec, includeFrozen, }: { indexPatternId: string; @@ -168,11 +175,14 @@ async function legacyFetchFieldExistenceSampling({ fromDate?: string; toDate?: string; timeFieldName?: string; + spec?: DataViewSpec; includeFrozen: boolean; }) { const coreContext = await context.core; const metaFields: string[] = await coreContext.uiSettings.client.get(UI_SETTINGS.META_FIELDS); - const indexPattern = await dataViewsService.get(indexPatternId); + const indexPattern = spec + ? await dataViewsService.create(spec) + : await dataViewsService.get(indexPatternId); const fields = buildFieldList(indexPattern, metaFields); const runtimeMappings = indexPattern.getRuntimeMappings(); diff --git a/x-pack/plugins/lens/server/routes/field_stats.ts b/x-pack/plugins/lens/server/routes/field_stats.ts index 35a15ea44be67..7bbb66b47a6df 100644 --- a/x-pack/plugins/lens/server/routes/field_stats.ts +++ b/x-pack/plugins/lens/server/routes/field_stats.ts @@ -33,6 +33,7 @@ export async function initFieldsRoute(setup: CoreSetup) { toDate: schema.string(), fieldName: schema.string(), size: schema.maybe(schema.number()), + spec: schema.object({}, { unknowns: 'allow' }), }, { unknowns: 'allow' } ), @@ -40,7 +41,7 @@ export async function initFieldsRoute(setup: CoreSetup) { }, async (context, req, res) => { const requestClient = (await context.core).elasticsearch.client.asCurrentUser; - const { fromDate, toDate, fieldName, dslQuery, size } = req.body; + const { fromDate, toDate, fieldName, dslQuery, size, spec } = req.body; const [{ savedObjects, elasticsearch }, { dataViews }] = await setup.getStartServices(); const savedObjectsClient = savedObjects.getScopedClient(req); @@ -51,7 +52,9 @@ export async function initFieldsRoute(setup: CoreSetup) { ); try { - const indexPattern = await indexPatternsService.get(req.params.indexPatternId); + const indexPattern = spec + ? await indexPatternsService.create(spec) + : await indexPatternsService.get(req.params.indexPatternId); const timeFieldName = indexPattern.timeFieldName; const field = indexPattern.fields.find((f) => f.name === fieldName); From b5ee47ec0d80ae5735eaa46f60f97b07aceae2ac Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Fri, 12 Aug 2022 10:29:12 +0300 Subject: [PATCH 23/27] Fix --- x-pack/plugins/lens/server/routes/existing_fields.ts | 7 ++++--- x-pack/plugins/lens/server/routes/field_stats.ts | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/lens/server/routes/existing_fields.ts b/x-pack/plugins/lens/server/routes/existing_fields.ts index 4183791bc3f05..a5ae772241999 100644 --- a/x-pack/plugins/lens/server/routes/existing_fields.ts +++ b/x-pack/plugins/lens/server/routes/existing_fields.ts @@ -142,9 +142,10 @@ async function fetchFieldExistence({ const uiSettingsClient = (await context.core).uiSettings.client; const metaFields: string[] = await uiSettingsClient.get(UI_SETTINGS.META_FIELDS); - const dataView = spec - ? await dataViewsService.create(spec) - : await dataViewsService.get(indexPatternId); + const dataView = + spec && Object.keys(spec).length !== 0 + ? await dataViewsService.create(spec) + : await dataViewsService.get(indexPatternId); const allFields = buildFieldList(dataView, metaFields); const existingFieldList = await dataViewsService.getFieldsForIndexPattern(dataView, { // filled in by data views service diff --git a/x-pack/plugins/lens/server/routes/field_stats.ts b/x-pack/plugins/lens/server/routes/field_stats.ts index 7bbb66b47a6df..a844801473878 100644 --- a/x-pack/plugins/lens/server/routes/field_stats.ts +++ b/x-pack/plugins/lens/server/routes/field_stats.ts @@ -52,9 +52,10 @@ export async function initFieldsRoute(setup: CoreSetup) { ); try { - const indexPattern = spec - ? await indexPatternsService.create(spec) - : await indexPatternsService.get(req.params.indexPatternId); + const indexPattern = + spec && Object.keys(spec).length !== 0 + ? await indexPatternsService.create(spec) + : await indexPatternsService.get(req.params.indexPatternId); const timeFieldName = indexPattern.timeFieldName; const field = indexPattern.fields.find((f) => f.name === fieldName); From e8657ee3d6f2eeb045f4a7764f9eb40e932b5380 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Fri, 12 Aug 2022 11:01:39 +0300 Subject: [PATCH 24/27] Populate adhoc dataview to dashboard --- .../lens/public/embeddable/embeddable.tsx | 20 +++++++++++++++++- x-pack/plugins/lens/public/utils.ts | 21 +++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/lens/public/embeddable/embeddable.tsx b/x-pack/plugins/lens/public/embeddable/embeddable.tsx index 6335581775b5a..3bddbc789f257 100644 --- a/x-pack/plugins/lens/public/embeddable/embeddable.tsx +++ b/x-pack/plugins/lens/public/embeddable/embeddable.tsx @@ -74,7 +74,12 @@ import { LensAttributeService } from '../lens_attribute_service'; import type { ErrorMessage, TableInspectorAdapter } from '../editor_frame_service/types'; import { getLensInspectorService, LensInspector } from '../lens_inspector_service'; import { SharingSavedObjectProps, VisualizationDisplayOptions } from '../types'; -import { getActiveDatasourceIdFromDoc, getIndexPatternsObjects, inferTimeField } from '../utils'; +import { + getActiveDatasourceIdFromDoc, + getIndexPatternsObjects, + inferTimeField, + getAdHocIndexSpecs, +} from '../utils'; import { getLayerMetaInfo, combineQueryAndFilters } from '../app_plugin/show_underlying_data'; import { convertDataViewIntoLensIndexPattern } from '../indexpattern_service/loader'; @@ -840,6 +845,19 @@ export class Embeddable this.savedVis?.references.map(({ id }) => id) || [], this.deps.dataViews ); + const activeDatasourceId = getActiveDatasourceIdFromDoc(this.savedVis); + if (activeDatasourceId) { + const activeDatasource = this.deps.datasourceMap[activeDatasourceId]; + const dataSourceState = this.savedVis?.state.datasourceStates[activeDatasourceId]; + const { adHocDataviews } = await getAdHocIndexSpecs( + activeDatasource, + dataSourceState, + this.deps.dataViews + ); + if (adHocDataviews.length) { + indexPatterns.push(...adHocDataviews); + } + } this.indexPatterns = uniqBy(indexPatterns, 'id'); diff --git a/x-pack/plugins/lens/public/utils.ts b/x-pack/plugins/lens/public/utils.ts index 6ebf1cb7d3f83..5a903bc5c327c 100644 --- a/x-pack/plugins/lens/public/utils.ts +++ b/x-pack/plugins/lens/public/utils.ts @@ -150,6 +150,27 @@ export async function getIndexPatternsObjects( return { indexPatterns: fullfilled.map((response) => response.value), rejectedIds }; } +export async function getAdHocIndexSpecs( + dataSource: Datasource, + dataSourceState: unknown, + dataViews: DataViewsContract +): Promise<{ adHocDataviews: DataView[] }> { + const adHocIndexPatterns = dataSource?.getAdHocIndexSpecs?.(dataSourceState); + + const adHocDataviews: DataView[] = []; + + if (adHocIndexPatterns) { + const adHocSpecs = Object.values(adHocIndexPatterns); + if (adHocSpecs?.length) { + for (const addHocDataView of adHocSpecs) { + const d = await dataViews.create(addHocDataView); + adHocDataviews.push(d); + } + } + } + return { adHocDataviews }; +} + export function getRemoveOperation( activeVisualization: Visualization, visualizationState: VisualizationState['state'], From fb223fdc38ac3378e83ed76a72b12370e640da61 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Fri, 12 Aug 2022 12:47:58 +0300 Subject: [PATCH 25/27] Enhace the dataview picker lists with the adhoc dataviews --- .../dataview_picker/change_dataview.tsx | 14 ++- .../public/dataview_picker/index.tsx | 7 ++ .../lens/public/app_plugin/lens_top_nav.tsx | 55 +++++++---- .../change_indexpattern.tsx | 93 ------------------- .../indexpattern.test.ts | 1 + .../public/indexpattern_datasource/loader.ts | 2 +- x-pack/plugins/lens/public/types.ts | 1 - 7 files changed, 60 insertions(+), 113 deletions(-) delete mode 100644 x-pack/plugins/lens/public/indexpattern_datasource/change_indexpattern.tsx diff --git a/src/plugins/unified_search/public/dataview_picker/change_dataview.tsx b/src/plugins/unified_search/public/dataview_picker/change_dataview.tsx index 39ccfb44e7def..301e0a8c347b3 100644 --- a/src/plugins/unified_search/public/dataview_picker/change_dataview.tsx +++ b/src/plugins/unified_search/public/dataview_picker/change_dataview.tsx @@ -69,6 +69,7 @@ export function ChangeDataView({ onSaveTextLanguageQuery, onTextLangQuerySubmit, textBasedLanguage, + adHocDataViews, }: DataViewPickerPropsExtended) { const { euiTheme } = useEuiTheme(); const [isPopoverOpen, setPopoverIsOpen] = useState(false); @@ -93,10 +94,21 @@ export function ChangeDataView({ useEffect(() => { const fetchDataViews = async () => { const dataViewsRefs = await data.dataViews.getIdsWithTitle(); + if (adHocDataViews?.length) { + adHocDataViews.forEach((adHocDataView) => { + if (adHocDataView.id) { + dataViewsRefs.push({ + title: adHocDataView.title, + name: adHocDataView.name, + id: adHocDataView.id, + }); + } + }); + } setDataViewsList(dataViewsRefs); }; fetchDataViews(); - }, [data, currentDataViewId]); + }, [data, currentDataViewId, adHocDataViews]); useEffect(() => { if (trigger.label) { diff --git a/src/plugins/unified_search/public/dataview_picker/index.tsx b/src/plugins/unified_search/public/dataview_picker/index.tsx index 82909714cffad..2b3e20801f501 100644 --- a/src/plugins/unified_search/public/dataview_picker/index.tsx +++ b/src/plugins/unified_search/public/dataview_picker/index.tsx @@ -7,6 +7,7 @@ */ import React from 'react'; +import type { DataView } from '@kbn/data-views-plugin/public'; import type { EuiButtonProps, EuiSelectableProps } from '@elastic/eui'; import type { AggregateQuery, Query } from '@kbn/es-query'; import { ChangeDataView } from './change_dataview'; @@ -44,6 +45,10 @@ export interface DataViewPickerProps { * The id of the selected dataview. */ currentDataViewId?: string; + /** + * The adHocDataview selected. + */ + adHocDataViews?: DataView[]; /** * EuiSelectable properties. */ @@ -84,6 +89,7 @@ export interface DataViewPickerPropsExtended extends DataViewPickerProps { export const DataViewPicker = ({ isMissingCurrent, currentDataViewId, + adHocDataViews, onChangeDataView, onAddField, onDataViewCreated, @@ -98,6 +104,7 @@ export const DataViewPicker = ({ ) => dispatch(setState(state)), [dispatch] ); + const [indexPatterns, setIndexPatterns] = useState([]); + const [adHocDataViews, setAdHocDataViews] = useState(); + const [currentIndexPattern, setCurrentIndexPattern] = useState(); + const [rejectedIndexPatterns, setRejectedIndexPatterns] = useState([]); + const dispatchChangeIndexPattern = React.useCallback( - async (dataViewOrId, isAdHoc?) => { + async (dataViewOrId) => { const indexPatternId = typeof dataViewOrId === 'string' ? dataViewOrId : dataViewOrId.id; const [newIndexPatternRefs, newIndexPatterns] = await Promise.all([ // Reload refs in case it's a new indexPattern created on the spot @@ -266,23 +272,24 @@ export const LensTopNavMenu = ({ cache: dataViews.indexPatterns, }), ]); - let indexPatternRefsEnhanced = newIndexPatternRefs; - if (isAdHoc) { - indexPatternRefsEnhanced = [ - ...indexPatternRefsEnhanced, - { - title: dataViewOrId.title, - name: dataViewOrId.name, - id: indexPatternId, - adHoc: true, - }, - ]; + + // enhance the references with the adHoc dataviews + if (adHocDataViews?.length) { + adHocDataViews.forEach((adHoc) => { + if (adHoc.id) { + newIndexPatternRefs.push({ + title: adHoc.title, + name: adHoc.name, + id: adHoc.id, + }); + } + }); } dispatch( changeIndexPattern({ dataViews: { indexPatterns: newIndexPatterns, - indexPatternRefs: indexPatternRefsEnhanced, + indexPatternRefs: newIndexPatternRefs, }, datasourceIds: Object.keys(datasourceStates), visualizationIds: visualization.activeId ? [visualization.activeId] : [], @@ -297,12 +304,10 @@ export const LensTopNavMenu = ({ dispatch, indexPatternService, visualization.activeId, + adHocDataViews, ] ); - const [indexPatterns, setIndexPatterns] = useState([]); - const [currentIndexPattern, setCurrentIndexPattern] = useState(); - const [rejectedIndexPatterns, setRejectedIndexPatterns] = useState([]); const canEditDataView = Boolean(dataViewEditor?.userPermissions.editDataView()); const closeFieldEditor = useRef<() => void | undefined>(); const closeDataViewEditor = useRef<() => void | undefined>(); @@ -360,6 +365,21 @@ export const LensTopNavMenu = ({ } }, [indexPatterns]); + // add to the dataview picker list the adHoc dataviews + useEffect(() => { + const setAdHoc = async () => { + await asyncForEach(indexPatterns, async (indexPattern) => { + if (indexPattern.id && !adHocDataViews?.some((d) => d.id === indexPattern.id)) { + const dataViewInstance = await data.dataViews.get(indexPattern.id); + if (!dataViewInstance.isPersisted()) { + setAdHocDataViews([...(adHocDataViews ?? []), indexPattern]); + } + } + }); + }; + setAdHoc(); + }, [adHocDataViews, data.dataViews, indexPatterns]); + useEffect(() => { return () => { // Make sure to close the editors when unmounting @@ -719,7 +739,7 @@ export const LensTopNavMenu = ({ closeDataViewEditor.current = dataViewEditor.openEditor({ onSave: async (dataView) => { if (dataView.id) { - dispatchChangeIndexPattern(dataView, !dataView.isPersisted()); + dispatchChangeIndexPattern(dataView); setCurrentIndexPattern(dataView); if (!dataView.isPersisted()) { // add the ad-hoc dataview on the indexPatterns list @@ -742,6 +762,7 @@ export const LensTopNavMenu = ({ title: currentIndexPattern?.title || '', }, currentDataViewId: currentIndexPattern?.id, + adHocDataViews, onAddField: addField, onDataViewCreated: createNewDataView, onChangeDataView: (newIndexPatternId: string) => { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/change_indexpattern.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/change_indexpattern.tsx deleted file mode 100644 index 75ebbfdeb27a4..0000000000000 --- a/x-pack/plugins/lens/public/indexpattern_datasource/change_indexpattern.tsx +++ /dev/null @@ -1,93 +0,0 @@ -/* - * 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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { i18n } from '@kbn/i18n'; -import React, { useState } from 'react'; -import { EuiPopover, EuiPopoverTitle, EuiSelectableProps } from '@elastic/eui'; -import { ToolbarButton, ToolbarButtonProps } from '@kbn/kibana-react-plugin/public'; -import { DataViewsList } from '@kbn/unified-search-plugin/public'; -import type { IndexPatternRef } from '../types'; - -export type ChangeIndexPatternTriggerProps = ToolbarButtonProps & { - label: string; - title?: string; -}; - -export function ChangeIndexPattern({ - indexPatternRefs, - isMissingCurrent, - indexPatternId, - onChangeIndexPattern, - trigger, - selectableProps, -}: { - trigger: ChangeIndexPatternTriggerProps; - indexPatternRefs: IndexPatternRef[]; - isMissingCurrent?: boolean; - onChangeIndexPattern: (newId: string) => void; - indexPatternId?: string; - selectableProps?: EuiSelectableProps; -}) { - const [isPopoverOpen, setPopoverIsOpen] = useState(false); - - // be careful to only add color with a value, otherwise it will fallbacks to "primary" - const colorProp = isMissingCurrent - ? { - color: 'danger' as const, - } - : {}; - - const createTrigger = function () { - const { label, title, ...rest } = trigger; - return ( - setPopoverIsOpen(!isPopoverOpen)} - fullWidth - {...colorProp} - {...rest} - > - {label} - - ); - }; - - return ( - <> - setPopoverIsOpen(false)} - display="block" - panelPaddingSize="none" - ownFocus - > -
- - {i18n.translate('xpack.lens.indexPattern.changeDataViewTitle', { - defaultMessage: 'Data view', - })} - - - { - onChangeIndexPattern(newId); - setPopoverIsOpen(false); - }} - currentDataViewId={indexPatternId} - selectableProps={selectableProps} - /> -
-
- - ); -} diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts index e91aa1b286bec..0f031351dc963 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts @@ -282,6 +282,7 @@ describe('IndexPattern Data Source', () => { }, }, }, + adHocIndexPatterns: {}, }, savedObjectReferences: [ { name: 'indexpattern-datasource-layer-first', type: 'index-pattern', id: '1' }, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/loader.ts b/x-pack/plugins/lens/public/indexpattern_datasource/loader.ts index 4fb2b67712e2f..ec3e47cc8993a 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/loader.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/loader.ts @@ -172,7 +172,7 @@ export function loadInitialState({ if (persistedState?.adHocIndexPatterns) { Object.entries(persistedState?.adHocIndexPatterns).forEach(([id, { name, title }]) => { - indexPatternRefs.push({ id, name, title: title || '', adHoc: true }); + indexPatternRefs.push({ id, name, title: title || '' }); }); } const availableIndexPatterns = new Set(indexPatternRefs.map(({ id }: IndexPatternRef) => id)); diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index f1fe6cf8fb2ca..f7009af897739 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -51,7 +51,6 @@ export interface IndexPatternRef { id: string; title: string; name?: string; - adHoc?: boolean; } export interface IndexPattern { From 0916a3c274274e6eba5b04caf49653b5a4d951f8 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Fri, 12 Aug 2022 13:54:11 +0300 Subject: [PATCH 26/27] Fix test --- x-pack/plugins/lens/public/mocks/data_plugin_mock.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/lens/public/mocks/data_plugin_mock.ts b/x-pack/plugins/lens/public/mocks/data_plugin_mock.ts index 157c910440fb6..8260c71b04981 100644 --- a/x-pack/plugins/lens/public/mocks/data_plugin_mock.ts +++ b/x-pack/plugins/lens/public/mocks/data_plugin_mock.ts @@ -102,7 +102,7 @@ export function mockDataPlugin( extract: (filtersIn: Filter[]) => { const state = filtersIn.map((filter) => ({ ...filter, - meta: { ...filter.meta, index: 'extracted!' }, + meta: { ...filter.meta }, })); return { state, references: [] }; }, @@ -127,6 +127,13 @@ export function mockDataPlugin( indexPatterns: { get: jest.fn().mockImplementation((id) => Promise.resolve({ id, isTimeBased: () => true })), }, + dataViews: { + get: jest + .fn() + .mockImplementation((id) => + Promise.resolve({ id, isTimeBased: () => true, isPersisted: () => true }) + ), + }, search: createMockSearchService(), nowProvider: { get: jest.fn(), From eaa2093d7ca9f07d980f176ab26717f88c00cd3a Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Fri, 12 Aug 2022 15:51:39 +0300 Subject: [PATCH 27/27] Cleanup from merge coflicts --- x-pack/plugins/lens/public/visualizations/xy/types.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/lens/public/visualizations/xy/types.ts b/x-pack/plugins/lens/public/visualizations/xy/types.ts index 37176797b5d4c..c2ca25c61c42e 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/types.ts +++ b/x-pack/plugins/lens/public/visualizations/xy/types.ts @@ -113,7 +113,6 @@ export interface XYAnnotationLayerConfig { layerId: string; layerType: 'annotations'; annotations: EventAnnotationConfig[]; - hide?: boolean; simpleView?: boolean; }