feat: add subexecution count

This commit is contained in:
Mutasem Aldmour 2024-11-12 10:43:11 +01:00
parent 597aa3ee3b
commit f32039647b
No known key found for this signature in database
GPG key ID: 3DFA8122BB7FD6B8
5 changed files with 125 additions and 62 deletions

View file

@ -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,11 +1350,12 @@ defineExpose({ enterEditMode });
v-show="!editMode.enabled"
:class="$style.runSelector"
>
<div :class="$style.runSelectorInner">
<slot v-if="inputSelectLocation === 'runs'" name="input-select"></slot>
<N8nSelect
:model-value="runIndex"
:class="$style.runSelectorInner"
:class="$style.runSelectorSelect"
size="small"
teleported
data-test-id="run-selector"
@ -1373,6 +1389,22 @@ defineExpose({ enterEditMode });
<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" />
<N8nCallout
@ -1424,20 +1456,31 @@ defineExpose({ enterEditMode });
}}
</N8nText>
<N8nText v-else :class="$style.itemsText">
<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)"
<a
v-if="
subWorkflowData && hasReleatedExectuion && !(paneType === 'input' && hasInputOverwrite)
"
:class="$style.parentExecutionInfo"
@click.stop="openRelatedExecution(subWorkflowData, displayMode)"
>
<a @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;

View file

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

View file

@ -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": "Youre 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",

View file

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

View file

@ -2150,6 +2150,7 @@ export interface ITaskMetadata {
subRun?: ITaskSubRunMetadata[];
parentExecution?: RelatedExecution;
subExecution?: RelatedExecution;
subExecutionsCount?: number;
}
// The data that gets returned when a node runs