mirror of
https://github.com/umami-software/umami.git
synced 2026-02-08 06:37:18 +01:00
Added regions and cities support.
This commit is contained in:
parent
ad3ee5e953
commit
f2edd0d604
58 changed files with 293 additions and 70 deletions
|
|
@ -32,9 +32,13 @@ export default function HamburgerButton() {
|
|||
label: formatMessage(labels.users),
|
||||
url: '/settings/users',
|
||||
},
|
||||
{
|
||||
label: formatMessage(labels.profile),
|
||||
url: '/settings/profile',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
cloudMode && {
|
||||
label: formatMessage(labels.profile),
|
||||
url: '/settings/profile',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ import styles from './WorldMap.module.css';
|
|||
import useCountryNames from 'hooks/useCountryNames';
|
||||
import useLocale from 'hooks/useLocale';
|
||||
import HoverTooltip from './HoverTooltip';
|
||||
import { formatLongNumber } from '../../lib/format';
|
||||
import { formatLongNumber } from 'lib/format';
|
||||
import { percentFilter } from 'lib/filters';
|
||||
|
||||
function WorldMap({ data, className }) {
|
||||
const { basePath } = useRouter();
|
||||
|
|
@ -26,10 +27,11 @@ function WorldMap({ data, className }) {
|
|||
);
|
||||
const { locale } = useLocale();
|
||||
const countryNames = useCountryNames(locale);
|
||||
const metrics = useMemo(() => percentFilter(data), [data]);
|
||||
|
||||
function getFillColor(code) {
|
||||
if (code === 'AQ') return;
|
||||
const country = data?.find(({ x }) => x === code);
|
||||
const country = metrics?.find(({ x }) => x === code);
|
||||
|
||||
if (!country) {
|
||||
return colors.fillColor;
|
||||
|
|
@ -46,7 +48,7 @@ function WorldMap({ data, className }) {
|
|||
|
||||
function handleHover(code) {
|
||||
if (code === 'AQ') return;
|
||||
const country = data?.find(({ x }) => x === code);
|
||||
const country = metrics?.find(({ x }) => x === code);
|
||||
setTooltip(`${countryNames[code]}: ${formatLongNumber(country?.y || 0)} visitors`);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -113,6 +113,8 @@ export const labels = defineMessages({
|
|||
editDashboard: { id: 'label.edit-dashboard', defaultMessage: 'Edit dashboard' },
|
||||
title: { id: 'label.title', defaultMessage: 'Title' },
|
||||
view: { id: 'label.view', defaultMessage: 'View' },
|
||||
cities: { id: 'label.cities', defaultMessage: 'Cities' },
|
||||
regions: { id: 'label.regions', defaultMessage: 'Regions' },
|
||||
});
|
||||
|
||||
export const messages = defineMessages({
|
||||
|
|
|
|||
30
components/metrics/CitiesTable.js
Normal file
30
components/metrics/CitiesTable.js
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import MetricsTable from './MetricsTable';
|
||||
import { emptyFilter } from 'lib/filters';
|
||||
import FilterLink from 'components/common/FilterLink';
|
||||
import useLocale from 'hooks/useLocale';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function CitiesTable({ websiteId, ...props }) {
|
||||
const { locale } = useLocale();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
function renderLink({ x }) {
|
||||
return (
|
||||
<div className={locale}>
|
||||
<FilterLink id="city" value={x} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<MetricsTable
|
||||
{...props}
|
||||
title={formatMessage(labels.cities)}
|
||||
type="city"
|
||||
metric={formatMessage(labels.visitors)}
|
||||
websiteId={websiteId}
|
||||
dataFilter={emptyFilter}
|
||||
renderLabel={renderLink}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,11 +1,10 @@
|
|||
import MetricsTable from './MetricsTable';
|
||||
import { percentFilter } from 'lib/filters';
|
||||
import FilterLink from 'components/common/FilterLink';
|
||||
import useCountryNames from 'hooks/useCountryNames';
|
||||
import useLocale from 'hooks/useLocale';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function CountriesTable({ websiteId, onDataLoad, ...props }) {
|
||||
export default function CountriesTable({ websiteId, ...props }) {
|
||||
const { locale } = useLocale();
|
||||
const countryNames = useCountryNames(locale);
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
|
@ -25,7 +24,6 @@ export default function CountriesTable({ websiteId, onDataLoad, ...props }) {
|
|||
type="country"
|
||||
metric={formatMessage(labels.visitors)}
|
||||
websiteId={websiteId}
|
||||
onDataLoad={data => onDataLoad?.(percentFilter(data))}
|
||||
renderLabel={renderLink}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ export default function MetricsTable({
|
|||
const {
|
||||
resolveUrl,
|
||||
router,
|
||||
query: { url, referrer, os, browser, device, country },
|
||||
query: { url, referrer, os, browser, device, country, region, city },
|
||||
} = usePageQuery();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { get, useQuery } = useApi();
|
||||
|
|
@ -37,7 +37,7 @@ export default function MetricsTable({
|
|||
const { data, isLoading, isFetched, error } = useQuery(
|
||||
[
|
||||
'websites:metrics',
|
||||
{ websiteId, type, modified, url, referrer, os, browser, device, country },
|
||||
{ websiteId, type, modified, url, referrer, os, browser, device, country, region, city },
|
||||
],
|
||||
() =>
|
||||
get(`/websites/${websiteId}/metrics`, {
|
||||
|
|
@ -50,6 +50,8 @@ export default function MetricsTable({
|
|||
browser,
|
||||
device,
|
||||
country,
|
||||
region,
|
||||
city,
|
||||
}),
|
||||
{ onSuccess: onDataLoad, retryDelay: delay || DEFAULT_ANIMATION_DURATION },
|
||||
);
|
||||
|
|
|
|||
31
components/metrics/RegionsTable.js
Normal file
31
components/metrics/RegionsTable.js
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import MetricsTable from './MetricsTable';
|
||||
import { emptyFilter } from 'lib/filters';
|
||||
import FilterLink from 'components/common/FilterLink';
|
||||
import useLocale from 'hooks/useLocale';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
import regions from 'public/iso-3166-2.json';
|
||||
|
||||
export default function RegionsTable({ websiteId, ...props }) {
|
||||
const { locale } = useLocale();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
function renderLink({ x }) {
|
||||
return (
|
||||
<div className={locale}>
|
||||
<FilterLink id="region" value={x} label={regions[x] || x} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<MetricsTable
|
||||
{...props}
|
||||
title={formatMessage(labels.regions)}
|
||||
type="region"
|
||||
metric={formatMessage(labels.visitors)}
|
||||
websiteId={websiteId}
|
||||
dataFilter={emptyFilter}
|
||||
renderLabel={renderLink}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -33,13 +33,16 @@ export default function WebsiteChart({
|
|||
const { startDate, endDate, unit, value, modified } = dateRange;
|
||||
const [timezone] = useTimezone();
|
||||
const {
|
||||
query: { url, referrer, os, browser, device, country, title },
|
||||
query: { url, referrer, os, browser, device, country, region, city, title },
|
||||
} = usePageQuery();
|
||||
const { get, useQuery } = useApi();
|
||||
const { ref, isSticky } = useSticky({ enabled: stickyHeader });
|
||||
|
||||
const { data, isLoading, error } = useQuery(
|
||||
['websites:pageviews', { websiteId, modified, url, referrer, os, browser, device, country }],
|
||||
[
|
||||
'websites:pageviews',
|
||||
{ websiteId, modified, url, referrer, os, browser, device, country, region, city, title },
|
||||
],
|
||||
() =>
|
||||
get(`/websites/${websiteId}/pageviews`, {
|
||||
startAt: +startDate,
|
||||
|
|
@ -52,6 +55,9 @@ export default function WebsiteChart({
|
|||
browser,
|
||||
device,
|
||||
country,
|
||||
region,
|
||||
city,
|
||||
title,
|
||||
}),
|
||||
{ onSuccess: onDataLoad },
|
||||
);
|
||||
|
|
@ -82,7 +88,7 @@ export default function WebsiteChart({
|
|||
</WebsiteHeader>
|
||||
<FilterTags
|
||||
websiteId={websiteId}
|
||||
params={{ url, referrer, os, browser, device, country, title }}
|
||||
params={{ url, referrer, os, browser, device, country, region, city, title }}
|
||||
/>
|
||||
<Row
|
||||
ref={ref}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ export default function TeamDeleteForm({ teamId, teamName, onSave, onClose }) {
|
|||
return (
|
||||
<Form onSubmit={handleSubmit} error={error}>
|
||||
<p>
|
||||
<FormattedMessage {...messages.confirmDelete} values={{ name: <b>{teamName}</b> }} />
|
||||
<FormattedMessage {...messages.confirmDelete} values={{ target: <b>{teamName}</b> }} />
|
||||
</p>
|
||||
<FormButtons flex>
|
||||
<SubmitButton variant="danger" disabled={isLoading}>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ import Link from 'next/link';
|
|||
import { GridRow, GridColumn } from 'components/layout/Grid';
|
||||
import BrowsersTable from 'components/metrics/BrowsersTable';
|
||||
import CountriesTable from 'components/metrics/CountriesTable';
|
||||
import RegionsTable from 'components/metrics/RegionsTable';
|
||||
import CitiesTable from 'components/metrics/CitiesTable';
|
||||
import DevicesTable from 'components/metrics/DevicesTable';
|
||||
import LanguagesTable from 'components/metrics/LanguagesTable';
|
||||
import OSTable from 'components/metrics/OSTable';
|
||||
|
|
@ -26,6 +28,8 @@ const views = {
|
|||
device: DevicesTable,
|
||||
screen: ScreenTable,
|
||||
country: CountriesTable,
|
||||
region: RegionsTable,
|
||||
city: CitiesTable,
|
||||
language: LanguagesTable,
|
||||
event: EventsTable,
|
||||
query: QueryParametersTable,
|
||||
|
|
@ -69,6 +73,16 @@ export default function WebsiteMenuView({ websiteId, websiteDomain }) {
|
|||
label: formatMessage(labels.countries),
|
||||
url: resolveUrl({ view: 'country' }),
|
||||
},
|
||||
{
|
||||
key: 'region',
|
||||
label: formatMessage(labels.regions),
|
||||
url: resolveUrl({ view: 'region' }),
|
||||
},
|
||||
{
|
||||
key: 'city',
|
||||
label: formatMessage(labels.cities),
|
||||
url: resolveUrl({ view: 'city' }),
|
||||
},
|
||||
{
|
||||
key: 'language',
|
||||
label: formatMessage(labels.languages),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue