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">
.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)

View file

@ -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);

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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') },
];
},

View file

@ -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 } });
},
},
});

View file

@ -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 };
},

View file

@ -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 wont 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",