feat(editor): Schema preview UI updates (#13578)
Some checks are pending
Test Master / install-and-build (push) Waiting to run
Test Master / Unit tests (18.x) (push) Blocked by required conditions
Test Master / Unit tests (20.x) (push) Blocked by required conditions
Test Master / Unit tests (22.4) (push) Blocked by required conditions
Test Master / Lint (push) Blocked by required conditions
Test Master / Notify Slack on failure (push) Blocked by required conditions

This commit is contained in:
Elias Meire 2025-03-05 10:29:13 +01:00 committed by GitHub
parent c821f1c532
commit 8790a0df3d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 148 additions and 163 deletions

View file

@ -314,7 +314,7 @@ const onDragEnd = (el: HTMLElement) => {
@click="toggleNodeAndScrollTop(item.id)" @click="toggleNodeAndScrollTop(item.id)"
/> />
<VirtualSchemaItem <VirtualSchemaItem
v-else v-else-if="item.type === 'item'"
v-bind="item" v-bind="item"
:search="search" :search="search"
:draggable="mappingEnabled" :draggable="mappingEnabled"
@ -323,6 +323,10 @@ const onDragEnd = (el: HTMLElement) => {
@click="toggleLeaf(item.id)" @click="toggleLeaf(item.id)"
> >
</VirtualSchemaItem> </VirtualSchemaItem>
<N8nTooltip v-else-if="item.type === 'icon'" :content="item.tooltip" placement="top">
<N8nIcon :size="14" :icon="item.icon" class="icon" />
</N8nTooltip>
</DynamicScrollerItem> </DynamicScrollerItem>
</template> </template>
</DynamicScroller> </DynamicScroller>
@ -347,4 +351,11 @@ const onDragEnd = (el: HTMLElement) => {
text-align: center; text-align: center;
padding: var(--spacing-s) var(--spacing-s) var(--spacing-xl) var(--spacing-s); padding: var(--spacing-s) var(--spacing-s) var(--spacing-xl) var(--spacing-s);
} }
.icon {
display: inline-flex;
margin-left: var(--spacing-xl);
color: var(--color-text-light);
margin-bottom: var(--spacing-s);
}
</style> </style>

View file

@ -4,8 +4,7 @@ import NodeIcon from '@/components/NodeIcon.vue';
import { type INodeTypeDescription } from 'n8n-workflow'; import { type INodeTypeDescription } from 'n8n-workflow';
import { useI18n } from '@/composables/useI18n'; import { useI18n } from '@/composables/useI18n';
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
import { DATA_EDITING_DOCS_URL } from '@/constants'; import { SCHEMA_PREVIEW_DOCS_URL } from '@/constants';
import { N8nNotice } from '@n8n/design-system';
const props = defineProps<{ const props = defineProps<{
title: string; title: string;
@ -42,24 +41,28 @@ const emit = defineEmits<{
<span v-if="info" class="info">{{ info }}</span> <span v-if="info" class="info">{{ info }}</span>
</div> </div>
<FontAwesomeIcon v-if="isTrigger" class="trigger-icon" icon="bolt" size="xs" /> <FontAwesomeIcon v-if="isTrigger" class="trigger-icon" icon="bolt" size="xs" />
<div v-if="itemCount" class="item-count" data-test-id="run-data-schema-node-item-count"> <div v-if="itemCount" class="extra-info" data-test-id="run-data-schema-node-item-count">
{{ i18n.baseText('ndv.output.items', { interpolate: { count: itemCount } }) }} {{ i18n.baseText('ndv.output.items', { interpolate: { count: itemCount } }) }}
</div> </div>
<div v-else-if="preview" class="extra-info">
{{ i18n.baseText('dataMapping.schemaView.previewNode') }}
</div>
</div> </div>
<N8nNotice <div
v-if="preview && !collapsed" v-if="preview && !collapsed"
class="notice" class="notice"
theme="warning" theme="warning"
data-test-id="schema-preview-warning" data-test-id="schema-preview-warning"
@click.stop
> >
<i18n-t keypath="dataMapping.schemaView.preview"> <i18n-t keypath="dataMapping.schemaView.preview">
<template #link> <template #link>
<N8nLink :to="DATA_EDITING_DOCS_URL" size="small"> <N8nLink :to="SCHEMA_PREVIEW_DOCS_URL" size="small" bold>
{{ i18n.baseText('generic.learnMore') }} {{ i18n.baseText('generic.learnMore') }}
</N8nLink> </N8nLink>
</template> </template>
</i18n-t> </i18n-t>
</N8nNotice> </div>
</div> </div>
</template> </template>
@ -117,7 +120,7 @@ const emit = defineEmits<{
color: var(--color-primary); color: var(--color-primary);
} }
.item-count { .extra-info {
font-size: var(--font-size-2xs); font-size: var(--font-size-2xs);
color: var(--color-text-light); color: var(--color-text-light);
margin-left: auto; margin-left: auto;
@ -126,6 +129,9 @@ const emit = defineEmits<{
.notice { .notice {
margin-left: var(--spacing-2xl); margin-left: var(--spacing-2xl);
margin-top: var(--spacing-2xs); margin-top: var(--spacing-2xs);
margin-bottom: 0; padding-bottom: var(--spacing-2xs);
color: var(--color-text-base);
font-size: var(--font-size-2xs);
line-height: var(--font-line-height-loose);
} }
</style> </style>

View file

@ -43,7 +43,10 @@ const emit = defineEmits<{
:data-node-type="nodeType" :data-node-type="nodeType"
data-target="mappable" data-target="mappable"
class="pill" class="pill"
:class="{ 'pill--highlight': highlight, 'pill--preview': preview }" :class="{
'pill--highlight': highlight,
'pill--preview': preview,
}"
data-test-id="run-data-schema-node-name" data-test-id="run-data-schema-node-name"
> >
<FontAwesomeIcon class="type-icon" :icon size="sm" /> <FontAwesomeIcon class="type-icon" :icon size="sm" />
@ -77,6 +80,7 @@ const emit = defineEmits<{
justify-content: center; justify-content: center;
cursor: pointer; cursor: pointer;
font-size: var(--font-size-s); font-size: var(--font-size-s);
color: var(--color-text-light);
} }
.pill { .pill {
@ -98,16 +102,31 @@ const emit = defineEmits<{
} }
&.pill--preview { &.pill--preview {
border-style: dashed; /* Cannot use CSS variable inside data URL, so instead switching based on data-theme and media query */
border-width: 1.5px; --schema-preview-dashed-border: url("data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' viewBox='0 0 400 400' fill='none' rx='4' ry='4' stroke='%230000002A' stroke-width='2' stroke-dasharray='4%2c 4' stroke-dashoffset='0' stroke-linecap='square'/%3e%3c/svg%3e");
--schema-preview-dashed-border-dark: url("data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' viewBox='0 0 400 400' fill='none' rx='4' ry='4' stroke='%23FFFFFF2A' stroke-width='2' stroke-dasharray='4%2c 4' stroke-dashoffset='0' stroke-linecap='square'/%3e%3c/svg%3e");
color: var(--color-text-light);
background-color: var(--color-run-data-background);
border: none;
max-width: calc(100% - var(--spacing-l));
background-image: var(--schema-preview-dashed-border);
.title { .title {
color: var(--color-text-light); color: var(--color-text-light);
border-left: 1.5px dashed var(--color-foreground-light);
} }
} }
} }
@media (prefers-color-scheme: dark) {
body:not([data-theme]) .pill--preview {
background-image: var(--schema-preview-dashed-border-dark);
}
}
[data-theme='dark'] .pill--preview {
background-image: var(--schema-preview-dashed-border-dark);
}
.draggable .pill.pill--highlight { .draggable .pill.pill--highlight {
color: var(--color-primary); color: var(--color-primary);
border-color: var(--color-primary-tint-1); border-color: var(--color-primary-tint-1);

View file

@ -110,7 +110,12 @@ exports[`VirtualSchema.vue > renders preview schema when enabled and available 1
swapopacity="false" swapopacity="false"
symbol="false" symbol="false"
/> />
<!--v-if--> <div
class="extra-info"
data-v-882a318e=""
>
Preview
</div>
</div> </div>
<div <div
class="notice" class="notice"
@ -119,13 +124,12 @@ exports[`VirtualSchema.vue > renders preview schema when enabled and available 1
theme="warning" theme="warning"
> >
Usually outputs the following fields. Execute the node to see the actual ones.
This is a preview of the schema, execute the node to see the exact schema and data.
<a <a
class="n8n-link" class="n8n-link"
data-v-882a318e="" data-v-882a318e=""
href="https://docs.n8n.io/data/data-editing/" href="https://docs.n8n.io/data/schema-preview/"
target="_blank" target="_blank"
> >
@ -133,7 +137,7 @@ exports[`VirtualSchema.vue > renders preview schema when enabled and available 1
class="primary" class="primary"
> >
<span <span
class="n8n-text size-small regular" class="n8n-text size-small bold"
> >
@ -146,7 +150,6 @@ exports[`VirtualSchema.vue > renders preview schema when enabled and available 1
</a> </a>
</div> </div>
</div> </div>
@ -328,70 +331,38 @@ exports[`VirtualSchema.vue > renders preview schema when enabled and available 1
<div
class="schema-item draggable" <span
data-test-id="run-data-schema-item" class="n8n-text compact size-14 regular n8n-icon icon el-tooltip__trigger el-tooltip__trigger icon el-tooltip__trigger el-tooltip__trigger n8n-icon icon el-tooltip__trigger el-tooltip__trigger icon el-tooltip__trigger el-tooltip__trigger"
data-v-0f5e7239=""
data-v-d00cba9a="" data-v-d00cba9a=""
type="item"
> >
<div
class="toggle-container" <font-awesome-icon-stub
data-v-0f5e7239="" beat="false"
> beatfade="false"
<!--v-if--> border="false"
</div> bounce="false"
<div class="14"
class="pill pill--preview" fade="false"
data-name="..." fixedwidth="false"
data-nest-level="1" flash="false"
data-target="mappable" flip="false"
data-test-id="run-data-schema-node-name" icon="ellipsis-h"
data-v-0f5e7239="" inverse="false"
> listitem="false"
<font-awesome-icon-stub pulse="false"
beat="false" shake="false"
beatfade="false" spin="false"
border="false" spinpulse="false"
bounce="false" spinreverse="false"
class="type-icon" swapopacity="false"
data-v-0f5e7239="" symbol="false"
fade="false" />
fixedwidth="false"
flash="false" </span>
flip="false" <!--teleport start-->
icon="" <!--teleport end-->
inverse="false"
listitem="false"
pulse="false"
shake="false"
size="sm"
spin="false"
spinpulse="false"
spinreverse="false"
swapopacity="false"
symbol="false"
/>
<span
class="content title"
data-v-0f5e7239=""
>
<span>
<!--v-if-->
...
</span>
</span>
</div>
<span
class="content text"
data-test-id="run-data-schema-item-value"
data-v-0f5e7239=""
>
<span />
</span>
</div>
@ -464,7 +435,12 @@ exports[`VirtualSchema.vue > renders preview schema when enabled and available 1
<!--v-if--> <!--v-if-->
</div> </div>
<!--v-if--> <!--v-if-->
<!--v-if--> <div
class="extra-info"
data-v-882a318e=""
>
Preview
</div>
</div> </div>
<div <div
class="notice" class="notice"
@ -473,13 +449,12 @@ exports[`VirtualSchema.vue > renders preview schema when enabled and available 1
theme="warning" theme="warning"
> >
Usually outputs the following fields. Execute the node to see the actual ones.
This is a preview of the schema, execute the node to see the exact schema and data.
<a <a
class="n8n-link" class="n8n-link"
data-v-882a318e="" data-v-882a318e=""
href="https://docs.n8n.io/data/data-editing/" href="https://docs.n8n.io/data/schema-preview/"
target="_blank" target="_blank"
> >
@ -487,7 +462,7 @@ exports[`VirtualSchema.vue > renders preview schema when enabled and available 1
class="primary" class="primary"
> >
<span <span
class="n8n-text size-small regular" class="n8n-text size-small bold"
> >
@ -500,7 +475,6 @@ exports[`VirtualSchema.vue > renders preview schema when enabled and available 1
</a> </a>
</div> </div>
</div> </div>
@ -682,70 +656,38 @@ exports[`VirtualSchema.vue > renders preview schema when enabled and available 1
<div
class="schema-item draggable" <span
data-test-id="run-data-schema-item" class="n8n-text compact size-14 regular n8n-icon icon el-tooltip__trigger el-tooltip__trigger icon el-tooltip__trigger el-tooltip__trigger n8n-icon icon el-tooltip__trigger el-tooltip__trigger icon el-tooltip__trigger el-tooltip__trigger"
data-v-0f5e7239=""
data-v-d00cba9a="" data-v-d00cba9a=""
type="item"
> >
<div
class="toggle-container" <font-awesome-icon-stub
data-v-0f5e7239="" beat="false"
> beatfade="false"
<!--v-if--> border="false"
</div> bounce="false"
<div class="14"
class="pill pill--preview" fade="false"
data-name="..." fixedwidth="false"
data-nest-level="1" flash="false"
data-target="mappable" flip="false"
data-test-id="run-data-schema-node-name" icon="ellipsis-h"
data-v-0f5e7239="" inverse="false"
> listitem="false"
<font-awesome-icon-stub pulse="false"
beat="false" shake="false"
beatfade="false" spin="false"
border="false" spinpulse="false"
bounce="false" spinreverse="false"
class="type-icon" swapopacity="false"
data-v-0f5e7239="" symbol="false"
fade="false" />
fixedwidth="false"
flash="false" </span>
flip="false" <!--teleport start-->
icon="" <!--teleport end-->
inverse="false"
listitem="false"
pulse="false"
shake="false"
size="sm"
spin="false"
spinpulse="false"
spinreverse="false"
swapopacity="false"
symbol="false"
/>
<span
class="content title"
data-v-0f5e7239=""
>
<span>
<!--v-if-->
...
</span>
</span>
</div>
<span
class="content text"
data-test-id="run-data-schema-item-value"
data-v-0f5e7239=""
>
<span />
</span>
</div>
@ -821,7 +763,7 @@ exports[`VirtualSchema.vue > renders previous nodes schema for AI tools 1`] = `
</div> </div>
<!--v-if--> <!--v-if-->
<div <div
class="item-count" class="extra-info"
data-test-id="run-data-schema-node-item-count" data-test-id="run-data-schema-node-item-count"
data-v-882a318e="" data-v-882a318e=""
> >
@ -893,7 +835,7 @@ exports[`VirtualSchema.vue > renders schema for correct output branch 1`] = `
</div> </div>
<!--v-if--> <!--v-if-->
<div <div
class="item-count" class="extra-info"
data-test-id="run-data-schema-node-item-count" data-test-id="run-data-schema-node-item-count"
data-v-882a318e="" data-v-882a318e=""
> >
@ -1447,7 +1389,7 @@ exports[`VirtualSchema.vue > renders schema with spaces and dots 1`] = `
symbol="false" symbol="false"
/> />
<div <div
class="item-count" class="extra-info"
data-test-id="run-data-schema-node-item-count" data-test-id="run-data-schema-node-item-count"
data-v-882a318e="" data-v-882a318e=""
> >

View file

@ -261,7 +261,14 @@ export type RenderHeader = {
preview?: boolean; preview?: boolean;
}; };
type Renders = RenderHeader | RenderItem; export type RenderIcon = {
id: string;
type: 'icon';
icon: string;
tooltip: string;
};
type Renders = RenderHeader | RenderItem | RenderIcon;
const icons = { const icons = {
object: 'cube', object: 'cube',
@ -285,13 +292,11 @@ const emptyItem = (): RenderItem => ({
type: 'item', type: 'item',
}); });
const dummyItem = (): RenderItem => ({ const moreFieldsItem = (): RenderIcon => ({
id: `dummy-${window.crypto.randomUUID()}`, id: `moreFields-${window.crypto.randomUUID()}`,
icon: '', type: 'icon',
level: 1, icon: 'ellipsis-h',
title: '...', tooltip: useI18n().baseText('dataMapping.schemaView.previewExtraFields'),
type: 'item',
preview: true,
}); });
const isDataEmpty = (schema: Schema) => { const isDataEmpty = (schema: Schema) => {
@ -445,7 +450,7 @@ export const useFlattenSchema = () => {
acc.push(...flattenSchema(item)); acc.push(...flattenSchema(item));
if (item.preview) { if (item.preview) {
acc.push(dummyItem()); acc.push(moreFieldsItem());
} }
return acc; return acc;

View file

@ -92,6 +92,7 @@ export const BUILTIN_NODES_DOCS_URL = `https://${DOCS_DOMAIN}/integrations/built
export const BUILTIN_CREDENTIALS_DOCS_URL = `https://${DOCS_DOMAIN}/integrations/builtin/credentials/`; export const BUILTIN_CREDENTIALS_DOCS_URL = `https://${DOCS_DOMAIN}/integrations/builtin/credentials/`;
export const DATA_PINNING_DOCS_URL = `https://${DOCS_DOMAIN}/data/data-pinning/`; export const DATA_PINNING_DOCS_URL = `https://${DOCS_DOMAIN}/data/data-pinning/`;
export const DATA_EDITING_DOCS_URL = `https://${DOCS_DOMAIN}/data/data-editing/`; export const DATA_EDITING_DOCS_URL = `https://${DOCS_DOMAIN}/data/data-editing/`;
export const SCHEMA_PREVIEW_DOCS_URL = `https://${DOCS_DOMAIN}/data/schema-preview/`;
export const MFA_DOCS_URL = `https://${DOCS_DOMAIN}/user-management/two-factor-auth/`; export const MFA_DOCS_URL = `https://${DOCS_DOMAIN}/user-management/two-factor-auth/`;
export const NPM_COMMUNITY_NODE_SEARCH_API_URL = 'https://api.npms.io/v2/'; export const NPM_COMMUNITY_NODE_SEARCH_API_URL = 'https://api.npms.io/v2/';
export const NPM_PACKAGE_DOCS_BASE_URL = 'https://www.npmjs.com/package/'; export const NPM_PACKAGE_DOCS_BASE_URL = 'https://www.npmjs.com/package/';

View file

@ -661,8 +661,9 @@
"dataMapping.schemaView.emptyData": "No fields - item(s) exist, but they're empty", "dataMapping.schemaView.emptyData": "No fields - item(s) exist, but they're empty",
"dataMapping.schemaView.disabled": "This node is disabled and will just pass data through", "dataMapping.schemaView.disabled": "This node is disabled and will just pass data through",
"dataMapping.schemaView.noMatches": "No results for '{search}'", "dataMapping.schemaView.noMatches": "No results for '{search}'",
"dataMapping.schemaView.preview": "This is a preview of the schema, execute the node to see the exact schema and data. {link}", "dataMapping.schemaView.preview": "Usually outputs the following fields. Execute the node to see the actual ones. {link}",
"dataMapping.schemaView.previewNode": "(schema preview)", "dataMapping.schemaView.previewExtraFields": "There may be more fields. Execute the node to be sure.",
"dataMapping.schemaView.previewNode": "Preview",
"displayWithChange.cancelEdit": "Cancel Edit", "displayWithChange.cancelEdit": "Cancel Edit",
"displayWithChange.clickToChange": "Click to Change", "displayWithChange.clickToChange": "Click to Change",
"displayWithChange.setValue": "Set Value", "displayWithChange.setValue": "Set Value",