Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update console to use core.http instead of jQuery.ajax #3080

Merged
merged 4 commits into from
Dec 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,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);
kristenTian marked this conversation as resolved.
Show resolved Hide resolved
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);
kristenTian marked this conversation as resolved.
Show resolved Hide resolved
} 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