From a5c45a32b4ac8b3346fb9584e3fe8d30942ba829 Mon Sep 17 00:00:00 2001 From: Kishor Rathva Date: Fri, 15 Dec 2023 00:34:49 +0530 Subject: [PATCH] fix: Dev tools console autocomplete issue #5567 (#5568) * fix: Dev tools console autocomplete issue #5567 * Added CHANGELOG * Added test for retrieveAutoCompleteInfo * Refactored tests * Added suggested changes. --------- Signed-off-by: Kishor Rathva --- CHANGELOG.md | 1 + .../send_request_to_opensearch.test.ts | 49 +-------- .../lib/mappings/__tests__/mapping.test.ts | 99 +++++++++++++++++++ .../console/public/lib/mappings/mappings.ts | 34 +++---- .../lib/opensearch/http_response.mock.ts | 44 +++++++++ 5 files changed, 163 insertions(+), 64 deletions(-) create mode 100644 src/plugins/console/public/lib/opensearch/http_response.mock.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 102205566e80..87aa8c065eac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -514,6 +514,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - [VisBuilder] Fix Firefox legend selection issue ([#3732](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3732)) - [VisBuilder] Fix type errors ([#3732](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3732)) - [VisBuilder] Fix indexpattern selection in filter bar ([#3751](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3751)) +- [Console] Fix dev tool console autocomplete not loading issue for aliases ([#5568](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5568)) ### 🚞 Infrastructure diff --git a/src/plugins/console/public/application/hooks/use_send_current_request_to_opensearch/send_request_to_opensearch.test.ts b/src/plugins/console/public/application/hooks/use_send_current_request_to_opensearch/send_request_to_opensearch.test.ts index fbeadf4cc7de..47a73e4af98a 100644 --- a/src/plugins/console/public/application/hooks/use_send_current_request_to_opensearch/send_request_to_opensearch.test.ts +++ b/src/plugins/console/public/application/hooks/use_send_current_request_to_opensearch/send_request_to_opensearch.test.ts @@ -9,53 +9,14 @@ * GitHub history for details. */ -import { - HttpFetchError, - HttpFetchOptionsWithPath, - HttpResponse, - HttpSetup, -} from '../../../../../../core/public'; +import { HttpFetchError, HttpSetup } from '../../../../../../core/public'; import { OpenSearchRequestArgs, sendRequestToOpenSearch } from './send_request_to_opensearch'; import * as opensearch from '../../../lib/opensearch/opensearch'; +import { + createMockHttpResponse, + createMockResponse, +} from '../../../lib/opensearch/http_response.mock'; -const createMockResponse = ( - statusCode: number, - statusText: string, - headers: Array<[string, string]> -): Response => { - return { - // headers: {} as Headers, - headers: new Headers(headers), - ok: true, - redirected: false, - status: statusCode, - statusText, - type: 'basic', - url: '', - clone: jest.fn(), - body: (jest.fn() as unknown) as ReadableStream, - bodyUsed: true, - arrayBuffer: jest.fn(), - blob: jest.fn(), - text: jest.fn(), - formData: jest.fn(), - json: jest.fn(), - }; -}; - -const createMockHttpResponse = ( - statusCode: number, - statusText: string, - headers: Array<[string, string]>, - body: any -): HttpResponse => { - return { - fetchOptions: (jest.fn() as unknown) as Readonly, - request: (jest.fn() as unknown) as Readonly, - response: createMockResponse(statusCode, statusText, headers), - body, - }; -}; const dummyArgs: OpenSearchRequestArgs = { http: ({ post: jest.fn(), diff --git a/src/plugins/console/public/lib/mappings/__tests__/mapping.test.ts b/src/plugins/console/public/lib/mappings/__tests__/mapping.test.ts index b5b1975a6a5c..8e023df6c1ce 100644 --- a/src/plugins/console/public/lib/mappings/__tests__/mapping.test.ts +++ b/src/plugins/console/public/lib/mappings/__tests__/mapping.test.ts @@ -30,7 +30,10 @@ import { Field } from '../mappings'; import '../../../application/models/sense_editor/sense_editor.test.mocks'; +import * as opensearch from '../../opensearch/opensearch'; import * as mappings from '../mappings'; +import { createMockHttpResponse } from '../../opensearch/http_response.mock'; +import { serviceContextMock } from '../../../application/contexts/services_context.mock'; describe('Mappings', () => { beforeEach(() => { @@ -291,3 +294,99 @@ describe('Mappings', () => { expect(mappings.getTypes()).toEqual(['properties']); }); }); + +describe('Auto Complete Info', () => { + let response = {}; + + const mockHttpResponse = createMockHttpResponse( + 200, + 'ok', + [['Content-Type', 'application/json, utf-8']], + response + ); + + beforeEach(() => { + mappings.clear(); + response = { + body: { + 'sample-ecommerce': { + mappings: { + properties: { + timestamp: { + type: 'date', + }, + }, + }, + }, + }, + }; + jest.resetAllMocks(); + jest.useFakeTimers(); // Enable automatic mocking of timers + }); + + afterEach(() => { + jest.clearAllMocks(); + jest.useRealTimers(); + }); + + test('Retrieve AutoComplete Info for Mappings, Aliases and Templates', async () => { + const dataSourceId = 'mock-data-source-id'; + const sendSpy = jest.spyOn(opensearch, 'send').mockResolvedValue(mockHttpResponse); + + const { + services: { http, settings: settingsService }, + } = serviceContextMock.create(); + + mappings.retrieveAutoCompleteInfo( + http, + settingsService, + { + fields: true, + indices: true, + templates: true, + }, + dataSourceId + ); + + // Fast-forward until all timers have been executed + jest.runAllTimers(); + + expect(sendSpy).toHaveBeenCalledTimes(3); + + // Ensure send is called with different arguments + expect(sendSpy).toHaveBeenCalledWith(http, 'GET', '_mapping', null, dataSourceId); + expect(sendSpy).toHaveBeenCalledWith(http, 'GET', '_aliases', null, dataSourceId); + expect(sendSpy).toHaveBeenCalledWith(http, 'GET', '_template', null, dataSourceId); + }); + + test('Retrieve AutoComplete Info for Specified Fields from the Settings', async () => { + const dataSourceId = 'mock-data-source-id'; + const sendSpy = jest.spyOn(opensearch, 'send').mockResolvedValue(mockHttpResponse); + + const { + services: { http, settings: settingsService }, + } = serviceContextMock.create(); + + mappings.retrieveAutoCompleteInfo( + http, + settingsService, + { + fields: true, + indices: false, + templates: false, + }, + dataSourceId + ); + + // Fast-forward until all timers have been executed + jest.runAllTimers(); + + // Resolve the promise chain + await Promise.resolve(); + + expect(sendSpy).toHaveBeenCalledTimes(1); + + // Ensure send is called with different arguments + expect(sendSpy).toHaveBeenCalledWith(http, 'GET', '_mapping', null, dataSourceId); + }); +}); diff --git a/src/plugins/console/public/lib/mappings/mappings.ts b/src/plugins/console/public/lib/mappings/mappings.ts index 2bd425ade95e..4cc188a9875f 100644 --- a/src/plugins/console/public/lib/mappings/mappings.ts +++ b/src/plugins/console/public/lib/mappings/mappings.ts @@ -76,9 +76,15 @@ interface IndexAliases { type IndicesOrAliases = string | string[] | null; +const SETTING_KEY_TO_PATH_MAP = { + fields: '_mapping', + indices: '_aliases', + templates: '_template', +}; + // NOTE: If this value ever changes to be a few seconds or less, it might introduce flakiness // due to timing issues in our app.js tests. -const POLL_INTERVAL = 60000; +export const POLL_INTERVAL = 60000; let pollTimeoutId: NodeJS.Timeout | null; let perIndexTypes: { [index: string]: { [type: string]: Field[] } } = {}; @@ -316,27 +322,16 @@ export function clear() { function retrieveSettings( http: HttpSetup, - settingsKey: string, + settingsKey: keyof typeof SETTING_KEY_TO_PATH_MAP, settingsToRetrieve: any, dataSourceId: string -): Promise> | Promise | Promise<{}> { - const settingKeyToPathMap: { [settingsKey: string]: string } = { - fields: '_mapping', - indices: '_aliases', - templates: '_template', - }; - +): Promise> | Promise { // Fetch autocomplete info if setting is set to true, and if user has made changes. if (settingsToRetrieve[settingsKey] === true) { - return opensearch.send(http, 'GET', settingKeyToPathMap[settingsKey], null, dataSourceId); + return opensearch.send(http, 'GET', SETTING_KEY_TO_PATH_MAP[settingsKey], null, dataSourceId); } else { - if (settingsToRetrieve[settingsKey] === false) { - // If the user doesn't want autocomplete suggestions, then clear any that exist - return Promise.resolve({}); - } else { - // If the user doesn't want autocomplete suggestions, then clear any that exist - return Promise.resolve(); - } + // If the user doesn't want autocomplete suggestions, then clear any that exist + return Promise.resolve(); } } @@ -385,7 +380,7 @@ const retrieveMappings = async (http: HttpSetup, settingsToRetrieve: any, dataSo }; const retrieveAliases = async (http: HttpSetup, settingsToRetrieve: any, dataSourceId: string) => { - const response = await retrieveSettings(http, 'fields', settingsToRetrieve, dataSourceId); + const response = await retrieveSettings(http, 'indices', settingsToRetrieve, dataSourceId); if (isHttpResponse(response) && response.body) { const aliases = response.body as IndexAliases; @@ -398,7 +393,7 @@ const retrieveTemplates = async ( settingsToRetrieve: any, dataSourceId: string ) => { - const response = await retrieveSettings(http, 'fields', settingsToRetrieve, dataSourceId); + const response = await retrieveSettings(http, 'templates', settingsToRetrieve, dataSourceId); if (isHttpResponse(response) && response.body) { const resTemplates = response.body; @@ -418,7 +413,6 @@ export function retrieveAutoCompleteInfo( dataSourceId: string ) { clearSubscriptions(); - Promise.allSettled([ retrieveMappings(http, settingsToRetrieve, dataSourceId), retrieveAliases(http, settingsToRetrieve, dataSourceId), diff --git a/src/plugins/console/public/lib/opensearch/http_response.mock.ts b/src/plugins/console/public/lib/opensearch/http_response.mock.ts new file mode 100644 index 000000000000..3ee40be979ab --- /dev/null +++ b/src/plugins/console/public/lib/opensearch/http_response.mock.ts @@ -0,0 +1,44 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { HttpFetchOptionsWithPath, HttpResponse } from '../../../../../core/public'; + +export const createMockResponse = ( + statusCode: number, + statusText: string, + headers: Array<[string, string]> +): Response => { + return { + headers: new Headers(headers), + ok: true, + redirected: false, + status: statusCode, + statusText, + type: 'basic', + url: '', + clone: jest.fn(), + body: (jest.fn() as unknown) as ReadableStream, + bodyUsed: true, + arrayBuffer: jest.fn(), + blob: jest.fn(), + text: jest.fn(), + formData: jest.fn(), + json: jest.fn(), + }; +}; + +export const createMockHttpResponse = ( + statusCode: number, + statusText: string, + headers: Array<[string, string]>, + body: any +): HttpResponse => { + return { + fetchOptions: (jest.fn() as unknown) as Readonly, + request: (jest.fn() as unknown) as Readonly, + response: createMockResponse(statusCode, statusText, headers), + body, + }; +};