mirror of
https://github.com/kremalicious/umami.git
synced 2025-02-01 20:39:44 +01:00
Merge remote-tracking branch 'origin/dev' into dev
This commit is contained in:
commit
499392c110
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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,
|
||||||
},
|
},
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
@ -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(',')}`;
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user