diff --git a/x-pack/plugins/monitoring/common/cancel_promise.ts b/x-pack/plugins/monitoring/common/cancel_promise.ts new file mode 100644 index 000000000000..f100edda5079 --- /dev/null +++ b/x-pack/plugins/monitoring/common/cancel_promise.ts @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export enum Status { + Canceled, + Failed, + Resolved, + Awaiting, + Idle, +} + +/** + * Simple [PromiseWithCancel] factory + */ +export class PromiseWithCancel { + private _promise: Promise; + private _status: Status = Status.Idle; + + /** + * @param {Promise} promise Promise you want to cancel / track + */ + constructor(promise: Promise) { + this._promise = promise; + } + + /** + * Cancel the promise in any state + */ + public cancel = (): void => { + this._status = Status.Canceled; + }; + + /** + * @returns status based on [Status] + */ + public status = (): Status => { + return this._status; + }; + + /** + * @returns promise passed in [constructor] + * This sets the state to Status.Awaiting + */ + public promise = (): Promise => { + if (this._status === Status.Canceled) { + throw Error('Getting a canceled promise is not allowed'); + } else if (this._status !== Status.Idle) { + return this._promise; + } + return new Promise((resolve, reject) => { + this._status = Status.Awaiting; + return this._promise + .then(response => { + if (this._status !== Status.Canceled) { + this._status = Status.Resolved; + return resolve(response); + } + }) + .catch(error => { + if (this._status !== Status.Canceled) { + this._status = Status.Failed; + return reject(error); + } + }); + }); + }; +} diff --git a/x-pack/plugins/monitoring/public/views/__tests__/base_controller.js b/x-pack/plugins/monitoring/public/views/__tests__/base_controller.js index ed9899183463..94ce9f890163 100644 --- a/x-pack/plugins/monitoring/public/views/__tests__/base_controller.js +++ b/x-pack/plugins/monitoring/public/views/__tests__/base_controller.js @@ -8,6 +8,7 @@ import { spy, stub } from 'sinon'; import expect from '@kbn/expect'; import { MonitoringViewBaseController } from '../'; import { timefilter } from 'ui/timefilter'; +import { PromiseWithCancel, Status } from '../../../common/cancel_promise'; /* * Mostly copied from base_table_controller test, with modifications @@ -20,6 +21,7 @@ describe('MonitoringViewBaseController', function () { let opts; let titleService; let executorService; + const httpCall = (ms) => new Promise((resolve) => setTimeout(() => resolve(), ms)); before(() => { titleService = spy(); @@ -36,7 +38,8 @@ describe('MonitoringViewBaseController', function () { $scope = { cluster: { cluster_uuid: 'foo' }, - $on: stub() + $on: stub(), + $apply: stub() }; opts = { @@ -73,17 +76,15 @@ describe('MonitoringViewBaseController', function () { let counter = 0; const opts = { title: 'testo', - getPageData: () => Promise.resolve(++counter), + getPageData: (ms) => httpCall(ms), $injector, $scope }; const ctrl = new MonitoringViewBaseController(opts); - Promise.all([ - ctrl.updateData(), - ctrl.updateData(), - ]).then(() => { - expect(counter).to.be(1); + ctrl.updateData(30).then(() => ++counter); + ctrl.updateData(60).then(() => { + expect(counter).to.be(0); done(); }); }); @@ -140,6 +141,29 @@ describe('MonitoringViewBaseController', function () { expect(timefilter.isTimeRangeSelectorEnabled).to.be(false); expect(timefilter.isAutoRefreshSelectorEnabled).to.be(false); }); + + it('disables timepicker and auto refresh', (done) => { + opts = { + title: 'test', + getPageData: () => httpCall(60), + $injector, + $scope + }; + + ctrl = new MonitoringViewBaseController({ ...opts }); + ctrl.updateDataPromise = new PromiseWithCancel(httpCall(50)); + + let shouldBeFalse = false; + ctrl.updateDataPromise.promise().then(() => (shouldBeFalse = true)); + + const lastUpdateDataPromise = ctrl.updateDataPromise; + + ctrl.updateData().then(() => { + expect(shouldBeFalse).to.be(false); + expect(lastUpdateDataPromise.status()).to.be(Status.Canceled); + done(); + }); + }); }); }); diff --git a/x-pack/plugins/monitoring/public/views/base_controller.js b/x-pack/plugins/monitoring/public/views/base_controller.js index 3c49dfa81f1e..0d7ad6e4352a 100644 --- a/x-pack/plugins/monitoring/public/views/base_controller.js +++ b/x-pack/plugins/monitoring/public/views/base_controller.js @@ -11,6 +11,7 @@ import { getPageData } from '../lib/get_page_data'; import { PageLoading } from 'plugins/monitoring/components'; import { timefilter } from 'ui/timefilter'; import { I18nContext } from 'ui/i18n'; +import { PromiseWithCancel } from '../../common/cancel_promise'; /** * Class to manage common instantiation behaviors in a view controller @@ -96,23 +97,21 @@ export class MonitoringViewBaseController { timefilter.enableAutoRefreshSelector(); } - this.updateDataPromise = null; this.updateData = () => { if (this.updateDataPromise) { // Do not sent another request if one is inflight // See https://github.com/elastic/kibana/issues/24082 - return this.updateDataPromise; + this.updateDataPromise.cancel(); + this.updateDataPromise = null; } const _api = apiUrlFn ? apiUrlFn() : api; - return this.updateDataPromise = _getPageData($injector, _api) - .then(pageData => { + this.updateDataPromise = new PromiseWithCancel(_getPageData($injector, _api)); + return this.updateDataPromise.promise().then((pageData) => { + $scope.$apply(() => { this._isDataInitialized = true; // render will replace loading screen with the react component $scope.pageData = this.data = pageData; // update the view's data with the fetch result - this.updateDataPromise = null; - }) - .catch(() => { - this.updateDataPromise = null; }); + }); }; this.updateData(); diff --git a/x-pack/tsconfig.json b/x-pack/tsconfig.json index 979d0737d03a..64d0cb0b2849 100644 --- a/x-pack/tsconfig.json +++ b/x-pack/tsconfig.json @@ -28,6 +28,9 @@ ], "test_utils/*": [ "x-pack/test_utils/*" + ], + "monitoring/common/*": [ + "x-pack/monitoring/common/*" ] }, "types": [