Fixed funnel report saving invalid data.
Some checks are pending
Node.js CI / build (postgresql, 18.18) (push) Waiting to run

This commit is contained in:
Mike Cao 2025-09-22 22:03:26 -07:00
parent bf16ade184
commit 980e4e6b41
11 changed files with 34 additions and 25 deletions

View file

@ -82,7 +82,7 @@
"@react-spring/web": "^10.0.1",
"@svgr/cli": "^8.1.0",
"@tanstack/react-query": "^5.85.5",
"@umami/react-zen": "^0.183.0",
"@umami/react-zen": "^0.184.0",
"@umami/redis-client": "^0.29.0",
"bcryptjs": "^3.0.2",
"chalk": "^5.6.0",

10
pnpm-lock.yaml generated
View file

@ -45,8 +45,8 @@ importers:
specifier: ^5.85.5
version: 5.85.5(react@19.1.1)
'@umami/react-zen':
specifier: ^0.183.0
version: 0.183.0(@babel/core@7.28.3)(@types/react@19.1.12)(babel-plugin-react-compiler@19.1.0-rc.2)(immer@10.1.1)(use-sync-external-store@1.5.0(react@19.1.1))
specifier: ^0.184.0
version: 0.184.0(@babel/core@7.28.3)(@types/react@19.1.12)(babel-plugin-react-compiler@19.1.0-rc.2)(immer@10.1.1)(use-sync-external-store@1.5.0(react@19.1.1))
'@umami/redis-client':
specifier: ^0.29.0
version: 0.29.0
@ -2735,8 +2735,8 @@ packages:
'@prisma/client': ^6.1.0
'@prisma/extension-read-replicas': ^0.4.1
'@umami/react-zen@0.183.0':
resolution: {integrity: sha512-snzfp87NElKbQn5lHUs3jTdCkXww4FHNJAGzyXVUcTv/5Bqv9mAmW+6htELychvQnO259t8GL/qJrcC5V/e4cg==}
'@umami/react-zen@0.184.0':
resolution: {integrity: sha512-XfxTiP4ljumflx02ymDMXLnhcJW+mOxxKCPEVEjuDrQfR6VUlbHg0EdH04S4gvCJZJC/WnP6guyO2eabhJL88Q==}
'@umami/redis-client@0.29.0':
resolution: {integrity: sha512-Jaqh++jskqDB7ny75pfC02OvKp1JTS4asGDsFrRL3qy8sxL3PAl9+/mybCJe4/6vWrXDJKqpgkSfUDJq2bFjyw==}
@ -10323,7 +10323,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@umami/react-zen@0.183.0(@babel/core@7.28.3)(@types/react@19.1.12)(babel-plugin-react-compiler@19.1.0-rc.2)(immer@10.1.1)(use-sync-external-store@1.5.0(react@19.1.1))':
'@umami/react-zen@0.184.0(@babel/core@7.28.3)(@types/react@19.1.12)(babel-plugin-react-compiler@19.1.0-rc.2)(immer@10.1.1)(use-sync-external-store@1.5.0(react@19.1.1))':
dependencies:
'@fontsource/jetbrains-mono': 5.2.8
'@internationalized/date': 3.9.0

View file

