fix(editor): Fix schema view bugs (#14734)

Co-authored-by: Michael Kret <michael.k@radency.com>
This commit is contained in:
Elias Meire
2025-04-24 08:53:02 +02:00
committed by GitHub
parent 1b1d6043d6
commit 022f4755c2
14 changed files with 1040 additions and 781 deletions

View File

@@ -56,9 +56,6 @@ type Props = {
node?: INodeUi | null;
data?: IDataObject[];
mappingEnabled?: boolean;
runIndex?: number;
outputIndex?: number;
totalRuns?: number;
paneType: 'input' | 'output';
connectionType?: NodeConnectionType;
search?: string;
@@ -70,9 +67,6 @@ const props = withDefaults(defineProps<Props>(), {
distanceFromActive: 1,
node: null,
data: () => [],
runIndex: 0,
outputIndex: 0,
totalRuns: 1,
connectionType: NodeConnectionTypes.Main,
search: '',
mappingEnabled: false,
@@ -91,20 +85,24 @@ const posthogStore = usePostHog();
const { getSchemaForExecutionData, getSchemaForJsonSchema, getSchema, filterSchema } =
useDataSchema();
const { closedNodes, flattenSchema, flattenMultipleSchemas, toggleLeaf, toggleNode } =
useFlattenSchema();
const { getNodeInputData, getNodeTaskData } = useNodeHelpers();
const { closedNodes, flattenSchema, flattenMultipleSchemas, toggleNode } = useFlattenSchema();
const { getNodeInputData, getLastRunIndexWithData, hasNodeExecuted } = useNodeHelpers();
const emit = defineEmits<{
'clear:search': [];
}>();
const scroller = ref<RecycleScrollerInstance>();
const closedNodesBeforeSearch = ref(new Set<string>());
const canDraggableDrop = computed(() => ndvStore.canDraggableDrop);
const draggableStickyPosition = computed(() => ndvStore.draggableStickyPos);
const toggleNodeAndScrollTop = (id: string) => {
const toggleNodeExclusiveAndScrollTop = (id: string) => {
const isClosed = closedNodes.value.has(id);
if (isClosed) {
closedNodes.value = new Set(items.value.map((item) => item.id));
}
toggleNode(id);
scroller.value?.scrollToItem(0);
};
@@ -112,13 +110,23 @@ const toggleNodeAndScrollTop = (id: string) => {
const getNodeSchema = async (fullNode: INodeUi, connectedNode: IConnectedNode) => {
const pinData = workflowsStore.pinDataByNodeName(connectedNode.name);
const hasPinnedData = pinData ? pinData.length > 0 : false;
const isNodeExecuted = getNodeTaskData(fullNode, props.runIndex) !== null || hasPinnedData;
const isNodeExecuted = hasPinnedData || hasNodeExecuted(connectedNode.name);
const connectedOutputIndexes = connectedNode.indicies.length > 0 ? connectedNode.indicies : [0];
const nodeData = connectedOutputIndexes.map((outputIndex) =>
getNodeInputData(fullNode, props.runIndex, outputIndex, props.paneType, props.connectionType),
);
const hasBinary = nodeData.flat().some((data) => !isEmpty(data.binary));
const data = pinData ?? nodeData.map(executionDataToJson).flat();
const connectedOutputsWithData = connectedOutputIndexes
.map((outputIndex) => ({
outputIndex,
runIndex: getLastRunIndexWithData(fullNode.name, outputIndex, props.connectionType),
}))
.filter(({ runIndex }) => runIndex !== -1);
const nodeData = connectedOutputsWithData
.map(({ outputIndex, runIndex }) =>
getNodeInputData(fullNode, runIndex, outputIndex, props.paneType, props.connectionType),
)
.flat();
const hasBinary = nodeData.some((data) => !isEmpty(data.binary));
const data = pinData ?? executionDataToJson(nodeData);
const isDataEmpty = data.length === 0;
let schema = getSchemaForExecutionData(data);
let preview = false;
@@ -135,9 +143,11 @@ const getNodeSchema = async (fullNode: INodeUi, connectedNode: IConnectedNode) =
schema,
connectedOutputIndexes,
itemsCount: data.length,
runIndex: connectedOutputsWithData[0]?.runIndex ?? 0,
preview,
hasBinary,
isNodeExecuted,
isDataEmpty,
};
};
@@ -164,7 +174,7 @@ const contextSchema = computed(() => {
$workflow: pick(workflowsStore.workflow, ['id', 'name', 'active']),
};
return getSchema(schemaSource);
return filterSchema(getSchema(schemaSource), props.search);
});
const contextItems = computed(() => {
@@ -178,10 +188,13 @@ const contextItems = computed(() => {
if (closedNodes.value.has(header.id)) return [header];
const fields: Renders[] = flattenSchema({
schema: contextSchema.value,
depth: 1,
}).flatMap((renderItem) => {
const schema = contextSchema.value;
if (!schema) {
return [];
}
const fields: Renders[] = flattenSchema({ schema, depth: 1 }).flatMap((renderItem) => {
const isVars =
renderItem.type === 'item' && renderItem.depth === 1 && renderItem.title === '$vars';
@@ -252,8 +265,16 @@ const nodesSchemas = asyncComputed<SchemaNode[]>(async () => {
const nodeType = nodeTypesStore.getNodeType(fullNode.type, fullNode.typeVersion);
if (!nodeType) continue;
const { schema, connectedOutputIndexes, itemsCount, preview, hasBinary, isNodeExecuted } =
await getNodeSchema(fullNode, node);
const {
schema,
connectedOutputIndexes,
itemsCount,
preview,
hasBinary,
isNodeExecuted,
isDataEmpty,
runIndex,
} = await getNodeSchema(fullNode, node);
const filteredSchema = filterSchema(schema, search);
@@ -269,6 +290,8 @@ const nodesSchemas = asyncComputed<SchemaNode[]>(async () => {
preview,
hasBinary,
isNodeExecuted,
isDataEmpty,
runIndex,
});
}
@@ -319,25 +342,28 @@ const noSearchResults = computed(() => {
});
watch(
() => props.search,
(newSearch) => {
if (!newSearch) return;
closedNodes.value.clear();
() => Boolean(props.search),
(hasSearch) => {
if (hasSearch) {
closedNodesBeforeSearch.value = new Set(closedNodes.value);
closedNodes.value.clear();
} else if (closedNodes.value.size === 0) {
closedNodes.value = closedNodesBeforeSearch.value;
}
},
);
// Variables & context items should be collapsed by default
watch(
contextItems,
(currentContextItems) => {
currentContextItems
// Collapse all nodes except the first
const unwatchItems = watch(items, (newItems) => {
if (newItems.length < 2) return;
closedNodes.value = new Set(
newItems
.filter((item) => item.type === 'header')
.forEach((item) => {
closedNodes.value.add(item.id);
});
},
{ once: true, immediate: true },
);
.slice(1)
.map((item) => item.id),
);
unwatchItems();
});
const onDragStart = (el: HTMLElement, data?: string) => {
ndvStore.draggableStartDragging({
@@ -356,13 +382,14 @@ const onDragEnd = (el: HTMLElement) => {
const isPreview = parentNode?.preview ?? false;
const hasCredential = !isEmpty(parentNode?.node.credentials);
const runIndex = Number(el.dataset.runIndex);
const telemetryPayload = {
src_node_type: el.dataset.nodeType,
src_field_name: el.dataset.name ?? '',
src_nodes_back: el.dataset.depth,
src_run_index: props.runIndex,
src_runs_total: props.totalRuns,
src_run_index: runIndex,
src_runs_total: runIndex,
src_field_nest_level: el.dataset.level ?? 0,
src_view: isPreview ? 'schema_preview' : 'schema',
src_has_credential: hasCredential,
@@ -424,8 +451,8 @@ const onDragEnd = (el: HTMLElement) => {
v-if="item.type === 'header'"
v-bind="item"
:collapsed="closedNodes.has(item.id)"
@click:toggle="toggleLeaf(item.id)"
@click="toggleNodeAndScrollTop(item.id)"
@click:toggle="toggleNode(item.id)"
@click="toggleNodeExclusiveAndScrollTop(item.id)"
/>
<VirtualSchemaItem
v-else-if="item.type === 'item'"
@@ -434,7 +461,7 @@ const onDragEnd = (el: HTMLElement) => {
:draggable="mappingEnabled"
:collapsed="closedNodes.has(item.id)"
:highlight="ndvStore.highlightDraggables"
@click="toggleLeaf(item.id)"
@click="toggleNode(item.id)"
>
</VirtualSchemaItem>
@@ -461,13 +488,14 @@ const onDragEnd = (el: HTMLElement) => {
>
<template #link>
<NodeExecuteButton
:node-name="item.nodeName"
v-if="ndvStore.activeNodeName"
:node-name="ndvStore.activeNodeName"
:label="i18n.baseText('ndv.input.noOutputData.executePrevious')"
text
telemetry-source="inputs"
hide-icon
size="small"
class="execute-button"
:class="$style.executeButton"
/>
</template>
</i18n-t>
@@ -481,6 +509,12 @@ const onDragEnd = (el: HTMLElement) => {
</div>
</template>
<style lang="css" module>
.executeButton {
padding: 0;
}
</style>
<style lang="css" scoped>
.full-height {
height: 100%;
@@ -532,8 +566,4 @@ const onDragEnd = (el: HTMLElement) => {
padding-bottom: var(--spacing-xs);
margin-left: calc((var(--spacing-xl) * var(--schema-level)));
}
.execute-button {
padding: 0;
}
</style>