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-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">
{{ $locale.baseText('executionDetails.runningTimeRunning', { interpolate: { time: executionUIDetails.runningTime } }) }}
{{ $locale.baseText('executionDetails.runningTimeRunning') }}
<execution-time :start-time="execution.startedAt"/>
</n8n-text>
<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 } }) }}
@ -56,6 +57,7 @@ import { executionHelpers, IExecutionUIData } from '../mixins/executionsHelpers'
import { VIEWS } from '../../constants';
import { showMessage } from '../mixins/showMessage';
import { restApi } from '../mixins/restApi';
import ExecutionTime from '@/components/ExecutionTime.vue';
export default mixins(
executionHelpers,
@ -63,6 +65,9 @@ export default mixins(
restApi,
).extend({
name: 'execution-card',
components: {
ExecutionTime,
},
data() {
return {
VIEWS,

View file

@ -1,11 +1,14 @@
<template>
<div v-if="executionUIDetails && executionUIDetails.name === 'running'" :class="$style.runningInfo">
<div :class="$style.spinner">
<font-awesome-icon icon="spinner" spin />
<n8n-spinner type="ring" />
</div>
<n8n-text :class="$style.runningMessage">
<n8n-text :class="$style.runningMessage" color="text-light">
{{ $locale.baseText('executionDetails.runningMessage') }}
</n8n-text>
<n8n-button class="mt-l" type="tertiary" size="medium" @click="handleStopClick">
{{ $locale.baseText('executionsList.stopExecution') }}
</n8n-button>
</div>
<div v-else :class="$style.previewContainer">
<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 {
this.$emit('retryExecution', { execution: this.activeExecution, command });
},
handleStopClick(): void {
this.$emit('stopExecution');
},
onRetryButtonBlur(event: FocusEvent): void {
// Hide dropdown when clicking outside of current document
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); }
.waiting { color: var(--color-secondary); }
.success { color: var(--color-success); }
@ -158,11 +172,6 @@ export default mixins(restApi, showMessage, executionHelpers).extend({
margin-top: var(--spacing-4xl);
}
.spinner {
font-size: var(--font-size-2xl);
color: var(--color-primary);
}
.runningMessage {
width: 200px;
margin-top: var(--spacing-l);

View file

@ -12,7 +12,12 @@
@refresh="loadAutoRefresh"
/>
<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 v-if="executions.length === 0 && filterApplied" :class="$style.noResultsContainer">
<n8n-text color="text-base" size="medium" align="center">
@ -240,6 +245,29 @@ export default mixins(restApi, showMessage, executionHelpers, debounceHelper, wo
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 {
this.filter = newFilter;
this.setExecutions();
@ -304,9 +332,12 @@ export default mixins(restApi, showMessage, executionHelpers, debounceHelper, wo
const activeNotInTheList = existingExecutions.find(ex => ex.id === this.activeExecution.id) === undefined;
if (activeNotInTheList && this.executions.length > 0) {
this.$router.push({
name: VIEWS.EXECUTION_PREVIEW,
params: { name: this.currentWorkflow, executionId: this.executions[0].id },
}).catch(()=>{});;
name: VIEWS.EXECUTION_PREVIEW,
params: { name: this.currentWorkflow, executionId: this.executions[0].id },
}).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.retry": "Retry of execution",
"executionDetails.runningTimeFinished": "in {time}",
"executionDetails.runningTimeRunning": "for {time}",
"executionDetails.runningTimeRunning": "for",
"executionDetails.runningMessage": "Execution is running. It will show here once finished.",
"executionDetails.workflow": "workflow",
"executionsLandingPage.emptyState.noTrigger.heading": "Set up the first step. Then execute your workflow",

View file

@ -725,21 +725,11 @@ export default mixins(
}
}
if (!nodeErrorFound) {
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
// TODO: Improve this in the future by displaying in UI
console.error(`Execution ${executionId} error:`); // eslint-disable-line no-console
console.error(data.data.resultData.error.stack); // eslint-disable-line no-console
}
if (!nodeErrorFound && data.data.resultData.error.stack) {
// Display some more information for now in console to make debugging easier
// TODO: Improve this in the future by displaying in UI
console.error(`Execution ${executionId} error:`); // eslint-disable-line no-console
console.error(data.data.resultData.error.stack); // eslint-disable-line no-console
}
}
if ((data as IExecutionsSummary).waitTill) {