mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Console] Remove use of jQuery (#127867)
* Remove jquery * Fixed failing functional test * Fixed failing jest test * Lint * Address comments * Remove unused imports * Remove unused imports * Get http service through context * Import http to services mock * Fix use_send_current_request_to_es.test * Add tests for send_request_to_es logic * Address comments * Fix base path Co-authored-by: Muhammad Ibragimov <muhammad.ibragimov@elastic.co>
This commit is contained in:
parent
edf6e21941
commit
8a0cc6efcd
14 changed files with 332 additions and 201 deletions
9
src/plugins/console/common/constants/api.ts
Normal file
9
src/plugins/console/common/constants/api.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 const API_BASE_PATH = '/api/console';
|
|
@ -7,3 +7,4 @@
|
|||
*/
|
||||
|
||||
export { MAJOR_VERSION } from './plugin';
|
||||
export { API_BASE_PATH } from './api';
|
||||
|
|
|
@ -64,7 +64,7 @@ const inputId = 'ConAppInputTextarea';
|
|||
|
||||
function EditorUI({ initialTextValue }: EditorProps) {
|
||||
const {
|
||||
services: { history, notifications, settings: settingsService, esHostService },
|
||||
services: { history, notifications, settings: settingsService, esHostService, http },
|
||||
docLinkVersion,
|
||||
} = useServicesContext();
|
||||
|
||||
|
@ -194,7 +194,7 @@ function EditorUI({ initialTextValue }: EditorProps) {
|
|||
setInputEditor(editor);
|
||||
setTextArea(editorRef.current!.querySelector('textarea'));
|
||||
|
||||
retrieveAutoCompleteInfo(settingsService, settingsService.getAutocomplete());
|
||||
retrieveAutoCompleteInfo(http, settingsService, settingsService.getAutocomplete());
|
||||
|
||||
const unsubscribeResizer = subscribeResizeChecker(editorRef.current!, editor);
|
||||
setupAutosave();
|
||||
|
@ -214,6 +214,7 @@ function EditorUI({ initialTextValue }: EditorProps) {
|
|||
history,
|
||||
setInputEditor,
|
||||
settingsService,
|
||||
http,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
import React from 'react';
|
||||
|
||||
import type { HttpSetup } from 'kibana/public';
|
||||
import { AutocompleteOptions, DevToolsSettingsModal } from '../components';
|
||||
|
||||
// @ts-ignore
|
||||
|
@ -26,13 +27,15 @@ const getAutocompleteDiff = (
|
|||
};
|
||||
|
||||
const refreshAutocompleteSettings = (
|
||||
http: HttpSetup,
|
||||
settings: SettingsService,
|
||||
selectedSettings: DevToolsSettings['autocomplete']
|
||||
) => {
|
||||
retrieveAutoCompleteInfo(settings, selectedSettings);
|
||||
retrieveAutoCompleteInfo(http, settings, selectedSettings);
|
||||
};
|
||||
|
||||
const fetchAutocompleteSettingsIfNeeded = (
|
||||
http: HttpSetup,
|
||||
settings: SettingsService,
|
||||
newSettings: DevToolsSettings,
|
||||
prevSettings: DevToolsSettings
|
||||
|
@ -57,10 +60,10 @@ const fetchAutocompleteSettingsIfNeeded = (
|
|||
},
|
||||
{} as DevToolsSettings['autocomplete']
|
||||
);
|
||||
retrieveAutoCompleteInfo(settings, changedSettings);
|
||||
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(settings, settings.getAutocomplete());
|
||||
retrieveAutoCompleteInfo(http, settings, settings.getAutocomplete());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -71,14 +74,14 @@ export interface Props {
|
|||
|
||||
export function Settings({ onClose }: Props) {
|
||||
const {
|
||||
services: { settings },
|
||||
services: { settings, http },
|
||||
} = useServicesContext();
|
||||
|
||||
const dispatch = useEditorActionContext();
|
||||
|
||||
const onSaveSettings = (newSettings: DevToolsSettings) => {
|
||||
const prevSettings = settings.toJSON();
|
||||
fetchAutocompleteSettingsIfNeeded(settings, newSettings, prevSettings);
|
||||
fetchAutocompleteSettingsIfNeeded(http, settings, newSettings, prevSettings);
|
||||
|
||||
// Update the new settings in localStorage
|
||||
settings.updateSettings(newSettings);
|
||||
|
@ -96,7 +99,7 @@ export function Settings({ onClose }: Props) {
|
|||
onClose={onClose}
|
||||
onSaveSettings={onSaveSettings}
|
||||
refreshAutocompleteSettings={(selectedSettings) =>
|
||||
refreshAutocompleteSettings(settings, selectedSettings)
|
||||
refreshAutocompleteSettings(http, settings, selectedSettings)
|
||||
}
|
||||
settings={settings.toJSON()}
|
||||
/>
|
||||
|
|
|
@ -37,6 +37,7 @@ export const serviceContextMock = {
|
|||
history: new HistoryMock(storage),
|
||||
notifications: notificationServiceMock.createSetupContract(),
|
||||
objectStorageClient: {} as unknown as ObjectStorageClient,
|
||||
http,
|
||||
},
|
||||
docLinkVersion: 'NA',
|
||||
theme$: themeServiceMock.create().start().theme$,
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import React, { createContext, useContext, useEffect } from 'react';
|
||||
import { Observable } from 'rxjs';
|
||||
import { NotificationsSetup, CoreTheme, DocLinksStart } from 'kibana/public';
|
||||
import type { NotificationsSetup, CoreTheme, DocLinksStart, HttpSetup } from 'kibana/public';
|
||||
|
||||
import { History, Settings, Storage } from '../../services';
|
||||
import { ObjectStorageClient } from '../../../common/types';
|
||||
|
@ -23,6 +23,7 @@ interface ContextServices {
|
|||
objectStorageClient: ObjectStorageClient;
|
||||
trackUiMetric: MetricsTracker;
|
||||
esHostService: EsHostService;
|
||||
http: HttpSetup;
|
||||
}
|
||||
|
||||
export interface ContextValue {
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* 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 { ContextValue } from '../../contexts';
|
||||
|
||||
jest.mock('./send_request_to_es', () => ({ sendRequestToES: jest.fn(() => Promise.resolve()) }));
|
||||
|
||||
import { sendRequestToES } from './send_request_to_es';
|
||||
import { serviceContextMock } from '../../contexts/services_context.mock';
|
||||
|
||||
const mockedSendRequestToES = sendRequestToES as jest.Mock;
|
||||
|
||||
describe('sendRequestToES', () => {
|
||||
let mockContextValue: ContextValue;
|
||||
|
||||
beforeEach(() => {
|
||||
mockContextValue = serviceContextMock.create();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('should send request to ES', async () => {
|
||||
mockedSendRequestToES.mockResolvedValue([
|
||||
{
|
||||
response: {
|
||||
statusCode: 200,
|
||||
value: '{\n "acknowledged": true \n}',
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
const args = {
|
||||
http: mockContextValue.services.http,
|
||||
requests: [{ method: 'PUT', url: 'test', data: [] }],
|
||||
};
|
||||
const results = await sendRequestToES(args);
|
||||
|
||||
const [request] = results;
|
||||
expect(request.response.statusCode).toEqual(200);
|
||||
expect(request.response.value).toContain('"acknowledged": true');
|
||||
expect(mockedSendRequestToES).toHaveBeenCalledWith(args);
|
||||
expect(mockedSendRequestToES).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should send multiple requests to ES', async () => {
|
||||
mockedSendRequestToES.mockResolvedValue([
|
||||
{
|
||||
response: {
|
||||
statusCode: 200,
|
||||
},
|
||||
},
|
||||
{
|
||||
response: {
|
||||
statusCode: 200,
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
const args = {
|
||||
http: mockContextValue.services.http,
|
||||
requests: [
|
||||
{ method: 'GET', url: 'test-1', data: [] },
|
||||
{ method: 'GET', url: 'test-2', data: [] },
|
||||
],
|
||||
};
|
||||
const results = await sendRequestToES(args);
|
||||
|
||||
const [firstRequest, secondRequest] = results;
|
||||
expect(firstRequest.response.statusCode).toEqual(200);
|
||||
expect(secondRequest.response.statusCode).toEqual(200);
|
||||
expect(mockedSendRequestToES).toHaveBeenCalledWith(args);
|
||||
expect(mockedSendRequestToES).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should handle errors', async () => {
|
||||
mockedSendRequestToES.mockRejectedValue({
|
||||
response: {
|
||||
statusCode: 500,
|
||||
statusText: 'error',
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
await sendRequestToES({
|
||||
http: mockContextValue.services.http,
|
||||
requests: [{ method: 'GET', url: 'test', data: [] }],
|
||||
});
|
||||
} catch (error) {
|
||||
expect(error.response.statusCode).toEqual(500);
|
||||
expect(error.response.statusText).toEqual('error');
|
||||
expect(mockedSendRequestToES).toHaveBeenCalledTimes(1);
|
||||
}
|
||||
});
|
||||
});
|
|
@ -6,6 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { HttpSetup, IHttpFetchError } from 'kibana/public';
|
||||
import { extractWarningMessages } from '../../../lib/utils';
|
||||
import { XJson } from '../../../../../es_ui_shared/public';
|
||||
// @ts-ignore
|
||||
|
@ -15,6 +16,7 @@ import { BaseResponseType } from '../../../types';
|
|||
const { collapseLiteralStrings } = XJson;
|
||||
|
||||
export interface EsRequestArgs {
|
||||
http: HttpSetup;
|
||||
requests: Array<{ url: string; method: string; data: string[] }>;
|
||||
}
|
||||
|
||||
|
@ -47,7 +49,7 @@ export function sendRequestToES(args: EsRequestArgs): Promise<ESRequestResult[]>
|
|||
|
||||
const isMultiRequest = requests.length > 1;
|
||||
|
||||
const sendNextRequest = () => {
|
||||
const sendNextRequest = async () => {
|
||||
if (reqId !== CURRENT_REQ_ID) {
|
||||
resolve(results);
|
||||
return;
|
||||
|
@ -65,23 +67,30 @@ export function sendRequestToES(args: EsRequestArgs): Promise<ESRequestResult[]>
|
|||
} // append a new line for bulk requests.
|
||||
|
||||
const startTime = Date.now();
|
||||
es.send(esMethod, esPath, esData).always(
|
||||
(dataOrjqXHR, textStatus: string, jqXhrORerrorThrown) => {
|
||||
if (reqId !== CURRENT_REQ_ID) {
|
||||
return;
|
||||
}
|
||||
|
||||
const xhr = dataOrjqXHR.promise ? dataOrjqXHR : jqXhrORerrorThrown;
|
||||
try {
|
||||
const { response, body } = await es.send({
|
||||
http: args.http,
|
||||
method: esMethod,
|
||||
path: esPath,
|
||||
data: esData,
|
||||
asResponse: true,
|
||||
});
|
||||
|
||||
if (reqId !== CURRENT_REQ_ID) {
|
||||
// Skip if previous request is not resolved yet. This can happen when issuing multiple requests at the same time and with slow networks
|
||||
return;
|
||||
}
|
||||
|
||||
if (response) {
|
||||
const isSuccess =
|
||||
typeof xhr.status === 'number' &&
|
||||
// Things like DELETE index where the index is not there are OK.
|
||||
((xhr.status >= 200 && xhr.status < 300) || xhr.status === 404);
|
||||
(response.status >= 200 && response.status < 300) || response.status === 404;
|
||||
|
||||
if (isSuccess) {
|
||||
let value = xhr.responseText;
|
||||
let value = JSON.stringify(body, null, 2);
|
||||
|
||||
const warnings = xhr.getResponseHeader('warning');
|
||||
const warnings = response.headers.get('warning');
|
||||
if (warnings) {
|
||||
const warningMessages = extractWarningMessages(warnings);
|
||||
value = warningMessages.join('\n') + '\n' + value;
|
||||
|
@ -94,9 +103,9 @@ export function sendRequestToES(args: EsRequestArgs): Promise<ESRequestResult[]>
|
|||
results.push({
|
||||
response: {
|
||||
timeMs: Date.now() - startTime,
|
||||
statusCode: xhr.status,
|
||||
statusText: xhr.statusText,
|
||||
contentType: xhr.getResponseHeader('Content-Type'),
|
||||
statusCode: response.status,
|
||||
statusText: response.statusText,
|
||||
contentType: response.headers.get('Content-Type') as BaseResponseType,
|
||||
value,
|
||||
},
|
||||
request: {
|
||||
|
@ -107,37 +116,47 @@ export function sendRequestToES(args: EsRequestArgs): Promise<ESRequestResult[]>
|
|||
});
|
||||
|
||||
// single request terminate via sendNextRequest as well
|
||||
sendNextRequest();
|
||||
} else {
|
||||
let value;
|
||||
let contentType: string;
|
||||
if (xhr.responseText) {
|
||||
value = xhr.responseText; // ES error should be shown
|
||||
contentType = xhr.getResponseHeader('Content-Type');
|
||||
} else {
|
||||
value = 'Request failed to get to the server (status code: ' + xhr.status + ')';
|
||||
contentType = 'text/plain';
|
||||
}
|
||||
if (isMultiRequest) {
|
||||
value = '# ' + req.method + ' ' + req.url + '\n' + value;
|
||||
}
|
||||
reject({
|
||||
response: {
|
||||
value,
|
||||
contentType,
|
||||
timeMs: Date.now() - startTime,
|
||||
statusCode: xhr.status,
|
||||
statusText: xhr.statusText,
|
||||
},
|
||||
request: {
|
||||
data: esData,
|
||||
method: esMethod,
|
||||
path: esPath,
|
||||
},
|
||||
});
|
||||
await sendNextRequest();
|
||||
}
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
let value;
|
||||
let contentType: string | null = '';
|
||||
|
||||
const { response, body = {} } = error as IHttpFetchError;
|
||||
if (response) {
|
||||
const { status, headers } = response;
|
||||
if (body) {
|
||||
value = JSON.stringify(body, null, 2); // ES error should be shown
|
||||
contentType = headers.get('Content-Type');
|
||||
} else {
|
||||
value = 'Request failed to get to the server (status code: ' + status + ')';
|
||||
contentType = headers.get('Content-Type');
|
||||
}
|
||||
|
||||
if (isMultiRequest) {
|
||||
value = '# ' + req.method + ' ' + req.url + '\n' + value;
|
||||
}
|
||||
} else {
|
||||
value =
|
||||
"\n\nFailed to connect to Console's backend.\nPlease check the Kibana server is up and running";
|
||||
}
|
||||
|
||||
reject({
|
||||
response: {
|
||||
value,
|
||||
contentType,
|
||||
timeMs: Date.now() - startTime,
|
||||
statusCode: error?.response?.status ?? 500,
|
||||
statusText: error?.response?.statusText ?? 'error',
|
||||
},
|
||||
request: {
|
||||
data: esData,
|
||||
method: esMethod,
|
||||
path: esPath,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
sendNextRequest();
|
||||
|
|
|
@ -52,7 +52,10 @@ describe('useSendCurrentRequestToES', () => {
|
|||
|
||||
const { result } = renderHook(() => useSendCurrentRequestToES(), { wrapper: contexts });
|
||||
await act(() => result.current());
|
||||
expect(sendRequestToES).toHaveBeenCalledWith({ requests: ['test'] });
|
||||
expect(sendRequestToES).toHaveBeenCalledWith({
|
||||
http: mockContextValue.services.http,
|
||||
requests: ['test'],
|
||||
});
|
||||
|
||||
// Second call should be the request success
|
||||
const [, [requestSucceededCall]] = (dispatch as jest.Mock).mock.calls;
|
||||
|
|
|
@ -21,7 +21,7 @@ import { track } from './track';
|
|||
|
||||
export const useSendCurrentRequestToES = () => {
|
||||
const {
|
||||
services: { history, settings, notifications, trackUiMetric },
|
||||
services: { history, settings, notifications, trackUiMetric, http },
|
||||
theme$,
|
||||
} = useServicesContext();
|
||||
|
||||
|
@ -46,7 +46,7 @@ export const useSendCurrentRequestToES = () => {
|
|||
// Fire and forget
|
||||
setTimeout(() => track(requests, editor, trackUiMetric), 0);
|
||||
|
||||
const results = await sendRequestToES({ requests });
|
||||
const results = await sendRequestToES({ http, requests });
|
||||
|
||||
let saveToHistoryError: undefined | Error;
|
||||
const { historyDisabled } = settings.toJSON();
|
||||
|
@ -102,7 +102,7 @@ export const useSendCurrentRequestToES = () => {
|
|||
// 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(settings, settings.getAutocomplete());
|
||||
retrieveAutoCompleteInfo(http, settings, settings.getAutocomplete());
|
||||
}
|
||||
|
||||
dispatch({
|
||||
|
@ -129,5 +129,5 @@ export const useSendCurrentRequestToES = () => {
|
|||
});
|
||||
}
|
||||
}
|
||||
}, [dispatch, settings, history, notifications, trackUiMetric, theme$]);
|
||||
}, [dispatch, http, settings, notifications.toasts, trackUiMetric, history, theme$]);
|
||||
};
|
||||
|
|
|
@ -75,6 +75,7 @@ export function renderApp({
|
|||
notifications,
|
||||
trackUiMetric,
|
||||
objectStorageClient,
|
||||
http,
|
||||
},
|
||||
theme$,
|
||||
}}
|
||||
|
|
|
@ -6,12 +6,8 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import $ from 'jquery';
|
||||
import { stringify } from 'query-string';
|
||||
|
||||
interface SendOptions {
|
||||
asSystemRequest?: boolean;
|
||||
}
|
||||
import type { HttpFetchOptions, HttpResponse, HttpSetup } from 'kibana/public';
|
||||
import { API_BASE_PATH } from '../../../common/constants';
|
||||
|
||||
const esVersion: string[] = [];
|
||||
|
||||
|
@ -24,44 +20,32 @@ export function getContentType(body: unknown) {
|
|||
return 'application/json';
|
||||
}
|
||||
|
||||
export function send(
|
||||
method: string,
|
||||
path: string,
|
||||
data: string | object,
|
||||
{ asSystemRequest }: SendOptions = {},
|
||||
withProductOrigin: boolean = false
|
||||
) {
|
||||
const wrappedDfd = $.Deferred();
|
||||
interface SendProps {
|
||||
http: HttpSetup;
|
||||
method: string;
|
||||
path: string;
|
||||
data?: string;
|
||||
asSystemRequest?: boolean;
|
||||
withProductOrigin?: boolean;
|
||||
asResponse?: boolean;
|
||||
}
|
||||
|
||||
const options: JQuery.AjaxSettings = {
|
||||
url:
|
||||
'../api/console/proxy?' +
|
||||
stringify({ path, method, ...(withProductOrigin && { withProductOrigin }) }, { sort: false }),
|
||||
headers: {
|
||||
'kbn-xsrf': 'kibana',
|
||||
...(asSystemRequest && { 'kbn-system-request': 'true' }),
|
||||
},
|
||||
data,
|
||||
contentType: getContentType(data),
|
||||
cache: false,
|
||||
crossDomain: true,
|
||||
type: 'POST',
|
||||
dataType: 'text', // disable automatic guessing
|
||||
export async function send({
|
||||
http,
|
||||
method,
|
||||
path,
|
||||
data,
|
||||
asSystemRequest = false,
|
||||
withProductOrigin = false,
|
||||
asResponse = false,
|
||||
}: SendProps) {
|
||||
const options: HttpFetchOptions = {
|
||||
query: { path, method, ...(withProductOrigin && { withProductOrigin }) },
|
||||
body: data,
|
||||
asResponse,
|
||||
asSystemRequest,
|
||||
};
|
||||
|
||||
$.ajax(options).then(
|
||||
(responseData, textStatus: string, jqXHR: unknown) => {
|
||||
wrappedDfd.resolveWith({}, [responseData, textStatus, jqXHR]);
|
||||
},
|
||||
((jqXHR: { status: number; responseText: string }, textStatus: string, errorThrown: Error) => {
|
||||
if (jqXHR.status === 0) {
|
||||
jqXHR.responseText =
|
||||
"\n\nFailed to connect to Console's backend.\nPlease check the Kibana server is up and running";
|
||||
}
|
||||
wrappedDfd.rejectWith({}, [jqXHR, textStatus, errorThrown]);
|
||||
}) as any
|
||||
);
|
||||
return wrappedDfd;
|
||||
return await http.post<HttpResponse>(`${API_BASE_PATH}/proxy`, options);
|
||||
}
|
||||
|
||||
export function constructESUrl(baseUri: string, path: string) {
|
||||
|
|
|
@ -6,21 +6,18 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import $ from 'jquery';
|
||||
import _ from 'lodash';
|
||||
import * as es from '../es/es';
|
||||
|
||||
let pollTimeoutId;
|
||||
|
||||
let perIndexTypes = {};
|
||||
let perAliasIndexes = [];
|
||||
let perAliasIndexes = {};
|
||||
let legacyTemplates = [];
|
||||
let indexTemplates = [];
|
||||
let componentTemplates = [];
|
||||
let dataStreams = [];
|
||||
|
||||
const mappingObj = {};
|
||||
|
||||
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.
|
||||
|
@ -32,7 +29,8 @@ export function expandAliases(indicesOrAliases) {
|
|||
if (typeof indicesOrAliases === 'string') {
|
||||
indicesOrAliases = [indicesOrAliases];
|
||||
}
|
||||
indicesOrAliases = $.map(indicesOrAliases, function (iOrA) {
|
||||
|
||||
indicesOrAliases = indicesOrAliases.map((iOrA) => {
|
||||
if (perAliasIndexes[iOrA]) {
|
||||
return perAliasIndexes[iOrA];
|
||||
}
|
||||
|
@ -40,12 +38,14 @@ export function expandAliases(indicesOrAliases) {
|
|||
});
|
||||
let ret = [].concat.apply([], indicesOrAliases);
|
||||
ret.sort();
|
||||
let last;
|
||||
ret = $.map(ret, function (v) {
|
||||
const r = last === v ? null : v;
|
||||
last = v;
|
||||
return r;
|
||||
});
|
||||
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];
|
||||
}
|
||||
|
||||
|
@ -81,8 +81,8 @@ export function getFields(indices, types) {
|
|||
ret = f ? f : [];
|
||||
} else {
|
||||
// filter what we need
|
||||
$.each(typeDict, function (type, fields) {
|
||||
if (!types || types.length === 0 || $.inArray(type, types) !== -1) {
|
||||
Object.entries(typeDict).forEach(([type, fields]) => {
|
||||
if (!types || types.length === 0 || types.includes(type)) {
|
||||
ret.push(fields);
|
||||
}
|
||||
});
|
||||
|
@ -91,11 +91,12 @@ export function getFields(indices, types) {
|
|||
}
|
||||
} else {
|
||||
// multi index mode.
|
||||
$.each(perIndexTypes, function (index) {
|
||||
if (!indices || indices.length === 0 || $.inArray(index, indices) !== -1) {
|
||||
Object.keys(perIndexTypes).forEach((index) => {
|
||||
if (!indices || indices.length === 0 || indices.includes(index)) {
|
||||
ret.push(getFields(index, types));
|
||||
}
|
||||
});
|
||||
|
||||
ret = [].concat.apply([], ret);
|
||||
}
|
||||
|
||||
|
@ -114,13 +115,19 @@ export function getTypes(indices) {
|
|||
}
|
||||
|
||||
// filter what we need
|
||||
$.each(typeDict, function (type) {
|
||||
ret.push(type);
|
||||
});
|
||||
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.
|
||||
$.each(perIndexTypes, function (index) {
|
||||
if (!indices || $.inArray(index, indices) !== -1) {
|
||||
Object.keys(perIndexTypes).forEach((index) => {
|
||||
if (!indices || indices.includes(index)) {
|
||||
ret.push(getTypes(index));
|
||||
}
|
||||
});
|
||||
|
@ -132,13 +139,15 @@ export function getTypes(indices) {
|
|||
|
||||
export function getIndices(includeAliases) {
|
||||
const ret = [];
|
||||
$.each(perIndexTypes, function (index) {
|
||||
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) {
|
||||
$.each(perAliasIndexes, function (alias) {
|
||||
Object.keys(perAliasIndexes).forEach((alias) => {
|
||||
ret.push(alias);
|
||||
});
|
||||
}
|
||||
|
@ -154,7 +163,7 @@ function getFieldNamesFromFieldMapping(fieldName, fieldMapping) {
|
|||
function applyPathSettings(nestedFieldNames) {
|
||||
const pathType = fieldMapping.path || 'full';
|
||||
if (pathType === 'full') {
|
||||
return $.map(nestedFieldNames, function (f) {
|
||||
return nestedFieldNames.map((f) => {
|
||||
f.name = fieldName + '.' + f.name;
|
||||
return f;
|
||||
});
|
||||
|
@ -177,7 +186,7 @@ function getFieldNamesFromFieldMapping(fieldName, fieldMapping) {
|
|||
}
|
||||
|
||||
if (fieldMapping.fields) {
|
||||
nestedFields = $.map(fieldMapping.fields, function (fieldMapping, fieldName) {
|
||||
nestedFields = Object.entries(fieldMapping.fields).flatMap(([fieldName, fieldMapping]) => {
|
||||
return getFieldNamesFromFieldMapping(fieldName, fieldMapping);
|
||||
});
|
||||
nestedFields = applyPathSettings(nestedFields);
|
||||
|
@ -189,7 +198,7 @@ function getFieldNamesFromFieldMapping(fieldName, fieldMapping) {
|
|||
}
|
||||
|
||||
function getFieldNamesFromProperties(properties = {}) {
|
||||
const fieldList = $.map(properties, function (fieldMapping, fieldName) {
|
||||
const fieldList = Object.entries(properties).flatMap(([fieldName, fieldMapping]) => {
|
||||
return getFieldNamesFromFieldMapping(fieldName, fieldMapping);
|
||||
});
|
||||
|
||||
|
@ -218,15 +227,15 @@ export function loadDataStreams(data) {
|
|||
export function loadMappings(mappings) {
|
||||
perIndexTypes = {};
|
||||
|
||||
$.each(mappings, function (index, indexMapping) {
|
||||
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 && _.keys(indexMapping).length === 1) {
|
||||
if (indexMapping.mappings && Object.keys(indexMapping).length === 1) {
|
||||
indexMapping = indexMapping.mappings;
|
||||
}
|
||||
|
||||
$.each(indexMapping, function (typeName, typeMapping) {
|
||||
Object.entries(indexMapping).forEach(([typeName, typeMapping]) => {
|
||||
if (typeName === 'properties') {
|
||||
const fieldList = getFieldNamesFromProperties(typeMapping);
|
||||
normalizedIndexMappings[typeName] = fieldList;
|
||||
|
@ -234,18 +243,17 @@ export function loadMappings(mappings) {
|
|||
normalizedIndexMappings[typeName] = [];
|
||||
}
|
||||
});
|
||||
|
||||
perIndexTypes[index] = normalizedIndexMappings;
|
||||
});
|
||||
}
|
||||
|
||||
export function loadAliases(aliases) {
|
||||
perAliasIndexes = {};
|
||||
$.each(aliases || {}, function (index, omdexAliases) {
|
||||
Object.entries(aliases).forEach(([index, omdexAliases]) => {
|
||||
// verify we have an index defined. useful when mapping loading is disabled
|
||||
perIndexTypes[index] = perIndexTypes[index] || {};
|
||||
|
||||
$.each(omdexAliases.aliases || {}, function (alias) {
|
||||
Object.keys(omdexAliases.aliases || {}).forEach((alias) => {
|
||||
if (alias === index) {
|
||||
return;
|
||||
} // alias which is identical to index means no index.
|
||||
|
@ -269,7 +277,7 @@ export function clear() {
|
|||
componentTemplates = [];
|
||||
}
|
||||
|
||||
function retrieveSettings(settingsKey, settingsToRetrieve) {
|
||||
function retrieveSettings(http, settingsKey, settingsToRetrieve) {
|
||||
const settingKeyToPathMap = {
|
||||
fields: '_mapping',
|
||||
indices: '_aliases',
|
||||
|
@ -278,22 +286,23 @@ function retrieveSettings(settingsKey, settingsToRetrieve) {
|
|||
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 WITH_PRODUCT_ORIGIN = true;
|
||||
const method = 'GET';
|
||||
const asSystemRequest = true;
|
||||
const withProductOrigin = true;
|
||||
|
||||
return es.send('GET', path, null, true, WITH_PRODUCT_ORIGIN);
|
||||
return es.send({ http, method, path, asSystemRequest, withProductOrigin });
|
||||
} else {
|
||||
const settingsPromise = new $.Deferred();
|
||||
if (settingsToRetrieve[settingsKey] === false) {
|
||||
// If the user doesn't want autocomplete suggestions, then clear any that exist
|
||||
return settingsPromise.resolveWith(this, [[JSON.stringify({})]]);
|
||||
return Promise.resolve({});
|
||||
// return settingsPromise.resolveWith(this, [{}]);
|
||||
} else {
|
||||
// If the user doesn't want autocomplete suggestions, then clear any that exist
|
||||
return settingsPromise.resolve();
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -315,12 +324,65 @@ export function clearSubscriptions() {
|
|||
}
|
||||
}
|
||||
|
||||
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(settings, settingsToRetrieve) {
|
||||
export function retrieveAutoCompleteInfo(http, settings, settingsToRetrieve) {
|
||||
clearSubscriptions();
|
||||
|
||||
const templatesSettingToRetrieve = {
|
||||
|
@ -330,73 +392,18 @@ export function retrieveAutoCompleteInfo(settings, settingsToRetrieve) {
|
|||
componentTemplates: settingsToRetrieve.templates,
|
||||
};
|
||||
|
||||
const mappingPromise = retrieveSettings('fields', settingsToRetrieve);
|
||||
const aliasesPromise = retrieveSettings('indices', settingsToRetrieve);
|
||||
const legacyTemplatesPromise = retrieveSettings('legacyTemplates', templatesSettingToRetrieve);
|
||||
const indexTemplatesPromise = retrieveSettings('indexTemplates', templatesSettingToRetrieve);
|
||||
const componentTemplatesPromise = retrieveSettings(
|
||||
'componentTemplates',
|
||||
templatesSettingToRetrieve
|
||||
);
|
||||
const dataStreamsPromise = retrieveSettings('dataStreams', settingsToRetrieve);
|
||||
|
||||
$.when(
|
||||
mappingPromise,
|
||||
aliasesPromise,
|
||||
legacyTemplatesPromise,
|
||||
indexTemplatesPromise,
|
||||
componentTemplatesPromise,
|
||||
dataStreamsPromise
|
||||
).done((mappings, aliases, legacyTemplates, indexTemplates, componentTemplates, dataStreams) => {
|
||||
let mappingsResponse;
|
||||
try {
|
||||
if (mappings && mappings.length) {
|
||||
const maxMappingSize = mappings[0].length > 10 * 1024 * 1024;
|
||||
if (maxMappingSize) {
|
||||
console.warn(
|
||||
`Mapping size is larger than 10MB (${mappings[0].length / 1024 / 1024} MB). Ignoring...`
|
||||
);
|
||||
mappingsResponse = '[{}]';
|
||||
} else {
|
||||
mappingsResponse = mappings[0];
|
||||
}
|
||||
loadMappings(JSON.parse(mappingsResponse));
|
||||
}
|
||||
|
||||
if (aliases) {
|
||||
loadAliases(JSON.parse(aliases[0]));
|
||||
}
|
||||
|
||||
if (legacyTemplates) {
|
||||
loadLegacyTemplates(JSON.parse(legacyTemplates[0]));
|
||||
}
|
||||
|
||||
if (indexTemplates) {
|
||||
loadIndexTemplates(JSON.parse(indexTemplates[0]));
|
||||
}
|
||||
|
||||
if (componentTemplates) {
|
||||
loadComponentTemplates(JSON.parse(componentTemplates[0]));
|
||||
}
|
||||
|
||||
if (dataStreams) {
|
||||
loadDataStreams(JSON.parse(dataStreams[0]));
|
||||
}
|
||||
|
||||
if (mappings && aliases) {
|
||||
// Trigger an update event with the mappings, aliases
|
||||
$(mappingObj).trigger('update', [mappingsResponse, aliases[0]]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
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(settings, settings.getAutocomplete());
|
||||
retrieveAutoCompleteInfo(http, settings, settings.getAutocomplete());
|
||||
}
|
||||
}, settings.getPollInterval());
|
||||
});
|
||||
|
|
|
@ -49,7 +49,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
});
|
||||
|
||||
it('default request response should include `"timed_out" : false`', async () => {
|
||||
const expectedResponseContains = '"timed_out" : false,';
|
||||
const expectedResponseContains = `"timed_out": false`;
|
||||
await PageObjects.console.clickPlay();
|
||||
await retry.try(async () => {
|
||||
const actualResponse = await PageObjects.console.getResponse();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue