Skip to content

Commit

Permalink
Change schema preview frontend to accept JSON schema
Browse files Browse the repository at this point in the history
  • Loading branch information
elsmr committed Jan 30, 2025
1 parent 0158be4 commit 2b85854
Show file tree
Hide file tree
Showing 9 changed files with 233 additions and 119 deletions.
1 change: 1 addition & 0 deletions packages/editor-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@
"@types/dateformat": "^3.0.0",
"@types/file-saver": "^2.0.1",
"@types/humanize-duration": "^3.27.1",
"@types/json-schema": "^7.0.15",
"@types/jsonpath": "^0.2.0",
"@types/lodash-es": "^4.17.6",
"@types/luxon": "^3.2.0",
Expand Down
4 changes: 2 additions & 2 deletions packages/editor-ui/src/api/schemaPreview.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { request } from '@/utils/apiUtils';
import type { Schema } from '@/Interface';
import type { JSONSchema7 } from 'json-schema';

export type GetSchemaPreviewOptions = {
nodeName: string;
Expand All @@ -15,7 +15,7 @@ const padVersion = (version: number) => {
export const getSchemaPreview = async (
baseUrl: string,
options: GetSchemaPreviewOptions,
): Promise<Schema> => {
): Promise<JSONSchema7> => {
const { nodeName, version, resource, operation } = options;
const versionString = padVersion(version);
const path = ['schemas', nodeName, versionString, resource, operation].filter(Boolean).join('/');
Expand Down
6 changes: 3 additions & 3 deletions packages/editor-ui/src/components/VirtualSchema.vue
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ const nodeTypesStore = useNodeTypesStore();
const workflowsStore = useWorkflowsStore();
const schemaPreviewStore = useSchemaPreviewStore();
const posthogStore = usePostHog();
const { getSchemaForExecutionData, filterSchema } = useDataSchema();
const { getSchemaForExecutionData, getSchemaForJsonSchema, filterSchema } = useDataSchema();
const { closedNodes, flattenSchema, flattenMultipleSchemas, toggleLeaf, toggleNode } =
useFlattenSchema();
const { getNodeInputData } = useNodeHelpers();
Expand Down Expand Up @@ -115,7 +115,7 @@ const getNodeSchema = async (fullNode: INodeUi, connectedNode: IConnectedNode) =
if (data.length === 0 && isSchemaPreviewEnabled.value) {
const previewSchema = await getSchemaPreview(fullNode);
if (previewSchema.ok) {
schema = previewSchema.result;
schema = getSchemaForJsonSchema(previewSchema.result);
preview = true;
}
}
Expand All @@ -136,7 +136,7 @@ const nodeSchema = asyncComputed(async () => {
if (props.data.length === 0 && isSchemaPreviewEnabled.value) {
const previewSchema = await getSchemaPreview(props.node);
if (previewSchema.ok) {
return filterSchema(previewSchema.result, props.search);
return filterSchema(getSchemaForJsonSchema(previewSchema.result), props.search);
}
}
Expand Down
129 changes: 129 additions & 0 deletions packages/editor-ui/src/composables/useDataSchema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ import {
type ITaskDataConnections,
} from 'n8n-workflow';
import { useWorkflowsStore } from '@/stores/workflows.store';
import type { JSONSchema7 } from 'json-schema';

vi.mock('@/stores/workflows.store');

describe('useDataSchema', () => {
const getSchema = useDataSchema().getSchema;

describe('getSchema', () => {
test.each([
[, { type: 'undefined', value: 'undefined', path: '' }],
Expand Down Expand Up @@ -534,6 +536,7 @@ describe('useDataSchema', () => {
expect(filterSchema(flatSchema, '')).toEqual(flatSchema);
});
});

describe('getNodeInputData', () => {
const getNodeInputData = useDataSchema().getNodeInputData;

Expand Down Expand Up @@ -648,6 +651,132 @@ describe('useDataSchema', () => {
},
);
});

describe('getSchemaForJsonSchema', () => {
const getSchemaForJsonSchema = useDataSchema().getSchemaForJsonSchema;

it('should convert JSON schema to Schema type', () => {
const jsonSchema: JSONSchema7 = {
type: 'object',
properties: {
id: {
type: 'string',
},
email: {
type: 'string',
},
address: {
type: 'object',
properties: {
line1: {
type: 'string',
},
country: {
type: 'string',
},
},
},
tags: {
type: 'array',
items: { type: 'string' },
},
workspaces: {
type: 'array',
items: {
type: 'object',
properties: {
id: {
type: 'string',
},
name: {
type: 'string',
},
},
required: ['gid', 'name', 'resource_type'],
},
},
},
required: ['gid', 'email', 'name', 'photo', 'resource_type', 'workspaces'],
};

expect(getSchemaForJsonSchema(jsonSchema)).toEqual({
path: '',
type: 'object',
value: [
{
key: 'id',
path: '.id',
type: 'string',
value: '',
},
{
key: 'email',
path: '.email',
type: 'string',
value: '',
},
{
key: 'address',
path: '.address',
type: 'object',
value: [
{
key: 'line1',
path: '.address.line1',
type: 'string',
value: '',
},
{
key: 'country',
path: '.address.country',
type: 'string',
value: '',
},
],
},
{
key: 'tags',
path: '.tags',
type: 'array',
value: [
{
key: '0',
path: '.tags[0]',
type: 'string',
value: '',
},
],
},
{
key: 'workspaces',
path: '.workspaces',
type: 'array',
value: [
{
key: '0',
path: '.workspaces[0]',
type: 'object',
value: [
{
key: 'id',
path: '.workspaces[0].id',
type: 'string',
value: '',
},
{
key: 'name',
path: '.workspaces[0].name',
type: 'string',
value: '',
},
],
},
],
},
],
});
});
});
});

describe('useFlattenSchema', () => {
Expand Down
57 changes: 56 additions & 1 deletion packages/editor-ui/src/composables/useDataSchema.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ref } from 'vue';
import type { Optional, Primitives, Schema, INodeUi } from '@/Interface';
import type { Optional, Primitives, Schema, INodeUi, SchemaType } from '@/Interface';
import {
type ITaskDataConnections,
type IDataObject,
Expand All @@ -13,6 +13,8 @@ import { isObj } from '@/utils/typeGuards';
import { useWorkflowsStore } from '@/stores/workflows.store';
import { isPresent, shorten } from '@/utils/typesUtils';
import { useI18n } from '@/composables/useI18n';
import type { JSONSchema7, JSONSchema7Definition, JSONSchema7TypeName } from 'json-schema';
import { isObject } from '@/utils/objectUtils';

export function useDataSchema() {
function getSchema(
Expand Down Expand Up @@ -67,6 +69,58 @@ export function useDataSchema() {
return getSchema(merge({}, head, ...tail, head), undefined, excludeValues);
}

function getSchemaForJsonSchema(schema: JSONSchema7 | JSONSchema7Definition, path = ''): Schema {
if (typeof schema !== 'object') {
return {
type: 'null',
path,
value: 'null',
};
}
if (schema.type === 'array') {
return {
type: 'array',
value: isObject(schema.items)
? [{ ...getSchemaForJsonSchema(schema.items, `${path}[0]`), key: '0' }]
: [],
path,
};
}

if (schema.type === 'object') {
const properties = schema.properties ?? {};
const value = Object.entries(properties).map(([key, propSchema]) => {
const newPath = path ? `${path}.${key}` : `.${key}`;
const transformed = getSchemaForJsonSchema(propSchema, newPath);
return { ...transformed, key };
});

return {
type: 'object',
value,
path,
};
}

const type = Array.isArray(schema.type) ? schema.type[0] : schema.type;
return {
type: JsonSchemaTypeToSchemaType(type),
value: '',
path,
};
}

function JsonSchemaTypeToSchemaType(type: JSONSchema7TypeName | undefined): SchemaType {
switch (type) {
case undefined:
return 'undefined';
case 'integer':
return 'number';
default:
return type;
}
}

// Returns the data of the main input
function getMainInputData(
connectionsData: ITaskDataConnections,
Expand Down Expand Up @@ -164,6 +218,7 @@ export function useDataSchema() {
return {
getSchema,
getSchemaForExecutionData,
getSchemaForJsonSchema,
getNodeInputData,
getInputDataWithPinned,
filterSchema,
Expand Down
6 changes: 3 additions & 3 deletions packages/editor-ui/src/stores/schemaPreview.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import { createResultError, createResultOk, type Result } from 'n8n-workflow';
import { defineStore } from 'pinia';
import { reactive } from 'vue';
import { useRootStore } from './root.store';
import type { Schema } from '@/Interface';
import type { JSONSchema7 } from 'json-schema';

export const useSchemaPreviewStore = defineStore('schemaPreview', () => {
// Type cast to avoid 'Type instantiation is excessively deep and possibly infinite'
const schemaPreviews = reactive<Map<string, Schema>>(new Map()) as Map<string, Schema>;
const schemaPreviews = reactive<Map<string, JSONSchema7>>(new Map()) as Map<string, JSONSchema7>;

const rootStore = useRootStore();

Expand All @@ -22,7 +22,7 @@ export const useSchemaPreviewStore = defineStore('schemaPreview', () => {

async function getSchemaPreview(
options: schemaPreviewApi.GetSchemaPreviewOptions,
): Promise<Result<Schema, Error>> {
): Promise<Result<JSONSchema7, Error>> {
const key = getSchemaPreviewKey(options);
const cached = schemaPreviews.get(key);
if (cached) return createResultOk(cached);
Expand Down
Loading

0 comments on commit 2b85854

Please sign in to comment.