mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-14 16:44:07 -08:00
feat: add subexecution count
This commit is contained in:
parent
597aa3ee3b
commit
f32039647b
|
@ -514,13 +514,13 @@ const subWorkflowData = computed((): ITaskMetadata | null => {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const metadata = get(workflowRunData.value, [node.value.name, props.runIndex, 'metadata'], null);
|
const metadata = get(workflowRunData.value, [node.value.name, props.runIndex, 'metadata'], null);
|
||||||
if (!metadata?.parentExecution && !metadata?.subExecution) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return metadata;
|
return metadata;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const hasReleatedExectuion = computed((): boolean => {
|
||||||
|
return Boolean(subWorkflowData.value?.subExecution || subWorkflowData.value?.parentExecution);
|
||||||
|
});
|
||||||
|
|
||||||
const hasInputOverwrite = computed((): boolean => {
|
const hasInputOverwrite = computed((): boolean => {
|
||||||
if (!node.value) {
|
if (!node.value) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -977,6 +977,10 @@ function onDisplayModeChange(newDisplayMode: IRunDataDisplayMode) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRunLabel(option: number) {
|
function getRunLabel(option: number) {
|
||||||
|
if (!node.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let itemsCount = 0;
|
let itemsCount = 0;
|
||||||
for (let i = 0; i <= maxOutputIndex.value; i++) {
|
for (let i = 0; i <= maxOutputIndex.value; i++) {
|
||||||
itemsCount += getPinDataOrLiveData(getRawInputData(option - 1, i)).length;
|
itemsCount += getPinDataOrLiveData(getRawInputData(option - 1, i)).length;
|
||||||
|
@ -985,7 +989,18 @@ function getRunLabel(option: number) {
|
||||||
adjustToNumber: itemsCount,
|
adjustToNumber: itemsCount,
|
||||||
interpolate: { count: itemsCount },
|
interpolate: { count: itemsCount },
|
||||||
});
|
});
|
||||||
const itemsLabel = itemsCount > 0 ? ` (${items})` : '';
|
|
||||||
|
const metadata = get(workflowRunData.value, [node.value.name, props.runIndex, 'metadata'], null);
|
||||||
|
const subexecutions = metadata?.subExecutionsCount
|
||||||
|
? i18n.baseText('ndv.output.andSubExecutions', {
|
||||||
|
adjustToNumber: metadata.subExecutionsCount,
|
||||||
|
interpolate: {
|
||||||
|
count: metadata.subExecutionsCount,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
: '';
|
||||||
|
|
||||||
|
const itemsLabel = itemsCount > 0 ? ` (${items}${subexecutions})` : '';
|
||||||
return option + i18n.baseText('ndv.output.of') + (maxRunIndex.value + 1) + itemsLabel;
|
return option + i18n.baseText('ndv.output.of') + (maxRunIndex.value + 1) + itemsLabel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1335,42 +1350,59 @@ defineExpose({ enterEditMode });
|
||||||
v-show="!editMode.enabled"
|
v-show="!editMode.enabled"
|
||||||
:class="$style.runSelector"
|
:class="$style.runSelector"
|
||||||
>
|
>
|
||||||
<slot v-if="inputSelectLocation === 'runs'" name="input-select"></slot>
|
<div :class="$style.runSelectorInner">
|
||||||
|
<slot v-if="inputSelectLocation === 'runs'" name="input-select"></slot>
|
||||||
|
|
||||||
<N8nSelect
|
<N8nSelect
|
||||||
:model-value="runIndex"
|
:model-value="runIndex"
|
||||||
:class="$style.runSelectorInner"
|
:class="$style.runSelectorSelect"
|
||||||
size="small"
|
|
||||||
teleported
|
|
||||||
data-test-id="run-selector"
|
|
||||||
@update:model-value="onRunIndexChange"
|
|
||||||
@click.stop
|
|
||||||
>
|
|
||||||
<template #prepend>{{ $locale.baseText('ndv.output.run') }}</template>
|
|
||||||
<N8nOption
|
|
||||||
v-for="option in maxRunIndex + 1"
|
|
||||||
:key="option"
|
|
||||||
:label="getRunLabel(option)"
|
|
||||||
:value="option - 1"
|
|
||||||
></N8nOption>
|
|
||||||
</N8nSelect>
|
|
||||||
|
|
||||||
<N8nTooltip v-if="canLinkRuns" placement="right">
|
|
||||||
<template #content>
|
|
||||||
{{ $locale.baseText(linkedRuns ? 'runData.unlinking.hint' : 'runData.linking.hint') }}
|
|
||||||
</template>
|
|
||||||
<N8nIconButton
|
|
||||||
:icon="linkedRuns ? 'unlink' : 'link'"
|
|
||||||
class="linkRun"
|
|
||||||
text
|
|
||||||
type="tertiary"
|
|
||||||
size="small"
|
size="small"
|
||||||
data-test-id="link-run"
|
teleported
|
||||||
@click="toggleLinkRuns"
|
data-test-id="run-selector"
|
||||||
/>
|
@update:model-value="onRunIndexChange"
|
||||||
</N8nTooltip>
|
@click.stop
|
||||||
|
>
|
||||||
|
<template #prepend>{{ $locale.baseText('ndv.output.run') }}</template>
|
||||||
|
<N8nOption
|
||||||
|
v-for="option in maxRunIndex + 1"
|
||||||
|
:key="option"
|
||||||
|
:label="getRunLabel(option)"
|
||||||
|
:value="option - 1"
|
||||||
|
></N8nOption>
|
||||||
|
</N8nSelect>
|
||||||
|
|
||||||
<slot name="run-info"></slot>
|
<N8nTooltip v-if="canLinkRuns" placement="right">
|
||||||
|
<template #content>
|
||||||
|
{{ $locale.baseText(linkedRuns ? 'runData.unlinking.hint' : 'runData.linking.hint') }}
|
||||||
|
</template>
|
||||||
|
<N8nIconButton
|
||||||
|
:icon="linkedRuns ? 'unlink' : 'link'"
|
||||||
|
class="linkRun"
|
||||||
|
text
|
||||||
|
type="tertiary"
|
||||||
|
size="small"
|
||||||
|
data-test-id="link-run"
|
||||||
|
@click="toggleLinkRuns"
|
||||||
|
/>
|
||||||
|
</N8nTooltip>
|
||||||
|
|
||||||
|
<slot name="run-info"></slot>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a
|
||||||
|
v-if="
|
||||||
|
subWorkflowData && hasReleatedExectuion && !(paneType === 'input' && hasInputOverwrite)
|
||||||
|
"
|
||||||
|
:class="$style.parentExecutionInfo"
|
||||||
|
@click.stop="openRelatedExecution(subWorkflowData, displayMode)"
|
||||||
|
>
|
||||||
|
<N8nIcon icon="external-link-alt" size="xsmall" />
|
||||||
|
{{
|
||||||
|
subWorkflowData.parentExecution
|
||||||
|
? $locale.baseText('runData.openParentExecution')
|
||||||
|
: $locale.baseText('runData.openSubExecution')
|
||||||
|
}}
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<slot v-if="!displaysMultipleNodes" name="before-data" />
|
<slot v-if="!displaysMultipleNodes" name="before-data" />
|
||||||
|
@ -1424,20 +1456,31 @@ defineExpose({ enterEditMode });
|
||||||
}}
|
}}
|
||||||
</N8nText>
|
</N8nText>
|
||||||
<N8nText v-else :class="$style.itemsText">
|
<N8nText v-else :class="$style.itemsText">
|
||||||
{{
|
<span>
|
||||||
$locale.baseText('ndv.output.items', {
|
{{
|
||||||
adjustToNumber: dataCount,
|
$locale.baseText('ndv.output.items', {
|
||||||
interpolate: { count: dataCount },
|
adjustToNumber: dataCount,
|
||||||
})
|
interpolate: { count: dataCount },
|
||||||
}}
|
})
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
<span v-if="subWorkflowData?.subExecutionsCount">
|
||||||
|
{{
|
||||||
|
$locale.baseText('ndv.output.andSubExecutions', {
|
||||||
|
adjustToNumber: subWorkflowData.subExecutionsCount,
|
||||||
|
interpolate: { count: subWorkflowData.subExecutionsCount },
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
</N8nText>
|
</N8nText>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
<a
|
||||||
v-if="subWorkflowData && !(paneType === 'input' && hasInputOverwrite)"
|
v-if="
|
||||||
:class="$style.parentExecutionInfo"
|
subWorkflowData && hasReleatedExectuion && !(paneType === 'input' && hasInputOverwrite)
|
||||||
>
|
"
|
||||||
<a @click.stop="openRelatedExecution(subWorkflowData, displayMode)">
|
:class="$style.parentExecutionInfo"
|
||||||
|
@click.stop="openRelatedExecution(subWorkflowData, displayMode)"
|
||||||
|
>
|
||||||
<N8nIcon icon="external-link-alt" size="xsmall" />
|
<N8nIcon icon="external-link-alt" size="xsmall" />
|
||||||
{{
|
{{
|
||||||
subWorkflowData.parentExecution
|
subWorkflowData.parentExecution
|
||||||
|
@ -1630,6 +1673,7 @@ defineExpose({ enterEditMode });
|
||||||
:total-runs="maxRunIndex"
|
:total-runs="maxRunIndex"
|
||||||
:has-default-hover-state="paneType === 'input' && !search"
|
:has-default-hover-state="paneType === 'input' && !search"
|
||||||
:search="search"
|
:search="search"
|
||||||
|
:sub-execution-override="subWorkflowData?.subExecution"
|
||||||
@mounted="emit('tableMounted', $event)"
|
@mounted="emit('tableMounted', $event)"
|
||||||
@active-row-changed="onItemHover"
|
@active-row-changed="onItemHover"
|
||||||
@display-mode-change="onDisplayModeChange"
|
@display-mode-change="onDisplayModeChange"
|
||||||
|
@ -1911,6 +1955,7 @@ defineExpose({ enterEditMode });
|
||||||
padding-left: var(--spacing-s);
|
padding-left: var(--spacing-s);
|
||||||
padding-right: var(--spacing-s);
|
padding-right: var(--spacing-s);
|
||||||
padding-bottom: var(--spacing-s);
|
padding-bottom: var(--spacing-s);
|
||||||
|
flex-flow: wrap;
|
||||||
|
|
||||||
.itemsText {
|
.itemsText {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
@ -1932,24 +1977,31 @@ defineExpose({ enterEditMode });
|
||||||
}
|
}
|
||||||
|
|
||||||
.runSelector {
|
.runSelector {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-flow: wrap;
|
||||||
padding-left: var(--spacing-s);
|
padding-left: var(--spacing-s);
|
||||||
padding-right: var(--spacing-s);
|
padding-right: var(--spacing-s);
|
||||||
padding-bottom: var(--spacing-s);
|
margin-bottom: var(--spacing-s);
|
||||||
display: flex;
|
gap: var(--spacing-3xs);
|
||||||
gap: var(--spacing-4xs);
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
:global(.el-input--suffix .el-input__inner) {
|
:global(.el-input--suffix .el-input__inner) {
|
||||||
padding-right: var(--spacing-l);
|
padding-right: var(--spacing-l);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.search {
|
.runSelectorInner {
|
||||||
margin-left: auto;
|
display: flex;
|
||||||
|
gap: var(--spacing-4xs);
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.runSelectorInner {
|
.runSelectorSelect {
|
||||||
max-width: 172px;
|
max-width: 205px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search {
|
||||||
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pagination {
|
.pagination {
|
||||||
|
@ -2112,7 +2164,7 @@ defineExpose({ enterEditMode });
|
||||||
|
|
||||||
.parentExecutionInfo {
|
.parentExecutionInfo {
|
||||||
font-size: var(--font-size-xs);
|
font-size: var(--font-size-xs);
|
||||||
padding: 0 0 var(--spacing-s) var(--spacing-s);
|
margin-left: var(--spacing-3xs);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
padding-bottom: 2px;
|
padding-bottom: 2px;
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||||
import { getMappedExpression } from '@/utils/mappingUtils';
|
import { getMappedExpression } from '@/utils/mappingUtils';
|
||||||
import { getPairedItemId } from '@/utils/pairedItemUtils';
|
import { getPairedItemId } from '@/utils/pairedItemUtils';
|
||||||
import { shorten } from '@/utils/typesUtils';
|
import { shorten } from '@/utils/typesUtils';
|
||||||
import type { GenericValue, IDataObject, INodeExecutionData } from 'n8n-workflow';
|
import type { GenericValue, IDataObject, INodeExecutionData, RelatedExecution } from 'n8n-workflow';
|
||||||
import { computed, onMounted, ref, watch } from 'vue';
|
import { computed, onMounted, ref, watch } from 'vue';
|
||||||
import Draggable from './Draggable.vue';
|
import Draggable from './Draggable.vue';
|
||||||
import MappingPill from './MappingPill.vue';
|
import MappingPill from './MappingPill.vue';
|
||||||
|
@ -32,6 +32,7 @@ type Props = {
|
||||||
mappingEnabled?: boolean;
|
mappingEnabled?: boolean;
|
||||||
hasDefaultHoverState?: boolean;
|
hasDefaultHoverState?: boolean;
|
||||||
search?: string;
|
search?: string;
|
||||||
|
subExecutionOverride?: RelatedExecution;
|
||||||
};
|
};
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
@ -341,7 +342,10 @@ function convertToTable(inputData: INodeExecutionData[]): ITableData {
|
||||||
leftEntryColumns = entryColumns;
|
leftEntryColumns = entryColumns;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.metadata?.subExecution) {
|
if (props.subExecutionOverride) {
|
||||||
|
metadata.data.push({ subExecution: props.subExecutionOverride });
|
||||||
|
metadata.hasExecutionIds = true;
|
||||||
|
} else if (data.metadata?.subExecution) {
|
||||||
metadata.data.push(data.metadata);
|
metadata.data.push(data.metadata);
|
||||||
metadata.hasExecutionIds = true;
|
metadata.hasExecutionIds = true;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -957,6 +957,7 @@
|
||||||
"ndv.output.branch": "Branch",
|
"ndv.output.branch": "Branch",
|
||||||
"ndv.output.executing": "Executing node...",
|
"ndv.output.executing": "Executing node...",
|
||||||
"ndv.output.items": "{count} item | {count} items",
|
"ndv.output.items": "{count} item | {count} items",
|
||||||
|
"ndv.output.andSubExecutions": ", {count} sub execution | , {count} sub executions",
|
||||||
"ndv.output.noOutputData.message": "n8n stops executing the workflow when a node has no output data. You can change this default behaviour via",
|
"ndv.output.noOutputData.message": "n8n stops executing the workflow when a node has no output data. You can change this default behaviour via",
|
||||||
"ndv.output.noOutputData.message.settings": "Settings",
|
"ndv.output.noOutputData.message.settings": "Settings",
|
||||||
"ndv.output.noOutputData.message.settingsOption": "> \"Always Output Data\".",
|
"ndv.output.noOutputData.message.settingsOption": "> \"Always Output Data\".",
|
||||||
|
@ -2614,7 +2615,7 @@
|
||||||
"executionUsage.button.upgrade": "Upgrade plan",
|
"executionUsage.button.upgrade": "Upgrade plan",
|
||||||
"executionUsage.expired.text": "Your trial is over. Upgrade now to keep your data.",
|
"executionUsage.expired.text": "Your trial is over. Upgrade now to keep your data.",
|
||||||
"executionUsage.ranOutOfExecutions.text": "You’re out of executions. Upgrade your plan to keep automating.",
|
"executionUsage.ranOutOfExecutions.text": "You’re out of executions. Upgrade your plan to keep automating.",
|
||||||
"openExecution.missingExeuctionId": "Could not find execution. Make sure workflow saving is turned on.",
|
"openExecution.missingExeuctionId": "Make sure this workflow saves executions via the settings.",
|
||||||
"type.string": "String",
|
"type.string": "String",
|
||||||
"type.number": "Number",
|
"type.number": "Number",
|
||||||
"type.dateTime": "Date & Time",
|
"type.dateTime": "Date & Time",
|
||||||
|
|
|
@ -304,6 +304,10 @@ export class ExecuteWorkflow implements INodeType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.setMetadata({
|
||||||
|
subExecutionsCount: items.length,
|
||||||
|
});
|
||||||
|
|
||||||
return returnData;
|
return returnData;
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
|
@ -334,6 +338,7 @@ export class ExecuteWorkflow implements INodeType {
|
||||||
executionId: executionResult.executionId,
|
executionId: executionResult.executionId,
|
||||||
workflowId: workflowInfo.id ?? (workflowProxy.$workflow.id as string),
|
workflowId: workflowInfo.id ?? (workflowProxy.$workflow.id as string),
|
||||||
},
|
},
|
||||||
|
subExecutionsCount: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!waitForSubWorkflow) {
|
if (!waitForSubWorkflow) {
|
||||||
|
|
|
@ -2150,6 +2150,7 @@ export interface ITaskMetadata {
|
||||||
subRun?: ITaskSubRunMetadata[];
|
subRun?: ITaskSubRunMetadata[];
|
||||||
parentExecution?: RelatedExecution;
|
parentExecution?: RelatedExecution;
|
||||||
subExecution?: RelatedExecution;
|
subExecution?: RelatedExecution;
|
||||||
|
subExecutionsCount?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The data that gets returned when a node runs
|
// The data that gets returned when a node runs
|
||||||
|
|
Loading…
Reference in a new issue