|
|
@@ -37,11 +37,9 @@ export const EXECUTION_STATUS = {
|
|
|
closed: 'closed'
|
|
|
};
|
|
|
|
|
|
-const notifyUpdates = executable => {
|
|
|
- huePubSub.publish('hue.executable.updated', executable);
|
|
|
-};
|
|
|
+const EXECUTABLE_UPDATED_EVENT = 'hue.executable.updated';
|
|
|
|
|
|
-export class ExecutableStatement {
|
|
|
+export default class Executable {
|
|
|
/**
|
|
|
* @param options
|
|
|
* @param {string} options.sourceType
|
|
|
@@ -54,123 +52,126 @@ export class ExecutableStatement {
|
|
|
*/
|
|
|
constructor(options) {
|
|
|
this.compute = options.compute;
|
|
|
- this.database = options.database;
|
|
|
this.namespace = options.namespace;
|
|
|
this.sourceType = options.sourceType;
|
|
|
- this.parsedStatement = options.parsedStatement;
|
|
|
- this.statement = options.statement;
|
|
|
+
|
|
|
this.handle = {
|
|
|
statement_id: 0 // TODO: Get rid of need for initial handle in the backend
|
|
|
};
|
|
|
-
|
|
|
- this.lastCancellable = undefined;
|
|
|
this.status = EXECUTION_STATUS.ready;
|
|
|
this.progress = 0;
|
|
|
+
|
|
|
+ this.lastCancellable = undefined;
|
|
|
+ this.notifyThrottle = -1;
|
|
|
}
|
|
|
|
|
|
- getStatement() {
|
|
|
- return this.statement || this.parsedStatement.statement;
|
|
|
+ setStatus(status) {
|
|
|
+ this.status = status;
|
|
|
+ this.notify();
|
|
|
}
|
|
|
|
|
|
- async execute() {
|
|
|
- return new Promise((resolve, reject) => {
|
|
|
- if (this.status !== EXECUTION_STATUS.ready) {
|
|
|
- reject();
|
|
|
- return;
|
|
|
- }
|
|
|
+ setProgress(progress) {
|
|
|
+ this.progress = progress;
|
|
|
+ this.notify();
|
|
|
+ }
|
|
|
|
|
|
- let statusCheckCount = 0;
|
|
|
- let checkStatusTimeout = -1;
|
|
|
-
|
|
|
- // TODO: Switch to async/await when we have cancellable Promise (not $.deferred)
|
|
|
- const checkStatus = () =>
|
|
|
- new Promise((statusResolve, statusReject) => {
|
|
|
- statusCheckCount++;
|
|
|
- this.lastCancellable = apiHelper
|
|
|
- .checkExecutionStatus({ executable: this })
|
|
|
- .done(queryStatus => {
|
|
|
- this.status = queryStatus;
|
|
|
- switch (this.status) {
|
|
|
- case 'success':
|
|
|
- this.progress = 99; // TODO: why 99 here (from old code)?
|
|
|
- statusResolve();
|
|
|
- break;
|
|
|
- case 'available':
|
|
|
- this.progress = 100;
|
|
|
- statusResolve();
|
|
|
- break;
|
|
|
- case 'expired':
|
|
|
- statusReject();
|
|
|
- break;
|
|
|
- case 'running':
|
|
|
- case 'starting':
|
|
|
- case 'waiting':
|
|
|
- notifyUpdates(this);
|
|
|
- checkStatusTimeout = window.setTimeout(
|
|
|
- () => {
|
|
|
- checkStatus()
|
|
|
- .then(statusResolve)
|
|
|
- .catch(statusReject);
|
|
|
- },
|
|
|
- statusCheckCount > 45 ? 5000 : 1000
|
|
|
- );
|
|
|
- break;
|
|
|
- default:
|
|
|
- console.warn('Got unknown status ' + queryStatus);
|
|
|
- statusReject();
|
|
|
- }
|
|
|
- })
|
|
|
- .fail(statusReject);
|
|
|
-
|
|
|
- this.lastCancellable.onCancel(() => {
|
|
|
- window.clearTimeout(checkStatusTimeout);
|
|
|
- });
|
|
|
- }).finally(() => {
|
|
|
- notifyUpdates(this);
|
|
|
- });
|
|
|
+ notify() {
|
|
|
+ window.clearTimeout(this.notifyThrottle);
|
|
|
+ this.notifyThrottle = window.setTimeout(() => {
|
|
|
+ huePubSub.publish(EXECUTABLE_UPDATED_EVENT, this);
|
|
|
+ }, 1);
|
|
|
+ }
|
|
|
|
|
|
- hueAnalytics.log('notebook', 'execute/' + this.sourceType);
|
|
|
- this.status = EXECUTION_STATUS.running;
|
|
|
- this.progress = 0;
|
|
|
+ async execute() {
|
|
|
+ if (this.status !== EXECUTION_STATUS.ready) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
- notifyUpdates(this);
|
|
|
+ let statusCheckCount = 0;
|
|
|
+ let checkStatusTimeout = -1;
|
|
|
|
|
|
- sessionManager.getSession({ type: this.sourceType }).then(session => {
|
|
|
+ const checkStatus = () =>
|
|
|
+ new Promise((statusResolve, statusReject) => {
|
|
|
+ statusCheckCount++;
|
|
|
this.lastCancellable = apiHelper
|
|
|
- .executeStatement({
|
|
|
- executable: this,
|
|
|
- session: session
|
|
|
- })
|
|
|
- .done(handle => {
|
|
|
- this.handle = handle;
|
|
|
-
|
|
|
- checkStatus()
|
|
|
- .then(() => {
|
|
|
- this.result = new ExecutionResult(this);
|
|
|
- resolve(this.result);
|
|
|
- })
|
|
|
- .catch(error => {
|
|
|
- reject(error);
|
|
|
- });
|
|
|
+ .checkExecutionStatus({ executable: this })
|
|
|
+ .done(queryStatus => {
|
|
|
+ switch (this.status) {
|
|
|
+ case EXECUTION_STATUS.success:
|
|
|
+ this.setStatus(queryStatus);
|
|
|
+ this.setProgress(99); // TODO: why 99 here (from old code)?
|
|
|
+ statusResolve();
|
|
|
+ break;
|
|
|
+ case EXECUTION_STATUS.available:
|
|
|
+ this.setStatus(queryStatus);
|
|
|
+ this.setProgress(100);
|
|
|
+ statusResolve();
|
|
|
+ break;
|
|
|
+ case EXECUTION_STATUS.expired:
|
|
|
+ this.setStatus(queryStatus);
|
|
|
+ statusReject();
|
|
|
+ break;
|
|
|
+ case EXECUTION_STATUS.running:
|
|
|
+ case EXECUTION_STATUS.starting:
|
|
|
+ case EXECUTION_STATUS.waiting:
|
|
|
+ this.setStatus(queryStatus);
|
|
|
+ checkStatusTimeout = window.setTimeout(
|
|
|
+ () => {
|
|
|
+ checkStatus()
|
|
|
+ .then(statusResolve)
|
|
|
+ .catch(statusReject);
|
|
|
+ },
|
|
|
+ statusCheckCount > 45 ? 5000 : 1000
|
|
|
+ );
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ console.warn('Got unknown status ' + queryStatus);
|
|
|
+ statusReject();
|
|
|
+ }
|
|
|
})
|
|
|
- .fail(error => {
|
|
|
- this.status = EXECUTION_STATUS.failed;
|
|
|
- notifyUpdates(this);
|
|
|
- reject(error);
|
|
|
- });
|
|
|
+ .fail(statusReject);
|
|
|
+
|
|
|
+ this.lastCancellable.onCancel(() => {
|
|
|
+ window.clearTimeout(checkStatusTimeout);
|
|
|
+ });
|
|
|
});
|
|
|
- });
|
|
|
+
|
|
|
+ this.setStatus(EXECUTION_STATUS.running);
|
|
|
+ this.setProgress(0);
|
|
|
+
|
|
|
+ try {
|
|
|
+ const session = await sessionManager.getSession({ type: this.sourceType });
|
|
|
+ hueAnalytics.log('notebook', 'execute/' + this.sourceType);
|
|
|
+ this.handle = await this.internalExecute(session);
|
|
|
+ await checkStatus();
|
|
|
+ this.result = new ExecutionResult(this);
|
|
|
+ } catch (err) {
|
|
|
+ this.setStatus(EXECUTION_STATUS.failed);
|
|
|
+ throw err;
|
|
|
+ }
|
|
|
+
|
|
|
+ return this.result;
|
|
|
+ }
|
|
|
+
|
|
|
+ setLastCancellable(lastCancellable) {
|
|
|
+ this.lastCancellable = lastCancellable;
|
|
|
+ }
|
|
|
+
|
|
|
+ async internalExecute(session) {
|
|
|
+ throw new Error('Implement in subclass!');
|
|
|
+ }
|
|
|
+
|
|
|
+ canExecuteInBatch() {
|
|
|
+ throw new Error('Implement in subclass!');
|
|
|
}
|
|
|
|
|
|
async cancel() {
|
|
|
return new Promise(resolve => {
|
|
|
if (this.lastCancellable && this.status === EXECUTION_STATUS.running) {
|
|
|
hueAnalytics.log('notebook', 'cancel/' + this.sourceType);
|
|
|
- this.status = EXECUTION_STATUS.canceling;
|
|
|
- notifyUpdates(this);
|
|
|
+ this.setStatus(EXECUTION_STATUS.canceling);
|
|
|
this.lastCancellable.cancel().always(() => {
|
|
|
- this.status = EXECUTION_STATUS.canceled;
|
|
|
- notifyUpdates(this);
|
|
|
+ this.setStatus(EXECUTION_STATUS.canceled);
|
|
|
resolve();
|
|
|
});
|
|
|
this.lastCancellable = undefined;
|
|
|
@@ -188,8 +189,7 @@ export class ExecutableStatement {
|
|
|
apiHelper.closeStatement({ executable: this }).finally(resolve);
|
|
|
}
|
|
|
}).finally(() => {
|
|
|
- this.status = EXECUTION_STATUS.closed;
|
|
|
- notifyUpdates(this);
|
|
|
+ this.setStatus(EXECUTION_STATUS.closed);
|
|
|
});
|
|
|
}
|
|
|
}
|