fix(editor): Support 'View Execution' links with multiple branches (#14345)

This commit is contained in:
Charlie Kolb
2025-04-02 14:55:55 +02:00
committed by GitHub
parent 9c8a5f9c57
commit 744e2da3f9
3 changed files with 89 additions and 76 deletions

View File

@@ -86,11 +86,11 @@ import {
} from '@n8n/design-system';
import { storeToRefs } from 'pinia';
import { useRoute } from 'vue-router';
import { useExecutionHelpers } from '@/composables/useExecutionHelpers';
import { useUIStore } from '@/stores/ui.store';
import { useSchemaPreviewStore } from '@/stores/schemaPreview.store';
import { asyncComputed } from '@vueuse/core';
import { usePostHog } from '@/stores/posthog.store';
import ViewSubExecution from './ViewSubExecution.vue';
const LazyRunDataTable = defineAsyncComponent(
async () => await import('@/components/RunDataTable.vue'),
@@ -200,7 +200,6 @@ const nodeHelpers = useNodeHelpers();
const externalHooks = useExternalHooks();
const telemetry = useTelemetry();
const i18n = useI18n();
const { trackOpeningRelatedExecution, resolveRelatedExecutionUrl } = useExecutionHelpers();
const node = toRef(props, 'node');
@@ -558,12 +557,6 @@ const activeTaskMetadata = computed((): ITaskMetadata | null => {
return workflowRunData.value?.[node.value.name]?.[props.runIndex]?.metadata ?? null;
});
const hasRelatedExecution = computed(() => {
return Boolean(
activeTaskMetadata.value?.subExecution ?? activeTaskMetadata.value?.parentExecution,
);
});
const hasInputOverwrite = computed((): boolean => {
if (!node.value) {
return false;
@@ -1313,26 +1306,6 @@ function onSearchClear() {
document.dispatchEvent(new KeyboardEvent('keyup', { key: '/' }));
}
function getExecutionLinkLabel(task: ITaskMetadata): string | undefined {
if (task.parentExecution) {
return i18n.baseText('runData.openParentExecution', {
interpolate: { id: task.parentExecution.executionId },
});
}
if (task.subExecution) {
if (activeTaskMetadata.value?.subExecutionsCount === 1) {
return i18n.baseText('runData.openSubExecutionSingle');
} else {
return i18n.baseText('runData.openSubExecutionWithId', {
interpolate: { id: task.subExecution.executionId },
});
}
}
return;
}
defineExpose({ enterEditMode });
</script>
@@ -1504,20 +1477,11 @@ defineExpose({ enterEditMode });
<slot name="run-info"></slot>
</div>
<a
v-if="
activeTaskMetadata && hasRelatedExecution && !(paneType === 'input' && hasInputOverwrite)
"
:class="$style.relatedExecutionInfo"
data-test-id="related-execution-link"
:href="resolveRelatedExecutionUrl(activeTaskMetadata)"
target="_blank"
@click.stop="trackOpeningRelatedExecution(activeTaskMetadata, displayMode)"
>
<N8nIcon icon="external-link-alt" size="xsmall" />
{{ getExecutionLinkLabel(activeTaskMetadata) }}
</a>
<ViewSubExecution
v-if="activeTaskMetadata && !(paneType === 'input' && hasInputOverwrite)"
:task-metadata="activeTaskMetadata"
:display-mode="displayMode"
/>
</div>
<slot v-if="!displaysMultipleNodes" name="before-data" />
@@ -1544,6 +1508,11 @@ defineExpose({ enterEditMode });
data-test-id="branches"
>
<slot v-if="inputSelectLocation === 'outputs'" name="input-select"></slot>
<ViewSubExecution
v-if="activeTaskMetadata && !(paneType === 'input' && hasInputOverwrite)"
:task-metadata="activeTaskMetadata"
:display-mode="displayMode"
/>
<div :class="$style.tabs">
<N8nTabs
@@ -1594,20 +1563,11 @@ defineExpose({ enterEditMode });
}}
</span>
</N8nText>
<a
v-if="
activeTaskMetadata && hasRelatedExecution && !(paneType === 'input' && hasInputOverwrite)
"
:class="$style.relatedExecutionInfo"
data-test-id="related-execution-link"
:href="resolveRelatedExecutionUrl(activeTaskMetadata)"
target="_blank"
@click.stop="trackOpeningRelatedExecution(activeTaskMetadata, displayMode)"
>
<N8nIcon icon="external-link-alt" size="xsmall" />
{{ getExecutionLinkLabel(activeTaskMetadata) }}
</a>
<ViewSubExecution
v-if="activeTaskMetadata && !(paneType === 'input' && hasInputOverwrite)"
:task-metadata="activeTaskMetadata"
:display-mode="displayMode"
/>
</div>
<div ref="dataContainerRef" :class="$style.dataContainer" data-test-id="ndv-data-container">
@@ -2304,15 +2264,6 @@ defineExpose({ enterEditMode });
.schema {
padding: 0 var(--spacing-s);
}
.relatedExecutionInfo {
font-size: var(--font-size-s);
margin-left: var(--spacing-3xs);
svg {
padding-bottom: 2px;
}
}
</style>
<style lang="scss" scoped>

View File

@@ -6,10 +6,10 @@ import type { INodeTypeDescription, NodeConnectionType, NodeError } from 'n8n-wo
import { computed } from 'vue';
import NodeIcon from '@/components/NodeIcon.vue';
import AiRunContentBlock from './AiRunContentBlock.vue';
import { useExecutionHelpers } from '@/composables/useExecutionHelpers';
import { useI18n } from '@/composables/useI18n';
import { formatTokenUsageCount, getConsumedTokens } from '@/components/RunDataAi/utils';
import ConsumedTokensDetails from '@/components/ConsumedTokensDetails.vue';
import ViewSubExecution from '../ViewSubExecution.vue';
interface RunMeta {
startTimeMs: number;
@@ -30,7 +30,6 @@ const props = defineProps<{
const nodeTypesStore = useNodeTypesStore();
const workflowsStore = useWorkflowsStore();
const { trackOpeningRelatedExecution, resolveRelatedExecutionUrl } = useExecutionHelpers();
const i18n = useI18n();
const consumedTokensSum = computed(() => {
@@ -105,15 +104,8 @@ const outputError = computed(() => {
}}
</n8n-tooltip>
</li>
<li v-if="runMeta?.subExecution">
<a
:href="resolveRelatedExecutionUrl(runMeta)"
target="_blank"
@click.stop="trackOpeningRelatedExecution(runMeta, 'ai')"
>
<N8nIcon icon="external-link-alt" size="xsmall" />
{{ i18n.baseText('runData.openSubExecutionSingle') }}
</a>
<li v-if="runMeta">
<ViewSubExecution :task-metadata="runMeta" :display-mode="'ai'" :inline="true" />
</li>
<li v-if="(consumedTokensSum?.totalTokens ?? 0) > 0" :class="$style.tokensUsage">
{{

View File

@@ -0,0 +1,70 @@
<script setup lang="ts">
import { useExecutionHelpers } from '@/composables/useExecutionHelpers';
import { useI18n } from '@/composables/useI18n';
import type { IRunDataDisplayMode } from '@/Interface';
import type { ITaskMetadata } from 'n8n-workflow';
import { computed } from 'vue';
const { trackOpeningRelatedExecution, resolveRelatedExecutionUrl } = useExecutionHelpers();
const i18n = useI18n();
const props = withDefaults(
defineProps<{
taskMetadata: ITaskMetadata;
displayMode: IRunDataDisplayMode;
inline?: boolean;
}>(),
{
inline: false,
},
);
const hasRelatedExecution = computed(() => {
return Boolean(props.taskMetadata.subExecution ?? props.taskMetadata.parentExecution);
});
function getExecutionLinkLabel(task: ITaskMetadata): string | undefined {
if (task.parentExecution) {
return i18n.baseText('runData.openParentExecution', {
interpolate: { id: task.parentExecution.executionId },
});
}
if (task.subExecution) {
if (props.taskMetadata.subExecutionsCount === 1) {
return i18n.baseText('runData.openSubExecutionSingle');
} else {
return i18n.baseText('runData.openSubExecutionWithId', {
interpolate: { id: task.subExecution.executionId },
});
}
}
return;
}
</script>
<template>
<a
v-if="hasRelatedExecution"
:class="{ [$style.relatedExecutionInfo]: !inline }"
data-test-id="related-execution-link"
:href="resolveRelatedExecutionUrl(taskMetadata)"
target="_blank"
@click.stop="trackOpeningRelatedExecution(taskMetadata, displayMode)"
>
<N8nIcon icon="external-link-alt" size="xsmall" />
{{ getExecutionLinkLabel(taskMetadata) }}
</a>
</template>
<style lang="scss" module>
.relatedExecutionInfo {
font-size: var(--font-size-s);
margin-left: var(--spacing-3xs);
svg {
padding-bottom: var(--spacing-5xs);
}
}
</style>