diff --git a/next.config.ts b/next.config.ts index e52df6ab..f2c07aff 100644 --- a/next.config.ts +++ b/next.config.ts @@ -199,18 +199,6 @@ export default { typescript: { ignoreBuildErrors: true, }, - functions: { - 'app/api/**/*.js': { - maxDuration: 30, - }, - }, - outputFileTracing: { - include: [ - 'src/generated/prisma/**/*', - 'node_modules/@prisma/client/**/*', - 'node_modules/.prisma/client/**/*', - ], - }, async headers() { return headers; }, diff --git a/src/app/(main)/App.tsx b/src/app/(main)/App.tsx index f3975c3a..ecff5023 100644 --- a/src/app/(main)/App.tsx +++ b/src/app/(main)/App.tsx @@ -4,7 +4,7 @@ import Script from 'next/script'; import { usePathname } from 'next/navigation'; import { UpdateNotice } from './UpdateNotice'; import { SideNav } from '@/app/(main)/SideNav'; -import { MenuBar } from '@/app/(main)/MenuBar'; +import { TopNav } from '@/app/(main)/TopNav'; import { useLoginQuery, useConfig } from '@/components/hooks'; export function App({ children }) { @@ -35,7 +35,7 @@ export function App({ children }) { - + {}; + const { teamId, websiteId, pathname } = useNavigation(); + const isSettings = pathname.includes('/settings'); return ( - {websiteId && ( + {websiteId && !isSettings && ( <> - + )} diff --git a/src/app/(main)/admin/users/UserDeleteForm.tsx b/src/app/(main)/admin/users/UserDeleteForm.tsx index 59b12721..620da1d1 100644 --- a/src/app/(main)/admin/users/UserDeleteForm.tsx +++ b/src/app/(main)/admin/users/UserDeleteForm.tsx @@ -21,6 +21,7 @@ export function UserDeleteForm({ mutate(null, { onSuccess: async () => { touch('users'); + touch(`users:${userId}`); onSave?.(); onClose?.(); }, @@ -35,9 +36,7 @@ export function UserDeleteForm({ confirmLabel={formatMessage(labels.delete)} isDanger > - - {formatMessage(messages.confirmDelete, { target: {username} })} - + {formatMessage(messages.confirmDelete, { target: username })} ); } diff --git a/src/app/(main)/admin/websites/AdminWebsitesTable.tsx b/src/app/(main)/admin/websites/AdminWebsitesTable.tsx index 99105bc3..7b970e7f 100644 --- a/src/app/(main)/admin/websites/AdminWebsitesTable.tsx +++ b/src/app/(main)/admin/websites/AdminWebsitesTable.tsx @@ -1,15 +1,16 @@ import { useState } from 'react'; import Link from 'next/link'; -import { Row, Text, Icon, DataTable, DataColumn, MenuItem, Modal } from '@umami/react-zen'; +import { Row, Text, Icon, DataTable, DataColumn, MenuItem, Modal, Dialog } from '@umami/react-zen'; import { Trash, Users } from '@/components/icons'; import { useMessages } from '@/components/hooks'; import { Edit } from '@/components/icons'; import { MenuButton } from '@/components/input/MenuButton'; import { DateDistance } from '@/components/common/DateDistance'; +import { WebsiteDeleteForm } from '@/app/(main)/settings/websites/[websiteId]/WebsiteDeleteForm'; export function AdminWebsitesTable({ data = [] }: { data: any[] }) { const { formatMessage, labels } = useMessages(); - const [deleteUser, setDeleteUser] = useState(null); + const [deleteWebsite, setDeleteWebsite] = useState(null); return ( <> @@ -64,7 +65,7 @@ export function AdminWebsitesTable({ data = [] }: { data: any[] }) { setDeleteUser(row)} + onAction={() => setDeleteWebsite(id)} data-test="link-button-delete" > @@ -79,7 +80,11 @@ export function AdminWebsitesTable({ data = [] }: { data: any[] }) { }} - + + + setDeleteWebsite(null)} /> + + ); } diff --git a/src/app/(main)/settings/SettingsLayout.tsx b/src/app/(main)/settings/SettingsLayout.tsx index cfc901d4..55c77d7b 100644 --- a/src/app/(main)/settings/SettingsLayout.tsx +++ b/src/app/(main)/settings/SettingsLayout.tsx @@ -42,7 +42,7 @@ export function SettingsLayout({ children }: { children: ReactNode }) { - {children} + {children} diff --git a/src/app/(main)/settings/teams/[teamId]/TeamDetails.tsx b/src/app/(main)/settings/teams/[teamId]/TeamDetails.tsx index e4d7e514..071c56a8 100644 --- a/src/app/(main)/settings/teams/[teamId]/TeamDetails.tsx +++ b/src/app/(main)/settings/teams/[teamId]/TeamDetails.tsx @@ -1,7 +1,7 @@ import { useContext, useState } from 'react'; import { Column, Tabs, TabList, Tab, TabPanel } from '@umami/react-zen'; import { TeamContext } from '@/app/(main)/teams/[teamId]/TeamProvider'; -import { useLoginQuery, useMessages } from '@/components/hooks'; +import { useLoginQuery, useMessages, useNavigation } from '@/components/hooks'; import { SectionHeader } from '@/components/common/SectionHeader'; import { ROLES } from '@/lib/constants'; import { Users } from '@/components/icons'; @@ -15,7 +15,10 @@ export function TeamDetails({ teamId }: { teamId: string }) { const team = useContext(TeamContext); const { formatMessage, labels } = useMessages(); const { user } = useLoginQuery(); - const [tab, setTab] = useState('details'); + const { query, pathname } = useNavigation(); + const [tab, setTab] = useState(query?.tab || 'details'); + + const isAdmin = pathname.includes('/admin'); const isTeamOwner = !!team?.teamUser?.find(({ userId, role }) => role === ROLES.teamOwner && userId === user.id) && @@ -32,7 +35,7 @@ export function TeamDetails({ teamId }: { teamId: string }) { return ( }> - {!isTeamOwner && } + {!isTeamOwner && !isAdmin && } setTab(value)}> diff --git a/src/app/(main)/settings/teams/[teamId]/TeamMembersTable.tsx b/src/app/(main)/settings/teams/[teamId]/TeamMembersTable.tsx index f84f58a9..c4cd45a8 100644 --- a/src/app/(main)/settings/teams/[teamId]/TeamMembersTable.tsx +++ b/src/app/(main)/settings/teams/[teamId]/TeamMembersTable.tsx @@ -33,6 +33,10 @@ export function TeamMembersTable({ {allowEdit && ( {(row: any) => { + if (row?.role === ROLES.teamOwner) { + return null; + } + return ( diff --git a/src/app/(main)/settings/websites/WebsitesTable.tsx b/src/app/(main)/settings/websites/WebsitesTable.tsx index 2d285c75..a7948781 100644 --- a/src/app/(main)/settings/websites/WebsitesTable.tsx +++ b/src/app/(main)/settings/websites/WebsitesTable.tsx @@ -57,7 +57,7 @@ export function WebsitesTable({ )} {allowEdit && ( - + diff --git a/src/app/(main)/settings/websites/[websiteId]/WebsiteDeleteForm.tsx b/src/app/(main)/settings/websites/[websiteId]/WebsiteDeleteForm.tsx index 9958e8cb..b6e723d7 100644 --- a/src/app/(main)/settings/websites/[websiteId]/WebsiteDeleteForm.tsx +++ b/src/app/(main)/settings/websites/[websiteId]/WebsiteDeleteForm.tsx @@ -1,4 +1,4 @@ -import { useApi, useMessages } from '@/components/hooks'; +import { useApi, useMessages, useModified } from '@/components/hooks'; import { TypeConfirmationForm } from '@/components/common/TypeConfirmationForm'; const CONFIRM_VALUE = 'DELETE'; @@ -17,10 +17,13 @@ export function WebsiteDeleteForm({ const { mutate, isPending, error } = useMutation({ mutationFn: () => del(`/websites/${websiteId}`), }); + const { touch } = useModified(); const handleConfirm = async () => { mutate(null, { onSuccess: async () => { + touch('websites'); + touch(`websites:${websiteId}`); onSave?.(); onClose?.(); }, diff --git a/src/app/(main)/settings/websites/[websiteId]/WebsiteShareForm.tsx b/src/app/(main)/settings/websites/[websiteId]/WebsiteShareForm.tsx index dd3dcd26..4f3fe247 100644 --- a/src/app/(main)/settings/websites/[websiteId]/WebsiteShareForm.tsx +++ b/src/app/(main)/settings/websites/[websiteId]/WebsiteShareForm.tsx @@ -73,7 +73,7 @@ export function WebsiteShareForm({ websiteId, shareId, onSave, onClose }: Websit {id && } - + {onClose && } {formatMessage(labels.save)} diff --git a/src/app/(main)/websites/WebsitesPage.tsx b/src/app/(main)/websites/WebsitesPage.tsx index ce922ca2..d07192dc 100644 --- a/src/app/(main)/websites/WebsitesPage.tsx +++ b/src/app/(main)/websites/WebsitesPage.tsx @@ -18,7 +18,7 @@ export function WebsitesPage() { - + diff --git a/src/app/(main)/websites/[websiteId]/WebsiteHeader.tsx b/src/app/(main)/websites/[websiteId]/WebsiteHeader.tsx index 9c4dd08c..75a8e97e 100644 --- a/src/app/(main)/websites/[websiteId]/WebsiteHeader.tsx +++ b/src/app/(main)/websites/[websiteId]/WebsiteHeader.tsx @@ -5,12 +5,11 @@ import { Share, Edit } from '@/components/icons'; import { Favicon } from '@/components/common/Favicon'; import { ActiveUsers } from '@/components/metrics/ActiveUsers'; import { WebsiteShareForm } from '@/app/(main)/settings/websites/[websiteId]/WebsiteShareForm'; -import { useMessages, useNavigation } from '@/components/hooks'; +import { useMessages } from '@/components/hooks'; import { LinkButton } from '@/components/common/LinkButton'; export function WebsiteHeader() { const website = useWebsite(); - const { renderUrl } = useNavigation(); return ( } showBorder={false}> @@ -18,7 +17,7 @@ export function WebsiteHeader() { - + @@ -42,7 +41,7 @@ const ShareButton = ({ websiteId, shareId }) => { Share - + {({ close }) => { return ; }} diff --git a/src/app/api/realtime/[websiteId]/route.ts b/src/app/api/realtime/[websiteId]/route.ts index f78688c3..f86bfccc 100644 --- a/src/app/api/realtime/[websiteId]/route.ts +++ b/src/app/api/realtime/[websiteId]/route.ts @@ -21,7 +21,7 @@ export async function GET( return unauthorized(); } - const filters = getQueryFilters({ + const filters = await getQueryFilters({ ...query, websiteId, startAt: subMinutes(startOfMinute(new Date()), REALTIME_RANGE).getTime(), diff --git a/src/app/api/reports/attribution/route.ts b/src/app/api/reports/attribution/route.ts index 8c5ab9b3..5f14d667 100644 --- a/src/app/api/reports/attribution/route.ts +++ b/src/app/api/reports/attribution/route.ts @@ -18,7 +18,7 @@ export async function POST(request: Request) { } const parameters = await setWebsiteDate(websiteId, body.parameters); - const filters = getQueryFilters(body.filters); + const filters = await getQueryFilters(body.filters); const data = await getAttribution(websiteId, parameters as AttributionParameters, filters); diff --git a/src/app/api/reports/breakdown/route.ts b/src/app/api/reports/breakdown/route.ts index a95d22b2..96a55f65 100644 --- a/src/app/api/reports/breakdown/route.ts +++ b/src/app/api/reports/breakdown/route.ts @@ -18,7 +18,7 @@ export async function POST(request: Request) { } const parameters = await setWebsiteDate(websiteId, body.parameters); - const filters = getQueryFilters(body.filters); + const filters = await getQueryFilters(body.filters); const data = await getBreakdown(websiteId, parameters as BreakdownParameters, filters); diff --git a/src/app/api/reports/funnel/route.ts b/src/app/api/reports/funnel/route.ts index db68b3c5..3df426a2 100644 --- a/src/app/api/reports/funnel/route.ts +++ b/src/app/api/reports/funnel/route.ts @@ -18,7 +18,7 @@ export async function POST(request: Request) { } const parameters = await setWebsiteDate(websiteId, body.parameters); - const filters = getQueryFilters(body.filters); + const filters = await getQueryFilters(body.filters); const data = await getFunnel(websiteId, parameters as FunnelParameters, filters); diff --git a/src/app/api/reports/goal/route.ts b/src/app/api/reports/goal/route.ts index f7d82549..450e917c 100644 --- a/src/app/api/reports/goal/route.ts +++ b/src/app/api/reports/goal/route.ts @@ -18,7 +18,7 @@ export async function POST(request: Request) { } const parameters = await setWebsiteDate(websiteId, body.parameters); - const filters = getQueryFilters(body.filters); + const filters = await getQueryFilters(body.filters); const data = await getGoal(websiteId, parameters as GoalParameters, filters); diff --git a/src/app/api/reports/journey/route.ts b/src/app/api/reports/journey/route.ts index 3c751f1b..9be1b936 100644 --- a/src/app/api/reports/journey/route.ts +++ b/src/app/api/reports/journey/route.ts @@ -17,7 +17,7 @@ export async function POST(request: Request) { return unauthorized(); } - const queryFilters = await setWebsiteDate(websiteId, getQueryFilters(filters)); + const queryFilters = await setWebsiteDate(websiteId, await getQueryFilters(filters)); const data = await getJourney(websiteId, parameters, queryFilters); diff --git a/src/app/api/reports/retention/route.ts b/src/app/api/reports/retention/route.ts index dfdde298..50a48937 100644 --- a/src/app/api/reports/retention/route.ts +++ b/src/app/api/reports/retention/route.ts @@ -17,7 +17,7 @@ export async function POST(request: Request) { return unauthorized(); } - const filters = getQueryFilters(body.filters); + const filters = await getQueryFilters(body.filters); const parameters = await setWebsiteDate(websiteId, body.parameters); const data = await getRetention(websiteId, parameters as RetentionParameters, filters); diff --git a/src/app/api/reports/revenue/route.ts b/src/app/api/reports/revenue/route.ts index c03b083b..02fc69d6 100644 --- a/src/app/api/reports/revenue/route.ts +++ b/src/app/api/reports/revenue/route.ts @@ -18,7 +18,7 @@ export async function POST(request: Request) { } const parameters = await setWebsiteDate(websiteId, body.parameters); - const filters = getQueryFilters(body.filters); + const filters = await getQueryFilters(body.filters); const data = await getRevenue(websiteId, parameters as RevenuParameters, filters); diff --git a/src/app/api/reports/utm/route.ts b/src/app/api/reports/utm/route.ts index 039f88f2..2449c492 100644 --- a/src/app/api/reports/utm/route.ts +++ b/src/app/api/reports/utm/route.ts @@ -18,7 +18,7 @@ export async function POST(request: Request) { } const parameters = await setWebsiteDate(websiteId, body.parameters); - const filters = getQueryFilters(body.filters); + const filters = await getQueryFilters(body.filters); const data = await getUTM(websiteId, parameters as UTMParameters, filters); diff --git a/src/app/api/teams/[teamId]/users/route.ts b/src/app/api/teams/[teamId]/users/route.ts index cf1b4b9b..6fde0c66 100644 --- a/src/app/api/teams/[teamId]/users/route.ts +++ b/src/app/api/teams/[teamId]/users/route.ts @@ -23,7 +23,7 @@ export async function GET(request: Request, { params }: { params: Promise<{ team return unauthorized('You must be the owner of this team.'); } - const filters = getQueryFilters(query); + const filters = await getQueryFilters(query); const users = await getTeamUsers( { diff --git a/src/app/api/users/[userId]/usage/route.ts b/src/app/api/users/[userId]/usage/route.ts index 466d97e5..677e0bd7 100644 --- a/src/app/api/users/[userId]/usage/route.ts +++ b/src/app/api/users/[userId]/usage/route.ts @@ -22,7 +22,7 @@ export async function GET(request: Request, { params }: { params: Promise<{ user } const { userId } = await params; - const filters = getQueryFilters(query); + const filters = await getQueryFilters(query); const websites = await getAllUserWebsitesIncludingTeamOwner(userId); diff --git a/src/app/api/websites/[websiteId]/event-data/events/route.ts b/src/app/api/websites/[websiteId]/event-data/events/route.ts index 6b819609..eb984207 100644 --- a/src/app/api/websites/[websiteId]/event-data/events/route.ts +++ b/src/app/api/websites/[websiteId]/event-data/events/route.ts @@ -26,7 +26,7 @@ export async function GET( } const { event } = query; - const filters = getQueryFilters(query); + const filters = await getQueryFilters(query); const data = await getEventDataEvents(websiteId, { ...filters, diff --git a/src/app/api/websites/[websiteId]/event-data/fields/route.ts b/src/app/api/websites/[websiteId]/event-data/fields/route.ts index 9448c748..34aa6162 100644 --- a/src/app/api/websites/[websiteId]/event-data/fields/route.ts +++ b/src/app/api/websites/[websiteId]/event-data/fields/route.ts @@ -25,7 +25,7 @@ export async function GET( return unauthorized(); } - const filters = getQueryFilters(query); + const filters = await getQueryFilters(query); const data = await getEventDataFields(websiteId, filters); diff --git a/src/app/api/websites/[websiteId]/event-data/properties/route.ts b/src/app/api/websites/[websiteId]/event-data/properties/route.ts index 918d51a2..ad39e37a 100644 --- a/src/app/api/websites/[websiteId]/event-data/properties/route.ts +++ b/src/app/api/websites/[websiteId]/event-data/properties/route.ts @@ -27,7 +27,7 @@ export async function GET( } const { propertyName } = query; - const filters = getQueryFilters(query); + const filters = await getQueryFilters(query); const data = await getEventDataProperties(websiteId, { ...filters, propertyName }); diff --git a/src/app/api/websites/[websiteId]/event-data/stats/route.ts b/src/app/api/websites/[websiteId]/event-data/stats/route.ts index ffc57f96..79dd0059 100644 --- a/src/app/api/websites/[websiteId]/event-data/stats/route.ts +++ b/src/app/api/websites/[websiteId]/event-data/stats/route.ts @@ -26,7 +26,7 @@ export async function GET( return unauthorized(); } - const filters = getQueryFilters(query); + const filters = await getQueryFilters(query); const data = await getEventDataStats(websiteId, filters); diff --git a/src/app/api/websites/[websiteId]/event-data/values/route.ts b/src/app/api/websites/[websiteId]/event-data/values/route.ts index 9377c4a4..1eab63b5 100644 --- a/src/app/api/websites/[websiteId]/event-data/values/route.ts +++ b/src/app/api/websites/[websiteId]/event-data/values/route.ts @@ -28,7 +28,7 @@ export async function GET( } const { eventName, propertyName } = query; - const filters = getQueryFilters(query); + const filters = await getQueryFilters(query); const data = await getEventDataValues(websiteId, { ...filters, diff --git a/src/app/api/websites/[websiteId]/events/route.ts b/src/app/api/websites/[websiteId]/events/route.ts index 1db9be97..0bf6f72c 100644 --- a/src/app/api/websites/[websiteId]/events/route.ts +++ b/src/app/api/websites/[websiteId]/events/route.ts @@ -27,7 +27,7 @@ export async function GET( return unauthorized(); } - const filters = getQueryFilters(query); + const filters = await getQueryFilters(query); const data = await getWebsiteEvents(websiteId, filters); diff --git a/src/app/api/websites/[websiteId]/events/series/route.ts b/src/app/api/websites/[websiteId]/events/series/route.ts index f8d86cd5..3d9df347 100644 --- a/src/app/api/websites/[websiteId]/events/series/route.ts +++ b/src/app/api/websites/[websiteId]/events/series/route.ts @@ -29,7 +29,7 @@ export async function GET( return unauthorized(); } - const filters = await setWebsiteDate(websiteId, getQueryFilters(query)); + const filters = await setWebsiteDate(websiteId, await getQueryFilters(query)); const data = await getEventMetrics(websiteId, filters); diff --git a/src/app/api/websites/[websiteId]/metrics/route.ts b/src/app/api/websites/[websiteId]/metrics/route.ts index 4e6c321f..c576ff26 100644 --- a/src/app/api/websites/[websiteId]/metrics/route.ts +++ b/src/app/api/websites/[websiteId]/metrics/route.ts @@ -11,10 +11,10 @@ import { VIDEO_DOMAINS, PAID_AD_PARAMS, } from '@/lib/constants'; -import { parseRequest, getQueryFilters, setWebsiteDate } from '@/lib/request'; +import { parseRequest, getQueryFilters } from '@/lib/request'; import { json, unauthorized, badRequest } from '@/lib/response'; import { getPageviewMetrics, getSessionMetrics, getChannelMetrics } from '@/queries'; -import { filterParams } from '@/lib/schema'; +import { dateRangeParams, filterParams, searchParams } from '@/lib/schema'; export async function GET( request: Request, @@ -22,11 +22,10 @@ export async function GET( ) { const schema = z.object({ type: z.string(), - startAt: z.coerce.number().int(), - endAt: z.coerce.number().int(), limit: z.coerce.number().optional(), offset: z.coerce.number().optional(), - search: z.string().optional(), + ...dateRangeParams, + ...searchParams, ...filterParams, }); @@ -37,13 +36,13 @@ export async function GET( } const { websiteId } = await params; - const { type, limit, offset, search } = query; if (!(await canViewWebsite(auth, websiteId))) { return unauthorized(); } - const filters = await setWebsiteDate(websiteId, getQueryFilters(query)); + const { type, limit, offset, search } = query; + const filters = await getQueryFilters(query, websiteId); if (search) { filters[type] = `c.${search}`; diff --git a/src/app/api/websites/[websiteId]/pageviews/route.ts b/src/app/api/websites/[websiteId]/pageviews/route.ts index 41cb1182..83175d0e 100644 --- a/src/app/api/websites/[websiteId]/pageviews/route.ts +++ b/src/app/api/websites/[websiteId]/pageviews/route.ts @@ -1,6 +1,6 @@ import { z } from 'zod'; import { canViewWebsite } from '@/lib/auth'; -import { getQueryFilters, parseRequest, setWebsiteDate } from '@/lib/request'; +import { getQueryFilters, parseRequest } from '@/lib/request'; import { dateRangeParams, filterParams } from '@/lib/schema'; import { getCompareDate } from '@/lib/date'; import { unauthorized, json } from '@/lib/response'; @@ -27,7 +27,7 @@ export async function GET( return unauthorized(); } - const filters = await setWebsiteDate(websiteId, getQueryFilters(query)); + const filters = await getQueryFilters(query, websiteId); const [pageviews, sessions] = await Promise.all([ getPageviewStats(websiteId, filters), diff --git a/src/app/api/websites/[websiteId]/session-data/properties/route.ts b/src/app/api/websites/[websiteId]/session-data/properties/route.ts index f38ec556..bdc7c53c 100644 --- a/src/app/api/websites/[websiteId]/session-data/properties/route.ts +++ b/src/app/api/websites/[websiteId]/session-data/properties/route.ts @@ -22,7 +22,7 @@ export async function GET( const { websiteId } = await params; const { propertyName } = query; - const filters = getQueryFilters(query); + const filters = await getQueryFilters(query); if (!(await canViewWebsite(auth, websiteId))) { return unauthorized(); diff --git a/src/app/api/websites/[websiteId]/session-data/values/route.ts b/src/app/api/websites/[websiteId]/session-data/values/route.ts index dd7502cd..e236a922 100644 --- a/src/app/api/websites/[websiteId]/session-data/values/route.ts +++ b/src/app/api/websites/[websiteId]/session-data/values/route.ts @@ -22,7 +22,7 @@ export async function GET( const { propertyName } = query; const { websiteId } = await params; - const filters = await setWebsiteDate(websiteId, getQueryFilters(query)); + const filters = await setWebsiteDate(websiteId, await getQueryFilters(query)); if (!(await canViewWebsite(auth, websiteId))) { return unauthorized(); diff --git a/src/app/api/websites/[websiteId]/sessions/[sessionId]/activity/route.ts b/src/app/api/websites/[websiteId]/sessions/[sessionId]/activity/route.ts index c15d1135..5daf2dfb 100644 --- a/src/app/api/websites/[websiteId]/sessions/[sessionId]/activity/route.ts +++ b/src/app/api/websites/[websiteId]/sessions/[sessionId]/activity/route.ts @@ -25,7 +25,7 @@ export async function GET( return unauthorized(); } - const filters = getQueryFilters(query); + const filters = await getQueryFilters(query); const data = await getSessionActivity(websiteId, sessionId, filters); diff --git a/src/app/api/websites/[websiteId]/sessions/route.ts b/src/app/api/websites/[websiteId]/sessions/route.ts index dba227be..e5eaf6f0 100644 --- a/src/app/api/websites/[websiteId]/sessions/route.ts +++ b/src/app/api/websites/[websiteId]/sessions/route.ts @@ -27,7 +27,7 @@ export async function GET( return unauthorized(); } - const filters = await setWebsiteDate(websiteId, getQueryFilters(query)); + const filters = await setWebsiteDate(websiteId, await getQueryFilters(query)); const data = await getWebsiteSessions(websiteId, filters); diff --git a/src/app/api/websites/[websiteId]/sessions/stats/route.ts b/src/app/api/websites/[websiteId]/sessions/stats/route.ts index 6bee799c..37289f52 100644 --- a/src/app/api/websites/[websiteId]/sessions/stats/route.ts +++ b/src/app/api/websites/[websiteId]/sessions/stats/route.ts @@ -27,7 +27,7 @@ export async function GET( return unauthorized(); } - const filters = await setWebsiteDate(websiteId, getQueryFilters(query)); + const filters = await setWebsiteDate(websiteId, await getQueryFilters(query)); const metrics = await getWebsiteSessionStats(websiteId, filters); diff --git a/src/app/api/websites/[websiteId]/sessions/weekly/route.ts b/src/app/api/websites/[websiteId]/sessions/weekly/route.ts index fb0eb1db..757e9da3 100644 --- a/src/app/api/websites/[websiteId]/sessions/weekly/route.ts +++ b/src/app/api/websites/[websiteId]/sessions/weekly/route.ts @@ -28,7 +28,7 @@ export async function GET( return unauthorized(); } - const filters = getQueryFilters(query); + const filters = await getQueryFilters(query); const data = await getWebsiteSessionsWeekly(websiteId, filters); diff --git a/src/app/api/websites/[websiteId]/stats/route.ts b/src/app/api/websites/[websiteId]/stats/route.ts index bd85c96a..924d189e 100644 --- a/src/app/api/websites/[websiteId]/stats/route.ts +++ b/src/app/api/websites/[websiteId]/stats/route.ts @@ -1,8 +1,8 @@ import { z } from 'zod'; -import { parseRequest, getQueryFilters, setWebsiteDate } from '@/lib/request'; +import { parseRequest, getQueryFilters } from '@/lib/request'; import { unauthorized, json } from '@/lib/response'; import { canViewWebsite } from '@/lib/auth'; -import { filterParams } from '@/lib/schema'; +import { dateRangeParams, filterParams } from '@/lib/schema'; import { getWebsiteStats } from '@/queries'; import { getCompareDate } from '@/lib/date'; @@ -11,9 +11,8 @@ export async function GET( { params }: { params: Promise<{ websiteId: string }> }, ) { const schema = z.object({ - startAt: z.coerce.number().int(), - endAt: z.coerce.number().int(), compare: z.string().optional(), + ...dateRangeParams, ...filterParams, }); @@ -29,7 +28,7 @@ export async function GET( return unauthorized(); } - const filters = await setWebsiteDate(websiteId, getQueryFilters(query)); + const filters = await getQueryFilters(query, websiteId); const data = await getWebsiteStats(websiteId, filters); diff --git a/src/app/api/websites/[websiteId]/values/route.ts b/src/app/api/websites/[websiteId]/values/route.ts index 8e5a88ac..937c847a 100644 --- a/src/app/api/websites/[websiteId]/values/route.ts +++ b/src/app/api/websites/[websiteId]/values/route.ts @@ -39,7 +39,7 @@ export async function GET( if (FILTER_GROUPS[type]) { values = (await getWebsiteSegments(websiteId, type)).map(segment => ({ value: segment.name })); } else { - const filters = getQueryFilters(query); + const filters = await getQueryFilters(query); values = await getValues(websiteId, FILTER_COLUMNS[type], filters); } diff --git a/src/components/common/Pager.tsx b/src/components/common/Pager.tsx index 31ff8aa5..abc0c172 100644 --- a/src/components/common/Pager.tsx +++ b/src/components/common/Pager.tsx @@ -42,16 +42,18 @@ export function Pager({ page, pageSize, count, onPageChange }: PagerProps) { total: maxPage.toLocaleString(), })} - - + + + + ); diff --git a/src/components/hooks/queries/useTeamsQuery.ts b/src/components/hooks/queries/useTeamsQuery.ts index 1e78db84..6a0ecc34 100644 --- a/src/components/hooks/queries/useTeamsQuery.ts +++ b/src/components/hooks/queries/useTeamsQuery.ts @@ -11,8 +11,8 @@ export function useTeamsQuery(params?: Record, options?: ReactQuery queryKey: ['teams:admin', { modified, ...params }], queryFn: pageParams => { return get(`/admin/teams`, { - ...params, ...pageParams, + ...params, }); }, ...options, diff --git a/src/components/hooks/queries/useUserWebsitesQuery.ts b/src/components/hooks/queries/useUserWebsitesQuery.ts index 5f37d67d..16c28518 100644 --- a/src/components/hooks/queries/useUserWebsitesQuery.ts +++ b/src/components/hooks/queries/useUserWebsitesQuery.ts @@ -17,8 +17,8 @@ export function useUserWebsitesQuery( queryKey: ['websites', { userId, teamId, modified, ...params }], queryFn: pageParams => { return get(teamId ? `/teams/${teamId}/websites` : `/users/${userId || user.id}/websites`, { - ...params, ...pageParams, + ...params, }); }, ...options, diff --git a/src/components/input/WebsiteSelect.tsx b/src/components/input/WebsiteSelect.tsx index 3a8d5519..6e32b0e1 100644 --- a/src/components/input/WebsiteSelect.tsx +++ b/src/components/input/WebsiteSelect.tsx @@ -1,28 +1,24 @@ import { useState } from 'react'; import { Select, SelectProps, ListItem } from '@umami/react-zen'; -import { useUserWebsitesQuery, useMessages } from '@/components/hooks'; +import { useUserWebsitesQuery, useWebsiteQuery, useNavigation } from '@/components/hooks'; export function WebsiteSelect({ websiteId, teamId, variant, - onSelect, ...props }: { websiteId?: string; teamId?: string; variant?: 'primary' | 'outline' | 'quiet' | 'danger' | 'zero'; - onSelect?: (key: any) => void; } & SelectProps) { - const { formatMessage, labels } = useMessages(); + const { router, renderUrl } = useNavigation(); const [search, setSearch] = useState(''); - const [selectedId, setSelectedId] = useState(websiteId); - + const { data: website } = useWebsiteQuery(websiteId); const { data, isLoading } = useUserWebsitesQuery({ teamId }, { search, pageSize: 5 }); const handleSelect = (value: any) => { - setSelectedId(value); - onSelect?.(value); + router.push(renderUrl(`/websites/${value}`)); }; const handleSearch = (value: string) => { @@ -33,14 +29,14 @@ export function WebsiteSelect({ diff --git a/src/lib/request.ts b/src/lib/request.ts index b0df42a3..3d0048f1 100644 --- a/src/lib/request.ts +++ b/src/lib/request.ts @@ -66,7 +66,7 @@ export function getRequestDateRange(query: Record) { }; } -export async function getRequestFilters(query: Record, websiteId?: string) { +export function getRequestFilters(query: Record) { const result: Record = {}; for (const key of Object.keys(FILTER_COLUMNS)) { @@ -76,20 +76,17 @@ export async function getRequestFilters(query: Record, websiteId?: } } + return result; +} + +export async function getRequestSegments(websiteId: string, query: Record) { for (const key of Object.keys(FILTER_GROUPS)) { const value = query[key]; + if (value !== undefined) { - const segment = await getWebsiteSegment(websiteId, key, value); - if (key === 'segment') { - // merge filters into result - Object.assign(result, segment.parameters); - } else { - result[key] = segment.parameters; - } + return getWebsiteSegment(websiteId, key, value); } } - - return result; } export async function setWebsiteDate(websiteId: string, data: Record) { @@ -102,13 +99,18 @@ export async function setWebsiteDate(websiteId: string, data: Record): QueryFilters { - const dateRange = getRequestDateRange(params); +export async function getQueryFilters( + params: Record, + websiteId?: string, +): Promise { + const dateRange = await setWebsiteDate(websiteId, getRequestDateRange(params)); const filters = getRequestFilters(params); + const segments = await getRequestSegments(websiteId, params); return { ...dateRange, ...filters, + ...segments, page: params?.page, pageSize: params?.page ? params?.pageSize || DEFAULT_PAGE_SIZE : undefined, orderBy: params?.orderBy, diff --git a/src/lib/types.ts b/src/lib/types.ts index e1263826..29d566a5 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -71,6 +71,9 @@ export interface FilterParams { search?: string; tag?: string; eventType?: number; + segment?: string; + cohort?: string; + compare?: string; } export interface SortParams { diff --git a/src/queries/sql/events/getEventMetrics.ts b/src/queries/sql/events/getEventMetrics.ts index 0e078ff1..06c141ba 100644 --- a/src/queries/sql/events/getEventMetrics.ts +++ b/src/queries/sql/events/getEventMetrics.ts @@ -85,7 +85,7 @@ async function clickhouseQuery( from ( select arrayJoin(event_name) as event_name, created_at - from website_event_stats_hourly website_event + from website_event_stats_hourly as website_event where website_id = {websiteId:UUID} and created_at between {startDate:DateTime64} and {endDate:DateTime64} and event_type = {eventType:UInt32} diff --git a/src/queries/sql/pageviews/getPageviewStats.ts b/src/queries/sql/pageviews/getPageviewStats.ts index 7ef51137..704dea1e 100644 --- a/src/queries/sql/pageviews/getPageviewStats.ts +++ b/src/queries/sql/pageviews/getPageviewStats.ts @@ -81,7 +81,7 @@ async function clickhouseQuery( select ${getDateSQL('website_event.created_at', unit, timezone)} as t, sum(views) as y - from website_event_stats_hourly website_event + from website_event_stats_hourly as website_event ${cohortQuery} where website_id = {websiteId:UUID} and created_at between {startDate:DateTime64} and {endDate:DateTime64} diff --git a/src/queries/sql/sessions/getSessionMetrics.ts b/src/queries/sql/sessions/getSessionMetrics.ts index 285eaa7f..7e2393cf 100644 --- a/src/queries/sql/sessions/getSessionMetrics.ts +++ b/src/queries/sql/sessions/getSessionMetrics.ts @@ -103,7 +103,7 @@ async function clickhouseQuery( ${column} x, uniq(session_id) y ${includeCountry ? ', country' : ''} - from website_event_stats_hourly website_event + from website_event_stats_hourly as website_event ${cohortQuery} where website_id = {websiteId:UUID} and created_at between {startDate:DateTime64} and {endDate:DateTime64} diff --git a/src/queries/sql/sessions/getSessionStats.ts b/src/queries/sql/sessions/getSessionStats.ts index 9911a235..258de290 100644 --- a/src/queries/sql/sessions/getSessionStats.ts +++ b/src/queries/sql/sessions/getSessionStats.ts @@ -81,7 +81,7 @@ async function clickhouseQuery( select ${getDateSQL('website_event.created_at', unit, timezone)} as t, uniq(session_id) as y - from website_event_stats_hourly website_event + from website_event_stats_hourly as website_event where website_id = {websiteId:UUID} and created_at between {startDate:DateTime64} and {endDate:DateTime64} and event_type = {eventType:UInt32} diff --git a/src/queries/sql/sessions/getWebsiteSessions.ts b/src/queries/sql/sessions/getWebsiteSessions.ts index 57dba14e..19b6cf93 100644 --- a/src/queries/sql/sessions/getWebsiteSessions.ts +++ b/src/queries/sql/sessions/getWebsiteSessions.ts @@ -94,7 +94,7 @@ async function clickhouseQuery(websiteId: string, filters: QueryFilters) { uniq(visit_id) as visits, sumIf(views, event_type = 1) as views, lastAt as createdAt - from website_event_stats_hourly website_event + from website_event_stats_hourly as website_event ${cohortQuery} where website_id = {websiteId:UUID} ${dateQuery}