Handle relations for filters (#13654)
- Refacto variable dropdown so it can display both objects and fields - we do not filter on object name anymore to simplify the code - add relation handler in filters https://github.com/user-attachments/assets/e4f03f11-45cb-4d3f-b628-e996129dd996
This commit is contained in:
+190
-153
@@ -3,189 +3,226 @@ import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { filterOutputSchema } from '../filterOutputSchema';
|
||||
|
||||
describe('filterOutputSchema', () => {
|
||||
describe('edge cases', () => {
|
||||
it('should return the input schema when objectNameSingularToSelect is undefined', () => {
|
||||
const inputSchema: OutputSchema = {
|
||||
_outputSchemaType: 'RECORD',
|
||||
object: {
|
||||
nameSingular: 'person',
|
||||
fieldIdName: 'id',
|
||||
isLeaf: true,
|
||||
value: 'Fake value',
|
||||
objectMetadataId: '123',
|
||||
},
|
||||
fields: {},
|
||||
};
|
||||
const createRecordSchema = (
|
||||
nameSingular: string,
|
||||
fields = {},
|
||||
): OutputSchema => ({
|
||||
_outputSchemaType: 'RECORD',
|
||||
object: {
|
||||
nameSingular,
|
||||
fieldIdName: 'id',
|
||||
isLeaf: true,
|
||||
value: 'Fake value',
|
||||
objectMetadataId: '123',
|
||||
},
|
||||
fields,
|
||||
});
|
||||
|
||||
expect(filterOutputSchema(inputSchema, undefined)).toBe(inputSchema);
|
||||
const createBaseSchema = (fields = {}): OutputSchema => ({
|
||||
...fields,
|
||||
});
|
||||
|
||||
describe('shouldDisplayRecordFields only (true, false)', () => {
|
||||
describe('record schema', () => {
|
||||
it('should return the input schema unchanged', () => {
|
||||
const inputSchema = createRecordSchema('person', {
|
||||
name: { isLeaf: true, value: 'string' },
|
||||
id: { isLeaf: true, type: FieldMetadataType.UUID },
|
||||
});
|
||||
|
||||
expect(
|
||||
filterOutputSchema({
|
||||
shouldDisplayRecordFields: true,
|
||||
shouldDisplayRecordObjects: false,
|
||||
outputSchema: inputSchema,
|
||||
}),
|
||||
).toBe(inputSchema);
|
||||
});
|
||||
|
||||
it('should return undefined when input schema is undefined', () => {
|
||||
expect(
|
||||
filterOutputSchema({
|
||||
shouldDisplayRecordFields: true,
|
||||
shouldDisplayRecordObjects: false,
|
||||
outputSchema: undefined,
|
||||
}),
|
||||
).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
it('should return undefined when input schema is undefined', () => {
|
||||
expect(filterOutputSchema(undefined, 'person')).toBeUndefined();
|
||||
describe('base schema', () => {
|
||||
it('should return the input schema unchanged', () => {
|
||||
const inputSchema = createBaseSchema({
|
||||
field1: { isLeaf: true, value: 'string' },
|
||||
field2: { isLeaf: true, type: FieldMetadataType.NUMBER },
|
||||
});
|
||||
|
||||
expect(
|
||||
filterOutputSchema({
|
||||
shouldDisplayRecordFields: true,
|
||||
shouldDisplayRecordObjects: false,
|
||||
outputSchema: inputSchema,
|
||||
}),
|
||||
).toBe(inputSchema);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('record output schema', () => {
|
||||
const createRecordSchema = (
|
||||
nameSingular: string,
|
||||
fields = {},
|
||||
): OutputSchema => ({
|
||||
_outputSchemaType: 'RECORD',
|
||||
object: {
|
||||
nameSingular,
|
||||
fieldIdName: 'id',
|
||||
isLeaf: true,
|
||||
value: 'Fake value',
|
||||
objectMetadataId: '123',
|
||||
},
|
||||
fields,
|
||||
});
|
||||
|
||||
it('should keep a matching record schema', () => {
|
||||
const inputSchema = createRecordSchema('person');
|
||||
|
||||
expect(filterOutputSchema(inputSchema, 'person')).toEqual(inputSchema);
|
||||
});
|
||||
|
||||
it('should filter out a non-matching record schema with no valid fields', () => {
|
||||
const inputSchema = createRecordSchema('company');
|
||||
|
||||
expect(filterOutputSchema(inputSchema, 'person')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should keep valid nested records while filtering out invalid ones', () => {
|
||||
const inputSchema = createRecordSchema('company', {
|
||||
employee: {
|
||||
isLeaf: false,
|
||||
value: createRecordSchema('person', {
|
||||
manager: {
|
||||
isLeaf: false,
|
||||
value: createRecordSchema('person'),
|
||||
},
|
||||
}),
|
||||
},
|
||||
department: {
|
||||
isLeaf: false,
|
||||
value: createRecordSchema('department'),
|
||||
},
|
||||
});
|
||||
|
||||
const expectedSchema = {
|
||||
_outputSchemaType: 'RECORD',
|
||||
fields: {
|
||||
describe('shouldDisplayRecordObjects only (false, true)', () => {
|
||||
describe('record schema', () => {
|
||||
it('should keep record schema with object and filter compatible fields', () => {
|
||||
const inputSchema = createRecordSchema('person', {
|
||||
name: { isLeaf: true, value: 'string' },
|
||||
id: { isLeaf: true, type: FieldMetadataType.UUID },
|
||||
employee: {
|
||||
isLeaf: false,
|
||||
value: createRecordSchema('person', {
|
||||
manager: {
|
||||
isLeaf: false,
|
||||
value: createRecordSchema('person'),
|
||||
},
|
||||
}),
|
||||
value: createRecordSchema('employee'),
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
expect(filterOutputSchema(inputSchema, 'person')).toEqual(expectedSchema);
|
||||
});
|
||||
|
||||
it('should ignore leaf fields that are field metadata types', () => {
|
||||
const inputSchema = createRecordSchema('company', {
|
||||
name: { isLeaf: true, value: 'string' },
|
||||
id: { isLeaf: true, type: FieldMetadataType.UUID },
|
||||
employee: {
|
||||
isLeaf: false,
|
||||
value: createRecordSchema('person'),
|
||||
},
|
||||
});
|
||||
|
||||
const expectedSchema = {
|
||||
_outputSchemaType: 'RECORD',
|
||||
fields: {
|
||||
const expectedSchema = createRecordSchema('person', {
|
||||
name: { isLeaf: true, value: 'string' },
|
||||
employee: {
|
||||
isLeaf: false,
|
||||
value: createRecordSchema('employee'),
|
||||
},
|
||||
});
|
||||
|
||||
expect(
|
||||
filterOutputSchema({
|
||||
shouldDisplayRecordFields: false,
|
||||
shouldDisplayRecordObjects: true,
|
||||
outputSchema: inputSchema,
|
||||
}),
|
||||
).toEqual(expectedSchema);
|
||||
});
|
||||
|
||||
it('should return undefined for record schema without object and no valid fields', () => {
|
||||
const inputSchema = {
|
||||
_outputSchemaType: 'RECORD',
|
||||
fields: {
|
||||
invalidField: { isLeaf: true, type: FieldMetadataType.NUMBER },
|
||||
},
|
||||
} as any;
|
||||
|
||||
expect(
|
||||
filterOutputSchema({
|
||||
shouldDisplayRecordFields: false,
|
||||
shouldDisplayRecordObjects: true,
|
||||
outputSchema: inputSchema,
|
||||
}),
|
||||
).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('base schema', () => {
|
||||
it('should keep base schema with valid nested records', () => {
|
||||
const inputSchema = createBaseSchema({
|
||||
field1: {
|
||||
isLeaf: false,
|
||||
value: createRecordSchema('person'),
|
||||
},
|
||||
},
|
||||
};
|
||||
field2: { isLeaf: true, type: FieldMetadataType.NUMBER },
|
||||
});
|
||||
|
||||
expect(filterOutputSchema(inputSchema, 'person')).toEqual(expectedSchema);
|
||||
const expectedSchema = {
|
||||
field1: {
|
||||
isLeaf: false,
|
||||
value: createRecordSchema('person'),
|
||||
},
|
||||
};
|
||||
|
||||
expect(
|
||||
filterOutputSchema({
|
||||
shouldDisplayRecordFields: false,
|
||||
shouldDisplayRecordObjects: true,
|
||||
outputSchema: inputSchema,
|
||||
}),
|
||||
).toEqual(expectedSchema);
|
||||
});
|
||||
|
||||
it('should return undefined for base schema with no valid records', () => {
|
||||
const inputSchema = createBaseSchema({
|
||||
field1: { isLeaf: true, type: FieldMetadataType.NUMBER },
|
||||
field2: { isLeaf: true, type: FieldMetadataType.BOOLEAN },
|
||||
});
|
||||
|
||||
expect(
|
||||
filterOutputSchema({
|
||||
shouldDisplayRecordFields: false,
|
||||
shouldDisplayRecordObjects: true,
|
||||
outputSchema: inputSchema,
|
||||
}),
|
||||
).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('base output schema', () => {
|
||||
const createBaseSchema = (fields = {}): OutputSchema => ({
|
||||
...fields,
|
||||
describe('both shouldDisplayRecordFields and shouldDisplayRecordObjects (true, true)', () => {
|
||||
it('should return the input schema unchanged for record schema', () => {
|
||||
const inputSchema = createRecordSchema('person', {
|
||||
name: { isLeaf: true, value: 'string' },
|
||||
id: { isLeaf: true, type: FieldMetadataType.UUID },
|
||||
});
|
||||
|
||||
expect(
|
||||
filterOutputSchema({
|
||||
shouldDisplayRecordFields: true,
|
||||
shouldDisplayRecordObjects: true,
|
||||
outputSchema: inputSchema,
|
||||
}),
|
||||
).toBe(inputSchema);
|
||||
});
|
||||
|
||||
it('should filter out base schema with no valid records', () => {
|
||||
it('should return the input schema unchanged for base schema', () => {
|
||||
const inputSchema = createBaseSchema({
|
||||
field1: {
|
||||
isLeaf: true,
|
||||
type: FieldMetadataType.TEXT,
|
||||
value: 'string',
|
||||
},
|
||||
field1: { isLeaf: true, value: 'string' },
|
||||
field2: { isLeaf: true, type: FieldMetadataType.NUMBER },
|
||||
});
|
||||
|
||||
expect(filterOutputSchema(inputSchema, 'person')).toBeUndefined();
|
||||
expect(
|
||||
filterOutputSchema({
|
||||
shouldDisplayRecordFields: true,
|
||||
shouldDisplayRecordObjects: true,
|
||||
outputSchema: inputSchema,
|
||||
}),
|
||||
).toBe(inputSchema);
|
||||
});
|
||||
|
||||
it('should keep base schema with valid nested records', () => {
|
||||
const inputSchema = createBaseSchema({
|
||||
field1: {
|
||||
isLeaf: false,
|
||||
value: {
|
||||
_outputSchemaType: 'RECORD',
|
||||
object: { nameSingular: 'person' },
|
||||
fields: {},
|
||||
},
|
||||
},
|
||||
it('should return undefined when input schema is undefined', () => {
|
||||
expect(
|
||||
filterOutputSchema({
|
||||
shouldDisplayRecordFields: true,
|
||||
shouldDisplayRecordObjects: true,
|
||||
outputSchema: undefined,
|
||||
}),
|
||||
).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('both shouldDisplayRecordFields and shouldDisplayRecordObjects false (false, false)', () => {
|
||||
it('should return the input schema unchanged', () => {
|
||||
const inputSchema = createRecordSchema('person', {
|
||||
name: { isLeaf: true, value: 'string' },
|
||||
});
|
||||
|
||||
expect(filterOutputSchema(inputSchema, 'person')).toEqual({
|
||||
field1: {
|
||||
isLeaf: false,
|
||||
value: {
|
||||
_outputSchemaType: 'RECORD',
|
||||
object: { nameSingular: 'person' },
|
||||
fields: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(
|
||||
filterOutputSchema({
|
||||
shouldDisplayRecordFields: false,
|
||||
shouldDisplayRecordObjects: false,
|
||||
outputSchema: inputSchema,
|
||||
}),
|
||||
).toBe(inputSchema);
|
||||
});
|
||||
|
||||
it('should handle deeply nested valid records', () => {
|
||||
const inputSchema = createBaseSchema({
|
||||
level1: {
|
||||
isLeaf: false,
|
||||
value: createBaseSchema({
|
||||
level2: {
|
||||
isLeaf: false,
|
||||
value: {
|
||||
_outputSchemaType: 'RECORD',
|
||||
object: { nameSingular: 'person' },
|
||||
fields: {},
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
expect(filterOutputSchema(inputSchema, 'person')).toEqual({
|
||||
level1: {
|
||||
isLeaf: false,
|
||||
value: {
|
||||
level2: {
|
||||
isLeaf: false,
|
||||
value: {
|
||||
_outputSchemaType: 'RECORD',
|
||||
object: { nameSingular: 'person' },
|
||||
fields: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
it('should return undefined when input schema is undefined', () => {
|
||||
expect(
|
||||
filterOutputSchema({
|
||||
shouldDisplayRecordFields: false,
|
||||
shouldDisplayRecordObjects: false,
|
||||
outputSchema: undefined,
|
||||
}),
|
||||
).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import { getVariableTemplateFromPath } from '@/workflow/workflow-variables/utils/getVariableTemplateFromPath';
|
||||
|
||||
describe('getVariableTemplateFromPath', () => {
|
||||
it('should return stepId template when path is empty', () => {
|
||||
const result = getVariableTemplateFromPath({
|
||||
stepId: 'step-1',
|
||||
path: [],
|
||||
});
|
||||
|
||||
expect(result).toBe('{{step-1}}');
|
||||
});
|
||||
|
||||
it('should return stepId with path', () => {
|
||||
const result = getVariableTemplateFromPath({
|
||||
stepId: 'step-2',
|
||||
path: ['company', 'name'],
|
||||
});
|
||||
|
||||
expect(result).toBe('{{step-2.company.name}}');
|
||||
});
|
||||
});
|
||||
+71
-33
@@ -9,24 +9,31 @@ import { isLinkOutputSchema } from '@/workflow/workflow-variables/utils/isLinkOu
|
||||
import { isRecordOutputSchema } from '@/workflow/workflow-variables/utils/isRecordOutputSchema';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
const isValidRecordOutputSchema = (
|
||||
outputSchema: RecordOutputSchema,
|
||||
objectNameSingularToSelect?: string,
|
||||
): boolean => {
|
||||
if (isDefined(objectNameSingularToSelect)) {
|
||||
return (
|
||||
isDefined(outputSchema.object) &&
|
||||
outputSchema.object.nameSingular === objectNameSingularToSelect
|
||||
);
|
||||
const isValidRecordOutputSchema = ({
|
||||
shouldDisplayRecordFields,
|
||||
shouldDisplayRecordObjects,
|
||||
outputSchema,
|
||||
}: {
|
||||
shouldDisplayRecordFields: boolean;
|
||||
shouldDisplayRecordObjects: boolean;
|
||||
outputSchema: RecordOutputSchema;
|
||||
}): boolean => {
|
||||
if (shouldDisplayRecordObjects && !shouldDisplayRecordFields) {
|
||||
return isDefined(outputSchema.object);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const filterRecordOutputSchema = (
|
||||
outputSchema: RecordOutputSchema,
|
||||
objectNameSingularToSelect: string,
|
||||
): RecordOutputSchema | undefined => {
|
||||
const filterRecordOutputSchema = ({
|
||||
outputSchema,
|
||||
shouldDisplayRecordFields,
|
||||
shouldDisplayRecordObjects,
|
||||
}: {
|
||||
outputSchema: RecordOutputSchema;
|
||||
shouldDisplayRecordFields: boolean;
|
||||
shouldDisplayRecordObjects: boolean;
|
||||
}): RecordOutputSchema | undefined => {
|
||||
const filteredFields: BaseOutputSchema = {};
|
||||
let hasValidFields = false;
|
||||
|
||||
@@ -41,10 +48,12 @@ const filterRecordOutputSchema = (
|
||||
continue;
|
||||
}
|
||||
|
||||
const validSubSchema = filterOutputSchema(
|
||||
field.value,
|
||||
objectNameSingularToSelect,
|
||||
);
|
||||
const validSubSchema = filterOutputSchema({
|
||||
outputSchema: field.value,
|
||||
shouldDisplayRecordFields,
|
||||
shouldDisplayRecordObjects,
|
||||
});
|
||||
|
||||
if (isDefined(validSubSchema)) {
|
||||
filteredFields[key] = {
|
||||
...field,
|
||||
@@ -54,7 +63,13 @@ const filterRecordOutputSchema = (
|
||||
}
|
||||
}
|
||||
|
||||
if (isValidRecordOutputSchema(outputSchema, objectNameSingularToSelect)) {
|
||||
if (
|
||||
isValidRecordOutputSchema({
|
||||
shouldDisplayRecordFields,
|
||||
shouldDisplayRecordObjects,
|
||||
outputSchema,
|
||||
})
|
||||
) {
|
||||
return {
|
||||
...outputSchema,
|
||||
fields: filteredFields,
|
||||
@@ -69,10 +84,15 @@ const filterRecordOutputSchema = (
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const filterBaseOutputSchema = (
|
||||
outputSchema: BaseOutputSchema,
|
||||
objectNameSingularToSelect: string,
|
||||
): BaseOutputSchema | undefined => {
|
||||
const filterBaseOutputSchema = ({
|
||||
outputSchema,
|
||||
shouldDisplayRecordFields,
|
||||
shouldDisplayRecordObjects,
|
||||
}: {
|
||||
outputSchema: BaseOutputSchema;
|
||||
shouldDisplayRecordFields: boolean;
|
||||
shouldDisplayRecordObjects: boolean;
|
||||
}): BaseOutputSchema | undefined => {
|
||||
const filteredSchema: BaseOutputSchema = {};
|
||||
let hasValidFields = false;
|
||||
|
||||
@@ -87,10 +107,11 @@ const filterBaseOutputSchema = (
|
||||
continue;
|
||||
}
|
||||
|
||||
const validSubSchema = filterOutputSchema(
|
||||
field.value,
|
||||
objectNameSingularToSelect,
|
||||
);
|
||||
const validSubSchema = filterOutputSchema({
|
||||
shouldDisplayRecordFields,
|
||||
shouldDisplayRecordObjects,
|
||||
outputSchema: field.value,
|
||||
});
|
||||
if (isDefined(validSubSchema)) {
|
||||
filteredSchema[key] = {
|
||||
...field,
|
||||
@@ -107,20 +128,37 @@ const filterBaseOutputSchema = (
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const filterOutputSchema = (
|
||||
outputSchema?: OutputSchema,
|
||||
objectNameSingularToSelect?: string,
|
||||
): OutputSchema | undefined => {
|
||||
if (!objectNameSingularToSelect || !outputSchema) {
|
||||
export const filterOutputSchema = ({
|
||||
shouldDisplayRecordFields,
|
||||
shouldDisplayRecordObjects,
|
||||
outputSchema,
|
||||
}: {
|
||||
shouldDisplayRecordFields: boolean;
|
||||
shouldDisplayRecordObjects: boolean;
|
||||
outputSchema?: OutputSchema;
|
||||
}): OutputSchema | undefined => {
|
||||
if (
|
||||
!shouldDisplayRecordObjects ||
|
||||
shouldDisplayRecordFields ||
|
||||
!outputSchema
|
||||
) {
|
||||
return outputSchema;
|
||||
}
|
||||
|
||||
if (isLinkOutputSchema(outputSchema)) {
|
||||
return outputSchema;
|
||||
} else if (isRecordOutputSchema(outputSchema)) {
|
||||
return filterRecordOutputSchema(outputSchema, objectNameSingularToSelect);
|
||||
return filterRecordOutputSchema({
|
||||
outputSchema,
|
||||
shouldDisplayRecordFields,
|
||||
shouldDisplayRecordObjects,
|
||||
});
|
||||
} else if (isBaseOutputSchema(outputSchema)) {
|
||||
return filterBaseOutputSchema(outputSchema, objectNameSingularToSelect);
|
||||
return filterBaseOutputSchema({
|
||||
outputSchema,
|
||||
shouldDisplayRecordFields,
|
||||
shouldDisplayRecordObjects,
|
||||
});
|
||||
}
|
||||
|
||||
return undefined;
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
export const getVariableTemplateFromPath = ({
|
||||
stepId,
|
||||
path,
|
||||
}: {
|
||||
stepId: string;
|
||||
path: string[];
|
||||
}) => {
|
||||
if (path.length === 0) {
|
||||
return `{{${stepId}}}`;
|
||||
}
|
||||
|
||||
return `{{${stepId}.${path.join('.')}}}`;
|
||||
};
|
||||
Reference in New Issue
Block a user