diff --git a/db/clickhouse/migrations/11_add_event_data_pivot.sql b/db/clickhouse/migrations/11_add_event_data_pivot.sql
index 09020457d..15ba1a7e8 100644
--- a/db/clickhouse/migrations/11_add_event_data_pivot.sql
+++ b/db/clickhouse/migrations/11_add_event_data_pivot.sql
@@ -26,7 +26,7 @@ AS SELECT
created_at,
groupArrayState(data_key) AS property_keys,
groupArrayState(multiIf(
- data_type = 1, ifNull(string_value, ''),
+ data_type IN (1, 3, 5), ifNull(string_value, ''),
data_type = 2, toString(ifNull(number_value, 0)),
data_type = 4, toString(ifNull(date_value, toDateTime(0))),
''
@@ -46,7 +46,7 @@ SELECT
created_at,
groupArrayState(data_key),
groupArrayState(multiIf(
- data_type = 1, ifNull(string_value, ''),
+ data_type IN (1, 3, 5), ifNull(string_value, ''),
data_type = 2, toString(ifNull(number_value, 0)),
data_type = 4, toString(ifNull(date_value, toDateTime(0))),
''
diff --git a/db/clickhouse/schema.sql b/db/clickhouse/schema.sql
index ddcf54d46..0540c97b2 100644
--- a/db/clickhouse/schema.sql
+++ b/db/clickhouse/schema.sql
@@ -336,11 +336,11 @@ AS SELECT
created_at,
groupArrayState(data_key) AS property_keys,
groupArrayState(multiIf(
- data_type = 1, ifNull(string_value, ''),
+ data_type IN (1, 3, 5), ifNull(string_value, ''),
data_type = 2, toString(ifNull(number_value, 0)),
data_type = 4, toString(ifNull(date_value, toDateTime(0))),
''
)) AS property_values,
groupArrayState(data_type) AS property_types
FROM umami.event_data
-GROUP BY website_id, session_id, event_id, event_name, url_path, created_at;
\ No newline at end of file
+GROUP BY website_id, session_id, event_id, event_name, url_path, created_at;
diff --git a/src/app/(main)/websites/[websiteId]/event-data/EventDataFilterBar.tsx b/src/app/(main)/websites/[websiteId]/event-data/EventDataFilterBar.tsx
index a424d595a..cec4ed408 100644
--- a/src/app/(main)/websites/[websiteId]/event-data/EventDataFilterBar.tsx
+++ b/src/app/(main)/websites/[websiteId]/event-data/EventDataFilterBar.tsx
@@ -2,6 +2,7 @@
import { Button, Icon, Row, Text, Tooltip, TooltipTrigger } from '@umami/react-zen';
import { useMessages } from '@/components/hooks';
import { X } from '@/components/icons';
+import { OPERATORS } from '@/lib/constants';
import type { EventPropertyFilter } from '@/lib/types';
export function EventDataFilterBar({
@@ -17,16 +18,16 @@ export function EventDataFilterBar({
const operatorLabel = (op: string) => {
switch (op) {
- case 'eq': return t(labels.is);
- case 'neq': return t(labels.isNot);
- case 'c': return t(labels.contains);
- case 'dnc': return t(labels.doesNotContain);
- case 'regex': return t(labels.regexMatch);
- case 'notRegex': return t(labels.regexNotMatch);
- case 'gt': return t(labels.greaterThan);
- case 'lt': return t(labels.lessThan);
- case 'gte': return t(labels.greaterThanEquals);
- case 'lte': return t(labels.lessThanEquals);
+ case OPERATORS.equals: return t(labels.is);
+ case OPERATORS.notEquals: return t(labels.isNot);
+ case OPERATORS.contains: return t(labels.contains);
+ case OPERATORS.doesNotContain: return t(labels.doesNotContain);
+ case OPERATORS.regex: return t(labels.regexMatch);
+ case OPERATORS.notRegex: return t(labels.regexNotMatch);
+ case OPERATORS.greaterThan: return t(labels.greaterThan);
+ case OPERATORS.lessThan: return t(labels.lessThan);
+ case OPERATORS.greaterThanEquals: return t(labels.greaterThanEquals);
+ case OPERATORS.lessThanEquals: return t(labels.lessThanEquals);
default: return op;
}
};
diff --git a/src/app/(main)/websites/[websiteId]/event-data/EventDataFilterEditForm.tsx b/src/app/(main)/websites/[websiteId]/event-data/EventDataFilterEditForm.tsx
index 82030cd32..5d8c1f960 100644
--- a/src/app/(main)/websites/[websiteId]/event-data/EventDataFilterEditForm.tsx
+++ b/src/app/(main)/websites/[websiteId]/event-data/EventDataFilterEditForm.tsx
@@ -4,6 +4,7 @@ import { useState } from 'react';
import { Empty } from '@/components/common/Empty';
import { useEventDataFieldsQuery, useMessages, useMobile } from '@/components/hooks';
import { Plus } from '@/components/icons';
+import { OPERATORS } from '@/lib/constants';
import type { EventPropertyFilter } from '@/lib/types';
import { EventDataFilterRecord } from './EventDataFilterRecord';
@@ -29,7 +30,10 @@ export function EventDataFilterEditForm({
const handleAdd = (propertyName: string) => {
const field = (fields as any[]).find(f => f.propertyName === propertyName);
const dataType: number = field?.dataType ?? 1;
- setFilters(prev => [...prev, { propertyName, dataType, operator: 'eq', value: '' }]);
+ setFilters(prev => [
+ ...prev,
+ { propertyName, dataType, operator: OPERATORS.equals, value: '' },
+ ]);
};
const handleChange = (index: number, filter: EventPropertyFilter) => {
diff --git a/src/app/(main)/websites/[websiteId]/event-data/EventDataFilterRecord.tsx b/src/app/(main)/websites/[websiteId]/event-data/EventDataFilterRecord.tsx
index 4ca358029..16097db1d 100644
--- a/src/app/(main)/websites/[websiteId]/event-data/EventDataFilterRecord.tsx
+++ b/src/app/(main)/websites/[websiteId]/event-data/EventDataFilterRecord.tsx
@@ -5,10 +5,32 @@ import { Empty } from '@/components/common/Empty';
import { MultiSelect } from '@/components/common/MultiSelect';
import { useEventDataValuesQuery, useMessages } from '@/components/hooks';
import { X } from '@/components/icons';
-import type { EventPropertyFilter } from '@/lib/types';
+import { OPERATORS } from '@/lib/constants';
+import type { EventPropertyFilter, Operator } from '@/lib/types';
-const STRING_OPERATORS = ['eq', 'neq', 'c', 'dnc', 'regex', 'notRegex'] as const;
-const NUMERIC_OPERATORS = ['eq', 'neq', 'gt', 'lt', 'gte', 'lte'] as const;
+const STRING_OPERATORS: Operator[] = [
+ OPERATORS.equals,
+ OPERATORS.notEquals,
+ OPERATORS.contains,
+ OPERATORS.doesNotContain,
+ OPERATORS.regex,
+ OPERATORS.notRegex,
+];
+const NUMERIC_OPERATORS: Operator[] = [
+ OPERATORS.equals,
+ OPERATORS.notEquals,
+ OPERATORS.greaterThan,
+ OPERATORS.lessThan,
+ OPERATORS.greaterThanEquals,
+ OPERATORS.lessThanEquals,
+];
+const MULTI_OPERATORS: Operator[] = [OPERATORS.equals, OPERATORS.notEquals];
+const FREE_TEXT_OPERATORS: Operator[] = [
+ OPERATORS.contains,
+ OPERATORS.doesNotContain,
+ OPERATORS.regex,
+ OPERATORS.notRegex,
+];
export function EventDataFilterRecord({
websiteId,
@@ -28,7 +50,7 @@ export function EventDataFilterRecord({
const isNumeric = filter.dataType === 2;
const operators = isNumeric ? NUMERIC_OPERATORS : STRING_OPERATORS;
- const isFreeText = filter.operator === 'c' || filter.operator === 'dnc' || filter.operator === 'regex' || filter.operator === 'notRegex';
+ const isFreeText = FREE_TEXT_OPERATORS.includes(filter.operator);
const { data, isLoading } = useEventDataValuesQuery(
websiteId,
@@ -43,27 +65,31 @@ export function EventDataFilterRecord({
: values;
const selected = filter.value ? filter.value.split(',').filter(Boolean) : [];
- const operatorLabel = (op: string) => {
+ const operatorLabel = (op: Operator) => {
switch (op) {
- case 'eq': return t(labels.is);
- case 'neq': return t(labels.isNot);
- case 'c': return t(labels.contains);
- case 'dnc': return t(labels.doesNotContain);
- case 'regex': return t(labels.regexMatch);
- case 'notRegex': return t(labels.regexNotMatch);
- case 'gt': return t(labels.greaterThan);
- case 'lt': return t(labels.lessThan);
- case 'gte': return t(labels.greaterThanEquals);
- case 'lte': return t(labels.lessThanEquals);
+ case OPERATORS.equals: return t(labels.is);
+ case OPERATORS.notEquals: return t(labels.isNot);
+ case OPERATORS.contains: return t(labels.contains);
+ case OPERATORS.doesNotContain: return t(labels.doesNotContain);
+ case OPERATORS.regex: return t(labels.regexMatch);
+ case OPERATORS.notRegex: return t(labels.regexNotMatch);
+ case OPERATORS.greaterThan: return t(labels.greaterThan);
+ case OPERATORS.lessThan: return t(labels.lessThan);
+ case OPERATORS.greaterThanEquals: return t(labels.greaterThanEquals);
+ case OPERATORS.lessThanEquals: return t(labels.lessThanEquals);
default: return op;
}
};
- const handleOperatorChange = (op: string) => {
+ const handleOperatorChange = (op: Operator) => {
// clear value when switching between multi-select and free-text modes
- const wasMulti = filter.operator === 'eq' || filter.operator === 'neq';
- const isMulti = op === 'eq' || op === 'neq';
- onChange({ ...filter, operator: op, value: wasMulti === isMulti ? filter.value : '' });
+ const wasMulti = MULTI_OPERATORS.includes(filter.operator);
+ const isMulti = MULTI_OPERATORS.includes(op);
+ onChange({
+ ...filter,
+ operator: op,
+ value: wasMulti === isMulti ? filter.value : '',
+ });
};
return (
@@ -71,8 +97,8 @@ export function EventDataFilterRecord({
-