mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
refactor(editor): Overhaul workflow level executions list (#5089)
* fix(editor): update texts and styles * fix(editor): update texts and styles * fix(editor): move 'No execution found' to sidebar * fix(editor): change empty state title in executions * fix(editor): workflow execution list delete item * fix(editor): workflow execution always show sidebar * fix(editor): workflow execution unify date display mode * fix(editor): workflow execution empty list
This commit is contained in:
parent
62d06b1e6e
commit
3c109ffab1
File diff suppressed because it is too large
Load diff
|
@ -135,10 +135,9 @@ export default mixins(executionHelpers, showMessage, restApi).extend({
|
|||
<style module lang="scss">
|
||||
.executionCard {
|
||||
display: flex;
|
||||
padding-right: var(--spacing-2xs);
|
||||
padding-right: var(--spacing-m);
|
||||
|
||||
&.active {
|
||||
padding: 0 var(--spacing-2xs) var(--spacing-2xs) 0;
|
||||
border-left: var(--spacing-4xs) var(--border-style-base) transparent !important;
|
||||
|
||||
.executionStatus {
|
||||
|
@ -146,14 +145,10 @@ export default mixins(executionHelpers, showMessage, restApi).extend({
|
|||
}
|
||||
}
|
||||
|
||||
& + &.active {
|
||||
padding-top: var(--spacing-2xs);
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&.active {
|
||||
.executionLink {
|
||||
background-color: var(--color-foreground-base);
|
||||
background-color: var(--color-foreground-light);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -217,7 +212,6 @@ export default mixins(executionHelpers, showMessage, restApi).extend({
|
|||
font-size: var(--font-size-xs);
|
||||
padding: var(--spacing-xs);
|
||||
padding-right: var(--spacing-s);
|
||||
border-radius: var(--border-radius-base);
|
||||
position: relative;
|
||||
left: calc(
|
||||
-1 * var(--spacing-4xs)
|
||||
|
|
|
@ -49,7 +49,7 @@ import { workflowHelpers } from '@/mixins/workflowHelpers';
|
|||
interface IWorkflowSaveSettings {
|
||||
saveFailedExecutions: boolean;
|
||||
saveSuccessfulExecutions: boolean;
|
||||
saveManualExecutions: boolean;
|
||||
saveTestExecutions: boolean;
|
||||
}
|
||||
|
||||
export default mixins(workflowHelpers).extend({
|
||||
|
@ -70,7 +70,7 @@ export default mixins(workflowHelpers).extend({
|
|||
workflowSaveSettings: {
|
||||
saveFailedExecutions: false,
|
||||
saveSuccessfulExecutions: false,
|
||||
saveManualExecutions: false,
|
||||
saveTestExecutions: false,
|
||||
} as IWorkflowSaveSettings,
|
||||
};
|
||||
},
|
||||
|
@ -105,11 +105,9 @@ export default mixins(workflowHelpers).extend({
|
|||
},
|
||||
{
|
||||
id: 'manualExecutions',
|
||||
label: this.$locale.baseText(
|
||||
'executionsLandingPage.emptyState.accordion.manualExecutions',
|
||||
),
|
||||
icon: this.workflowSaveSettings.saveManualExecutions ? 'check' : 'times',
|
||||
iconColor: this.workflowSaveSettings.saveManualExecutions ? 'success' : 'danger',
|
||||
label: this.$locale.baseText('executionsLandingPage.emptyState.accordion.testExecutions'),
|
||||
icon: this.workflowSaveSettings.saveTestExecutions ? 'check' : 'times',
|
||||
iconColor: this.workflowSaveSettings.saveTestExecutions ? 'success' : 'danger',
|
||||
},
|
||||
];
|
||||
},
|
||||
|
@ -118,9 +116,9 @@ export default mixins(workflowHelpers).extend({
|
|||
return false;
|
||||
}
|
||||
return (
|
||||
this.workflowSaveSettings.saveFailedExecutions === false ||
|
||||
this.workflowSaveSettings.saveSuccessfulExecutions === false ||
|
||||
this.workflowSaveSettings.saveManualExecutions === false
|
||||
!this.workflowSaveSettings.saveFailedExecutions ||
|
||||
!this.workflowSaveSettings.saveSuccessfulExecutions ||
|
||||
!this.workflowSaveSettings.saveTestExecutions
|
||||
);
|
||||
},
|
||||
productionExecutionsIcon(): { icon: string; color: string } {
|
||||
|
@ -136,7 +134,7 @@ export default mixins(workflowHelpers).extend({
|
|||
this.workflowSaveSettings.saveSuccessfulExecutions ===
|
||||
this.workflowSaveSettings.saveFailedExecutions
|
||||
) {
|
||||
if (this.workflowSaveSettings.saveSuccessfulExecutions === true) {
|
||||
if (this.workflowSaveSettings.saveSuccessfulExecutions) {
|
||||
return 'saving';
|
||||
}
|
||||
return 'not-saving';
|
||||
|
@ -150,7 +148,7 @@ export default mixins(workflowHelpers).extend({
|
|||
},
|
||||
accordionIcon(): { icon: string; color: string } | null {
|
||||
if (
|
||||
this.workflowSaveSettings.saveManualExecutions !== true ||
|
||||
!this.workflowSaveSettings.saveTestExecutions ||
|
||||
this.productionExecutionsStatus !== 'saving'
|
||||
) {
|
||||
return { icon: 'exclamation-triangle', color: 'warning' };
|
||||
|
@ -184,7 +182,7 @@ export default mixins(workflowHelpers).extend({
|
|||
workflowSettings.saveDataSuccessExecution === undefined
|
||||
? this.defaultValues.saveSuccessfulExecutions === 'all'
|
||||
: workflowSettings.saveDataSuccessExecution === 'all';
|
||||
this.workflowSaveSettings.saveManualExecutions =
|
||||
this.workflowSaveSettings.saveTestExecutions =
|
||||
workflowSettings.saveManualExecutions === undefined
|
||||
? this.defaultValues.saveManualExecutions
|
||||
: (workflowSettings.saveManualExecutions as boolean);
|
||||
|
|
|
@ -16,9 +16,6 @@
|
|||
<n8n-heading tag="h2" size="xlarge" color="text-dark" class="mb-2xs">
|
||||
{{ $locale.baseText('executionsLandingPage.emptyState.heading') }}
|
||||
</n8n-heading>
|
||||
<n8n-text size="medium">
|
||||
{{ $locale.baseText('executionsLandingPage.emptyState.message') }}
|
||||
</n8n-text>
|
||||
<executions-info-accordion />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<template>
|
||||
<div :class="$style.container" v-if="!loading">
|
||||
<executions-sidebar
|
||||
v-if="showSidebar"
|
||||
:executions="executions"
|
||||
:loading="loading"
|
||||
:loadingMore="loadingMore"
|
||||
|
@ -19,11 +18,6 @@
|
|||
@stopExecution="onStopExecution"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="executions.length === 0 && filterApplied" :class="$style.noResultsContainer">
|
||||
<n8n-text color="text-base" size="medium" align="center">
|
||||
{{ $locale.baseText('executionsLandingPage.noResults') }}
|
||||
</n8n-text>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -76,7 +70,7 @@ export default mixins(
|
|||
debounceHelper,
|
||||
workflowHelpers,
|
||||
).extend({
|
||||
name: 'executions-list',
|
||||
name: 'executions-view',
|
||||
components: {
|
||||
ExecutionsSidebar,
|
||||
},
|
||||
|
@ -98,12 +92,6 @@ export default mixins(
|
|||
) === undefined;
|
||||
return this.loading || nothingToShow || activeNotPresent;
|
||||
},
|
||||
showSidebar(): boolean {
|
||||
if (this.executions.length === 0) {
|
||||
return this.filterApplied;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
filterApplied(): boolean {
|
||||
return this.filter.status !== '';
|
||||
},
|
||||
|
@ -256,22 +244,32 @@ export default mixins(
|
|||
async onDeleteCurrentExecution(): Promise<void> {
|
||||
this.loading = true;
|
||||
try {
|
||||
const executionIndex = this.executions.findIndex(
|
||||
(execution: IExecutionsSummary) => execution.id === this.$route.params.executionId,
|
||||
);
|
||||
const nextExecution =
|
||||
this.executions[executionIndex + 1] ||
|
||||
this.executions[executionIndex - 1] ||
|
||||
this.executions[0];
|
||||
|
||||
await this.restApi().deleteExecutions({ ids: [this.$route.params.executionId] });
|
||||
await this.setExecutions();
|
||||
// Select first execution in the list after deleting the current one
|
||||
if (this.executions.length > 0) {
|
||||
this.workflowsStore.activeWorkflowExecution = this.executions[0];
|
||||
this.$router
|
||||
await this.$router
|
||||
.push({
|
||||
name: VIEWS.EXECUTION_PREVIEW,
|
||||
params: { name: this.currentWorkflow, executionId: this.executions[0].id },
|
||||
params: { name: this.currentWorkflow, executionId: nextExecution.id },
|
||||
})
|
||||
.catch(() => {});
|
||||
this.workflowsStore.activeWorkflowExecution = nextExecution;
|
||||
} else {
|
||||
// If there are no executions left, show empty state and clear active execution from the store
|
||||
this.workflowsStore.activeWorkflowExecution = null;
|
||||
this.$router.push({ name: VIEWS.EXECUTION_HOME, params: { name: this.currentWorkflow } });
|
||||
await this.$router.push({
|
||||
name: VIEWS.EXECUTION_HOME,
|
||||
params: { name: this.currentWorkflow },
|
||||
});
|
||||
}
|
||||
await this.setExecutions();
|
||||
} catch (error) {
|
||||
this.loading = false;
|
||||
this.$showError(
|
||||
|
@ -314,8 +312,7 @@ export default mixins(
|
|||
this.setExecutions();
|
||||
},
|
||||
async setExecutions(): Promise<void> {
|
||||
const workflowExecutions = await this.loadExecutions();
|
||||
this.workflowsStore.currentWorkflowExecutions = workflowExecutions;
|
||||
this.workflowsStore.currentWorkflowExecutions = await this.loadExecutions();
|
||||
await this.setActiveExecution();
|
||||
},
|
||||
async loadAutoRefresh(): Promise<void> {
|
||||
|
@ -667,10 +664,4 @@ export default mixins(
|
|||
.content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.noResultsContainer {
|
||||
width: 100%;
|
||||
margin-top: var(--spacing-2xl);
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -66,6 +66,11 @@
|
|||
<n8n-loading :class="$style.loader" variant="p" :rows="1" />
|
||||
<n8n-loading :class="$style.loader" variant="p" :rows="1" />
|
||||
</div>
|
||||
<div v-if="executions.length === 0 && statusFilterApplied" :class="$style.noResultsContainer">
|
||||
<n8n-text color="text-base" size="medium" align="center">
|
||||
{{ $locale.baseText('executionsLandingPage.noResults') }}
|
||||
</n8n-text>
|
||||
</div>
|
||||
<execution-card
|
||||
v-else
|
||||
v-for="execution in executions"
|
||||
|
@ -224,7 +229,7 @@ export default Vue.extend({
|
|||
|
||||
// Find out how many execution card can fit into list
|
||||
// and load more if needed
|
||||
if (sidebarContainer && currentExecutionCard) {
|
||||
if (sidebarContainer && currentExecutionCard?.length) {
|
||||
const cardElement = currentExecutionCard[0].$el as HTMLElement;
|
||||
const listCapacity = Math.ceil(sidebarContainer.clientHeight / cardElement.clientHeight);
|
||||
|
||||
|
@ -239,7 +244,11 @@ export default Vue.extend({
|
|||
`execution-${this.workflowsStore.activeWorkflowExecution?.id}`
|
||||
] as Vue[];
|
||||
|
||||
if (executionsList && currentExecutionCard && this.workflowsStore.activeWorkflowExecution) {
|
||||
if (
|
||||
executionsList &&
|
||||
currentExecutionCard?.length &&
|
||||
this.workflowsStore.activeWorkflowExecution
|
||||
) {
|
||||
const cardElement = currentExecutionCard[0].$el as HTMLElement;
|
||||
const cardRect = cardElement.getBoundingClientRect();
|
||||
const LIST_HEADER_OFFSET = 200;
|
||||
|
@ -316,4 +325,10 @@ export default Vue.extend({
|
|||
margin-top: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.noResultsContainer {
|
||||
width: 100%;
|
||||
margin-top: var(--spacing-2xl);
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -52,7 +52,7 @@ export default mixins(pushConnection, workflowHelpers).extend({
|
|||
...mapStores(useNDVStore, useUIStore),
|
||||
tabBarItems(): ITabBarItem[] {
|
||||
return [
|
||||
{ value: MAIN_HEADER_TABS.WORKFLOW, label: this.$locale.baseText('generic.workflow') },
|
||||
{ value: MAIN_HEADER_TABS.WORKFLOW, label: this.$locale.baseText('generic.editor') },
|
||||
{ value: MAIN_HEADER_TABS.EXECUTIONS, label: this.$locale.baseText('generic.executions') },
|
||||
];
|
||||
},
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { IExecutionsSummary } from '@/Interface';
|
||||
import { useWorkflowsStore } from '@/stores/workflows';
|
||||
import dateFormat from 'dateformat';
|
||||
import { i18n as locale } from '@/plugins/i18n';
|
||||
import { mapStores } from 'pinia';
|
||||
import mixins from 'vue-typed-mixins';
|
||||
import { genericHelpers } from './genericHelpers';
|
||||
|
@ -35,7 +35,7 @@ export const executionHelpers = mixins(genericHelpers).extend({
|
|||
getExecutionUIDetails(execution: IExecutionsSummary): IExecutionUIData {
|
||||
const status = {
|
||||
name: 'unknown',
|
||||
startTime: this.formatDate(new Date(execution.startedAt)),
|
||||
startTime: this.formatDate(execution.startedAt),
|
||||
label: 'Status unknown',
|
||||
runningTime: '',
|
||||
};
|
||||
|
@ -72,11 +72,9 @@ export const executionHelpers = mixins(genericHelpers).extend({
|
|||
|
||||
return status;
|
||||
},
|
||||
formatDate(date: Date) {
|
||||
if (date.getFullYear() === new Date().getFullYear()) {
|
||||
return dateFormat(date.getTime(), 'HH:MM:ss "on" d mmm');
|
||||
}
|
||||
return dateFormat(date.getTime(), 'HH:MM:ss "on" d mmm yyyy');
|
||||
formatDate(fullDate: Date | string | number) {
|
||||
const { date, time } = this.convertToDisplayDate(fullDate);
|
||||
return locale.baseText('executionsList.started', { interpolate: { time, date } });
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -36,8 +36,11 @@ export const genericHelpers = mixins(showMessage).extend({
|
|||
|
||||
return `${minutesPassed}:${secondsLeft}${this.$locale.baseText('genericHelpers.minShort')}`;
|
||||
},
|
||||
convertToDisplayDate(epochTime: number): { date: string; time: string } {
|
||||
const formattedDate = dateformat(epochTime, 'd mmm, yyyy#HH:MM:ss');
|
||||
convertToDisplayDate(fullDate: Date | string | number): { date: string; time: string } {
|
||||
const mask = `d mmm${
|
||||
new Date(fullDate).getFullYear() === new Date().getFullYear() ? '' : ', yyyy'
|
||||
}#HH:MM:ss`;
|
||||
const formattedDate = dateformat(fullDate, mask);
|
||||
const [date, time] = formattedDate.split('#');
|
||||
return { date, time };
|
||||
},
|
||||
|
|
|
@ -40,6 +40,7 @@
|
|||
"generic.unsavedWork.confirmMessage.confirmButtonText": "Save",
|
||||
"generic.unsavedWork.confirmMessage.cancelButtonText": "Leave without saving",
|
||||
"generic.workflow": "Workflow",
|
||||
"generic.editor": "Editor",
|
||||
"about.aboutN8n": "About n8n",
|
||||
"about.close": "Close",
|
||||
"about.license": "License",
|
||||
|
@ -412,11 +413,12 @@
|
|||
"executionsLandingPage.emptyState.noTrigger.heading": "Set up the first step. Then execute your workflow",
|
||||
"executionsLandingPage.emptyState.noTrigger.buttonText": "Add first step...",
|
||||
"executionsLandingPage.clickExecutionMessage": "Click on an execution from the list to view it",
|
||||
"executionsLandingPage.emptyState.heading": " No executions yet",
|
||||
"executionsLandingPage.emptyState.heading": "Nothing here yet",
|
||||
"executionsLandingPage.emptyState.message": "New workflow executions will show here",
|
||||
"executionsLandingPage.emptyState.accordion.title": "Which executions is this workflow saving?",
|
||||
"executionsLandingPage.emptyState.accordion.titleWarning": "Some executions won’t be saved",
|
||||
"executionsLandingPage.emptyState.accordion.productionExecutions": "Production executions",
|
||||
"executionsLandingPage.emptyState.accordion.manualExecutions": "Manual executions",
|
||||
"executionsLandingPage.emptyState.accordion.testExecutions": "Test executions",
|
||||
"executionsLandingPage.emptyState.accordion.productionExecutionsWarningTooltip": "Not all production executions are being saved. Change this in the workflow's <a href=\"#\">settings</a>",
|
||||
"executionsLandingPage.emptyState.accordion.footer": "You can change this in",
|
||||
"executionsLandingPage.emptyState.accordion.footer.settingsLink": "Workflow settings",
|
||||
|
|
Loading…
Reference in a new issue