diff --git a/src/app/(main)/websites/[websiteId]/WebsiteDetails.tsx b/src/app/(main)/websites/[websiteId]/WebsiteDetails.tsx
index d917c6d71..6ccde2812 100644
--- a/src/app/(main)/websites/[websiteId]/WebsiteDetails.tsx
+++ b/src/app/(main)/websites/[websiteId]/WebsiteDetails.tsx
@@ -10,7 +10,13 @@ import WebsiteHeader from './WebsiteHeader';
import WebsiteMetricsBar from './WebsiteMetricsBar';
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 pathname = usePathname();
const showLinks = !pathname.includes('/share/');
@@ -32,7 +38,13 @@ export default function WebsiteDetails({ websiteId }: { websiteId: string }) {
{!website && }
{website && (
<>
- {!view && }
+ {!view && (
+
+ )}
{view && }
>
)}
diff --git a/src/app/(main)/websites/[websiteId]/WebsiteTableView.tsx b/src/app/(main)/websites/[websiteId]/WebsiteTableView.tsx
index 7cc415e5c..9cabcf7de 100644
--- a/src/app/(main)/websites/[websiteId]/WebsiteTableView.tsx
+++ b/src/app/(main)/websites/[websiteId]/WebsiteTableView.tsx
@@ -9,13 +9,17 @@ import WorldMap from 'components/metrics/WorldMap';
import CountriesTable from 'components/metrics/CountriesTable';
import EventsTable from 'components/metrics/EventsTable';
import EventsChart from 'components/metrics/EventsChart';
+import PageviewCustomDataTable from 'components/metrics/PageviewCustomDataTable';
+import { chunkArray } from 'next-basics';
export default function WebsiteTableView({
websiteId,
domainName,
+ customDataFields,
}: {
websiteId: string;
domainName: string;
+ customDataFields: string[];
}) {
const [countryData, setCountryData] = useState();
const tableProps = {
@@ -35,6 +39,16 @@ export default function WebsiteTableView({
+ {chunkArray(customDataFields, 3).map((fields, index) => (
+
+ {fields.map(field => (
+
+ ))}
+
+ ))}
diff --git a/src/components/metrics/PageviewCustomDataTable.tsx b/src/components/metrics/PageviewCustomDataTable.tsx
new file mode 100644
index 000000000..c7cffa76a
--- /dev/null
+++ b/src/components/metrics/PageviewCustomDataTable.tsx
@@ -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 (
+
+ );
+}
+
+export default PageviewCustomDataTable;
diff --git a/src/pages/api/websites/[websiteId]/metrics.ts b/src/pages/api/websites/[websiteId]/metrics.ts
index eb6361707..abc63cd5f 100644
--- a/src/pages/api/websites/[websiteId]/metrics.ts
+++ b/src/pages/api/websites/[websiteId]/metrics.ts
@@ -28,6 +28,7 @@ export interface WebsiteMetricsRequestQuery {
limit?: number;
offset?: number;
search?: string;
+ fieldName?: string;
}
const schema = {
@@ -51,6 +52,7 @@ const schema = {
limit: yup.number(),
offset: yup.number(),
search: yup.string(),
+ fieldName: yup.string(),
}),
};
@@ -80,6 +82,7 @@ export default async (
limit,
offset,
search,
+ fieldName,
} = req.query;
if (req.method === 'GET') {
@@ -146,6 +149,10 @@ export default async (
offset,
);
+ return ok(res, data);
+ } else if (type === 'custom') {
+ const data = await getPageviewMetrics(websiteId, column, filters, limit, fieldName);
+
return ok(res, data);
}
diff --git a/src/queries/analytics/pageviews/getPageviewMetrics.ts b/src/queries/analytics/pageviews/getPageviewMetrics.ts
index 8673dbe60..6112c69df 100644
--- a/src/queries/analytics/pageviews/getPageviewMetrics.ts
+++ b/src/queries/analytics/pageviews/getPageviewMetrics.ts
@@ -11,6 +11,7 @@ export async function getPageviewMetrics(
filters: QueryFilters,
limit?: number,
offset?: number,
+ fieldName?: string,
]
) {
return runQuery({
@@ -25,6 +26,7 @@ async function relationalQuery(
filters: QueryFilters,
limit: number = 500,
offset: number = 0,
+ fieldName?: string,
) {
const { rawQuery, parseFilters } = prisma;
const { filterQuery, joinSession, params } = await parseFilters(
@@ -37,19 +39,26 @@ async function relationalQuery(
);
let excludeDomain = '';
+ let joinEventData = '';
+ let filterEventDataOnFieldName = '';
if (column === 'referrer_domain') {
excludeDomain =
'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(
`
- select ${column} x, count(*) y
+ select ${column === 'custom' ? `event_data.string_value` : column} x, count(*) y
from website_event
+ ${joinEventData}
${joinSession}
where website_event.website_id = {{websiteId::uuid}}
and website_event.created_at between {{startDate}} and {{endDate}}
and event_type = {{eventType}}
+ ${filterEventDataOnFieldName}
${excludeDomain}
${filterQuery}
group by 1
@@ -57,7 +66,7 @@ async function relationalQuery(
limit ${limit}
offset ${offset}
`,
- params,
+ { ...params, fieldName },
);
}
@@ -67,6 +76,8 @@ async function clickhouseQuery(
filters: QueryFilters,
limit: number = 500,
offset: number = 0,
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ fieldName?: string,
): Promise<{ x: string; y: number }[]> {
const { rawQuery, parseFilters } = clickhouse;
const { filterQuery, params } = await parseFilters(websiteId, {