mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
feat(editor): Improve schema view empty state when node has binary data (#14044)
This commit is contained in:
@@ -1,33 +1,33 @@
|
||||
import { createComponentRenderer } from '@/__tests__/render';
|
||||
import VirtualSchema from '@/components/VirtualSchema.vue';
|
||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||
import { userEvent } from '@testing-library/user-event';
|
||||
import { cleanup, waitFor } from '@testing-library/vue';
|
||||
import { createPinia, setActivePinia } from 'pinia';
|
||||
import {
|
||||
createTestNode,
|
||||
defaultNodeDescriptions,
|
||||
mockNodeTypeDescription,
|
||||
} from '@/__tests__/mocks';
|
||||
import { IF_NODE_TYPE, SET_NODE_TYPE, MANUAL_TRIGGER_NODE_TYPE } from '@/constants';
|
||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
import { createComponentRenderer } from '@/__tests__/render';
|
||||
import VirtualSchema from '@/components/VirtualSchema.vue';
|
||||
import * as nodeHelpers from '@/composables/useNodeHelpers';
|
||||
import { useTelemetry } from '@/composables/useTelemetry';
|
||||
import { IF_NODE_TYPE, MANUAL_TRIGGER_NODE_TYPE, SET_NODE_TYPE } from '@/constants';
|
||||
import type { IWorkflowDb } from '@/Interface';
|
||||
import { useNDVStore } from '@/stores/ndv.store';
|
||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||
import { createTestingPinia } from '@pinia/testing';
|
||||
import { fireEvent } from '@testing-library/dom';
|
||||
import { userEvent } from '@testing-library/user-event';
|
||||
import { cleanup, waitFor } from '@testing-library/vue';
|
||||
import {
|
||||
createResultOk,
|
||||
NodeConnectionTypes,
|
||||
type IDataObject,
|
||||
type IBinaryData,
|
||||
type INodeExecutionData,
|
||||
} from 'n8n-workflow';
|
||||
import * as nodeHelpers from '@/composables/useNodeHelpers';
|
||||
import * as workflowHelpers from '@/composables/useWorkflowHelpers';
|
||||
import { useNDVStore } from '@/stores/ndv.store';
|
||||
import { fireEvent } from '@testing-library/dom';
|
||||
import { useTelemetry } from '@/composables/useTelemetry';
|
||||
import { useSchemaPreviewStore } from '../stores/schemaPreview.store';
|
||||
import { usePostHog } from '../stores/posthog.store';
|
||||
import { useSettingsStore } from '../stores/settings.store';
|
||||
import { setActivePinia } from 'pinia';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
import { defaultSettings } from '../__tests__/defaults';
|
||||
import { usePostHog } from '../stores/posthog.store';
|
||||
import { useSchemaPreviewStore } from '../stores/schemaPreview.store';
|
||||
import { useSettingsStore } from '../stores/settings.store';
|
||||
|
||||
const mockNode1 = createTestNode({
|
||||
name: 'Manual Trigger',
|
||||
@@ -65,6 +65,14 @@ const aiTool = createTestNode({
|
||||
disabled: false,
|
||||
});
|
||||
|
||||
const nodeWithCredential = createTestNode({
|
||||
name: 'Notion',
|
||||
type: 'n8n-nodes-base.notion',
|
||||
typeVersion: 1,
|
||||
credentials: { notionApi: { id: 'testId', name: 'testName' } },
|
||||
disabled: false,
|
||||
});
|
||||
|
||||
const unknownNodeType = createTestNode({
|
||||
name: 'Unknown Node Type',
|
||||
type: 'unknown',
|
||||
@@ -76,15 +84,23 @@ const defaultNodes = [
|
||||
];
|
||||
|
||||
async function setupStore() {
|
||||
const workflow = mock<IWorkflowDb>({
|
||||
const workflow = {
|
||||
id: '123',
|
||||
name: 'Test Workflow',
|
||||
connections: {},
|
||||
active: true,
|
||||
nodes: [mockNode1, mockNode2, disabledNode, ifNode, aiTool, unknownNodeType],
|
||||
});
|
||||
nodes: [
|
||||
mockNode1,
|
||||
mockNode2,
|
||||
disabledNode,
|
||||
ifNode,
|
||||
aiTool,
|
||||
unknownNodeType,
|
||||
nodeWithCredential,
|
||||
],
|
||||
};
|
||||
|
||||
const pinia = createPinia();
|
||||
const pinia = createTestingPinia({ stubActions: false });
|
||||
setActivePinia(pinia);
|
||||
|
||||
const workflowsStore = useWorkflowsStore();
|
||||
@@ -102,20 +118,24 @@ async function setupStore() {
|
||||
name: IF_NODE_TYPE,
|
||||
outputs: [NodeConnectionTypes.Main, NodeConnectionTypes.Main],
|
||||
}),
|
||||
mockNodeTypeDescription({
|
||||
name: 'n8n-nodes-base.notion',
|
||||
outputs: [NodeConnectionTypes.Main],
|
||||
}),
|
||||
]);
|
||||
workflowsStore.workflow = workflow;
|
||||
workflowsStore.workflow = workflow as IWorkflowDb;
|
||||
|
||||
return pinia;
|
||||
}
|
||||
|
||||
function mockNodeOutputData(nodeName: string, data: IDataObject[], outputIndex = 0) {
|
||||
function mockNodeOutputData(nodeName: string, data: INodeExecutionData[], outputIndex = 0) {
|
||||
const originalNodeHelpers = nodeHelpers.useNodeHelpers();
|
||||
vi.spyOn(nodeHelpers, 'useNodeHelpers').mockImplementation(() => {
|
||||
return {
|
||||
...originalNodeHelpers,
|
||||
getNodeInputData: vi.fn((node, _, output) => {
|
||||
if (node.name === nodeName && output === outputIndex) {
|
||||
return data.map((json) => ({ json }));
|
||||
return data;
|
||||
}
|
||||
return [];
|
||||
}),
|
||||
@@ -146,7 +166,7 @@ describe('VirtualSchema.vue', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
cleanup();
|
||||
vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValue(123);
|
||||
vi.resetAllMocks();
|
||||
vi.setSystemTime('2025-01-01');
|
||||
renderComponent = createComponentRenderer(VirtualSchema, {
|
||||
global: {
|
||||
@@ -183,6 +203,20 @@ describe('VirtualSchema.vue', () => {
|
||||
expect(getAllByText("No fields - item(s) exist, but they're empty").length).toBe(1);
|
||||
});
|
||||
|
||||
it('renders schema for empty data with binary', async () => {
|
||||
mockNodeOutputData(mockNode1.name, [{ json: {}, binary: { data: mock<IBinaryData>() } }]);
|
||||
|
||||
const { getByText } = renderComponent({
|
||||
props: { nodes: [{ name: mockNode1.name, indicies: [], depth: 1 }] },
|
||||
});
|
||||
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
getByText("Only binary data exists. View it using the 'Binary' tab"),
|
||||
).toBeInTheDocument(),
|
||||
);
|
||||
});
|
||||
|
||||
it('renders schema for data', async () => {
|
||||
useWorkflowsStore().pinData({
|
||||
node: mockNode1,
|
||||
@@ -307,10 +341,7 @@ describe('VirtualSchema.vue', () => {
|
||||
it('renders schema for correct output branch', async () => {
|
||||
mockNodeOutputData(
|
||||
'If',
|
||||
[
|
||||
{ id: 1, name: 'John' },
|
||||
{ id: 2, name: 'Jane' },
|
||||
],
|
||||
[{ json: { id: 1, name: 'John' } }, { json: { id: 2, name: 'Jane' } }],
|
||||
1,
|
||||
);
|
||||
const { getAllByTestId } = renderComponent({
|
||||
@@ -330,10 +361,7 @@ describe('VirtualSchema.vue', () => {
|
||||
it('renders previous nodes schema for AI tools', async () => {
|
||||
mockNodeOutputData(
|
||||
'If',
|
||||
[
|
||||
{ id: 1, name: 'John' },
|
||||
{ id: 2, name: 'Jane' },
|
||||
],
|
||||
[{ json: { id: 1, name: 'John' } }, { json: { id: 2, name: 'Jane' } }],
|
||||
0,
|
||||
);
|
||||
const { getAllByTestId } = renderComponent({
|
||||
@@ -481,14 +509,6 @@ describe('VirtualSchema.vue', () => {
|
||||
});
|
||||
|
||||
it('should track data pill drag and drop for schema preview', async () => {
|
||||
useWorkflowsStore().pinData({
|
||||
node: {
|
||||
...mockNode2,
|
||||
credentials: { myCredential: { id: 'myCredential', name: 'myCredential' } },
|
||||
},
|
||||
data: [],
|
||||
});
|
||||
|
||||
const telemetry = useTelemetry();
|
||||
const trackSpy = vi.spyOn(telemetry, 'track');
|
||||
const posthogStore = usePostHog();
|
||||
@@ -513,7 +533,7 @@ describe('VirtualSchema.vue', () => {
|
||||
|
||||
const { getAllByTestId } = renderComponent({
|
||||
props: {
|
||||
nodes: [{ name: mockNode2.name, indicies: [], depth: 1 }],
|
||||
nodes: [{ name: nodeWithCredential.name, indicies: [], depth: 1 }],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -639,4 +659,8 @@ describe('VirtualSchema.vue', () => {
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should do something', () => {
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -105,21 +105,11 @@ const toggleNodeAndScrollTop = (id: string) => {
|
||||
const getNodeSchema = async (fullNode: INodeUi, connectedNode: IConnectedNode) => {
|
||||
const pinData = workflowsStore.pinDataByNodeName(connectedNode.name);
|
||||
const connectedOutputIndexes = connectedNode.indicies.length > 0 ? connectedNode.indicies : [0];
|
||||
const data =
|
||||
pinData ??
|
||||
connectedOutputIndexes
|
||||
.map((outputIndex) =>
|
||||
executionDataToJson(
|
||||
getNodeInputData(
|
||||
fullNode,
|
||||
props.runIndex,
|
||||
outputIndex,
|
||||
props.paneType,
|
||||
props.connectionType,
|
||||
),
|
||||
),
|
||||
)
|
||||
.flat();
|
||||
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();
|
||||
|
||||
let schema = getSchemaForExecutionData(data);
|
||||
let preview = false;
|
||||
@@ -137,6 +127,7 @@ const getNodeSchema = async (fullNode: INodeUi, connectedNode: IConnectedNode) =
|
||||
connectedOutputIndexes,
|
||||
itemsCount: data.length,
|
||||
preview,
|
||||
hasBinary,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -251,7 +242,7 @@ const nodesSchemas = asyncComputed<SchemaNode[]>(async () => {
|
||||
const nodeType = nodeTypesStore.getNodeType(fullNode.type, fullNode.typeVersion);
|
||||
if (!nodeType) continue;
|
||||
|
||||
const { schema, connectedOutputIndexes, itemsCount, preview } = await getNodeSchema(
|
||||
const { schema, connectedOutputIndexes, itemsCount, preview, hasBinary } = await getNodeSchema(
|
||||
fullNode,
|
||||
node,
|
||||
);
|
||||
@@ -268,6 +259,7 @@ const nodesSchemas = asyncComputed<SchemaNode[]>(async () => {
|
||||
nodeType,
|
||||
schema: filteredSchema,
|
||||
preview,
|
||||
hasBinary,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user