From a5c45a32b4ac8b3346fb9584e3fe8d30942ba829 Mon Sep 17 00:00:00 2001
From: Kishor Rathva <kishorrathva8298@gmail.com>
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 <kishorrathva8298@gmail.com>
---
 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<any> => {
-  return {
-    fetchOptions: (jest.fn() as unknown) as Readonly<HttpFetchOptionsWithPath>,
-    request: (jest.fn() as unknown) as Readonly<Request>,
-    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<HttpResponse<any>> | Promise<void> | Promise<{}> {
-  const settingKeyToPathMap: { [settingsKey: string]: string } = {
-    fields: '_mapping',
-    indices: '_aliases',
-    templates: '_template',
-  };
-
+): Promise<HttpResponse<any>> | Promise<void> {
   // 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<any> => {
+  return {
+    fetchOptions: (jest.fn() as unknown) as Readonly<HttpFetchOptionsWithPath>,
+    request: (jest.fn() as unknown) as Readonly<Request>,
+    response: createMockResponse(statusCode, statusText, headers),
+    body,
+  };
+};