mirror of
https://github.com/umami-software/umami.git
synced 2025-12-08 05:12:36 +01:00
Allow filtering on session fields.
This commit is contained in:
parent
edd1645bab
commit
fb2dc9f5ab
19 changed files with 275 additions and 211 deletions
34
components/common/FilterLink.js
Normal file
34
components/common/FilterLink.js
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import React from 'react';
|
||||
import Link from 'next/link';
|
||||
import classNames from 'classnames';
|
||||
import usePageQuery from 'hooks/usePageQuery';
|
||||
import { safeDecodeURI } from 'lib/url';
|
||||
import Icon from './Icon';
|
||||
import External from 'assets/arrow-up-right-from-square.svg';
|
||||
import styles from './FilterLink.module.css';
|
||||
|
||||
export default function FilterLink({ id, value, label, externalUrl }) {
|
||||
const { resolve, query } = usePageQuery();
|
||||
const active = query[id] !== undefined;
|
||||
const selected = query[id] === value;
|
||||
|
||||
return (
|
||||
<div className={styles.row}>
|
||||
<Link href={resolve({ [id]: value })} replace>
|
||||
<a
|
||||
className={classNames(styles.label, {
|
||||
[styles.inactive]: active && !selected,
|
||||
[styles.active]: active && selected,
|
||||
})}
|
||||
>
|
||||
{safeDecodeURI(label || value)}
|
||||
</a>
|
||||
</Link>
|
||||
{externalUrl && (
|
||||
<a href={externalUrl} target="_blank" rel="noreferrer noopener" className={styles.link}>
|
||||
<Icon icon={<External />} className={styles.icon} />
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,19 +1,20 @@
|
|||
body .inactive {
|
||||
.row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.row .inactive {
|
||||
color: var(--gray500);
|
||||
}
|
||||
|
||||
body .active {
|
||||
.row .active {
|
||||
color: var(--gray900);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.row .link {
|
||||
display: none;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.row .label {
|
||||
|
|
@ -2,8 +2,13 @@ import React from 'react';
|
|||
import { FormattedMessage } from 'react-intl';
|
||||
import MetricsTable from './MetricsTable';
|
||||
import { browserFilter } from 'lib/filters';
|
||||
import FilterLink from '../common/FilterLink';
|
||||
|
||||
export default function BrowsersTable({ websiteId, ...props }) {
|
||||
function renderLink({ x: browser }) {
|
||||
return <FilterLink id="browser" value={browser} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<MetricsTable
|
||||
{...props}
|
||||
|
|
@ -12,6 +17,7 @@ export default function BrowsersTable({ websiteId, ...props }) {
|
|||
metric={<FormattedMessage id="metrics.visitors" defaultMessage="Visitors" />}
|
||||
websiteId={websiteId}
|
||||
dataFilter={browserFilter}
|
||||
renderLabel={renderLink}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||
import MetricsTable from './MetricsTable';
|
||||
import { percentFilter } from 'lib/filters';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import FilterLink from 'components/common/FilterLink';
|
||||
import useCountryNames from 'hooks/useCountryNames';
|
||||
import useLocale from 'hooks/useLocale';
|
||||
|
||||
|
|
@ -9,10 +10,16 @@ export default function CountriesTable({ websiteId, onDataLoad, ...props }) {
|
|||
const { locale } = useLocale();
|
||||
const countryNames = useCountryNames(locale);
|
||||
|
||||
function renderLabel({ x }) {
|
||||
function renderLink({ x: code }) {
|
||||
return (
|
||||
<div className={locale}>
|
||||
{countryNames[x] ?? <FormattedMessage id="label.unknown" defaultMessage="Unknown" />}
|
||||
<FilterLink
|
||||
id="country"
|
||||
value={code}
|
||||
label={
|
||||
countryNames[code] ?? <FormattedMessage id="label.unknown" defaultMessage="Unknown" />
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -25,7 +32,7 @@ export default function CountriesTable({ websiteId, onDataLoad, ...props }) {
|
|||
metric={<FormattedMessage id="metrics.visitors" defaultMessage="Visitors" />}
|
||||
websiteId={websiteId}
|
||||
onDataLoad={data => onDataLoad?.(percentFilter(data))}
|
||||
renderLabel={renderLabel}
|
||||
renderLabel={renderLink}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,18 @@
|
|||
import React from 'react';
|
||||
import MetricsTable from './MetricsTable';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { useIntl, FormattedMessage } from 'react-intl';
|
||||
import { getDeviceMessage } from 'components/messages';
|
||||
import FilterLink from 'components/common/FilterLink';
|
||||
|
||||
export default function DevicesTable({ websiteId, ...props }) {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
function renderLink({ x: device }) {
|
||||
return (
|
||||
<FilterLink id="device" value={device} label={formatMessage(getDeviceMessage(device))} />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<MetricsTable
|
||||
{...props}
|
||||
|
|
@ -11,7 +20,7 @@ export default function DevicesTable({ websiteId, ...props }) {
|
|||
type="device"
|
||||
metric={<FormattedMessage id="metrics.visitors" defaultMessage="Visitors" />}
|
||||
websiteId={websiteId}
|
||||
renderLabel={({ x }) => <FormattedMessage {...getDeviceMessage(x)} />}
|
||||
renderLabel={renderLink}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ export default function MetricsBar({ websiteId, className }) {
|
|||
const { startDate, endDate, modified } = dateRange;
|
||||
const [format, setFormat] = useState(true);
|
||||
const {
|
||||
query: { url, referrer },
|
||||
query: { url, referrer, os, browser, device, country },
|
||||
} = usePageQuery();
|
||||
|
||||
const { data, error, loading } = useFetch(
|
||||
|
|
@ -29,10 +29,14 @@ export default function MetricsBar({ websiteId, className }) {
|
|||
end_at: +endDate,
|
||||
url,
|
||||
referrer,
|
||||
os,
|
||||
browser,
|
||||
device,
|
||||
country,
|
||||
},
|
||||
headers: { [TOKEN_HEADER]: shareToken?.token },
|
||||
},
|
||||
[modified, url, referrer],
|
||||
[modified, url, referrer, os, browser, device, country],
|
||||
);
|
||||
|
||||
const formatFunc = format
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ export default function MetricsTable({
|
|||
const {
|
||||
resolve,
|
||||
router,
|
||||
query: { url, referrer },
|
||||
query: { url, referrer, os, browser, device, country },
|
||||
} = usePageQuery();
|
||||
|
||||
const { data, loading, error } = useFetch(
|
||||
|
|
@ -42,12 +42,16 @@ export default function MetricsTable({
|
|||
end_at: +endDate,
|
||||
url,
|
||||
referrer,
|
||||
os,
|
||||
browser,
|
||||
device,
|
||||
country,
|
||||
},
|
||||
onDataLoad,
|
||||
delay: DEFAULT_ANIMATION_DURATION,
|
||||
headers: { [TOKEN_HEADER]: shareToken?.token },
|
||||
},
|
||||
[modified, url, referrer],
|
||||
[modified, url, referrer, os, browser, device, country],
|
||||
);
|
||||
|
||||
const filteredData = useMemo(() => {
|
||||
|
|
|
|||
|
|
@ -1,14 +1,20 @@
|
|||
import React from 'react';
|
||||
import MetricsTable from './MetricsTable';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import FilterLink from 'components/common/FilterLink';
|
||||
|
||||
export default function OSTable({ websiteId, ...props }) {
|
||||
function renderLink({ x: os }) {
|
||||
return <FilterLink id="os" value={os} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<MetricsTable
|
||||
{...props}
|
||||
title={<FormattedMessage id="metrics.operating-systems" defaultMessage="Operating system" />}
|
||||
type="os"
|
||||
metric={<FormattedMessage id="metrics.visitors" defaultMessage="Visitors" />}
|
||||
renderLabel={renderLink}
|
||||
websiteId={websiteId}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,23 +1,15 @@
|
|||
import React, { useState } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import classNames from 'classnames';
|
||||
import Link from 'next/link';
|
||||
import FilterLink from 'components/common/FilterLink';
|
||||
import FilterButtons from 'components/common/FilterButtons';
|
||||
import { urlFilter } from 'lib/filters';
|
||||
import { safeDecodeURI } from 'lib/url';
|
||||
import usePageQuery from 'hooks/usePageQuery';
|
||||
import MetricsTable from './MetricsTable';
|
||||
import styles from './PagesTable.module.css';
|
||||
|
||||
export const FILTER_COMBINED = 0;
|
||||
export const FILTER_RAW = 1;
|
||||
|
||||
export default function PagesTable({ websiteId, websiteDomain, showFilters, ...props }) {
|
||||
const [filter, setFilter] = useState(FILTER_COMBINED);
|
||||
const {
|
||||
resolve,
|
||||
query: { url: currentUrl },
|
||||
} = usePageQuery();
|
||||
|
||||
const buttons = [
|
||||
{
|
||||
|
|
@ -28,18 +20,7 @@ export default function PagesTable({ websiteId, websiteDomain, showFilters, ...p
|
|||
];
|
||||
|
||||
const renderLink = ({ x: url }) => {
|
||||
return (
|
||||
<Link href={resolve({ url })} replace={true}>
|
||||
<a
|
||||
className={classNames({
|
||||
[styles.inactive]: currentUrl && url !== currentUrl,
|
||||
[styles.active]: url === currentUrl,
|
||||
})}
|
||||
>
|
||||
{safeDecodeURI(url)}
|
||||
</a>
|
||||
</Link>
|
||||
);
|
||||
return <FilterLink id="url" value={url} />;
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
body .inactive {
|
||||
color: var(--gray500);
|
||||
}
|
||||
|
||||
body .active {
|
||||
color: var(--gray900);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
|
@ -2,14 +2,8 @@ import React, { useState } from 'react';
|
|||
import { FormattedMessage } from 'react-intl';
|
||||
import MetricsTable from './MetricsTable';
|
||||
import FilterButtons from 'components/common/FilterButtons';
|
||||
import FilterLink from 'components/common/FilterLink';
|
||||
import { refFilter } from 'lib/filters';
|
||||
import { safeDecodeURI } from 'lib/url';
|
||||
import Link from 'next/link';
|
||||
import classNames from 'classnames';
|
||||
import usePageQuery from 'hooks/usePageQuery';
|
||||
import External from 'assets/arrow-up-right-from-square.svg';
|
||||
import Icon from '../common/Icon';
|
||||
import styles from './ReferrersTable.module.css';
|
||||
|
||||
export const FILTER_DOMAIN_ONLY = 0;
|
||||
export const FILTER_COMBINED = 1;
|
||||
|
|
@ -17,10 +11,6 @@ export const FILTER_RAW = 2;
|
|||
|
||||
export default function ReferrersTable({ websiteId, websiteDomain, showFilters, ...props }) {
|
||||
const [filter, setFilter] = useState(FILTER_COMBINED);
|
||||
const {
|
||||
resolve,
|
||||
query: { referrer: currentRef },
|
||||
} = usePageQuery();
|
||||
|
||||
const buttons = [
|
||||
{
|
||||
|
|
@ -34,24 +24,8 @@ export default function ReferrersTable({ websiteId, websiteDomain, showFilters,
|
|||
{ label: <FormattedMessage id="metrics.filter.raw" defaultMessage="Raw" />, value: FILTER_RAW },
|
||||
];
|
||||
|
||||
const renderLink = ({ w: link, x: label }) => {
|
||||
return (
|
||||
<div className={styles.row}>
|
||||
<Link href={resolve({ referrer: label })} replace={true}>
|
||||
<a
|
||||
className={classNames(styles.label, {
|
||||
[styles.inactive]: currentRef && label !== currentRef,
|
||||
[styles.active]: label === currentRef,
|
||||
})}
|
||||
>
|
||||
{safeDecodeURI(label)}
|
||||
</a>
|
||||
</Link>
|
||||
<a href={link || label} target="_blank" rel="noreferrer noopener" className={styles.link}>
|
||||
<Icon icon={<External />} className={styles.icon} />
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
const renderLink = ({ w: link, x: referrer }) => {
|
||||
return <FilterLink id="referrer" value={referrer} externalUrl={link} />;
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ export default function WebsiteChart({
|
|||
const {
|
||||
router,
|
||||
resolve,
|
||||
query: { url, referrer },
|
||||
query: { url, referrer, os, browser, device, country },
|
||||
} = usePageQuery();
|
||||
const { get } = useApi();
|
||||
|
||||
|
|
@ -47,11 +47,15 @@ export default function WebsiteChart({
|
|||
tz: timezone,
|
||||
url,
|
||||
referrer,
|
||||
os,
|
||||
browser,
|
||||
device,
|
||||
country,
|
||||
},
|
||||
onDataLoad,
|
||||
headers: { [TOKEN_HEADER]: shareToken?.token },
|
||||
},
|
||||
[modified, url, referrer],
|
||||
[modified, url, referrer, os, browser, device, country],
|
||||
);
|
||||
|
||||
const chartData = useMemo(() => {
|
||||
|
|
@ -88,7 +92,10 @@ export default function WebsiteChart({
|
|||
stickyClassName={styles.sticky}
|
||||
enabled={stickyHeader}
|
||||
>
|
||||
<FilterTags params={{ url, referrer }} onClick={handleCloseFilter} />
|
||||
<FilterTags
|
||||
params={{ url, referrer, os, browser, device, country }}
|
||||
onClick={handleCloseFilter}
|
||||
/>
|
||||
<div className="col-12 col-lg-9">
|
||||
<MetricsBar websiteId={websiteId} />
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue