mirror of
https://github.com/umami-software/umami.git
synced 2026-02-08 06:37:18 +01:00
Merge branch 'dev' into search-formatted-metrics
This commit is contained in:
commit
4ab8b1ff91
807 changed files with 45367 additions and 8474 deletions
|
|
@ -1,10 +1,18 @@
|
|||
export * from './queries/useApi';
|
||||
export * from './queries/useConfig';
|
||||
export * from './queries/useFilterQuery';
|
||||
export * from './queries/useEventDataEvents';
|
||||
export * from './queries/useEventDataProperties';
|
||||
export * from './queries/useEventDataValues';
|
||||
export * from './queries/useLogin';
|
||||
export * from './queries/useRealtime';
|
||||
export * from './queries/useReport';
|
||||
export * from './queries/useReports';
|
||||
export * from './queries/useSessionActivity';
|
||||
export * from './queries/useSessionData';
|
||||
export * from './queries/useSessionDataProperties';
|
||||
export * from './queries/useSessionDataValues';
|
||||
export * from './queries/useWebsiteSession';
|
||||
export * from './queries/useWebsiteSessions';
|
||||
export * from './queries/useWebsiteSessionsWeekly';
|
||||
export * from './queries/useShareToken';
|
||||
export * from './queries/useTeam';
|
||||
export * from './queries/useTeams';
|
||||
|
|
@ -15,8 +23,10 @@ export * from './queries/useUsers';
|
|||
export * from './queries/useWebsite';
|
||||
export * from './queries/useWebsites';
|
||||
export * from './queries/useWebsiteEvents';
|
||||
export * from './queries/useWebsiteEventsSeries';
|
||||
export * from './queries/useWebsiteMetrics';
|
||||
export * from './queries/useWebsiteValues';
|
||||
export * from './useApi';
|
||||
export * from './useCountryNames';
|
||||
export * from './useDateRange';
|
||||
export * from './useDocumentClick';
|
||||
|
|
@ -30,6 +40,8 @@ export * from './useLocale';
|
|||
export * from './useMessages';
|
||||
export * from './useModified';
|
||||
export * from './useNavigation';
|
||||
export * from './usePagedQuery';
|
||||
export * from './useRegionNames';
|
||||
export * from './useSticky';
|
||||
export * from './useTeamUrl';
|
||||
export * from './useTheme';
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useEffect } from 'react';
|
||||
import useStore, { setConfig } from 'store/app';
|
||||
import { useApi } from './useApi';
|
||||
import { useApi } from '../useApi';
|
||||
|
||||
let loading = false;
|
||||
|
||||
|
|
|
|||
20
src/components/hooks/queries/useEventDataEvents.ts
Normal file
20
src/components/hooks/queries/useEventDataEvents.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import { useApi } from '../useApi';
|
||||
import { UseQueryOptions } from '@tanstack/react-query';
|
||||
import { useFilterParams } from '../useFilterParams';
|
||||
|
||||
export function useEventDataEvents(
|
||||
websiteId: string,
|
||||
options?: Omit<UseQueryOptions, 'queryKey' | 'queryFn'>,
|
||||
) {
|
||||
const { get, useQuery } = useApi();
|
||||
const params = useFilterParams(websiteId);
|
||||
|
||||
return useQuery({
|
||||
queryKey: ['websites:event-data:events', { websiteId, ...params }],
|
||||
queryFn: () => get(`/websites/${websiteId}/event-data/events`, { ...params }),
|
||||
enabled: !!websiteId,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
export default useEventDataEvents;
|
||||
20
src/components/hooks/queries/useEventDataProperties.ts
Normal file
20
src/components/hooks/queries/useEventDataProperties.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import { UseQueryOptions } from '@tanstack/react-query';
|
||||
import { useApi } from '../useApi';
|
||||
import { useFilterParams } from '../useFilterParams';
|
||||
|
||||
export function useEventDataProperties(
|
||||
websiteId: string,
|
||||
options?: Omit<UseQueryOptions, 'queryKey' | 'queryFn'>,
|
||||
) {
|
||||
const { get, useQuery } = useApi();
|
||||
const params = useFilterParams(websiteId);
|
||||
|
||||
return useQuery<any>({
|
||||
queryKey: ['websites:event-data:properties', { websiteId, ...params }],
|
||||
queryFn: () => get(`/websites/${websiteId}/event-data/properties`, { ...params }),
|
||||
enabled: !!websiteId,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
export default useEventDataProperties;
|
||||
23
src/components/hooks/queries/useEventDataValues.ts
Normal file
23
src/components/hooks/queries/useEventDataValues.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import { UseQueryOptions } from '@tanstack/react-query';
|
||||
import { useApi } from '../useApi';
|
||||
import { useFilterParams } from '../useFilterParams';
|
||||
|
||||
export function useEventDataValues(
|
||||
websiteId: string,
|
||||
eventName: string,
|
||||
propertyName: string,
|
||||
options?: Omit<UseQueryOptions, 'queryKey' | 'queryFn'>,
|
||||
) {
|
||||
const { get, useQuery } = useApi();
|
||||
const params = useFilterParams(websiteId);
|
||||
|
||||
return useQuery<any>({
|
||||
queryKey: ['websites:event-data:values', { websiteId, eventName, propertyName, ...params }],
|
||||
queryFn: () =>
|
||||
get(`/websites/${websiteId}/event-data/values`, { ...params, eventName, propertyName }),
|
||||
enabled: !!(websiteId && propertyName),
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
export default useEventDataValues;
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import useStore, { setUser } from 'store/app';
|
||||
import useApi from './useApi';
|
||||
import { UseQueryResult } from '@tanstack/react-query';
|
||||
import useStore, { setUser } from 'store/app';
|
||||
import { useApi } from '../useApi';
|
||||
|
||||
const selector = (state: { user: any }) => state.user;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,87 +1,21 @@
|
|||
import { useMemo, useRef } from 'react';
|
||||
import { useTimezone } from 'components/hooks';
|
||||
import { REALTIME_INTERVAL } from 'lib/constants';
|
||||
import { RealtimeData } from 'lib/types';
|
||||
import { useApi } from 'components/hooks';
|
||||
import { REALTIME_INTERVAL, REALTIME_RANGE } from 'lib/constants';
|
||||
import { startOfMinute, subMinutes } from 'date-fns';
|
||||
import { percentFilter } from 'lib/filters';
|
||||
import thenby from 'thenby';
|
||||
|
||||
function mergeData(state = [], data = [], time: number) {
|
||||
const ids = state.map(({ id }) => id);
|
||||
return state
|
||||
.concat(data.filter(({ id }) => !ids.includes(id)))
|
||||
.filter(({ timestamp }) => timestamp >= time);
|
||||
}
|
||||
import { useApi } from '../useApi';
|
||||
|
||||
export function useRealtime(websiteId: string) {
|
||||
const currentData = useRef({
|
||||
pageviews: [],
|
||||
sessions: [],
|
||||
events: [],
|
||||
countries: [],
|
||||
visitors: [],
|
||||
timestamp: 0,
|
||||
});
|
||||
const { get, useQuery } = useApi();
|
||||
const { timezone } = useTimezone();
|
||||
const { data, isLoading, error } = useQuery<RealtimeData>({
|
||||
queryKey: ['realtime', websiteId],
|
||||
queryKey: ['realtime', { websiteId, timezone }],
|
||||
queryFn: async () => {
|
||||
const state = currentData.current;
|
||||
const data = await get(`/realtime/${websiteId}`, { startAt: state?.timestamp || 0 });
|
||||
const date = subMinutes(startOfMinute(new Date()), REALTIME_RANGE);
|
||||
const time = date.getTime();
|
||||
const { pageviews, sessions, events, timestamp } = data;
|
||||
|
||||
return {
|
||||
pageviews: mergeData(state?.pageviews, pageviews, time),
|
||||
sessions: mergeData(state?.sessions, sessions, time),
|
||||
events: mergeData(state?.events, events, time),
|
||||
timestamp,
|
||||
};
|
||||
return get(`/realtime/${websiteId}`, { timezone });
|
||||
},
|
||||
enabled: !!websiteId,
|
||||
refetchInterval: REALTIME_INTERVAL,
|
||||
});
|
||||
|
||||
const realtimeData: RealtimeData = useMemo(() => {
|
||||
if (!data) {
|
||||
return { pageviews: [], sessions: [], events: [], countries: [], visitors: [], timestamp: 0 };
|
||||
}
|
||||
|
||||
data.countries = percentFilter(
|
||||
data.sessions
|
||||
.reduce((arr, data) => {
|
||||
if (!arr.find(({ id }) => id === data.id)) {
|
||||
return arr.concat(data);
|
||||
}
|
||||
return arr;
|
||||
}, [])
|
||||
.reduce((arr: { x: any; y: number }[], { country }: any) => {
|
||||
if (country) {
|
||||
const row = arr.find(({ x }) => x === country);
|
||||
|
||||
if (!row) {
|
||||
arr.push({ x: country, y: 1 });
|
||||
} else {
|
||||
row.y += 1;
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
}, [])
|
||||
.sort(thenby.firstBy('y', -1)),
|
||||
);
|
||||
|
||||
data.visitors = data.sessions.reduce((arr, val) => {
|
||||
if (!arr.find(({ id }) => id === val.id)) {
|
||||
return arr.concat(val);
|
||||
}
|
||||
return arr;
|
||||
}, []);
|
||||
|
||||
return data;
|
||||
}, [data]);
|
||||
|
||||
return { data: realtimeData, isLoading, error };
|
||||
return { data, isLoading, error };
|
||||
}
|
||||
|
||||
export default useRealtime;
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import { produce } from 'immer';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useApi } from './useApi';
|
||||
import { useApi } from '../useApi';
|
||||
import { useTimezone } from '../useTimezone';
|
||||
import { useMessages } from '../useMessages';
|
||||
|
||||
export function useReport(
|
||||
reportId: string,
|
||||
defaultParameters: { type: string; parameters: { [key: string]: any } },
|
||||
defaultParameters?: { type: string; parameters: { [key: string]: any } },
|
||||
) {
|
||||
const [report, setReport] = useState(null);
|
||||
const [isRunning, setIsRunning] = useState(false);
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import useApi from './useApi';
|
||||
import useFilterQuery from './useFilterQuery';
|
||||
import useApi from '../useApi';
|
||||
import usePagedQuery from '../usePagedQuery';
|
||||
import useModified from '../useModified';
|
||||
|
||||
export function useReports({ websiteId, teamId }: { websiteId?: string; teamId?: string }) {
|
||||
const { modified } = useModified(`reports`);
|
||||
const { get, del, useMutation } = useApi();
|
||||
const queryResult = useFilterQuery({
|
||||
const queryResult = usePagedQuery({
|
||||
queryKey: ['reports', { websiteId, teamId, modified }],
|
||||
queryFn: (params: any) => {
|
||||
return get('/reports', { websiteId, teamId, ...params });
|
||||
|
|
|
|||
18
src/components/hooks/queries/useRevenueValues.ts
Normal file
18
src/components/hooks/queries/useRevenueValues.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import { useApi } from '../useApi';
|
||||
|
||||
export function useRevenueValues(websiteId: string, startDate: Date, endDate: Date) {
|
||||
const { get, useQuery } = useApi();
|
||||
|
||||
return useQuery({
|
||||
queryKey: ['revenue:values', { websiteId, startDate, endDate }],
|
||||
queryFn: () =>
|
||||
get(`/reports/revenue`, {
|
||||
websiteId,
|
||||
startDate,
|
||||
endDate,
|
||||
}),
|
||||
enabled: !!(websiteId && startDate && endDate),
|
||||
});
|
||||
}
|
||||
|
||||
export default useRevenueValues;
|
||||
21
src/components/hooks/queries/useSessionActivity.ts
Normal file
21
src/components/hooks/queries/useSessionActivity.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import { useApi } from '../useApi';
|
||||
|
||||
export function useSessionActivity(
|
||||
websiteId: string,
|
||||
sessionId: string,
|
||||
startDate: Date,
|
||||
endDate: Date,
|
||||
) {
|
||||
const { get, useQuery } = useApi();
|
||||
|
||||
return useQuery({
|
||||
queryKey: ['session:activity', { websiteId, sessionId, startDate, endDate }],
|
||||
queryFn: () => {
|
||||
return get(`/websites/${websiteId}/sessions/${sessionId}/activity`, {
|
||||
startAt: +new Date(startDate),
|
||||
endAt: +new Date(endDate),
|
||||
});
|
||||
},
|
||||
enabled: Boolean(websiteId && sessionId && startDate && endDate),
|
||||
});
|
||||
}
|
||||
12
src/components/hooks/queries/useSessionData.ts
Normal file
12
src/components/hooks/queries/useSessionData.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { useApi } from '../useApi';
|
||||
|
||||
export function useSessionData(websiteId: string, sessionId: string) {
|
||||
const { get, useQuery } = useApi();
|
||||
|
||||
return useQuery({
|
||||
queryKey: ['session:data', { websiteId, sessionId }],
|
||||
queryFn: () => {
|
||||
return get(`/websites/${websiteId}/sessions/${sessionId}/properties`, { websiteId });
|
||||
},
|
||||
});
|
||||
}
|
||||
20
src/components/hooks/queries/useSessionDataProperties.ts
Normal file
20
src/components/hooks/queries/useSessionDataProperties.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import { useApi } from '../useApi';
|
||||
import { UseQueryOptions } from '@tanstack/react-query';
|
||||
import { useFilterParams } from '../useFilterParams';
|
||||
|
||||
export function useSessionDataProperties(
|
||||
websiteId: string,
|
||||
options?: Omit<UseQueryOptions, 'queryKey' | 'queryFn'>,
|
||||
) {
|
||||
const { get, useQuery } = useApi();
|
||||
const params = useFilterParams(websiteId);
|
||||
|
||||
return useQuery<any>({
|
||||
queryKey: ['websites:event-data:properties', { websiteId, ...params }],
|
||||
queryFn: () => get(`/websites/${websiteId}/session-data/properties`, { ...params }),
|
||||
enabled: !!websiteId,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
export default useSessionDataProperties;
|
||||
21
src/components/hooks/queries/useSessionDataValues.ts
Normal file
21
src/components/hooks/queries/useSessionDataValues.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import { useApi } from '../useApi';
|
||||
import { UseQueryOptions } from '@tanstack/react-query';
|
||||
import { useFilterParams } from '../useFilterParams';
|
||||
|
||||
export function useSessionDataValues(
|
||||
websiteId: string,
|
||||
propertyName: string,
|
||||
options?: Omit<UseQueryOptions, 'queryKey' | 'queryFn'>,
|
||||
) {
|
||||
const { get, useQuery } = useApi();
|
||||
const params = useFilterParams(websiteId);
|
||||
|
||||
return useQuery<any>({
|
||||
queryKey: ['websites:session-data:values', { websiteId, propertyName, ...params }],
|
||||
queryFn: () => get(`/websites/${websiteId}/session-data/values`, { ...params, propertyName }),
|
||||
enabled: !!(websiteId && propertyName),
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
export default useSessionDataValues;
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import useStore, { setShareToken } from 'store/app';
|
||||
import useApi from './useApi';
|
||||
import { useApi } from '../useApi';
|
||||
|
||||
const selector = (state: { shareToken: string }) => state.shareToken;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import useApi from './useApi';
|
||||
import { useApi } from '../useApi';
|
||||
|
||||
export function useTeam(teamId: string) {
|
||||
const { get, useQuery } = useApi();
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import useApi from './useApi';
|
||||
import useFilterQuery from './useFilterQuery';
|
||||
import { useApi } from '../useApi';
|
||||
import usePagedQuery from '../usePagedQuery';
|
||||
import useModified from '../useModified';
|
||||
|
||||
export function useTeamMembers(teamId: string) {
|
||||
const { get } = useApi();
|
||||
const { modified } = useModified(`teams:members`);
|
||||
|
||||
return useFilterQuery({
|
||||
return usePagedQuery({
|
||||
queryKey: ['teams:members', { teamId, modified }],
|
||||
queryFn: (params: any) => {
|
||||
return get(`/teams/${teamId}/users`, params);
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import useApi from './useApi';
|
||||
import useFilterQuery from './useFilterQuery';
|
||||
import { useApi } from '../useApi';
|
||||
import { usePagedQuery } from '../usePagedQuery';
|
||||
import useModified from '../useModified';
|
||||
|
||||
export function useTeamWebsites(teamId: string) {
|
||||
const { get } = useApi();
|
||||
const { modified } = useModified(`websites`);
|
||||
|
||||
return useFilterQuery({
|
||||
return usePagedQuery({
|
||||
queryKey: ['teams:websites', { teamId, modified }],
|
||||
queryFn: (params: any) => {
|
||||
return get(`/teams/${teamId}/websites`, params);
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import useApi from './useApi';
|
||||
import useFilterQuery from './useFilterQuery';
|
||||
import { useApi } from '../useApi';
|
||||
import { usePagedQuery } from '../usePagedQuery';
|
||||
import useModified from '../useModified';
|
||||
|
||||
export function useTeams(userId: string) {
|
||||
const { get } = useApi();
|
||||
const { modified } = useModified(`teams`);
|
||||
|
||||
return useFilterQuery({
|
||||
return usePagedQuery({
|
||||
queryKey: ['teams', { userId, modified }],
|
||||
queryFn: (params: any) => {
|
||||
return get(`/users/${userId}/teams`, params);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import useApi from './useApi';
|
||||
import { useApi } from '../useApi';
|
||||
|
||||
export function useUser(userId: string, options?: { [key: string]: any }) {
|
||||
const { get, useQuery } = useApi();
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import useApi from './useApi';
|
||||
import useFilterQuery from './useFilterQuery';
|
||||
import { useApi } from '../useApi';
|
||||
import { usePagedQuery } from '../usePagedQuery';
|
||||
import useModified from '../useModified';
|
||||
|
||||
export function useUsers() {
|
||||
const { get } = useApi();
|
||||
const { modified } = useModified(`users`);
|
||||
|
||||
return useFilterQuery({
|
||||
return usePagedQuery({
|
||||
queryKey: ['users', { modified }],
|
||||
queryFn: (params: any) => {
|
||||
return get('/admin/users', {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import useApi from './useApi';
|
||||
import { useApi } from '../useApi';
|
||||
|
||||
export function useWebsite(websiteId: string, options?: { [key: string]: any }) {
|
||||
const { get, useQuery } = useApi();
|
||||
|
|
|
|||
|
|
@ -1,33 +1,19 @@
|
|||
import useApi from './useApi';
|
||||
import { useApi } from '../useApi';
|
||||
import { UseQueryOptions } from '@tanstack/react-query';
|
||||
import { useDateRange, useNavigation, useTimezone } from 'components/hooks';
|
||||
import { zonedTimeToUtc } from 'date-fns-tz';
|
||||
import { useFilterParams } from '../useFilterParams';
|
||||
import { usePagedQuery } from '../usePagedQuery';
|
||||
|
||||
export function useWebsiteEvents(
|
||||
websiteId: string,
|
||||
options?: Omit<UseQueryOptions, 'queryKey' | 'queryFn'>,
|
||||
) {
|
||||
const { get, useQuery } = useApi();
|
||||
const [dateRange] = useDateRange(websiteId);
|
||||
const { startDate, endDate, unit, offset } = dateRange;
|
||||
const { timezone } = useTimezone();
|
||||
const {
|
||||
query: { url, event },
|
||||
} = useNavigation();
|
||||
const { get } = useApi();
|
||||
const params = useFilterParams(websiteId);
|
||||
|
||||
const params = {
|
||||
startAt: +zonedTimeToUtc(startDate, timezone),
|
||||
endAt: +zonedTimeToUtc(endDate, timezone),
|
||||
unit,
|
||||
offset,
|
||||
timezone,
|
||||
url,
|
||||
event,
|
||||
};
|
||||
|
||||
return useQuery({
|
||||
queryKey: ['events', { ...params }],
|
||||
queryFn: () => get(`/websites/${websiteId}/events`, { ...params }),
|
||||
return usePagedQuery({
|
||||
queryKey: ['websites:events', { websiteId, ...params }],
|
||||
queryFn: pageParams =>
|
||||
get(`/websites/${websiteId}/events`, { ...params, ...pageParams, pageSize: 20 }),
|
||||
enabled: !!websiteId,
|
||||
...options,
|
||||
});
|
||||
|
|
|
|||
20
src/components/hooks/queries/useWebsiteEventsSeries.ts
Normal file
20
src/components/hooks/queries/useWebsiteEventsSeries.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import { useApi } from '../useApi';
|
||||
import { UseQueryOptions } from '@tanstack/react-query';
|
||||
import { useFilterParams } from '../useFilterParams';
|
||||
|
||||
export function useWebsiteEventsSeries(
|
||||
websiteId: string,
|
||||
options?: Omit<UseQueryOptions, 'queryKey' | 'queryFn'>,
|
||||
) {
|
||||
const { get, useQuery } = useApi();
|
||||
const params = useFilterParams(websiteId);
|
||||
|
||||
return useQuery({
|
||||
queryKey: ['websites:events:series', { websiteId, ...params }],
|
||||
queryFn: () => get(`/websites/${websiteId}/events/series`, { ...params }),
|
||||
enabled: !!websiteId,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
export default useWebsiteEventsSeries;
|
||||
|
|
@ -1,12 +1,16 @@
|
|||
import useApi from './useApi';
|
||||
import { UseQueryOptions } from '@tanstack/react-query';
|
||||
import { useApi } from '../useApi';
|
||||
import { useFilterParams } from '../useFilterParams';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
|
||||
export function useWebsiteMetrics(
|
||||
websiteId: string,
|
||||
params?: { [key: string]: any },
|
||||
queryParams: { type: string; limit?: number; search?: string; startAt?: number; endAt?: number },
|
||||
options?: Omit<UseQueryOptions & { onDataLoad?: (data: any) => void }, 'queryKey' | 'queryFn'>,
|
||||
) {
|
||||
const { get, useQuery } = useApi();
|
||||
const params = useFilterParams(websiteId);
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
return useQuery({
|
||||
queryKey: [
|
||||
|
|
@ -14,21 +18,21 @@ export function useWebsiteMetrics(
|
|||
{
|
||||
websiteId,
|
||||
...params,
|
||||
...queryParams,
|
||||
},
|
||||
],
|
||||
queryFn: async () => {
|
||||
const filters = { ...params };
|
||||
|
||||
filters[params.type] = undefined;
|
||||
|
||||
const data = await get(`/websites/${websiteId}/metrics`, {
|
||||
...filters,
|
||||
...params,
|
||||
[searchParams.get('view')]: undefined,
|
||||
...queryParams,
|
||||
});
|
||||
|
||||
options?.onDataLoad?.(data);
|
||||
|
||||
return data;
|
||||
},
|
||||
enabled: !!websiteId,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,34 +1,19 @@
|
|||
import { zonedTimeToUtc } from 'date-fns-tz';
|
||||
import { useApi, useDateRange, useNavigation, useTimezone } from 'components/hooks';
|
||||
import { UseQueryOptions } from '@tanstack/react-query';
|
||||
import { useApi } from '../useApi';
|
||||
import { useFilterParams } from '..//useFilterParams';
|
||||
|
||||
export function useWebsitePageviews(websiteId: string, options?: { [key: string]: string }) {
|
||||
export function useWebsitePageviews(
|
||||
websiteId: string,
|
||||
compare?: string,
|
||||
options?: Omit<UseQueryOptions, 'queryKey' | 'queryFn'>,
|
||||
) {
|
||||
const { get, useQuery } = useApi();
|
||||
const [dateRange] = useDateRange(websiteId);
|
||||
const { startDate, endDate, unit } = dateRange;
|
||||
const { timezone } = useTimezone();
|
||||
const {
|
||||
query: { url, referrer, os, browser, device, country, region, city, title },
|
||||
} = useNavigation();
|
||||
|
||||
const params = {
|
||||
startAt: +zonedTimeToUtc(startDate, timezone),
|
||||
endAt: +zonedTimeToUtc(endDate, timezone),
|
||||
unit,
|
||||
timezone,
|
||||
url,
|
||||
referrer,
|
||||
os,
|
||||
browser,
|
||||
device,
|
||||
country,
|
||||
region,
|
||||
city,
|
||||
title,
|
||||
};
|
||||
const params = useFilterParams(websiteId);
|
||||
|
||||
return useQuery({
|
||||
queryKey: ['websites:pageviews', { websiteId, ...params }],
|
||||
queryFn: () => get(`/websites/${websiteId}/pageviews`, params),
|
||||
queryKey: ['websites:pageviews', { websiteId, ...params, compare }],
|
||||
queryFn: () => get(`/websites/${websiteId}/pageviews`, { ...params, compare }),
|
||||
enabled: !!websiteId,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
14
src/components/hooks/queries/useWebsiteSession.ts
Normal file
14
src/components/hooks/queries/useWebsiteSession.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { useApi } from '../useApi';
|
||||
|
||||
export function useWebsiteSession(websiteId: string, sessionId: string) {
|
||||
const { get, useQuery } = useApi();
|
||||
|
||||
return useQuery({
|
||||
queryKey: ['session', { websiteId, sessionId }],
|
||||
queryFn: () => {
|
||||
return get(`/websites/${websiteId}/sessions/${sessionId}`);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export default useWebsiteSession;
|
||||
16
src/components/hooks/queries/useWebsiteSessionStats.ts
Normal file
16
src/components/hooks/queries/useWebsiteSessionStats.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import { useApi } from '../useApi';
|
||||
import { useFilterParams } from '../useFilterParams';
|
||||
|
||||
export function useWebsiteSessionStats(websiteId: string, options?: { [key: string]: string }) {
|
||||
const { get, useQuery } = useApi();
|
||||
const params = useFilterParams(websiteId);
|
||||
|
||||
return useQuery({
|
||||
queryKey: ['sessions:stats', { websiteId, ...params }],
|
||||
queryFn: () => get(`/websites/${websiteId}/sessions/stats`, { ...params }),
|
||||
enabled: !!websiteId,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
export default useWebsiteSessionStats;
|
||||
24
src/components/hooks/queries/useWebsiteSessions.ts
Normal file
24
src/components/hooks/queries/useWebsiteSessions.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import { useApi } from '../useApi';
|
||||
import { usePagedQuery } from '../usePagedQuery';
|
||||
import useModified from '../useModified';
|
||||
import { useFilterParams } from 'components/hooks/useFilterParams';
|
||||
|
||||
export function useWebsiteSessions(websiteId: string, params?: { [key: string]: string | number }) {
|
||||
const { get } = useApi();
|
||||
const { modified } = useModified(`sessions`);
|
||||
const filters = useFilterParams(websiteId);
|
||||
|
||||
return usePagedQuery({
|
||||
queryKey: ['sessions', { websiteId, modified, ...params, ...filters }],
|
||||
queryFn: (data: any) => {
|
||||
return get(`/websites/${websiteId}/sessions`, {
|
||||
...data,
|
||||
...params,
|
||||
...filters,
|
||||
pageSize: 20,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export default useWebsiteSessions;
|
||||
24
src/components/hooks/queries/useWebsiteSessionsWeekly.ts
Normal file
24
src/components/hooks/queries/useWebsiteSessionsWeekly.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import { useApi } from '../useApi';
|
||||
import useModified from '../useModified';
|
||||
import { useFilterParams } from 'components/hooks/useFilterParams';
|
||||
|
||||
export function useWebsiteSessionsWeekly(
|
||||
websiteId: string,
|
||||
params?: { [key: string]: string | number },
|
||||
) {
|
||||
const { get, useQuery } = useApi();
|
||||
const { modified } = useModified(`sessions`);
|
||||
const filters = useFilterParams(websiteId);
|
||||
|
||||
return useQuery({
|
||||
queryKey: ['sessions', { websiteId, modified, ...params, ...filters }],
|
||||
queryFn: () => {
|
||||
return get(`/websites/${websiteId}/sessions/weekly`, {
|
||||
...params,
|
||||
...filters,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export default useWebsiteSessionsWeekly;
|
||||
|
|
@ -1,30 +1,18 @@
|
|||
import { useApi, useDateRange, useNavigation } from 'components/hooks';
|
||||
import { useApi } from '../useApi';
|
||||
import { useFilterParams } from '../useFilterParams';
|
||||
|
||||
export function useWebsiteStats(websiteId: string, options?: { [key: string]: string }) {
|
||||
export function useWebsiteStats(
|
||||
websiteId: string,
|
||||
compare?: string,
|
||||
options?: { [key: string]: string },
|
||||
) {
|
||||
const { get, useQuery } = useApi();
|
||||
const [dateRange] = useDateRange(websiteId);
|
||||
const { startDate, endDate } = dateRange;
|
||||
const {
|
||||
query: { url, referrer, title, os, browser, device, country, region, city },
|
||||
} = useNavigation();
|
||||
|
||||
const params = {
|
||||
startAt: +startDate,
|
||||
endAt: +endDate,
|
||||
url,
|
||||
referrer,
|
||||
title,
|
||||
os,
|
||||
browser,
|
||||
device,
|
||||
country,
|
||||
region,
|
||||
city,
|
||||
};
|
||||
const params = useFilterParams(websiteId);
|
||||
|
||||
return useQuery({
|
||||
queryKey: ['websites:stats', { websiteId, ...params }],
|
||||
queryFn: () => get(`/websites/${websiteId}/stats`, params),
|
||||
queryKey: ['websites:stats', { websiteId, ...params, compare }],
|
||||
queryFn: () => get(`/websites/${websiteId}/stats`, { ...params, compare }),
|
||||
enabled: !!websiteId,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import { useApi } from 'components/hooks';
|
||||
import { useApi } from '../useApi';
|
||||
import { useCountryNames, useRegionNames } from 'components/hooks';
|
||||
import useLocale from '../useLocale';
|
||||
|
||||
export function useWebsiteValues({
|
||||
websiteId,
|
||||
|
|
@ -14,6 +16,36 @@ export function useWebsiteValues({
|
|||
search?: string;
|
||||
}) {
|
||||
const { get, useQuery } = useApi();
|
||||
const { locale } = useLocale();
|
||||
const { countryNames } = useCountryNames(locale);
|
||||
const { regionNames } = useRegionNames(locale);
|
||||
|
||||
const names = {
|
||||
country: countryNames,
|
||||
region: regionNames,
|
||||
};
|
||||
|
||||
const getSearch = (type: string, value: string) => {
|
||||
if (value) {
|
||||
const values = names[type];
|
||||
|
||||
if (values) {
|
||||
return (
|
||||
Object.keys(values)
|
||||
.reduce((arr: string[], key: string) => {
|
||||
if (values[key].toLowerCase().includes(value.toLowerCase())) {
|
||||
return arr.concat(key);
|
||||
}
|
||||
return arr;
|
||||
}, [])
|
||||
.slice(0, 5)
|
||||
.join(',') || value
|
||||
);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
return useQuery({
|
||||
queryKey: ['websites:values', { websiteId, type, startDate, endDate, search }],
|
||||
|
|
@ -22,7 +54,7 @@ export function useWebsiteValues({
|
|||
type,
|
||||
startAt: +startDate,
|
||||
endAt: +endDate,
|
||||
search,
|
||||
search: getSearch(type, search),
|
||||
}),
|
||||
enabled: !!(websiteId && type && startDate && endDate),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useApi } from './useApi';
|
||||
import { useFilterQuery } from './useFilterQuery';
|
||||
import { useApi } from '../useApi';
|
||||
import { usePagedQuery } from '../usePagedQuery';
|
||||
import { useLogin } from './useLogin';
|
||||
import useModified from '../useModified';
|
||||
|
||||
|
|
@ -11,7 +11,7 @@ export function useWebsites(
|
|||
const { user } = useLogin();
|
||||
const { modified } = useModified(`websites`);
|
||||
|
||||
return useFilterQuery({
|
||||
return usePagedQuery({
|
||||
queryKey: ['websites', { userId, teamId, modified, ...params }],
|
||||
queryFn: (data: any) => {
|
||||
return get(teamId ? `/teams/${teamId}/websites` : `/users/${userId || user.id}/websites`, {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ export function useCountryNames(locale: string) {
|
|||
const [list, setList] = useState(countryNames[locale] || enUS);
|
||||
|
||||
async function loadData(locale: string) {
|
||||
const { data } = await httpGet(`${process.env.basePath}/intl/country/${locale}.json`);
|
||||
const { data } = await httpGet(`${process.env.basePath || ''}/intl/country/${locale}.json`);
|
||||
|
||||
if (data) {
|
||||
countryNames[locale] = data;
|
||||
|
|
@ -28,7 +28,7 @@ export function useCountryNames(locale: string) {
|
|||
}
|
||||
}, [locale]);
|
||||
|
||||
return list;
|
||||
return { countryNames: list };
|
||||
}
|
||||
|
||||
export default useCountryNames;
|
||||
|
|
|
|||
|
|
@ -1,19 +1,25 @@
|
|||
import { getMinimumUnit, parseDateRange } from 'lib/date';
|
||||
import { setItem } from 'next-basics';
|
||||
import { DATE_RANGE_CONFIG, DEFAULT_DATE_RANGE } from 'lib/constants';
|
||||
import websiteStore, { setWebsiteDateRange } from 'store/websites';
|
||||
import { DATE_RANGE_CONFIG, DEFAULT_DATE_COMPARE, DEFAULT_DATE_RANGE } from 'lib/constants';
|
||||
import websiteStore, { setWebsiteDateRange, setWebsiteDateCompare } from 'store/websites';
|
||||
import appStore, { setDateRange } from 'store/app';
|
||||
import { DateRange } from 'lib/types';
|
||||
import { useLocale } from './useLocale';
|
||||
import { useApi } from './queries/useApi';
|
||||
import { useApi } from './useApi';
|
||||
|
||||
export function useDateRange(websiteId?: string): [DateRange, (value: string | DateRange) => void] {
|
||||
export function useDateRange(websiteId?: string): {
|
||||
dateRange: DateRange;
|
||||
saveDateRange: (value: string | DateRange) => void;
|
||||
dateCompare: string;
|
||||
saveDateCompare: (value: string) => void;
|
||||
} {
|
||||
const { get } = useApi();
|
||||
const { locale } = useLocale();
|
||||
const websiteConfig = websiteStore(state => state[websiteId]?.dateRange);
|
||||
const defaultConfig = DEFAULT_DATE_RANGE;
|
||||
const globalConfig = appStore(state => state.dateRange);
|
||||
const dateRange = parseDateRange(websiteConfig || globalConfig || defaultConfig, locale);
|
||||
const dateCompare = websiteStore(state => state[websiteId]?.dateCompare || DEFAULT_DATE_COMPARE);
|
||||
|
||||
const saveDateRange = async (value: DateRange | string) => {
|
||||
if (websiteId) {
|
||||
|
|
@ -45,7 +51,11 @@ export function useDateRange(websiteId?: string): [DateRange, (value: string | D
|
|||
}
|
||||
};
|
||||
|
||||
return [dateRange, saveDateRange];
|
||||
const saveDateCompare = (value: string) => {
|
||||
setWebsiteDateCompare(websiteId, value);
|
||||
};
|
||||
|
||||
return { dateRange, saveDateRange, dateCompare, saveDateCompare };
|
||||
}
|
||||
|
||||
export default useDateRange;
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ export function useFields() {
|
|||
{ name: 'country', type: 'string', label: formatMessage(labels.country) },
|
||||
{ name: 'region', type: 'string', label: formatMessage(labels.region) },
|
||||
{ name: 'city', type: 'string', label: formatMessage(labels.city) },
|
||||
{ name: 'host', type: 'string', label: formatMessage(labels.host) },
|
||||
{ name: 'tag', type: 'string', label: formatMessage(labels.tag) },
|
||||
];
|
||||
|
||||
return { fields };
|
||||
|
|
|
|||
46
src/components/hooks/useFilterParams.ts
Normal file
46
src/components/hooks/useFilterParams.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import { useNavigation } from './useNavigation';
|
||||
import { useDateRange } from './useDateRange';
|
||||
import { useTimezone } from './useTimezone';
|
||||
|
||||
export function useFilterParams(websiteId: string) {
|
||||
const { dateRange } = useDateRange(websiteId);
|
||||
const { startDate, endDate, unit } = dateRange;
|
||||
const { timezone, toUtc } = useTimezone();
|
||||
const {
|
||||
query: {
|
||||
url,
|
||||
referrer,
|
||||
title,
|
||||
query,
|
||||
host,
|
||||
os,
|
||||
browser,
|
||||
device,
|
||||
country,
|
||||
region,
|
||||
city,
|
||||
event,
|
||||
tag,
|
||||
},
|
||||
} = useNavigation();
|
||||
|
||||
return {
|
||||
startAt: +toUtc(startDate),
|
||||
endAt: +toUtc(endDate),
|
||||
unit,
|
||||
timezone,
|
||||
url,
|
||||
referrer,
|
||||
title,
|
||||
query,
|
||||
host,
|
||||
os,
|
||||
browser,
|
||||
device,
|
||||
country,
|
||||
region,
|
||||
city,
|
||||
event,
|
||||
tag,
|
||||
};
|
||||
}
|
||||
|
|
@ -8,8 +8,8 @@ import regions from '../../../public/iso-3166-2.json';
|
|||
export function useFormat() {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { locale } = useLocale();
|
||||
const countryNames = useCountryNames(locale);
|
||||
const languageNames = useLanguageNames(locale);
|
||||
const { countryNames } = useCountryNames(locale);
|
||||
const { languageNames } = useLanguageNames(locale);
|
||||
|
||||
const formatOS = (value: string): string => {
|
||||
return OS_NAMES[value] || value;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ export function useLanguageNames(locale) {
|
|||
const [list, setList] = useState(languageNames[locale] || enUS);
|
||||
|
||||
async function loadData(locale) {
|
||||
const { data } = await httpGet(`${process.env.basePath}/intl/language/${locale}.json`);
|
||||
const { data } = await httpGet(`${process.env.basePath || ''}/intl/language/${locale}.json`);
|
||||
|
||||
if (data) {
|
||||
languageNames[locale] = data;
|
||||
|
|
@ -28,7 +28,7 @@ export function useLanguageNames(locale) {
|
|||
}
|
||||
}, [locale]);
|
||||
|
||||
return list;
|
||||
return { languageNames: list };
|
||||
}
|
||||
|
||||
export default useLanguageNames;
|
||||
|
|
|
|||
|
|
@ -19,7 +19,9 @@ export function useLocale() {
|
|||
const dateLocale = getDateLocale(locale);
|
||||
|
||||
async function loadMessages(locale: string) {
|
||||
const { ok, data } = await httpGet(`${process.env.basePath}/intl/messages/${locale}.json`);
|
||||
const { ok, data } = await httpGet(
|
||||
`${process.env.basePath || ''}/intl/messages/${locale}.json`,
|
||||
);
|
||||
|
||||
if (ok) {
|
||||
messages[locale] = data;
|
||||
|
|
|
|||
|
|
@ -1,16 +1,18 @@
|
|||
import { UseQueryOptions } from '@tanstack/react-query';
|
||||
import { useState } from 'react';
|
||||
import { PageResult, PageParams, PagedQueryResult } from 'lib/types';
|
||||
import { useApi } from './useApi';
|
||||
import { FilterResult, SearchFilter, FilterQueryResult } from 'lib/types';
|
||||
import { useNavigation } from './useNavigation';
|
||||
|
||||
export function useFilterQuery<T = any>({
|
||||
export function usePagedQuery<T = any>({
|
||||
queryKey,
|
||||
queryFn,
|
||||
...options
|
||||
}: Omit<UseQueryOptions, 'queryFn'> & { queryFn: (params?: object) => any }): FilterQueryResult<T> {
|
||||
const [params, setParams] = useState<T | SearchFilter>({
|
||||
}: Omit<UseQueryOptions, 'queryFn'> & { queryFn: (params?: object) => any }): PagedQueryResult<T> {
|
||||
const { query: queryParams } = useNavigation();
|
||||
const [params, setParams] = useState<PageParams>({
|
||||
query: '',
|
||||
page: 1,
|
||||
page: +queryParams.page || 1,
|
||||
});
|
||||
|
||||
const { useQuery } = useApi();
|
||||
|
|
@ -21,11 +23,11 @@ export function useFilterQuery<T = any>({
|
|||
});
|
||||
|
||||
return {
|
||||
result: data as FilterResult<any>,
|
||||
result: data as PageResult<T>,
|
||||
query,
|
||||
params,
|
||||
setParams,
|
||||
};
|
||||
}
|
||||
|
||||
export default useFilterQuery;
|
||||
export default usePagedQuery;
|
||||
19
src/components/hooks/useRegionNames.ts
Normal file
19
src/components/hooks/useRegionNames.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import useCountryNames from './useCountryNames';
|
||||
import regions from '../../../public/iso-3166-2.json';
|
||||
|
||||
export function useRegionNames(locale: string) {
|
||||
const { countryNames } = useCountryNames(locale);
|
||||
|
||||
const getRegionName = (regionCode: string, countryCode?: string) => {
|
||||
if (!countryCode) {
|
||||
return regions[regionCode];
|
||||
}
|
||||
|
||||
const region = regionCode.includes('-') ? regionCode : `${countryCode}-${regionCode}`;
|
||||
return regions[region] ? `${regions[region]}, ${countryNames[countryCode]}` : region;
|
||||
};
|
||||
|
||||
return { regionNames: regions, getRegionName };
|
||||
}
|
||||
|
||||
export default useRegionNames;
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import { setItem } from 'next-basics';
|
||||
import { TIMEZONE_CONFIG } from 'lib/constants';
|
||||
import { formatInTimeZone, zonedTimeToUtc, utcToZonedTime } from 'date-fns-tz';
|
||||
import useStore, { setTimezone } from 'store/app';
|
||||
|
||||
const selector = (state: { timezone: string }) => state.timezone;
|
||||
|
|
@ -12,7 +13,25 @@ export function useTimezone() {
|
|||
setTimezone(value);
|
||||
};
|
||||
|
||||
return { timezone, saveTimezone };
|
||||
const formatTimezoneDate = (date: string, pattern: string) => {
|
||||
return formatInTimeZone(
|
||||
/^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]{3})?Z$/.test(date)
|
||||
? date
|
||||
: date.split(' ').join('T') + 'Z',
|
||||
timezone,
|
||||
pattern,
|
||||
);
|
||||
};
|
||||
|
||||
const toUtc = (date: Date | string | number) => {
|
||||
return zonedTimeToUtc(date, timezone);
|
||||
};
|
||||
|
||||
const fromUtc = (date: Date | string | number) => {
|
||||
return utcToZonedTime(date, timezone);
|
||||
};
|
||||
|
||||
return { timezone, saveTimezone, formatTimezoneDate, toUtc, fromUtc };
|
||||
}
|
||||
|
||||
export default useTimezone;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue