Skip to content

Optimized MetricsBar & Fixed Mobile Layout #3258

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: dev
Choose a base branch
from
Open
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/1.bug_report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ body:
- type: input
attributes:
label: Which Umami version are you using? (if relevant)
description: 'For example: Chrome, Edge, Firefox, etc'
description: 'For example: 2.18.0, 2.15.1, 1.39.0, etc'
- type: input
attributes:
label: Which browser are you using? (if relevant)
Expand Down
5 changes: 3 additions & 2 deletions scripts/data-migrations/convert-utm-clid-columns.sql
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ FROM (SELECT event_id, website_id, session_id,
(regexp_matches(url_query, '(?:[&?]|^)utm_medium=([^&]+)', 'i'))[1] AS utm_medium,
(regexp_matches(url_query, '(?:[&?]|^)utm_source=([^&]+)', 'i'))[1] AS utm_source,
(regexp_matches(url_query, '(?:[&?]|^)utm_term=([^&]+)', 'i'))[1] AS utm_term
FROM "website_event") url
FROM "website_event"
WHERE url_query IS NOT NULL) url
WHERE we.event_id = url.event_id
and we.session_id = url.session_id
and we.website_id = url.website_id;
Expand All @@ -45,4 +46,4 @@ SET fbclid = LEFT(SUBSTRING_INDEX(SUBSTRING_INDEX(REGEXP_SUBSTR(url_query, '(?:[
utm_medium = LEFT(SUBSTRING_INDEX(SUBSTRING_INDEX(REGEXP_SUBSTR(url_query, '(?:[&?]|^)utm_medium=[^&]+'), '=', -1), '&', 1), 255),
utm_source = LEFT(SUBSTRING_INDEX(SUBSTRING_INDEX(REGEXP_SUBSTR(url_query, '(?:[&?]|^)utm_source=[^&]+'), '=', -1), '&', 1), 255),
utm_term = LEFT(SUBSTRING_INDEX(SUBSTRING_INDEX(REGEXP_SUBSTR(url_query, '(?:[&?]|^)utm_term=[^&]+'), '=', -1), '&', 1), 255)
WHERE 1 = 1;
WHERE url_query IS NOT NULL;
13 changes: 8 additions & 5 deletions src/app/(main)/websites/[websiteId]/WebsiteChartList.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { Button, Text, Icon, Icons } from 'react-basics';
import { useLocale, useMessages, useTeamUrl } from '@/components/hooks';
import useDashboard from '@/store/dashboard';
import Link from 'next/link';
import { useMemo } from 'react';
import { Button, Icon, Icons, Text } from 'react-basics';
import { firstBy } from 'thenby';
import Link from 'next/link';
import WebsiteChart from './WebsiteChart';
import useDashboard from '@/store/dashboard';
import WebsiteHeader from './WebsiteHeader';
import WebsiteMetrics from './WebsiteMetrics';
import { WebsiteMetricsBar } from './WebsiteMetricsBar';
import { useMessages, useLocale, useTeamUrl } from '@/components/hooks';

export default function WebsiteChartList({
websites,
Expand Down Expand Up @@ -46,7 +47,9 @@ export default function WebsiteChartList({
</Button>
</Link>
</WebsiteHeader>
<WebsiteMetricsBar websiteId={id} showChange={true} />
<WebsiteMetrics websiteId={id}>
<WebsiteMetricsBar websiteId={id} showChange={true} />
</WebsiteMetrics>
{showCharts && <WebsiteChart websiteId={id} />}
</div>
) : null;
Expand Down
11 changes: 7 additions & 4 deletions src/app/(main)/websites/[websiteId]/WebsiteDetailsPage.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
'use client';
import { usePathname } from 'next/navigation';
import FilterTags from '@/components/metrics/FilterTags';
import { useNavigation } from '@/components/hooks';
import FilterTags from '@/components/metrics/FilterTags';
import { FILTER_COLUMNS } from '@/lib/constants';
import { usePathname } from 'next/navigation';
import WebsiteChart from './WebsiteChart';
import WebsiteExpandedView from './WebsiteExpandedView';
import WebsiteHeader from './WebsiteHeader';
import WebsiteMetrics from './WebsiteMetrics';
import WebsiteMetricsBar from './WebsiteMetricsBar';
import WebsiteTableView from './WebsiteTableView';
import { FILTER_COLUMNS } from '@/lib/constants';

export default function WebsiteDetailsPage({ websiteId }: { websiteId: string }) {
const pathname = usePathname();
Expand All @@ -27,7 +28,9 @@ export default function WebsiteDetailsPage({ websiteId }: { websiteId: string })
<>
<WebsiteHeader websiteId={websiteId} showLinks={showLinks} />
<FilterTags websiteId={websiteId} params={params} />
<WebsiteMetricsBar websiteId={websiteId} showFilter={true} showChange={true} sticky={true} />
<WebsiteMetrics websiteId={websiteId} showFilter={true} sticky={true}>
<WebsiteMetricsBar websiteId={websiteId} showChange={true} />
</WebsiteMetrics>
<WebsiteChart websiteId={websiteId} />
{!view && <WebsiteTableView websiteId={websiteId} />}
{view && <WebsiteExpandedView websiteId={websiteId} />}
Expand Down
66 changes: 66 additions & 0 deletions src/app/(main)/websites/[websiteId]/WebsiteMetrics.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { useMessages, useSticky } from '@/components/hooks';
import WebsiteDateFilter from '@/components/input/WebsiteDateFilter';
import useStore, { setWebsiteDateCompare } from '@/store/websites';
import classNames from 'classnames';
import { ReactNode } from 'react';
import { Dropdown, Item } from 'react-basics';
import WebsiteFilterButton from './WebsiteFilterButton';
import styles from './WebsiteMetrics.module.css';

export function WebsiteMetrics({
websiteId,
sticky,
compareMode = false,
showFilter = false,
children,
}: {
websiteId: string;
sticky?: boolean;
compareMode?: boolean;
showFilter?: boolean;
children?: ReactNode;
}) {
const { formatMessage, labels } = useMessages();
const { ref, isSticky } = useSticky({ enabled: sticky });
const dateCompare = useStore(state => state[websiteId]?.dateCompare);

const items = [
{ label: formatMessage(labels.previousPeriod), value: 'prev' },
{ label: formatMessage(labels.previousYear), value: 'yoy' },
];

return (
<div
ref={ref}
className={classNames(styles.container, {
[styles.sticky]: sticky,
[styles.isSticky]: sticky && isSticky,
})}
>
<div>{children}</div>
<div className={styles.actions}>
{showFilter && <WebsiteFilterButton websiteId={websiteId} />}
<WebsiteDateFilter websiteId={websiteId} showAllTime={!compareMode} />
{compareMode && (
<div className={styles.vs}>
<b>VS</b>
<Dropdown
className={styles.dropdown}
items={items}
value={dateCompare || 'prev'}
renderValue={value => items.find(i => i.value === value)?.label}
alignment="end"
onChange={(value: any) => setWebsiteDateCompare(websiteId, value)}
>
{items.map(({ label, value }) => (
<Item key={value}>{label}</Item>
))}
</Dropdown>
</div>
)}
</div>
</div>
);
}

export default WebsiteMetrics;
84 changes: 20 additions & 64 deletions src/app/(main)/websites/[websiteId]/WebsiteMetricsBar.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,14 @@
import { Dropdown, Item } from 'react-basics';
import classNames from 'classnames';
import { useDateRange, useMessages, useSticky } from '@/components/hooks';
import WebsiteDateFilter from '@/components/input/WebsiteDateFilter';
import { useDateRange, useMessages } from '@/components/hooks';
import useWebsiteStats from '@/components/hooks/queries/useWebsiteStats';
import MetricCard from '@/components/metrics/MetricCard';
import MetricsBar from '@/components/metrics/MetricsBar';
import { formatShortTime, formatLongNumber } from '@/lib/format';
import useWebsiteStats from '@/components/hooks/queries/useWebsiteStats';
import useStore, { setWebsiteDateCompare } from '@/store/websites';
import WebsiteFilterButton from './WebsiteFilterButton';
import styles from './WebsiteMetricsBar.module.css';
import { formatLongNumber, formatShortTime } from '@/lib/format';
import useStore from '@/store/websites';

export function WebsiteMetricsBar({
websiteId,
sticky,
showChange = false,
compareMode = false,
showFilter = false,
}: {
websiteId: string;
sticky?: boolean;
Expand All @@ -26,7 +19,6 @@ export function WebsiteMetricsBar({
const { dateRange } = useDateRange(websiteId);
const { formatMessage, labels } = useMessages();
const dateCompare = useStore(state => state[websiteId]?.dateCompare);
const { ref, isSticky } = useSticky({ enabled: sticky });
const { data, isLoading, isFetched, error } = useWebsiteStats(
websiteId,
compareMode && dateCompare,
Expand Down Expand Up @@ -76,60 +68,24 @@ export function WebsiteMetricsBar({
]
: [];

const items = [
{ label: formatMessage(labels.previousPeriod), value: 'prev' },
{ label: formatMessage(labels.previousYear), value: 'yoy' },
];

return (
<div
ref={ref}
className={classNames(styles.container, {
[styles.sticky]: sticky,
[styles.isSticky]: sticky && isSticky,
<MetricsBar isLoading={isLoading} isFetched={isFetched} error={error}>
{metrics.map(({ label, value, prev, change, formatValue, reverseColors }) => {
return (
<MetricCard
key={label}
value={value}
previousValue={prev}
label={label}
change={change}
formatValue={formatValue}
reverseColors={reverseColors}
showChange={!isAllTime && (compareMode || showChange)}
showPrevious={!isAllTime && compareMode}
/>
);
})}
>
<div>
<MetricsBar isLoading={isLoading} isFetched={isFetched} error={error}>
{metrics.map(({ label, value, prev, change, formatValue, reverseColors }) => {
return (
<MetricCard
key={label}
value={value}
previousValue={prev}
label={label}
change={change}
formatValue={formatValue}
reverseColors={reverseColors}
showChange={!isAllTime && (compareMode || showChange)}
showPrevious={!isAllTime && compareMode}
/>
);
})}
</MetricsBar>
</div>
<div className={styles.actions}>
{showFilter && <WebsiteFilterButton websiteId={websiteId} />}
<WebsiteDateFilter websiteId={websiteId} showAllTime={!compareMode} />
{compareMode && (
<div className={styles.vs}>
<b>VS</b>
<Dropdown
className={styles.dropdown}
items={items}
value={dateCompare || 'prev'}
renderValue={value => items.find(i => i.value === value)?.label}
alignment="end"
onChange={(value: any) => setWebsiteDateCompare(websiteId, value)}
>
{items.map(({ label, value }) => (
<Item key={value}>{label}</Item>
))}
</Dropdown>
</div>
)}
</div>
</div>
</MetricsBar>
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
'use client';
import WebsiteHeader from '../WebsiteHeader';
import WebsiteMetricsBar from '../WebsiteMetricsBar';
import FilterTags from '@/components/metrics/FilterTags';
import { useNavigation } from '@/components/hooks';
import FilterTags from '@/components/metrics/FilterTags';
import { FILTER_COLUMNS } from '@/lib/constants';
import WebsiteChart from '../WebsiteChart';
import WebsiteHeader from '../WebsiteHeader';
import WebsiteMetrics from '../WebsiteMetrics';
import WebsiteMetricsBar from '../WebsiteMetricsBar';
import WebsiteCompareTables from './WebsiteCompareTables';

export function WebsiteComparePage({ websiteId }) {
Expand All @@ -21,7 +22,9 @@ export function WebsiteComparePage({ websiteId }) {
<>
<WebsiteHeader websiteId={websiteId} />
<FilterTags websiteId={websiteId} params={params} />
<WebsiteMetricsBar websiteId={websiteId} compareMode={true} showFilter={true} />
<WebsiteMetrics websiteId={websiteId} compareMode={true} showFilter={true}>
<WebsiteMetricsBar websiteId={websiteId} compareMode={true} />
</WebsiteMetrics>
<WebsiteChart websiteId={websiteId} compareMode={true} />
<WebsiteCompareTables websiteId={websiteId} />
</>
Expand Down
49 changes: 22 additions & 27 deletions src/app/(main)/websites/[websiteId]/events/EventsMetricsBar.tsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,36 @@
import { useMessages } from '@/components/hooks';
import useWebsiteSessionStats from '@/components/hooks/queries/useWebsiteSessionStats';
import WebsiteDateFilter from '@/components/input/WebsiteDateFilter';
import MetricCard from '@/components/metrics/MetricCard';
import MetricsBar from '@/components/metrics/MetricsBar';
import { formatLongNumber } from '@/lib/format';
import { Flexbox } from 'react-basics';

export function EventsMetricsBar({ websiteId }: { websiteId: string }) {
const { formatMessage, labels } = useMessages();
const { data, isLoading, isFetched, error } = useWebsiteSessionStats(websiteId);

return (
<Flexbox direction="row" justifyContent="space-between" style={{ minHeight: 120 }}>
<MetricsBar isLoading={isLoading} isFetched={isFetched} error={error}>
<MetricCard
value={data?.visitors?.value}
label={formatMessage(labels.visitors)}
formatValue={formatLongNumber}
/>
<MetricCard
value={data?.visits?.value}
label={formatMessage(labels.visits)}
formatValue={formatLongNumber}
/>
<MetricCard
value={data?.pageviews?.value}
label={formatMessage(labels.views)}
formatValue={formatLongNumber}
/>
<MetricCard
value={data?.events?.value}
label={formatMessage(labels.events)}
formatValue={formatLongNumber}
/>
</MetricsBar>
<WebsiteDateFilter websiteId={websiteId} />
</Flexbox>
<MetricsBar isLoading={isLoading} isFetched={isFetched} error={error}>
<MetricCard
value={data?.visitors?.value}
label={formatMessage(labels.visitors)}
formatValue={formatLongNumber}
/>
<MetricCard
value={data?.visits?.value}
label={formatMessage(labels.visits)}
formatValue={formatLongNumber}
/>
<MetricCard
value={data?.pageviews?.value}
label={formatMessage(labels.views)}
formatValue={formatLongNumber}
/>
<MetricCard
value={data?.events?.value}
label={formatMessage(labels.events)}
formatValue={formatLongNumber}
/>
</MetricsBar>
);
}

Expand Down
17 changes: 10 additions & 7 deletions src/app/(main)/websites/[websiteId]/events/EventsPage.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
'use client';
import WebsiteHeader from '../WebsiteHeader';
import EventsDataTable from './EventsDataTable';
import EventsMetricsBar from './EventsMetricsBar';
import EventsChart from '@/components/metrics/EventsChart';
import { useMessages } from '@/components/hooks';
import { GridRow } from '@/components/layout/Grid';
import EventsChart from '@/components/metrics/EventsChart';
import EventsTable from '@/components/metrics/EventsTable';
import { useMessages } from '@/components/hooks';
import { Item, Tabs } from 'react-basics';
import { useState } from 'react';
import { Item, Tabs } from 'react-basics';
import WebsiteHeader from '../WebsiteHeader';
import WebsiteMetrics from '../WebsiteMetrics';
import EventProperties from './EventProperties';
import EventsDataTable from './EventsDataTable';
import EventsMetricsBar from './EventsMetricsBar';

export default function EventsPage({ websiteId }) {
const [label, setLabel] = useState(null);
Expand All @@ -22,7 +23,9 @@ export default function EventsPage({ websiteId }) {
return (
<>
<WebsiteHeader websiteId={websiteId} />
<EventsMetricsBar websiteId={websiteId} />
<WebsiteMetrics websiteId={websiteId}>
<EventsMetricsBar websiteId={websiteId} />
</WebsiteMetrics>
<GridRow columns="two-one">
<EventsChart websiteId={websiteId} focusLabel={label} />
<EventsTable
Expand Down
Loading