mirror of
https://github.com/umami-software/umami.git
synced 2025-12-06 01:18:00 +01:00
Merge remote-tracking branch 'origin/dev' into dev
This commit is contained in:
commit
581ddc0233
7 changed files with 45 additions and 103 deletions
2
pnpm-lock.yaml
generated
2
pnpm-lock.yaml
generated
|
|
@ -367,8 +367,6 @@ importers:
|
|||
specifier: ^5.9.3
|
||||
version: 5.9.3
|
||||
|
||||
dist: {}
|
||||
|
||||
packages:
|
||||
|
||||
'@ampproject/remapping@2.3.0':
|
||||
|
|
|
|||
|
|
@ -1,35 +1,33 @@
|
|||
import { useState } from 'react';
|
||||
import { Grid, Row, Text } from '@umami/react-zen';
|
||||
import classNames from 'classnames';
|
||||
import { colord } from 'colord';
|
||||
import { BarChart } from '@/components/charts/BarChart';
|
||||
import { LoadingPanel } from '@/components/common/LoadingPanel';
|
||||
import { Panel } from '@/components/common/Panel';
|
||||
import { TypeIcon } from '@/components/common/TypeIcon';
|
||||
import { useCountryNames, useLocale, useMessages, useResultQuery } from '@/components/hooks';
|
||||
import { CurrencySelect } from '@/components/input/CurrencySelect';
|
||||
import { ListTable } from '@/components/metrics/ListTable';
|
||||
import { MetricCard } from '@/components/metrics/MetricCard';
|
||||
import { MetricsBar } from '@/components/metrics/MetricsBar';
|
||||
import { renderDateLabels } from '@/lib/charts';
|
||||
import { CHART_COLORS } from '@/lib/constants';
|
||||
import { generateTimeSeries } from '@/lib/date';
|
||||
import { formatLongCurrency, formatLongNumber } from '@/lib/format';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { Panel } from '@/components/common/Panel';
|
||||
import { Column } from '@umami/react-zen';
|
||||
import { LoadingPanel } from '@/components/common/LoadingPanel';
|
||||
import { getMinimumUnit } from '@/lib/date';
|
||||
import { CurrencySelect } from '@/components/input/CurrencySelect';
|
||||
import { Column, Grid, Row, Text } from '@umami/react-zen';
|
||||
import classNames from 'classnames';
|
||||
import { colord } from 'colord';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
export interface RevenueProps {
|
||||
websiteId: string;
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
unit: string;
|
||||
}
|
||||
|
||||
export function Revenue({ websiteId, startDate, endDate }: RevenueProps) {
|
||||
export function Revenue({ websiteId, startDate, endDate, unit }: RevenueProps) {
|
||||
const [currency, setCurrency] = useState('USD');
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { locale } = useLocale();
|
||||
const { locale, dateLocale } = useLocale();
|
||||
const { countryNames } = useCountryNames(locale);
|
||||
const unit = getMinimumUnit(startDate, endDate);
|
||||
const { data, error, isLoading } = useResultQuery<any>('revenue', {
|
||||
websiteId,
|
||||
startDate,
|
||||
|
|
@ -65,7 +63,7 @@ export function Revenue({ websiteId, startDate, endDate }: RevenueProps) {
|
|||
const color = colord(CHART_COLORS[index % CHART_COLORS.length]);
|
||||
return {
|
||||
label: key,
|
||||
data: map[key],
|
||||
data: generateTimeSeries(map[key], startDate, endDate, unit, dateLocale),
|
||||
lineTension: 0,
|
||||
backgroundColor: color.alpha(0.6).toRgbString(),
|
||||
borderColor: color.alpha(0.7).toRgbString(),
|
||||
|
|
@ -104,6 +102,8 @@ export function Revenue({ websiteId, startDate, endDate }: RevenueProps) {
|
|||
] as any;
|
||||
}, [data, locale]);
|
||||
|
||||
const renderXLabel = useCallback(renderDateLabels(unit, locale), [unit, locale]);
|
||||
|
||||
return (
|
||||
<Column gap>
|
||||
<Grid columns="280px" gap>
|
||||
|
|
@ -127,7 +127,7 @@ export function Revenue({ websiteId, startDate, endDate }: RevenueProps) {
|
|||
unit={unit}
|
||||
stacked={true}
|
||||
currency={currency}
|
||||
renderXLabel={renderDateLabels(unit, locale)}
|
||||
renderXLabel={renderXLabel}
|
||||
height="400px"
|
||||
/>
|
||||
</Panel>
|
||||
|
|
|
|||
|
|
@ -6,13 +6,13 @@ import { useDateRange } from '@/components/hooks';
|
|||
|
||||
export function RevenuePage({ websiteId }: { websiteId: string }) {
|
||||
const {
|
||||
dateRange: { startDate, endDate },
|
||||
dateRange: { startDate, endDate, unit },
|
||||
} = useDateRange(websiteId);
|
||||
|
||||
return (
|
||||
<Column gap>
|
||||
<WebsiteControls websiteId={websiteId} />
|
||||
<Revenue websiteId={websiteId} startDate={startDate} endDate={endDate} />
|
||||
<Revenue websiteId={websiteId} startDate={startDate} endDate={endDate} unit={unit} />
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,8 +37,8 @@ export function WebsiteTransferForm({
|
|||
const isTeamWebsite = !!website?.teamId;
|
||||
|
||||
const items =
|
||||
teams?.data?.filter(({ teamUser }) =>
|
||||
teamUser.find(
|
||||
teams?.data?.filter(({ members }) =>
|
||||
members.some(
|
||||
({ role, userId }) =>
|
||||
[ROLES.teamOwner, ROLES.teamManager].includes(role) && userId === user.id,
|
||||
),
|
||||
|
|
@ -79,7 +79,7 @@ export function WebsiteTransferForm({
|
|||
<Select onSelectionChange={handleChange} selectedKey={teamId}>
|
||||
{items.map(({ id, name }) => {
|
||||
return (
|
||||
<ListItem key={`${id}!!!!`} id={`${id}????`}>
|
||||
<ListItem key={`${id}`} id={`${id}`}>
|
||||
{name}
|
||||
</ListItem>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -73,19 +73,23 @@ export function MetricsTable({
|
|||
isFetching={isFetching}
|
||||
isLoading={isLoading}
|
||||
error={error}
|
||||
minHeight="380px"
|
||||
minHeight="400px"
|
||||
>
|
||||
{data && <ListTable {...props} data={filteredData} renderLabel={renderLabel} />}
|
||||
{showMore && limit && (
|
||||
<Row justifyContent="center">
|
||||
<LinkButton href={updateParams({ view: type })} variant="quiet">
|
||||
<Icon size="sm">
|
||||
<Maximize />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.more)}</Text>
|
||||
</LinkButton>
|
||||
</Row>
|
||||
)}
|
||||
<div style={{ display: 'grid', gridTemplateRows: '1fr auto', minHeight: '400px' }}>
|
||||
<div>{data && <ListTable {...props} data={filteredData} renderLabel={renderLabel} />}</div>
|
||||
<div>
|
||||
{showMore && limit && (
|
||||
<Row justifyContent="center" alignItems="flex-end">
|
||||
<LinkButton href={updateParams({ view: type })} variant="quiet">
|
||||
<Icon size="sm">
|
||||
<Maximize />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.more)}</Text>
|
||||
</LinkButton>
|
||||
</Row>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</LoadingPanel>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -122,8 +122,9 @@ export function parseDateValue(value: string) {
|
|||
if (!match) return null;
|
||||
|
||||
const { num, unit } = match.groups;
|
||||
const formattedNum = +num > 0 ? +num - 1 : +num;
|
||||
|
||||
return { num: +num, unit };
|
||||
return { num: formattedNum, unit };
|
||||
}
|
||||
|
||||
export function parseDateRange(value: string, locale = 'en-US'): DateRange {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ export interface RevenuParameters {
|
|||
startDate: Date;
|
||||
endDate: Date;
|
||||
unit: string;
|
||||
timezone: string;
|
||||
currency: string;
|
||||
}
|
||||
|
||||
|
|
@ -14,12 +15,6 @@ export interface RevenueResult {
|
|||
chart: { x: string; t: string; y: number }[];
|
||||
country: { name: string; value: number }[];
|
||||
total: { sum: number; count: number; average: number; unique_count: number };
|
||||
table: {
|
||||
currency: string;
|
||||
sum: number;
|
||||
count: number;
|
||||
unique_count: number;
|
||||
}[];
|
||||
}
|
||||
|
||||
export async function getRevenue(
|
||||
|
|
@ -36,7 +31,7 @@ async function relationalQuery(
|
|||
parameters: RevenuParameters,
|
||||
filters: QueryFilters,
|
||||
): Promise<RevenueResult> {
|
||||
const { startDate, endDate, currency, unit = 'day' } = parameters;
|
||||
const { startDate, endDate, unit = 'day', timezone = 'utc', currency } = parameters;
|
||||
const { getDateSQL, rawQuery, parseFilters } = prisma;
|
||||
const { queryParams, filterQuery, cohortQuery, joinSessionQuery } = parseFilters({
|
||||
...filters,
|
||||
|
|
@ -50,7 +45,7 @@ async function relationalQuery(
|
|||
`
|
||||
select
|
||||
revenue.event_name x,
|
||||
${getDateSQL('revenue.created_at', unit)} t,
|
||||
${getDateSQL('revenue.created_at', unit, timezone)} t,
|
||||
sum(revenue.revenue) y
|
||||
from revenue
|
||||
join website_event
|
||||
|
|
@ -121,32 +116,7 @@ async function relationalQuery(
|
|||
|
||||
total.average = total.count > 0 ? total.sum / total.count : 0;
|
||||
|
||||
const table = await rawQuery(
|
||||
`
|
||||
select
|
||||
revenue.currency,
|
||||
sum(revenue.revenue) as sum,
|
||||
count(distinct revenue.event_id) as count,
|
||||
count(distinct revenue.session_id) as unique_count
|
||||
from revenue
|
||||
join website_event
|
||||
on website_event.website_id = revenue.website_id
|
||||
and website_event.session_id = revenue.session_id
|
||||
and website_event.event_id = revenue.event_id
|
||||
and website_event.website_id = {{websiteId::uuid}}
|
||||
and website_event.created_at between {{startDate}} and {{endDate}}
|
||||
${cohortQuery}
|
||||
${joinSessionQuery}
|
||||
where revenue.website_id = {{websiteId::uuid}}
|
||||
and revenue.created_at between {{startDate}} and {{endDate}}
|
||||
${filterQuery}
|
||||
group by revenue.currency
|
||||
order by sum desc
|
||||
`,
|
||||
queryParams,
|
||||
);
|
||||
|
||||
return { chart, country, table, total };
|
||||
return { chart, country, total };
|
||||
}
|
||||
|
||||
async function clickhouseQuery(
|
||||
|
|
@ -154,7 +124,7 @@ async function clickhouseQuery(
|
|||
parameters: RevenuParameters,
|
||||
filters: QueryFilters,
|
||||
): Promise<RevenueResult> {
|
||||
const { startDate, endDate, unit = 'day', currency } = parameters;
|
||||
const { startDate, endDate, unit = 'day', timezone = 'utc', currency } = parameters;
|
||||
const { getDateSQL, rawQuery, parseFilters } = clickhouse;
|
||||
const { filterQuery, cohortQuery, queryParams } = parseFilters({
|
||||
...filters,
|
||||
|
|
@ -174,7 +144,7 @@ async function clickhouseQuery(
|
|||
`
|
||||
select
|
||||
website_revenue.event_name x,
|
||||
${getDateSQL('website_revenue.created_at', unit)} t,
|
||||
${getDateSQL('website_revenue.created_at', unit, timezone)} t,
|
||||
sum(website_revenue.revenue) y
|
||||
from website_revenue
|
||||
join website_event
|
||||
|
|
@ -250,36 +220,5 @@ async function clickhouseQuery(
|
|||
|
||||
total.average = total.count > 0 ? total.sum / total.count : 0;
|
||||
|
||||
const table = await rawQuery<
|
||||
{
|
||||
currency: string;
|
||||
sum: number;
|
||||
count: number;
|
||||
unique_count: number;
|
||||
}[]
|
||||
>(
|
||||
`
|
||||
select
|
||||
website_revenue.currency,
|
||||
sum(website_revenue.revenue) as sum,
|
||||
uniqExact(website_revenue.event_id) as count,
|
||||
uniqExact(website_revenue.session_id) as unique_count
|
||||
from website_revenue
|
||||
join website_event
|
||||
on website_event.website_id = website_revenue.website_id
|
||||
and website_event.session_id = website_revenue.session_id
|
||||
and website_event.event_id = website_revenue.event_id
|
||||
and website_event.website_id = {websiteId:UUID}
|
||||
and website_event.created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||
${cohortQuery}
|
||||
where website_revenue.website_id = {websiteId:UUID}
|
||||
and website_revenue.created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||
${filterQuery}
|
||||
group by website_revenue.currency
|
||||
order by sum desc
|
||||
`,
|
||||
queryParams,
|
||||
);
|
||||
|
||||
return { chart, country, table, total };
|
||||
return { chart, country, total };
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue