feat: Execution custom data saving and filtering (#5496)

* wip: workflow execution filtering

* fix: import type failing to build

* fix: remove console.logs

* feat: execution metadata migrations

* fix(editor): Move global executions filter to its own component

* fix(editor): Using the same filter component in workflow level

* fix(editor): a small housekeeping

* checking workflowId in filter applied

* fix(editor): update filter after resolving merge conflicts

* fix(editor): unify empy filter status

* feat(editor): add datetime picker to filter

* feat(editor): add meta fields

* fix: fix button override in datepicker panel

* feat(editor): add filter metadata

* feat(core): add 'startedBefore' execution filter prop

* feat(core): add 'tags' execution query filter

* Revert "feat(core): add 'tags' execution query filter"

This reverts commit a7b968081c91290b0c94df18c6a73d29950222d9.

* feat(editor): add translations and tooltip and counting selected filter props

* fix(editor): fix label layouts

* fix(editor): update custom data docs link

* fix(editor): update custom data tooltip position

* fix(editor): update tooltip text

* refactor: Ignore metadata if not enabled by license

* fix(editor): Add paywall states to advanced execution filter

* refactor: Save custom data also for worker mode

* fix: Remove duplicate migration name from list

* fix(editor): Reducing filter complexity and add debounce to text inputs

* fix(editor): Remove unused import, add comment

* fix(editor): simplify event listener

* fix: Prevent error when there are running executions

* test(editor): Add advanced execution filter basic unit test

* test(editor): Add advanced execution filter state change unit test

* fix: Small lint issue

* feat: Add indices to speed up queries

* feat: add customData limits

* refactor: put metadata save in transaction

* chore: remove unneed comment

* test: add tests for execution metadata

* fix(editor): Fixes after merge conflict

* fix(editor): Remove unused import

* wordings and ui fixes

* fix(editor): type fixes

* feat: add code node autocompletions for customData

* fix: Prevent transaction issues and ambiguous ID in sql clauses

* fix(editor): Suppress requesting current executions if metadata is used in filter (#5739)

* fix(editor): Suppress requesting current executions if metadata is used in filter

* fix(editor): Fix arrows for select in popover

* refactor: Improve performance by correcting database indices

* fix: Lint issue

* test: Fix broken test

* fix: Broken test

* test: add call data check for saveExecutionMetadata test

---------

Co-authored-by: Valya Bullions <valya@n8n.io>
Co-authored-by: Alex Grozav <alex@grozav.com>
Co-authored-by: Omar Ajoue <krynble@gmail.com>
Co-authored-by: Romain Minaud <romain.minaud@gmail.com>
This commit is contained in:
Csaba Tuncsik
2023-03-23 18:07:46 +01:00
committed by GitHub
parent 4c583e2be4
commit d78a41db54
30 changed files with 1430 additions and 269 deletions

View File

@@ -31,9 +31,14 @@ import {
VIEWS,
WEBHOOK_NODE_TYPE,
} from '@/constants';
import { IExecutionsListResponse, INodeUi, ITag, IWorkflowDb } from '@/Interface';
import {
ExecutionStatus,
ExecutionFilterType,
IExecutionsListResponse,
INodeUi,
ITag,
IWorkflowDb,
} from '@/Interface';
import {
IExecutionsSummary,
IConnection,
IConnections,
@@ -50,7 +55,7 @@ import { Route } from 'vue-router';
import { executionHelpers } from '@/mixins/executionsHelpers';
import { range as _range } from 'lodash-es';
import { debounceHelper } from '@/mixins/debounce';
import { getNodeViewTab, NO_NETWORK_ERROR_CODE } from '@/utils';
import { getNodeViewTab, isEmpty, NO_NETWORK_ERROR_CODE } from '@/utils';
import { workflowHelpers } from '@/mixins/workflowHelpers';
import { mapStores } from 'pinia';
import { useWorkflowsStore } from '@/stores/workflows';
@@ -58,6 +63,7 @@ import { useUIStore } from '@/stores/ui';
import { useSettingsStore } from '@/stores/settings';
import { useNodeTypesStore } from '@/stores/nodeTypes';
import { useTagsStore } from '@/stores/tags';
import { executionFilterToQueryFilter } from '@/utils/executionUtils';
export default mixins(
restApi,
@@ -74,7 +80,7 @@ export default mixins(
return {
loading: false,
loadingMore: false,
filter: { finished: true, status: '' },
filter: {} as ExecutionFilterType,
};
},
computed: {
@@ -86,7 +92,7 @@ export default mixins(
return this.loading || !this.executions.length || activeNotPresent;
},
filterApplied(): boolean {
return this.filter.status !== '';
return this.filter.status !== 'all';
},
workflowDataNotLoaded(): boolean {
return (
@@ -101,29 +107,10 @@ export default mixins(
return this.workflowsStore.getTotalFinishedExecutionsCount;
},
requestFilter(): IDataObject {
const rFilter: IDataObject = { workflowId: this.currentWorkflow };
if (this.filter.status === 'waiting') {
rFilter.waitTill = true;
} else if (this.filter.status !== '') {
rFilter.finished = this.filter.status === 'success';
}
switch (this.filter.status as ExecutionStatus) {
case 'waiting':
rFilter.status = ['waiting'];
break;
case 'error':
rFilter.status = ['failed', 'crashed'];
break;
case 'success':
rFilter.status = ['success'];
break;
case 'running':
rFilter.status = ['running'];
break;
}
return rFilter;
return executionFilterToQueryFilter({
...this.filter,
workflowId: this.currentWorkflow,
});
},
},
watch: {
@@ -317,8 +304,8 @@ export default mixins(
);
}
},
onFilterUpdated(newFilter: { finished: boolean; status: string }): void {
this.filter = newFilter;
onFilterUpdated(filter: ExecutionFilterType): void {
this.filter = filter;
this.setExecutions();
},
async setExecutions(): Promise<void> {

View File

@@ -17,64 +17,7 @@
>
{{ $locale.baseText('executionsList.autoRefresh') }}
</el-checkbox>
<n8n-popover trigger="click">
<template #reference>
<div :class="$style.filterButton">
<n8n-button
icon="filter"
type="tertiary"
size="medium"
:active="statusFilterApplied"
data-test-id="executions-filter-button"
>
<n8n-badge v-if="statusFilterApplied" theme="primary" class="mr-4xs">1</n8n-badge>
{{ $locale.baseText('executionsList.filters') }}
</n8n-button>
</div>
</template>
<div :class="$style['filters-dropdown']">
<div class="mb-s">
<n8n-input-label
:label="$locale.baseText('executions.ExecutionStatus')"
:bold="false"
size="small"
color="text-base"
class="mb-3xs"
/>
<n8n-select
v-model="filter.status"
size="small"
ref="typeInput"
:class="$style['type-input']"
:placeholder="$locale.baseText('generic.any')"
data-test-id="execution-status-select"
@change="onFilterChange"
>
<n8n-option
v-for="item in executionStatuses"
:key="item.id"
:label="item.name"
:value="item.id"
:data-test-id="`execution-status-${item.id}`"
>
</n8n-option>
</n8n-select>
</div>
<div :class="[$style.filterMessage, 'mt-s']" v-if="statusFilterApplied">
<n8n-link @click="resetFilters">
{{ $locale.baseText('generic.reset') }}
</n8n-link>
</div>
</div>
</n8n-popover>
</div>
<div v-show="statusFilterApplied" class="mb-xs">
<n8n-info-tip :bold="false">
{{ $locale.baseText('generic.filtersApplied') }}
<n8n-link @click="resetFilters" size="small">
{{ $locale.baseText('generic.resetAllFilters') }}
</n8n-link>
</n8n-info-tip>
<execution-filter popover-placement="left-start" @filterChanged="onFilterChanged" />
</div>
<div
:class="$style.executionList"
@@ -87,7 +30,7 @@
<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">
<div v-if="executions.length === 0" :class="$style.noResultsContainer">
<n8n-text color="text-base" size="medium" align="center">
{{ $locale.baseText('executionsLandingPage.noResults') }}
</n8n-text>
@@ -115,20 +58,23 @@
<script lang="ts">
import ExecutionCard from '@/components/ExecutionsView/ExecutionCard.vue';
import ExecutionsInfoAccordion from '@/components/ExecutionsView/ExecutionsInfoAccordion.vue';
import ExecutionFilter from '@/components/ExecutionFilter.vue';
import { VIEWS } from '@/constants';
import { IExecutionsSummary } from '@/Interface';
import type { IExecutionsSummary } from 'n8n-workflow';
import { Route } from 'vue-router';
import Vue from 'vue';
import { PropType } from 'vue';
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui';
import { useWorkflowsStore } from '@/stores/workflows';
import { ExecutionFilterType } from '@/Interface';
export default Vue.extend({
name: 'executions-sidebar',
components: {
ExecutionCard,
ExecutionsInfoAccordion,
ExecutionFilter,
},
props: {
executions: {
@@ -147,26 +93,13 @@ export default Vue.extend({
data() {
return {
VIEWS,
filter: {
status: '',
},
filter: {} as ExecutionFilterType,
autoRefresh: false,
autoRefreshInterval: undefined as undefined | NodeJS.Timer,
};
},
computed: {
...mapStores(useUIStore, useWorkflowsStore),
statusFilterApplied(): boolean {
return this.filter.status !== '';
},
executionStatuses(): Array<{ id: string; name: string }> {
return [
{ id: 'error', name: this.$locale.baseText('executionsList.error') },
{ id: 'running', name: this.$locale.baseText('executionsList.running') },
{ id: 'success', name: this.$locale.baseText('executionsList.success') },
{ id: 'waiting', name: this.$locale.baseText('executionsList.waiting') },
];
},
},
watch: {
$route(to: Route, from: Route) {
@@ -215,8 +148,8 @@ export default Vue.extend({
onRefresh(): void {
this.$emit('refresh');
},
onFilterChange(): void {
this.$emit('filterUpdated', this.prepareFilter());
onFilterChanged(filter: ExecutionFilterType) {
this.$emit('filterUpdated', filter);
},
reloadExecutions(): void {
this.$emit('reloadExecutions');
@@ -232,16 +165,6 @@ export default Vue.extend({
this.autoRefreshInterval = setInterval(() => this.onRefresh(), 4 * 1000); // refresh data every 4 secs
}
},
async resetFilters(): Promise<void> {
this.filter.status = '';
this.$emit('filterUpdated', this.prepareFilter());
},
prepareFilter(): object {
return {
finished: this.filter.status !== 'running',
status: this.filter.status,
};
},
checkListSize(): void {
const sidebarContainer = this.$refs.container as HTMLElement;
const currentExecutionCard = this.$refs[
@@ -304,7 +227,7 @@ export default Vue.extend({
display: flex;
align-items: center;
justify-content: space-between;
padding-right: var(--spacing-l);
padding-right: var(--spacing-m);
button {
display: flex;