twenty / packages /twenty-shared /src /workflow /validation /utils /get-variable-path-suggestions.util.ts
Jules
Initial clean CRM deployment with prebuilt assets
d9494a5
Raw
History Blame Contribute Delete
2.42 kB
import { isNonEmptyArray } from '@sniptt/guards';
import { isDefined, isPlainObject } from '@/utils';
import { getEditDistance } from '@/workflow/validation/utils/get-edit-distance.util';
import { isBaseOutputSchemaV2 } from '@/workflow/workflow-schema/guards/isBaseOutputSchemaV2';
import { collectOutputSchemaPaths } from '@/workflow/workflow-schema/utils/collect-output-schema-paths';
import { findOutputSchemaPathFailure } from '@/workflow/workflow-schema/utils/find-output-schema-path-failure';
import { collectOutputSchemaVariablePaths } from '@/workflow/workflow-schema/utils/resolve-variable-path-in-output-schema';
const MAX_SUGGESTIONS = 3;
const containsRecordOutputSchema = (value: unknown): boolean => {
if (!isPlainObject(value)) {
return false;
}
if (value['_outputSchemaType'] === 'RECORD') {
return true;
}
return Object.values(value).some(
(entry) =>
isPlainObject(entry) && containsRecordOutputSchema(entry['value']),
);
};
const rankClosestCandidates = (
target: string,
candidates: string[],
): string[] =>
candidates
.map((candidate) => ({
candidate,
distance: getEditDistance(target, candidate),
}))
.filter(
({ candidate, distance }) => distance <= Math.ceil(candidate.length / 2),
)
.sort((a, b) => a.distance - b.distance)
.slice(0, MAX_SUGGESTIONS)
.map(({ candidate }) => candidate);
export const getVariablePathSuggestions = ({
schema,
propertyPath,
referencedStepId,
}: {
schema: unknown;
propertyPath: string[];
referencedStepId: string;
}): string[] => {
if (!isBaseOutputSchemaV2(schema) || containsRecordOutputSchema(schema)) {
const allPaths = collectOutputSchemaVariablePaths(schema);
return rankClosestCandidates(propertyPath.join('.'), allPaths).map((path) =>
[referencedStepId, path].join('.'),
);
}
const failure = findOutputSchemaPathFailure({ schema, propertyPath });
if (!isDefined(failure)) {
return [];
}
const localMatches = rankClosestCandidates(
failure.failedSegment,
failure.availableKeys,
).map((key) => [referencedStepId, ...failure.validPrefix, key].join('.'));
if (isNonEmptyArray(localMatches)) {
return localMatches;
}
const allPaths = collectOutputSchemaPaths(schema);
return rankClosestCandidates(propertyPath.join('.'), allPaths).map((path) =>
[referencedStepId, path].join('.'),
);
};