mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Console] Refactor retrieval of mappings, aliases, templates, data-streams for autocomplete (#130633)
* Create a specific route for fetching mappings, aliases, templates, etc... * Encapsulate data streams * Encapsulate the mappings data into a class * Setup up autocompleteInfo service and provide its instance through context * Migrate the logic from mappings.js to Kibana server * Translate the logic to consume the appropriate ES client method * Update related test cases * Lint * Address comments * Fix server proxy/mock * Add API integration tests for /api/console/autocomplete_entities * Lint * Add tests * Add API integration tests for autocomplete_entities API * Add deleted tests Co-authored-by: Muhammad Ibragimov <muhammad.ibragimov@elastic.co> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
59120c9340
commit
b7866ac7f0
46 changed files with 1306 additions and 777 deletions
|
@ -130,12 +130,12 @@ warning: This document is auto-generated and is meant to be viewed inside our ex
|
|||
|
||||
| Deprecated API | Reference location(s) | Remove By |
|
||||
| ---------------|-----------|-----------|
|
||||
| <DocLink id="kibDataViewsPluginApi" section="def-public.IndexPatternField" text="IndexPatternField"/> | [field.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/aggs/param_types/field.ts#:~:text=IndexPatternField), [field.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/aggs/param_types/field.ts#:~:text=IndexPatternField), [field.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/aggs/param_types/field.ts#:~:text=IndexPatternField), [field.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/aggs/param_types/field.ts#:~:text=IndexPatternField), [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/index.ts#:~:text=IndexPatternField), [kibana_context_type.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/expressions/kibana_context_type.ts#:~:text=IndexPatternField), [kibana_context_type.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/expressions/kibana_context_type.ts#:~:text=IndexPatternField), [search_source.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/search_source.ts#:~:text=IndexPatternField), [search_source.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/search_source.ts#:~:text=IndexPatternField), [search_source.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/search_source.ts#:~:text=IndexPatternField)+ 16 more | - |
|
||||
| <DocLink id="kibDataViewsPluginApi" section="def-public.IndexPatternField" text="IndexPatternField"/> | [mapping.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/aggs/param_types/mapping.ts#:~:text=IndexPatternField), [mapping.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/aggs/param_types/mapping.ts#:~:text=IndexPatternField), [mapping.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/aggs/param_types/mapping.ts#:~:text=IndexPatternField), [mapping.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/aggs/param_types/mapping.ts#:~:text=IndexPatternField), [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/index.ts#:~:text=IndexPatternField), [kibana_context_type.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/expressions/kibana_context_type.ts#:~:text=IndexPatternField), [kibana_context_type.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/expressions/kibana_context_type.ts#:~:text=IndexPatternField), [search_source.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/search_source.ts#:~:text=IndexPatternField), [search_source.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/search_source.ts#:~:text=IndexPatternField), [search_source.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/search_source.ts#:~:text=IndexPatternField)+ 16 more | - |
|
||||
| <DocLink id="kibDataViewsPluginApi" section="def-public.IndexPatternsContract" text="IndexPatternsContract"/> | [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/index.ts#:~:text=IndexPatternsContract), [create_search_source.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/create_search_source.ts#:~:text=IndexPatternsContract), [create_search_source.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/create_search_source.ts#:~:text=IndexPatternsContract), [search_source_service.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/search_source_service.ts#:~:text=IndexPatternsContract), [search_source_service.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/search_source_service.ts#:~:text=IndexPatternsContract), [esaggs_fn.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/expressions/esaggs/esaggs_fn.ts#:~:text=IndexPatternsContract), [esaggs_fn.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/expressions/esaggs/esaggs_fn.ts#:~:text=IndexPatternsContract), [types.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/public/search/types.ts#:~:text=IndexPatternsContract), [types.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/public/search/types.ts#:~:text=IndexPatternsContract), [create_search_source.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/create_search_source.test.ts#:~:text=IndexPatternsContract)+ 19 more | - |
|
||||
| <DocLink id="kibDataViewsPluginApi" section="def-public.IndexPatternsService" text="IndexPatternsService"/> | [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/index.ts#:~:text=IndexPatternsService), [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/server/index.ts#:~:text=IndexPatternsService), [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/server/index.ts#:~:text=IndexPatternsService), [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/server/index.ts#:~:text=IndexPatternsService), [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/public/index.ts#:~:text=IndexPatternsService) | - |
|
||||
| <DocLink id="kibDataViewsPluginApi" section="def-public.IndexPattern" text="IndexPattern"/> | [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/index.ts#:~:text=IndexPattern), [types.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/types.ts#:~:text=IndexPattern), [types.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/types.ts#:~:text=IndexPattern), [types.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/types.ts#:~:text=IndexPattern), [types.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/types.ts#:~:text=IndexPattern), [search_source.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/search_source.ts#:~:text=IndexPattern), [search_source.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/search_source.ts#:~:text=IndexPattern), [search_source.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/search_source.ts#:~:text=IndexPattern), [tabify_docs.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/tabify/tabify_docs.ts#:~:text=IndexPattern), [tabify_docs.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/tabify/tabify_docs.ts#:~:text=IndexPattern)+ 89 more | - |
|
||||
| <DocLink id="kibDataViewsPluginApi" section="def-common.IFieldType" text="IFieldType"/> | [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/index.ts#:~:text=IFieldType), [date_histogram.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/aggs/buckets/date_histogram.ts#:~:text=IFieldType), [date_histogram.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/aggs/buckets/date_histogram.ts#:~:text=IFieldType), [generate_filters.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts#:~:text=IFieldType), [generate_filters.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts#:~:text=IFieldType), [generate_filters.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts#:~:text=IFieldType), [generate_filters.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts#:~:text=IFieldType), [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/public/index.ts#:~:text=IFieldType), [create_filters_from_range_select.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/public/actions/filters/create_filters_from_range_select.ts#:~:text=IFieldType), [create_filters_from_range_select.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/public/actions/filters/create_filters_from_range_select.ts#:~:text=IFieldType)+ 6 more | 8.2 |
|
||||
| <DocLink id="kibDataViewsPluginApi" section="def-common.IndexPatternField" text="IndexPatternField"/> | [field.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/aggs/param_types/field.ts#:~:text=IndexPatternField), [field.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/aggs/param_types/field.ts#:~:text=IndexPatternField), [field.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/aggs/param_types/field.ts#:~:text=IndexPatternField), [field.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/aggs/param_types/field.ts#:~:text=IndexPatternField), [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/index.ts#:~:text=IndexPatternField), [kibana_context_type.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/expressions/kibana_context_type.ts#:~:text=IndexPatternField), [kibana_context_type.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/expressions/kibana_context_type.ts#:~:text=IndexPatternField), [search_source.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/search_source.ts#:~:text=IndexPatternField), [search_source.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/search_source.ts#:~:text=IndexPatternField), [search_source.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/search_source.ts#:~:text=IndexPatternField)+ 16 more | - |
|
||||
| <DocLink id="kibDataViewsPluginApi" section="def-common.IndexPatternField" text="IndexPatternField"/> | [mapping.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/aggs/param_types/mapping.ts#:~:text=IndexPatternField), [mapping.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/aggs/param_types/mapping.ts#:~:text=IndexPatternField), [mapping.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/aggs/param_types/mapping.ts#:~:text=IndexPatternField), [mapping.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/aggs/param_types/mapping.ts#:~:text=IndexPatternField), [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/index.ts#:~:text=IndexPatternField), [kibana_context_type.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/expressions/kibana_context_type.ts#:~:text=IndexPatternField), [kibana_context_type.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/expressions/kibana_context_type.ts#:~:text=IndexPatternField), [search_source.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/search_source.ts#:~:text=IndexPatternField), [search_source.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/search_source.ts#:~:text=IndexPatternField), [search_source.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/search_source.ts#:~:text=IndexPatternField)+ 16 more | - |
|
||||
| <DocLink id="kibDataViewsPluginApi" section="def-common.IIndexPattern" text="IIndexPattern"/> | [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/index.ts#:~:text=IIndexPattern), [get_time.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/query/timefilter/get_time.ts#:~:text=IIndexPattern), [get_time.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/query/timefilter/get_time.ts#:~:text=IIndexPattern), [get_time.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/query/timefilter/get_time.ts#:~:text=IIndexPattern), [get_time.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/query/timefilter/get_time.ts#:~:text=IIndexPattern), [normalize_sort_request.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/normalize_sort_request.ts#:~:text=IIndexPattern), [normalize_sort_request.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/normalize_sort_request.ts#:~:text=IIndexPattern), [normalize_sort_request.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/normalize_sort_request.ts#:~:text=IIndexPattern), [search_source.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/search_source.ts#:~:text=IIndexPattern), [search_source.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/search_source.ts#:~:text=IIndexPattern)+ 23 more | - |
|
||||
| <DocLink id="kibDataViewsPluginApi" section="def-common.IndexPatternAttributes" text="IndexPatternAttributes"/> | [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/index.ts#:~:text=IndexPatternAttributes), [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/public/index.ts#:~:text=IndexPatternAttributes), [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/server/index.ts#:~:text=IndexPatternAttributes) | - |
|
||||
| <DocLink id="kibDataViewsPluginApi" section="def-common.IndexPatternsContract" text="IndexPatternsContract"/> | [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/index.ts#:~:text=IndexPatternsContract), [create_search_source.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/create_search_source.ts#:~:text=IndexPatternsContract), [create_search_source.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/create_search_source.ts#:~:text=IndexPatternsContract), [search_source_service.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/search_source_service.ts#:~:text=IndexPatternsContract), [search_source_service.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/search_source_service.ts#:~:text=IndexPatternsContract), [esaggs_fn.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/expressions/esaggs/esaggs_fn.ts#:~:text=IndexPatternsContract), [esaggs_fn.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/expressions/esaggs/esaggs_fn.ts#:~:text=IndexPatternsContract), [types.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/public/search/types.ts#:~:text=IndexPatternsContract), [types.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/public/search/types.ts#:~:text=IndexPatternsContract), [create_search_source.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/create_search_source.test.ts#:~:text=IndexPatternsContract)+ 19 more | - |
|
||||
|
|
|
@ -16,10 +16,6 @@ jest.mock('../../../../contexts/editor_context/editor_registry', () => ({
|
|||
},
|
||||
}));
|
||||
jest.mock('../../../../components/editor_example', () => {});
|
||||
jest.mock('../../../../../lib/mappings/mappings', () => ({
|
||||
retrieveAutoCompleteInfo: () => {},
|
||||
clearSubscriptions: () => {},
|
||||
}));
|
||||
jest.mock('../../../../models/sense_editor', () => {
|
||||
return {
|
||||
create: () => ({
|
||||
|
|
|
@ -20,8 +20,6 @@ import { decompressFromEncodedURIComponent } from 'lz-string';
|
|||
import { parse } from 'query-string';
|
||||
import React, { CSSProperties, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { ace } from '@kbn/es-ui-shared-plugin/public';
|
||||
// @ts-ignore
|
||||
import { retrieveAutoCompleteInfo, clearSubscriptions } from '../../../../../lib/mappings/mappings';
|
||||
import { ConsoleMenu } from '../../../../components';
|
||||
import { useEditorReadContext, useServicesContext } from '../../../../contexts';
|
||||
import {
|
||||
|
@ -66,7 +64,14 @@ const inputId = 'ConAppInputTextarea';
|
|||
|
||||
function EditorUI({ initialTextValue, setEditorInstance }: EditorProps) {
|
||||
const {
|
||||
services: { history, notifications, settings: settingsService, esHostService, http },
|
||||
services: {
|
||||
history,
|
||||
notifications,
|
||||
settings: settingsService,
|
||||
esHostService,
|
||||
http,
|
||||
autocompleteInfo,
|
||||
},
|
||||
docLinkVersion,
|
||||
} = useServicesContext();
|
||||
|
||||
|
@ -196,14 +201,14 @@ function EditorUI({ initialTextValue, setEditorInstance }: EditorProps) {
|
|||
setInputEditor(editor);
|
||||
setTextArea(editorRef.current!.querySelector('textarea'));
|
||||
|
||||
retrieveAutoCompleteInfo(http, settingsService, settingsService.getAutocomplete());
|
||||
autocompleteInfo.retrieve(settingsService, settingsService.getAutocomplete());
|
||||
|
||||
const unsubscribeResizer = subscribeResizeChecker(editorRef.current!, editor);
|
||||
setupAutosave();
|
||||
|
||||
return () => {
|
||||
unsubscribeResizer();
|
||||
clearSubscriptions();
|
||||
autocompleteInfo.clearSubscriptions();
|
||||
window.removeEventListener('hashchange', onHashChange);
|
||||
if (editorInstanceRef.current) {
|
||||
editorInstanceRef.current.getCoreEditor().destroy();
|
||||
|
@ -217,6 +222,7 @@ function EditorUI({ initialTextValue, setEditorInstance }: EditorProps) {
|
|||
setInputEditor,
|
||||
settingsService,
|
||||
http,
|
||||
autocompleteInfo,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -8,11 +8,8 @@
|
|||
|
||||
import React from 'react';
|
||||
|
||||
import type { HttpSetup } from '@kbn/core/public';
|
||||
import { AutocompleteOptions, DevToolsSettingsModal } from '../components';
|
||||
|
||||
// @ts-ignore
|
||||
import { retrieveAutoCompleteInfo } from '../../lib/mappings/mappings';
|
||||
import { useServicesContext, useEditorActionContext } from '../contexts';
|
||||
import { DevToolsSettings, Settings as SettingsService } from '../../services';
|
||||
import type { SenseEditor } from '../models';
|
||||
|
@ -27,48 +24,6 @@ const getAutocompleteDiff = (
|
|||
}) as AutocompleteOptions[];
|
||||
};
|
||||
|
||||
const refreshAutocompleteSettings = (
|
||||
http: HttpSetup,
|
||||
settings: SettingsService,
|
||||
selectedSettings: DevToolsSettings['autocomplete']
|
||||
) => {
|
||||
retrieveAutoCompleteInfo(http, settings, selectedSettings);
|
||||
};
|
||||
|
||||
const fetchAutocompleteSettingsIfNeeded = (
|
||||
http: HttpSetup,
|
||||
settings: SettingsService,
|
||||
newSettings: DevToolsSettings,
|
||||
prevSettings: DevToolsSettings
|
||||
) => {
|
||||
// We'll only retrieve settings if polling is on. The expectation here is that if the user
|
||||
// disables polling it's because they want manual control over the fetch request (possibly
|
||||
// because it's a very expensive request given their cluster and bandwidth). In that case,
|
||||
// they would be unhappy with any request that's sent automatically.
|
||||
if (newSettings.polling) {
|
||||
const autocompleteDiff = getAutocompleteDiff(newSettings, prevSettings);
|
||||
|
||||
const isSettingsChanged = autocompleteDiff.length > 0;
|
||||
const isPollingChanged = prevSettings.polling !== newSettings.polling;
|
||||
|
||||
if (isSettingsChanged) {
|
||||
// If the user has changed one of the autocomplete settings, then we'll fetch just the
|
||||
// ones which have changed.
|
||||
const changedSettings: DevToolsSettings['autocomplete'] = autocompleteDiff.reduce(
|
||||
(changedSettingsAccum, setting) => {
|
||||
changedSettingsAccum[setting] = newSettings.autocomplete[setting];
|
||||
return changedSettingsAccum;
|
||||
},
|
||||
{} as DevToolsSettings['autocomplete']
|
||||
);
|
||||
retrieveAutoCompleteInfo(http, settings, changedSettings);
|
||||
} else if (isPollingChanged && newSettings.polling) {
|
||||
// If the user has turned polling on, then we'll fetch all selected autocomplete settings.
|
||||
retrieveAutoCompleteInfo(http, settings, settings.getAutocomplete());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export interface Props {
|
||||
onClose: () => void;
|
||||
editorInstance: SenseEditor | null;
|
||||
|
@ -76,14 +31,57 @@ export interface Props {
|
|||
|
||||
export function Settings({ onClose, editorInstance }: Props) {
|
||||
const {
|
||||
services: { settings, http },
|
||||
services: { settings, autocompleteInfo },
|
||||
} = useServicesContext();
|
||||
|
||||
const dispatch = useEditorActionContext();
|
||||
|
||||
const refreshAutocompleteSettings = (
|
||||
settingsService: SettingsService,
|
||||
selectedSettings: DevToolsSettings['autocomplete']
|
||||
) => {
|
||||
autocompleteInfo.retrieve(settingsService, selectedSettings);
|
||||
};
|
||||
|
||||
const fetchAutocompleteSettingsIfNeeded = (
|
||||
settingsService: SettingsService,
|
||||
newSettings: DevToolsSettings,
|
||||
prevSettings: DevToolsSettings
|
||||
) => {
|
||||
// We'll only retrieve settings if polling is on. The expectation here is that if the user
|
||||
// disables polling it's because they want manual control over the fetch request (possibly
|
||||
// because it's a very expensive request given their cluster and bandwidth). In that case,
|
||||
// they would be unhappy with any request that's sent automatically.
|
||||
if (newSettings.polling) {
|
||||
const autocompleteDiff = getAutocompleteDiff(newSettings, prevSettings);
|
||||
|
||||
const isSettingsChanged = autocompleteDiff.length > 0;
|
||||
const isPollingChanged = prevSettings.polling !== newSettings.polling;
|
||||
|
||||
if (isSettingsChanged) {
|
||||
// If the user has changed one of the autocomplete settings, then we'll fetch just the
|
||||
// ones which have changed.
|
||||
const changedSettings: DevToolsSettings['autocomplete'] = autocompleteDiff.reduce(
|
||||
(changedSettingsAccum, setting) => {
|
||||
changedSettingsAccum[setting] = newSettings.autocomplete[setting];
|
||||
return changedSettingsAccum;
|
||||
},
|
||||
{} as DevToolsSettings['autocomplete']
|
||||
);
|
||||
autocompleteInfo.retrieve(settingsService, {
|
||||
...settingsService.getAutocomplete(),
|
||||
...changedSettings,
|
||||
});
|
||||
} else if (isPollingChanged && newSettings.polling) {
|
||||
// If the user has turned polling on, then we'll fetch all selected autocomplete settings.
|
||||
autocompleteInfo.retrieve(settingsService, settingsService.getAutocomplete());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onSaveSettings = (newSettings: DevToolsSettings) => {
|
||||
const prevSettings = settings.toJSON();
|
||||
fetchAutocompleteSettingsIfNeeded(http, settings, newSettings, prevSettings);
|
||||
fetchAutocompleteSettingsIfNeeded(settings, newSettings, prevSettings);
|
||||
|
||||
// Update the new settings in localStorage
|
||||
settings.updateSettings(newSettings);
|
||||
|
@ -101,7 +99,7 @@ export function Settings({ onClose, editorInstance }: Props) {
|
|||
onClose={onClose}
|
||||
onSaveSettings={onSaveSettings}
|
||||
refreshAutocompleteSettings={(selectedSettings) =>
|
||||
refreshAutocompleteSettings(http, settings, selectedSettings)
|
||||
refreshAutocompleteSettings(settings, selectedSettings)
|
||||
}
|
||||
settings={settings.toJSON()}
|
||||
editorInstance={editorInstance}
|
||||
|
|
|
@ -17,6 +17,7 @@ import type { ObjectStorageClient } from '../../../common/types';
|
|||
import { HistoryMock } from '../../services/history.mock';
|
||||
import { SettingsMock } from '../../services/settings.mock';
|
||||
import { StorageMock } from '../../services/storage.mock';
|
||||
import { AutocompleteInfoMock } from '../../services/autocomplete.mock';
|
||||
import { createApi, createEsHostService } from '../lib';
|
||||
|
||||
import { ContextValue } from './services_context';
|
||||
|
@ -38,6 +39,7 @@ export const serviceContextMock = {
|
|||
notifications: notificationServiceMock.createSetupContract(),
|
||||
objectStorageClient: {} as unknown as ObjectStorageClient,
|
||||
http,
|
||||
autocompleteInfo: new AutocompleteInfoMock(),
|
||||
},
|
||||
docLinkVersion: 'NA',
|
||||
theme$: themeServiceMock.create().start().theme$,
|
||||
|
|
|
@ -10,7 +10,7 @@ import React, { createContext, useContext, useEffect } from 'react';
|
|||
import { Observable } from 'rxjs';
|
||||
import type { NotificationsSetup, CoreTheme, DocLinksStart, HttpSetup } from '@kbn/core/public';
|
||||
|
||||
import { History, Settings, Storage } from '../../services';
|
||||
import { AutocompleteInfo, History, Settings, Storage } from '../../services';
|
||||
import { ObjectStorageClient } from '../../../common/types';
|
||||
import { MetricsTracker } from '../../types';
|
||||
import { EsHostService } from '../lib';
|
||||
|
@ -24,6 +24,7 @@ interface ContextServices {
|
|||
trackUiMetric: MetricsTracker;
|
||||
esHostService: EsHostService;
|
||||
http: HttpSetup;
|
||||
autocompleteInfo: AutocompleteInfo;
|
||||
}
|
||||
|
||||
export interface ContextValue {
|
||||
|
|
|
@ -11,8 +11,6 @@ import { useCallback } from 'react';
|
|||
|
||||
import { toMountPoint } from '../../../shared_imports';
|
||||
import { isQuotaExceededError } from '../../../services/history';
|
||||
// @ts-ignore
|
||||
import { retrieveAutoCompleteInfo } from '../../../lib/mappings/mappings';
|
||||
import { instance as registry } from '../../contexts/editor_context/editor_registry';
|
||||
import { useRequestActionContext, useServicesContext } from '../../contexts';
|
||||
import { StorageQuotaError } from '../../components/storage_quota_error';
|
||||
|
@ -21,7 +19,7 @@ import { track } from './track';
|
|||
|
||||
export const useSendCurrentRequest = () => {
|
||||
const {
|
||||
services: { history, settings, notifications, trackUiMetric, http },
|
||||
services: { history, settings, notifications, trackUiMetric, http, autocompleteInfo },
|
||||
theme$,
|
||||
} = useServicesContext();
|
||||
|
||||
|
@ -102,7 +100,7 @@ export const useSendCurrentRequest = () => {
|
|||
// or templates may have changed, so we'll need to update this data. Assume that if
|
||||
// the user disables polling they're trying to optimize performance or otherwise
|
||||
// preserve resources, so they won't want this request sent either.
|
||||
retrieveAutoCompleteInfo(http, settings, settings.getAutocomplete());
|
||||
autocompleteInfo.retrieve(settings, settings.getAutocomplete());
|
||||
}
|
||||
|
||||
dispatch({
|
||||
|
@ -129,5 +127,14 @@ export const useSendCurrentRequest = () => {
|
|||
});
|
||||
}
|
||||
}
|
||||
}, [dispatch, http, settings, notifications.toasts, trackUiMetric, history, theme$]);
|
||||
}, [
|
||||
dispatch,
|
||||
http,
|
||||
settings,
|
||||
notifications.toasts,
|
||||
trackUiMetric,
|
||||
history,
|
||||
theme$,
|
||||
autocompleteInfo,
|
||||
]);
|
||||
};
|
||||
|
|
|
@ -19,7 +19,7 @@ import {
|
|||
|
||||
import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public';
|
||||
import { KibanaThemeProvider } from '../shared_imports';
|
||||
import { createStorage, createHistory, createSettings } from '../services';
|
||||
import { createStorage, createHistory, createSettings, AutocompleteInfo } from '../services';
|
||||
import { createUsageTracker } from '../services/tracker';
|
||||
import * as localStorageObjectClient from '../lib/local_storage_object_client';
|
||||
import { Main } from './containers';
|
||||
|
@ -35,6 +35,7 @@ export interface BootDependencies {
|
|||
element: HTMLElement;
|
||||
theme$: Observable<CoreTheme>;
|
||||
docLinks: DocLinksStart['links'];
|
||||
autocompleteInfo: AutocompleteInfo;
|
||||
}
|
||||
|
||||
export function renderApp({
|
||||
|
@ -46,6 +47,7 @@ export function renderApp({
|
|||
http,
|
||||
theme$,
|
||||
docLinks,
|
||||
autocompleteInfo,
|
||||
}: BootDependencies) {
|
||||
const trackUiMetric = createUsageTracker(usageCollection);
|
||||
trackUiMetric.load('opened_app');
|
||||
|
@ -76,6 +78,7 @@ export function renderApp({
|
|||
trackUiMetric,
|
||||
objectStorageClient,
|
||||
http,
|
||||
autocompleteInfo,
|
||||
},
|
||||
theme$,
|
||||
}}
|
||||
|
|
|
@ -12,10 +12,12 @@ import _ from 'lodash';
|
|||
import $ from 'jquery';
|
||||
|
||||
import * as kb from '../../../lib/kb/kb';
|
||||
import * as mappings from '../../../lib/mappings/mappings';
|
||||
import { AutocompleteInfo, setAutocompleteInfo } from '../../../services';
|
||||
|
||||
describe('Integration', () => {
|
||||
let senseEditor;
|
||||
let autocompleteInfo;
|
||||
|
||||
beforeEach(() => {
|
||||
// Set up our document body
|
||||
document.body.innerHTML =
|
||||
|
@ -24,10 +26,14 @@ describe('Integration', () => {
|
|||
senseEditor = create(document.querySelector('#ConAppEditor'));
|
||||
$(senseEditor.getCoreEditor().getContainer()).show();
|
||||
senseEditor.autocomplete._test.removeChangeListener();
|
||||
autocompleteInfo = new AutocompleteInfo();
|
||||
setAutocompleteInfo(autocompleteInfo);
|
||||
});
|
||||
afterEach(() => {
|
||||
$(senseEditor.getCoreEditor().getContainer()).hide();
|
||||
senseEditor.autocomplete._test.addChangeListener();
|
||||
autocompleteInfo = null;
|
||||
setAutocompleteInfo(null);
|
||||
});
|
||||
|
||||
function processContextTest(data, mapping, kbSchemes, requestLine, testToRun) {
|
||||
|
@ -45,8 +51,8 @@ describe('Integration', () => {
|
|||
|
||||
testToRun.cursor.lineNumber += lineOffset;
|
||||
|
||||
mappings.clear();
|
||||
mappings.loadMappings(mapping);
|
||||
autocompleteInfo.clear();
|
||||
autocompleteInfo.mapping.loadMappings(mapping);
|
||||
const json = {};
|
||||
json[test.name] = kbSchemes || {};
|
||||
const testApi = kb._test.loadApisFromJson(json);
|
||||
|
|
|
@ -6,12 +6,12 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { getComponentTemplates } from '../../mappings/mappings';
|
||||
import { getAutocompleteInfo } from '../../../services';
|
||||
import { ListComponent } from './list_component';
|
||||
|
||||
export class ComponentTemplateAutocompleteComponent extends ListComponent {
|
||||
constructor(name, parent) {
|
||||
super(name, getComponentTemplates, parent, true, true);
|
||||
super(name, getAutocompleteInfo().getEntityProvider('componentTemplates'), parent, true, true);
|
||||
}
|
||||
|
||||
getContextKey() {
|
||||
|
|
|
@ -6,12 +6,12 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { getDataStreams } from '../../mappings/mappings';
|
||||
import { ListComponent } from './list_component';
|
||||
import { getAutocompleteInfo } from '../../../services';
|
||||
|
||||
export class DataStreamAutocompleteComponent extends ListComponent {
|
||||
constructor(name, parent, multiValued) {
|
||||
super(name, getDataStreams, parent, multiValued);
|
||||
super(name, getAutocompleteInfo().getEntityProvider('dataStreams'), parent, multiValued);
|
||||
}
|
||||
|
||||
getContextKey() {
|
||||
|
|
|
@ -7,11 +7,11 @@
|
|||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import { getFields } from '../../mappings/mappings';
|
||||
import { getAutocompleteInfo } from '../../../services';
|
||||
import { ListComponent } from './list_component';
|
||||
|
||||
function FieldGenerator(context) {
|
||||
return _.map(getFields(context.indices, context.types), function (field) {
|
||||
return _.map(getAutocompleteInfo().getEntityProvider('fields', context), function (field) {
|
||||
return { name: field.name, meta: field.type };
|
||||
});
|
||||
}
|
||||
|
|
|
@ -7,14 +7,16 @@
|
|||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import { getIndices } from '../../mappings/mappings';
|
||||
import { getAutocompleteInfo } from '../../../services';
|
||||
import { ListComponent } from './list_component';
|
||||
|
||||
function nonValidIndexType(token) {
|
||||
return !(token === '_all' || token[0] !== '_');
|
||||
}
|
||||
|
||||
export class IndexAutocompleteComponent extends ListComponent {
|
||||
constructor(name, parent, multiValued) {
|
||||
super(name, getIndices, parent, multiValued);
|
||||
super(name, getAutocompleteInfo().getEntityProvider('indices'), parent, multiValued);
|
||||
}
|
||||
validateTokens(tokens) {
|
||||
if (!this.multiValued && tokens.length > 1) {
|
||||
|
|
|
@ -6,12 +6,12 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { getIndexTemplates } from '../../mappings/mappings';
|
||||
import { getAutocompleteInfo } from '../../../services';
|
||||
import { ListComponent } from './list_component';
|
||||
|
||||
export class IndexTemplateAutocompleteComponent extends ListComponent {
|
||||
constructor(name, parent) {
|
||||
super(name, getIndexTemplates, parent, true, true);
|
||||
super(name, getAutocompleteInfo().getEntityProvider('indexTemplates'), parent, true, true);
|
||||
}
|
||||
|
||||
getContextKey() {
|
||||
|
|
|
@ -6,12 +6,12 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { getLegacyTemplates } from '../../../mappings/mappings';
|
||||
import { getAutocompleteInfo } from '../../../../services';
|
||||
import { ListComponent } from '../list_component';
|
||||
|
||||
export class LegacyTemplateAutocompleteComponent extends ListComponent {
|
||||
constructor(name, parent) {
|
||||
super(name, getLegacyTemplates, parent, true, true);
|
||||
super(name, getAutocompleteInfo().getEntityProvider('legacyTemplates'), parent, true, true);
|
||||
}
|
||||
getContextKey() {
|
||||
return 'template';
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import _ from 'lodash';
|
||||
import { ListComponent } from './list_component';
|
||||
import { getTypes } from '../../mappings/mappings';
|
||||
import { getTypes } from '../../autocomplete_entities';
|
||||
function TypeGenerator(context) {
|
||||
return getTypes(context.indices);
|
||||
}
|
||||
|
|
|
@ -7,14 +7,16 @@
|
|||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import { getIndices } from '../../mappings/mappings';
|
||||
import { getAutocompleteInfo } from '../../../services';
|
||||
import { ListComponent } from './list_component';
|
||||
|
||||
function nonValidUsernameType(token) {
|
||||
return token[0] === '_';
|
||||
}
|
||||
|
||||
export class UsernameAutocompleteComponent extends ListComponent {
|
||||
constructor(name, parent, multiValued) {
|
||||
super(name, getIndices, parent, multiValued);
|
||||
super(name, getAutocompleteInfo().getEntityProvider('indices'), parent, multiValued);
|
||||
}
|
||||
validateTokens(tokens) {
|
||||
if (!this.multiValued && tokens.length > 1) {
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { IndicesGetAliasResponse } from '@elastic/elasticsearch/lib/api/types';
|
||||
import type { BaseMapping } from './mapping';
|
||||
|
||||
interface BaseAlias {
|
||||
getIndices(includeAliases: boolean, collaborator: BaseMapping): string[];
|
||||
loadAliases(aliases: IndicesGetAliasResponse, collaborator: BaseMapping): void;
|
||||
clearAliases(): void;
|
||||
}
|
||||
|
||||
export class Alias implements BaseAlias {
|
||||
public perAliasIndexes: Record<string, string[]> = {};
|
||||
|
||||
getIndices = (includeAliases: boolean, collaborator: BaseMapping): string[] => {
|
||||
const ret: string[] = [];
|
||||
const perIndexTypes = collaborator.perIndexTypes;
|
||||
Object.keys(perIndexTypes).forEach((index) => {
|
||||
// ignore .ds* indices in the suggested indices list.
|
||||
if (!index.startsWith('.ds')) {
|
||||
ret.push(index);
|
||||
}
|
||||
});
|
||||
|
||||
if (typeof includeAliases === 'undefined' ? true : includeAliases) {
|
||||
Object.keys(this.perAliasIndexes).forEach((alias) => {
|
||||
ret.push(alias);
|
||||
});
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
loadAliases = (aliases: IndicesGetAliasResponse, collaborator: BaseMapping) => {
|
||||
this.perAliasIndexes = {};
|
||||
const perIndexTypes = collaborator.perIndexTypes;
|
||||
|
||||
Object.entries(aliases).forEach(([index, indexAliases]) => {
|
||||
// verify we have an index defined. useful when mapping loading is disabled
|
||||
perIndexTypes[index] = perIndexTypes[index] || {};
|
||||
Object.keys(indexAliases.aliases || {}).forEach((alias) => {
|
||||
if (alias === index) {
|
||||
return;
|
||||
} // alias which is identical to index means no index.
|
||||
let curAliases = this.perAliasIndexes[alias];
|
||||
if (!curAliases) {
|
||||
curAliases = [];
|
||||
this.perAliasIndexes[alias] = curAliases;
|
||||
}
|
||||
curAliases.push(index);
|
||||
});
|
||||
});
|
||||
const includeAliases = false;
|
||||
this.perAliasIndexes._all = this.getIndices(includeAliases, collaborator);
|
||||
};
|
||||
|
||||
clearAliases = () => {
|
||||
this.perAliasIndexes = {};
|
||||
};
|
||||
}
|
|
@ -0,0 +1,315 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import '../../application/models/sense_editor/sense_editor.test.mocks';
|
||||
import { setAutocompleteInfo, AutocompleteInfo } from '../../services';
|
||||
import { expandAliases } from './expand_aliases';
|
||||
|
||||
function fc(f1, f2) {
|
||||
if (f1.name < f2.name) {
|
||||
return -1;
|
||||
}
|
||||
if (f1.name > f2.name) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function f(name, type) {
|
||||
return { name, type: type || 'string' };
|
||||
}
|
||||
|
||||
describe('Autocomplete entities', () => {
|
||||
let mapping;
|
||||
let alias;
|
||||
let legacyTemplate;
|
||||
let indexTemplate;
|
||||
let componentTemplate;
|
||||
let dataStream;
|
||||
let autocompleteInfo;
|
||||
beforeEach(() => {
|
||||
autocompleteInfo = new AutocompleteInfo();
|
||||
setAutocompleteInfo(autocompleteInfo);
|
||||
mapping = autocompleteInfo.mapping;
|
||||
alias = autocompleteInfo.alias;
|
||||
legacyTemplate = autocompleteInfo.legacyTemplate;
|
||||
indexTemplate = autocompleteInfo.indexTemplate;
|
||||
componentTemplate = autocompleteInfo.componentTemplate;
|
||||
dataStream = autocompleteInfo.dataStream;
|
||||
});
|
||||
afterEach(() => {
|
||||
autocompleteInfo.clear();
|
||||
autocompleteInfo = null;
|
||||
});
|
||||
|
||||
describe('Mappings', function () {
|
||||
test('Multi fields 1.0 style', function () {
|
||||
mapping.loadMappings({
|
||||
index: {
|
||||
properties: {
|
||||
first_name: {
|
||||
type: 'string',
|
||||
index: 'analyzed',
|
||||
path: 'just_name',
|
||||
fields: {
|
||||
any_name: { type: 'string', index: 'analyzed' },
|
||||
},
|
||||
},
|
||||
last_name: {
|
||||
type: 'string',
|
||||
index: 'no',
|
||||
fields: {
|
||||
raw: { type: 'string', index: 'analyzed' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(mapping.getMappings('index').sort(fc)).toEqual([
|
||||
f('any_name', 'string'),
|
||||
f('first_name', 'string'),
|
||||
f('last_name', 'string'),
|
||||
f('last_name.raw', 'string'),
|
||||
]);
|
||||
});
|
||||
|
||||
test('Simple fields', function () {
|
||||
mapping.loadMappings({
|
||||
index: {
|
||||
properties: {
|
||||
str: {
|
||||
type: 'string',
|
||||
},
|
||||
number: {
|
||||
type: 'int',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(mapping.getMappings('index').sort(fc)).toEqual([
|
||||
f('number', 'int'),
|
||||
f('str', 'string'),
|
||||
]);
|
||||
});
|
||||
|
||||
test('Simple fields - 1.0 style', function () {
|
||||
mapping.loadMappings({
|
||||
index: {
|
||||
mappings: {
|
||||
properties: {
|
||||
str: {
|
||||
type: 'string',
|
||||
},
|
||||
number: {
|
||||
type: 'int',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(mapping.getMappings('index').sort(fc)).toEqual([
|
||||
f('number', 'int'),
|
||||
f('str', 'string'),
|
||||
]);
|
||||
});
|
||||
|
||||
test('Nested fields', function () {
|
||||
mapping.loadMappings({
|
||||
index: {
|
||||
properties: {
|
||||
person: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: {
|
||||
properties: {
|
||||
first_name: { type: 'string' },
|
||||
last_name: { type: 'string' },
|
||||
},
|
||||
},
|
||||
sid: { type: 'string', index: 'not_analyzed' },
|
||||
},
|
||||
},
|
||||
message: { type: 'string' },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(mapping.getMappings('index', []).sort(fc)).toEqual([
|
||||
f('message'),
|
||||
f('person.name.first_name'),
|
||||
f('person.name.last_name'),
|
||||
f('person.sid'),
|
||||
]);
|
||||
});
|
||||
|
||||
test('Enabled fields', function () {
|
||||
mapping.loadMappings({
|
||||
index: {
|
||||
properties: {
|
||||
person: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: {
|
||||
type: 'object',
|
||||
enabled: false,
|
||||
},
|
||||
sid: { type: 'string', index: 'not_analyzed' },
|
||||
},
|
||||
},
|
||||
message: { type: 'string' },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(mapping.getMappings('index', []).sort(fc)).toEqual([f('message'), f('person.sid')]);
|
||||
});
|
||||
|
||||
test('Path tests', function () {
|
||||
mapping.loadMappings({
|
||||
index: {
|
||||
properties: {
|
||||
name1: {
|
||||
type: 'object',
|
||||
path: 'just_name',
|
||||
properties: {
|
||||
first1: { type: 'string' },
|
||||
last1: { type: 'string', index_name: 'i_last_1' },
|
||||
},
|
||||
},
|
||||
name2: {
|
||||
type: 'object',
|
||||
path: 'full',
|
||||
properties: {
|
||||
first2: { type: 'string' },
|
||||
last2: { type: 'string', index_name: 'i_last_2' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(mapping.getMappings().sort(fc)).toEqual([
|
||||
f('first1'),
|
||||
f('i_last_1'),
|
||||
f('name2.first2'),
|
||||
f('name2.i_last_2'),
|
||||
]);
|
||||
});
|
||||
|
||||
test('Use index_name tests', function () {
|
||||
mapping.loadMappings({
|
||||
index: {
|
||||
properties: {
|
||||
last1: { type: 'string', index_name: 'i_last_1' },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(mapping.getMappings().sort(fc)).toEqual([f('i_last_1')]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Aliases', function () {
|
||||
test('Aliases', function () {
|
||||
alias.loadAliases(
|
||||
{
|
||||
test_index1: {
|
||||
aliases: {
|
||||
alias1: {},
|
||||
},
|
||||
},
|
||||
test_index2: {
|
||||
aliases: {
|
||||
alias2: {
|
||||
filter: {
|
||||
term: {
|
||||
FIELD: 'VALUE',
|
||||
},
|
||||
},
|
||||
},
|
||||
alias1: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
mapping
|
||||
);
|
||||
mapping.loadMappings({
|
||||
test_index1: {
|
||||
properties: {
|
||||
last1: { type: 'string', index_name: 'i_last_1' },
|
||||
},
|
||||
},
|
||||
test_index2: {
|
||||
properties: {
|
||||
last1: { type: 'string', index_name: 'i_last_1' },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(alias.getIndices(true, mapping).sort()).toEqual([
|
||||
'_all',
|
||||
'alias1',
|
||||
'alias2',
|
||||
'test_index1',
|
||||
'test_index2',
|
||||
]);
|
||||
expect(alias.getIndices(false, mapping).sort()).toEqual(['test_index1', 'test_index2']);
|
||||
expect(expandAliases(['alias1', 'test_index2']).sort()).toEqual([
|
||||
'test_index1',
|
||||
'test_index2',
|
||||
]);
|
||||
expect(expandAliases('alias2')).toEqual('test_index2');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Templates', function () {
|
||||
test('legacy templates, index templates, component templates', function () {
|
||||
legacyTemplate.loadTemplates({
|
||||
test_index1: { order: 0 },
|
||||
test_index2: { order: 0 },
|
||||
test_index3: { order: 0 },
|
||||
});
|
||||
|
||||
indexTemplate.loadTemplates({
|
||||
index_templates: [
|
||||
{ name: 'test_index1' },
|
||||
{ name: 'test_index2' },
|
||||
{ name: 'test_index3' },
|
||||
],
|
||||
});
|
||||
|
||||
componentTemplate.loadTemplates({
|
||||
component_templates: [
|
||||
{ name: 'test_index1' },
|
||||
{ name: 'test_index2' },
|
||||
{ name: 'test_index3' },
|
||||
],
|
||||
});
|
||||
|
||||
const expectedResult = ['test_index1', 'test_index2', 'test_index3'];
|
||||
|
||||
expect(legacyTemplate.getTemplates()).toEqual(expectedResult);
|
||||
expect(indexTemplate.getTemplates()).toEqual(expectedResult);
|
||||
expect(componentTemplate.getTemplates()).toEqual(expectedResult);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Data streams', function () {
|
||||
test('data streams', function () {
|
||||
dataStream.loadDataStreams({
|
||||
data_streams: [{ name: 'test_index1' }, { name: 'test_index2' }, { name: 'test_index3' }],
|
||||
});
|
||||
|
||||
const expectedResult = ['test_index1', 'test_index2', 'test_index3'];
|
||||
expect(dataStream.getDataStreams()).toEqual(expectedResult);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export abstract class BaseTemplate<T> {
|
||||
protected templates: string[] = [];
|
||||
|
||||
public abstract loadTemplates(templates: T): void;
|
||||
|
||||
public getTemplates = (): string[] => {
|
||||
return [...this.templates];
|
||||
};
|
||||
|
||||
public clearTemplates = () => {
|
||||
this.templates = [];
|
||||
};
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { ClusterGetComponentTemplateResponse } from '@elastic/elasticsearch/lib/api/types';
|
||||
import { BaseTemplate } from './base_template';
|
||||
|
||||
export class ComponentTemplate extends BaseTemplate<ClusterGetComponentTemplateResponse> {
|
||||
loadTemplates = (templates: ClusterGetComponentTemplateResponse) => {
|
||||
this.templates = (templates.component_templates ?? []).map(({ name }) => name).sort();
|
||||
};
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { IndicesGetDataStreamResponse } from '@elastic/elasticsearch/lib/api/types';
|
||||
|
||||
export class DataStream {
|
||||
private dataStreams: string[] = [];
|
||||
|
||||
getDataStreams = (): string[] => {
|
||||
return [...this.dataStreams];
|
||||
};
|
||||
|
||||
loadDataStreams = (dataStreams: IndicesGetDataStreamResponse) => {
|
||||
this.dataStreams = (dataStreams.data_streams ?? []).map(({ name }) => name).sort();
|
||||
};
|
||||
|
||||
clearDataStreams = () => {
|
||||
this.dataStreams = [];
|
||||
};
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { getAutocompleteInfo } from '../../services';
|
||||
|
||||
export function expandAliases(indicesOrAliases: string | string[]) {
|
||||
// takes a list of indices or aliases or a string which may be either and returns a list of indices
|
||||
// returns a list for multiple values or a string for a single.
|
||||
const perAliasIndexes = getAutocompleteInfo().alias.perAliasIndexes;
|
||||
if (!indicesOrAliases) {
|
||||
return indicesOrAliases;
|
||||
}
|
||||
|
||||
if (typeof indicesOrAliases === 'string') {
|
||||
indicesOrAliases = [indicesOrAliases];
|
||||
}
|
||||
|
||||
indicesOrAliases = indicesOrAliases.flatMap((iOrA) => {
|
||||
if (perAliasIndexes[iOrA]) {
|
||||
return perAliasIndexes[iOrA];
|
||||
}
|
||||
return [iOrA];
|
||||
});
|
||||
|
||||
let ret = ([] as string[]).concat.apply([], indicesOrAliases);
|
||||
ret.sort();
|
||||
ret = ret.reduce((result, value, index, array) => {
|
||||
const last = array[index - 1];
|
||||
if (last !== value) {
|
||||
result.push(value);
|
||||
}
|
||||
return result;
|
||||
}, [] as string[]);
|
||||
|
||||
return ret.length > 1 ? ret : ret[0];
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export { Alias } from './alias';
|
||||
export { Mapping } from './mapping';
|
||||
export { DataStream } from './data_stream';
|
||||
export { LegacyTemplate } from './legacy';
|
||||
export { IndexTemplate } from './index_template';
|
||||
export { ComponentTemplate } from './component_template';
|
||||
export { getTypes } from './type';
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { IndicesGetIndexTemplateResponse } from '@elastic/elasticsearch/lib/api/types';
|
||||
import { BaseTemplate } from './base_template';
|
||||
|
||||
export class IndexTemplate extends BaseTemplate<IndicesGetIndexTemplateResponse> {
|
||||
loadTemplates = (templates: IndicesGetIndexTemplateResponse) => {
|
||||
this.templates = (templates.index_templates ?? []).map(({ name }) => name).sort();
|
||||
};
|
||||
}
|
|
@ -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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export { LegacyTemplate } from './legacy_template';
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { IndicesGetTemplateResponse } from '@elastic/elasticsearch/lib/api/types';
|
||||
import { BaseTemplate } from '../base_template';
|
||||
|
||||
export class LegacyTemplate extends BaseTemplate<IndicesGetTemplateResponse> {
|
||||
loadTemplates = (templates: IndicesGetTemplateResponse) => {
|
||||
this.templates = Object.keys(templates).sort();
|
||||
};
|
||||
}
|
164
src/plugins/console/public/lib/autocomplete_entities/mapping.ts
Normal file
164
src/plugins/console/public/lib/autocomplete_entities/mapping.ts
Normal file
|
@ -0,0 +1,164 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import type { IndicesGetMappingResponse } from '@elastic/elasticsearch/lib/api/types';
|
||||
import { expandAliases } from './expand_aliases';
|
||||
import type { Field, FieldMapping } from './types';
|
||||
|
||||
function getFieldNamesFromProperties(properties: Record<string, FieldMapping> = {}) {
|
||||
const fieldList = Object.entries(properties).flatMap(([fieldName, fieldMapping]) => {
|
||||
return getFieldNamesFromFieldMapping(fieldName, fieldMapping);
|
||||
});
|
||||
|
||||
// deduping
|
||||
return _.uniqBy(fieldList, function (f) {
|
||||
return f.name + ':' + f.type;
|
||||
});
|
||||
}
|
||||
|
||||
function getFieldNamesFromFieldMapping(
|
||||
fieldName: string,
|
||||
fieldMapping: FieldMapping
|
||||
): Array<{ name: string; type: string | undefined }> {
|
||||
if (fieldMapping.enabled === false) {
|
||||
return [];
|
||||
}
|
||||
let nestedFields;
|
||||
|
||||
function applyPathSettings(nestedFieldNames: Array<{ name: string; type: string | undefined }>) {
|
||||
const pathType = fieldMapping.path || 'full';
|
||||
if (pathType === 'full') {
|
||||
return nestedFieldNames.map((f) => {
|
||||
f.name = fieldName + '.' + f.name;
|
||||
return f;
|
||||
});
|
||||
}
|
||||
return nestedFieldNames;
|
||||
}
|
||||
|
||||
if (fieldMapping.properties) {
|
||||
// derived object type
|
||||
nestedFields = getFieldNamesFromProperties(fieldMapping.properties);
|
||||
return applyPathSettings(nestedFields);
|
||||
}
|
||||
|
||||
const fieldType = fieldMapping.type;
|
||||
|
||||
const ret = { name: fieldName, type: fieldType };
|
||||
|
||||
if (fieldMapping.index_name) {
|
||||
ret.name = fieldMapping.index_name;
|
||||
}
|
||||
|
||||
if (fieldMapping.fields) {
|
||||
nestedFields = Object.entries(fieldMapping.fields).flatMap(([name, mapping]) => {
|
||||
return getFieldNamesFromFieldMapping(name, mapping);
|
||||
});
|
||||
nestedFields = applyPathSettings(nestedFields);
|
||||
nestedFields.unshift(ret);
|
||||
return nestedFields;
|
||||
}
|
||||
|
||||
return [ret];
|
||||
}
|
||||
|
||||
export interface BaseMapping {
|
||||
perIndexTypes: Record<string, object>;
|
||||
getMappings(indices: string | string[], types?: string | string[]): Field[];
|
||||
loadMappings(mappings: IndicesGetMappingResponse): void;
|
||||
clearMappings(): void;
|
||||
}
|
||||
|
||||
export class Mapping implements BaseMapping {
|
||||
public perIndexTypes: Record<string, object> = {};
|
||||
|
||||
getMappings = (indices: string | string[], types?: string | string[]) => {
|
||||
// get fields for indices and types. Both can be a list, a string or null (meaning all).
|
||||
let ret: Field[] = [];
|
||||
indices = expandAliases(indices);
|
||||
|
||||
if (typeof indices === 'string') {
|
||||
const typeDict = this.perIndexTypes[indices] as Record<string, unknown>;
|
||||
if (!typeDict) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (typeof types === 'string') {
|
||||
const f = typeDict[types];
|
||||
if (Array.isArray(f)) {
|
||||
ret = f;
|
||||
}
|
||||
} else {
|
||||
// filter what we need
|
||||
Object.entries(typeDict).forEach(([type, fields]) => {
|
||||
if (!types || types.length === 0 || types.includes(type)) {
|
||||
ret.push(fields as Field);
|
||||
}
|
||||
});
|
||||
|
||||
ret = ([] as Field[]).concat.apply([], ret);
|
||||
}
|
||||
} else {
|
||||
// multi index mode.
|
||||
Object.keys(this.perIndexTypes).forEach((index) => {
|
||||
if (!indices || indices.length === 0 || indices.includes(index)) {
|
||||
ret.push(this.getMappings(index, types) as unknown as Field);
|
||||
}
|
||||
});
|
||||
|
||||
ret = ([] as Field[]).concat.apply([], ret);
|
||||
}
|
||||
|
||||
return _.uniqBy(ret, function (f) {
|
||||
return f.name + ':' + f.type;
|
||||
});
|
||||
};
|
||||
|
||||
loadMappings = (mappings: IndicesGetMappingResponse) => {
|
||||
const maxMappingSize = Object.keys(mappings).length > 10 * 1024 * 1024;
|
||||
let mappingsResponse;
|
||||
if (maxMappingSize) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(
|
||||
`Mapping size is larger than 10MB (${
|
||||
Object.keys(mappings).length / 1024 / 1024
|
||||
} MB). Ignoring...`
|
||||
);
|
||||
mappingsResponse = {};
|
||||
} else {
|
||||
mappingsResponse = mappings;
|
||||
}
|
||||
|
||||
this.perIndexTypes = {};
|
||||
|
||||
Object.entries(mappingsResponse).forEach(([index, indexMapping]) => {
|
||||
const normalizedIndexMappings: Record<string, object[]> = {};
|
||||
let transformedMapping: Record<string, any> = indexMapping;
|
||||
|
||||
// Migrate 1.0.0 mappings. This format has changed, so we need to extract the underlying mapping.
|
||||
if (indexMapping.mappings && Object.keys(indexMapping).length === 1) {
|
||||
transformedMapping = indexMapping.mappings;
|
||||
}
|
||||
|
||||
Object.entries(transformedMapping).forEach(([typeName, typeMapping]) => {
|
||||
if (typeName === 'properties') {
|
||||
const fieldList = getFieldNamesFromProperties(typeMapping);
|
||||
normalizedIndexMappings[typeName] = fieldList;
|
||||
} else {
|
||||
normalizedIndexMappings[typeName] = [];
|
||||
}
|
||||
});
|
||||
this.perIndexTypes[index] = normalizedIndexMappings;
|
||||
});
|
||||
};
|
||||
|
||||
clearMappings = () => {
|
||||
this.perIndexTypes = {};
|
||||
};
|
||||
}
|
44
src/plugins/console/public/lib/autocomplete_entities/type.ts
Normal file
44
src/plugins/console/public/lib/autocomplete_entities/type.ts
Normal file
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import { getAutocompleteInfo } from '../../services';
|
||||
import { expandAliases } from './expand_aliases';
|
||||
|
||||
export function getTypes(indices: string | string[]) {
|
||||
let ret: string[] = [];
|
||||
const perIndexTypes = getAutocompleteInfo().mapping.perIndexTypes;
|
||||
indices = expandAliases(indices);
|
||||
if (typeof indices === 'string') {
|
||||
const typeDict = perIndexTypes[indices];
|
||||
if (!typeDict) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// filter what we need
|
||||
if (Array.isArray(typeDict)) {
|
||||
typeDict.forEach((type) => {
|
||||
ret.push(type);
|
||||
});
|
||||
} else if (typeof typeDict === 'object') {
|
||||
Object.keys(typeDict).forEach((type) => {
|
||||
ret.push(type);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// multi index mode.
|
||||
Object.keys(perIndexTypes).forEach((index) => {
|
||||
if (!indices || indices.includes(index)) {
|
||||
ret.push(getTypes(index) as unknown as string);
|
||||
}
|
||||
});
|
||||
ret = ([] as string[]).concat.apply([], ret);
|
||||
}
|
||||
|
||||
return _.uniq(ret);
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type {
|
||||
ClusterGetComponentTemplateResponse,
|
||||
IndicesGetAliasResponse,
|
||||
IndicesGetDataStreamResponse,
|
||||
IndicesGetIndexTemplateResponse,
|
||||
IndicesGetMappingResponse,
|
||||
IndicesGetTemplateResponse,
|
||||
} from '@elastic/elasticsearch/lib/api/types';
|
||||
|
||||
export interface Field {
|
||||
name: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface FieldMapping {
|
||||
enabled?: boolean;
|
||||
path?: string;
|
||||
properties?: Record<string, FieldMapping>;
|
||||
type?: string;
|
||||
index_name?: string;
|
||||
fields?: FieldMapping[];
|
||||
}
|
||||
|
||||
export interface MappingsApiResponse {
|
||||
mappings: IndicesGetMappingResponse;
|
||||
aliases: IndicesGetAliasResponse;
|
||||
dataStreams: IndicesGetDataStreamResponse;
|
||||
legacyTemplates: IndicesGetTemplateResponse;
|
||||
indexTemplates: IndicesGetIndexTemplateResponse;
|
||||
componentTemplates: ClusterGetComponentTemplateResponse;
|
||||
}
|
|
@ -11,16 +11,20 @@ import { populateContext } from '../autocomplete/engine';
|
|||
|
||||
import '../../application/models/sense_editor/sense_editor.test.mocks';
|
||||
import * as kb from '.';
|
||||
import * as mappings from '../mappings/mappings';
|
||||
import { AutocompleteInfo, setAutocompleteInfo } from '../../services';
|
||||
|
||||
describe('Knowledge base', () => {
|
||||
let autocompleteInfo;
|
||||
beforeEach(() => {
|
||||
mappings.clear();
|
||||
kb.setActiveApi(kb._test.loadApisFromJson({}));
|
||||
autocompleteInfo = new AutocompleteInfo();
|
||||
setAutocompleteInfo(autocompleteInfo);
|
||||
autocompleteInfo.mapping.clearMappings();
|
||||
});
|
||||
afterEach(() => {
|
||||
mappings.clear();
|
||||
kb.setActiveApi(kb._test.loadApisFromJson({}));
|
||||
autocompleteInfo = null;
|
||||
setAutocompleteInfo(null);
|
||||
});
|
||||
|
||||
const MAPPING = {
|
||||
|
@ -122,7 +126,7 @@ describe('Knowledge base', () => {
|
|||
|
||||
kb.setActiveApi(testApi);
|
||||
|
||||
mappings.loadMappings(MAPPING);
|
||||
autocompleteInfo.mapping.loadMappings(MAPPING);
|
||||
testUrlContext(tokenPath, otherTokenValues, expectedContext);
|
||||
});
|
||||
}
|
||||
|
@ -165,7 +169,7 @@ describe('Knowledge base', () => {
|
|||
);
|
||||
kb.setActiveApi(testApi);
|
||||
|
||||
mappings.loadMappings(MAPPING);
|
||||
autocompleteInfo.mapping.loadMappings(MAPPING);
|
||||
|
||||
testUrlContext(tokenPath, otherTokenValues, expectedContext);
|
||||
});
|
||||
|
|
|
@ -1,278 +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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import '../../application/models/sense_editor/sense_editor.test.mocks';
|
||||
import * as mappings from './mappings';
|
||||
|
||||
describe('Mappings', () => {
|
||||
beforeEach(() => {
|
||||
mappings.clear();
|
||||
});
|
||||
afterEach(() => {
|
||||
mappings.clear();
|
||||
});
|
||||
|
||||
function fc(f1, f2) {
|
||||
if (f1.name < f2.name) {
|
||||
return -1;
|
||||
}
|
||||
if (f1.name > f2.name) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function f(name, type) {
|
||||
return { name: name, type: type || 'string' };
|
||||
}
|
||||
|
||||
test('Multi fields 1.0 style', function () {
|
||||
mappings.loadMappings({
|
||||
index: {
|
||||
properties: {
|
||||
first_name: {
|
||||
type: 'string',
|
||||
index: 'analyzed',
|
||||
path: 'just_name',
|
||||
fields: {
|
||||
any_name: { type: 'string', index: 'analyzed' },
|
||||
},
|
||||
},
|
||||
last_name: {
|
||||
type: 'string',
|
||||
index: 'no',
|
||||
fields: {
|
||||
raw: { type: 'string', index: 'analyzed' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(mappings.getFields('index').sort(fc)).toEqual([
|
||||
f('any_name', 'string'),
|
||||
f('first_name', 'string'),
|
||||
f('last_name', 'string'),
|
||||
f('last_name.raw', 'string'),
|
||||
]);
|
||||
});
|
||||
|
||||
test('Simple fields', function () {
|
||||
mappings.loadMappings({
|
||||
index: {
|
||||
properties: {
|
||||
str: {
|
||||
type: 'string',
|
||||
},
|
||||
number: {
|
||||
type: 'int',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(mappings.getFields('index').sort(fc)).toEqual([f('number', 'int'), f('str', 'string')]);
|
||||
});
|
||||
|
||||
test('Simple fields - 1.0 style', function () {
|
||||
mappings.loadMappings({
|
||||
index: {
|
||||
mappings: {
|
||||
properties: {
|
||||
str: {
|
||||
type: 'string',
|
||||
},
|
||||
number: {
|
||||
type: 'int',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(mappings.getFields('index').sort(fc)).toEqual([f('number', 'int'), f('str', 'string')]);
|
||||
});
|
||||
|
||||
test('Nested fields', function () {
|
||||
mappings.loadMappings({
|
||||
index: {
|
||||
properties: {
|
||||
person: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: {
|
||||
properties: {
|
||||
first_name: { type: 'string' },
|
||||
last_name: { type: 'string' },
|
||||
},
|
||||
},
|
||||
sid: { type: 'string', index: 'not_analyzed' },
|
||||
},
|
||||
},
|
||||
message: { type: 'string' },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(mappings.getFields('index', []).sort(fc)).toEqual([
|
||||
f('message'),
|
||||
f('person.name.first_name'),
|
||||
f('person.name.last_name'),
|
||||
f('person.sid'),
|
||||
]);
|
||||
});
|
||||
|
||||
test('Enabled fields', function () {
|
||||
mappings.loadMappings({
|
||||
index: {
|
||||
properties: {
|
||||
person: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: {
|
||||
type: 'object',
|
||||
enabled: false,
|
||||
},
|
||||
sid: { type: 'string', index: 'not_analyzed' },
|
||||
},
|
||||
},
|
||||
message: { type: 'string' },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(mappings.getFields('index', []).sort(fc)).toEqual([f('message'), f('person.sid')]);
|
||||
});
|
||||
|
||||
test('Path tests', function () {
|
||||
mappings.loadMappings({
|
||||
index: {
|
||||
properties: {
|
||||
name1: {
|
||||
type: 'object',
|
||||
path: 'just_name',
|
||||
properties: {
|
||||
first1: { type: 'string' },
|
||||
last1: { type: 'string', index_name: 'i_last_1' },
|
||||
},
|
||||
},
|
||||
name2: {
|
||||
type: 'object',
|
||||
path: 'full',
|
||||
properties: {
|
||||
first2: { type: 'string' },
|
||||
last2: { type: 'string', index_name: 'i_last_2' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(mappings.getFields().sort(fc)).toEqual([
|
||||
f('first1'),
|
||||
f('i_last_1'),
|
||||
f('name2.first2'),
|
||||
f('name2.i_last_2'),
|
||||
]);
|
||||
});
|
||||
|
||||
test('Use index_name tests', function () {
|
||||
mappings.loadMappings({
|
||||
index: {
|
||||
properties: {
|
||||
last1: { type: 'string', index_name: 'i_last_1' },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(mappings.getFields().sort(fc)).toEqual([f('i_last_1')]);
|
||||
});
|
||||
|
||||
test('Aliases', function () {
|
||||
mappings.loadAliases({
|
||||
test_index1: {
|
||||
aliases: {
|
||||
alias1: {},
|
||||
},
|
||||
},
|
||||
test_index2: {
|
||||
aliases: {
|
||||
alias2: {
|
||||
filter: {
|
||||
term: {
|
||||
FIELD: 'VALUE',
|
||||
},
|
||||
},
|
||||
},
|
||||
alias1: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
mappings.loadMappings({
|
||||
test_index1: {
|
||||
properties: {
|
||||
last1: { type: 'string', index_name: 'i_last_1' },
|
||||
},
|
||||
},
|
||||
test_index2: {
|
||||
properties: {
|
||||
last1: { type: 'string', index_name: 'i_last_1' },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(mappings.getIndices().sort()).toEqual([
|
||||
'_all',
|
||||
'alias1',
|
||||
'alias2',
|
||||
'test_index1',
|
||||
'test_index2',
|
||||
]);
|
||||
expect(mappings.getIndices(false).sort()).toEqual(['test_index1', 'test_index2']);
|
||||
expect(mappings.expandAliases(['alias1', 'test_index2']).sort()).toEqual([
|
||||
'test_index1',
|
||||
'test_index2',
|
||||
]);
|
||||
expect(mappings.expandAliases('alias2')).toEqual('test_index2');
|
||||
});
|
||||
|
||||
test('Templates', function () {
|
||||
mappings.loadLegacyTemplates({
|
||||
test_index1: { order: 0 },
|
||||
test_index2: { order: 0 },
|
||||
test_index3: { order: 0 },
|
||||
});
|
||||
|
||||
mappings.loadIndexTemplates({
|
||||
index_templates: [{ name: 'test_index1' }, { name: 'test_index2' }, { name: 'test_index3' }],
|
||||
});
|
||||
|
||||
mappings.loadComponentTemplates({
|
||||
component_templates: [
|
||||
{ name: 'test_index1' },
|
||||
{ name: 'test_index2' },
|
||||
{ name: 'test_index3' },
|
||||
],
|
||||
});
|
||||
|
||||
const expectedResult = ['test_index1', 'test_index2', 'test_index3'];
|
||||
|
||||
expect(mappings.getLegacyTemplates()).toEqual(expectedResult);
|
||||
expect(mappings.getIndexTemplates()).toEqual(expectedResult);
|
||||
expect(mappings.getComponentTemplates()).toEqual(expectedResult);
|
||||
});
|
||||
|
||||
test('Data streams', function () {
|
||||
mappings.loadDataStreams({
|
||||
data_streams: [{ name: 'test_index1' }, { name: 'test_index2' }, { name: 'test_index3' }],
|
||||
});
|
||||
|
||||
const expectedResult = ['test_index1', 'test_index2', 'test_index3'];
|
||||
expect(mappings.getDataStreams()).toEqual(expectedResult);
|
||||
});
|
||||
});
|
|
@ -1,410 +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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import * as es from '../es/es';
|
||||
|
||||
let pollTimeoutId;
|
||||
|
||||
let perIndexTypes = {};
|
||||
let perAliasIndexes = {};
|
||||
let legacyTemplates = [];
|
||||
let indexTemplates = [];
|
||||
let componentTemplates = [];
|
||||
let dataStreams = [];
|
||||
|
||||
export function expandAliases(indicesOrAliases) {
|
||||
// takes a list of indices or aliases or a string which may be either and returns a list of indices
|
||||
// returns a list for multiple values or a string for a single.
|
||||
|
||||
if (!indicesOrAliases) {
|
||||
return indicesOrAliases;
|
||||
}
|
||||
|
||||
if (typeof indicesOrAliases === 'string') {
|
||||
indicesOrAliases = [indicesOrAliases];
|
||||
}
|
||||
|
||||
indicesOrAliases = indicesOrAliases.map((iOrA) => {
|
||||
if (perAliasIndexes[iOrA]) {
|
||||
return perAliasIndexes[iOrA];
|
||||
}
|
||||
return [iOrA];
|
||||
});
|
||||
let ret = [].concat.apply([], indicesOrAliases);
|
||||
ret.sort();
|
||||
ret = ret.reduce((result, value, index, array) => {
|
||||
const last = array[index - 1];
|
||||
if (last !== value) {
|
||||
result.push(value);
|
||||
}
|
||||
return result;
|
||||
}, []);
|
||||
|
||||
return ret.length > 1 ? ret : ret[0];
|
||||
}
|
||||
|
||||
export function getLegacyTemplates() {
|
||||
return [...legacyTemplates];
|
||||
}
|
||||
|
||||
export function getIndexTemplates() {
|
||||
return [...indexTemplates];
|
||||
}
|
||||
|
||||
export function getComponentTemplates() {
|
||||
return [...componentTemplates];
|
||||
}
|
||||
|
||||
export function getDataStreams() {
|
||||
return [...dataStreams];
|
||||
}
|
||||
|
||||
export function getFields(indices, types) {
|
||||
// get fields for indices and types. Both can be a list, a string or null (meaning all).
|
||||
let ret = [];
|
||||
indices = expandAliases(indices);
|
||||
|
||||
if (typeof indices === 'string') {
|
||||
const typeDict = perIndexTypes[indices];
|
||||
if (!typeDict) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (typeof types === 'string') {
|
||||
const f = typeDict[types];
|
||||
ret = f ? f : [];
|
||||
} else {
|
||||
// filter what we need
|
||||
Object.entries(typeDict).forEach(([type, fields]) => {
|
||||
if (!types || types.length === 0 || types.includes(type)) {
|
||||
ret.push(fields);
|
||||
}
|
||||
});
|
||||
|
||||
ret = [].concat.apply([], ret);
|
||||
}
|
||||
} else {
|
||||
// multi index mode.
|
||||
Object.keys(perIndexTypes).forEach((index) => {
|
||||
if (!indices || indices.length === 0 || indices.includes(index)) {
|
||||
ret.push(getFields(index, types));
|
||||
}
|
||||
});
|
||||
|
||||
ret = [].concat.apply([], ret);
|
||||
}
|
||||
|
||||
return _.uniqBy(ret, function (f) {
|
||||
return f.name + ':' + f.type;
|
||||
});
|
||||
}
|
||||
|
||||
export function getTypes(indices) {
|
||||
let ret = [];
|
||||
indices = expandAliases(indices);
|
||||
if (typeof indices === 'string') {
|
||||
const typeDict = perIndexTypes[indices];
|
||||
if (!typeDict) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// filter what we need
|
||||
if (Array.isArray(typeDict)) {
|
||||
typeDict.forEach((type) => {
|
||||
ret.push(type);
|
||||
});
|
||||
} else if (typeof typeDict === 'object') {
|
||||
Object.keys(typeDict).forEach((type) => {
|
||||
ret.push(type);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// multi index mode.
|
||||
Object.keys(perIndexTypes).forEach((index) => {
|
||||
if (!indices || indices.includes(index)) {
|
||||
ret.push(getTypes(index));
|
||||
}
|
||||
});
|
||||
ret = [].concat.apply([], ret);
|
||||
}
|
||||
|
||||
return _.uniq(ret);
|
||||
}
|
||||
|
||||
export function getIndices(includeAliases) {
|
||||
const ret = [];
|
||||
Object.keys(perIndexTypes).forEach((index) => {
|
||||
// ignore .ds* indices in the suggested indices list.
|
||||
if (!index.startsWith('.ds')) {
|
||||
ret.push(index);
|
||||
}
|
||||
});
|
||||
|
||||
if (typeof includeAliases === 'undefined' ? true : includeAliases) {
|
||||
Object.keys(perAliasIndexes).forEach((alias) => {
|
||||
ret.push(alias);
|
||||
});
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
function getFieldNamesFromFieldMapping(fieldName, fieldMapping) {
|
||||
if (fieldMapping.enabled === false) {
|
||||
return [];
|
||||
}
|
||||
let nestedFields;
|
||||
|
||||
function applyPathSettings(nestedFieldNames) {
|
||||
const pathType = fieldMapping.path || 'full';
|
||||
if (pathType === 'full') {
|
||||
return nestedFieldNames.map((f) => {
|
||||
f.name = fieldName + '.' + f.name;
|
||||
return f;
|
||||
});
|
||||
}
|
||||
return nestedFieldNames;
|
||||
}
|
||||
|
||||
if (fieldMapping.properties) {
|
||||
// derived object type
|
||||
nestedFields = getFieldNamesFromProperties(fieldMapping.properties);
|
||||
return applyPathSettings(nestedFields);
|
||||
}
|
||||
|
||||
const fieldType = fieldMapping.type;
|
||||
|
||||
const ret = { name: fieldName, type: fieldType };
|
||||
|
||||
if (fieldMapping.index_name) {
|
||||
ret.name = fieldMapping.index_name;
|
||||
}
|
||||
|
||||
if (fieldMapping.fields) {
|
||||
nestedFields = Object.entries(fieldMapping.fields).flatMap(([fieldName, fieldMapping]) => {
|
||||
return getFieldNamesFromFieldMapping(fieldName, fieldMapping);
|
||||
});
|
||||
nestedFields = applyPathSettings(nestedFields);
|
||||
nestedFields.unshift(ret);
|
||||
return nestedFields;
|
||||
}
|
||||
|
||||
return [ret];
|
||||
}
|
||||
|
||||
function getFieldNamesFromProperties(properties = {}) {
|
||||
const fieldList = Object.entries(properties).flatMap(([fieldName, fieldMapping]) => {
|
||||
return getFieldNamesFromFieldMapping(fieldName, fieldMapping);
|
||||
});
|
||||
|
||||
// deduping
|
||||
return _.uniqBy(fieldList, function (f) {
|
||||
return f.name + ':' + f.type;
|
||||
});
|
||||
}
|
||||
|
||||
export function loadLegacyTemplates(templatesObject = {}) {
|
||||
legacyTemplates = Object.keys(templatesObject);
|
||||
}
|
||||
|
||||
export function loadIndexTemplates(data) {
|
||||
indexTemplates = (data.index_templates ?? []).map(({ name }) => name);
|
||||
}
|
||||
|
||||
export function loadComponentTemplates(data) {
|
||||
componentTemplates = (data.component_templates ?? []).map(({ name }) => name);
|
||||
}
|
||||
|
||||
export function loadDataStreams(data) {
|
||||
dataStreams = (data.data_streams ?? []).map(({ name }) => name);
|
||||
}
|
||||
|
||||
export function loadMappings(mappings) {
|
||||
perIndexTypes = {};
|
||||
|
||||
Object.entries(mappings).forEach(([index, indexMapping]) => {
|
||||
const normalizedIndexMappings = {};
|
||||
|
||||
// Migrate 1.0.0 mappings. This format has changed, so we need to extract the underlying mapping.
|
||||
if (indexMapping.mappings && Object.keys(indexMapping).length === 1) {
|
||||
indexMapping = indexMapping.mappings;
|
||||
}
|
||||
|
||||
Object.entries(indexMapping).forEach(([typeName, typeMapping]) => {
|
||||
if (typeName === 'properties') {
|
||||
const fieldList = getFieldNamesFromProperties(typeMapping);
|
||||
normalizedIndexMappings[typeName] = fieldList;
|
||||
} else {
|
||||
normalizedIndexMappings[typeName] = [];
|
||||
}
|
||||
});
|
||||
perIndexTypes[index] = normalizedIndexMappings;
|
||||
});
|
||||
}
|
||||
|
||||
export function loadAliases(aliases) {
|
||||
perAliasIndexes = {};
|
||||
Object.entries(aliases).forEach(([index, omdexAliases]) => {
|
||||
// verify we have an index defined. useful when mapping loading is disabled
|
||||
perIndexTypes[index] = perIndexTypes[index] || {};
|
||||
|
||||
Object.keys(omdexAliases.aliases || {}).forEach((alias) => {
|
||||
if (alias === index) {
|
||||
return;
|
||||
} // alias which is identical to index means no index.
|
||||
let curAliases = perAliasIndexes[alias];
|
||||
if (!curAliases) {
|
||||
curAliases = [];
|
||||
perAliasIndexes[alias] = curAliases;
|
||||
}
|
||||
curAliases.push(index);
|
||||
});
|
||||
});
|
||||
|
||||
perAliasIndexes._all = getIndices(false);
|
||||
}
|
||||
|
||||
export function clear() {
|
||||
perIndexTypes = {};
|
||||
perAliasIndexes = {};
|
||||
legacyTemplates = [];
|
||||
indexTemplates = [];
|
||||
componentTemplates = [];
|
||||
}
|
||||
|
||||
function retrieveSettings(http, settingsKey, settingsToRetrieve) {
|
||||
const settingKeyToPathMap = {
|
||||
fields: '_mapping',
|
||||
indices: '_aliases',
|
||||
legacyTemplates: '_template',
|
||||
indexTemplates: '_index_template',
|
||||
componentTemplates: '_component_template',
|
||||
dataStreams: '_data_stream',
|
||||
};
|
||||
// Fetch autocomplete info if setting is set to true, and if user has made changes.
|
||||
if (settingsToRetrieve[settingsKey] === true) {
|
||||
// Use pretty=false in these request in order to compress the response by removing whitespace
|
||||
const path = `${settingKeyToPathMap[settingsKey]}?pretty=false`;
|
||||
const method = 'GET';
|
||||
const asSystemRequest = true;
|
||||
const withProductOrigin = true;
|
||||
|
||||
return es.send({ http, method, path, asSystemRequest, withProductOrigin });
|
||||
} else {
|
||||
if (settingsToRetrieve[settingsKey] === false) {
|
||||
// If the user doesn't want autocomplete suggestions, then clear any that exist
|
||||
return Promise.resolve({});
|
||||
// return settingsPromise.resolveWith(this, [{}]);
|
||||
} else {
|
||||
// If the user doesn't want autocomplete suggestions, then clear any that exist
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve all selected settings by default.
|
||||
// TODO: We should refactor this to be easier to consume. Ideally this function should retrieve
|
||||
// whatever settings are specified, otherwise just use the saved settings. This requires changing
|
||||
// the behavior to not *clear* whatever settings have been unselected, but it's hard to tell if
|
||||
// this is possible without altering the autocomplete behavior. These are the scenarios we need to
|
||||
// support:
|
||||
// 1. Manual refresh. Specify what we want. Fetch specified, leave unspecified alone.
|
||||
// 2. Changed selection and saved: Specify what we want. Fetch changed and selected, leave
|
||||
// unchanged alone (both selected and unselected).
|
||||
// 3. Poll: Use saved. Fetch selected. Ignore unselected.
|
||||
|
||||
export function clearSubscriptions() {
|
||||
if (pollTimeoutId) {
|
||||
clearTimeout(pollTimeoutId);
|
||||
}
|
||||
}
|
||||
|
||||
const retrieveMappings = async (http, settingsToRetrieve) => {
|
||||
const mappings = await retrieveSettings(http, 'fields', settingsToRetrieve);
|
||||
|
||||
if (mappings) {
|
||||
const maxMappingSize = Object.keys(mappings).length > 10 * 1024 * 1024;
|
||||
let mappingsResponse;
|
||||
if (maxMappingSize) {
|
||||
console.warn(
|
||||
`Mapping size is larger than 10MB (${
|
||||
Object.keys(mappings).length / 1024 / 1024
|
||||
} MB). Ignoring...`
|
||||
);
|
||||
mappingsResponse = '{}';
|
||||
} else {
|
||||
mappingsResponse = mappings;
|
||||
}
|
||||
loadMappings(mappingsResponse);
|
||||
}
|
||||
};
|
||||
|
||||
const retrieveAliases = async (http, settingsToRetrieve) => {
|
||||
const aliases = await retrieveSettings(http, 'indices', settingsToRetrieve);
|
||||
|
||||
if (aliases) {
|
||||
loadAliases(aliases);
|
||||
}
|
||||
};
|
||||
|
||||
const retrieveTemplates = async (http, settingsToRetrieve) => {
|
||||
const legacyTemplates = await retrieveSettings(http, 'legacyTemplates', settingsToRetrieve);
|
||||
const indexTemplates = await retrieveSettings(http, 'indexTemplates', settingsToRetrieve);
|
||||
const componentTemplates = await retrieveSettings(http, 'componentTemplates', settingsToRetrieve);
|
||||
|
||||
if (legacyTemplates) {
|
||||
loadLegacyTemplates(legacyTemplates);
|
||||
}
|
||||
|
||||
if (indexTemplates) {
|
||||
loadIndexTemplates(indexTemplates);
|
||||
}
|
||||
|
||||
if (componentTemplates) {
|
||||
loadComponentTemplates(componentTemplates);
|
||||
}
|
||||
};
|
||||
|
||||
const retrieveDataStreams = async (http, settingsToRetrieve) => {
|
||||
const dataStreams = await retrieveSettings(http, 'dataStreams', settingsToRetrieve);
|
||||
|
||||
if (dataStreams) {
|
||||
loadDataStreams(dataStreams);
|
||||
}
|
||||
};
|
||||
/**
|
||||
*
|
||||
* @param settings Settings A way to retrieve the current settings
|
||||
* @param settingsToRetrieve any
|
||||
*/
|
||||
export function retrieveAutoCompleteInfo(http, settings, settingsToRetrieve) {
|
||||
clearSubscriptions();
|
||||
|
||||
const templatesSettingToRetrieve = {
|
||||
...settingsToRetrieve,
|
||||
legacyTemplates: settingsToRetrieve.templates,
|
||||
indexTemplates: settingsToRetrieve.templates,
|
||||
componentTemplates: settingsToRetrieve.templates,
|
||||
};
|
||||
|
||||
Promise.allSettled([
|
||||
retrieveMappings(http, settingsToRetrieve),
|
||||
retrieveAliases(http, settingsToRetrieve),
|
||||
retrieveTemplates(http, templatesSettingToRetrieve),
|
||||
retrieveDataStreams(http, settingsToRetrieve),
|
||||
]).then(() => {
|
||||
// Schedule next request.
|
||||
pollTimeoutId = setTimeout(() => {
|
||||
// This looks strange/inefficient, but it ensures correct behavior because we don't want to send
|
||||
// a scheduled request if the user turns off polling.
|
||||
if (settings.getPolling()) {
|
||||
retrieveAutoCompleteInfo(http, settings, settings.getAutocomplete());
|
||||
}
|
||||
}, settings.getPollInterval());
|
||||
});
|
||||
}
|
|
@ -15,8 +15,10 @@ import {
|
|||
ConsolePluginSetup,
|
||||
ConsoleUILocatorParams,
|
||||
} from './types';
|
||||
import { AutocompleteInfo, setAutocompleteInfo } from './services';
|
||||
|
||||
export class ConsoleUIPlugin implements Plugin<void, void, AppSetupUIPluginDependencies> {
|
||||
private readonly autocompleteInfo = new AutocompleteInfo();
|
||||
constructor(private ctx: PluginInitializerContext) {}
|
||||
|
||||
public setup(
|
||||
|
@ -27,6 +29,9 @@ export class ConsoleUIPlugin implements Plugin<void, void, AppSetupUIPluginDepen
|
|||
ui: { enabled: isConsoleUiEnabled },
|
||||
} = this.ctx.config.get<ClientConfigType>();
|
||||
|
||||
this.autocompleteInfo.setup(http);
|
||||
setAutocompleteInfo(this.autocompleteInfo);
|
||||
|
||||
if (isConsoleUiEnabled) {
|
||||
if (home) {
|
||||
home.featureCatalogue.register({
|
||||
|
@ -70,6 +75,7 @@ export class ConsoleUIPlugin implements Plugin<void, void, AppSetupUIPluginDepen
|
|||
usageCollection,
|
||||
element,
|
||||
theme$,
|
||||
autocompleteInfo: this.autocompleteInfo,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
17
src/plugins/console/public/services/autocomplete.mock.ts
Normal file
17
src/plugins/console/public/services/autocomplete.mock.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { AutocompleteInfo } from './autocomplete';
|
||||
|
||||
export class AutocompleteInfoMock extends AutocompleteInfo {
|
||||
setup = jest.fn();
|
||||
getEntityProvider = jest.fn();
|
||||
retrieve = jest.fn();
|
||||
clearSubscriptions = jest.fn();
|
||||
clear = jest.fn();
|
||||
}
|
107
src/plugins/console/public/services/autocomplete.ts
Normal file
107
src/plugins/console/public/services/autocomplete.ts
Normal file
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { createGetterSetter } from '@kbn/kibana-utils-plugin/public';
|
||||
import type { HttpSetup } from '@kbn/core/public';
|
||||
import type { MappingsApiResponse } from '../lib/autocomplete_entities/types';
|
||||
import { API_BASE_PATH } from '../../common/constants';
|
||||
import {
|
||||
Alias,
|
||||
DataStream,
|
||||
Mapping,
|
||||
LegacyTemplate,
|
||||
IndexTemplate,
|
||||
ComponentTemplate,
|
||||
} from '../lib/autocomplete_entities';
|
||||
import { DevToolsSettings, Settings } from './settings';
|
||||
|
||||
export class AutocompleteInfo {
|
||||
public readonly alias = new Alias();
|
||||
public readonly mapping = new Mapping();
|
||||
public readonly dataStream = new DataStream();
|
||||
public readonly legacyTemplate = new LegacyTemplate();
|
||||
public readonly indexTemplate = new IndexTemplate();
|
||||
public readonly componentTemplate = new ComponentTemplate();
|
||||
private http!: HttpSetup;
|
||||
private pollTimeoutId: ReturnType<typeof setTimeout> | undefined;
|
||||
|
||||
public setup(http: HttpSetup) {
|
||||
this.http = http;
|
||||
}
|
||||
|
||||
public getEntityProvider(
|
||||
type: string,
|
||||
context: { indices: string[]; types: string[] } = { indices: [], types: [] }
|
||||
) {
|
||||
switch (type) {
|
||||
case 'indices':
|
||||
const includeAliases = true;
|
||||
const collaborator = this.mapping;
|
||||
return () => this.alias.getIndices(includeAliases, collaborator);
|
||||
case 'fields':
|
||||
return this.mapping.getMappings(context.indices, context.types);
|
||||
case 'indexTemplates':
|
||||
return () => this.indexTemplate.getTemplates();
|
||||
case 'componentTemplates':
|
||||
return () => this.componentTemplate.getTemplates();
|
||||
case 'legacyTemplates':
|
||||
return () => this.legacyTemplate.getTemplates();
|
||||
case 'dataStreams':
|
||||
return () => this.dataStream.getDataStreams();
|
||||
default:
|
||||
throw new Error(`Unsupported type: ${type}`);
|
||||
}
|
||||
}
|
||||
|
||||
public retrieve(settings: Settings, settingsToRetrieve: DevToolsSettings['autocomplete']) {
|
||||
this.clearSubscriptions();
|
||||
this.http
|
||||
.get<MappingsApiResponse>(`${API_BASE_PATH}/autocomplete_entities`, {
|
||||
query: { ...settingsToRetrieve },
|
||||
})
|
||||
.then((data) => {
|
||||
this.load(data);
|
||||
// Schedule next request.
|
||||
this.pollTimeoutId = setTimeout(() => {
|
||||
// This looks strange/inefficient, but it ensures correct behavior because we don't want to send
|
||||
// a scheduled request if the user turns off polling.
|
||||
if (settings.getPolling()) {
|
||||
this.retrieve(settings, settings.getAutocomplete());
|
||||
}
|
||||
}, settings.getPollInterval());
|
||||
});
|
||||
}
|
||||
|
||||
public clearSubscriptions() {
|
||||
if (this.pollTimeoutId) {
|
||||
clearTimeout(this.pollTimeoutId);
|
||||
}
|
||||
}
|
||||
|
||||
private load(data: MappingsApiResponse) {
|
||||
this.mapping.loadMappings(data.mappings);
|
||||
const collaborator = this.mapping;
|
||||
this.alias.loadAliases(data.aliases, collaborator);
|
||||
this.indexTemplate.loadTemplates(data.indexTemplates);
|
||||
this.componentTemplate.loadTemplates(data.componentTemplates);
|
||||
this.legacyTemplate.loadTemplates(data.legacyTemplates);
|
||||
this.dataStream.loadDataStreams(data.dataStreams);
|
||||
}
|
||||
|
||||
public clear() {
|
||||
this.alias.clearAliases();
|
||||
this.mapping.clearMappings();
|
||||
this.dataStream.clearDataStreams();
|
||||
this.legacyTemplate.clearTemplates();
|
||||
this.indexTemplate.clearTemplates();
|
||||
this.componentTemplate.clearTemplates();
|
||||
}
|
||||
}
|
||||
|
||||
export const [getAutocompleteInfo, setAutocompleteInfo] =
|
||||
createGetterSetter<AutocompleteInfo>('AutocompleteInfo');
|
|
@ -10,3 +10,4 @@ export { createHistory, History } from './history';
|
|||
export { createStorage, Storage, StorageKeys } from './storage';
|
||||
export type { DevToolsSettings } from './settings';
|
||||
export { createSettings, Settings, DEFAULT_SETTINGS } from './settings';
|
||||
export { AutocompleteInfo, getAutocompleteInfo, setAutocompleteInfo } from './autocomplete';
|
||||
|
|
|
@ -16,6 +16,7 @@ import { ConsoleConfig, ConsoleConfig7x } from './config';
|
|||
import { registerRoutes } from './routes';
|
||||
|
||||
import { ESConfigForProxy, ConsoleSetup, ConsoleStart } from './types';
|
||||
import { handleEsError } from './shared_imports';
|
||||
|
||||
export class ConsoleServerPlugin implements Plugin<ConsoleSetup, ConsoleStart> {
|
||||
log: Logger;
|
||||
|
@ -58,6 +59,9 @@ export class ConsoleServerPlugin implements Plugin<ConsoleSetup, ConsoleStart> {
|
|||
esLegacyConfigService: this.esLegacyConfigService,
|
||||
specDefinitionService: this.specDefinitionsService,
|
||||
},
|
||||
lib: {
|
||||
handleEsError,
|
||||
},
|
||||
proxy: {
|
||||
readLegacyESConfig: async (): Promise<ESConfigForProxy> => {
|
||||
const legacyConfig = await this.esLegacyConfigService.readConfig();
|
||||
|
|
|
@ -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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export { registerMappingsRoute } from './register_mappings_route';
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import type { IScopedClusterClient } from '@kbn/core/server';
|
||||
import { parse } from 'query-string';
|
||||
import type { RouteDependencies } from '../../..';
|
||||
import { API_BASE_PATH } from '../../../../../common/constants';
|
||||
|
||||
interface Settings {
|
||||
indices: boolean;
|
||||
fields: boolean;
|
||||
templates: boolean;
|
||||
dataStreams: boolean;
|
||||
}
|
||||
|
||||
async function getMappings(esClient: IScopedClusterClient, settings: Settings) {
|
||||
if (settings.fields) {
|
||||
return esClient.asInternalUser.indices.getMapping();
|
||||
}
|
||||
// If the user doesn't want autocomplete suggestions, then clear any that exist.
|
||||
return Promise.resolve({});
|
||||
}
|
||||
|
||||
async function getAliases(esClient: IScopedClusterClient, settings: Settings) {
|
||||
if (settings.indices) {
|
||||
return esClient.asInternalUser.indices.getAlias();
|
||||
}
|
||||
// If the user doesn't want autocomplete suggestions, then clear any that exist.
|
||||
return Promise.resolve({});
|
||||
}
|
||||
|
||||
async function getDataStreams(esClient: IScopedClusterClient, settings: Settings) {
|
||||
if (settings.dataStreams) {
|
||||
return esClient.asInternalUser.indices.getDataStream();
|
||||
}
|
||||
// If the user doesn't want autocomplete suggestions, then clear any that exist.
|
||||
return Promise.resolve({});
|
||||
}
|
||||
|
||||
async function getTemplates(esClient: IScopedClusterClient, settings: Settings) {
|
||||
if (settings.templates) {
|
||||
return Promise.all([
|
||||
esClient.asInternalUser.indices.getTemplate(),
|
||||
esClient.asInternalUser.indices.getIndexTemplate(),
|
||||
esClient.asInternalUser.cluster.getComponentTemplate(),
|
||||
]);
|
||||
}
|
||||
// If the user doesn't want autocomplete suggestions, then clear any that exist.
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
export function registerGetRoute({ router, lib: { handleEsError } }: RouteDependencies) {
|
||||
router.get(
|
||||
{
|
||||
path: `${API_BASE_PATH}/autocomplete_entities`,
|
||||
validate: false,
|
||||
},
|
||||
async (ctx, request, response) => {
|
||||
try {
|
||||
const settings = parse(request.url.search, { parseBooleans: true }) as unknown as Settings;
|
||||
|
||||
// If no settings are provided return 400
|
||||
if (Object.keys(settings).length === 0) {
|
||||
return response.badRequest({
|
||||
body: 'Request must contain a query param of autocomplete settings',
|
||||
});
|
||||
}
|
||||
|
||||
const esClient = (await ctx.core).elasticsearch.client;
|
||||
const mappings = await getMappings(esClient, settings);
|
||||
const aliases = await getAliases(esClient, settings);
|
||||
const dataStreams = await getDataStreams(esClient, settings);
|
||||
const [legacyTemplates = {}, indexTemplates = {}, componentTemplates = {}] =
|
||||
await getTemplates(esClient, settings);
|
||||
|
||||
return response.ok({
|
||||
body: {
|
||||
mappings,
|
||||
aliases,
|
||||
dataStreams,
|
||||
legacyTemplates,
|
||||
indexTemplates,
|
||||
componentTemplates,
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
return handleEsError({ error: e, response });
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { RouteDependencies } from '../../..';
|
||||
import { registerGetRoute } from './register_get_route';
|
||||
|
||||
export function registerMappingsRoute(deps: RouteDependencies) {
|
||||
registerGetRoute(deps);
|
||||
}
|
|
@ -17,6 +17,7 @@ import { MAJOR_VERSION } from '../../../../../common/constants';
|
|||
import { ProxyConfigCollection } from '../../../../lib';
|
||||
import { RouteDependencies, ProxyDependencies } from '../../..';
|
||||
import { EsLegacyConfigService, SpecDefinitionsService } from '../../../../services';
|
||||
import { handleEsError } from '../../../../shared_imports';
|
||||
|
||||
const kibanaVersion = new SemVer(MAJOR_VERSION);
|
||||
|
||||
|
@ -65,5 +66,6 @@ export const getProxyRouteHandlerDeps = ({
|
|||
: defaultProxyValue,
|
||||
log,
|
||||
kibanaVersion,
|
||||
lib: { handleEsError },
|
||||
};
|
||||
};
|
||||
|
|
|
@ -12,10 +12,12 @@ import { SemVer } from 'semver';
|
|||
import { EsLegacyConfigService, SpecDefinitionsService } from '../services';
|
||||
import { ESConfigForProxy } from '../types';
|
||||
import { ProxyConfigCollection } from '../lib';
|
||||
import { handleEsError } from '../shared_imports';
|
||||
|
||||
import { registerEsConfigRoute } from './api/console/es_config';
|
||||
import { registerProxyRoute } from './api/console/proxy';
|
||||
import { registerSpecDefinitionsRoute } from './api/console/spec_definitions';
|
||||
import { registerMappingsRoute } from './api/console/autocomplete_entities';
|
||||
|
||||
export interface ProxyDependencies {
|
||||
readLegacyESConfig: () => Promise<ESConfigForProxy>;
|
||||
|
@ -31,6 +33,9 @@ export interface RouteDependencies {
|
|||
esLegacyConfigService: EsLegacyConfigService;
|
||||
specDefinitionService: SpecDefinitionsService;
|
||||
};
|
||||
lib: {
|
||||
handleEsError: typeof handleEsError;
|
||||
};
|
||||
kibanaVersion: SemVer;
|
||||
}
|
||||
|
||||
|
@ -38,4 +43,5 @@ export const registerRoutes = (dependencies: RouteDependencies) => {
|
|||
registerEsConfigRoute(dependencies);
|
||||
registerProxyRoute(dependencies);
|
||||
registerSpecDefinitionsRoute(dependencies);
|
||||
registerMappingsRoute(dependencies);
|
||||
};
|
||||
|
|
9
src/plugins/console/server/shared_imports.ts
Normal file
9
src/plugins/console/server/shared_imports.ts
Normal file
|
@ -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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export { handleEsError } from '@kbn/es-ui-shared-plugin/server';
|
133
test/api_integration/apis/console/autocomplete_entities.ts
Normal file
133
test/api_integration/apis/console/autocomplete_entities.ts
Normal file
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import type { Response } from 'superagent';
|
||||
import type { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
export default ({ getService }: FtrProviderContext) => {
|
||||
const supertest = getService('supertest');
|
||||
|
||||
function utilTest(name: string, query: object, test: (response: Response) => void) {
|
||||
it(name, async () => {
|
||||
const response = await supertest.get('/api/console/autocomplete_entities').query(query);
|
||||
test(response);
|
||||
});
|
||||
}
|
||||
|
||||
describe('/api/console/autocomplete_entities', () => {
|
||||
utilTest('should not succeed if no settings are provided in query params', {}, (response) => {
|
||||
const { status } = response;
|
||||
expect(status).to.be(400);
|
||||
});
|
||||
|
||||
utilTest(
|
||||
'should return an object with properties of "mappings", "aliases", "dataStreams", "legacyTemplates", "indexTemplates", "componentTemplates"',
|
||||
{
|
||||
indices: true,
|
||||
fields: true,
|
||||
templates: true,
|
||||
dataStreams: true,
|
||||
},
|
||||
(response) => {
|
||||
const { body, status } = response;
|
||||
expect(status).to.be(200);
|
||||
expect(Object.keys(body).sort()).to.eql([
|
||||
'aliases',
|
||||
'componentTemplates',
|
||||
'dataStreams',
|
||||
'indexTemplates',
|
||||
'legacyTemplates',
|
||||
'mappings',
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
utilTest(
|
||||
'should return empty payload with all settings are set to false',
|
||||
{
|
||||
indices: false,
|
||||
fields: false,
|
||||
templates: false,
|
||||
dataStreams: false,
|
||||
},
|
||||
(response) => {
|
||||
const { body, status } = response;
|
||||
expect(status).to.be(200);
|
||||
expect(body.legacyTemplates).to.eql({});
|
||||
expect(body.indexTemplates).to.eql({});
|
||||
expect(body.componentTemplates).to.eql({});
|
||||
expect(body.aliases).to.eql({});
|
||||
expect(body.mappings).to.eql({});
|
||||
expect(body.dataStreams).to.eql({});
|
||||
}
|
||||
);
|
||||
|
||||
utilTest(
|
||||
'should return empty templates with templates setting is set to false',
|
||||
{
|
||||
indices: true,
|
||||
fields: true,
|
||||
templates: false,
|
||||
dataStreams: true,
|
||||
},
|
||||
(response) => {
|
||||
const { body, status } = response;
|
||||
expect(status).to.be(200);
|
||||
expect(body.legacyTemplates).to.eql({});
|
||||
expect(body.indexTemplates).to.eql({});
|
||||
expect(body.componentTemplates).to.eql({});
|
||||
}
|
||||
);
|
||||
|
||||
utilTest(
|
||||
'should return empty data streams with dataStreams setting is set to false',
|
||||
{
|
||||
indices: true,
|
||||
fields: true,
|
||||
templates: true,
|
||||
dataStreams: false,
|
||||
},
|
||||
(response) => {
|
||||
const { body, status } = response;
|
||||
expect(status).to.be(200);
|
||||
expect(body.dataStreams).to.eql({});
|
||||
}
|
||||
);
|
||||
|
||||
utilTest(
|
||||
'should return empty aliases with indices setting is set to false',
|
||||
{
|
||||
indices: false,
|
||||
fields: true,
|
||||
templates: true,
|
||||
dataStreams: true,
|
||||
},
|
||||
(response) => {
|
||||
const { body, status } = response;
|
||||
expect(status).to.be(200);
|
||||
expect(body.aliases).to.eql({});
|
||||
}
|
||||
);
|
||||
|
||||
utilTest(
|
||||
'should return empty mappings with fields setting is set to false',
|
||||
{
|
||||
indices: true,
|
||||
fields: false,
|
||||
templates: true,
|
||||
dataStreams: true,
|
||||
},
|
||||
(response) => {
|
||||
const { body, status } = response;
|
||||
expect(status).to.be(200);
|
||||
expect(body.mappings).to.eql({});
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
|
@ -11,5 +11,6 @@ import { FtrProviderContext } from '../../ftr_provider_context';
|
|||
export default function ({ loadTestFile }: FtrProviderContext) {
|
||||
describe('core', () => {
|
||||
loadTestFile(require.resolve('./proxy_route'));
|
||||
loadTestFile(require.resolve('./autocomplete_entities'));
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue