Compare commits

...

11 commits

Author SHA1 Message Date
Francis Cao
bac3ab2c1a add maxwidth on event/session activity
Some checks are pending
Create docker images (cloud) / Build, push, and deploy (push) Waiting to run
Node.js CI / build (postgresql, 18.18, 10) (push) Waiting to run
2025-10-07 11:35:28 -07:00
Francis Cao
1b43aa8d37 add max width to listtable label 2025-10-07 11:22:58 -07:00
Francis Cao
d948e122ea fix resetAt logic
Some checks are pending
Create docker images (cloud) / Build, push, and deploy (push) Waiting to run
Node.js CI / build (postgresql, 18.18, 10) (push) Waiting to run
2025-10-07 09:44:59 -07:00
Francis Cao
36ae53654d Merge branch 'analytics' of https://github.com/umami-software/umami into analytics 2025-10-07 09:11:34 -07:00
Francis Cao
d440a44d96 clean-up useWebsite query params 2025-10-07 09:07:34 -07:00
Mike Cao
5a7c6a9816 Merge remote-tracking branch 'origin/analytics' into analytics 2025-10-07 01:55:33 -07:00
Mike Cao
d0aa266dce Fixed resetAt lookup. Added teams GET. 2025-10-07 01:55:19 -07:00
Francis Cao
0d67f1fdaa optimize getSessionActivity hasData 2025-10-06 22:56:34 -07:00
Francis Cao
cc3710880c optimize getWebsiteEvent hasData 2025-10-06 22:50:49 -07:00
Mike Cao
5244d8608b Fixed links/pixel lookup. 2025-10-06 22:08:33 -07:00
Mike Cao
895b41cb78 Updated login form. 2025-10-06 21:15:28 -07:00
12 changed files with 76 additions and 26 deletions

View file

@ -30,7 +30,7 @@ export function EventsTable({ data = [] }) {
<Text>
{formatMessage(row.eventName ? labels.triggeredEvent : labels.viewedPage)}
</Text>
<Text weight="bold" truncate>
<Text weight="bold" style={{ maxWidth: '300px' }} truncate>
{row.eventName || row.urlPath}
</Text>
</Row>

View file

@ -59,7 +59,9 @@ export function SessionActivity({
? formatMessage(labels.triggeredEvent)
: formatMessage(labels.viewedPage)}
</Text>
<Text weight="bold">{eventName || urlPath}</Text>
<Text weight="bold" style={{ maxWidth: '400px' }} truncate>
{eventName || urlPath}
</Text>
{hasData > 0 && <PropertiesButton websiteId={websiteId} eventId={eventId} />}
</Row>
</Row>

View file

@ -3,8 +3,27 @@ import { getRandomChars } from '@/lib/generate';
import { unauthorized, json } from '@/lib/response';
import { canCreateTeam } from '@/permissions';
import { uuid } from '@/lib/crypto';
import { parseRequest } from '@/lib/request';
import { createTeam } from '@/queries/prisma';
import { getQueryFilters, parseRequest } from '@/lib/request';
import { createTeam, getUserTeams } from '@/queries/prisma';
import { pagingParams } from '@/lib/schema';
export async function GET(request: Request) {
const schema = z.object({
...pagingParams,
});
const { auth, query, error } = await parseRequest(request, schema);
if (error) {
return error();
}
const filters = await getQueryFilters(query);
const teams = await getUserTeams(auth.user.id, filters);
return json(teams);
}
export async function POST(request: Request) {
const schema = z.object({

View file

@ -55,7 +55,12 @@ export function LoginForm() {
<PasswordField />
</FormField>
<FormButtons>
<FormSubmitButton data-test="button-submit" variant="primary" style={{ flex: 1 }}>
<FormSubmitButton
data-test="button-submit"
variant="primary"
style={{ flex: 1 }}
isDisabled={false}
>
{formatMessage(labels.login)}
</FormSubmitButton>
</FormButtons>

View file

@ -1,8 +1,8 @@
import { ReactQueryOptions } from '@/lib/types';
import { keepPreviousData } from '@tanstack/react-query';
import { useApi } from '../useApi';
import { useFilterParameters } from '../useFilterParameters';
import { useDateParameters } from '../useDateParameters';
import { ReactQueryOptions } from '@/lib/types';
import { useFilterParameters } from '../useFilterParameters';
export type WebsiteMetricsData = {
x: string;
@ -15,7 +15,7 @@ export function useWebsiteMetricsQuery(
options?: ReactQueryOptions<WebsiteMetricsData>,
) {
const { get, useQuery } = useApi();
const date = useDateParameters();
const { startAt, endAt, unit, timezone } = useDateParameters();
const filters = useFilterParameters();
return useQuery<WebsiteMetricsData>({
@ -23,14 +23,20 @@ export function useWebsiteMetricsQuery(
'websites:metrics',
{
websiteId,
...date,
startAt,
endAt,
unit,
timezone,
...filters,
...params,
},
],
queryFn: async () =>
get(`/websites/${websiteId}/metrics`, {
...date,
startAt,
endAt,
unit,
timezone,
...filters,
...params,
}),

View file

@ -13,12 +13,23 @@ export function useWebsitePageviewsQuery(
options?: ReactQueryOptions<WebsitePageviewsData>,
) {
const { get, useQuery } = useApi();
const date = useDateParameters();
const { startAt, endAt, unit, timezone } = useDateParameters();
const queryParams = useFilterParameters();
return useQuery<WebsitePageviewsData>({
queryKey: ['websites:pageviews', { websiteId, compare, ...date, ...queryParams }],
queryFn: () => get(`/websites/${websiteId}/pageviews`, { compare, ...date, ...queryParams }),
queryKey: [
'websites:pageviews',
{ websiteId, compare, startAt, endAt, unit, timezone, ...queryParams },
],
queryFn: () =>
get(`/websites/${websiteId}/pageviews`, {
compare,
startAt,
endAt,
unit,
timezone,
...queryParams,
}),
enabled: !!websiteId,
...options,
});

View file

@ -23,12 +23,13 @@ export function useWebsiteStatsQuery(
options?: UseQueryOptions<WebsiteStatsData, Error, WebsiteStatsData>,
) {
const { get, useQuery } = useApi();
const date = useDateParameters();
const { startAt, endAt, unit, timezone } = useDateParameters();
const filters = useFilterParameters();
return useQuery<WebsiteStatsData>({
queryKey: ['websites:stats', { websiteId, ...date, ...filters }],
queryFn: () => get(`/websites/${websiteId}/stats`, { ...date, ...filters }),
queryKey: ['websites:stats', { websiteId, startAt, endAt, unit, timezone, ...filters }],
queryFn: () =>
get(`/websites/${websiteId}/stats`, { startAt, endAt, unit, timezone, ...filters }),
enabled: !!websiteId,
...options,
});

View file

@ -117,7 +117,9 @@ const AnimatedRow = ({
gap
>
<Row alignItems="center">
<Text>{label}</Text>
<Text truncate={true} style={{ maxWidth: '400px' }}>
{label}
</Text>
</Row>
<Row alignItems="center" height="30px" justifyContent="flex-end">
{change}

View file

@ -83,7 +83,7 @@ export function getRequestFilters(query: Record<string, any>) {
export async function setWebsiteDate(websiteId: string, data: Record<string, any>) {
const website = await fetchWebsite(websiteId);
if (website) {
if (website?.resetAt) {
data.startDate = maxDate(data.startDate, new Date(website?.resetAt));
}

View file

@ -1,7 +1,7 @@
import { Auth } from '@/lib/types';
import { PERMISSIONS } from '@/lib/constants';
import { hasPermission } from '@/lib/auth';
import { getTeamUser, getWebsite } from '@/queries/prisma';
import { getLink, getPixel, getTeamUser, getWebsite } from '@/queries/prisma';
export async function canViewWebsite({ user, shareToken }: Auth, websiteId: string) {
if (user?.isAdmin) {
@ -13,17 +13,21 @@ export async function canViewWebsite({ user, shareToken }: Auth, websiteId: stri
}
const website = await getWebsite(websiteId);
const link = await getLink(websiteId);
const pixel = await getPixel(websiteId);
if (!website) {
const entity = website || link || pixel;
if (!entity) {
return false;
}
if (website.userId) {
return user.id === website.userId;
if (entity.userId) {
return user.id === entity.userId;
}
if (website.teamId) {
const teamUser = await getTeamUser(website.teamId, user.id);
if (entity.teamId) {
const teamUser = await getTeamUser(entity.teamId, user.id);
return !!teamUser;
}

View file

@ -96,7 +96,7 @@ async function clickhouseQuery(websiteId: string, filters: QueryFilters) {
page_title as pageTitle,
event_type as eventType,
event_name as eventName,
event_id IN (SELECT event_id FROM event_data) as hasData
event_id IN (SELECT event_id FROM event_data where website_id = {websiteId:UUID}) as hasData
from website_event
${cohortQuery}
where website_id = {websiteId:UUID}

View file

@ -57,7 +57,7 @@ async function clickhouseQuery(websiteId: string, sessionId: string, filters: Qu
event_type as eventType,
event_name as eventName,
visit_id as visitId,
event_id IN (SELECT event_id FROM event_data) AS hasData
event_id IN (SELECT event_id FROM event_data where website_id = {websiteId:UUID} and session_id = {sessionId:UUID}) AS hasData
from website_event e
where e.website_id = {websiteId:UUID}
and e.session_id = {sessionId:UUID}