Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(editor): Show multiple nodes in input pane schema view #9816

23 changes: 11 additions & 12 deletions cypress/e2e/14-mapping.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,16 +174,22 @@ describe('Data mapping', () => {
workflowPage.actions.zoomToFit();
workflowPage.actions.openNode('Set1');

ndv.actions.selectInputNode(SCHEDULE_TRIGGER_NODE_NAME);

ndv.getters.inputDataContainer().find('span').contains('count').realMouseDown();
ndv.actions.executePrevious();
ndv.actions.expandSchemaViewNode(SCHEDULE_TRIGGER_NODE_NAME);

const dataPill = ndv.getters
.inputDataContainer()
.findChildByTestId('run-data-schema-item')
.contains('count')
.should('be.visible');
dataPill.realMouseDown();
ndv.actions.mapToParameter('value');
ndv.getters
.inlineExpressionEditorInput()
.should('have.text', `{{ $('${SCHEDULE_TRIGGER_NODE_NAME}').item.json.input[0].count }}`);

ndv.actions.switchInputMode('Table');
ndv.actions.selectInputNode(SCHEDULE_TRIGGER_NODE_NAME);
ndv.actions.mapDataFromHeader(1, 'value');
ndv.getters
.inlineExpressionEditorInput()
Expand All @@ -194,7 +200,6 @@ describe('Data mapping', () => {

ndv.actions.selectInputNode('Set');

ndv.actions.executePrevious();
ndv.getters.executingLoader().should('not.exist');
ndv.getters.inputDataContainer().should('exist');
ndv.actions.validateExpressionPreview('value', '0 [object Object]');
Expand Down Expand Up @@ -291,14 +296,8 @@ describe('Data mapping', () => {
ndv.actions.executePrevious();
ndv.getters.executingLoader().should('not.exist');
ndv.getters.inputDataContainer().should('exist');
ndv.getters
.inputDataContainer()
.should('exist')
.find('span')
.contains('test_name')
.realMouseDown();
ndv.actions.mapToParameter('value');

ndv.actions.switchInputMode('Table');
ndv.actions.mapDataFromHeader(1, 'value');
ndv.actions.validateExpressionPreview('value', 'test_value');
ndv.actions.selectInputNode(SCHEDULE_TRIGGER_NODE_NAME);
ndv.actions.validateExpressionPreview('value', 'test_value');
Expand Down
3 changes: 2 additions & 1 deletion cypress/e2e/5-ndv.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,10 @@ describe('NDV', () => {
cy.createFixtureWorkflow('NDV-test-select-input.json', 'NDV test select input');
workflowPage.actions.zoomToFit();
workflowPage.getters.canvasNodes().last().dblclick();
ndv.actions.switchInputMode('Table');
ndv.getters.inputSelect().click();
ndv.getters.inputOption().last().click();
ndv.getters.inputDataContainer().find('[class*=schema_]').should('exist');
ndv.getters.inputDataContainer().should('be.visible');
ndv.getters.inputDataContainer().should('contain', 'start');
ndv.getters.backToCanvas().click();
ndv.getters.container().should('not.be.visible');
Expand Down
5 changes: 5 additions & 0 deletions cypress/pages/ndv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ export class NDV extends BasePage {
nodeRunErrorIndicator: () => cy.getByTestId('node-run-info-danger'),
nodeRunErrorMessage: () => cy.getByTestId('node-error-message'),
nodeRunErrorDescription: () => cy.getByTestId('node-error-description'),
schemaViewNode: () => cy.getByTestId('run-data-schema-node'),
schemaViewNodeName: () => cy.getByTestId('run-data-schema-node-name'),
};

actions = {
Expand Down Expand Up @@ -212,6 +214,9 @@ export class NDV extends BasePage {
this.getters.inputSelect().find('.el-select').click();
this.getters.inputOption().contains(nodeName).click();
},
expandSchemaViewNode: (nodeName: string) => {
this.getters.schemaViewNodeName().contains(nodeName).click();
},
addDefaultPinnedData: () => {
this.actions.editPinnedData();
this.actions.savePinnedData();
Expand Down
3 changes: 3 additions & 0 deletions packages/design-system/src/css/_tokens.dark.scss
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,9 @@
--color-mfa-recovery-code-color: var(--color-text-dark);
--color-mfa-lose-access-text-color: var(--color-danger);

// Text highlight
--color-text-highlight-background: var(--prim-color-alt-d-shade-600);

// AI
--node-type-background-l: 20%;
--node-type-supplemental-label-color-h: 235;
Expand Down
3 changes: 3 additions & 0 deletions packages/design-system/src/css/_tokens.scss
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,9 @@
--color-mfa-recovery-code-color: var(--prim-gray-490);
--color-mfa-lose-access-text-color: var(--color-danger);

// Text highlight
--color-text-highlight-background: var(--prim-color-alt-d-shade-150);

// AI
--node-type-background-l: 95%;
--node-type-supplemental-label-color-h: 235;
Expand Down
2 changes: 1 addition & 1 deletion packages/design-system/src/css/reset.scss
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ ins {
}

mark {
background-color: var(--color-warning);
background-color: var(--color-text-highlight-background);
color: var(--color-text-dark);
font-style: italic;
font-weight: bold;
Expand Down
9 changes: 8 additions & 1 deletion packages/editor-ui/src/components/InputNodeSelect.vue
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,8 @@ function onInputNodeChange(value: string) {

<style lang="scss" module>
.select {
max-width: 224px;
--max-select-width: 224px;
max-width: var(--max-select-width);

:global(.el-input--suffix .el-input__inner) {
padding-left: calc(var(--spacing-l) + var(--spacing-4xs));
Expand All @@ -180,13 +181,19 @@ function onInputNodeChange(value: string) {
.title {
color: var(--color-text-dark);
font-weight: var(--font-weight-regular);
max-width: var(--max-select-width);
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}

.disabled .title {
color: var(--color-text-light);
}

.subtitle {
margin-left: auto;
padding-left: var(--spacing-2xs);
color: var(--color-text-light);
font-weight: var(--font-weight-regular);
}
Expand Down
1 change: 1 addition & 0 deletions packages/editor-ui/src/components/InputPanel.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<template>
<RunData
:node="currentNode"
:nodes="isMappingMode ? rootNodesParents : parentNodes"
:workflow="workflow"
:run-index="runIndex"
:linked-runs="linkedRuns"
Expand Down
4 changes: 2 additions & 2 deletions packages/editor-ui/src/components/NDVFloatingNodes.vue
Original file line number Diff line number Diff line change
Expand Up @@ -218,9 +218,9 @@ defineExpose({
content: '';
position: absolute;
top: -35%;
right: -30%;
right: -15%;
bottom: -35%;
left: -30%;
left: -15%;
z-index: -1;
}
.outputMain &,
Expand Down
86 changes: 44 additions & 42 deletions packages/editor-ui/src/components/RunData.vue
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,22 @@
data-test-id="run-data-pane-header"
@click.stop
>
<RunDataSearch
v-if="showIOSearch"
v-model="search"
:class="$style.search"
:pane-type="paneType"
:display-mode="displayMode"
:is-area-active="isPaneActive"
@focus="activatePane"
/>

<n8n-radio-buttons
v-show="
hasNodeRun && (inputData.length || binaryData.length || search) && !editMode.enabled
"
:model-value="displayMode"
:options="buttons"
:options="displayModes"
data-test-id="ndv-run-data-display-mode"
@update:model-value="onDisplayModeChange"
/>
Expand All @@ -66,7 +76,6 @@
:title="$locale.baseText('runData.editOutput')"
:circle="false"
:disabled="node?.disabled"
class="ml-2xs"
icon="pencil-alt"
type="tertiary"
data-test-id="ndv-edit-pinned-data"
Expand Down Expand Up @@ -107,12 +116,16 @@
</div>
</div>

<div v-if="extraControlsLocation === 'header'" :class="$style.inputSelect">
<div v-if="inputSelectLocation === 'header'" :class="$style.inputSelect">
<slot name="input-select"></slot>
</div>

<div v-if="maxRunIndex > 0" v-show="!editMode.enabled" :class="$style.runSelector">
<slot v-if="extraControlsLocation === 'runs'" name="input-select"></slot>
<div
v-if="maxRunIndex > 0 && !isInputSchemaView"
v-show="!editMode.enabled"
:class="$style.runSelector"
>
<slot v-if="inputSelectLocation === 'runs'" name="input-select"></slot>

<n8n-select
:model-value="runIndex"
Expand Down Expand Up @@ -148,18 +161,9 @@
</n8n-tooltip>

<slot name="run-info"></slot>

<RunDataSearch
v-if="showIOSearch && extraControlsLocation === 'runs'"
v-model="search"
:class="$style.search"
:pane-type="paneType"
:is-area-active="isPaneActive"
@focus="activatePane"
/>
</div>

<slot name="before-data" />
<slot v-if="!isInputSchemaView" name="before-data" />

<n8n-callout
v-for="hint in getNodeHints()"
Expand All @@ -171,26 +175,18 @@
</n8n-callout>

<div
v-if="maxOutputIndex > 0 && branches.length > 1"
v-if="maxOutputIndex > 0 && branches.length > 1 && !isInputSchemaView"
:class="$style.outputs"
data-test-id="branches"
>
<slot v-if="extraControlsLocation === 'outputs'" name="input-select"></slot>
<slot v-if="inputSelectLocation === 'outputs'" name="input-select"></slot>

<div :class="$style.tabs">
<n8n-tabs
:model-value="currentOutputIndex"
:options="branches"
@update:model-value="onBranchChange"
/>

<RunDataSearch
v-if="showIOSearch && extraControlsLocation === 'outputs'"
v-model="search"
:pane-type="paneType"
:is-area-active="isPaneActive"
@focus="activatePane"
/>
</div>
</div>

Expand All @@ -199,13 +195,14 @@
!hasRunError &&
hasNodeRun &&
((dataCount > 0 && maxRunIndex === 0) || search) &&
!isArtificialRecoveredEventItem
!isArtificialRecoveredEventItem &&
!isSchemaView
"
v-show="!editMode.enabled && !hasRunError"
:class="[$style.itemsCount, { [$style.muted]: paneType === 'input' && maxRunIndex === 0 }]"
data-test-id="ndv-items-count"
>
<slot v-if="extraControlsLocation === 'items'" name="input-select"></slot>
<slot v-if="inputSelectLocation === 'items'" name="input-select"></slot>

<n8n-text v-if="search" :class="$style.itemsText">
{{
Expand All @@ -223,15 +220,6 @@
})
}}
</n8n-text>

<RunDataSearch
v-if="showIOSearch && extraControlsLocation === 'items'"
v-model="search"
:class="$style.search"
:pane-type="paneType"
:is-area-active="isPaneActive"
@focus="activatePane"
/>
</div>

<div ref="dataContainer" :class="$style.dataContainer" data-test-id="ndv-data-container">
Expand Down Expand Up @@ -426,14 +414,17 @@

<Suspense v-else-if="hasNodeRun && isSchemaView">
<RunDataSchema
:data="jsonData"
:nodes="nodes"
:mapping-enabled="mappingEnabled"
:distance-from-active="distanceFromActive"
:node="node"
:data="jsonData"
:pane-type="paneType"
:connection-type="connectionType"
:run-index="runIndex"
:output-index="currentOutputIndex"
:total-runs="maxRunIndex"
:search="search"
@clear:search="onSearchClear"
/>
</Suspense>

Expand Down Expand Up @@ -587,6 +578,7 @@ import type {
NodeHint,
NodeError,
Workflow,
IConnectedNode,
} from 'n8n-workflow';
import { NodeHelpers, NodeConnectionType } from 'n8n-workflow';

Expand Down Expand Up @@ -667,6 +659,10 @@ export default defineComponent({
type: Object as PropType<INodeUi | null>,
default: null,
},
nodes: {
type: Array as PropType<IConnectedNode[]>,
default: () => [],
},
workflow: {
type: Object as PropType<Workflow>,
required: true,
Expand Down Expand Up @@ -795,6 +791,9 @@ export default defineComponent({
isSchemaView(): boolean {
return this.displayMode === 'schema';
},
isInputSchemaView(): boolean {
return this.isSchemaView && this.paneType === 'input';
},
isTriggerNode(): boolean {
if (this.node === null) {
return false;
Expand All @@ -815,7 +814,7 @@ export default defineComponent({
!(this.binaryData && this.binaryData.length > 0)
);
},
buttons(): Array<{ label: string; value: string }> {
displayModes(): Array<{ label: string; value: string }> {
const defaults = [
{ label: this.$locale.baseText('runData.table'), value: 'table' },
{ label: this.$locale.baseText('runData.json'), value: 'json' },
Expand Down Expand Up @@ -1046,7 +1045,8 @@ export default defineComponent({
showIOSearch(): boolean {
return this.hasNodeRun && !this.hasRunError && this.unfilteredInputData.length > 0;
},
extraControlsLocation() {
inputSelectLocation() {
if (this.isSchemaView) return 'none';
if (!this.hasNodeRun) return 'header';
if (this.maxRunIndex > 0) return 'runs';
if (this.maxOutputIndex > 0 && this.branches.length > 1) {
Expand Down Expand Up @@ -1521,7 +1521,7 @@ export default defineComponent({
return inputData;
},
getFilteredData(inputData: INodeExecutionData[]): INodeExecutionData[] {
if (!this.search) {
if (!this.search || this.isSchemaView) {
return inputData;
}

Expand Down Expand Up @@ -1795,7 +1795,7 @@ export default defineComponent({

.itemsText {
flex-shrink: 0;
overflow-x: hidden;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
Expand Down Expand Up @@ -1914,7 +1914,9 @@ export default defineComponent({
display: flex;
justify-content: flex-end;
flex-grow: 1;
gap: var(--spacing-2xs);
}

.tooltipContain {
max-width: 240px;
}
Expand Down
1 change: 0 additions & 1 deletion packages/editor-ui/src/components/RunDataPinButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ const visible = computed(() =>

<style lang="scss" module>
.pinDataButton {
margin-left: var(--spacing-2xs);
svg {
transition: transform 0.3s ease;
}
Expand Down
Loading
Loading