mirror of
https://github.com/umami-software/umami.git
synced 2026-02-04 04:37:11 +01:00
269 lines
5.9 KiB
TypeScript
269 lines
5.9 KiB
TypeScript
import { z } from 'zod';
|
|
import { isValidTimezone, normalizeTimezone } from '@/lib/date';
|
|
import { UNIT_TYPES } from './constants';
|
|
|
|
export const timezoneParam = z
|
|
.string()
|
|
.refine((value: string) => isValidTimezone(value), {
|
|
message: 'Invalid timezone',
|
|
})
|
|
.transform((value: string) => normalizeTimezone(value));
|
|
|
|
export const unitParam = z.string().refine(value => UNIT_TYPES.includes(value), {
|
|
message: 'Invalid unit',
|
|
});
|
|
|
|
export const dateRangeParams = {
|
|
startAt: z.coerce.number().optional(),
|
|
endAt: z.coerce.number().optional(),
|
|
startDate: z.coerce.date().optional(),
|
|
endDate: z.coerce.date().optional(),
|
|
timezone: timezoneParam.optional(),
|
|
unit: unitParam.optional(),
|
|
compare: z.enum(['prev', 'yoy']).optional(),
|
|
};
|
|
|
|
export const filterParams = {
|
|
path: z.string().optional(),
|
|
referrer: z.string().optional(),
|
|
title: z.string().optional(),
|
|
query: z.string().optional(),
|
|
os: z.string().optional(),
|
|
browser: z.string().optional(),
|
|
device: z.string().optional(),
|
|
country: z.string().optional(),
|
|
region: z.string().optional(),
|
|
city: z.string().optional(),
|
|
tag: z.string().optional(),
|
|
hostname: z.string().optional(),
|
|
language: z.string().optional(),
|
|
event: z.string().optional(),
|
|
segment: z.uuid().optional(),
|
|
cohort: z.uuid().optional(),
|
|
eventType: z.coerce.number().int().positive().optional(),
|
|
};
|
|
|
|
export const searchParams = {
|
|
search: z.string().optional(),
|
|
};
|
|
|
|
export const pagingParams = {
|
|
page: z.coerce.number().int().positive().optional(),
|
|
pageSize: z.coerce.number().int().positive().optional(),
|
|
};
|
|
|
|
export const sortingParams = {
|
|
orderBy: z.string().optional(),
|
|
};
|
|
|
|
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.record(z.string(), z.any());
|
|
|
|
export const urlOrPathParam = z.string().refine(
|
|
value => {
|
|
try {
|
|
new URL(value, 'https://localhost');
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
},
|
|
{
|
|
message: 'Invalid URL.',
|
|
},
|
|
);
|
|
|
|
export const fieldsParam = z.enum([
|
|
'path',
|
|
'referrer',
|
|
'title',
|
|
'query',
|
|
'os',
|
|
'browser',
|
|
'device',
|
|
'country',
|
|
'region',
|
|
'city',
|
|
'tag',
|
|
'hostname',
|
|
'language',
|
|
'event',
|
|
]);
|
|
|
|
export const reportTypeParam = z.enum([
|
|
'attribution',
|
|
'breakdown',
|
|
'funnel',
|
|
'goal',
|
|
'journey',
|
|
'retention',
|
|
'revenue',
|
|
'utm',
|
|
]);
|
|
|
|
export const operatorParam = z.enum([
|
|
'eq',
|
|
'neq',
|
|
's',
|
|
'ns',
|
|
'c',
|
|
'dnc',
|
|
't',
|
|
'f',
|
|
'gt',
|
|
'lt',
|
|
'gte',
|
|
'lte',
|
|
'bf',
|
|
'af',
|
|
]);
|
|
|
|
export const goalReportSchema = z.object({
|
|
type: z.literal('goal'),
|
|
parameters: z
|
|
.object({
|
|
startDate: z.coerce.date(),
|
|
endDate: z.coerce.date(),
|
|
type: z.string(),
|
|
value: z.string(),
|
|
operator: z.enum(['count', 'sum', 'average']).optional(),
|
|
property: z.string().optional(),
|
|
})
|
|
.refine(data => {
|
|
if (data.type === 'event' && data.property) {
|
|
return data.operator && data.property;
|
|
}
|
|
return true;
|
|
}),
|
|
});
|
|
|
|
export const funnelReportSchema = z.object({
|
|
type: z.literal('funnel'),
|
|
parameters: z.object({
|
|
startDate: z.coerce.date(),
|
|
endDate: z.coerce.date(),
|
|
window: z.coerce.number().positive(),
|
|
steps: z
|
|
.array(
|
|
z.object({
|
|
type: z.enum(['path', 'event']),
|
|
value: z.string(),
|
|
}),
|
|
)
|
|
.min(2)
|
|
.max(8),
|
|
}),
|
|
});
|
|
|
|
export const journeyReportSchema = z.object({
|
|
type: z.literal('journey'),
|
|
parameters: z.object({
|
|
startDate: z.coerce.date(),
|
|
endDate: z.coerce.date(),
|
|
steps: z.coerce.number().min(2).max(7),
|
|
startStep: z.string().optional(),
|
|
endStep: z.string().optional(),
|
|
}),
|
|
});
|
|
|
|
export const retentionReportSchema = z.object({
|
|
type: z.literal('retention'),
|
|
parameters: z.object({
|
|
startDate: z.coerce.date(),
|
|
endDate: z.coerce.date(),
|
|
timezone: timezoneParam.optional(),
|
|
}),
|
|
});
|
|
|
|
export const utmReportSchema = z.object({
|
|
type: z.literal('utm'),
|
|
parameters: z.object({
|
|
startDate: z.coerce.date(),
|
|
endDate: z.coerce.date(),
|
|
}),
|
|
});
|
|
|
|
export const revenueReportSchema = z.object({
|
|
type: z.literal('revenue'),
|
|
parameters: z.object({
|
|
startDate: z.coerce.date(),
|
|
endDate: z.coerce.date(),
|
|
unit: unitParam.optional(),
|
|
timezone: timezoneParam.optional(),
|
|
currency: z.string(),
|
|
}),
|
|
});
|
|
|
|
export const attributionReportSchema = z.object({
|
|
type: z.literal('attribution'),
|
|
parameters: z.object({
|
|
startDate: z.coerce.date(),
|
|
endDate: z.coerce.date(),
|
|
model: z.enum(['first-click', 'last-click']),
|
|
type: z.enum(['path', 'event']),
|
|
step: z.string(),
|
|
currency: z.string().optional(),
|
|
}),
|
|
});
|
|
|
|
export const breakdownReportSchema = z.object({
|
|
type: z.literal('breakdown'),
|
|
parameters: z.object({
|
|
startDate: z.coerce.date(),
|
|
endDate: z.coerce.date(),
|
|
fields: z.array(fieldsParam),
|
|
}),
|
|
});
|
|
|
|
export const reportBaseSchema = z.object({
|
|
websiteId: z.uuid(),
|
|
type: reportTypeParam,
|
|
name: z.string().max(200),
|
|
description: z.string().max(500).optional(),
|
|
parameters: anyObjectParam,
|
|
});
|
|
|
|
export const reportTypeSchema = z.discriminatedUnion('type', [
|
|
goalReportSchema,
|
|
funnelReportSchema,
|
|
journeyReportSchema,
|
|
retentionReportSchema,
|
|
utmReportSchema,
|
|
revenueReportSchema,
|
|
attributionReportSchema,
|
|
breakdownReportSchema,
|
|
]);
|
|
|
|
export const reportSchema = reportBaseSchema;
|
|
|
|
export const reportResultSchema = z.intersection(
|
|
z.object({
|
|
websiteId: z.uuid(),
|
|
filters: z.object({ ...filterParams }),
|
|
}),
|
|
reportTypeSchema,
|
|
);
|
|
|
|
export const segmentTypeParam = z.enum(['segment', 'cohort']);
|
|
|
|
export const segmentParamSchema = z.object({
|
|
filters: z
|
|
.array(
|
|
z.object({
|
|
name: z.string(),
|
|
operator: operatorParam,
|
|
value: z.string(),
|
|
}),
|
|
)
|
|
.optional(),
|
|
dateRange: z.string().optional(),
|
|
action: z
|
|
.object({
|
|
type: z.string(),
|
|
value: z.string(),
|
|
})
|
|
.optional(),
|
|
});
|