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

This commit is contained in:
Mike Cao 2024-06-13 22:15:48 -07:00
commit 499392c110
7 changed files with 92 additions and 48 deletions

View File

@ -3,5 +3,5 @@
grid-template-rows: max-content 1fr; grid-template-rows: max-content 1fr;
grid-template-columns: max-content 1fr; grid-template-columns: max-content 1fr;
margin-bottom: 60px; margin-bottom: 60px;
height: 100%; height: 90vh;
} }

View File

@ -1,14 +1,14 @@
import Link from 'next/link';
import { Button, Icons, Text, Icon } from 'react-basics';
import PageHeader from 'components/layout/PageHeader';
import Funnel from 'assets/funnel.svg'; import Funnel from 'assets/funnel.svg';
import Lightbulb from 'assets/lightbulb.svg'; import Lightbulb from 'assets/lightbulb.svg';
import Magnet from 'assets/magnet.svg'; import Magnet from 'assets/magnet.svg';
import Path from 'assets/path.svg';
import Tag from 'assets/tag.svg'; import Tag from 'assets/tag.svg';
import Target from 'assets/target.svg'; import Target from 'assets/target.svg';
import Path from 'assets/path.svg';
import styles from './ReportTemplates.module.css';
import { useMessages, useTeamUrl } from 'components/hooks'; import { useMessages, useTeamUrl } from 'components/hooks';
import PageHeader from 'components/layout/PageHeader';
import Link from 'next/link';
import { Button, Icon, Icons, Text } from 'react-basics';
import styles from './ReportTemplates.module.css';
export function ReportTemplates({ showHeader = true }: { showHeader?: boolean }) { export function ReportTemplates({ showHeader = true }: { showHeader?: boolean }) {
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();

View File

@ -3,6 +3,7 @@ import { GridTable, GridColumn } from 'react-basics';
import { useFormat, useMessages } from 'components/hooks'; import { useFormat, useMessages } from 'components/hooks';
import { ReportContext } from '../[reportId]/Report'; import { ReportContext } from '../[reportId]/Report';
import EmptyPlaceholder from 'components/common/EmptyPlaceholder'; import EmptyPlaceholder from 'components/common/EmptyPlaceholder';
import { formatShortTime } from 'lib/format';
export function InsightsTable() { export function InsightsTable() {
const [fields, setFields] = useState([]); const [fields, setFields] = useState([]);
@ -31,6 +32,12 @@ export function InsightsTable() {
</GridColumn> </GridColumn>
); );
})} })}
<GridColumn name="views" label={formatMessage(labels.views)} width="100px" alignment="end">
{row => row?.views?.toLocaleString()}
</GridColumn>
<GridColumn name="visits" label={formatMessage(labels.visits)} width="100px" alignment="end">
{row => row?.visits?.toLocaleString()}
</GridColumn>
<GridColumn <GridColumn
name="visitors" name="visitors"
label={formatMessage(labels.visitors)} label={formatMessage(labels.visitors)}
@ -39,8 +46,27 @@ export function InsightsTable() {
> >
{row => row?.visitors?.toLocaleString()} {row => row?.visitors?.toLocaleString()}
</GridColumn> </GridColumn>
<GridColumn name="views" label={formatMessage(labels.views)} width="100px" alignment="end"> <GridColumn
{row => row?.views?.toLocaleString()} name="bounceRate"
label={formatMessage(labels.bounceRate)}
width="100px"
alignment="end"
>
{row => {
const n = (Math.min(row?.visits, row?.bounces) / row?.visits) * 100;
return Math.round(+n) + '%';
}}
</GridColumn>
<GridColumn
name="visitDuration"
label={formatMessage(labels.visitDuration)}
width="100px"
alignment="end"
>
{row => {
const n = row?.totaltime / row?.visits;
return `${+n < 0 ? '-' : ''}${formatShortTime(Math.abs(~~n), ['m', 's'], ' ')}`;
}}
</GridColumn> </GridColumn>
</GridTable> </GridTable>
); );

View File

@ -57,11 +57,11 @@ export function WebsiteMetricsBar({
}, },
{ {
label: formatMessage(labels.bounceRate), label: formatMessage(labels.bounceRate),
value: (Math.min(visitors.value, bounces.value) / visitors.value) * 100, value: (Math.min(visits.value, bounces.value) / visits.value) * 100,
prev: (Math.min(visitors.prev, bounces.prev) / visitors.prev) * 100, prev: (Math.min(visits.prev, bounces.prev) / visits.prev) * 100,
change: change:
(Math.min(visitors.value, bounces.value) / visitors.value) * 100 - (Math.min(visits.value, bounces.value) / visits.value) * 100 -
(Math.min(visitors.prev, bounces.prev) / visitors.prev) * 100, (Math.min(visits.prev, bounces.prev) / visits.prev) * 100,
formatValue: n => Math.round(+n) + '%', formatValue: n => Math.round(+n) + '%',
reverseColors: true, reverseColors: true,
}, },

