Action button uses mixins for invoking and polling actions
This commit is contained in:
parent
4910bef717
commit
dd0a57520b
2 changed files with 118 additions and 102 deletions
|
|
@ -47,8 +47,6 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import axios from "axios";
|
|
||||||
|
|
||||||
import ActionProgressBar from "./actionProgressBar.vue";
|
import ActionProgressBar from "./actionProgressBar.vue";
|
||||||
import ActionStatusModal from "./actionStatusModal.vue";
|
import ActionStatusModal from "./actionStatusModal.vue";
|
||||||
|
|
||||||
|
|
@ -165,7 +163,7 @@ export default {
|
||||||
n = Math.trunc(n) || 0;
|
n = Math.trunc(n) || 0;
|
||||||
// Allow negative indexing from the end
|
// Allow negative indexing from the end
|
||||||
if (n < 0) n += this.length;
|
if (n < 0) n += this.length;
|
||||||
// OOB access is guaranteed to return undefined
|
// Out of bounds access is guaranteed to return undefined
|
||||||
if (n < 0 || n >= this.length) return undefined;
|
if (n < 0 || n >= this.length) return undefined;
|
||||||
// Otherwise, this is just normal property access
|
// Otherwise, this is just normal property access
|
||||||
return this[n];
|
return this[n];
|
||||||
|
|
@ -215,13 +213,9 @@ export default {
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
async checkExistingTasks() {
|
async checkExistingTasks() {
|
||||||
let response;
|
let response = await this.findOngoingAction(this.thing, this.action);
|
||||||
try {
|
// Exit if response is null, due to an error.
|
||||||
response = await axios.get(this.submitUrl);
|
if (response == null) return;
|
||||||
} catch (error) {
|
|
||||||
console.warn("checkExistingTasks: request failed", error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Check for a task that is ongoing.
|
// Check for a task that is ongoing.
|
||||||
// We can't handle multiple tasks ongoing, so this picks the first.
|
// We can't handle multiple tasks ongoing, so this picks the first.
|
||||||
const ongoingTask = response.data.find((t) => ["pending", "running"].includes(t.status));
|
const ongoingTask = response.data.find((t) => ["pending", "running"].includes(t.status));
|
||||||
|
|
@ -231,13 +225,7 @@ export default {
|
||||||
this.$emit("taskStarted");
|
this.$emit("taskStarted");
|
||||||
// Find its URL
|
// Find its URL
|
||||||
const taskUrl = ongoingTask.links.find((t) => t.rel == "self").href;
|
const taskUrl = ongoingTask.links.find((t) => t.rel == "self").href;
|
||||||
try {
|
this.startPollingTask(ongoingTask.id, taskUrl);
|
||||||
await this.pollOngoingTask(ongoingTask.id, taskUrl);
|
|
||||||
} catch (error) {
|
|
||||||
this.$emit("error", error);
|
|
||||||
} finally {
|
|
||||||
this.onTaskEnd();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -257,108 +245,106 @@ export default {
|
||||||
|
|
||||||
async startTask() {
|
async startTask() {
|
||||||
// Starts a new Action task
|
// Starts a new Action task
|
||||||
|
|
||||||
this.$emit("submit", this.submitData);
|
this.$emit("submit", this.submitData);
|
||||||
// Send a request to start a task
|
// Send a request to start a task
|
||||||
|
|
||||||
this.taskStarted = true;
|
this.taskStarted = true;
|
||||||
this.$emit("taskStarted");
|
this.$emit("taskStarted");
|
||||||
|
let response;
|
||||||
try {
|
try {
|
||||||
let response = await axios.post(this.submitUrl, this.submitData);
|
response = await this.invokeAction(
|
||||||
if (this.modalProgress) {
|
this.thing,
|
||||||
this.$refs.statusModal.show();
|
this.action,
|
||||||
}
|
this.submitData,
|
||||||
await this.pollOngoingTask(response.data.id, response.data.href);
|
false, // Stop invokeAction handling the error.
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.$emit("error", error);
|
this.$emit("error", error);
|
||||||
} finally {
|
|
||||||
this.onTaskEnd();
|
this.onTaskEnd();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
if (this.modalProgress) {
|
||||||
|
this.$refs.statusModal.show();
|
||||||
|
}
|
||||||
|
// This just starts the polling. No need to await it.
|
||||||
|
this.startPollingTask(response.data.id, response.data.href);
|
||||||
},
|
},
|
||||||
|
|
||||||
async pollOngoingTask(taskId, taskUrl) {
|
async startPollingTask(taskId, taskUrl) {
|
||||||
|
// Return if taskRunning already set.
|
||||||
|
if (this.taskRunning) return;
|
||||||
|
// Starts polling an existing Action task
|
||||||
|
this.taskUrl = taskUrl;
|
||||||
// Start the store polling TaskId for success
|
// Start the store polling TaskId for success
|
||||||
const response = await this.startPolling(taskId, taskUrl);
|
this.taskRunning = true;
|
||||||
if (response.status == "completed") {
|
this.$emit("taskRunning", taskId);
|
||||||
this.$emit("response", response);
|
this.pollUntilComplete(
|
||||||
this.$emit("completed", response.output);
|
taskUrl,
|
||||||
} else if (response.status == "cancelled") {
|
this.onPollingResponse,
|
||||||
this.$emit("cancelled", response);
|
this.onTaskEnd, // Method to run after task (even if error)
|
||||||
this.modalNotify(`The action '${this.submitLabel}' was cancelled.`);
|
500, // Interval
|
||||||
}
|
false, // Don't handle errors,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
onTaskEnd: function () {
|
onTaskEnd: function (response) {
|
||||||
// Reset taskRunning and taskId
|
if (response) {
|
||||||
|
this.taskStatus = response.data.status;
|
||||||
|
this.log = response.data.log;
|
||||||
|
if (response.data.status == "completed") {
|
||||||
|
this.$emit("response", response.data);
|
||||||
|
this.$emit("completed", response.data.output);
|
||||||
|
} else if (response.data.status == "cancelled") {
|
||||||
|
this.$emit("cancelled", response.data);
|
||||||
|
this.modalNotify(`The action '${this.submitLabel}' was cancelled.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.taskUrl = null;
|
||||||
this.taskRunning = false;
|
this.taskRunning = false;
|
||||||
this.taskStarted = false;
|
this.taskStarted = false;
|
||||||
this.$emit("finished");
|
this.$emit("finished");
|
||||||
},
|
},
|
||||||
|
|
||||||
startPolling: function (taskId, taskUrl) {
|
onPollingResponse(response) {
|
||||||
if (this.taskRunning != true) {
|
var result = response.data.status;
|
||||||
// Starts polling an existing Action task
|
this.taskStatus = result;
|
||||||
this.taskUrl = taskUrl;
|
if ((result == "running") | (result == "pending")) {
|
||||||
// Start the store polling TaskId for success
|
// If the task is still running, we update progress/log,
|
||||||
this.taskRunning = true;
|
// and schedule another poll
|
||||||
this.$emit("taskRunning", taskId);
|
this.progress = response.data.progress;
|
||||||
return this.pollTask(taskId, this.pollInterval);
|
this.log = response.data.log;
|
||||||
|
}
|
||||||
|
// If task ends with an error
|
||||||
|
else if (result == "error") {
|
||||||
|
this.handleErrorResponse(response);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
pollTask: function (taskId, interval) {
|
handleErrorResponse(response) {
|
||||||
interval = interval * 1000 || 500;
|
// Pass the error string back with reject
|
||||||
|
if (!this.progress) this.progress = 1;
|
||||||
var checkCondition = (resolve, reject) => {
|
// Test whether the log is empty or the most recent message is not from an error
|
||||||
// If the condition is met, we're done!
|
// If so, return a default message
|
||||||
axios.get(this.taskUrl, { baseURL: this.$store.getters.baseUri }).then((response) => {
|
if (
|
||||||
var result = response.data.status;
|
(response.data.log.length == 0) |
|
||||||
this.taskStatus = result;
|
(response.data.log.from_index(-1).levelname != "ERROR")
|
||||||
if ((result == "running") | (result == "pending")) {
|
) {
|
||||||
// If the task is still running, we update progress/log,
|
var message = "Unexpected error, please check the logs";
|
||||||
// and schedule another poll
|
}
|
||||||
this.progress = response.data.progress;
|
// As LabThings Actions add the message from any raised exception to the log, the
|
||||||
this.log = response.data.log;
|
// last message in the log is the message from the Exception.
|
||||||
// Check again after timeout
|
// If the Exception was raised with no message, use a default.
|
||||||
setTimeout(checkCondition, interval, resolve, reject);
|
else {
|
||||||
}
|
message =
|
||||||
// If task ends with an error
|
response.data.log.from_index(-1).message || "Unexpected error, please check the logs";
|
||||||
else if (result == "error") {
|
}
|
||||||
// Pass the error string back with reject
|
// Raise an Error with the chosen message
|
||||||
if (!this.progress) this.progress = 1;
|
throw new Error(message);
|
||||||
// Test whether the log is empty or the most recent message is not from an error
|
|
||||||
// If so, return a default message
|
|
||||||
if (
|
|
||||||
(response.data.log.length == 0) |
|
|
||||||
(response.data.log.from_index(-1).levelname != "ERROR")
|
|
||||||
) {
|
|
||||||
var message = "Unexpected error, please check the logs";
|
|
||||||
}
|
|
||||||
// As LabThings Actions add the message from any raised exception to the log, the
|
|
||||||
// last message in the log is the message from the Exception.
|
|
||||||
// If the Exception was raised with no message, use a default.
|
|
||||||
else {
|
|
||||||
message =
|
|
||||||
response.data.log.from_index(-1).message ||
|
|
||||||
"Unexpected error, please check the logs";
|
|
||||||
}
|
|
||||||
// Raise an Error with the chosen message
|
|
||||||
reject(new Error(message));
|
|
||||||
}
|
|
||||||
// If task ends without reporting an error
|
|
||||||
// (Note: this includes cancellation)
|
|
||||||
else {
|
|
||||||
resolve(response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return new Promise(checkCondition);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
terminateTask: function () {
|
terminateTask: function () {
|
||||||
axios.delete(this.taskUrl, { baseURL: this.$store.getters.baseUri });
|
if (this.taskUrl) {
|
||||||
this.$root.$emit("modalClosed");
|
this.terminateAction(this.taskUrl);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -64,19 +64,31 @@ export default {
|
||||||
}
|
}
|
||||||
await axios.put(url, value);
|
await axios.put(url, value);
|
||||||
},
|
},
|
||||||
async invokeAction(thing, action, data) {
|
async invokeAction(thing, action, data, handleErrors = true) {
|
||||||
let url = this.$store.getters["wot/thingActionUrl"](thing, action, "invokeaction", false);
|
let url = this.thingActionUrl(thing, action);
|
||||||
try {
|
try {
|
||||||
let response = await axios.post(url, data);
|
let response = await axios.post(url, data);
|
||||||
return response;
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.modalError(error);
|
if (handleErrors) {
|
||||||
return undefined;
|
this.modalError(error);
|
||||||
|
return undefined;
|
||||||
|
} else {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async pollUntilComplete(taskUrl, ongoingMethod, finalMethod, interval = 500) {
|
async pollUntilComplete(
|
||||||
|
taskUrl,
|
||||||
|
ongoingMethod,
|
||||||
|
finalMethod,
|
||||||
|
interval = 500,
|
||||||
|
modalErrors = true,
|
||||||
|
) {
|
||||||
|
let response;
|
||||||
|
let finalMethodCalled = false;
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(taskUrl, { baseURL: this.$store.getters.baseUri });
|
response = await axios.get(taskUrl, { baseURL: this.$store.getters.baseUri });
|
||||||
const result = response.data.status;
|
const result = response.data.status;
|
||||||
|
|
||||||
if ((result == "running") | (result == "pending")) {
|
if ((result == "running") | (result == "pending")) {
|
||||||
|
|
@ -87,15 +99,33 @@ export default {
|
||||||
} else {
|
} else {
|
||||||
clearTimeout(this.pollTimers[taskUrl]);
|
clearTimeout(this.pollTimers[taskUrl]);
|
||||||
delete this.pollTimers[taskUrl];
|
delete this.pollTimers[taskUrl];
|
||||||
|
finalMethodCalled = true;
|
||||||
finalMethod?.(response);
|
finalMethod?.(response);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
this.$emit("error", error);
|
||||||
|
if (modalErrors) {
|
||||||
|
this.modalError(error);
|
||||||
|
}
|
||||||
clearTimeout(this.pollTimers[taskUrl]);
|
clearTimeout(this.pollTimers[taskUrl]);
|
||||||
delete this.pollTimers[taskUrl];
|
delete this.pollTimers[taskUrl];
|
||||||
this.modalError(error);
|
if (!finalMethodCalled) {
|
||||||
|
finalMethod?.(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
terminateAction(taskUrl) {
|
||||||
|
axios.delete(taskUrl, { baseURL: this.$store.getters.baseUri });
|
||||||
|
},
|
||||||
|
async findOngoingAction(thing, action) {
|
||||||
|
let url = this.thingActionUrl(thing, action);
|
||||||
|
try {
|
||||||
|
return await axios.get(url);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn("checkExistingTasks: request failed", error);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
thingActionUrl(thing, action, allowUndefined = false) {
|
thingActionUrl(thing, action, allowUndefined = false) {
|
||||||
let url = this.$store.getters["wot/thingActionUrl"](
|
let url = this.$store.getters["wot/thingActionUrl"](
|
||||||
thing,
|
thing,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue