Merge remote-tracking branch 'origin/dev' into dev

This commit is contained in:
Brian Cao 2023-08-15 10:57:34 -07:00
commit 14df1e5a29
9 changed files with 46 additions and 16 deletions

View file

@ -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({

View file

@ -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}>

View file

@ -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}>

View file

@ -26,3 +26,7 @@
background: var(--blue100); background: var(--blue100);
border-radius: var(--border-radius); border-radius: var(--border-radius);
} }
.date {
min-width: 200px;
}

View file

@ -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 => {

View file

@ -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) {

View file

@ -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);

View file

@ -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,

View file

@ -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,