Skip to content

Commit

Permalink
feat(editor): Add type information to autocomplete dropdown (#8843)
Browse files Browse the repository at this point in the history
  • Loading branch information
elsmr committed Mar 19, 2024
1 parent ff8dd4e commit d7bfd45
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,16 @@ describe('Top-level completions', () => {
expect(result).toHaveLength(dollarOptions().length);

expect(result?.[0]).toEqual(
expect.objectContaining({ label: '$json', section: RECOMMENDED_SECTION }),
expect.objectContaining({
label: '$json',
section: RECOMMENDED_SECTION,
}),
);
expect(result?.[4]).toEqual(
expect.objectContaining({ label: '$execution', section: METADATA_SECTION }),
expect.objectContaining({
label: '$execution',
section: METADATA_SECTION,
}),
);
expect(result?.[14]).toEqual(
expect.objectContaining({ label: '$max()', section: METHODS_SECTION }),
Expand Down Expand Up @@ -614,6 +620,63 @@ describe('Resolution-based completions', () => {
);
});
});

describe('type information', () => {
test('should display type information for: {{ $json.obj.| }}', () => {
vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValueOnce({
str: 'bar',
empty: null,
arr: [],
obj: {},
});

const result = completions('{{ $json.obj.| }}');
expect(result).toContainEqual(expect.objectContaining({ label: 'str', detail: 'string' }));
expect(result).toContainEqual(expect.objectContaining({ label: 'empty', detail: 'null' }));
expect(result).toContainEqual(expect.objectContaining({ label: 'arr', detail: 'array' }));
expect(result).toContainEqual(expect.objectContaining({ label: 'obj', detail: 'object' }));
});

test('should display type information for: {{ $input.item.json.| }}', () => {
vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValueOnce({
str: 'bar',
empty: null,
arr: [],
obj: {},
});

const result = completions('{{ $json.item.json.| }}');
expect(result).toContainEqual(expect.objectContaining({ label: 'str', detail: 'string' }));
expect(result).toContainEqual(expect.objectContaining({ label: 'empty', detail: 'null' }));
expect(result).toContainEqual(expect.objectContaining({ label: 'arr', detail: 'array' }));
expect(result).toContainEqual(expect.objectContaining({ label: 'obj', detail: 'object' }));
});

test('should display type information for: {{ $("My Node").item.json.| }}', () => {
vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValueOnce({
str: 'bar',
empty: null,
arr: [],
obj: {},
});

const result = completions('{{ $("My Node").item.json.| }}');
expect(result).toContainEqual(expect.objectContaining({ label: 'str', detail: 'string' }));
expect(result).toContainEqual(expect.objectContaining({ label: 'empty', detail: 'null' }));
expect(result).toContainEqual(expect.objectContaining({ label: 'arr', detail: 'array' }));
expect(result).toContainEqual(expect.objectContaining({ label: 'obj', detail: 'object' }));
});

test('should not display type information for other completions', () => {
vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValue({
str: 'bar',
});

expect(completions('{{ $execution.| }}')?.every((item) => !item.detail)).toBe(true);
expect(completions('{{ $input.params.| }}')?.every((item) => !item.detail)).toBe(true);
expect(completions('{{ $("My Node").| }}')?.every((item) => !item.detail)).toBe(true);
});
});
});

export function completions(docWithCursor: string, explicit = false) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,24 @@ export const extensions = (
return toOptions(fnToDoc, typeName, 'extension-function', includeHidden, transformLabel);
};

export const getType = (value: unknown): string => {
if (Array.isArray(value)) return 'array';
if (value === null) return 'null';
return (typeof value).toLocaleLowerCase();
};

export const isInputData = (base: string): boolean => {
return (
/^\$input\..*\.json]/.test(base) || /^\$json/.test(base) || /^\$\(.*\)\..*\.json/.test(base)
);
};

export const getDetail = (base: string, value: unknown): string | undefined => {
const type = getType(value);
if (!isInputData(base) || type === 'function') return undefined;
return type;
};

export const toOptions = (
fnToDoc: FnToDoc,
typeName: ExtensionTypeName,
Expand Down Expand Up @@ -377,6 +395,7 @@ const objectOptions = (input: AutocompleteInput<IDataObject>): Completion[] => {
label: isFunction ? key + '()' : key,
type: isFunction ? 'function' : 'keyword',
section: getObjectPropertySection({ name, key, isFunction }),
detail: getDetail(name, resolvedProp),
apply: applyCompletion(hasArgs, transformLabel),
};

Expand All @@ -388,7 +407,7 @@ const objectOptions = (input: AutocompleteInput<IDataObject>): Completion[] => {
{
doc: {
name: key,
returnType: typeof resolvedProp,
returnType: getType(resolvedProp),
description: i18n.proxyVars[infoKey],
},
},
Expand Down Expand Up @@ -651,7 +670,7 @@ export const secretOptions = (base: string) => {
return [];
}
return Object.entries(resolved).map(([secret, value]) =>
createCompletionOption('Object', secret, 'keyword', {
createCompletionOption('', secret, 'keyword', {
doc: {
name: secret,
returnType: typeof value,
Expand Down
15 changes: 13 additions & 2 deletions packages/editor-ui/src/styles/plugins/_codemirror.scss
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,26 @@
li[role='option'] {
color: var(--color-text-base);
display: flex;
font-size: var(--font-size-2xs);
line-height: var(--font-line-height-xloose);
justify-content: space-between;
align-items: center;
padding: var(--spacing-5xs) var(--spacing-2xs);
gap: var(--spacing-2xs);
scroll-padding: 40px;
scroll-margin: 40px;
}

li .cm-completionLabel {
line-height: var(--font-line-height-xloose);
font-size: var(--font-size-2xs);
overflow: hidden;
text-overflow: ellipsis;
}

li .cm-completionDetail {
color: var(--color-text-light);
font-size: var(--font-size-3xs);
margin-left: 0;
font-style: normal;
}

li[aria-selected] {
Expand Down

0 comments on commit d7bfd45

Please sign in to comment.