View File

@ -4,7 +4,6 @@
flex-direction: column; flex-direction: column;
position: relative; position: relative;
width: 100%; width: 100%;
height: 100%;
max-width: 1320px; max-width: 1320px;
margin: 0 auto; margin: 0 auto;
padding: 0 20px; padding: 0 20px;

View File

@ -119,7 +119,7 @@ async function parseFilters(websiteId: string, filters: QueryFilters = {}, optio
}; };
} }
async function rawQuery(query: string, params: Record<string, unknown> = {}): Promise<unknown[]> { async function rawQuery(query: string, params: Record<string, unknown> = {}): Promise<unknown> {
if (process.env.LOG_QUERY) { if (process.env.LOG_QUERY) {
log('QUERY:\n', query); log('QUERY:\n', query);
log('PARAMETERS:\n', params); log('PARAMETERS:\n', params);

View File

@ -23,7 +23,7 @@ async function relationalQuery(
y: number; y: number;
}[] }[]
> { > {
const { parseFilters, rawQuery } = prisma; const { getTimestampDiffQuery, parseFilters, rawQuery } = prisma;
const { filterQuery, joinSession, params } = await parseFilters( const { filterQuery, joinSession, params } = await parseFilters(
websiteId, websiteId,
{ {
@ -38,14 +38,30 @@ async function relationalQuery(
return rawQuery( return rawQuery(
` `
select select
sum(t.c) as "views",
count(distinct t.session_id) as "visitors",
count(distinct t.visit_id) as "visits",
sum(case when t.c = 1 then 1 else 0 end) as "bounces",
sum(${getTimestampDiffQuery('t.min_time', 't.max_time')}) as "totaltime",
${parseFields(fields)} ${parseFields(fields)}
from (
select
${parseFields(fields)},
website_event.session_id,
website_event.visit_id,
count(*) as "c",
min(website_event.created_at) as "min_time",
max(website_event.created_at) as "max_time"
from website_event from website_event
${joinSession} ${joinSession}
where website_event.website_id = {{websiteId::uuid}} where website_event.website_id = {{websiteId::uuid}}
and website_event.created_at between {{startDate}} and {{endDate}} and website_event.created_at between {{startDate}} and {{endDate}}
and website_event.event_type = {{eventType}} and event_type = {{eventType}}
${filterQuery} ${filterQuery}
${parseGroupBy(fields)} group by ${parseFields(fields)},
website_event.session_id, website_event.visit_id
) as t
group by ${parseFields(fields)}
order by 1 desc, 2 desc order by 1 desc, 2 desc
limit 500 limit 500
`, `,
@ -72,13 +88,29 @@ async function clickhouseQuery(
return rawQuery( return rawQuery(
` `
select select
sum(t.c) as "views",
count(distinct t.session_id) as "visitors",
count(distinct t.visit_id) as "visits",
sum(if(t.c = 1, 1, 0)) as "bounces",
sum(max_time-min_time) as "totaltime",
${parseFields(fields)} ${parseFields(fields)}
from (
select
${parseFields(fields)},
session_id,
visit_id,
count(*) c,
min(created_at) min_time,
max(created_at) max_time
from website_event from website_event
where website_id = {websiteId:UUID} where website_id = {websiteId:UUID}
and created_at between {startDate:DateTime64} and {endDate:DateTime64} and created_at between {startDate:DateTime64} and {endDate:DateTime64}
and event_type = {eventType:UInt32} and event_type = {eventType:UInt32}
${filterQuery} ${filterQuery}
${parseGroupBy(fields)} group by ${parseFields(fields)},
session_id, visit_id
) as t
group by ${parseFields(fields)}
order by 1 desc, 2 desc order by 1 desc, 2 desc
limit 500 limit 500
`, `,
@ -89,27 +121,14 @@ async function clickhouseQuery(
...a, ...a,
views: Number(a.views), views: Number(a.views),
visitors: Number(a.visitors), visitors: Number(a.visitors),
visits: Number(a.visits),
bounces: Number(a.bounces),
totaltime: Number(a.totaltime),
}; };
}); });
}); });
} }
function parseFields(fields: any[]) { function parseFields(fields: { name: any }[]) {
const query = fields.reduce( return `${fields.map(({ name }) => FILTER_COLUMNS[name]).join(',')}`;
(arr, field) => {
const { name } = field;
return arr.concat(`${FILTER_COLUMNS[name]} as "${name}"`);
},
['count(*) as views', 'count(distinct website_event.session_id) as visitors'],
);
return query.join(',\n');
}
function parseGroupBy(fields: { name: any }[]) {
if (!fields.length) {
return '';
}
return `group by ${fields.map(({ name }) => FILTER_COLUMNS[name]).join(',')}`;
} }