mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-12 13:27:31 -08: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 {
|
import {
|
||||||
getFilterOperator,
|
getFilterOperator,
|
||||||
handleOperatorChange,
|
handleOperatorChange,
|
||||||
|
inferOperatorType,
|
||||||
isEmptyInput,
|
isEmptyInput,
|
||||||
operatorTypeToNodeProperty,
|
operatorTypeToNodeProperty,
|
||||||
resolveCondition,
|
resolveCondition,
|
||||||
|
@ -70,6 +71,14 @@ const conditionResult = computed(() =>
|
||||||
resolveCondition({ condition: condition.value, options: props.options }),
|
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(() => {
|
const allIssues = computed(() => {
|
||||||
if (conditionResult.value.status === 'validation_error' && !isEmpty.value) {
|
if (conditionResult.value.status === 'validation_error' && !isEmpty.value) {
|
||||||
return [conditionResult.value.error];
|
return [conditionResult.value.error];
|
||||||
|
@ -176,6 +185,7 @@ const onBlur = (): void => {
|
||||||
<template #middle>
|
<template #middle>
|
||||||
<OperatorSelect
|
<OperatorSelect
|
||||||
:selected="`${operator.type}:${operator.operation}`"
|
:selected="`${operator.type}:${operator.operation}`"
|
||||||
|
:suggested-type="suggestedType"
|
||||||
:read-only="readOnly"
|
:read-only="readOnly"
|
||||||
@operator-change="onOperatorChange"
|
@operator-change="onOperatorChange"
|
||||||
></OperatorSelect>
|
></OperatorSelect>
|
||||||
|
|
|
@ -4,13 +4,15 @@ import { computed, ref } from 'vue';
|
||||||
import { OPERATOR_GROUPS } from './constants';
|
import { OPERATOR_GROUPS } from './constants';
|
||||||
import type { FilterOperator } from './types';
|
import type { FilterOperator } from './types';
|
||||||
import { getFilterOperator } from './utils';
|
import { getFilterOperator } from './utils';
|
||||||
|
import type { FilterOperatorType } from 'n8n-workflow';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
selected: string;
|
selected: string;
|
||||||
|
suggestedType?: FilterOperatorType;
|
||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), { readOnly: false });
|
const props = withDefaults(defineProps<Props>(), { readOnly: false, suggestedType: 'any' });
|
||||||
|
|
||||||
const selected = ref(props.selected);
|
const selected = ref(props.selected);
|
||||||
const menuOpen = ref(false);
|
const menuOpen = ref(false);
|
||||||
|
@ -31,6 +33,8 @@ const selectedGroupIcon = computed(
|
||||||
|
|
||||||
const selectedOperator = computed(() => getFilterOperator(selected.value));
|
const selectedOperator = computed(() => getFilterOperator(selected.value));
|
||||||
|
|
||||||
|
const selectedType = computed(() => selectedOperator.value.type);
|
||||||
|
|
||||||
const onOperatorChange = (operator: string): void => {
|
const onOperatorChange = (operator: string): void => {
|
||||||
selected.value = operator;
|
selected.value = operator;
|
||||||
emit('operatorChange', operator);
|
emit('operatorChange', operator);
|
||||||
|
@ -65,12 +69,7 @@ function onGroupSelect(group: string) {
|
||||||
@mouseenter="shouldRenderItems = true"
|
@mouseenter="shouldRenderItems = true"
|
||||||
>
|
>
|
||||||
<template v-if="selectedGroupIcon" #prefix>
|
<template v-if="selectedGroupIcon" #prefix>
|
||||||
<n8n-icon
|
<n8n-icon :class="$style.icon" :icon="selectedGroupIcon" color="text-light" size="small" />
|
||||||
:class="$style.selectedGroupIcon"
|
|
||||||
:icon="selectedGroupIcon"
|
|
||||||
color="text-light"
|
|
||||||
size="small"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
<div v-if="shouldRenderItems" :class="$style.groups">
|
<div v-if="shouldRenderItems" :class="$style.groups">
|
||||||
<div v-for="group of groups" :key="group.name">
|
<div v-for="group of groups" :key="group.name">
|
||||||
|
@ -84,12 +83,18 @@ function onGroupSelect(group: string) {
|
||||||
>
|
>
|
||||||
<template #reference>
|
<template #reference>
|
||||||
<div
|
<div
|
||||||
:class="$style.group"
|
:class="[
|
||||||
|
$style.group,
|
||||||
|
{
|
||||||
|
[$style.selected]: group.id === selectedType,
|
||||||
|
[$style.suggested]: group.id === suggestedType,
|
||||||
|
},
|
||||||
|
]"
|
||||||
@mouseenter="() => onGroupSelect(group.id)"
|
@mouseenter="() => onGroupSelect(group.id)"
|
||||||
@click="() => onGroupSelect(group.id)"
|
@click="() => onGroupSelect(group.id)"
|
||||||
>
|
>
|
||||||
<div :class="$style.groupTitle">
|
<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>
|
<span>{{ i18n.baseText(group.name) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<n8n-icon icon="chevron-right" color="text-light" size="xsmall" />
|
<n8n-icon icon="chevron-right" color="text-light" size="xsmall" />
|
||||||
|
@ -116,7 +121,7 @@ function onGroupSelect(group: string) {
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
.selectedGroupIcon {
|
.icon {
|
||||||
color: var(--color-text-light);
|
color: var(--color-text-light);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,6 +142,10 @@ function onGroupSelect(group: string) {
|
||||||
padding: var(--spacing-2xs) var(--spacing-s);
|
padding: var(--spacing-2xs) var(--spacing-s);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
&.suggested {
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: var(--color-background-base);
|
background: var(--color-background-base);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { getFilterOperator, handleOperatorChange } from '../utils';
|
import { getFilterOperator, handleOperatorChange, inferOperatorType } from '../utils';
|
||||||
|
|
||||||
describe('FilterConditions > utils', () => {
|
describe('FilterConditions > utils', () => {
|
||||||
describe('handleOperatorChange', () => {
|
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 { BaseTextKey } from '@/plugins/i18n';
|
||||||
import type { FilterOperatorValue } from 'n8n-workflow';
|
import type { FilterConditionValue, FilterOperatorValue } from 'n8n-workflow';
|
||||||
|
|
||||||
export interface FilterOperator extends FilterOperatorValue {
|
export interface FilterOperator extends FilterOperatorValue {
|
||||||
name: BaseTextKey;
|
name: BaseTextKey;
|
||||||
|
@ -14,5 +14,9 @@ export interface FilterOperatorGroup {
|
||||||
|
|
||||||
export type ConditionResult =
|
export type ConditionResult =
|
||||||
| { status: 'resolve_error' }
|
| { status: 'resolve_error' }
|
||||||
| { status: 'validation_error'; error: string }
|
| { status: 'validation_error'; error: string; resolved: FilterConditionValue }
|
||||||
| { status: 'success'; result: boolean };
|
| {
|
||||||
|
status: 'success';
|
||||||
|
result: boolean;
|
||||||
|
resolved: FilterConditionValue;
|
||||||
|
};
|
||||||
|
|
|
@ -13,6 +13,7 @@ import {
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import { OPERATORS_BY_ID, type FilterOperatorId } from './constants';
|
import { OPERATORS_BY_ID, type FilterOperatorId } from './constants';
|
||||||
import type { ConditionResult, FilterOperator } from './types';
|
import type { ConditionResult, FilterOperator } from './types';
|
||||||
|
import { DateTime } from 'luxon';
|
||||||
|
|
||||||
export const getFilterOperator = (key: string) =>
|
export const getFilterOperator = (key: string) =>
|
||||||
OPERATORS_BY_ID[key as FilterOperatorId] as FilterOperator;
|
OPERATORS_BY_ID[key as FilterOperatorId] as FilterOperator;
|
||||||
|
@ -98,7 +99,7 @@ export const resolveCondition = ({
|
||||||
index,
|
index,
|
||||||
errorFormat: 'inline',
|
errorFormat: 'inline',
|
||||||
});
|
});
|
||||||
return { status: 'success', result };
|
return { status: 'success', result, resolved };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
let errorMessage = i18n.baseText('parameterInput.error');
|
let errorMessage = i18n.baseText('parameterInput.error');
|
||||||
|
|
||||||
|
@ -108,6 +109,7 @@ export const resolveCondition = ({
|
||||||
return {
|
return {
|
||||||
status: 'validation_error',
|
status: 'validation_error',
|
||||||
error: errorMessage,
|
error: errorMessage,
|
||||||
|
resolved,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -135,3 +137,20 @@ export const operatorTypeToNodeProperty = (
|
||||||
return { type: operatorType };
|
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 a new issue