File size: 2,415 Bytes
d9494a5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
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('.'),
  );
};