fix(editor): Add 'Stop execution' button to execution preview (#4632)

*  Adding `Stop execution` button to execution preview
*  Added execution timer for running executions
* 💄 Adjusting spinner size and text color
* 🔥 Removing excessive popup error message when opening failed executions preview
* 🐛 Handling execution stopping when workflow is not saving manual executions
This commit is contained in:
Milorad FIlipović 2022-11-17 17:34:55 +01:00 committed by GitHub
parent 0ade24dfaf
commit be7672a177
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 63 additions and 28 deletions

View file

@ -18,7 +18,8 @@
<n8n-spinner v-if="executionUIDetails.name === 'running'" size="small" :class="[$style.spinner, 'mr-4xs']"/> <n8n-spinner v-if="executionUIDetails.name === 'running'" size="small" :class="[$style.spinner, 'mr-4xs']"/>
<n8n-text :class="$style.statusLabel" size="small">{{ executionUIDetails.label }}</n8n-text> <n8n-text :class="$style.statusLabel" size="small">{{ executionUIDetails.label }}</n8n-text>
<n8n-text v-if="executionUIDetails.name === 'running'" :color="isActive? 'text-dark' : 'text-base'" size="small"> <n8n-text v-if="executionUIDetails.name === 'running'" :color="isActive? 'text-dark' : 'text-base'" size="small">
{{ $locale.baseText('executionDetails.runningTimeRunning', { interpolate: { time: executionUIDetails.runningTime } }) }} {{ $locale.baseText('executionDetails.runningTimeRunning') }}
<execution-time :start-time="execution.startedAt"/>
</n8n-text> </n8n-text>
<n8n-text v-else-if="executionUIDetails.name !== 'waiting' && executionUIDetails.name !== 'unknown'" :color="isActive? 'text-dark' : 'text-base'" size="small"> <n8n-text v-else-if="executionUIDetails.name !== 'waiting' && executionUIDetails.name !== 'unknown'" :color="isActive? 'text-dark' : 'text-base'" size="small">
{{ $locale.baseText('executionDetails.runningTimeFinished', { interpolate: { time: executionUIDetails.runningTime } }) }} {{ $locale.baseText('executionDetails.runningTimeFinished', { interpolate: { time: executionUIDetails.runningTime } }) }}
@ -56,6 +57,7 @@ import { executionHelpers, IExecutionUIData } from '../mixins/executionsHelpers'
import { VIEWS } from '../../constants'; import { VIEWS } from '../../constants';
import { showMessage } from '../mixins/showMessage'; import { showMessage } from '../mixins/showMessage';
import { restApi } from '../mixins/restApi'; import { restApi } from '../mixins/restApi';
import ExecutionTime from '@/components/ExecutionTime.vue';
export default mixins( export default mixins(
executionHelpers, executionHelpers,
@ -63,6 +65,9 @@ export default mixins(
restApi, restApi,
).extend({ ).extend({
name: 'execution-card', name: 'execution-card',
components: {
ExecutionTime,
},
data() { data() {
return { return {
VIEWS, VIEWS,

View file

@ -1,11 +1,14 @@
<template> <template>
<div v-if="executionUIDetails && executionUIDetails.name === 'running'" :class="$style.runningInfo"> <div v-if="executionUIDetails && executionUIDetails.name === 'running'" :class="$style.runningInfo">
<div :class="$style.spinner"> <div :class="$style.spinner">
<font-awesome-icon icon="spinner" spin /> <n8n-spinner type="ring" />
</div> </div>
<n8n-text :class="$style.runningMessage"> <n8n-text :class="$style.runningMessage" color="text-light">
{{ $locale.baseText('executionDetails.runningMessage') }} {{ $locale.baseText('executionDetails.runningMessage') }}
</n8n-text> </n8n-text>
<n8n-button class="mt-l" type="tertiary" size="medium" @click="handleStopClick">
{{ $locale.baseText('executionsList.stopExecution') }}
</n8n-button>
</div> </div>
<div v-else :class="$style.previewContainer"> <div v-else :class="$style.previewContainer">
<div :class="{[$style.executionDetails]: true, [$style.sidebarCollapsed]: sidebarCollapsed }" v-if="activeExecution"> <div :class="{[$style.executionDetails]: true, [$style.sidebarCollapsed]: sidebarCollapsed }" v-if="activeExecution">
@ -111,6 +114,9 @@ export default mixins(restApi, showMessage, executionHelpers).extend({
handleRetryClick(command: string): void { handleRetryClick(command: string): void {
this.$emit('retryExecution', { execution: this.activeExecution, command }); this.$emit('retryExecution', { execution: this.activeExecution, command });
}, },
handleStopClick(): void {
this.$emit('stopExecution');
},
onRetryButtonBlur(event: FocusEvent): void { onRetryButtonBlur(event: FocusEvent): void {
// Hide dropdown when clicking outside of current document // Hide dropdown when clicking outside of current document
const retryDropdown = this.$refs.retryDropdown as Vue & { hide: () => void } | undefined; const retryDropdown = this.$refs.retryDropdown as Vue & { hide: () => void } | undefined;
@ -146,6 +152,14 @@ export default mixins(restApi, showMessage, executionHelpers).extend({
} }
} }
.spinner {
div div {
width: 30px;
height: 30px;
border-width: 2px;
}
}
.running, .spinner { color: var(--color-warning); } .running, .spinner { color: var(--color-warning); }
.waiting { color: var(--color-secondary); } .waiting { color: var(--color-secondary); }
.success { color: var(--color-success); } .success { color: var(--color-success); }
@ -158,11 +172,6 @@ export default mixins(restApi, showMessage, executionHelpers).extend({
margin-top: var(--spacing-4xl); margin-top: var(--spacing-4xl);
} }
.spinner {
font-size: var(--font-size-2xl);
color: var(--color-primary);
}
.runningMessage { .runningMessage {
width: 200px; width: 200px;
margin-top: var(--spacing-l); margin-top: var(--spacing-l);

View file

@ -12,7 +12,12 @@
@refresh="loadAutoRefresh" @refresh="loadAutoRefresh"
/> />
<div :class="$style.content" v-if="!hidePreview"> <div :class="$style.content" v-if="!hidePreview">
<router-view name="executionPreview" @deleteCurrentExecution="onDeleteCurrentExecution" @retryExecution="onRetryExecution"/> <router-view
name="executionPreview"
@deleteCurrentExecution="onDeleteCurrentExecution"
@retryExecution="onRetryExecution"
@stopExecution="onStopExecution"
/>
</div> </div>
<div v-if="executions.length === 0 && filterApplied" :class="$style.noResultsContainer"> <div v-if="executions.length === 0 && filterApplied" :class="$style.noResultsContainer">
<n8n-text color="text-base" size="medium" align="center"> <n8n-text color="text-base" size="medium" align="center">
@ -240,6 +245,29 @@ export default mixins(restApi, showMessage, executionHelpers, debounceHelper, wo
type: 'success', type: 'success',
}); });
}, },
async onStopExecution(): Promise<void> {
const activeExecutionId = this.$route.params.executionId;
try {
await this.restApi().stopCurrentExecution(activeExecutionId);
this.$showMessage({
title: this.$locale.baseText('executionsList.showMessage.stopExecution.title'),
message: this.$locale.baseText(
'executionsList.showMessage.stopExecution.message',
{ interpolate: { activeExecutionId } },
),
type: 'success',
});
this.loadAutoRefresh();
} catch (error) {
this.$showError(
error,
this.$locale.baseText('executionsList.showError.stopExecution.title'),
);
}
},
onFilterUpdated(newFilter: { finished: boolean, status: string }): void { onFilterUpdated(newFilter: { finished: boolean, status: string }): void {
this.filter = newFilter; this.filter = newFilter;
this.setExecutions(); this.setExecutions();
@ -306,7 +334,10 @@ export default mixins(restApi, showMessage, executionHelpers, debounceHelper, wo
this.$router.push({ this.$router.push({
name: VIEWS.EXECUTION_PREVIEW, name: VIEWS.EXECUTION_PREVIEW,
params: { name: this.currentWorkflow, executionId: this.executions[0].id }, params: { name: this.currentWorkflow, executionId: this.executions[0].id },
}).catch(()=>{});; }).catch(()=>{});
} else if (this.executions.length === 0) {
this.$router.push({ name: VIEWS.EXECUTION_HOME }).catch(()=>{});
this.workflowsStore.activeWorkflowExecution = null;
} }
} }
}, },

View file

@ -398,7 +398,7 @@
"executionDetails.readOnly.youreViewingTheLogOf": "You're viewing the log of a previous execution. You cannot<br />\n\t\tmake changes since this execution already occurred. Make changes<br />\n\t\tto this workflow by clicking on its name on the left.", "executionDetails.readOnly.youreViewingTheLogOf": "You're viewing the log of a previous execution. You cannot<br />\n\t\tmake changes since this execution already occurred. Make changes<br />\n\t\tto this workflow by clicking on its name on the left.",
"executionDetails.retry": "Retry of execution", "executionDetails.retry": "Retry of execution",
"executionDetails.runningTimeFinished": "in {time}", "executionDetails.runningTimeFinished": "in {time}",
"executionDetails.runningTimeRunning": "for {time}", "executionDetails.runningTimeRunning": "for",
"executionDetails.runningMessage": "Execution is running. It will show here once finished.", "executionDetails.runningMessage": "Execution is running. It will show here once finished.",
"executionDetails.workflow": "workflow", "executionDetails.workflow": "workflow",
"executionsLandingPage.emptyState.noTrigger.heading": "Set up the first step. Then execute your workflow", "executionsLandingPage.emptyState.noTrigger.heading": "Set up the first step. Then execute your workflow",

View file

@ -725,23 +725,13 @@ export default mixins(
} }
} }
if (!nodeErrorFound) { if (!nodeErrorFound && data.data.resultData.error.stack) {
const resultError = data.data.resultData.error;
const errorMessage = this.$getExecutionError(data.data);
const shouldTrack = resultError && 'node' in resultError && resultError.node!.type.startsWith('n8n-nodes-base');
this.$showMessage({
title: 'Failed execution',
message: errorMessage,
type: 'error',
}, shouldTrack);
if (data.data.resultData.error.stack) {
// Display some more information for now in console to make debugging easier // Display some more information for now in console to make debugging easier
// TODO: Improve this in the future by displaying in UI // TODO: Improve this in the future by displaying in UI
console.error(`Execution ${executionId} error:`); // eslint-disable-line no-console console.error(`Execution ${executionId} error:`); // eslint-disable-line no-console
console.error(data.data.resultData.error.stack); // eslint-disable-line no-console console.error(data.data.resultData.error.stack); // eslint-disable-line no-console
} }
} }
}
if ((data as IExecutionsSummary).waitTill) { if ((data as IExecutionsSummary).waitTill) {
this.$showMessage({ this.$showMessage({
title: this.$locale.baseText('nodeView.thisExecutionHasntFinishedYet'), title: this.$locale.baseText('nodeView.thisExecutionHasntFinishedYet'),