Skip to content

Commit f4d53aa

Browse files
committed
Highlight matching operator type in filter dropdown
1 parent e6fd996 commit f4d53aa

File tree

5 files changed

+70
-15
lines changed

5 files changed

+70
-15
lines changed

packages/editor-ui/src/components/FilterConditions/Condition.vue

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { type FilterOperatorId } from './constants';
1717
import {
1818
getFilterOperator,
1919
handleOperatorChange,
20+
inferOperatorType,
2021
isEmptyInput,
2122
operatorTypeToNodeProperty,
2223
resolveCondition,
@@ -70,6 +71,14 @@ const conditionResult = computed(() =>
7071
resolveCondition({ condition: condition.value, options: props.options }),
7172
);
7273
74+
const suggestedType = computed(() => {
75+
if (conditionResult.value.status !== 'resolve_error') {
76+
return inferOperatorType(conditionResult.value.resolved.leftValue);
77+
}
78+
79+
return 'any';
80+
});
81+
7382
const allIssues = computed(() => {
7483
if (conditionResult.value.status === 'validation_error' && !isEmpty.value) {
7584
return [conditionResult.value.error];
@@ -176,6 +185,7 @@ const onBlur = (): void => {
176185
<template #middle>
177186
<OperatorSelect
178187
:selected="`${operator.type}:${operator.operation}`"
188+
:suggested-type="suggestedType"
179189
:read-only="readOnly"
180190
@operator-change="onOperatorChange"
181191
></OperatorSelect>

packages/editor-ui/src/components/FilterConditions/OperatorSelect.vue

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ import { computed, ref } from 'vue';
44
import { OPERATOR_GROUPS } from './constants';
55
import type { FilterOperator } from './types';
66
import { getFilterOperator } from './utils';
7+
import type { FilterOperatorType } from 'n8n-workflow';
78
89
interface Props {
910
selected: string;
11+
suggestedType?: FilterOperatorType;
1012
readOnly?: boolean;
1113
}
1214
13-
const props = withDefaults(defineProps<Props>(), { readOnly: false });
15+
const props = withDefaults(defineProps<Props>(), { readOnly: false, suggestedType: 'any' });
1416
1517
const selected = ref(props.selected);
1618
const menuOpen = ref(false);
@@ -31,6 +33,8 @@ const selectedGroupIcon = computed(
3133
3234
const selectedOperator = computed(() => getFilterOperator(selected.value));
3335
36+
const selectedType = computed(() => selectedOperator.value.type);
37+
3438
const onOperatorChange = (operator: string): void => {
3539
selected.value = operator;
3640
emit('operatorChange', operator);
@@ -65,12 +69,7 @@ function onGroupSelect(group: string) {
6569
@mouseenter="shouldRenderItems = true"
6670
>
6771
<template v-if="selectedGroupIcon" #prefix>
68-
<n8n-icon
69-
:class="$style.selectedGroupIcon"
70-
:icon="selectedGroupIcon"
71-
color="text-light"
72-
size="small"
73-
/>
72+
<n8n-icon :class="$style.icon" :icon="selectedGroupIcon" color="text-light" size="small" />
7473
</template>
7574
<div v-if="shouldRenderItems" :class="$style.groups">
7675
<div v-for="group of groups" :key="group.name">
@@ -84,12 +83,18 @@ function onGroupSelect(group: string) {
8483
>
8584
<template #reference>
8685
<div
87-
:class="$style.group"
86+
:class="[
87+
$style.group,
88+
{
89+
[$style.selected]: group.id === selectedType,
90+
[$style.suggested]: group.id === suggestedType,
91+
},
92+
]"
8893
@mouseenter="() => onGroupSelect(group.id)"
8994
@click="() => onGroupSelect(group.id)"
9095
>
9196
<div :class="$style.groupTitle">
92-
<n8n-icon v-if="group.icon" :icon="group.icon" color="text-light" size="small" />
97+
<n8n-icon v-if="group.icon" :icon="group.icon" :class="$style.icon" size="small" />
9398
<span>{{ i18n.baseText(group.name) }}</span>
9499
</div>
95100
<n8n-icon icon="chevron-right" color="text-light" size="xsmall" />
@@ -116,7 +121,7 @@ function onGroupSelect(group: string) {
116121
</template>
117122

118123
<style lang="scss" module>
119-
.selectedGroupIcon {
124+
.icon {
120125
color: var(--color-text-light);
121126
}
122127
@@ -137,6 +142,10 @@ function onGroupSelect(group: string) {
137142
padding: var(--spacing-2xs) var(--spacing-s);
138143
cursor: pointer;
139144
145+
&.suggested {
146+
font-weight: 800;
147+
}
148+
140149
&:hover {
141150
background: var(--color-background-base);
142151
}

packages/editor-ui/src/components/FilterConditions/__tests__/utils.test.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { getFilterOperator, handleOperatorChange } from '../utils';
1+
import { getFilterOperator, handleOperatorChange, inferOperatorType } from '../utils';
22

33
describe('FilterConditions > utils', () => {
44
describe('handleOperatorChange', () => {
@@ -71,4 +71,17 @@ describe('FilterConditions > utils', () => {
7171
});
7272
});
7373
});
74+
75+
describe('inferOperatorType', () => {
76+
test.each([
77+
['hello world', 'string'],
78+
[14.2, 'number'],
79+
[false, 'boolean'],
80+
[[{ a: 1 }], 'array'],
81+
[{ a: 1 }, 'object'],
82+
['2024-01-01', 'dateTime'],
83+
])('should correctly infer %s type', (value, result) => {
84+
expect(inferOperatorType(value)).toBe(result);
85+
});
86+
});
7487
});

packages/editor-ui/src/components/FilterConditions/types.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { BaseTextKey } from '@/plugins/i18n';
2-
import type { FilterOperatorValue } from 'n8n-workflow';
2+
import type { FilterConditionValue, FilterOperatorValue } from 'n8n-workflow';
33

44
export interface FilterOperator extends FilterOperatorValue {
55
name: BaseTextKey;
@@ -14,5 +14,9 @@ export interface FilterOperatorGroup {
1414

1515
export type ConditionResult =
1616
| { status: 'resolve_error' }
17-
| { status: 'validation_error'; error: string }
18-
| { status: 'success'; result: boolean };
17+
| { status: 'validation_error'; error: string; resolved: FilterConditionValue }
18+
| {
19+
status: 'success';
20+
result: boolean;
21+
resolved: FilterConditionValue;
22+
};

packages/editor-ui/src/components/FilterConditions/utils.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
} from 'n8n-workflow';
1414
import { OPERATORS_BY_ID, type FilterOperatorId } from './constants';
1515
import type { ConditionResult, FilterOperator } from './types';
16+
import { DateTime } from 'luxon';
1617

1718
export const getFilterOperator = (key: string) =>
1819
OPERATORS_BY_ID[key as FilterOperatorId] as FilterOperator;
@@ -98,7 +99,7 @@ export const resolveCondition = ({
9899
index,
99100
errorFormat: 'inline',
100101
});
101-
return { status: 'success', result };
102+
return { status: 'success', result, resolved };
102103
} catch (error) {
103104
let errorMessage = i18n.baseText('parameterInput.error');
104105

@@ -108,6 +109,7 @@ export const resolveCondition = ({
108109
return {
109110
status: 'validation_error',
110111
error: errorMessage,
112+
resolved,
111113
};
112114
}
113115
} catch (error) {
@@ -135,3 +137,20 @@ export const operatorTypeToNodeProperty = (
135137
return { type: operatorType };
136138
}
137139
};
140+
141+
export const inferOperatorType = (value: unknown): FilterOperatorType => {
142+
if (typeof value === 'string') {
143+
if (validateFieldType('filter', value, 'dateTime').valid) return 'dateTime';
144+
return 'string';
145+
} else if (typeof value === 'number') {
146+
return 'number';
147+
} else if (typeof value === 'boolean') {
148+
return 'boolean';
149+
} else if (DateTime.isDateTime(value)) {
150+
return 'dateTime';
151+
} else if (value && typeof value === 'object') {
152+
return Array.isArray(value) ? 'array' : 'object';
153+
}
154+
155+
return 'any';
156+
};

0 commit comments

Comments
 (0)