Added promise with cancel with typescript support (#36293)

* Added promise with cancel

* Fixed imports

* Fixed an async unit test
This commit is contained in:
igoristic 2019-05-23 02:02:00 -04:00 committed by GitHub
parent cb928977a3
commit 851b1f6955
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 111 additions and 15 deletions

View file

@ -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<any>;
private _status: Status = Status.Idle;
/**
* @param {Promise} promise Promise you want to cancel / track
*/
constructor(promise: Promise<any>) {
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<any> => {
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);
}
});
});
};
}

View file

@ -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();
});
});
});
});

View file

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

View file

@ -28,6 +28,9 @@
],
"test_utils/*": [
"x-pack/test_utils/*"
],
"monitoring/common/*": [
"x-pack/monitoring/common/*"
]
},
"types": [