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:
Csaba Tuncsik 2023-01-11 15:08:00 +01:00 committed by GitHub
parent 62d06b1e6e
commit 3c109ffab1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 641 additions and 637 deletions

File diff suppressed because it is too large Load diff

View file

@ -135,10 +135,9 @@ export default mixins(executionHelpers, showMessage, restApi).extend({
<style module lang="scss"> <style module lang="scss">
.executionCard { .executionCard {
display: flex; display: flex;
padding-right: var(--spacing-2xs); padding-right: var(--spacing-m);
&.active { &.active {
padding: 0 var(--spacing-2xs) var(--spacing-2xs) 0;
border-left: var(--spacing-4xs) var(--border-style-base) transparent !important; border-left: var(--spacing-4xs) var(--border-style-base) transparent !important;
.executionStatus { .executionStatus {
@ -146,14 +145,10 @@ export default mixins(executionHelpers, showMessage, restApi).extend({
} }
} }
& + &.active {
padding-top: var(--spacing-2xs);
}
&:hover, &:hover,
&.active { &.active {
.executionLink { .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); font-size: var(--font-size-xs);
padding: var(--spacing-xs); padding: var(--spacing-xs);
padding-right: var(--spacing-s); padding-right: var(--spacing-s);
border-radius: var(--border-radius-base);
position: relative; position: relative;
left: calc( left: calc(
-1 * var(--spacing-4xs) -1 * var(--spacing-4xs)

View file

@ -49,7 +49,7 @@ import { workflowHelpers } from '@/mixins/workflowHelpers';
interface IWorkflowSaveSettings { interface IWorkflowSaveSettings {
saveFailedExecutions: boolean; saveFailedExecutions: boolean;
saveSuccessfulExecutions: boolean; saveSuccessfulExecutions: boolean;
saveManualExecutions: boolean; saveTestExecutions: boolean;
} }
export default mixins(workflowHelpers).extend({ export default mixins(workflowHelpers).extend({
@ -70,7 +70,7 @@ export default mixins(workflowHelpers).extend({
workflowSaveSettings: { workflowSaveSettings: {
saveFailedExecutions: false, saveFailedExecutions: false,
saveSuccessfulExecutions: false, saveSuccessfulExecutions: false,
saveManualExecutions: false, saveTestExecutions: false,
} as IWorkflowSaveSettings, } as IWorkflowSaveSettings,
}; };
}, },
@ -105,11 +105,9 @@ export default mixins(workflowHelpers).extend({
}, },
{ {
id: 'manualExecutions', id: 'manualExecutions',
label: this.$locale.baseText( label: this.$locale.baseText('executionsLandingPage.emptyState.accordion.testExecutions'),
'executionsLandingPage.emptyState.accordion.manualExecutions', icon: this.workflowSaveSettings.saveTestExecutions ? 'check' : 'times',
), iconColor: this.workflowSaveSettings.saveTestExecutions ? 'success' : 'danger',
icon: this.workflowSaveSettings.saveManualExecutions ? 'check' : 'times',
iconColor: this.workflowSaveSettings.saveManualExecutions ? 'success' : 'danger',
}, },
]; ];
}, },
@ -118,9 +116,9 @@ export default mixins(workflowHelpers).extend({
return false; return false;
} }
return ( return (
this.workflowSaveSettings.saveFailedExecutions === false || !this.workflowSaveSettings.saveFailedExecutions ||
this.workflowSaveSettings.saveSuccessfulExecutions === false || !this.workflowSaveSettings.saveSuccessfulExecutions ||
this.workflowSaveSettings.saveManualExecutions === false !this.workflowSaveSettings.saveTestExecutions
); );
}, },
productionExecutionsIcon(): { icon: string; color: string } { productionExecutionsIcon(): { icon: string; color: string } {
@ -136,7 +134,7 @@ export default mixins(workflowHelpers).extend({
this.workflowSaveSettings.saveSuccessfulExecutions === this.workflowSaveSettings.saveSuccessfulExecutions ===
this.workflowSaveSettings.saveFailedExecutions this.workflowSaveSettings.saveFailedExecutions
) { ) {
if (this.workflowSaveSettings.saveSuccessfulExecutions === true) { if (this.workflowSaveSettings.saveSuccessfulExecutions) {
return 'saving'; return 'saving';
} }
return 'not-saving'; return 'not-saving';
@ -150,7 +148,7 @@ export default mixins(workflowHelpers).extend({
}, },
accordionIcon(): { icon: string; color: string } | null { accordionIcon(): { icon: string; color: string } | null {
if ( if (
this.workflowSaveSettings.saveManualExecutions !== true || !this.workflowSaveSettings.saveTestExecutions ||
this.productionExecutionsStatus !== 'saving' this.productionExecutionsStatus !== 'saving'
) { ) {
return { icon: 'exclamation-triangle', color: 'warning' }; return { icon: 'exclamation-triangle', color: 'warning' };
@ -184,7 +182,7 @@ export default mixins(workflowHelpers).extend({
workflowSettings.saveDataSuccessExecution === undefined workflowSettings.saveDataSuccessExecution === undefined
? this.defaultValues.saveSuccessfulExecutions === 'all' ? this.defaultValues.saveSuccessfulExecutions === 'all'
: workflowSettings.saveDataSuccessExecution === 'all'; : workflowSettings.saveDataSuccessExecution === 'all';
this.workflowSaveSettings.saveManualExecutions = this.workflowSaveSettings.saveTestExecutions =
workflowSettings.saveManualExecutions === undefined workflowSettings.saveManualExecutions === undefined
? this.defaultValues.saveManualExecutions ? this.defaultValues.saveManualExecutions
: (workflowSettings.saveManualExecutions as boolean); : (workflowSettings.saveManualExecutions as boolean);

View file

@ -16,9 +16,6 @@
<n8n-heading tag="h2" size="xlarge" color="text-dark" class="mb-2xs"> <n8n-heading tag="h2" size="xlarge" color="text-dark" class="mb-2xs">
{{ $locale.baseText('executionsLandingPage.emptyState.heading') }} {{ $locale.baseText('executionsLandingPage.emptyState.heading') }}
</n8n-heading> </n8n-heading>
<n8n-text size="medium">
{{ $locale.baseText('executionsLandingPage.emptyState.message') }}
</n8n-text>
<executions-info-accordion /> <executions-info-accordion />
</div> </div>
</div> </div>

View file

@ -1,7 +1,6 @@
<template> <template>
<div :class="$style.container" v-if="!loading"> <div :class="$style.container" v-if="!loading">
<executions-sidebar <executions-sidebar
v-if="showSidebar"
:executions="executions" :executions="executions"
:loading="loading" :loading="loading"
:loadingMore="loadingMore" :loadingMore="loadingMore"
@ -19,11 +18,6 @@
@stopExecution="onStopExecution" @stopExecution="onStopExecution"
/> />
</div> </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> </div>
</template> </template>
@ -76,7 +70,7 @@ export default mixins(
debounceHelper, debounceHelper,
workflowHelpers, workflowHelpers,
).extend({ ).extend({
name: 'executions-list', name: 'executions-view',
components: { components: {
ExecutionsSidebar, ExecutionsSidebar,
}, },
@ -98,12 +92,6 @@ export default mixins(
) === undefined; ) === undefined;
return this.loading || nothingToShow || activeNotPresent; return this.loading || nothingToShow || activeNotPresent;
}, },
showSidebar(): boolean {
if (this.executions.length === 0) {
return this.filterApplied;
}
return true;
},
filterApplied(): boolean { filterApplied(): boolean {
return this.filter.status !== ''; return this.filter.status !== '';
}, },
@ -256,22 +244,32 @@ export default mixins(
async onDeleteCurrentExecution(): Promise<void> { async onDeleteCurrentExecution(): Promise<void> {
this.loading = true; this.loading = true;
try { 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.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) { if (this.executions.length > 0) {
this.workflowsStore.activeWorkflowExecution = this.executions[0]; await this.$router
this.$router
.push({ .push({
name: VIEWS.EXECUTION_PREVIEW, name: VIEWS.EXECUTION_PREVIEW,
params: { name: this.currentWorkflow, executionId: this.executions[0].id }, params: { name: this.currentWorkflow, executionId: nextExecution.id },
}) })
.catch(() => {}); .catch(() => {});
this.workflowsStore.activeWorkflowExecution = nextExecution;
} else { } else {
// If there are no executions left, show empty state and clear active execution from the store // If there are no executions left, show empty state and clear active execution from the store
this.workflowsStore.activeWorkflowExecution = null; 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) { } catch (error) {
this.loading = false; this.loading = false;
this.$showError( this.$showError(
@ -314,8 +312,7 @@ export default mixins(
this.setExecutions(); this.setExecutions();
}, },
async setExecutions(): Promise<void> { async setExecutions(): Promise<void> {
const workflowExecutions = await this.loadExecutions(); this.workflowsStore.currentWorkflowExecutions = await this.loadExecutions();
this.workflowsStore.currentWorkflowExecutions = workflowExecutions;
await this.setActiveExecution(); await this.setActiveExecution();
}, },
async loadAutoRefresh(): Promise<void> { async loadAutoRefresh(): Promise<void> {
@ -667,10 +664,4 @@ export default mixins(
.content { .content {
flex: 1; flex: 1;
} }
.noResultsContainer {
width: 100%;
margin-top: var(--spacing-2xl);
text-align: center;
}
</style> </style>

View file

@ -66,6 +66,11 @@
<n8n-loading :class="$style.loader" variant="p" :rows="1" /> <n8n-loading :class="$style.loader" variant="p" :rows="1" />
<n8n-loading :class="$style.loader" variant="p" :rows="1" /> <n8n-loading :class="$style.loader" variant="p" :rows="1" />
</div> </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 <execution-card
v-else v-else
v-for="execution in executions" v-for="execution in executions"
@ -224,7 +229,7 @@ export default Vue.extend({
// Find out how many execution card can fit into list // Find out how many execution card can fit into list
// and load more if needed // and load more if needed
if (sidebarContainer && currentExecutionCard) { if (sidebarContainer && currentExecutionCard?.length) {
const cardElement = currentExecutionCard[0].$el as HTMLElement; const cardElement = currentExecutionCard[0].$el as HTMLElement;
const listCapacity = Math.ceil(sidebarContainer.clientHeight / cardElement.clientHeight); const listCapacity = Math.ceil(sidebarContainer.clientHeight / cardElement.clientHeight);
@ -239,7 +244,11 @@ export default Vue.extend({
`execution-${this.workflowsStore.activeWorkflowExecution?.id}` `execution-${this.workflowsStore.activeWorkflowExecution?.id}`
] as Vue[]; ] as Vue[];
if (executionsList && currentExecutionCard && this.workflowsStore.activeWorkflowExecution) { if (
executionsList &&
currentExecutionCard?.length &&
this.workflowsStore.activeWorkflowExecution
) {
const cardElement = currentExecutionCard[0].$el as HTMLElement; const cardElement = currentExecutionCard[0].$el as HTMLElement;
const cardRect = cardElement.getBoundingClientRect(); const cardRect = cardElement.getBoundingClientRect();
const LIST_HEADER_OFFSET = 200; const LIST_HEADER_OFFSET = 200;
@ -316,4 +325,10 @@ export default Vue.extend({
margin-top: 0 !important; margin-top: 0 !important;
} }
} }
.noResultsContainer {
width: 100%;
margin-top: var(--spacing-2xl);
text-align: center;
}
</style> </style>

View file

@ -52,7 +52,7 @@ export default mixins(pushConnection, workflowHelpers).extend({
...mapStores(useNDVStore, useUIStore), ...mapStores(useNDVStore, useUIStore),
tabBarItems(): ITabBarItem[] { tabBarItems(): ITabBarItem[] {
return [ 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') }, { value: MAIN_HEADER_TABS.EXECUTIONS, label: this.$locale.baseText('generic.executions') },
]; ];
}, },

View file

@ -1,6 +1,6 @@
import { IExecutionsSummary } from '@/Interface'; import { IExecutionsSummary } from '@/Interface';
import { useWorkflowsStore } from '@/stores/workflows'; import { useWorkflowsStore } from '@/stores/workflows';
import dateFormat from 'dateformat'; import { i18n as locale } from '@/plugins/i18n';
import { mapStores } from 'pinia'; import { mapStores } from 'pinia';
import mixins from 'vue-typed-mixins'; import mixins from 'vue-typed-mixins';
import { genericHelpers } from './genericHelpers'; import { genericHelpers } from './genericHelpers';
@ -35,7 +35,7 @@ export const executionHelpers = mixins(genericHelpers).extend({
getExecutionUIDetails(execution: IExecutionsSummary): IExecutionUIData { getExecutionUIDetails(execution: IExecutionsSummary): IExecutionUIData {
const status = { const status = {
name: 'unknown', name: 'unknown',
startTime: this.formatDate(new Date(execution.startedAt)), startTime: this.formatDate(execution.startedAt),
label: 'Status unknown', label: 'Status unknown',
runningTime: '', runningTime: '',
}; };
@ -72,11 +72,9 @@ export const executionHelpers = mixins(genericHelpers).extend({
return status; return status;
}, },
formatDate(date: Date) { formatDate(fullDate: Date | string | number) {
if (date.getFullYear() === new Date().getFullYear()) { const { date, time } = this.convertToDisplayDate(fullDate);
return dateFormat(date.getTime(), 'HH:MM:ss "on" d mmm'); return locale.baseText('executionsList.started', { interpolate: { time, date } });
}
return dateFormat(date.getTime(), 'HH:MM:ss "on" d mmm yyyy');
}, },
}, },
}); });

View file

@ -36,8 +36,11 @@ export const genericHelpers = mixins(showMessage).extend({
return `${minutesPassed}:${secondsLeft}${this.$locale.baseText('genericHelpers.minShort')}`; return `${minutesPassed}:${secondsLeft}${this.$locale.baseText('genericHelpers.minShort')}`;
}, },
convertToDisplayDate(epochTime: number): { date: string; time: string } { convertToDisplayDate(fullDate: Date | string | number): { date: string; time: string } {
const formattedDate = dateformat(epochTime, 'd mmm, yyyy#HH:MM:ss'); 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('#'); const [date, time] = formattedDate.split('#');
return { date, time }; return { date, time };
}, },

View file

@ -40,6 +40,7 @@
"generic.unsavedWork.confirmMessage.confirmButtonText": "Save", "generic.unsavedWork.confirmMessage.confirmButtonText": "Save",
"generic.unsavedWork.confirmMessage.cancelButtonText": "Leave without saving", "generic.unsavedWork.confirmMessage.cancelButtonText": "Leave without saving",
"generic.workflow": "Workflow", "generic.workflow": "Workflow",
"generic.editor": "Editor",
"about.aboutN8n": "About n8n", "about.aboutN8n": "About n8n",
"about.close": "Close", "about.close": "Close",
"about.license": "License", "about.license": "License",
@ -412,11 +413,12 @@
"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",
"executionsLandingPage.emptyState.noTrigger.buttonText": "Add first step...", "executionsLandingPage.emptyState.noTrigger.buttonText": "Add first step...",
"executionsLandingPage.clickExecutionMessage": "Click on an execution from the list to view it", "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.message": "New workflow executions will show here",
"executionsLandingPage.emptyState.accordion.title": "Which executions is this workflow saving?", "executionsLandingPage.emptyState.accordion.title": "Which executions is this workflow saving?",
"executionsLandingPage.emptyState.accordion.titleWarning": "Some executions wont be saved",
"executionsLandingPage.emptyState.accordion.productionExecutions": "Production executions", "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.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": "You can change this in",
"executionsLandingPage.emptyState.accordion.footer.settingsLink": "Workflow settings", "executionsLandingPage.emptyState.accordion.footer.settingsLink": "Workflow settings",