mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
fix(editor): Show mappings by default in sub-node NDVs when the root node isn't executed (#12642)
This commit is contained in:
parent
114ed88368
commit
fb662dd95c
|
@ -4,10 +4,15 @@ import InputPanel, { type Props } from '@/components/InputPanel.vue';
|
|||
import { STORES } from '@/constants';
|
||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||
import { createTestingPinia } from '@pinia/testing';
|
||||
import { NodeConnectionType, type IConnections, type INodeExecutionData } from 'n8n-workflow';
|
||||
import { waitFor } from '@testing-library/vue';
|
||||
import {
|
||||
NodeConnectionType,
|
||||
type IConnections,
|
||||
type INodeExecutionData,
|
||||
type IRunData,
|
||||
} from 'n8n-workflow';
|
||||
import { setActivePinia } from 'pinia';
|
||||
import { mockedStore } from '../__tests__/utils';
|
||||
import { waitFor } from '@testing-library/vue';
|
||||
|
||||
vi.mock('vue-router', () => {
|
||||
return {
|
||||
|
@ -24,7 +29,7 @@ const nodes = [
|
|||
createTestNode({ name: 'Tool' }),
|
||||
];
|
||||
|
||||
const render = (props: Partial<Props> = {}, pinData?: INodeExecutionData[]) => {
|
||||
const render = (props: Partial<Props> = {}, pinData?: INodeExecutionData[], runData?: IRunData) => {
|
||||
const connections: IConnections = {
|
||||
[nodes[0].name]: {
|
||||
[NodeConnectionType.Main]: [
|
||||
|
@ -50,12 +55,38 @@ const render = (props: Partial<Props> = {}, pinData?: INodeExecutionData[]) => {
|
|||
setActivePinia(pinia);
|
||||
|
||||
const workflow = createTestWorkflow({ nodes, connections });
|
||||
useWorkflowsStore().setWorkflow(workflow);
|
||||
const workflowStore = useWorkflowsStore();
|
||||
|
||||
workflowStore.setWorkflow(workflow);
|
||||
|
||||
if (pinData) {
|
||||
mockedStore(useWorkflowsStore).pinDataByNodeName.mockReturnValue(pinData);
|
||||
}
|
||||
|
||||
if (runData) {
|
||||
workflowStore.setWorkflowExecutionData({
|
||||
id: '',
|
||||
workflowData: {
|
||||
id: '',
|
||||
name: '',
|
||||
active: false,
|
||||
createdAt: '',
|
||||
updatedAt: '',
|
||||
nodes,
|
||||
connections,
|
||||
versionId: '',
|
||||
},
|
||||
finished: false,
|
||||
mode: 'trigger',
|
||||
status: 'success',
|
||||
startedAt: new Date(),
|
||||
createdAt: new Date(),
|
||||
data: {
|
||||
resultData: { runData },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const workflowObject = createTestWorkflowObject({
|
||||
nodes,
|
||||
connections,
|
||||
|
@ -83,4 +114,27 @@ describe('InputPanel', () => {
|
|||
await waitFor(() => expect(queryByTestId('ndv-data-size-warning')).not.toBeInTheDocument());
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("opens mapping tab by default if the node hasn't run yet", async () => {
|
||||
const { findByTestId } = render({ currentNodeName: 'Tool' });
|
||||
|
||||
expect((await findByTestId('radio-button-mapping')).parentNode).toBeChecked();
|
||||
expect((await findByTestId('radio-button-debugging')).parentNode).not.toBeChecked();
|
||||
});
|
||||
|
||||
it('opens debugging tab by default if the node has already run', async () => {
|
||||
const { findByTestId } = render({ currentNodeName: 'Tool' }, undefined, {
|
||||
Tool: [
|
||||
{
|
||||
startTime: 0,
|
||||
executionTime: 0,
|
||||
source: [],
|
||||
data: {},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect((await findByTestId('radio-button-mapping')).parentNode).not.toBeChecked();
|
||||
expect((await findByTestId('radio-button-debugging')).parentNode).toBeChecked();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,27 +1,27 @@
|
|||
<script setup lang="ts">
|
||||
import { useI18n } from '@/composables/useI18n';
|
||||
import { useTelemetry } from '@/composables/useTelemetry';
|
||||
import {
|
||||
CRON_NODE_TYPE,
|
||||
INTERVAL_NODE_TYPE,
|
||||
MANUAL_TRIGGER_NODE_TYPE,
|
||||
START_NODE_TYPE,
|
||||
} from '@/constants';
|
||||
import { useNDVStore } from '@/stores/ndv.store';
|
||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||
import { useUIStore } from '@/stores/ui.store';
|
||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||
import { waitingNodeTooltip } from '@/utils/executionUtils';
|
||||
import { uniqBy } from 'lodash-es';
|
||||
import { N8nRadioButtons, N8nText, N8nTooltip } from 'n8n-design-system';
|
||||
import type { INodeInputConfiguration, INodeOutputConfiguration, Workflow } from 'n8n-workflow';
|
||||
import { NodeConnectionType, NodeHelpers } from 'n8n-workflow';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { useNDVStore } from '../stores/ndv.store';
|
||||
import InputNodeSelect from './InputNodeSelect.vue';
|
||||
import NodeExecuteButton from './NodeExecuteButton.vue';
|
||||
import RunData from './RunData.vue';
|
||||
import WireMeUp from './WireMeUp.vue';
|
||||
import { useTelemetry } from '@/composables/useTelemetry';
|
||||
import { N8nRadioButtons, N8nTooltip, N8nText } from 'n8n-design-system';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
type MappingMode = 'debugging' | 'mapping';
|
||||
|
||||
|
@ -71,7 +71,7 @@ const telemetry = useTelemetry();
|
|||
|
||||
const showDraggableHintWithDelay = ref(false);
|
||||
const draggableHintShown = ref(false);
|
||||
const inputMode = ref<MappingMode>('debugging');
|
||||
|
||||
const mappedNode = ref<string | null>(null);
|
||||
const inputModes = [
|
||||
{ value: 'mapping', label: i18n.baseText('ndv.input.mapping') },
|
||||
|
@ -88,6 +88,27 @@ const {
|
|||
focusedMappableInput,
|
||||
isMappingOnboarded: isUserOnboarded,
|
||||
} = storeToRefs(ndvStore);
|
||||
|
||||
const rootNode = computed(() => {
|
||||
if (!activeNode.value) return null;
|
||||
|
||||
return props.workflow.getChildNodes(activeNode.value.name, 'ALL').at(0) ?? null;
|
||||
});
|
||||
|
||||
const hasRootNodeRun = computed(() => {
|
||||
return !!(
|
||||
rootNode.value && workflowsStore.getWorkflowExecution?.data?.resultData.runData[rootNode.value]
|
||||
);
|
||||
});
|
||||
|
||||
const inputMode = ref<MappingMode>(
|
||||
// Show debugging mode by default only when the node has already run
|
||||
activeNode.value &&
|
||||
workflowsStore.getWorkflowExecution?.data?.resultData.runData[activeNode.value.name]
|
||||
? 'debugging'
|
||||
: 'mapping',
|
||||
);
|
||||
|
||||
const isMappingMode = computed(() => isActiveNodeConfig.value && inputMode.value === 'mapping');
|
||||
const showDraggableHint = computed(() => {
|
||||
const toIgnore = [START_NODE_TYPE, MANUAL_TRIGGER_NODE_TYPE, CRON_NODE_TYPE, INTERVAL_NODE_TYPE];
|
||||
|
@ -159,12 +180,6 @@ const isExecutingPrevious = computed(() => {
|
|||
});
|
||||
const workflowRunning = computed(() => uiStore.isActionActive.workflowRunning);
|
||||
|
||||
const rootNode = computed(() => {
|
||||
if (!activeNode.value) return null;
|
||||
|
||||
return props.workflow.getChildNodes(activeNode.value.name, 'ALL').at(0) ?? null;
|
||||
});
|
||||
|
||||
const rootNodesParents = computed(() => {
|
||||
if (!rootNode.value) return [];
|
||||
return props.workflow.getParentNodesByDepth(rootNode.value);
|
||||
|
@ -404,9 +419,28 @@ function activatePane() {
|
|||
v-if="(isActiveNodeConfig && rootNode) || parentNodes.length"
|
||||
:class="$style.noOutputData"
|
||||
>
|
||||
<N8nText tag="div" :bold="true" color="text-dark" size="large">{{
|
||||
i18n.baseText('ndv.input.noOutputData.title')
|
||||
}}</N8nText>
|
||||
<template v-if="isMappingEnabled || hasRootNodeRun">
|
||||
<N8nText tag="div" :bold="true" color="text-dark" size="large">{{
|
||||
i18n.baseText('ndv.input.noOutputData.title')
|
||||
}}</N8nText>
|
||||
</template>
|
||||
<template v-else>
|
||||
<N8nText tag="div" :bold="true" color="text-dark" size="large">{{
|
||||
i18n.baseText('ndv.input.rootNodeHasNotRun.title')
|
||||
}}</N8nText>
|
||||
<N8nText tag="div" color="text-dark" size="medium">
|
||||
<i18n-t tag="span" keypath="ndv.input.rootNodeHasNotRun.description">
|
||||
<template #link>
|
||||
<a
|
||||
href="#"
|
||||
data-test-id="switch-to-mapping-mode-link"
|
||||
@click.prevent="onInputModeChange('mapping')"
|
||||
>{{ i18n.baseText('ndv.input.rootNodeHasNotRun.description.link') }}</a
|
||||
>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</N8nText>
|
||||
</template>
|
||||
<N8nTooltip v-if="!readOnly" :visible="showDraggableHint && showDraggableHintWithDelay">
|
||||
<template #content>
|
||||
<div
|
||||
|
@ -423,6 +457,7 @@ function activatePane() {
|
|||
:transparent="true"
|
||||
:node-name="(isActiveNodeConfig ? rootNode : currentNodeName) ?? ''"
|
||||
:label="i18n.baseText('ndv.input.noOutputData.executePrevious')"
|
||||
class="mt-m"
|
||||
telemetry-source="inputs"
|
||||
data-test-id="execute-previous-node"
|
||||
@execute="onNodeExecute"
|
||||
|
@ -494,11 +529,7 @@ function activatePane() {
|
|||
margin-left: auto;
|
||||
}
|
||||
.noOutputData {
|
||||
max-width: 180px;
|
||||
|
||||
> *:first-child {
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
max-width: 250px;
|
||||
|
||||
> * {
|
||||
margin-bottom: var(--spacing-2xs);
|
||||
|
|
|
@ -28,19 +28,6 @@ exports[`InputPanel > should render 1`] = `
|
|||
role="radiogroup"
|
||||
>
|
||||
|
||||
<label
|
||||
aria-checked="false"
|
||||
class="n8n-radio-button container hoverable"
|
||||
role="radio"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="button medium"
|
||||
data-test-id="radio-button-mapping"
|
||||
>
|
||||
Mapping
|
||||
</div>
|
||||
</label>
|
||||
<label
|
||||
aria-checked="true"
|
||||
class="n8n-radio-button container hoverable"
|
||||
|
@ -49,6 +36,19 @@ exports[`InputPanel > should render 1`] = `
|
|||
>
|
||||
<div
|
||||
class="button active medium"
|
||||
data-test-id="radio-button-mapping"
|
||||
>
|
||||
Mapping
|
||||
</div>
|
||||
</label>
|
||||
<label
|
||||
aria-checked="false"
|
||||
class="n8n-radio-button container hoverable"
|
||||
role="radio"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="button medium"
|
||||
data-test-id="radio-button-debugging"
|
||||
>
|
||||
Debugging
|
||||
|
|
|
@ -984,6 +984,9 @@
|
|||
"ndv.input.notConnected.learnMore": "Learn more",
|
||||
"ndv.input.disabled": "The '{nodeName}' node is disabled and won’t execute.",
|
||||
"ndv.input.disabled.cta": "Enable it",
|
||||
"ndv.input.rootNodeHasNotRun.title": "Parent node hasn’t run yet",
|
||||
"ndv.input.rootNodeHasNotRun.description": "Inputs that the parent node sends to this one will appear here. To map data in from previous nodes, use the {link} view.",
|
||||
"ndv.input.rootNodeHasNotRun.description.link": "mapping",
|
||||
"ndv.output": "Output",
|
||||
"ndv.output.ai.empty": "👈 Use these logs to see information on how the {node} node completed processing. You can click on a node to see the input it received and data it output.",
|
||||
"ndv.output.ai.waiting": "Waiting for message",
|
||||
|
|
Loading…
Reference in a new issue