feat(editor): Executions page (#4997)

* fix(editor): Create executions page

* fix(editor): lint fix

* fix(editor): Reuse execution list in both modal and page

* fix(editor): fix ts issues

* fix(editor): Reorganizing exec list components for easier redesign (everything is in its new place now)

* fix(editor): Exec list item restyling

* fix(editor): Exec list add back stripes

* fix(editor): Exec list formatting dates and times

* fix(editor): Exec list revert accidental searc and replace

* fix(editor): Exec list translations and execution IDs

* fix(editor): Exec list playing with table cell sizing

* fix(editor): Exec list playing with table cell sizing

* fix(editor): Exec list drop Element UI Table

* fix(editor): Exec list adding sticky header and View button on row hover

* fix(editor): Exec list open execution in new tab, add ellipsis menu to all rows with Delete action

* fix(editor): Global exec list update translations snd fix tabindex

* fix(editor): Global exec list redesign selection

* fix(editor): Global exec list fix scrolling container

* fix(editor): Global exec list fix running status

* fix(editor): Global exec list fix waiting status
This commit is contained in:
Csaba Tuncsik 2023-01-10 16:28:15 +01:00 committed by GitHub
parent 57e515dd4b
commit 819c4adb3c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 612 additions and 452 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,33 @@
<template>
<Modal :name="EXECUTIONS_MODAL_KEY" width="80%" :eventBus="modalBus">
<template #content>
<ExecutionsList @closeModal="onCloseModal" />
</template>
</Modal>
</template>
<script lang="ts">
import Vue from 'vue';
import ExecutionsList from '@/components/ExecutionsList.vue';
import Modal from '@/components/Modal.vue';
import { EXECUTIONS_MODAL_KEY } from '@/constants';
export default Vue.extend({
name: 'ExecutionsModal',
components: {
Modal,
ExecutionsList,
},
data() {
return {
modalBus: new Vue(),
EXECUTIONS_MODAL_KEY,
};
},
methods: {
onCloseModal() {
this.modalBus.$emit('close');
},
},
});
</script>

View file

@ -76,7 +76,7 @@ export default mixins(
debounceHelper,
workflowHelpers,
).extend({
name: 'executions-page',
name: 'executions-list',
components: {
ExecutionsSidebar,
},

View file

@ -99,7 +99,7 @@ export default mixins(pushConnection, workflowHelpers).extend({
syncTabsWithRoute(route: Route): void {
if (
route.name === VIEWS.EXECUTION_HOME ||
route.name === VIEWS.EXECUTIONS ||
route.name === VIEWS.WORKFLOW_EXECUTIONS ||
route.name === VIEWS.EXECUTION_PREVIEW
) {
this.activeHeaderTab = MAIN_HEADER_TABS.EXECUTIONS;

View file

@ -240,7 +240,7 @@ export default mixins(workflowHelpers, titleChange).extend({
onExecutionsTab(): boolean {
return [
VIEWS.EXECUTION_HOME.toString(),
VIEWS.EXECUTIONS.toString(),
VIEWS.WORKFLOW_EXECUTIONS.toString(),
VIEWS.EXECUTION_PREVIEW,
].includes(this.$route.name || '');
},

View file

@ -93,7 +93,6 @@
<script lang="ts">
import { IExecutionResponse, IMenuItem, IVersion } from '../Interface';
import ExecutionsList from '@/components/ExecutionsList.vue';
import GiftNotificationIcon from './GiftNotificationIcon.vue';
import WorkflowSettings from '@/components/WorkflowSettings.vue';
@ -111,7 +110,6 @@ import {
MODAL_CONFIRMED,
ABOUT_MODAL_KEY,
VERSIONS_MODAL_KEY,
EXECUTIONS_MODAL_KEY,
VIEWS,
PLACEHOLDER_EMPTY_WORKFLOW_ID,
} from '@/constants';
@ -138,7 +136,6 @@ export default mixins(
).extend({
name: 'MainSidebar',
components: {
ExecutionsList,
GiftNotificationIcon,
WorkflowSettings,
},
@ -239,8 +236,9 @@ export default mixins(
{
id: 'executions',
icon: 'tasks',
label: this.$locale.baseText('generic.executions'),
label: this.$locale.baseText('mainSidebar.executions'),
position: 'top',
activateOnRouteNames: [VIEWS.EXECUTIONS],
},
{
id: 'settings',
@ -390,7 +388,9 @@ export default mixins(
break;
}
case 'executions': {
this.uiStore.openModal(EXECUTIONS_MODAL_KEY);
if (this.$router.currentRoute.name !== VIEWS.EXECUTIONS) {
this.$router.push({ name: VIEWS.EXECUTIONS });
}
break;
}
case 'settings': {

View file

@ -62,7 +62,7 @@
</ModalRoot>
<ModalRoot :name="EXECUTIONS_MODAL_KEY">
<ExecutionsList />
<ExecutionsModal />
</ModalRoot>
<ModalRoot :name="WORKFLOW_ACTIVE_MODAL_KEY">
@ -153,7 +153,7 @@ import UpdatesPanel from './UpdatesPanel.vue';
import ValueSurvey from './ValueSurvey.vue';
import WorkflowSettings from './WorkflowSettings.vue';
import DeleteUserModal from './DeleteUserModal.vue';
import ExecutionsList from './ExecutionsList.vue';
import ExecutionsModal from './ExecutionsModal.vue';
import ActivationModal from './ActivationModal.vue';
import ImportCurlModal from './ImportCurlModal.vue';
import WorkflowShareModal from './WorkflowShareModal.ee.vue';
@ -173,7 +173,7 @@ export default Vue.extend({
DeleteUserModal,
DuplicateWorkflowDialog,
InviteUsersModal,
ExecutionsList,
ExecutionsModal,
ModalRoot,
OnboardingCallSignupModal,
PersonalizationModal,

View file

@ -297,7 +297,7 @@ export enum VIEWS {
HOMEPAGE = 'Homepage',
COLLECTION = 'TemplatesCollectionView',
EXECUTION = 'ExecutionById',
EXECUTIONS = 'ExecutionList',
EXECUTIONS = 'Executions',
EXECUTION_PREVIEW = 'ExecutionPreview',
EXECUTION_HOME = 'ExecutionsLandingPage',
TEMPLATE = 'TemplatesWorkflowView',
@ -319,6 +319,7 @@ export enum VIEWS {
FAKE_DOOR = 'ComingSoon',
COMMUNITY_NODES = 'CommunityNodes',
WORKFLOWS = 'WorkflowsView',
WORKFLOW_EXECUTIONS = 'WorkflowExecutions',
USAGE = 'Usage',
LOG_STREAMING_SETTINGS = 'LogStreamingSettingsView',
}

View file

@ -1,7 +1,8 @@
import { showMessage } from '@/mixins/showMessage';
import { VIEWS } from '@/constants';
import mixins from 'vue-typed-mixins';
import dateformat from 'dateformat';
import { VIEWS } from '@/constants';
import { showMessage } from '@/mixins/showMessage';
export const genericHelpers = mixins(showMessage).extend({
data() {
@ -21,17 +22,24 @@ export const genericHelpers = mixins(showMessage).extend({
displayTimer(msPassed: number, showMs = false): string {
if (msPassed < 60000) {
if (!showMs) {
return `${Math.floor(msPassed / 1000)} ${this.$locale.baseText('genericHelpers.sec')}`;
return `${Math.floor(msPassed / 1000)}${this.$locale.baseText(
'genericHelpers.secShort',
)}`;
}
return `${msPassed / 1000} ${this.$locale.baseText('genericHelpers.sec')}`;
return `${msPassed / 1000}${this.$locale.baseText('genericHelpers.secShort')}`;
}
const secondsPassed = Math.floor(msPassed / 1000);
const minutesPassed = Math.floor(secondsPassed / 60);
const secondsLeft = (secondsPassed - minutesPassed * 60).toString().padStart(2, '0');
return `${minutesPassed}:${secondsLeft} ${this.$locale.baseText('genericHelpers.min')}`;
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');
const [date, time] = formattedDate.split('#');
return { date, time };
},
editAllowedCheck(): boolean {
if (this.isReadOnly) {

View file

@ -430,11 +430,11 @@
"executionsList.confirmMessage.confirmButtonText": "Yes, delete",
"executionsList.confirmMessage.headline": "Delete Executions?",
"executionsList.confirmMessage.message": "Are you sure that you want to delete the {numSelected} selected execution(s)?",
"executionsList.deleteSelected": "Delete Selected",
"executionsList.clearSelection": "Clear selection",
"executionsList.error": "Failed",
"executionsList.filters": "Filters",
"executionsList.loadMore": "Load More",
"executionsList.mode": "Mode",
"executionsList.loadedAll": "No more executions to fetch",
"executionsList.modes.error": "error",
"executionsList.modes.integrated": "integrated",
"executionsList.modes.manual": "manual",
@ -449,10 +449,9 @@
"executionsList.retryWithOriginalWorkflow": "Retry with original workflow (from node with error)",
"executionsList.running": "Running",
"executionsList.succeeded": "Succeeded",
"executionsList.runningTime": "Running Time",
"executionsList.selectStatus": "Select Status",
"executionsList.selectWorkflow": "Select Workflow",
"executionsList.selected": "Selected",
"executionsList.selected": "{numSelected} execution selected:",
"executionsList.manual": "Manual execution",
"executionsList.showError.handleDeleteSelected.title": "Problem deleting executions",
"executionsList.showError.loadMore.title": "Problem loading executions",
@ -465,17 +464,14 @@
"executionsList.showMessage.retrySuccessfulTrue.title": "Retry successful",
"executionsList.showMessage.stopExecution.message": "Execution ID {activeExecutionId}",
"executionsList.showMessage.stopExecution.title": "Execution stopped",
"executionsList.startedAtId": "Started At / ID",
"executionsList.startedAt": "Started At",
"executionsList.started": "{date} at {time}",
"executionsList.id": "Execution ID",
"executionsList.status": "Status",
"executionsList.statusTooltipText.theWorkflowExecutionFailed": "The workflow execution failed.",
"executionsList.statusTooltipText.theWorkflowExecutionFailedButTheRetryWasSuccessful": "The workflow execution failed but the retry {entryRetrySuccessId} was successful.",
"executionsList.statusTooltipText.theWorkflowExecutionIsProbablyStillRunning": "The workflow execution is probably still running but it may have crashed and n8n cannot safely tell. ",
"executionsList.statusTooltipText.theWorkflowExecutionWasARetryOfAndFailed": "The workflow execution was a retry of {entryRetryOf} and failed.<br />New retries have to be started from the original execution.",
"executionsList.statusTooltipText.theWorkflowExecutionWasARetryOfAndItWasSuccessful": "The workflow execution was a retry of {entryRetryOf} and it was successful.",
"executionsList.statusTooltipText.theWorkflowExecutionWasSuccessful": "The worklow execution was successful.",
"executionsList.statusTooltipText.theWorkflowIsCurrentlyExecuting": "The worklow is currently executing.",
"executionsList.statusTooltipText.theWorkflowIsWaitingIndefinitely": "The workflow is waiting indefinitely for an incoming webhook call.",
"executionsList.statusTooltipText.theWorkflowIsWaitingTill": "The worklow is waiting till {waitDateDate} {waitDateTime}.",
"executionsList.statusText": "{status} in {time}",
"executionsList.statusRunning": "{status} for {time}",
"executionsList.statusWaiting": "{status} until {time}",
"executionsList.statusUnknown": "{status}",
"executionsList.stopExecution": "Stop Execution",
"executionsList.success": "Success",
"executionsList.successRetry": "Success retry",
@ -483,6 +479,8 @@
"executionsList.unsavedWorkflow": "[UNSAVED WORKFLOW]",
"executionsList.waiting": "Waiting",
"executionsList.workflowExecutions": "Workflow Executions",
"executionsList.view": "View",
"executionsList.stop": "Stop",
"executionSidebar.executionName": "Execution {id}",
"executionSidebar.searchPlaceholder": "Search executions...",
"executionView.onPaste.title": "Cannot paste here",
@ -534,7 +532,9 @@
"generic.oauth2Api": "OAuth2 API",
"genericHelpers.loading": "Loading",
"genericHelpers.min": "min",
"genericHelpers.minShort": "m",
"genericHelpers.sec": "sec",
"genericHelpers.secShort": "s",
"genericHelpers.showMessage.message": "Executions are read-only. Make changes from the <b>Workflow</b> tab.",
"genericHelpers.showMessage.title": "Cannot edit execution",
"mainSidebar.aboutN8n": "About n8n",
@ -566,6 +566,7 @@
"mainSidebar.showMessage.stopExecution.title": "Execution stopped",
"mainSidebar.templates": "Templates",
"mainSidebar.workflows": "Workflows",
"mainSidebar.executions": "All executions",
"menuActions.duplicate": "Duplicate",
"menuActions.download": "Download",
"menuActions.importFromUrl": "Import from URL...",

View file

@ -6,7 +6,7 @@ import ForgotMyPasswordView from './views/ForgotMyPasswordView.vue';
import MainHeader from '@/components/MainHeader/MainHeader.vue';
import MainSidebar from '@/components/MainSidebar.vue';
import NodeView from '@/views/NodeView.vue';
import ExecutionsView from '@/components/ExecutionsView/ExecutionsView.vue';
import WorkflowExecutionsList from '@/components/ExecutionsView/ExecutionsList.vue';
import ExecutionsLandingPage from '@/components/ExecutionsView/ExecutionsLandingPage.vue';
import ExecutionPreview from '@/components/ExecutionsView/ExecutionPreview.vue';
import SettingsView from './views/SettingsView.vue';
@ -25,6 +25,7 @@ import TemplatesCollectionView from '@/views/TemplatesCollectionView.vue';
import TemplatesWorkflowView from '@/views/TemplatesWorkflowView.vue';
import TemplatesSearchView from '@/views/TemplatesSearchView.vue';
import CredentialsView from '@/views/CredentialsView.vue';
import ExecutionsView from '@/views/ExecutionsView.vue';
import WorkflowsView from '@/views/WorkflowsView.vue';
import { IPermissions } from './Interface';
import { LOGIN_STATUS, ROLE } from '@/utils';
@ -199,6 +200,21 @@ const router = new Router({
},
},
},
{
path: '/executions',
name: VIEWS.EXECUTIONS,
components: {
default: ExecutionsView,
sidebar: MainSidebar,
},
meta: {
permissions: {
allow: {
loginStatus: [LOGIN_STATUS.LoggedIn],
},
},
},
},
{
path: '/workflow',
redirect: '/workflow/new',
@ -254,9 +270,9 @@ const router = new Router({
},
{
path: '/workflow/:name/executions',
name: VIEWS.EXECUTIONS,
name: VIEWS.WORKFLOW_EXECUTIONS,
components: {
default: ExecutionsView,
default: WorkflowExecutionsList,
header: MainHeader,
sidebar: MainSidebar,
},

View file

@ -0,0 +1,15 @@
<template>
<ExecutionsList />
</template>
<script lang="ts">
import Vue from 'vue';
import ExecutionsList from '@/components/ExecutionsList.vue';
export default Vue.extend({
name: 'ExecutionsView',
components: {
ExecutionsList,
},
});
</script>