mirror of
https://github.com/n8n-io/n8n.git
synced 2024-09-11 13:15:28 +03:00
fix(editor): Highlight matching type in filter component (#10425)
This commit is contained in:
parent
e7ee10f243
commit
6bca879d4a
@ -17,6 +17,7 @@ import { type FilterOperatorId } from './constants';
|
||||
import {
|
||||
getFilterOperator,
|
||||
handleOperatorChange,
|
||||
inferOperatorType,
|
||||
isEmptyInput,
|
||||
operatorTypeToNodeProperty,
|
||||
resolveCondition,
|
||||
@ -70,6 +71,14 @@ const conditionResult = computed(() =>
|
||||
resolveCondition({ condition: condition.value, options: props.options }),
|
||||
);
|
||||
|
||||
const suggestedType = computed(() => {
|
||||
if (conditionResult.value.status !== 'resolve_error') {
|
||||
return inferOperatorType(conditionResult.value.resolved.leftValue);
|
||||
}
|
||||
|
||||
return 'any';
|
||||
});
|
||||
|
||||
const allIssues = computed(() => {
|
||||
if (conditionResult.value.status === 'validation_error' && !isEmpty.value) {
|
||||
return [conditionResult.value.error];
|
||||
@ -176,6 +185,7 @@ const onBlur = (): void => {
|
||||
<template #middle>
|
||||
<OperatorSelect
|
||||
:selected="`${operator.type}:${operator.operation}`"
|
||||
:suggested-type="suggestedType"
|
||||
:read-only="readOnly"
|
||||
@operator-change="onOperatorChange"
|
||||
></OperatorSelect>
|
||||
|
@ -4,13 +4,15 @@ import { computed, ref } from 'vue';
|
||||
import { OPERATOR_GROUPS } from './constants';
|
||||
import type { FilterOperator } from './types';
|
||||
import { getFilterOperator } from './utils';
|
||||
import type { FilterOperatorType } from 'n8n-workflow';
|
||||
|
||||
interface Props {
|
||||
selected: string;
|
||||
suggestedType?: FilterOperatorType;
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), { readOnly: false });
|
||||
const props = withDefaults(defineProps<Props>(), { readOnly: false, suggestedType: 'any' });
|
||||
|
||||
const selected = ref(props.selected);
|
||||
const menuOpen = ref(false);
|
||||
@ -31,6 +33,8 @@ const selectedGroupIcon = computed(
|
||||
|
||||
const selectedOperator = computed(() => getFilterOperator(selected.value));
|
||||
|
||||
const selectedType = computed(() => selectedOperator.value.type);
|
||||
|
||||
const onOperatorChange = (operator: string): void => {
|
||||
selected.value = operator;
|
||||
emit('operatorChange', operator);
|
||||
@ -65,12 +69,7 @@ function onGroupSelect(group: string) {
|
||||
@mouseenter="shouldRenderItems = true"
|
||||
>
|
||||
<template v-if="selectedGroupIcon" #prefix>
|
||||
<n8n-icon
|
||||
:class="$style.selectedGroupIcon"
|
||||
:icon="selectedGroupIcon"
|
||||
color="text-light"
|
||||
size="small"
|
||||
/>
|
||||
<n8n-icon :class="$style.icon" :icon="selectedGroupIcon" color="text-light" size="small" />
|
||||
</template>
|
||||
<div v-if="shouldRenderItems" :class="$style.groups">
|
||||
<div v-for="group of groups" :key="group.name">
|
||||
@ -84,12 +83,18 @@ function onGroupSelect(group: string) {
|
||||
>
|
||||
<template #reference>
|
||||
<div
|
||||
:class="$style.group"
|
||||
:class="[
|
||||
$style.group,
|
||||
{
|
||||
[$style.selected]: group.id === selectedType,
|
||||
[$style.suggested]: group.id === suggestedType,
|
||||
},
|
||||
]"
|
||||
@mouseenter="() => onGroupSelect(group.id)"
|
||||
@click="() => onGroupSelect(group.id)"
|
||||
>
|
||||
<div :class="$style.groupTitle">
|
||||
<n8n-icon v-if="group.icon" :icon="group.icon" color="text-light" size="small" />
|
||||
<n8n-icon v-if="group.icon" :icon="group.icon" :class="$style.icon" size="small" />
|
||||
<span>{{ i18n.baseText(group.name) }}</span>
|
||||
</div>
|
||||
<n8n-icon icon="chevron-right" color="text-light" size="xsmall" />
|
||||
@ -116,7 +121,7 @@ function onGroupSelect(group: string) {
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.selectedGroupIcon {
|
||||
.icon {
|
||||
color: var(--color-text-light);
|
||||
}
|
||||
|
||||
@ -137,6 +142,10 @@ function onGroupSelect(group: string) {
|
||||
padding: var(--spacing-2xs) var(--spacing-s);
|
||||
cursor: pointer;
|
||||
|
||||
&.suggested {
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--color-background-base);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { getFilterOperator, handleOperatorChange } from '../utils';
|
||||
import { getFilterOperator, handleOperatorChange, inferOperatorType } from '../utils';
|
||||
|
||||
describe('FilterConditions > utils', () => {
|
||||
describe('handleOperatorChange', () => {
|
||||
@ -71,4 +71,17 @@ describe('FilterConditions > utils', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('inferOperatorType', () => {
|
||||
test.each([
|
||||
['hello world', 'string'],
|
||||
[14.2, 'number'],
|
||||
[false, 'boolean'],
|
||||
[[{ a: 1 }], 'array'],
|
||||
[{ a: 1 }, 'object'],
|
||||
['2024-01-01', 'dateTime'],
|
||||
])('should correctly infer %s type', (value, result) => {
|
||||
expect(inferOperatorType(value)).toBe(result);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,5 +1,5 @@
|
||||
import type { BaseTextKey } from '@/plugins/i18n';
|
||||
import type { FilterOperatorValue } from 'n8n-workflow';
|
||||
import type { FilterConditionValue, FilterOperatorValue } from 'n8n-workflow';
|
||||
|
||||
export interface FilterOperator extends FilterOperatorValue {
|
||||
name: BaseTextKey;
|
||||
@ -14,5 +14,9 @@ export interface FilterOperatorGroup {
|
||||
|
||||
export type ConditionResult =
|
||||
| { status: 'resolve_error' }
|
||||
| { status: 'validation_error'; error: string }
|
||||
| { status: 'success'; result: boolean };
|
||||
| { status: 'validation_error'; error: string; resolved: FilterConditionValue }
|
||||
| {
|
||||
status: 'success';
|
||||
result: boolean;
|
||||
resolved: FilterConditionValue;
|
||||
};
|
||||
|
@ -13,6 +13,7 @@ import {
|
||||
} from 'n8n-workflow';
|
||||
import { OPERATORS_BY_ID, type FilterOperatorId } from './constants';
|
||||
import type { ConditionResult, FilterOperator } from './types';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
export const getFilterOperator = (key: string) =>
|
||||
OPERATORS_BY_ID[key as FilterOperatorId] as FilterOperator;
|
||||
@ -98,7 +99,7 @@ export const resolveCondition = ({
|
||||
index,
|
||||
errorFormat: 'inline',
|
||||
});
|
||||
return { status: 'success', result };
|
||||
return { status: 'success', result, resolved };
|
||||
} catch (error) {
|
||||
let errorMessage = i18n.baseText('parameterInput.error');
|
||||
|
||||
@ -108,6 +109,7 @@ export const resolveCondition = ({
|
||||
return {
|
||||
status: 'validation_error',
|
||||
error: errorMessage,
|
||||
resolved,
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
@ -135,3 +137,20 @@ export const operatorTypeToNodeProperty = (
|
||||
return { type: operatorType };
|
||||
}
|
||||
};
|
||||
|
||||
export const inferOperatorType = (value: unknown): FilterOperatorType => {
|
||||
if (typeof value === 'string') {
|
||||
if (validateFieldType('filter', value, 'dateTime').valid) return 'dateTime';
|
||||
return 'string';
|
||||
} else if (typeof value === 'number') {
|
||||
return 'number';
|
||||
} else if (typeof value === 'boolean') {
|
||||
return 'boolean';
|
||||
} else if (DateTime.isDateTime(value)) {
|
||||
return 'dateTime';
|
||||
} else if (value && typeof value === 'object') {
|
||||
return Array.isArray(value) ? 'array' : 'object';
|
||||
}
|
||||
|
||||
return 'any';
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user