diff --git a/package.json b/package.json index 48e1e3459f2fa..c48f499d589a5 100644 --- a/package.json +++ b/package.json @@ -88,6 +88,7 @@ "@kbn/test-subj-selector": "link:packages/kbn-test-subj-selector", "@kbn/ui-framework": "link:packages/kbn-ui-framework", "JSONStream": "1.1.1", + "abortcontroller-polyfill": "^1.1.9", "angular": "1.6.9", "angular-aria": "1.6.6", "angular-elastic": "2.5.0", diff --git a/src/ui/public/chrome/chrome.js b/src/ui/public/chrome/chrome.js index 2042c0070eceb..54bf84fe126db 100644 --- a/src/ui/public/chrome/chrome.js +++ b/src/ui/public/chrome/chrome.js @@ -21,9 +21,13 @@ import _ from 'lodash'; import angular from 'angular'; import { metadata } from '../metadata'; + +// Polyfills import 'babel-polyfill'; import 'whatwg-fetch'; import 'custom-event-polyfill'; +import 'abortcontroller-polyfill'; + import '../state_management/global_state'; import '../config'; import '../notify'; diff --git a/src/ui/public/kfetch/index.js b/src/ui/public/kfetch/index.js index 173c15dcfddf2..ab1cadf0c2371 100644 --- a/src/ui/public/kfetch/index.js +++ b/src/ui/public/kfetch/index.js @@ -17,57 +17,5 @@ * under the License. */ -import 'isomorphic-fetch'; -import url from 'url'; -import chrome from '../chrome'; -import { metadata } from '../metadata'; -import { merge } from 'lodash'; - -class FetchError extends Error { - constructor(res, body) { - super(res.statusText); - this.res = res; - this.body = body; - Error.captureStackTrace(this, FetchError); - } -} - -export async function kfetch(fetchOptions, kibanaOptions) { - // fetch specific options with defaults - const { pathname, query, ...combinedFetchOptions } = merge( - { - method: 'GET', - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json', - 'kbn-version': metadata.version, - }, - }, - fetchOptions - ); - - // kibana specific options with defaults - const combinedKibanaOptions = { - prependBasePath: true, - ...kibanaOptions, - }; - - const fullUrl = url.format({ - pathname: combinedKibanaOptions.prependBasePath ? chrome.addBasePath(pathname) : pathname, - query, - }); - - const res = await fetch(fullUrl, combinedFetchOptions); - - if (!res.ok) { - let body; - try { - body = await res.json(); - } catch (err) { - // ignore error, may not be able to get body for response that is not ok - } - throw new FetchError(res, body); - } - - return res.json(); -} +export { kfetch } from './kfetch'; +export { kfetchAbortable } from './kfetch_abortable'; diff --git a/src/ui/public/kfetch/kfetch.js b/src/ui/public/kfetch/kfetch.js new file mode 100644 index 0000000000000..af9c849f88c62 --- /dev/null +++ b/src/ui/public/kfetch/kfetch.js @@ -0,0 +1,77 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import 'isomorphic-fetch'; +import url from 'url'; +import chrome from '../chrome'; +import { metadata } from '../metadata'; +import { merge } from 'lodash'; + +class FetchError extends Error { + constructor(res, body) { + super(res.statusText); + this.res = res; + this.body = body; + Error.captureStackTrace(this, FetchError); + } +} + +export function kfetch(fetchOptions, kibanaOptions) { + // fetch specific options with defaults + const { pathname, query, ...combinedFetchOptions } = merge( + { + method: 'GET', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + 'kbn-version': metadata.version, + }, + }, + fetchOptions, + ); + + // kibana specific options with defaults + const combinedKibanaOptions = { + prependBasePath: true, + ...kibanaOptions, + }; + + const fullUrl = url.format({ + pathname: combinedKibanaOptions.prependBasePath ? chrome.addBasePath(pathname) : pathname, + query, + }); + + const fetching = new Promise(async (resolve, reject) => { + const res = await fetch(fullUrl, combinedFetchOptions); + + if (!res.ok) { + let body; + try { + body = await res.json(); + } catch (err) { + // ignore error, may not be able to get body for response that is not ok + } + return reject(new FetchError(res, body)); + } + + resolve(res.json()); + }); + + return fetching; +} diff --git a/src/ui/public/kfetch/index.test.js b/src/ui/public/kfetch/kfetch.test.js similarity index 98% rename from src/ui/public/kfetch/index.test.js rename to src/ui/public/kfetch/kfetch.test.js index 9b0ffae7c6ab6..0bd9f28e8bcd6 100644 --- a/src/ui/public/kfetch/index.test.js +++ b/src/ui/public/kfetch/kfetch.test.js @@ -18,11 +18,12 @@ */ import fetchMock from 'fetch-mock'; -import { kfetch } from './index'; +import { kfetch } from './kfetch'; jest.mock('../chrome', () => ({ addBasePath: path => `myBase/${path}`, })); + jest.mock('../metadata', () => ({ metadata: { version: 'my-version', diff --git a/src/ui/public/kfetch/kfetch_abortable.js b/src/ui/public/kfetch/kfetch_abortable.js new file mode 100644 index 0000000000000..8bd732d2ab5f6 --- /dev/null +++ b/src/ui/public/kfetch/kfetch_abortable.js @@ -0,0 +1,40 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { kfetch } from './kfetch'; + +function createAbortable() { + const abortController = new AbortController(); + const { signal, abort } = abortController; + + return { + signal, + abort: abort.bind(abortController), + }; +} + +export function kfetchAbortable(fetchOptions, kibanaOptions) { + const { signal, abort } = createAbortable(); + const fetching = kfetch({ ...fetchOptions, signal }, kibanaOptions); + + return { + fetching, + abort, + }; +} diff --git a/src/ui/public/kfetch/kfetch_abortable.test.js b/src/ui/public/kfetch/kfetch_abortable.test.js new file mode 100644 index 0000000000000..053f231b0f965 --- /dev/null +++ b/src/ui/public/kfetch/kfetch_abortable.test.js @@ -0,0 +1,39 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { kfetchAbortable } from './kfetch_abortable'; + +jest.mock('../chrome', () => ({ + addBasePath: path => `myBase/${path}`, +})); + +jest.mock('../metadata', () => ({ + metadata: { + version: 'my-version', + }, +})); + +describe('kfetchAbortable', () => { + it('should return an object with a fetching promise and an abort callback', () => { + const { fetching, abort } = kfetchAbortable({ pathname: 'my/path' }); + expect(typeof fetching.then).toBe('function'); + expect(typeof fetching.catch).toBe('function'); + expect(typeof abort).toBe('function'); + }); +}); diff --git a/yarn.lock b/yarn.lock index 348d0dafe3790..36ed0c2ab6f61 100644 --- a/yarn.lock +++ b/yarn.lock @@ -549,6 +549,10 @@ abbrev@1.0.x: version "1.0.9" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135" +abortcontroller-polyfill@^1.1.9: + version "1.1.9" + resolved "https://registry.yarnpkg.com/abortcontroller-polyfill/-/abortcontroller-polyfill-1.1.9.tgz#9fefe359fda2e9e0932dc85e6106453ac393b2da" + accept-language-parser@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/accept-language-parser/-/accept-language-parser-1.5.0.tgz#8877c54040a8dcb59e0a07d9c1fde42298334791"