allow showing pageview custom data on website detail page

This commit is contained in:
Ewen Le Bihan 2023-12-17 03:01:39 +01:00
parent 46dd16260a
commit 996c02ef1a
5 changed files with 77 additions and 4 deletions

View file

@ -10,7 +10,13 @@ import WebsiteHeader from './WebsiteHeader';
import WebsiteMetricsBar from './WebsiteMetricsBar'; import WebsiteMetricsBar from './WebsiteMetricsBar';
import WebsiteTableView from './WebsiteTableView'; import WebsiteTableView from './WebsiteTableView';
export default function WebsiteDetails({ websiteId }: { websiteId: string }) { export default function WebsiteDetails({
websiteId,
customDataFields,
}: {
websiteId: string;
customDataFields: string[];
}) {
const { data: website, isLoading, error } = useWebsite(websiteId); const { data: website, isLoading, error } = useWebsite(websiteId);
const pathname = usePathname(); const pathname = usePathname();
const showLinks = !pathname.includes('/share/'); const showLinks = !pathname.includes('/share/');
@ -32,7 +38,13 @@ export default function WebsiteDetails({ websiteId }: { websiteId: string }) {
{!website && <Loading icon="dots" style={{ minHeight: 300 }} />} {!website && <Loading icon="dots" style={{ minHeight: 300 }} />}
{website && ( {website && (
<> <>
{!view && <WebsiteTableView websiteId={websiteId} domainName={website.domain} />} {!view && (
<WebsiteTableView
customDataFields={customDataFields}
websiteId={websiteId}
domainName={website.domain}
/>
)}
{view && <WebsiteExpandedView websiteId={websiteId} domainName={website.domain} />} {view && <WebsiteExpandedView websiteId={websiteId} domainName={website.domain} />}
</> </>
)} )}

View file

@ -9,13 +9,17 @@ import WorldMap from 'components/metrics/WorldMap';
import CountriesTable from 'components/metrics/CountriesTable'; import CountriesTable from 'components/metrics/CountriesTable';
import EventsTable from 'components/metrics/EventsTable'; import EventsTable from 'components/metrics/EventsTable';
import EventsChart from 'components/metrics/EventsChart'; import EventsChart from 'components/metrics/EventsChart';
import PageviewCustomDataTable from 'components/metrics/PageviewCustomDataTable';
import { chunkArray } from 'next-basics';
export default function WebsiteTableView({ export default function WebsiteTableView({
websiteId, websiteId,
domainName, domainName,
customDataFields,
}: { }: {
websiteId: string; websiteId: string;
domainName: string; domainName: string;
customDataFields: string[];
}) { }) {
const [countryData, setCountryData] = useState(); const [countryData, setCountryData] = useState();
const tableProps = { const tableProps = {
@ -35,6 +39,16 @@ export default function WebsiteTableView({
<OSTable {...tableProps} /> <OSTable {...tableProps} />
<DevicesTable {...tableProps} /> <DevicesTable {...tableProps} />
</GridRow> </GridRow>
{chunkArray(customDataFields, 3).map((fields, index) => (
<GridRow
key={index}
columns={fields.length === 1 ? 'one' : fields.length === 2 ? 'two' : 'three'}
>
{fields.map(field => (
<PageviewCustomDataTable key="custom" {...tableProps} fieldName={field} />
))}
</GridRow>
))}
<GridRow columns="two-one"> <GridRow columns="two-one">
<WorldMap data={countryData} /> <WorldMap data={countryData} />
<CountriesTable {...tableProps} onDataLoad={setCountryData} /> <CountriesTable {...tableProps} onDataLoad={setCountryData} />

View file

@ -0,0 +1,29 @@
import useMessages from 'components/hooks/useMessages';
import MetricsTable, { MetricsTableProps } from 'components/metrics/MetricsTable';
export function PageviewCustomDataTable(props: MetricsTableProps) {
const { formatMessage, labels } = useMessages();
function renderLink({ x: field }) {
return <>{field}</>;
}
function titleize(fieldName: string) {
return fieldName
.split(/[_-]/)
.map((word, i) => (i === 0 ? word[0].toUpperCase() + word.slice(1) : word))
.join(' ');
}
return (
<MetricsTable
{...props}
title={titleize(props.fieldName)}
type="custom"
metric={formatMessage(labels.visitors)}
renderLabel={renderLink}
/>
);
}
export default PageviewCustomDataTable;

View file

@ -28,6 +28,7 @@ export interface WebsiteMetricsRequestQuery {
limit?: number; limit?: number;
offset?: number; offset?: number;
search?: string; search?: string;
fieldName?: string;
} }
const schema = { const schema = {
@ -51,6 +52,7 @@ const schema = {
limit: yup.number(), limit: yup.number(),
offset: yup.number(), offset: yup.number(),
search: yup.string(), search: yup.string(),
fieldName: yup.string(),
}), }),
}; };
@ -80,6 +82,7 @@ export default async (
limit, limit,
offset, offset,
search, search,
fieldName,
} = req.query; } = req.query;
if (req.method === 'GET') { if (req.method === 'GET') {
@ -146,6 +149,10 @@ export default async (
offset, offset,
); );
return ok(res, data);
} else if (type === 'custom') {
const data = await getPageviewMetrics(websiteId, column, filters, limit, fieldName);
return ok(res, data); return ok(res, data);
} }

View file

@ -11,6 +11,7 @@ export async function getPageviewMetrics(
filters: QueryFilters, filters: QueryFilters,
limit?: number, limit?: number,
offset?: number, offset?: number,
fieldName?: string,
] ]
) { ) {
return runQuery({ return runQuery({
@ -25,6 +26,7 @@ async function relationalQuery(
filters: QueryFilters, filters: QueryFilters,
limit: number = 500, limit: number = 500,
offset: number = 0, offset: number = 0,
fieldName?: string,
) { ) {
const { rawQuery, parseFilters } = prisma; const { rawQuery, parseFilters } = prisma;
const { filterQuery, joinSession, params } = await parseFilters( const { filterQuery, joinSession, params } = await parseFilters(
@ -37,19 +39,26 @@ async function relationalQuery(
); );
let excludeDomain = ''; let excludeDomain = '';
let joinEventData = '';
let filterEventDataOnFieldName = '';
if (column === 'referrer_domain') { if (column === 'referrer_domain') {
excludeDomain = excludeDomain =
'and (website_event.referrer_domain != {{websiteDomain}} or website_event.referrer_domain is null)'; 'and (website_event.referrer_domain != {{websiteDomain}} or website_event.referrer_domain is null)';
} else if (column === 'custom') {
joinEventData = 'join event_data on event_data.website_event_id = website_event.event_id';
filterEventDataOnFieldName = 'and event_data.event_key = {{fieldName}}';
} }
return rawQuery( return rawQuery(
` `
select ${column} x, count(*) y select ${column === 'custom' ? `event_data.string_value` : column} x, count(*) y
from website_event from website_event
${joinEventData}
${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 event_type = {{eventType}} and event_type = {{eventType}}
${filterEventDataOnFieldName}
${excludeDomain} ${excludeDomain}
${filterQuery} ${filterQuery}
group by 1 group by 1
@ -57,7 +66,7 @@ async function relationalQuery(
limit ${limit} limit ${limit}
offset ${offset} offset ${offset}
`, `,
params, { ...params, fieldName },
); );
} }
@ -67,6 +76,8 @@ async function clickhouseQuery(
filters: QueryFilters, filters: QueryFilters,
limit: number = 500, limit: number = 500,
offset: number = 0, offset: number = 0,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
fieldName?: string,
): Promise<{ x: string; y: number }[]> { ): Promise<{ x: string; y: number }[]> {
const { rawQuery, parseFilters } = clickhouse; const { rawQuery, parseFilters } = clickhouse;
const { filterQuery, params } = await parseFilters(websiteId, { const { filterQuery, params } = await parseFilters(websiteId, {