Skip to content
This repository has been archived by the owner on Mar 25, 2024. It is now read-only.

Commit

Permalink
Update console to use core.http instead of jQuery.ajax (opensearch-pr…
Browse files Browse the repository at this point in the history
…oject#3080)

* Update console to use core.http instead of jQuery.ajax

Signed-off-by: Yan Zeng <zengyan@amazon.com>
Signed-off-by: David Sinclair <david@sinclair.tech>
  • Loading branch information
zengyan-amazon authored and sikhote committed Apr 24, 2023
1 parent deb9eda commit 0748c06
Show file tree
Hide file tree
Showing 8 changed files with 293 additions and 104 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
- [MD] Refactor data source error handling ([#2661](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2661))
- Refactor and improve Discover field summaries ([#2391](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2391))
- [Vis Builder] Removed Hard Coded Strings and Used i18n to transalte([#2867](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2867))
- [Console] Replace jQuery.ajax with core.http when calling OSD APIs in console ([#3080]https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3080))

### 🔩 Tests

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
*/

import React, { createContext, useContext, useEffect } from 'react';
import { NotificationsSetup } from 'opensearch-dashboards/public';
import { HttpSetup, NotificationsSetup } from 'opensearch-dashboards/public';
import { History, Settings, Storage } from '../../services';
import { ObjectStorageClient } from '../../../common/types';
import { MetricsTracker } from '../../types';
Expand All @@ -43,6 +43,7 @@ interface ContextServices {
objectStorageClient: ObjectStorageClient;
trackUiMetric: MetricsTracker;
opensearchHostService: OpenSearchHostService;
http: HttpSetup;
}

export interface ContextValue {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*
* Any modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/

import {
HttpFetchError,
HttpFetchOptionsWithPath,
HttpResponse,
HttpSetup,
} from '../../../../../../core/public';
import { OpenSearchRequestArgs, sendRequestToOpenSearch } from './send_request_to_opensearch';
import * as opensearch from '../../../lib/opensearch/opensearch';

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(),
} as unknown) as HttpSetup,
requests: [
{
method: 'GET',
url: '/dummy/api',
data: ['{}'],
},
],
};

describe('test sendRequestToOpenSearch', () => {
it('test request success, json', () => {
const mockHttpResponse = createMockHttpResponse(
200,
'ok',
[['Content-Type', 'application/json, utf-8']],
{
ok: true,
}
);

jest.spyOn(opensearch, 'send').mockResolvedValue(mockHttpResponse);
sendRequestToOpenSearch(dummyArgs).then((result) => {
expect((result as any)[0].response.value).toBe('{\n "ok": true\n}');
});
});

it('test request success, text', () => {
const mockHttpResponse = createMockHttpResponse(
200,
'ok',
[['Content-Type', 'text/plain']],
'response text'
);

jest.spyOn(opensearch, 'send').mockResolvedValue(mockHttpResponse);
sendRequestToOpenSearch(dummyArgs).then((result) => {
expect((result as any)[0].response.value).toBe('response text');
});
});

it('test request success, with warning', () => {
const mockHttpResponse = createMockHttpResponse(
200,
'ok',
[
['Content-Type', 'text/plain'],
['warning', 'dummy warning'],
],
'response text'
);

jest.spyOn(opensearch, 'send').mockResolvedValue(mockHttpResponse);
sendRequestToOpenSearch(dummyArgs).then((result) => {
expect((result as any)[0].response.value).toBe(
'#! Deprecation: dummy warning\nresponse text'
);
});
});

it('test request 404', () => {
const mockHttpResponse = createMockHttpResponse(
404,
'not found',
[['Content-Type', 'text/plain']],
'response text'
);

jest.spyOn(opensearch, 'send').mockResolvedValue(mockHttpResponse);
sendRequestToOpenSearch(dummyArgs).then((result) => {
expect((result as any)[0].response.value).toBe('response text');
});
});

it('test request 500, json', () => {
const mockHttpError: HttpFetchError = new HttpFetchError(
'error message',
'name',
(jest.fn as unknown) as Request,
createMockResponse(500, 'Server Error', [['Content-Type', 'application/json, utf-8']]),
{ errorMsg: 'message' }
);

jest.spyOn(opensearch, 'send').mockRejectedValue(mockHttpError);
sendRequestToOpenSearch(dummyArgs).catch((error) => {
expect(error.response.value).toBe('{\n "errorMsg": "message"\n}');
});
});

it('test request 500, text', () => {
const mockHttpError: HttpFetchError = new HttpFetchError(
'error message',
'name',
(jest.fn as unknown) as Request,
createMockResponse(500, 'Server Error', [['Content-Type', 'text/plain']]),
'error message'
);

jest.spyOn(opensearch, 'send').mockRejectedValue(mockHttpError);
sendRequestToOpenSearch(dummyArgs).catch((error) => {
expect(error.response.value).toBe('error message');
});
});

it('test no connection', () => {
const mockHttpError: HttpFetchError = new HttpFetchError(
'error message',
'name',
(jest.fn as unknown) as Request,
undefined,
'error message'
);

jest.spyOn(opensearch, 'send').mockRejectedValue(mockHttpError);
sendRequestToOpenSearch(dummyArgs).catch((error) => {
expect(error.response.value).toBe(
"\n\nFailed to connect to Console's backend.\nPlease check the OpenSearch Dashboards server is up and running"
);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
* under the License.
*/

import { HttpFetchError, HttpSetup } from 'opensearch-dashboards/public';
import { extractDeprecationMessages } from '../../../lib/utils';
import { XJson } from '../../../../../opensearch_ui_shared/public';
const { collapseLiteralStrings } = XJson;
Expand All @@ -36,6 +37,7 @@ import * as opensearch from '../../../lib/opensearch/opensearch';
import { BaseResponseType } from '../../../types';

export interface OpenSearchRequestArgs {
http: HttpSetup;
requests: any;
}

Expand Down Expand Up @@ -76,7 +78,7 @@ export function sendRequestToOpenSearch(

const isMultiRequest = requests.length > 1;

const sendNextRequest = () => {
const sendNextRequest = async () => {
if (reqId !== CURRENT_REQ_ID) {
resolve(results);
return;
Expand All @@ -94,79 +96,96 @@ export function sendRequestToOpenSearch(
} // append a new line for bulk requests.

const startTime = Date.now();
opensearch
.send(opensearchMethod, opensearchPath, opensearchData)
.always((dataOrjqXHR: any, textStatus: string, jqXhrORerrorThrown: any) => {
if (reqId !== CURRENT_REQ_ID) {
return;
}

const xhr = dataOrjqXHR.promise ? dataOrjqXHR : jqXhrORerrorThrown;

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);

if (isSuccess) {
let value = xhr.responseText;

const warnings = xhr.getResponseHeader('warning');
if (warnings) {
const deprecationMessages = extractDeprecationMessages(warnings);
value = deprecationMessages.join('\n') + '\n' + value;
}

if (isMultiRequest) {
value = '# ' + req.method + ' ' + req.url + '\n' + value;
}

results.push({
response: {
timeMs: Date.now() - startTime,
statusCode: xhr.status,
statusText: xhr.statusText,
contentType: xhr.getResponseHeader('Content-Type'),
value,
},
request: {
data: opensearchData,
method: opensearchMethod,
path: opensearchPath,
},
});

// single request terminate via sendNextRequest as well
sendNextRequest();
try {
const httpResponse = await opensearch.send(
args.http,
opensearchMethod,
opensearchPath,
opensearchData
);
if (reqId !== CURRENT_REQ_ID) {
return;
}
const statusCode = httpResponse.response?.status;
const isSuccess =
// Things like DELETE index where the index is not there are OK.
statusCode && ((statusCode >= 200 && statusCode < 300) || statusCode === 404);
if (isSuccess) {
const contentType = httpResponse.response.headers.get('Content-Type') as BaseResponseType;
let value = '';
if (contentType.includes('application/json')) {
value = JSON.stringify(httpResponse.body, null, 2);
} else {
let value;
let contentType: string;
if (xhr.responseText) {
value = xhr.responseText; // OpenSearch error should be shown
contentType = xhr.getResponseHeader('Content-Type');
value = httpResponse.body;
}
const warnings = httpResponse.response.headers.get('warning');
if (warnings) {
const deprecationMessages = extractDeprecationMessages(warnings);
value = deprecationMessages.join('\n') + '\n' + value;
}
if (isMultiRequest) {
value = '# ' + req.method + ' ' + req.url + '\n' + value;
}
results.push({
response: {
timeMs: Date.now() - startTime,
statusCode,
statusText: httpResponse.response.statusText,
contentType,
value,
},
request: {
data: opensearchData,
method: opensearchMethod,
path: opensearchPath,
},
});

// single request terminate via sendNextRequest as well
await sendNextRequest();
}
} catch (error) {
const httpError = error as HttpFetchError;
const httpResponse = httpError.response;
let value;
let contentType: string;
if (httpResponse) {
if (httpError.body) {
contentType = httpResponse.headers.get('Content-Type') as string;
if (contentType?.includes('application/json')) {
value = JSON.stringify(httpError.body, null, 2);
} 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;
value = httpError.body;
}
reject({
response: {
value,
contentType,
timeMs: Date.now() - startTime,
statusCode: xhr.status,
statusText: xhr.statusText,
},
request: {
data: opensearchData,
method: opensearchMethod,
path: opensearchPath,
},
});
} else {
value =
'Request failed to get to the server (status code: ' + httpResponse.status + ')';
contentType = 'text/plain';
}
} else {
value =
"\n\nFailed to connect to Console's backend.\nPlease check the OpenSearch Dashboards server is up and running";
contentType = 'text/plain';
}

if (isMultiRequest) {
value = '# ' + req.method + ' ' + req.url + '\n' + value;
}
reject({
response: {
value,
contentType,
timeMs: Date.now() - startTime,
statusCode: httpResponse?.status,
statusText: httpResponse?.statusText,
},
request: {
data: opensearchData,
method: opensearchMethod,
path: opensearchPath,
},
});
}
};

sendNextRequest();
Expand Down
Loading

0 comments on commit 0748c06

Please sign in to comment.