Merge remote-tracking branch 'origin/dev' into dev

This commit is contained in:
Brian Cao 2023-03-31 14:53:50 -07:00
commit 91100393eb
20 changed files with 347 additions and 809 deletions

View File

@ -1,17 +1,21 @@
import { Icon, Icons } from 'react-basics';
import classNames from 'classnames'; import classNames from 'classnames';
import Link from 'next/link'; import Link from 'next/link';
import { safeDecodeURI } from 'next-basics'; import { safeDecodeURI } from 'next-basics';
import usePageQuery from 'hooks/usePageQuery'; import usePageQuery from 'hooks/usePageQuery';
import { Icon, Icons } from 'react-basics'; import useMessages from 'hooks/useMessages';
import styles from './FilterLink.module.css'; import styles from './FilterLink.module.css';
export default function FilterLink({ id, value, label, externalUrl }) { export default function FilterLink({ id, value, label, externalUrl }) {
const { formatMessage, labels } = useMessages();
const { resolveUrl, query } = usePageQuery(); const { resolveUrl, query } = usePageQuery();
const active = query[id] !== undefined; const active = query[id] !== undefined;
const selected = query[id] === value; const selected = query[id] === value;
return ( return (
<div className={styles.row}> <div className={styles.row}>
{!value && <span className={styles.empty}>{label || formatMessage(labels.unknown)}</span>}
{value && (
<Link <Link
href={resolveUrl({ [id]: value })} href={resolveUrl({ [id]: value })}
className={classNames(styles.label, { className={classNames(styles.label, {
@ -22,6 +26,7 @@ export default function FilterLink({ id, value, label, externalUrl }) {
> >
{safeDecodeURI(label || value)} {safeDecodeURI(label || value)}
</Link> </Link>
)}
{externalUrl && ( {externalUrl && (
<a className={styles.link} href={externalUrl} target="_blank" rel="noreferrer noopener"> <a className={styles.link} href={externalUrl} target="_blank" rel="noreferrer noopener">
<Icon className={styles.icon}> <Icon className={styles.icon}>

View File

@ -30,3 +30,7 @@
.icon { .icon {
cursor: pointer; cursor: pointer;
} }
.empty {
color: var(--base400);
}

View File

@ -1,6 +1,7 @@
.tag { .tag {
padding: 2px 4px; padding: 4px 6px;
border: 1px solid var(--base300);
border-radius: 4px; border-radius: 4px;
margin-right: 10px; margin-right: 10px;
color: var(--primary400);
background: var(--blue100);
} }

View File

@ -113,6 +113,8 @@ export const labels = defineMessages({
mobile: { id: 'label.mobile', defaultMessage: 'Mobile' }, mobile: { id: 'label.mobile', defaultMessage: 'Mobile' },
toggleCharts: { id: 'label.toggle-charts', defaultMessage: 'Toggle charts' }, toggleCharts: { id: 'label.toggle-charts', defaultMessage: 'Toggle charts' },
editDashboard: { id: 'label.edit-dashboard', defaultMessage: 'Edit dashboard' }, editDashboard: { id: 'label.edit-dashboard', defaultMessage: 'Edit dashboard' },
title: { id: 'label.title', defaultMessage: 'Title' },
url: { id: 'label.url', defaultMessage: 'URL' },
}); });
export const messages = defineMessages({ export const messages = defineMessages({

View File

@ -13,11 +13,7 @@ export default function CountriesTable({ websiteId, onDataLoad, ...props }) {
function renderLink({ x: code }) { function renderLink({ x: code }) {
return ( return (
<div className={locale}> <div className={locale}>
<FilterLink <FilterLink id="country" value={countryNames[code] && code} label={countryNames[code]} />
id="country"
value={code}
label={countryNames[code] ?? formatMessage(labels.unknown)}
/>
</div> </div>
); );
} }

View File

@ -9,7 +9,7 @@ export default function DevicesTable({ websiteId, ...props }) {
return ( return (
<FilterLink <FilterLink
id="device" id="device"
value={device} value={labels[device] && device}
label={formatMessage(labels[device] || labels.unknown)} label={formatMessage(labels[device] || labels.unknown)}
/> />
); );

View File

@ -1,27 +1,24 @@
import MetricsTable from './MetricsTable'; import MetricsTable from './MetricsTable';
import { percentFilter } from 'lib/filters'; import { percentFilter } from 'lib/filters';
import { FormattedMessage } from 'react-intl';
import useLanguageNames from 'hooks/useLanguageNames'; import useLanguageNames from 'hooks/useLanguageNames';
import useLocale from 'hooks/useLocale'; import useLocale from 'hooks/useLocale';
import useMessages from 'hooks/useMessages';
export default function LanguagesTable({ websiteId, onDataLoad, ...props }) { export default function LanguagesTable({ websiteId, onDataLoad, ...props }) {
const { formatMessage, labels } = useMessages();
const { locale } = useLocale(); const { locale } = useLocale();
const languageNames = useLanguageNames(locale); const languageNames = useLanguageNames(locale);
function renderLabel({ x }) { const renderLabel = ({ x }) => {
return ( return <div className={locale}>{languageNames[x?.split('-')[0]] ?? x}</div>;
<div className={locale}> };
{languageNames[x] ?? <FormattedMessage id="label.unknown" defaultMessage="Unknown" />}{' '}
</div>
);
}
return ( return (
<MetricsTable <MetricsTable
{...props} {...props}
title={<FormattedMessage id="metrics.languages" defaultMessage="Languages" />} title={formatMessage(labels.languages)}
type="language" type="language"
metric={<FormattedMessage id="metrics.visitors" defaultMessage="Visitors" />} metric={formatMessage(labels.visitors)}
websiteId={websiteId} websiteId={websiteId}
onDataLoad={data => onDataLoad?.(percentFilter(data))} onDataLoad={data => onDataLoad?.(percentFilter(data))}
renderLabel={renderLabel} renderLabel={renderLabel}

View File

@ -1,19 +1,42 @@
import FilterLink from 'components/common/FilterLink'; import FilterLink from 'components/common/FilterLink';
import FilterButtons from 'components/common/FilterButtons';
import MetricsTable from './MetricsTable'; import MetricsTable from './MetricsTable';
import useMessages from 'hooks/useMessages'; import useMessages from 'hooks/useMessages';
import usePageQuery from 'hooks/usePageQuery';
export default function PagesTable({ websiteId, ...props }) { export default function PagesTable({ websiteId, showFilters, ...props }) {
const {
router,
resolveUrl,
query: { view },
} = usePageQuery();
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
const renderLink = ({ x: url }) => { const handleSelect = key => {
return <FilterLink id="url" value={url} />; router.push(resolveUrl({ view: key }), null, { shallow: true });
};
const buttons = [
{
label: formatMessage(labels.url),
key: 'url',
},
{
label: formatMessage(labels.title),
key: 'title',
},
];
const renderLink = ({ x }) => {
return <FilterLink id={view} value={x || `(${formatMessage(labels.none)})`} />;
}; };
return ( return (
<> <>
{showFilters && <FilterButtons items={buttons} selectedKey={view} onSelect={handleSelect} />}
<MetricsTable <MetricsTable
title={formatMessage(labels.pages)} title={formatMessage(labels.pages)}
type="url" type={view}
metric={formatMessage(labels.views)} metric={formatMessage(labels.views)}
websiteId={websiteId} websiteId={websiteId}
renderLabel={renderLink} renderLabel={renderLink}

View File

@ -6,10 +6,13 @@ export default function ReferrersTable({ websiteId, ...props }) {
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
const renderLink = ({ w: link, x: referrer }) => { const renderLink = ({ w: link, x: referrer }) => {
return referrer ? ( return (
<FilterLink id="referrer" value={referrer} externalUrl={link} /> <FilterLink
) : ( id="referrer"
`(${formatMessage(labels.none)})` value={referrer}
externalUrl={link}
label={!referrer && `(${formatMessage(labels.none)})`}
/>
); );
}; };

View File

@ -21,7 +21,7 @@ import useMessages from 'hooks/useMessages';
export default function WebsiteChart({ export default function WebsiteChart({
websiteId, websiteId,
title, name,
domain, domain,
stickyHeader = false, stickyHeader = false,
showChart = true, showChart = true,
@ -33,7 +33,7 @@ export default function WebsiteChart({
const { startDate, endDate, unit, value, modified } = dateRange; const { startDate, endDate, unit, value, modified } = dateRange;
const [timezone] = useTimezone(); const [timezone] = useTimezone();
const { const {
query: { url, referrer, os, browser, device, country }, query: { url, referrer, os, browser, device, country, title },
} = usePageQuery(); } = usePageQuery();
const { get, useQuery } = useApi(); const { get, useQuery } = useApi();
const { ref, isSticky } = useSticky({ enabled: stickyHeader }); const { ref, isSticky } = useSticky({ enabled: stickyHeader });
@ -68,7 +68,7 @@ export default function WebsiteChart({
return ( return (
<> <>
<WebsiteHeader websiteId={websiteId} title={title} domain={domain}> <WebsiteHeader websiteId={websiteId} name={name} domain={domain}>
{showDetailsButton && ( {showDetailsButton && (
<Link href={`/websites/${websiteId}`}> <Link href={`/websites/${websiteId}`}>
<Button variant="primary"> <Button variant="primary">
@ -80,7 +80,10 @@ export default function WebsiteChart({
</Link> </Link>
)} )}
</WebsiteHeader> </WebsiteHeader>
<FilterTags websiteId={websiteId} params={{ url, referrer, os, browser, device, country }} /> <FilterTags
websiteId={websiteId}
params={{ url, referrer, os, browser, device, country, title }}
/>
<Row <Row
ref={ref} ref={ref}
className={classNames(styles.header, { className={classNames(styles.header, {

View File

@ -3,12 +3,12 @@ import Favicon from 'components/common/Favicon';
import ActiveUsers from './ActiveUsers'; import ActiveUsers from './ActiveUsers';
import styles from './WebsiteHeader.module.css'; import styles from './WebsiteHeader.module.css';
export default function WebsiteHeader({ websiteId, title, domain, children }) { export default function WebsiteHeader({ websiteId, name, domain, children }) {
return ( return (
<Row className={styles.header} justifyContent="center"> <Row className={styles.header} justifyContent="center">
<Column className={styles.title} variant="two"> <Column className={styles.name} variant="two">
<Favicon domain={domain} /> <Favicon domain={domain} />
<Text>{title}</Text> <Text>{name}</Text>
</Column> </Column>
<Column className={styles.body} variant="two"> <Column className={styles.body} variant="two">
<ActiveUsers websiteId={websiteId} /> <ActiveUsers websiteId={websiteId} />

View File

@ -2,7 +2,7 @@
height: 100px; height: 100px;
} }
.title { .name {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;

View File

@ -122,7 +122,7 @@ export default function TestConsole() {
<Column> <Column>
<WebsiteChart <WebsiteChart
websiteId={website.id} websiteId={website.id}
title={website.name} name={website.name}
domain={website.domain} domain={website.domain}
showLink showLink
/> />

View File

@ -22,7 +22,7 @@ export default function WebsiteChartList({ websites, showCharts, limit }) {
<div key={id} className={styles.website}> <div key={id} className={styles.website}>
<WebsiteChart <WebsiteChart
websiteId={id} websiteId={id}
title={name} name={name}
domain={domain} domain={domain}
showChart={showCharts} showChart={showCharts}
showDetailsButton={true} showDetailsButton={true}

View File

@ -29,7 +29,7 @@ export default function WebsiteDetails({ websiteId }) {
<Page loading={isLoading} error={error}> <Page loading={isLoading} error={error}>
<WebsiteChart <WebsiteChart
websiteId={websiteId} websiteId={websiteId}
title={data?.name} name={data?.name}
domain={data?.domain} domain={data?.domain}
onDataLoad={handleDataLoad} onDataLoad={handleDataLoad}
showLink={false} showLink={false}

View File

@ -1,4 +1,4 @@
import { Menu, Item, Icon, Button, Flexbox, Text } from 'react-basics'; import { Icon, Button, Flexbox, Text } from 'react-basics';
import Link from 'next/link'; import Link from 'next/link';
import { GridRow, GridColumn } from 'components/layout/Grid'; import { GridRow, GridColumn } from 'components/layout/Grid';
import BrowsersTable from 'components/metrics/BrowsersTable'; import BrowsersTable from 'components/metrics/BrowsersTable';
@ -19,6 +19,7 @@ import styles from './WebsiteMenuView.module.css';
const views = { const views = {
url: PagesTable, url: PagesTable,
title: PagesTable,
referrer: ReferrersTable, referrer: ReferrersTable,
browser: BrowsersTable, browser: BrowsersTable,
os: OSTable, os: OSTable,

View File

@ -1,6 +1,5 @@
import { useState } from 'react'; import { useState } from 'react';
import { GridRow, GridColumn } from 'components/layout/Grid'; import { GridRow, GridColumn } from 'components/layout/Grid';
//import { Row as GridRow, Column as GridColumn } from 'react-basics';
import PagesTable from 'components/metrics/PagesTable'; import PagesTable from 'components/metrics/PagesTable';
import ReferrersTable from 'components/metrics/ReferrersTable'; import ReferrersTable from 'components/metrics/ReferrersTable';
import BrowsersTable from 'components/metrics/BrowsersTable'; import BrowsersTable from 'components/metrics/BrowsersTable';

View File

@ -17,7 +17,7 @@ const sessionColumns = [
'subdivision2', 'subdivision2',
'city', 'city',
]; ];
const pageviewColumns = ['url', 'referrer', 'query', 'pageTitle']; const pageviewColumns = ['url', 'referrer', 'query', 'title'];
function getTable(type) { function getTable(type) {
if (type === 'event') { if (type === 'event') {
@ -45,6 +45,8 @@ function getColumn(type) {
return 'event_name'; return 'event_name';
case 'query': case 'query':
return 'url_query'; return 'url_query';
case 'title':
return 'page_title';
} }
return type; return type;
@ -57,7 +59,7 @@ export interface WebsiteMetricsRequestQuery {
endAt: number; endAt: number;
url: string; url: string;
referrer: string; referrer: string;
pageTitle: string; title: string;
os: string; os: string;
browser: string; browser: string;
device: string; device: string;
@ -81,7 +83,7 @@ export default async (
endAt, endAt,
url, url,
referrer, referrer,
pageTitle, title,
os, os,
browser, browser,
device, device,
@ -154,7 +156,7 @@ export default async (
domain, domain,
url: type !== 'url' && table !== 'event' ? url : undefined, url: type !== 'url' && table !== 'event' ? url : undefined,
referrer: type !== 'referrer' && table !== 'event' ? referrer : FILTER_IGNORED, referrer: type !== 'referrer' && table !== 'event' ? referrer : FILTER_IGNORED,
pageTitle: type !== 'pageTitle' && table !== 'event' ? pageTitle : undefined, title: type !== 'title' && table !== 'event' ? title : undefined,
os: type !== 'os' ? os : undefined, os: type !== 'os' ? os : undefined,
browser: type !== 'browser' ? browser : undefined, browser: type !== 'browser' ? browser : undefined,
device: type !== 'device' ? device : undefined, device: type !== 'device' ? device : undefined,

View File

@ -116,10 +116,9 @@
!(e.ctrlKey || e.shiftKey || e.metaKey || (e.button && e.button === 1)) !(e.ctrlKey || e.shiftKey || e.metaKey || (e.button && e.button === 1))
) { ) {
e.preventDefault(); e.preventDefault();
track(eventName, { data: eventData }).then(() => { return track(eventName, { data: eventData }).then(() => {
location.href = href; location.href = href;
}); });
return;
} }
} }
@ -132,7 +131,7 @@
const observeTitle = () => { const observeTitle = () => {
const callback = ([entry]) => { const callback = ([entry]) => {
title = entry.target.data; title = entry.target.text;
}; };
const observer = new MutationObserver(callback); const observer = new MutationObserver(callback);
@ -140,6 +139,7 @@
observer.observe(document.querySelector('head > title'), { observer.observe(document.querySelector('head > title'), {
subtree: true, subtree: true,
characterData: true, characterData: true,
childList: true,
}); });
}; };

1006
yarn.lock

File diff suppressed because it is too large Load Diff