Added website menu view. Fixed pages filter.

This commit is contained in:
Mike Cao 2023-02-10 23:21:50 -08:00
parent d4437427c4
commit 4a689bf294
13 changed files with 100 additions and 85 deletions

View File

@ -1,9 +1,9 @@
import { ButtonGroup, Button, Flexbox } from 'react-basics'; import { ButtonGroup, Button, Flexbox } from 'react-basics';
export default function FilterButtons({ items, selected, onSelect }) { export default function FilterButtons({ items, selectedKey, onSelect }) {
return ( return (
<Flexbox justifyContent="center"> <Flexbox justifyContent="center">
<ButtonGroup items={items} selectedKey={selected} onSelect={onSelect}> <ButtonGroup items={items} selectedKey={selectedKey} onSelect={onSelect}>
{({ key, label }) => <Button key={key}>{label}</Button>} {({ key, label }) => <Button key={key}>{label}</Button>}
</ButtonGroup> </ButtonGroup>
</Flexbox> </Flexbox>

View File

@ -7,9 +7,7 @@ import { getDateRangeValues } from 'lib/date';
import { getDateLocale } from 'lib/lang'; import { getDateLocale } from 'lib/lang';
import { labels } from 'components/messages'; import { labels } from 'components/messages';
import styles from './DatePickerForm.module.css'; import styles from './DatePickerForm.module.css';
import { FILTER_DAY, FILTER_RANGE } from 'lib/constants';
const FILTER_DAY = 'day';
const FILTER_RANGE = 'range';
export default function DatePickerForm({ export default function DatePickerForm({
startDate: defaultStartDate, startDate: defaultStartDate,

View File

@ -5,9 +5,12 @@ import FilterButtons from 'components/common/FilterButtons';
import { urlFilter } from 'lib/filters'; import { urlFilter } from 'lib/filters';
import { labels } from 'components/messages'; import { labels } from 'components/messages';
import MetricsTable from './MetricsTable'; import MetricsTable from './MetricsTable';
import { FILTER_COMBINED, FILTER_RAW } from 'lib/constants';
export const FILTER_COMBINED = 0; const filters = {
export const FILTER_RAW = 1; [FILTER_RAW]: null,
[FILTER_COMBINED]: urlFilter,
};
export default function PagesTable({ websiteId, showFilters, ...props }) { export default function PagesTable({ websiteId, showFilters, ...props }) {
const [filter, setFilter] = useState(FILTER_COMBINED); const [filter, setFilter] = useState(FILTER_COMBINED);
@ -16,11 +19,11 @@ export default function PagesTable({ websiteId, showFilters, ...props }) {
const buttons = [ const buttons = [
{ {
label: formatMessage(labels.filterCombined), label: formatMessage(labels.filterCombined),
value: FILTER_COMBINED, key: FILTER_COMBINED,
}, },
{ {
label: formatMessage(labels.filterRaw), label: formatMessage(labels.filterRaw),
value: FILTER_RAW, key: FILTER_RAW,
}, },
]; ];
@ -30,13 +33,13 @@ export default function PagesTable({ websiteId, showFilters, ...props }) {
return ( return (
<> <>
{showFilters && <FilterButtons buttons={buttons} selected={filter} onClick={setFilter} />} {showFilters && <FilterButtons items={buttons} selectedKey={filter} onSelect={setFilter} />}
<MetricsTable <MetricsTable
title={formatMessage(labels.pages)} title={formatMessage(labels.pages)}
type="url" type="url"
metric={formatMessage(labels.views)} metric={formatMessage(labels.views)}
websiteId={websiteId} websiteId={websiteId}
dataFilter={filter !== FILTER_RAW ? urlFilter : null} dataFilter={filters[filter]}
renderLabel={renderLink} renderLabel={renderLink}
{...props} {...props}
/> />

View File

@ -1,21 +1,17 @@
import { useState } from 'react'; import { useState } from 'react';
import { useIntl, defineMessages } from 'react-intl'; import { useIntl } from 'react-intl';
import { safeDecodeURI } from 'next-basics'; import { safeDecodeURI } from 'next-basics';
import Tag from 'components/common/Tag'; import Tag from 'components/common/Tag';
import FilterButtons from 'components/common/FilterButtons'; import FilterButtons from 'components/common/FilterButtons';
import { paramFilter } from 'lib/filters'; import { paramFilter } from 'lib/filters';
import { FILTER_RAW, FILTER_COMBINED } from 'lib/constants';
import { labels } from 'components/messages';
import MetricsTable from './MetricsTable'; import MetricsTable from './MetricsTable';
const FILTER_COMBINED = 0; const filters = {
const FILTER_RAW = 1; [FILTER_RAW]: null,
[FILTER_COMBINED]: paramFilter,
const messages = defineMessages({ };
combined: { id: 'metrics.filter.combined', defaultMessage: 'Combined' },
raw: { id: 'metrics.filter.raw', defaultMessage: 'Raw' },
views: { id: 'metrics.views', defaultMessage: 'Views' },
none: { id: 'label.none', defaultMessage: 'None' },
query: { id: 'metrics.query-parameters', defaultMessage: 'Query parameters' },
});
export default function QueryParametersTable({ websiteId, showFilters, ...props }) { export default function QueryParametersTable({ websiteId, showFilters, ...props }) {
const [filter, setFilter] = useState(FILTER_COMBINED); const [filter, setFilter] = useState(FILTER_COMBINED);
@ -23,22 +19,22 @@ export default function QueryParametersTable({ websiteId, showFilters, ...props
const buttons = [ const buttons = [
{ {
label: formatMessage(messages.combined), label: formatMessage(labels.filterCombined),
value: FILTER_COMBINED, key: FILTER_COMBINED,
}, },
{ label: formatMessage(messages.raw), value: FILTER_RAW }, { label: formatMessage(labels.filterRaw), key: FILTER_RAW },
]; ];
return ( return (
<> <>
{showFilters && <FilterButtons buttons={buttons} selected={filter} onClick={setFilter} />} {showFilters && <FilterButtons items={buttons} selectedKey={filter} onSelect={setFilter} />}
<MetricsTable <MetricsTable
{...props} {...props}
title={formatMessage(messages.query)} title={formatMessage(labels.query)}
type="query" type="query"
metric={formatMessage(messages.views)} metric={formatMessage(labels.views)}
websiteId={websiteId} websiteId={websiteId}
dataFilter={filter !== FILTER_RAW ? paramFilter : null} dataFilter={filters[filter]}
renderLabel={({ x, p, v }) => renderLabel={({ x, p, v }) =>
filter === FILTER_RAW ? ( filter === FILTER_RAW ? (
x x

View File

@ -54,19 +54,19 @@ export default function RealtimeLog({ data, websites, websiteId }) {
const buttons = [ const buttons = [
{ {
label: <FormattedMessage id="label.all" defaultMessage="All" />, label: <FormattedMessage id="label.all" defaultMessage="All" />,
value: TYPE_ALL, key: TYPE_ALL,
}, },
{ {
label: <FormattedMessage id="metrics.views" defaultMessage="Views" />, label: <FormattedMessage id="metrics.views" defaultMessage="Views" />,
value: TYPE_PAGEVIEW, key: TYPE_PAGEVIEW,
}, },
{ {
label: <FormattedMessage id="metrics.visitors" defaultMessage="Visitors" />, label: <FormattedMessage id="metrics.visitors" defaultMessage="Visitors" />,
value: TYPE_SESSION, key: TYPE_SESSION,
}, },
{ {
label: <FormattedMessage id="metrics.events" defaultMessage="Events" />, label: <FormattedMessage id="metrics.events" defaultMessage="Events" />,
value: TYPE_EVENT, key: TYPE_EVENT,
}, },
]; ];
@ -165,7 +165,7 @@ export default function RealtimeLog({ data, websites, websiteId }) {
return ( return (
<div className={styles.table}> <div className={styles.table}>
<FilterButtons buttons={buttons} selected={filter} onClick={setFilter} /> <FilterButtons items={buttons} selectedKey={filter} onSelect={setFilter} />
<div className={styles.header}> <div className={styles.header}>
<FormattedMessage id="label.realtime-logs" defaultMessage="Realtime logs" /> <FormattedMessage id="label.realtime-logs" defaultMessage="Realtime logs" />
</div> </div>

View File

@ -4,9 +4,7 @@ import firstBy from 'thenby';
import { percentFilter } from 'lib/filters'; import { percentFilter } from 'lib/filters';
import DataTable from './DataTable'; import DataTable from './DataTable';
import FilterButtons from 'components/common/FilterButtons'; import FilterButtons from 'components/common/FilterButtons';
import { FILTER_PAGES, FILTER_REFERRERS } from 'lib/constants';
const FILTER_REFERRERS = 0;
const FILTER_PAGES = 1;
export default function RealtimeViews({ websiteId, data, websites }) { export default function RealtimeViews({ websiteId, data, websites }) {
const { pageviews } = data; const { pageviews } = data;
@ -23,11 +21,11 @@ export default function RealtimeViews({ websiteId, data, websites }) {
const buttons = [ const buttons = [
{ {
label: <FormattedMessage id="metrics.referrers" defaultMessage="Referrers" />, label: <FormattedMessage id="metrics.referrers" defaultMessage="Referrers" />,
value: FILTER_REFERRERS, key: FILTER_REFERRERS,
}, },
{ {
label: <FormattedMessage id="metrics.pages" defaultMessage="Pages" />, label: <FormattedMessage id="metrics.pages" defaultMessage="Pages" />,
value: FILTER_PAGES, key: FILTER_PAGES,
}, },
]; ];
@ -90,7 +88,7 @@ export default function RealtimeViews({ websiteId, data, websites }) {
return ( return (
<> <>
<FilterButtons buttons={buttons} selected={filter} onClick={setFilter} /> <FilterButtons items={buttons} selectedKey={filter} onSelect={setFilter} />
{filter === FILTER_REFERRERS && ( {filter === FILTER_REFERRERS && (
<DataTable <DataTable
title={<FormattedMessage id="metrics.referrers" defaultMessage="Referrers" />} title={<FormattedMessage id="metrics.referrers" defaultMessage="Referrers" />}

View File

@ -5,9 +5,12 @@ import FilterButtons from 'components/common/FilterButtons';
import FilterLink from 'components/common/FilterLink'; import FilterLink from 'components/common/FilterLink';
import { refFilter } from 'lib/filters'; import { refFilter } from 'lib/filters';
import { labels } from 'components/messages'; import { labels } from 'components/messages';
import { FILTER_COMBINED, FILTER_RAW } from 'lib/constants';
const FILTER_COMBINED = 0; const filters = {
const FILTER_RAW = 1; [FILTER_RAW]: null,
[FILTER_COMBINED]: refFilter,
};
export default function ReferrersTable({ websiteId, showFilters, ...props }) { export default function ReferrersTable({ websiteId, showFilters, ...props }) {
const [filter, setFilter] = useState(FILTER_COMBINED); const [filter, setFilter] = useState(FILTER_COMBINED);
@ -16,9 +19,9 @@ export default function ReferrersTable({ websiteId, showFilters, ...props }) {
const items = [ const items = [
{ {
label: formatMessage(labels.filterCombined), label: formatMessage(labels.filterCombined),
value: FILTER_COMBINED, key: FILTER_COMBINED,
}, },
{ label: formatMessage(labels.filterRaw), value: FILTER_RAW }, { label: formatMessage(labels.filterRaw), key: FILTER_RAW },
]; ];
const renderLink = ({ w: link, x: referrer }) => { const renderLink = ({ w: link, x: referrer }) => {
@ -31,14 +34,14 @@ export default function ReferrersTable({ websiteId, showFilters, ...props }) {
return ( return (
<> <>
{showFilters && <FilterButtons items={items} selected={filter} onSelect={setFilter} />} {showFilters && <FilterButtons items={items} selectedKey={filter} onSelect={setFilter} />}
<MetricsTable <MetricsTable
{...props} {...props}
title={formatMessage(labels.referrers)} title={formatMessage(labels.referrers)}
type="referrer" type="referrer"
metric={formatMessage(labels.views)} metric={formatMessage(labels.views)}
websiteId={websiteId} websiteId={websiteId}
dataFilter={filter !== FILTER_RAW ? refFilter : null} dataFilter={filters[filter]}
renderLabel={renderLink} renderLabel={renderLink}
/> />
</> </>

View File

@ -1,4 +1,4 @@
import { Row, Column, Menu, Item, Icon, Button } from 'react-basics'; import { Row, Column, Menu, Item, Icon, Button, Flexbox, Text } from 'react-basics';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
import Link from 'next/link'; import Link from 'next/link';
import classNames from 'classnames'; import classNames from 'classnames';
@ -39,44 +39,44 @@ export default function WebsiteMenuView({ websiteId, websiteDomain }) {
const items = [ const items = [
{ {
key: 'url',
label: formatMessage(labels.pages), label: formatMessage(labels.pages),
value: resolve({ view: 'url' }),
}, },
{ {
key: 'referrer',
label: formatMessage(labels.referrers), label: formatMessage(labels.referrers),
value: resolve({ view: 'referrer' }),
}, },
{ {
key: 'browser',
label: formatMessage(labels.browsers), label: formatMessage(labels.browsers),
value: resolve({ view: 'browser' }),
}, },
{ {
key: 'os',
label: formatMessage(labels.os), label: formatMessage(labels.os),
value: resolve({ view: 'os' }),
}, },
{ {
key: 'device',
label: formatMessage(labels.devices), label: formatMessage(labels.devices),
value: resolve({ view: 'device' }),
}, },
{ {
key: 'country',
label: formatMessage(labels.countries), label: formatMessage(labels.countries),
value: resolve({ view: 'country' }),
}, },
{ {
key: 'language',
label: formatMessage(labels.languages), label: formatMessage(labels.languages),
value: resolve({ view: 'language' }),
}, },
{ {
key: 'screen',
label: formatMessage(labels.screens), label: formatMessage(labels.screens),
value: resolve({ view: 'screen' }),
}, },
{ {
key: 'event',
label: formatMessage(labels.events), label: formatMessage(labels.events),
value: resolve({ view: 'event' }),
}, },
{ {
key: 'query',
label: formatMessage(labels.query), label: formatMessage(labels.query),
value: resolve({ view: 'query' }),
}, },
]; ];
@ -85,31 +85,37 @@ export default function WebsiteMenuView({ websiteId, websiteDomain }) {
return ( return (
<Row className={styles.row}> <Row className={styles.row}>
<Column defaultSize={3} className={classNames(styles.col, styles.menu)}> <Column defaultSize={3} className={classNames(styles.col, styles.menu)}>
<Button> <Link href={resolve({ view: undefined })}>
<a>
<Flexbox justifyContent="center">
<Button variant="quiet">
<Icon rotate={180}> <Icon rotate={180}>
<Icons.ArrowRight /> <Icons.ArrowRight />
</Icon> </Icon>
{formatMessage(labels.back)} <Text>{formatMessage(labels.back)}</Text>
</Button> </Button>
<Menu items={items}> </Flexbox>
{({ value, label }) => ( </a>
<Item key={value}> </Link>
<Link href={resolve()}> <Menu items={items} selectedKey={view}>
{({ key, label }) => (
<Item key={key} className={styles.item}>
<Link href={resolve({ view: key })} shallow={true}>
<a>{label}</a> <a>{label}</a>
</Link> </Link>
</Item> </Item>
)} )}
</Menu> </Menu>
</Column> </Column>
<Column defaultSize={9} className={styles.col}> <Column defaultSize={9} className={classNames(styles.col, styles.data)}>
<DetailsComponent <DetailsComponent
websiteId={websiteId} websiteId={websiteId}
websiteDomain={websiteDomain} websiteDomain={websiteDomain}
height={500} height={500}
limit={false} limit={false}
animate={false} animate={false}
showFilters showFilters={true}
virtualize virtualize={true}
/> />
</Column> </Column>
</Row> </Row>

View File

@ -13,10 +13,19 @@
} }
.menu { .menu {
display: flex;
gap: 20px; gap: 20px;
} }
.menu a { .item a {
color: var(--font-color100); color: var(--font-color100);
flex: 1;
padding: var(--size300) var(--size600);
}
.item {
padding: 0;
}
.data {
min-height: 600px;
} }

View File

@ -25,6 +25,14 @@ export const REALTIME_INTERVAL = 3000;
export const UI_LAYOUT_BODY = 'ui-layout-body'; export const UI_LAYOUT_BODY = 'ui-layout-body';
export const FILTER_COMBINED = 'filter-combined';
export const FILTER_RAW = 'filter-raw';
export const FILTER_IGNORED = 'filter-ignored';
export const FILTER_DAY = 'filter-day';
export const FILTER_RANGE = 'filter-range';
export const FILTER_REFERRERS = 'filter-referrers';
export const FILTER_PAGES = 'filter-pages';
export const EVENT_TYPE = { export const EVENT_TYPE = {
pageView: 1, pageView: 1,
customEvent: 2, customEvent: 2,
@ -105,8 +113,6 @@ export const EVENT_COLORS = [
'#ffec16', '#ffec16',
]; ];
export const FILTER_IGNORED = Symbol.for('filter-ignored');
export const DOMAIN_REGEX = export const DOMAIN_REGEX =
/^(localhost(:[1-9]\d{0,4})?|((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63})$/; /^(localhost(:[1-9]\d{0,4})?|((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63})$/;

View File

@ -1,15 +1,11 @@
export const urlFilter = data => { export const urlFilter = data => {
const isValidUrl = url => { const isValidUrl = url => {
return url !== '' && url !== null && !url.startsWith('#'); return url !== '' && url !== null;
}; };
const cleanUrl = url => { const cleanUrl = url => {
try { try {
const { pathname, search } = new URL(url, location.origin); const { pathname } = new URL(url, location.origin);
if (search.startsWith('?')) {
return `${pathname}${search}`;
}
return pathname; return pathname;
} catch { } catch {

View File

@ -93,7 +93,7 @@
"node-fetch": "^3.2.8", "node-fetch": "^3.2.8",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"react": "^18.2.0", "react": "^18.2.0",
"react-basics": "^0.64.0", "react-basics": "^0.66.0",
"react-beautiful-dnd": "^13.1.0", "react-beautiful-dnd": "^13.1.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-intl": "^5.24.7", "react-intl": "^5.24.7",

View File

@ -6676,10 +6676,10 @@ rc@^1.2.7:
minimist "^1.2.0" minimist "^1.2.0"
strip-json-comments "~2.0.1" strip-json-comments "~2.0.1"
react-basics@^0.64.0: react-basics@^0.66.0:
version "0.64.0" version "0.66.0"
resolved "https://registry.yarnpkg.com/react-basics/-/react-basics-0.64.0.tgz#b921dab7e437db6655f033cae15b8c963b93b7b2" resolved "https://registry.yarnpkg.com/react-basics/-/react-basics-0.66.0.tgz#c9d756936e9e996cbd70fb83e164fc673932fa0d"
integrity sha512-MY/F5+VBqqi+Hx58PdRONoeu3W0sitPOFbvAGxiM9vpajQL1DD//0Xgl/MahW8sIDbMy00lFghouex5JS93C8Q== integrity sha512-VKndXhIyWb/e080/TUXB2WnEfF/DSFSExer8AcxXJk58Illbg6bqMLjGuP2gtJ/dvr5skSPqm8b9gLVFU9C1ig==
dependencies: dependencies:
classnames "^2.3.1" classnames "^2.3.1"
date-fns "^2.29.3" date-fns "^2.29.3"