mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-14 00:24: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;
|
||||
}
|
||||
const metadata = get(workflowRunData.value, [node.value.name, props.runIndex, 'metadata'], null);
|
||||
if (!metadata?.parentExecution && !metadata?.subExecution) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return metadata;
|
||||
});
|
||||
|
||||
const hasReleatedExectuion = computed((): boolean => {
|
||||
return Boolean(subWorkflowData.value?.subExecution || subWorkflowData.value?.parentExecution);
|
||||
});
|
||||
|
||||
const hasInputOverwrite = computed((): boolean => {
|
||||
if (!node.value) {
|
||||
return false;
|
||||
|
@ -977,6 +977,10 @@ function onDisplayModeChange(newDisplayMode: IRunDataDisplayMode) {
|
|||
}
|
||||
|
||||
function getRunLabel(option: number) {
|
||||
if (!node.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
let itemsCount = 0;
|
||||
for (let i = 0; i <= maxOutputIndex.value; i++) {
|
||||
itemsCount += getPinDataOrLiveData(getRawInputData(option - 1, i)).length;
|
||||
|
@ -985,7 +989,18 @@ function getRunLabel(option: number) {
|
|||
adjustToNumber: 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;
|
||||
}
|
||||
|
||||
|
@ -1335,42 +1350,59 @@ defineExpose({ enterEditMode });
|
|||
v-show="!editMode.enabled"
|
||||
: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
|
||||
:model-value="runIndex"
|
||||
:class="$style.runSelectorInner"
|
||||
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"
|
||||
<N8nSelect
|
||||
:model-value="runIndex"
|
||||
:class="$style.runSelectorSelect"
|
||||
size="small"
|
||||
data-test-id="link-run"
|
||||
@click="toggleLinkRuns"
|
||||
/>
|
||||
</N8nTooltip>
|
||||
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>
|
||||
|
||||
<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>
|
||||
|
||||
<slot v-if="!displaysMultipleNodes" name="before-data" />
|
||||
|
@ -1424,20 +1456,31 @@ defineExpose({ enterEditMode });
|
|||
}}
|
||||
</N8nText>
|
||||
<N8nText v-else :class="$style.itemsText">
|
||||
{{
|
||||
$locale.baseText('ndv.output.items', {
|
||||
adjustToNumber: dataCount,
|
||||
interpolate: { count: dataCount },
|
||||
})
|
||||
}}
|
||||
<span>
|
||||
{{
|
||||
$locale.baseText('ndv.output.items', {
|
||||
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>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="subWorkflowData && !(paneType === 'input' && hasInputOverwrite)"
|
||||
:class="$style.parentExecutionInfo"
|
||||
>
|
||||
<a @click.stop="openRelatedExecution(subWorkflowData, displayMode)">
|
||||
<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
|
||||
|
@ -1630,6 +1673,7 @@ defineExpose({ enterEditMode });
|
|||
:total-runs="maxRunIndex"
|
||||
:has-default-hover-state="paneType === 'input' && !search"
|
||||
:search="search"
|
||||
:sub-execution-override="subWorkflowData?.subExecution"
|
||||
@mounted="emit('tableMounted', $event)"
|
||||
@active-row-changed="onItemHover"
|
||||
@display-mode-change="onDisplayModeChange"
|
||||
|
@ -1911,6 +1955,7 @@ defineExpose({ enterEditMode });
|
|||
padding-left: var(--spacing-s);
|
||||
padding-right: var(--spacing-s);
|
||||
padding-bottom: var(--spacing-s);
|
||||
flex-flow: wrap;
|
||||
|
||||
.itemsText {
|
||||
flex-shrink: 0;
|
||||
|
@ -1932,24 +1977,31 @@ defineExpose({ enterEditMode });
|
|||
}
|
||||
|
||||
.runSelector {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-flow: wrap;
|
||||
padding-left: var(--spacing-s);
|
||||
padding-right: var(--spacing-s);
|
||||
padding-bottom: var(--spacing-s);
|
||||
display: flex;
|
||||
gap: var(--spacing-4xs);
|
||||
align-items: center;
|
||||
margin-bottom: var(--spacing-s);
|
||||
gap: var(--spacing-3xs);
|
||||
|
||||
:global(.el-input--suffix .el-input__inner) {
|
||||
padding-right: var(--spacing-l);
|
||||
}
|
||||
}
|
||||
|
||||
.search {
|
||||
margin-left: auto;
|
||||
.runSelectorInner {
|
||||
display: flex;
|
||||
gap: var(--spacing-4xs);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.runSelectorInner {
|
||||
max-width: 172px;
|
||||
.runSelectorSelect {
|
||||
max-width: 205px;
|
||||
}
|
||||
|
||||
.search {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
|
@ -2112,7 +2164,7 @@ defineExpose({ enterEditMode });
|
|||
|
||||
.parentExecutionInfo {
|
||||
font-size: var(--font-size-xs);
|
||||
padding: 0 0 var(--spacing-s) var(--spacing-s);
|
||||
margin-left: var(--spacing-3xs);
|
||||
|
||||
svg {
|
||||
padding-bottom: 2px;
|
||||
|
|
|
@ -6,7 +6,7 @@ import { useWorkflowsStore } from '@/stores/workflows.store';
|
|||
import { getMappedExpression } from '@/utils/mappingUtils';
|
||||
import { getPairedItemId } from '@/utils/pairedItemUtils';
|
||||
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 Draggable from './Draggable.vue';
|
||||
import MappingPill from './MappingPill.vue';
|
||||
|
@ -32,6 +32,7 @@ type Props = {
|
|||
mappingEnabled?: boolean;
|
||||
hasDefaultHoverState?: boolean;
|
||||
search?: string;
|
||||
subExecutionOverride?: RelatedExecution;
|
||||
};
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
|
@ -341,7 +342,10 @@ function convertToTable(inputData: INodeExecutionData[]): ITableData {
|
|||
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.hasExecutionIds = true;
|
||||
} else {
|
||||
|
|
|
@ -957,6 +957,7 @@
|
|||
"ndv.output.branch": "Branch",
|
||||
"ndv.output.executing": "Executing node...",
|
||||
"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.settings": "Settings",
|
||||
"ndv.output.noOutputData.message.settingsOption": "> \"Always Output Data\".",
|
||||
|
@ -2614,7 +2615,7 @@
|
|||
"executionUsage.button.upgrade": "Upgrade plan",
|
||||
"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.",
|
||||
"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.number": "Number",
|
||||
"type.dateTime": "Date & Time",
|
||||
|
|
|
@ -304,6 +304,10 @@ export class ExecuteWorkflow implements INodeType {
|
|||
}
|
||||
}
|
||||
|
||||
this.setMetadata({
|
||||
subExecutionsCount: items.length,
|
||||
});
|
||||
|
||||
return returnData;
|
||||
} else {
|
||||
try {
|
||||
|
@ -334,6 +338,7 @@ export class ExecuteWorkflow implements INodeType {
|
|||
executionId: executionResult.executionId,
|
||||
workflowId: workflowInfo.id ?? (workflowProxy.$workflow.id as string),
|
||||
},
|
||||
subExecutionsCount: 1,
|
||||
});
|
||||
|
||||
if (!waitForSubWorkflow) {
|
||||
|
|
|
@ -2150,6 +2150,7 @@ export interface ITaskMetadata {
|
|||
subRun?: ITaskSubRunMetadata[];
|
||||
parentExecution?: RelatedExecution;
|
||||
subExecution?: RelatedExecution;
|
||||
subExecutionsCount?: number;
|
||||
}
|
||||
|
||||
// The data that gets returned when a node runs
|
||||
|
|
Loading…
Reference in a new issue