mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-13 05:47:31 -08:00
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:
parent
0ade24dfaf
commit
be7672a177
|
@ -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,
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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'),
|
||||||
|
|
Loading…
Reference in a new issue