mirror of
https://github.com/umami-software/umami.git
synced 2026-02-20 12:35:38 +01:00
Merge branch 'master' into custom-data-on-pageview
This commit is contained in:
commit
ef2d56fc78
7 changed files with 82 additions and 6 deletions
|
|
@ -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 && <Loading icon="dots" style={{ minHeight: 300 }} />}
|
||||
{website && (
|
||||
<>
|
||||
{!view && <WebsiteTableView websiteId={websiteId} domainName={website.domain} />}
|
||||
{!view && (
|
||||
<WebsiteTableView
|
||||
customDataFields={customDataFields}
|
||||
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 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({
|
|||
<OSTable {...tableProps} />
|
||||
<DevicesTable {...tableProps} />
|
||||
</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">
|
||||
<WorldMap data={countryData} />
|
||||
<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;
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
|||
on website_event.event_id = event_data.website_event_id
|
||||
where event_data.website_id = {{websiteId::uuid}}
|
||||
and event_data.created_at between {{startDate}} and {{endDate}}
|
||||
and website_event.event_name is not null
|
||||
group by website_event.event_name, event_data.event_key, event_data.data_type
|
||||
order by 1 asc, 2 asc
|
||||
limit 500
|
||||
|
|
|
|||
|
|
@ -32,8 +32,10 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
|||
event_key,
|
||||
count(*) as "total"
|
||||
from event_data
|
||||
where website_id = {{websiteId::uuid}}
|
||||
and created_at between {{startDate}} and {{endDate}}
|
||||
join website_event on website_event.event_id = event_data.website_event_id
|
||||
where website_event.website_id = {{websiteId::uuid}}
|
||||
and event_data.created_at between {{startDate}} and {{endDate}}
|
||||
and website_event.event_name is not null
|
||||
${filterQuery}
|
||||
group by website_event_id, event_key
|
||||
) as t
|
||||
|
|
|
|||
|
|
@ -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, {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue