mirror of
https://github.com/umami-software/umami.git
synced 2026-02-20 20:45:39 +01:00
allow showing pageview custom data on website detail page
This commit is contained in:
parent
46dd16260a
commit
996c02ef1a
5 changed files with 77 additions and 4 deletions
|
|
@ -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} />}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -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} />
|
||||||
|
|
|
||||||
29
src/components/metrics/PageviewCustomDataTable.tsx
Normal file
29
src/components/metrics/PageviewCustomDataTable.tsx
Normal 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;
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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, {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue