Infer record pickers for record-typed logic function workflow inputs (#21494)
## Context Logic functions can declare workflow inputs typed as records or arrays of records (e.g. the People Data Labs enrichment functions), but the workflow builder rendered those as a plain text input with a variable picker, which is not usable. ## What this does - Adds an `objectUniversalIdentifier` link on input schema properties, so a record-typed input is tied to a workspace object. - The SDK build infers it from a `TwentyRecord<'objectUniversalIdentifier'>` marker type in the handler signature, reading the object's universal identifier straight from the source; explicit input schemas can still set the field directly. - The workflow builder renders these inputs as a single record picker or a record multi-select with the variable picker on the right. Selected records are stored as record ids; `TwentyRecord<UID>` is a branded `string`, so the handler signature reflects that it receives ids (a bound variable resolves to whatever the referenced step produced). - The multi-select collapses overflowing chips into a `+N` badge (reusing `ExpandableList`) and its variable picker offers both record objects and fields. - Updates the People Data Labs enrichment inputs as the reference implementation. <img width="802" height="824" alt="CleanShot 2026-06-12 at 16 54 10@2x" src="https://github.com/user-attachments/assets/a0896d74-0aab-49bd-a173-14c578a2e533" /> <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21494?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
import { isNonEmptyString, isObject } from '@sniptt/guards';
|
||||
|
||||
import { type InputJsonSchema } from '@/logic-function/input-json-schema.type';
|
||||
import { isRecordObjectSchema } from '@/logic-function/is-record-object-schema';
|
||||
import { isDefined } from '@/utils/validation/isDefined';
|
||||
|
||||
type ResolveObjectLabel = (
|
||||
objectUniversalIdentifier: string,
|
||||
) => string | undefined;
|
||||
|
||||
const buildRecordIdDescription = (
|
||||
objectUniversalIdentifier: string | undefined,
|
||||
resolveObjectLabel?: ResolveObjectLabel,
|
||||
): string => {
|
||||
const objectLabel = isNonEmptyString(objectUniversalIdentifier)
|
||||
? resolveObjectLabel?.(objectUniversalIdentifier)
|
||||
: undefined;
|
||||
|
||||
return `Id of the ${isNonEmptyString(objectLabel) ? objectLabel : 'linked'} record`;
|
||||
};
|
||||
|
||||
export const buildToolInputJsonSchema = (
|
||||
jsonSchema: InputJsonSchema,
|
||||
resolveObjectLabel?: ResolveObjectLabel,
|
||||
): InputJsonSchema => {
|
||||
if (isRecordObjectSchema(jsonSchema)) {
|
||||
return {
|
||||
type: 'string',
|
||||
description: buildRecordIdDescription(
|
||||
jsonSchema.objectUniversalIdentifier,
|
||||
resolveObjectLabel,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
if (jsonSchema.type === 'records') {
|
||||
return {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
description: buildRecordIdDescription(
|
||||
jsonSchema.objectUniversalIdentifier,
|
||||
resolveObjectLabel,
|
||||
),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const {
|
||||
objectUniversalIdentifier: _objectUniversalIdentifier,
|
||||
multiline: _multiline,
|
||||
label: _label,
|
||||
items,
|
||||
properties,
|
||||
additionalProperties,
|
||||
...standardKeywords
|
||||
} = jsonSchema;
|
||||
|
||||
const toolSchema: InputJsonSchema = { ...standardKeywords };
|
||||
|
||||
if (isDefined(items)) {
|
||||
toolSchema.items = buildToolInputJsonSchema(items, resolveObjectLabel);
|
||||
}
|
||||
|
||||
if (isDefined(properties)) {
|
||||
toolSchema.properties = Object.fromEntries(
|
||||
Object.entries(properties).map(([key, value]) => [
|
||||
key,
|
||||
buildToolInputJsonSchema(value, resolveObjectLabel),
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
if (isDefined(additionalProperties)) {
|
||||
toolSchema.additionalProperties = isObject(additionalProperties)
|
||||
? buildToolInputJsonSchema(additionalProperties, resolveObjectLabel)
|
||||
: additionalProperties;
|
||||
}
|
||||
|
||||
return toolSchema;
|
||||
};
|
||||
Reference in New Issue
Block a user