mirror of
https://github.com/umami-software/umami.git
synced 2026-02-11 16:17:13 +01:00
Merge remote-tracking branch 'origin/dev' into dev
This commit is contained in:
commit
14df1e5a29
9 changed files with 46 additions and 16 deletions
|
|
@ -175,6 +175,8 @@ export const labels = defineMessages({
|
||||||
browser: { id: 'label.browser', defaultMessage: 'Browser' },
|
browser: { id: 'label.browser', defaultMessage: 'Browser' },
|
||||||
device: { id: 'label.device', defaultMessage: 'Device' },
|
device: { id: 'label.device', defaultMessage: 'Device' },
|
||||||
pageTitle: { id: 'label.pageTitle', defaultMessage: 'Page title' },
|
pageTitle: { id: 'label.pageTitle', defaultMessage: 'Page title' },
|
||||||
|
day: { id: 'label.day', defaultMessage: 'Day' },
|
||||||
|
date: { id: 'label.date', defaultMessage: 'Date' },
|
||||||
});
|
});
|
||||||
|
|
||||||
export const messages = defineMessages({
|
export const messages = defineMessages({
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ export const ReportContext = createContext(null);
|
||||||
export function Report({ reportId, defaultParameters, children, ...props }) {
|
export function Report({ reportId, defaultParameters, children, ...props }) {
|
||||||
const report = useReport(reportId, defaultParameters);
|
const report = useReport(reportId, defaultParameters);
|
||||||
|
|
||||||
|
//console.log({ report });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReportContext.Provider value={{ ...report }}>
|
<ReportContext.Provider value={{ ...report }}>
|
||||||
<Page {...props} className={styles.container}>
|
<Page {...props} className={styles.container}>
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,12 @@ import { useContext } from 'react';
|
||||||
import { GridTable, GridColumn } from 'react-basics';
|
import { GridTable, GridColumn } from 'react-basics';
|
||||||
import { ReportContext } from '../Report';
|
import { ReportContext } from '../Report';
|
||||||
import EmptyPlaceholder from 'components/common/EmptyPlaceholder';
|
import EmptyPlaceholder from 'components/common/EmptyPlaceholder';
|
||||||
|
import { useMessages } from 'hooks';
|
||||||
|
import { dateFormat } from 'lib/date';
|
||||||
import styles from './RetentionTable.module.css';
|
import styles from './RetentionTable.module.css';
|
||||||
|
|
||||||
export function RetentionTable() {
|
export function RetentionTable() {
|
||||||
|
const { formatMessage, labels } = useMessages();
|
||||||
const { report } = useContext(ReportContext);
|
const { report } = useContext(ReportContext);
|
||||||
const { data } = report || {};
|
const { data } = report || {};
|
||||||
|
|
||||||
|
|
@ -19,21 +22,27 @@ export function RetentionTable() {
|
||||||
return arr;
|
return arr;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const days = Array(14).fill(null);
|
const days = Array(32).fill(null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={styles.table}>
|
<div className={styles.table}>
|
||||||
<div className={styles.row}>
|
<div className={styles.row}>
|
||||||
|
<div className={styles.date}>{formatMessage(labels.date)}</div>
|
||||||
{days.map((n, i) => (
|
{days.map((n, i) => (
|
||||||
<div key={i} className={styles.header}>
|
<div key={i} className={styles.header}>
|
||||||
Day {i}
|
{formatMessage(labels.day)} {i}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
{dates.map((date, i) => {
|
{dates.map((date, i) => {
|
||||||
return (
|
return (
|
||||||
<div key={i} className={styles.row}>
|
<div key={i} className={styles.row}>
|
||||||
|
<div className={styles.date}>
|
||||||
|
{dateFormat(date, 'P')}
|
||||||
|
<br />
|
||||||
|
{date}
|
||||||
|
</div>
|
||||||
{days.map((n, day) => {
|
{days.map((n, day) => {
|
||||||
return (
|
return (
|
||||||
<div key={day} className={styles.cell}>
|
<div key={day} className={styles.cell}>
|
||||||
|
|
|
||||||
|
|
@ -26,3 +26,7 @@
|
||||||
background: var(--blue100);
|
background: var(--blue100);
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.date {
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { produce } from 'immer';
|
import { produce } from 'immer';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
import { useTimezone } from './useTimezone';
|
||||||
import useApi from './useApi';
|
import useApi from './useApi';
|
||||||
|
|
||||||
const baseParameters = {
|
const baseParameters = {
|
||||||
|
|
@ -12,6 +13,7 @@ export function useReport(reportId, defaultParameters) {
|
||||||
const [report, setReport] = useState(null);
|
const [report, setReport] = useState(null);
|
||||||
const [isRunning, setIsRunning] = useState(false);
|
const [isRunning, setIsRunning] = useState(false);
|
||||||
const { get, post } = useApi();
|
const { get, post } = useApi();
|
||||||
|
const [timezone] = useTimezone();
|
||||||
|
|
||||||
const loadReport = async id => {
|
const loadReport = async id => {
|
||||||
const data = await get(`/reports/${id}`);
|
const data = await get(`/reports/${id}`);
|
||||||
|
|
@ -33,7 +35,7 @@ export function useReport(reportId, defaultParameters) {
|
||||||
|
|
||||||
const { type } = report;
|
const { type } = report;
|
||||||
|
|
||||||
const data = await post(`/reports/${type}`, parameters);
|
const data = await post(`/reports/${type}`, { ...parameters, timezone });
|
||||||
|
|
||||||
setReport(
|
setReport(
|
||||||
produce(state => {
|
produce(state => {
|
||||||
|
|
|
||||||
10
lib/date.js
10
lib/date.js
|
|
@ -250,9 +250,13 @@ export const customFormats = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export function dateFormat(date, str, locale = 'en-US') {
|
export function dateFormat(date, str, locale = 'en-US') {
|
||||||
return format(date, customFormats?.[locale]?.[str] || str, {
|
return format(
|
||||||
locale: getDateLocale(locale),
|
typeof date === 'string' ? new Date(date) : date,
|
||||||
});
|
customFormats?.[locale]?.[str] || str,
|
||||||
|
{
|
||||||
|
locale: getDateLocale(locale),
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function maxDate(...args) {
|
export function maxDate(...args) {
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import { getRetention } from 'queries';
|
||||||
export interface RetentionRequestBody {
|
export interface RetentionRequestBody {
|
||||||
websiteId: string;
|
websiteId: string;
|
||||||
dateRange: { window; startDate: string; endDate: string };
|
dateRange: { window; startDate: string; endDate: string };
|
||||||
|
timezone: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RetentionResponse {
|
export interface RetentionResponse {
|
||||||
|
|
@ -26,6 +27,7 @@ export default async (
|
||||||
const {
|
const {
|
||||||
websiteId,
|
websiteId,
|
||||||
dateRange: { startDate, endDate },
|
dateRange: { startDate, endDate },
|
||||||
|
timezone,
|
||||||
} = req.body;
|
} = req.body;
|
||||||
|
|
||||||
if (!(await canViewWebsite(req.auth, websiteId))) {
|
if (!(await canViewWebsite(req.auth, websiteId))) {
|
||||||
|
|
@ -35,6 +37,7 @@ export default async (
|
||||||
const data = await getRetention(websiteId, {
|
const data = await getRetention(websiteId, {
|
||||||
startDate: new Date(startDate),
|
startDate: new Date(startDate),
|
||||||
endDate: new Date(endDate),
|
endDate: new Date(endDate),
|
||||||
|
timezone,
|
||||||
});
|
});
|
||||||
|
|
||||||
return ok(res, data);
|
return ok(res, data);
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function clickhouseQuery(websiteId: string, filters: QueryFilters) {
|
async function clickhouseQuery(websiteId: string, filters: QueryFilters) {
|
||||||
const { timezone = 'utc', unit = 'day' } = filters;
|
const { timezone = 'UTC', unit = 'day' } = filters;
|
||||||
const { rawQuery, getDateQuery, parseFilters } = clickhouse;
|
const { rawQuery, getDateQuery, parseFilters } = clickhouse;
|
||||||
const { filterQuery, params } = await parseFilters(websiteId, {
|
const { filterQuery, params } = await parseFilters(websiteId, {
|
||||||
...filters,
|
...filters,
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ async function relationalQuery(
|
||||||
},
|
},
|
||||||
): Promise<
|
): Promise<
|
||||||
{
|
{
|
||||||
date: Date;
|
date: string;
|
||||||
day: number;
|
day: number;
|
||||||
visitors: number;
|
visitors: number;
|
||||||
returnVisitors: number;
|
returnVisitors: number;
|
||||||
|
|
@ -33,13 +33,15 @@ async function relationalQuery(
|
||||||
}[]
|
}[]
|
||||||
> {
|
> {
|
||||||
const { startDate, endDate } = dateRange;
|
const { startDate, endDate } = dateRange;
|
||||||
const { rawQuery } = prisma;
|
const { getDateQuery, rawQuery } = prisma;
|
||||||
|
const timezone = 'utc';
|
||||||
|
const unit = 'day';
|
||||||
|
|
||||||
return rawQuery(
|
return rawQuery(
|
||||||
`
|
`
|
||||||
WITH cohort_items AS (
|
WITH cohort_items AS (
|
||||||
select session_id,
|
select session_id,
|
||||||
date_trunc('day', created_at)::date as cohort_date
|
${getDateQuery('created_at', unit, timezone)} as cohort_date
|
||||||
from session
|
from session
|
||||||
where website_id = {{websiteId::uuid}}
|
where website_id = {{websiteId::uuid}}
|
||||||
and created_at between {{startDate}} and {{endDate}}
|
and created_at between {{startDate}} and {{endDate}}
|
||||||
|
|
@ -47,7 +49,7 @@ async function relationalQuery(
|
||||||
user_activities AS (
|
user_activities AS (
|
||||||
select distinct
|
select distinct
|
||||||
w.session_id,
|
w.session_id,
|
||||||
(date_trunc('day', w.created_at)::date - c.cohort_date::date) as day_number
|
(${getDateQuery('created_at', unit, timezone)}::date - c.cohort_date::date) as day_number
|
||||||
from website_event w
|
from website_event w
|
||||||
join cohort_items c
|
join cohort_items c
|
||||||
on w.session_id = c.session_id
|
on w.session_id = c.session_id
|
||||||
|
|
@ -98,7 +100,7 @@ async function clickhouseQuery(
|
||||||
},
|
},
|
||||||
): Promise<
|
): Promise<
|
||||||
{
|
{
|
||||||
date: Date;
|
date: string;
|
||||||
day: number;
|
day: number;
|
||||||
visitors: number;
|
visitors: number;
|
||||||
returnVisitors: number;
|
returnVisitors: number;
|
||||||
|
|
@ -106,13 +108,15 @@ async function clickhouseQuery(
|
||||||
}[]
|
}[]
|
||||||
> {
|
> {
|
||||||
const { startDate, endDate } = dateRange;
|
const { startDate, endDate } = dateRange;
|
||||||
const { rawQuery } = clickhouse;
|
const { getDateQuery, getDateStringQuery, rawQuery } = clickhouse;
|
||||||
|
const timezone = 'UTC';
|
||||||
|
const unit = 'day';
|
||||||
|
|
||||||
return rawQuery(
|
return rawQuery(
|
||||||
`
|
`
|
||||||
WITH cohort_items AS (
|
WITH cohort_items AS (
|
||||||
select
|
select
|
||||||
min(date_trunc('day', created_at)) as cohort_date,
|
min(${getDateQuery('created_at', unit, timezone)}) as cohort_date,
|
||||||
session_id
|
session_id
|
||||||
from website_event
|
from website_event
|
||||||
where website_id = {websiteId:UUID}
|
where website_id = {websiteId:UUID}
|
||||||
|
|
@ -122,7 +126,7 @@ async function clickhouseQuery(
|
||||||
user_activities AS (
|
user_activities AS (
|
||||||
select distinct
|
select distinct
|
||||||
w.session_id,
|
w.session_id,
|
||||||
(date_trunc('day', w.created_at) - c.cohort_date) / 86400 as day_number
|
(${getDateQuery('created_at', unit, timezone)} - c.cohort_date) / 86400 as day_number
|
||||||
from website_event w
|
from website_event w
|
||||||
join cohort_items c
|
join cohort_items c
|
||||||
on w.session_id = c.session_id
|
on w.session_id = c.session_id
|
||||||
|
|
@ -147,7 +151,7 @@ async function clickhouseQuery(
|
||||||
group by 1, 2
|
group by 1, 2
|
||||||
)
|
)
|
||||||
select
|
select
|
||||||
c.cohort_date as date,
|
${getDateStringQuery('c.cohort_date', unit)} as date,
|
||||||
c.day_number as day,
|
c.day_number as day,
|
||||||
s.visitors as visitors,
|
s.visitors as visitors,
|
||||||
c.visitors returnVisitors,
|
c.visitors returnVisitors,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue