Added promise with cancel with typescript support (#36293)
* Added promise with cancel * Fixed imports * Fixed an async unit test
This commit is contained in:
parent
cb928977a3
commit
851b1f6955
70
x-pack/plugins/monitoring/common/cancel_promise.ts
Normal file
70
x-pack/plugins/monitoring/common/cancel_promise.ts
Normal 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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
|
@ -8,6 +8,7 @@ import { spy, stub } from 'sinon';
|
||||||
import expect from '@kbn/expect';
|
import expect from '@kbn/expect';
|
||||||
import { MonitoringViewBaseController } from '../';
|
import { MonitoringViewBaseController } from '../';
|
||||||
import { timefilter } from 'ui/timefilter';
|
import { timefilter } from 'ui/timefilter';
|
||||||
|
import { PromiseWithCancel, Status } from '../../../common/cancel_promise';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Mostly copied from base_table_controller test, with modifications
|
* Mostly copied from base_table_controller test, with modifications
|
||||||
|
@ -20,6 +21,7 @@ describe('MonitoringViewBaseController', function () {
|
||||||
let opts;
|
let opts;
|
||||||
let titleService;
|
let titleService;
|
||||||
let executorService;
|
let executorService;
|
||||||
|
const httpCall = (ms) => new Promise((resolve) => setTimeout(() => resolve(), ms));
|
||||||
|
|
||||||
before(() => {
|
before(() => {
|
||||||
titleService = spy();
|
titleService = spy();
|
||||||
|
@ -36,7 +38,8 @@ describe('MonitoringViewBaseController', function () {
|
||||||
|
|
||||||
$scope = {
|
$scope = {
|
||||||
cluster: { cluster_uuid: 'foo' },
|
cluster: { cluster_uuid: 'foo' },
|
||||||
$on: stub()
|
$on: stub(),
|
||||||
|
$apply: stub()
|
||||||
};
|
};
|
||||||
|
|
||||||
opts = {
|
opts = {
|
||||||
|
@ -73,17 +76,15 @@ describe('MonitoringViewBaseController', function () {
|
||||||
let counter = 0;
|
let counter = 0;
|
||||||
const opts = {
|
const opts = {
|
||||||
title: 'testo',
|
title: 'testo',
|
||||||
getPageData: () => Promise.resolve(++counter),
|
getPageData: (ms) => httpCall(ms),
|
||||||
$injector,
|
$injector,
|
||||||
$scope
|
$scope
|
||||||
};
|
};
|
||||||
|
|
||||||
const ctrl = new MonitoringViewBaseController(opts);
|
const ctrl = new MonitoringViewBaseController(opts);
|
||||||
Promise.all([
|
ctrl.updateData(30).then(() => ++counter);
|
||||||
ctrl.updateData(),
|
ctrl.updateData(60).then(() => {
|
||||||
ctrl.updateData(),
|
expect(counter).to.be(0);
|
||||||
]).then(() => {
|
|
||||||
expect(counter).to.be(1);
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -140,6 +141,29 @@ describe('MonitoringViewBaseController', function () {
|
||||||
expect(timefilter.isTimeRangeSelectorEnabled).to.be(false);
|
expect(timefilter.isTimeRangeSelectorEnabled).to.be(false);
|
||||||
expect(timefilter.isAutoRefreshSelectorEnabled).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();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { getPageData } from '../lib/get_page_data';
|
||||||
import { PageLoading } from 'plugins/monitoring/components';
|
import { PageLoading } from 'plugins/monitoring/components';
|
||||||
import { timefilter } from 'ui/timefilter';
|
import { timefilter } from 'ui/timefilter';
|
||||||
import { I18nContext } from 'ui/i18n';
|
import { I18nContext } from 'ui/i18n';
|
||||||
|
import { PromiseWithCancel } from '../../common/cancel_promise';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class to manage common instantiation behaviors in a view controller
|
* Class to manage common instantiation behaviors in a view controller
|
||||||
|
@ -96,23 +97,21 @@ export class MonitoringViewBaseController {
|
||||||
timefilter.enableAutoRefreshSelector();
|
timefilter.enableAutoRefreshSelector();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateDataPromise = null;
|
|
||||||
this.updateData = () => {
|
this.updateData = () => {
|
||||||
if (this.updateDataPromise) {
|
if (this.updateDataPromise) {
|
||||||
// Do not sent another request if one is inflight
|
// Do not sent another request if one is inflight
|
||||||
// See https://github.com/elastic/kibana/issues/24082
|
// See https://github.com/elastic/kibana/issues/24082
|
||||||
return this.updateDataPromise;
|
this.updateDataPromise.cancel();
|
||||||
|
this.updateDataPromise = null;
|
||||||
}
|
}
|
||||||
const _api = apiUrlFn ? apiUrlFn() : api;
|
const _api = apiUrlFn ? apiUrlFn() : api;
|
||||||
return this.updateDataPromise = _getPageData($injector, _api)
|
this.updateDataPromise = new PromiseWithCancel(_getPageData($injector, _api));
|
||||||
.then(pageData => {
|
return this.updateDataPromise.promise().then((pageData) => {
|
||||||
|
$scope.$apply(() => {
|
||||||
this._isDataInitialized = true; // render will replace loading screen with the react component
|
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
|
$scope.pageData = this.data = pageData; // update the view's data with the fetch result
|
||||||
this.updateDataPromise = null;
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
this.updateDataPromise = null;
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
this.updateData();
|
this.updateData();
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,9 @@
|
||||||
],
|
],
|
||||||
"test_utils/*": [
|
"test_utils/*": [
|
||||||
"x-pack/test_utils/*"
|
"x-pack/test_utils/*"
|
||||||
|
],
|
||||||
|
"monitoring/common/*": [
|
||||||
|
"x-pack/monitoring/common/*"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"types": [
|
"types": [
|
||||||
|
|
Loading…
Reference in a new issue