mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
feat(editor): Add type information to autocomplete dropdown (#8843)
This commit is contained in:
parent
ff8dd4e604
commit
d7bfd45333
|
@ -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 }),
|
||||
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
|
@ -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),
|
||||
};
|
||||
|
||||
|
@ -388,7 +407,7 @@ const objectOptions = (input: AutocompleteInput<IDataObject>): Completion[] => {
|
|||
{
|
||||
doc: {
|
||||
name: key,
|
||||
returnType: typeof resolvedProp,
|
||||
returnType: getType(resolvedProp),
|
||||
description: i18n.proxyVars[infoKey],
|
||||
},
|
||||
},
|
||||
|
@ -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,
|
||||
|
|
|
@ -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] {
|
||||
|
|
Loading…
Reference in a new issue