@ -1,7 +1,7 @@
import { Grid, Column, Row, Text, Icon, ProgressBar, Dialog, Box } from '@umami/react-zen';
import { useMessages, useResultQuery } from '@/components/hooks';
import { LoadingPanel } from '@/components/common/LoadingPanel';
import { File, Lightning, User } from '@/components/icons';
import { File, LightningSvg, User } from '@/components/icons';
import { formatLongNumber } from '@/lib/format';
import { ReportEditButton } from '@/components/input/ReportEditButton';
import { FunnelEditForm } from './FunnelEditForm';
@ -92,7 +92,7 @@ export function Funnel({ id, name, type, parameters, websiteId }) {
</Row>
<Row alignItems="center" justifyContent="space-between" gap>
<Row alignItems="center" gap>
<Icon>{type === 'path' ? <File /> : <Lightning />}</Icon>
<Icon>{type === 'path' ? <File /> : <LightningSvg />}</Icon>
<Text>{value}</Text>
</Row>
<Row alignItems="center" gap>

View file

@ -14,7 +14,7 @@ import {
Column,
} from '@umami/react-zen';
import { useMessages, useReportQuery, useUpdateQuery } from '@/components/hooks';
import { Close, Plus } from '@/components/icons';
import { X, Plus } from '@/components/icons';
import { ActionSelect } from '@/components/input/ActionSelect';
import { LookupField } from '@/components/input/LookupField';
@ -36,6 +36,8 @@ export function FunnelEditForm({
const { mutate, error, isPending, touch } = useUpdateQuery(`/reports${id ? `/${id}` : ''}`);
const handleSubmit = async ({ name, ...parameters }) => {
//
mutate(
{ ...data, id, name, type: 'funnel', websiteId, parameters },
{
@ -75,7 +77,13 @@ export function FunnelEditForm({
>
<TextField />
</FormField>
<FormFieldArray name="steps" label={formatMessage(labels.steps)}>
<FormFieldArray
name="steps"
label={formatMessage(labels.steps)}
rules={{
validate: value => value.length > 1 || 'At least two steps are required',
}}
>
{({ fields, append, remove, getValues }) => {
return (
<Grid gap>
@ -104,7 +112,7 @@ export function FunnelEditForm({
</Column>
<Button onPress={() => remove(index)}>
<Icon size="sm">
<Close />
<X />
</Icon>
</Button>
</Grid>

View file

@ -3,7 +3,7 @@ import { TooltipTrigger, Tooltip, Focusable, Icon, Text, Row, Column } from '@um
import { firstBy } from 'thenby';
import classNames from 'classnames';
import { useEscapeKey, useMessages, useResultQuery } from '@/components/hooks';
import { File, Lightning } from '@/components/icons';
import { File, LightningSvg } from '@/components/icons';
import { objectToArray } from '@/lib/data';
import { formatLongNumber } from '@/lib/format';
import { LoadingPanel } from '@/components/common/LoadingPanel';
@ -215,7 +215,7 @@ export function Journey({ websiteId, steps, startStep, endStep }: JourneyProps)
onClick={() => handleClick(name, columnIndex, paths)}
>
<Row alignItems="center" className={styles.name} title={name} gap>
<Icon>{name.startsWith('/') ? <File /> : <Lightning />}</Icon>
<Icon>{name.startsWith('/') ? <File /> : <LightningSvg />}</Icon>
<Text truncate>{name}</Text>
</Row>
<div className={styles.count} title={nodeCount}>

View file

@ -3,12 +3,12 @@ import { useMessages, useNavigation } from '@/components/hooks';
import { MetricsExpandedTable } from '@/components/metrics/MetricsExpandedTable';
import { SideMenu } from '@/components/common/SideMenu';
import {
Link,
LogOut,
LogIn,
Search,
Type,
ArrowRight,
SquareSlash,
SquareArrowRight,
Megaphone,
Earth,
Globe,
@ -45,7 +45,7 @@ export function WebsiteExpandedView({
id: 'path',
label: formatMessage(labels.path),
path: updateParams({ view: 'path' }),
icon: <Link />,
icon: <SquareSlash />,
},
{
id: 'entry',
@ -80,7 +80,7 @@ export function WebsiteExpandedView({
id: 'referrer',
label: formatMessage(labels.referrer),
path: updateParams({ view: 'referrer' }),
icon: <ArrowRight />,
icon: <SquareArrowRight />,
},
{
id: 'channel',

View file

@ -2,8 +2,9 @@ import { z } from 'zod';
import * as send from '@/app/api/send/route';
import { parseRequest } from '@/lib/request';
import { json, serverError } from '@/lib/response';
import { anyObjectParam } from '@/lib/schema';
const schema = z.array(z.object({}).passthrough());
const schema = z.array(anyObjectParam);
export async function POST(request: Request) {
try {

View file

@ -1,7 +1,7 @@
import { canDeleteWebsite, canUpdateWebsite, canViewWebsite } from '@/permissions';
import { parseRequest } from '@/lib/request';
import { json, notFound, ok, unauthorized } from '@/lib/response';
import { segmentTypeParam } from '@/lib/schema';
import { anyObjectParam, segmentTypeParam } from '@/lib/schema';
import { deleteSegment, getSegment, updateSegment } from '@/queries';
import { z } from 'zod';
@ -33,7 +33,7 @@ export async function POST(
const schema = z.object({
type: segmentTypeParam,
name: z.string().max(200),
parameters: z.object({}).passthrough(),
parameters: anyObjectParam,
});
const { auth, body, error } = await parseRequest(request, schema);

View file

@ -2,7 +2,7 @@ import { canUpdateWebsite, canViewWebsite } from '@/permissions';
import { uuid } from '@/lib/crypto';
import { getQueryFilters, parseRequest } from '@/lib/request';
import { json, unauthorized } from '@/lib/response';
import { segmentTypeParam, searchParams } from '@/lib/schema';
import { segmentTypeParam, searchParams, anyObjectParam } from '@/lib/schema';
import { createSegment, getWebsiteSegments } from '@/queries';
import { z } from 'zod';
@ -42,7 +42,7 @@ export async function POST(
const schema = z.object({
type: segmentTypeParam,
name: z.string().max(200),
parameters: z.object({}).passthrough(),
parameters: anyObjectParam,
});
const { auth, body, error } = await parseRequest(request, schema);

View file

@ -59,7 +59,7 @@ export function WebsiteDateFilter({
</Button>
</Row>
)}
<Row width="200px">
<Row minWidth="200px">
<DateFilter
value={value}
onChange={handleChange}

View file

@ -57,7 +57,7 @@ export const userRoleParam = z.enum(['admin', 'user', 'view-only']);
export const teamRoleParam = z.enum(['team-member', 'team-view-only', 'team-manager']);
export const anyObjectParam = z.object({}).passthrough();
export const anyObjectParam = z.record(z.string(), z.any());
export const urlOrPathParam = z.string().refine(
value => {
@ -202,7 +202,7 @@ export const reportBaseSchema = z.object({
type: reportTypeParam,
name: z.string().max(200),
description: z.string().max(500).optional(),
parameters: z.object({}).passthrough(),
parameters: anyObjectParam,
});
export const reportTypeSchema = z.discriminatedUnion('type', [