Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(editor): Show expression infobox on hover and cursor position #9507

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
c381d89
Restyle info box + add args, examples
elsmr Apr 2, 2024
f6a6792
Add examples for String.includes
elsmr Apr 2, 2024
ac1e7b6
Disable closeOnBlur
elsmr Apr 2, 2024
e88f4b4
Infobox styling.
gandreini Apr 9, 2024
86575bb
Merge branch 'master' into node-1280-autocomplete-info-box-improve-st…
elsmr Apr 9, 2024
4bde4c8
Improve infobox DOM structure
elsmr Apr 9, 2024
56ae4e5
Add string extension docs
elsmr Apr 10, 2024
9e92b66
Extension methods tweaks
elsmr Apr 10, 2024
f9920d6
Infobox styling.
gandreini Apr 19, 2024
bea19e1
Merge branch 'master' into node-1280-autocomplete-info-box-improve-st…
elsmr Apr 19, 2024
a372082
Add all String examples/descriptions
elsmr Apr 19, 2024
e38663a
Re-enable closeOnBlur
elsmr Apr 19, 2024
7a64761
Add test for microseconds toDateTime
elsmr Apr 19, 2024
20332e0
Fix tests
elsmr Apr 22, 2024
181f2e3
Fix dollar completion info box layout
elsmr Apr 23, 2024
0704b0e
Styling improvements
elsmr Apr 23, 2024
c40ed94
Remove section from toDateTime
elsmr Apr 23, 2024
5ab4423
Review fixes
elsmr Apr 23, 2024
a8173e0
Replace unneeded innerHTML with textContent
elsmr Apr 25, 2024
3f1a834
Remove type annotations to improve typing
elsmr Apr 25, 2024
6efb364
Fix review remarks
elsmr Apr 25, 2024
1cc70ca
Merge branch 'master' into node-1280-autocomplete-info-box-improve-st…
elsmr Apr 26, 2024
93e98ed
Hide autocomplete in e2e test
elsmr Apr 26, 2024
9c80c4f
Fix code node autocomplete styling
elsmr Apr 26, 2024
baf0b17
Minor style tweaks.
gandreini Apr 29, 2024
d966bcd
Fix review feedback
elsmr May 2, 2024
fa58b9a
Infobox CSS improvements.
gandreini May 2, 2024
70e48f6
Merge branch 'node-1280-autocomplete-info-box-improve-structure-and-a…
gandreini May 2, 2024
04e72d5
Render examples section with 1 border
elsmr May 2, 2024
1329425
Hide docURL
elsmr May 3, 2024
d507efc
Merge branch 'master' into node-1280-autocomplete-info-box-improve-st…
elsmr May 3, 2024
e3d550a
Infobox color for dark mode.
gandreini May 6, 2024
e8fba7f
Review fixes
elsmr May 7, 2024
47bfb55
Fix variadic methods, improve links style, bring back docURL
elsmr May 8, 2024
9a63353
Restore object sections
elsmr May 8, 2024
3daee6a
Infobox tooltip experiment
elsmr May 8, 2024
9bb3c30
Tweaks
elsmr May 9, 2024
edd176d
Fix info box background color
elsmr May 9, 2024
d740a3b
Tweak info box code
elsmr May 10, 2024
b16c9f9
Add tooltip on hover
elsmr May 14, 2024
8ec396b
Hide title on expressions
elsmr May 14, 2024
9929c1b
Autoclose hover tooltips
elsmr May 15, 2024
94f3c98
Autoclose hover tooltips with options
elsmr May 15, 2024
3d57ab1
Merge branch 'master' into expr-infobox-popup-experiment
elsmr May 23, 2024
1a62e25
Improve infobox styling, return type
elsmr May 23, 2024
be6f1ef
Finish hover/cursor tooltip functionality
elsmr May 23, 2024
9c922d0
Merge branch 'master' into expr-infobox-popup-experiment
elsmr May 24, 2024
b2d14f0
Add tests for info box tooltips
elsmr May 24, 2024
bb77b71
Remove unnecessary mocks
elsmr May 24, 2024
60f7b64
Stop highlighting brackets next to argument
elsmr May 24, 2024
df9fe16
Update completion tests to use Object/Array (capitalized)
elsmr May 24, 2024
f462a50
Remove duplicate test
elsmr May 27, 2024
3f76c93
Merge branch 'master' into node-1376-show-expression-infobox-on-hover…
elsmr May 27, 2024
dc07b9e
Remove return types, remove info box for object properties
elsmr May 27, 2024
caabc35
Reuse CSS for infoboxes
elsmr May 28, 2024
ebb7d2c
Update tests, remove return type from title
elsmr May 28, 2024
9610f14
Fix infobox closing when editor loses focus to infobox
elsmr May 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ function useJsonFieldCompletions() {
* - Complete `$input.item.json[` to `['field']`.
*/
const inputJsonFieldCompletions = (context: CompletionContext): CompletionResult | null => {
console.log('🚀 ~ inputJsonFieldCompletions ~ context:', context);
const patterns = {
first: /\$input\.first\(\)\.json(\[|\.).*/,
last: /\$input\.last\(\)\.json(\[|\.).*/,
Expand Down Expand Up @@ -158,7 +157,6 @@ function useJsonFieldCompletions() {

if (name === 'all') {
const regexMatch = preCursor.text.match(regex);
console.log('🚀 ~ selectorJsonFieldCompletions ~ regexMatch:', regexMatch);
if (!regexMatch?.groups?.index) continue;

const { index } = regexMatch.groups;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
} from '@/plugins/codemirror/keymap';
import type { Segment } from '@/types/expressions';
import { removeExpressionPrefix } from '@/utils/expressions';
import { infoBoxTooltips } from '@/plugins/codemirror/tooltips/InfoBoxTooltip';

type Props = {
modelValue: string;
Expand Down Expand Up @@ -68,6 +69,7 @@ const extensions = computed(() => [
expressionInputHandler(),
EditorView.lineWrapping,
EditorView.domEventHandlers({ scroll: forceParse }),
infoBoxTooltips(),
]);
const editorValue = ref<string>(removeExpressionPrefix(props.modelValue));
const {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { removeExpressionPrefix } from '@/utils/expressions';
import { createEventBus, type EventBus } from 'n8n-design-system/utils';
import type { IDataObject } from 'n8n-workflow';
import { inputTheme } from './theme';
import { infoBoxTooltips } from '@/plugins/codemirror/tooltips/InfoBoxTooltip';

type Props = {
modelValue: string;
Expand Down Expand Up @@ -56,6 +57,7 @@ const extensions = computed(() => [
history(),
expressionInputHandler(),
EditorView.lineWrapping,
infoBoxTooltips(),
]);
const editorValue = ref<string>(removeExpressionPrefix(props.modelValue));
const {
Expand Down Expand Up @@ -138,7 +140,12 @@ onBeforeUnmount(() => {
</script>

<template>
<div ref="root" :class="$style.editor" data-test-id="inline-expression-editor-input"></div>
<div
ref="root"
title=""
:class="$style.editor"
data-test-id="inline-expression-editor-input"
></div>
</template>

<style lang="scss" module>
Expand Down
16 changes: 9 additions & 7 deletions packages/editor-ui/src/composables/useExpressionEditor.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import {
computed,
type MaybeRefOrGetter,
onBeforeUnmount,
onMounted,
ref,
watchEffect,
type Ref,
toValue,
watch,
onMounted,
watchEffect,
type MaybeRefOrGetter,
type Ref,
} from 'vue';

import { ensureSyntaxTree } from '@codemirror/language';
Expand All @@ -19,6 +19,8 @@ import { useNDVStore } from '@/stores/ndv.store';

import type { TargetItem } from '@/Interface';
import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers';
import { highlighter } from '@/plugins/codemirror/resolvableHighlighter';
import { closeCursorInfoBox } from '@/plugins/codemirror/tooltips/InfoBoxTooltip';
import type { Html, Plaintext, RawSegment, Resolvable, Segment } from '@/types/expressions';
import {
getExpressionErrorMessage,
Expand All @@ -28,16 +30,15 @@ import {
import { closeCompletion, completionStatus } from '@codemirror/autocomplete';
import {
Compartment,
EditorSelection,
EditorState,
type SelectionRange,
type Extension,
EditorSelection,
type SelectionRange,
} from '@codemirror/state';
import { EditorView, type ViewUpdate } from '@codemirror/view';
import { debounce, isEqual } from 'lodash-es';
import { useRouter } from 'vue-router';
import { useI18n } from '../composables/useI18n';
import { highlighter } from '../plugins/codemirror/resolvableHighlighter';
import { useWorkflowsStore } from '../stores/workflows.store';
import { useAutocompleteTelemetry } from './useAutocompleteTelemetry';

Expand Down Expand Up @@ -163,6 +164,7 @@ export const useExpressionEditor = ({
if (editor.value) {
editor.value.contentDOM.blur();
closeCompletion(editor.value);
closeCursorInfoBox(editor.value);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -735,8 +735,8 @@ describe('Resolution-based completions', () => {
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' }));
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.| }}', () => {
Expand All @@ -750,8 +750,8 @@ describe('Resolution-based completions', () => {
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' }));
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.| }}', () => {
Expand All @@ -765,8 +765,8 @@ describe('Resolution-based completions', () => {
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' }));
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', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export const ROOT_DOLLAR_COMPLETIONS: Completion[] = [
section: RECOMMENDED_SECTION,
info: createInfoBoxRenderer({
name: '$json',
returnType: 'object',
returnType: 'Object',
description: i18n.baseText('codeNodeEditor.completer.json'),
docURL: 'https://docs.n8n.io/data/data-structure/',
}),
Expand All @@ -64,7 +64,7 @@ export const ROOT_DOLLAR_COMPLETIONS: Completion[] = [
section: RECOMMENDED_SECTION,
info: createInfoBoxRenderer({
name: '$binary',
returnType: 'object',
returnType: 'Object',
description: i18n.baseText('codeNodeEditor.completer.binary'),
}),
},
Expand Down Expand Up @@ -170,7 +170,7 @@ export const ROOT_DOLLAR_COMPLETIONS: Completion[] = [
section: METADATA_SECTION,
info: createInfoBoxRenderer({
name: '$input',
returnType: 'object',
returnType: 'Object',
description: i18n.baseText('codeNodeEditor.completer.$input'),
}),
},
Expand All @@ -179,7 +179,7 @@ export const ROOT_DOLLAR_COMPLETIONS: Completion[] = [
section: METADATA_SECTION,
info: createInfoBoxRenderer({
name: '$parameter',
returnType: 'object',
returnType: 'Object',
description: i18n.baseText('codeNodeEditor.completer.$parameter'),
}),
},
Expand Down Expand Up @@ -215,7 +215,7 @@ export const ROOT_DOLLAR_COMPLETIONS: Completion[] = [
section: METADATA_SECTION,
info: createInfoBoxRenderer({
name: '$vars',
returnType: 'object',
returnType: 'Object',
description: i18n.baseText('codeNodeEditor.completer.$vars'),
}),
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ import { luxonInstanceDocs } from './nativesAutocompleteDocs/luxon.instance.docs
import { luxonStaticDocs } from './nativesAutocompleteDocs/luxon.static.docs';
import type { AutocompleteInput, ExtensionTypeName, FnToDoc, Resolved } from './types';
import {
applyBracketAccess,
applyBracketAccessCompletion,
applyCompletion,
getDefaultArgs,
getDisplayType,
hasNoParams,
hasRequiredArgs,
insertDefaultArgs,
Expand Down Expand Up @@ -181,7 +181,7 @@ export const natives = ({
typeName: ExtensionTypeName;
transformLabel?: (label: string) => string;
}): Completion[] => {
const nativeDocs: NativeDoc = NativeMethods.find((ee) => ee.typeName.toLowerCase() === typeName);
const nativeDocs = NativeMethods.find((ee) => ee.typeName.toLowerCase() === typeName);

if (!nativeDocs) return [];

Expand Down Expand Up @@ -231,12 +231,6 @@ export const extensions = ({
return toOptions({ fnToDoc, isFunction: true, 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)
Expand All @@ -258,7 +252,7 @@ export const isBinary = (input: AutocompleteInput<IDataObject>): boolean => {
};

export const getDetail = (base: string, value: unknown): string | undefined => {
const type = getType(value);
const type = getDisplayType(value);
if (!isInputData(base) || type === 'function') return undefined;
return type;
};
Expand Down Expand Up @@ -382,17 +376,6 @@ const objectOptions = (input: AutocompleteInput<IDataObject>): Completion[] => {
detail: getDetail(base, resolvedProp),
};

const infoName = needsBracketAccess ? applyBracketAccess(key) : key;
option.info = createCompletionOption({
name: infoName,
doc: {
name: infoName,
returnType: isFunction ? 'any' : getType(resolvedProp),
},
isFunction,
transformLabel,
}).info;

return option;
});

Expand Down Expand Up @@ -821,7 +804,7 @@ export const customDataOptions = () => {
},
{
name: 'getAll',
returnType: 'object',
returnType: 'Object',
docURL: 'https://docs.n8n.io/workflows/executions/custom-executions-data/',
description: i18n.baseText('codeNodeEditor.completer.$execution.customData.getAll'),
examples: [
Expand Down Expand Up @@ -1046,13 +1029,13 @@ export const itemOptions = () => {
return [
{
name: 'json',
returnType: 'object',
returnType: 'Object',
docURL: 'https://docs.n8n.io/data/data-structure/',
description: i18n.baseText('codeNodeEditor.completer.item.json'),
},
{
name: 'binary',
returnType: 'object',
returnType: 'Object',
docURL: 'https://docs.n8n.io/data/data-structure/',
description: i18n.baseText('codeNodeEditor.completer.item.binary'),
},
Expand Down Expand Up @@ -1161,7 +1144,7 @@ export const secretProvidersOptions = () => {
name: provider,
doc: {
name: provider,
returnType: 'object',
returnType: 'Object',
description: i18n.baseText('codeNodeEditor.completer.$secrets.provider'),
docURL: i18n.baseText('settings.externalSecrets.docs'),
},
Expand Down Expand Up @@ -1296,7 +1279,7 @@ export const objectGlobalOptions = () => {
evaluated: "{ id: 1, name: 'Banana' }",
},
],
returnType: 'object',
returnType: 'Object',
docURL:
'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign',
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@ import {
import type { Completion, CompletionContext, CompletionResult } from '@codemirror/autocomplete';
import { useExternalSecretsStore } from '@/stores/externalSecrets.ee.store';
import { escapeMappingString } from '@/utils/mappingUtils';
import { PREVIOUS_NODES_SECTION, RECOMMENDED_SECTION, ROOT_DOLLAR_COMPLETIONS } from './constants';
import {
METADATA_SECTION,
PREVIOUS_NODES_SECTION,
RECOMMENDED_SECTION,
ROOT_DOLLAR_COMPLETIONS,
} from './constants';
import { createInfoBoxRenderer } from './infoBoxRenderer';

/**
Expand Down Expand Up @@ -79,7 +84,7 @@ export function dollarOptions(): Completion[] {
section: RECOMMENDED_SECTION,
info: createInfoBoxRenderer({
name: '$request',
returnType: 'object',
returnType: 'Object',
docURL: 'https://docs.n8n.io/code/builtin/http-node-variables/',
description: i18n.baseText('codeNodeEditor.completer.$request'),
}),
Expand All @@ -91,12 +96,22 @@ export function dollarOptions(): Completion[] {
return useExternalSecretsStore().isEnterpriseExternalSecretsEnabled
? [
{
label: '$secrets',
type: 'keyword',
label: '$vars',
section: METADATA_SECTION,
info: createInfoBoxRenderer({
name: '$vars',
returnType: 'Object',
description: i18n.baseText('codeNodeEditor.completer.$vars'),
}),
},
{
label: '$vars',
type: 'keyword',
label: '$secrets',
section: METADATA_SECTION,
info: createInfoBoxRenderer({
name: '$secrets',
returnType: 'Object',
description: i18n.baseText('codeNodeEditor.completer.$secrets'),
}),
},
]
: [];
Expand All @@ -114,7 +129,7 @@ export function dollarOptions(): Completion[] {
label,
info: createInfoBoxRenderer({
name: label,
returnType: 'object',
returnType: 'Object',
description: i18n.baseText('codeNodeEditor.completer.$()', { interpolate: { nodeName } }),
}),
section: PREVIOUS_NODES_SECTION,
Expand Down
Loading
Loading