diff --git a/components/WebsiteDetails.js b/components/WebsiteDetails.js
index e3a7e2c2..f24332ba 100644
--- a/components/WebsiteDetails.js
+++ b/components/WebsiteDetails.js
@@ -1,12 +1,12 @@
import React, { useState } from 'react';
import { FormattedMessage } from 'react-intl';
-import { useRouter } from 'next/router';
import classNames from 'classnames';
import WebsiteChart from 'components/metrics/WebsiteChart';
import WorldMap from 'components/common/WorldMap';
import Page from 'components/layout/Page';
import MenuLayout from 'components/layout/MenuLayout';
-import Button from 'components/common/Button';
+import Link from 'components/common/Link';
+import Loading from 'components/common/Loading';
import Arrow from 'assets/arrow-right.svg';
import styles from './WebsiteDetails.module.css';
import PagesTable from './metrics/PagesTable';
@@ -18,7 +18,7 @@ import CountriesTable from './metrics/CountriesTable';
import EventsTable from './metrics/EventsTable';
import EventsChart from './metrics/EventsChart';
import useFetch from 'hooks/useFetch';
-import Loading from 'components/common/Loading';
+import usePageQuery from 'hooks/usePageQuery';
const views = {
url: PagesTable,
@@ -31,31 +31,26 @@ const views = {
};
export default function WebsiteDetails({ websiteId, token }) {
- const router = useRouter();
const { data } = useFetch(`/api/website/${websiteId}`, { token });
const [chartLoaded, setChartLoaded] = useState(false);
const [countryData, setCountryData] = useState();
const [eventsData, setEventsData] = useState();
const {
- query: { id, view },
- basePath,
- asPath,
- } = router;
-
- const path = `${basePath}/${asPath.split('/')[1]}/${id.join('/')}`;
+ resolve,
+ query: { view },
+ } = usePageQuery();
const BackButton = () => (
- }
- size="xsmall"
- onClick={() => router.push(path)}
+ size="small"
>
-
-
-
-
+
+
);
const menuOptions = [
@@ -64,31 +59,31 @@ export default function WebsiteDetails({ websiteId, token }) {
},
{
label: ,
- value: `${path}?view=url`,
+ value: resolve({ view: 'url' }),
},
{
label: ,
- value: `${path}?view=referrer`,
+ value: resolve({ view: 'referrer' }),
},
{
label: ,
- value: `${path}?view=browser`,
+ value: resolve({ view: 'browser' }),
},
{
label: ,
- value: `${path}?view=os`,
+ value: resolve({ view: 'os' }),
},
{
label: ,
- value: `${path}?view=device`,
+ value: resolve({ view: 'device' }),
},
{
label: ,
- value: `${path}?view=country`,
+ value: resolve({ view: 'country' }),
},
{
label: ,
- value: `${path}?view=event`,
+ value: resolve({ view: 'event' }),
},
];
@@ -97,7 +92,6 @@ export default function WebsiteDetails({ websiteId, token }) {
token,
websiteDomain: data?.domain,
limit: 10,
- onExpand: handleExpand,
};
const DetailsComponent = views[view];
@@ -108,10 +102,6 @@ export default function WebsiteDetails({ websiteId, token }) {
}
}
- function handleExpand(value) {
- router.push(`${path}?view=${value}`);
- }
-
if (!data) {
return null;
}
@@ -179,7 +169,7 @@ export default function WebsiteDetails({ websiteId, token }) {
contentClassName={styles.content}
menu={menuOptions}
>
-
+
)}
diff --git a/components/WebsiteDetails.module.css b/components/WebsiteDetails.module.css
index ca80dca0..0e1065c6 100644
--- a/components/WebsiteDetails.module.css
+++ b/components/WebsiteDetails.module.css
@@ -16,7 +16,6 @@
}
.backButton {
- align-self: flex-start;
margin-bottom: 16px;
}
diff --git a/components/WebsiteList.js b/components/WebsiteList.js
index b1819748..0df24877 100644
--- a/components/WebsiteList.js
+++ b/components/WebsiteList.js
@@ -34,9 +34,7 @@ export default function WebsiteList({ userId }) {
}
>
} size="medium" onClick={() => router.push('/settings')}>
-
-
-
+
)}
diff --git a/components/common/Button.js b/components/common/Button.js
index b973b36e..5e92d0d8 100644
--- a/components/common/Button.js
+++ b/components/common/Button.js
@@ -13,7 +13,8 @@ export default function Button({
className,
tooltip,
tooltipId,
- disabled = false,
+ disabled,
+ iconRight,
onClick = () => {},
...props
}) {
@@ -30,14 +31,14 @@ export default function Button({
[styles.action]: variant === 'action',
[styles.danger]: variant === 'danger',
[styles.light]: variant === 'light',
- [styles.disabled]: disabled,
+ [styles.iconRight]: iconRight,
})}
disabled={disabled}
onClick={!disabled ? onClick : null}
{...props}
>
- {icon && }
- {children}
+ {icon && }
+ {children && {children}
}
{tooltip && {tooltip}}
);
diff --git a/components/common/Button.module.css b/components/common/Button.module.css
index 324bbb22..f4fd8546 100644
--- a/components/common/Button.module.css
+++ b/components/common/Button.module.css
@@ -10,7 +10,6 @@
border: 0;
outline: none;
cursor: pointer;
- white-space: nowrap;
position: relative;
}
@@ -22,12 +21,15 @@
color: var(--gray900);
}
-.large {
- font-size: var(--font-size-large);
+.label {
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ max-width: 300px;
}
-.medium {
- font-size: var(--font-size-normal);
+.large {
+ font-size: var(--font-size-large);
}
.small {
@@ -38,7 +40,8 @@
font-size: var(--font-size-xsmall);
}
-.action {
+.action,
+.action:active {
color: var(--gray50);
background: var(--gray900);
}
@@ -64,6 +67,19 @@
background: inherit;
}
+.button .icon + * {
+ margin-left: 10px;
+}
+
+.button.iconRight .icon {
+ order: 1;
+ margin-left: 10px;
+}
+
+.button.iconRight .icon + * {
+ margin: 0;
+}
+
.button:disabled {
cursor: default;
color: var(--gray500);
diff --git a/components/common/Icon.module.css b/components/common/Icon.module.css
index 47d0ab0d..5b431668 100644
--- a/components/common/Icon.module.css
+++ b/components/common/Icon.module.css
@@ -5,10 +5,6 @@
vertical-align: middle;
}
-.icon + * {
- margin-left: 10px;
-}
-
.icon svg {
fill: currentColor;
}
diff --git a/components/common/Link.js b/components/common/Link.js
index c3a5fa7e..466e018c 100644
--- a/components/common/Link.js
+++ b/components/common/Link.js
@@ -1,12 +1,23 @@
import React from 'react';
import classNames from 'classnames';
import NextLink from 'next/link';
+import Icon from './Icon';
import styles from './Link.module.css';
-export default function Link({ className, children, ...props }) {
+export default function Link({ className, icon, children, size, iconRight, ...props }) {
return (
- {children}
+
+ {icon && }
+ {children}
+
);
}
diff --git a/components/common/Link.module.css b/components/common/Link.module.css
index 24d8f84c..ea6d281d 100644
--- a/components/common/Link.module.css
+++ b/components/common/Link.module.css
@@ -4,6 +4,8 @@ a.link:visited {
position: relative;
color: var(--gray900);
text-decoration: none;
+ display: inline-flex;
+ align-items: center;
}
a.link:before {
@@ -21,3 +23,28 @@ a.link:hover:before {
width: 100%;
transition: width 100ms;
}
+
+a.link.large {
+ font-size: var(--font-size-large);
+}
+
+a.link.small {
+ font-size: var(--font-size-small);
+}
+
+a.link.xsmall {
+ font-size: var(--font-size-xsmall);
+}
+
+a.link .icon + * {
+ margin-left: 10px;
+}
+
+a.link.iconRight .icon {
+ order: 1;
+ margin-left: 10px;
+}
+
+a.link.iconRight .icon + * {
+ margin: 0;
+}
diff --git a/components/metrics/BrowsersTable.js b/components/metrics/BrowsersTable.js
index f092e62f..97f9bfbd 100644
--- a/components/metrics/BrowsersTable.js
+++ b/components/metrics/BrowsersTable.js
@@ -3,7 +3,7 @@ import { FormattedMessage } from 'react-intl';
import MetricsTable from './MetricsTable';
import { browserFilter } from 'lib/filters';
-export default function BrowsersTable({ websiteId, token, limit, onExpand }) {
+export default function BrowsersTable({ websiteId, token, limit }) {
return (
}
@@ -13,7 +13,6 @@ export default function BrowsersTable({ websiteId, token, limit, onExpand }) {
token={token}
limit={limit}
dataFilter={browserFilter}
- onExpand={onExpand}
/>
);
}
diff --git a/components/metrics/CountriesTable.js b/components/metrics/CountriesTable.js
index 1f516653..58548d06 100644
--- a/components/metrics/CountriesTable.js
+++ b/components/metrics/CountriesTable.js
@@ -3,13 +3,7 @@ import MetricsTable from './MetricsTable';
import { countryFilter, percentFilter } from 'lib/filters';
import { FormattedMessage } from 'react-intl';
-export default function CountriesTable({
- websiteId,
- token,
- limit,
- onDataLoad = () => {},
- onExpand,
-}) {
+export default function CountriesTable({ websiteId, token, limit, onDataLoad = () => {} }) {
return (
}
@@ -20,7 +14,6 @@ export default function CountriesTable({
limit={limit}
dataFilter={countryFilter}
onDataLoad={data => onDataLoad(percentFilter(data))}
- onExpand={onExpand}
/>
);
}
diff --git a/components/metrics/DevicesTable.js b/components/metrics/DevicesTable.js
index 85d2bdfd..7d87d1c1 100644
--- a/components/metrics/DevicesTable.js
+++ b/components/metrics/DevicesTable.js
@@ -4,7 +4,7 @@ import { deviceFilter } from 'lib/filters';
import { FormattedMessage } from 'react-intl';
import { getDeviceMessage } from 'components/messages';
-export default function DevicesTable({ websiteId, token, limit, onExpand }) {
+export default function DevicesTable({ websiteId, token, limit }) {
return (
}
@@ -15,7 +15,6 @@ export default function DevicesTable({ websiteId, token, limit, onExpand }) {
limit={limit}
dataFilter={deviceFilter}
renderLabel={({ x }) => getDeviceMessage(x)}
- onExpand={onExpand}
/>
);
}
diff --git a/components/metrics/EventsChart.js b/components/metrics/EventsChart.js
index afc3e952..113c6f56 100644
--- a/components/metrics/EventsChart.js
+++ b/components/metrics/EventsChart.js
@@ -6,11 +6,13 @@ import useFetch from 'hooks/useFetch';
import useDateRange from 'hooks/useDateRange';
import useTimezone from 'hooks/useTimezone';
import { EVENT_COLORS } from 'lib/constants';
+import usePageQuery from '../../hooks/usePageQuery';
export default function EventsChart({ websiteId, token }) {
const [dateRange] = useDateRange(websiteId);
const { startDate, endDate, unit, modified } = dateRange;
const [timezone] = useTimezone();
+ const { query } = usePageQuery();
const { data } = useFetch(
`/api/website/${websiteId}/events`,
@@ -19,6 +21,7 @@ export default function EventsChart({ websiteId, token }) {
end_at: +endDate,
unit,
tz: timezone,
+ url: query.url,
token,
},
{ update: [modified] },
diff --git a/components/metrics/EventsTable.js b/components/metrics/EventsTable.js
index 948b9f7a..9a7a09cb 100644
--- a/components/metrics/EventsTable.js
+++ b/components/metrics/EventsTable.js
@@ -3,7 +3,7 @@ import { FormattedMessage } from 'react-intl';
import MetricsTable from './MetricsTable';
import styles from './EventsTable.module.css';
-export default function EventsTable({ websiteId, token, limit, onExpand, onDataLoad }) {
+export default function EventsTable({ websiteId, token, limit, onDataLoad }) {
return (
}
@@ -13,7 +13,6 @@ export default function EventsTable({ websiteId, token, limit, onExpand, onDataL
token={token}
limit={limit}
renderLabel={({ x }) => }
- onExpand={onExpand}
onDataLoad={onDataLoad}
/>
);
diff --git a/components/metrics/MetricsBar.js b/components/metrics/MetricsBar.js
index cad4c00e..f5d888d4 100644
--- a/components/metrics/MetricsBar.js
+++ b/components/metrics/MetricsBar.js
@@ -5,24 +5,30 @@ import Loading from 'components/common/Loading';
import useFetch from 'hooks/useFetch';
import useDateRange from 'hooks/useDateRange';
import { formatShortTime, formatNumber, formatLongNumber } from 'lib/format';
+import usePageQuery from 'hooks/usePageQuery';
import MetricCard from './MetricCard';
import styles from './MetricsBar.module.css';
export default function MetricsBar({ websiteId, token, className }) {
const [dateRange] = useDateRange(websiteId);
const { startDate, endDate, modified } = dateRange;
+ const [format, setFormat] = useState(true);
+ const {
+ query: { url },
+ } = usePageQuery();
+
const { data } = useFetch(
`/api/website/${websiteId}/metrics`,
{
start_at: +startDate,
end_at: +endDate,
+ url,
token,
},
{
update: [modified],
},
);
- const [format, setFormat] = useState(true);
const formatFunc = format ? formatLongNumber : formatNumber;
diff --git a/components/metrics/MetricsTable.js b/components/metrics/MetricsTable.js
index 75fd579c..1f2145f7 100644
--- a/components/metrics/MetricsTable.js
+++ b/components/metrics/MetricsTable.js
@@ -3,7 +3,7 @@ import { FormattedMessage } from 'react-intl';
import { FixedSizeList } from 'react-window';
import { useSpring, animated, config } from 'react-spring';
import classNames from 'classnames';
-import Button from 'components/common/Button';
+import Link from 'components/common/Link';
import Loading from 'components/common/Loading';
import NoData from 'components/common/NoData';
import useFetch from 'hooks/useFetch';
@@ -11,6 +11,7 @@ import Arrow from 'assets/arrow-right.svg';
import { percentFilter } from 'lib/filters';
import { formatNumber, formatLongNumber } from 'lib/format';
import useDateRange from 'hooks/useDateRange';
+import usePageQuery from 'hooks/usePageQuery';
import styles from './MetricsTable.module.css';
export default function MetricsTable({
@@ -26,10 +27,14 @@ export default function MetricsTable({
limit,
renderLabel,
onDataLoad = () => {},
- onExpand = () => {},
}) {
const [dateRange] = useDateRange(websiteId);
const { startDate, endDate, modified } = dateRange;
+ const {
+ resolve,
+ query: { url },
+ } = usePageQuery();
+
const { data } = useFetch(
`/api/website/${websiteId}/rankings`,
{
@@ -37,6 +42,7 @@ export default function MetricsTable({
start_at: +startDate,
end_at: +endDate,
domain: websiteDomain,
+ url,
token,
},
{ onDataLoad, delay: 300, update: [modified] },
@@ -100,11 +106,15 @@ export default function MetricsTable({
{limit && (
-
} size="xsmall" onClick={() => onExpand(type)}>
-
-
-
-
+
}
+ href="/website/[...id]"
+ as={resolve({ view: type })}
+ size="small"
+ iconRight
+ >
+
+
)}
>
diff --git a/components/metrics/MetricsTable.module.css b/components/metrics/MetricsTable.module.css
index 34fa77ad..bbba0009 100644
--- a/components/metrics/MetricsTable.module.css
+++ b/components/metrics/MetricsTable.module.css
@@ -1,6 +1,6 @@
.container {
position: relative;
- min-height: 460px;
+ min-height: 430px;
font-size: var(--font-size-small);
display: flex;
flex-direction: column;
@@ -21,6 +21,7 @@
.metric {
font-size: var(--font-size-small);
+ font-weight: 600;
text-align: center;
width: 100px;
cursor: pointer;
@@ -72,8 +73,8 @@
.percent {
position: relative;
width: 50px;
- color: #6e6e6e;
- border-left: 1px solid var(--gray500);
+ color: var(--gray600);
+ border-left: 1px solid var(--gray600);
padding-left: 10px;
z-index: 1;
}
diff --git a/components/metrics/OSTable.js b/components/metrics/OSTable.js
index 14f943a1..63d4739c 100644
--- a/components/metrics/OSTable.js
+++ b/components/metrics/OSTable.js
@@ -3,7 +3,7 @@ import MetricsTable from './MetricsTable';
import { osFilter } from 'lib/filters';
import { FormattedMessage } from 'react-intl';
-export default function OSTable({ websiteId, token, limit, onExpand }) {
+export default function OSTable({ websiteId, token, limit }) {
return (
}
@@ -13,7 +13,6 @@ export default function OSTable({ websiteId, token, limit, onExpand }) {
token={token}
limit={limit}
dataFilter={osFilter}
- onExpand={onExpand}
/>
);
}
diff --git a/components/metrics/PagesTable.js b/components/metrics/PagesTable.js
index ba04f871..0acda811 100644
--- a/components/metrics/PagesTable.js
+++ b/components/metrics/PagesTable.js
@@ -1,13 +1,21 @@
import React, { useState } from 'react';
import { FormattedMessage } from 'react-intl';
+import classNames from 'classnames';
+import Link from 'next/link';
import ButtonGroup from 'components/common/ButtonGroup';
+import ButtonLayout from 'components/layout/ButtonLayout';
import { urlFilter } from 'lib/filters';
import { FILTER_COMBINED, FILTER_RAW } from 'lib/constants';
+import usePageQuery from 'hooks/usePageQuery';
import MetricsTable from './MetricsTable';
-import ButtonLayout from '../layout/ButtonLayout';
+import styles from './PagesTable.module.css';
-export default function PagesTable({ websiteId, token, websiteDomain, limit, onExpand }) {
+export default function PagesTable({ websiteId, token, websiteDomain, limit, showFilters }) {
const [filter, setFilter] = useState(FILTER_COMBINED);
+ const {
+ resolve,
+ query: { url },
+ } = usePageQuery();
const buttons = [
{
@@ -17,9 +25,24 @@ export default function PagesTable({ websiteId, token, websiteDomain, limit, onE
{ label: , value: FILTER_RAW },
];
+ const renderLink = ({ x }) => {
+ return (
+
+
+ {decodeURI(x)}
+
+
+ );
+ };
+
return (
<>
- {!limit && }
+ {showFilters && }
}
type="url"
@@ -29,8 +52,7 @@ export default function PagesTable({ websiteId, token, websiteDomain, limit, onE
limit={limit}
dataFilter={urlFilter}
filterOptions={{ domain: websiteDomain, raw: filter === FILTER_RAW }}
- renderLabel={({ x }) => decodeURI(x)}
- onExpand={onExpand}
+ renderLabel={renderLink}
/>
>
);
diff --git a/components/metrics/PagesTable.module.css b/components/metrics/PagesTable.module.css
new file mode 100644
index 00000000..3c592a74
--- /dev/null
+++ b/components/metrics/PagesTable.module.css
@@ -0,0 +1,8 @@
+body .inactive {
+ color: var(--gray500);
+}
+
+body .active {
+ color: var(--gray900);
+ font-weight: 600;
+}
diff --git a/components/metrics/ReferrersTable.js b/components/metrics/ReferrersTable.js
index 93552cc5..df7a8c15 100644
--- a/components/metrics/ReferrersTable.js
+++ b/components/metrics/ReferrersTable.js
@@ -6,13 +6,7 @@ import ButtonGroup from 'components/common/ButtonGroup';
import { FILTER_DOMAIN_ONLY, FILTER_COMBINED, FILTER_RAW } from 'lib/constants';
import ButtonLayout from '../layout/ButtonLayout';
-export default function ReferrersTable({
- websiteId,
- websiteDomain,
- token,
- limit,
- onExpand = () => {},
-}) {
+export default function ReferrersTable({ websiteId, websiteDomain, token, limit, showFilters }) {
const [filter, setFilter] = useState(FILTER_COMBINED);
const buttons = [
@@ -39,7 +33,7 @@ export default function ReferrersTable({
return (
<>
- {!limit && }
+ {showFilters && }
}
type="referrer"
@@ -54,7 +48,6 @@ export default function ReferrersTable({
domainOnly: filter === FILTER_DOMAIN_ONLY,
raw: filter === FILTER_RAW,
}}
- onExpand={onExpand}
renderLabel={renderLink}
/>
>
diff --git a/components/metrics/WebsiteChart.js b/components/metrics/WebsiteChart.js
index ecbd0798..99c03951 100644
--- a/components/metrics/WebsiteChart.js
+++ b/components/metrics/WebsiteChart.js
@@ -5,10 +5,13 @@ import MetricsBar from './MetricsBar';
import WebsiteHeader from './WebsiteHeader';
import DateFilter from 'components/common/DateFilter';
import StickyHeader from 'components/helpers/StickyHeader';
+import Button from 'components/common/Button';
import useFetch from 'hooks/useFetch';
import useDateRange from 'hooks/useDateRange';
import useTimezone from 'hooks/useTimezone';
+import usePageQuery from 'hooks/usePageQuery';
import { getDateArray, getDateLength } from 'lib/date';
+import Times from 'assets/times.svg';
import styles from './WebsiteChart.module.css';
export default function WebsiteChart({
@@ -22,6 +25,11 @@ export default function WebsiteChart({
const [dateRange, setDateRange] = useDateRange(websiteId);
const { startDate, endDate, unit, value, modified } = dateRange;
const [timezone] = useTimezone();
+ const {
+ router,
+ resolve,
+ query: { url },
+ } = usePageQuery();
const { data, loading } = useFetch(
`/api/website/${websiteId}/pageviews`,
@@ -30,6 +38,7 @@ export default function WebsiteChart({
end_at: +endDate,
unit,
tz: timezone,
+ url,
token,
},
{ onDataLoad, update: [modified] },
@@ -45,6 +54,10 @@ export default function WebsiteChart({
return [[], []];
}, [data]);
+ function handleCloseFilter() {
+ router.push(resolve({ url: undefined }));
+ }
+
return (
<>
@@ -54,6 +67,7 @@ export default function WebsiteChart({
stickyClassName={styles.sticky}
enabled={stickyHeader}
>
+ {url && }
@@ -81,3 +95,13 @@ export default function WebsiteChart({
>
);
}
+
+const PageFilter = ({ url, onClick }) => {
+ return (
+
+ } onClick={onClick} variant="action" iconRight>
+ {url}
+
+
+ );
+};
diff --git a/components/metrics/WebsiteChart.module.css b/components/metrics/WebsiteChart.module.css
index ea0fcaee..29f94670 100644
--- a/components/metrics/WebsiteChart.module.css
+++ b/components/metrics/WebsiteChart.module.css
@@ -36,6 +36,11 @@
align-items: center;
}
+.url {
+ text-align: center;
+ margin-bottom: 10px;
+}
+
@media only screen and (max-width: 992px) {
.filter {
display: block;
diff --git a/components/metrics/WebsiteHeader.js b/components/metrics/WebsiteHeader.js
index 71c0db69..41e5385c 100644
--- a/components/metrics/WebsiteHeader.js
+++ b/components/metrics/WebsiteHeader.js
@@ -4,7 +4,6 @@ import Link from 'components/common/Link';
import PageHeader from 'components/layout/PageHeader';
import RefreshButton from 'components/common/RefreshButton';
import ButtonLayout from 'components/layout/ButtonLayout';
-import Icon from 'components/common/Icon';
import ActiveUsers from './ActiveUsers';
import Arrow from 'assets/arrow-right.svg';
import styles from './WebsiteHeader.module.css';
@@ -21,9 +20,11 @@ export default function WebsiteHeader({ websiteId, token, title, showLink = fals
href="/website/[...id]"
as={`/website/${websiteId}/${title}`}
className={styles.link}
+ icon={}
+ size="small"
+ iconRight
>
- } size="small" />
)}
diff --git a/components/metrics/WebsiteHeader.module.css b/components/metrics/WebsiteHeader.module.css
index 71ff5b1b..67d23e61 100644
--- a/components/metrics/WebsiteHeader.module.css
+++ b/components/metrics/WebsiteHeader.module.css
@@ -5,14 +5,9 @@
}
.link {
- font-size: var(--font-size-small);
font-weight: 600;
}
-.link svg {
- margin-left: 10px;
-}
-
@media only screen and (max-width: 576px) {
.active {
display: none;
diff --git a/components/settings/AccountSettings.js b/components/settings/AccountSettings.js
index bf02f4b3..aa206fa9 100644
--- a/components/settings/AccountSettings.js
+++ b/components/settings/AccountSettings.js
@@ -42,14 +42,10 @@ export default function AccountSettings() {
row.username !== 'admin' ? (
} size="small" onClick={() => setEditAccount(row)}>
-
-
-
+
} size="small" onClick={() => setDeleteAccount(row)}>
-
-
-
+
) : null;
@@ -102,9 +98,7 @@ export default function AccountSettings() {
} size="small" onClick={() => setAddAccount(true)}>
-
-
-
+
diff --git a/components/settings/ProfileSettings.js b/components/settings/ProfileSettings.js
index f28226c5..e23c73ed 100644
--- a/components/settings/ProfileSettings.js
+++ b/components/settings/ProfileSettings.js
@@ -29,9 +29,7 @@ export default function ProfileSettings() {
} size="small" onClick={() => setChangePassword(true)}>
-
-
-
+
diff --git a/components/settings/ThemeButton.js b/components/settings/ThemeButton.js
index a31440b7..6f32e23b 100644
--- a/components/settings/ThemeButton.js
+++ b/components/settings/ThemeButton.js
@@ -1,6 +1,5 @@
import React from 'react';
import { useTransition, animated } from 'react-spring';
-import Button from 'components/common/Button';
import useTheme from 'hooks/useTheme';
import Sun from 'assets/sun.svg';
import Moon from 'assets/moon.svg';
@@ -27,7 +26,7 @@ export default function ThemeButton() {
}
return (
-
} size="small" onClick={() => setDeleteWebsite(row)}>
-
-
-
+
);
@@ -117,9 +113,7 @@ export default function WebsiteSettings() {
}
>
} size="medium" onClick={() => setAddWebsite(true)}>
-
-
-
+
);
@@ -131,9 +125,7 @@ export default function WebsiteSettings() {
} size="small" onClick={() => setAddWebsite(true)}>
-
-
-
+
diff --git a/hooks/usePageQuery.js b/hooks/usePageQuery.js
new file mode 100644
index 00000000..ced19702
--- /dev/null
+++ b/hooks/usePageQuery.js
@@ -0,0 +1,32 @@
+import { useMemo } from 'react';
+import { useRouter } from 'next/router';
+import { getQueryString } from '../lib/url';
+
+export default function usePageQuery() {
+ const router = useRouter();
+ const { pathname, search } = location;
+
+ const query = useMemo(() => {
+ if (!search) {
+ return {};
+ }
+
+ const params = search.substring(1).split('&');
+
+ return params.reduce((obj, item) => {
+ const [key, value] = item.split('=');
+
+ obj[key] = decodeURIComponent(value);
+
+ return obj;
+ }, {});
+ }, [search]);
+
+ function resolve(params) {
+ const search = getQueryString({ ...query, ...params });
+
+ return `${pathname}${search}`;
+ }
+
+ return { pathname, query, resolve, router };
+}
diff --git a/lang/fo-FO.json b/lang/fo-FO.json
new file mode 100644
index 00000000..800f91c7
--- /dev/null
+++ b/lang/fo-FO.json
@@ -0,0 +1,95 @@
+{
+ "button.add-account": "Ger brúkara",
+ "button.add-website": "Legg heimasíðu til",
+ "button.back": "Aftur",
+ "button.cancel": "Strika",
+ "button.change-password": "Broyt loyniorð",
+ "button.copy-to-clipboard": "Kopier til clipboard",
+ "button.date-range": "Vel dato",
+ "button.delete": "Sletta",
+ "button.edit": "Ger broyting",
+ "button.login": "Rita inn",
+ "button.more": "Meira",
+ "button.refresh": "Endurskapa",
+ "button.reset": "Nulstilla",
+ "button.save": "Goym",
+ "button.single-day": "Einkultur dagur",
+ "button.view-details": "Vís upplýsingar",
+ "label.accounts": "Brúkarar",
+ "label.administrator": "Administrator",
+ "label.confirm-password": "Vátta loyniorð",
+ "label.current-password": "Núverandi loyniorð",
+ "label.custom-range": "Tillaga spenni",
+ "label.dashboard": "Yvirlitsskíggi",
+ "label.default-date-range": "Standard dato",
+ "label.domain": "Økisnavn",
+ "label.enable-share-url": "Virkja deili leinki",
+ "label.invalid": "Ógilda",
+ "label.invalid-domain": "Ógilt økisnavn",
+ "label.last-days": "Seinastu {x} dagarnar",
+ "label.last-hours": "Seinastu {x} tímanar",
+ "label.logged-in-as": "Ritaður inn sum {username}",
+ "label.logout": "Rita út",
+ "label.name": "Navn",
+ "label.new-password": "Nýtt loyniorð",
+ "label.password": "Loyniorð",
+ "label.passwords-dont-match": "Loyniorðini eru ikki eins",
+ "label.profile": "Brúkari",
+ "label.required": "Krav",
+ "label.settings": "Stillingar",
+ "label.this-month": "Hendan mánan",
+ "label.this-week": "Hesa vikuna",
+ "label.this-year": "Hetta árið",
+ "label.timezone": "Tíðarsona",
+ "label.today": "Í dag",
+ "label.unknown": "Ókent",
+ "label.username": "Brúkaranavn",
+ "label.websites": "Heimasíður",
+ "message.active-users": "{x} í løtuni {x, plural, one {vitjandi} other { vitjandi }}",
+ "message.confirm-delete": "Ert tú sikkur at tú ynskir at sletta {target}?",
+ "message.copied": "Kopiera!",
+ "message.delete-warning": "Øll data ið er knýtt at verður eisini sletta.",
+ "message.failure": "Okkurt bleiv gali.",
+ "message.get-share-url": "Fá leinku sum tú kanst deila",
+ "message.get-tracking-code": "Fá sporings kotu",
+ "message.go-to-settings": "Far til stillingar",
+ "message.incorrect-username-password": "Skeivt brúkaranavn/loyniorð.",
+ "message.no-data-available": "Einki data tøk.",
+ "message.no-websites-configured": "Tú hevur ongar heimasíður stillaða til.",
+ "message.page-not-found": "Síðan bleiv ikki funnin.",
+ "message.powered-by": "Powered by {name}",
+ "message.save-success": "Goymt.",
+ "message.share-url": "Hetta er tann almenna leinkan av {target}.",
+ "message.track-stats": "Fyri at spora hagtøl fyri {target}, koyr kotuna í {head} partin á tínari heimasíðu.",
+ "message.type-delete": "Skriva {delete} í feltið fyri at vátta",
+ "metrics.actions": "Gerðir",
+ "metrics.average-visit-time": "Miðal vitjurnartíð ",
+ "metrics.bounce-rate": "Bounce prosenttal",
+ "metrics.browsers": "Kagar",
+ "metrics.countries": "Lond",
+ "metrics.device.desktop": "Borðtelda",
+ "metrics.device.laptop": "Fartelda",
+ "metrics.device.mobile": "Telefon",
+ "metrics.device.tablet": "Teldil",
+ "metrics.devices": "Tóleindir",
+ "metrics.events": "Hendingar/tiltøk",
+ "metrics.filter.combined": "Samansett",
+ "metrics.filter.domain-only": "Bara økisnavn",
+ "metrics.filter.raw": "Óviðgjørt",
+ "metrics.operating-systems": "Stýrikervir",
+ "metrics.page-views": "Opnaðar síðir",
+ "metrics.pages": "Síðir",
+ "metrics.referrers": "Framsendingar",
+ "metrics.unique-visitors": "Einsýna vitjanir",
+ "metrics.views": "Vitjanir",
+ "metrics.visitors": "Vitjandi",
+ "title.add-account": "Ger brúkara",
+ "title.add-website": "Legg heimasíðu avtrat",
+ "title.change-password": "Skift loyniorð",
+ "title.delete-account": "Sletta brúkara",
+ "title.delete-website": "Sletta heimasíðu",
+ "title.edit-account": "Broyt brúkara",
+ "title.edit-website": "Broyt heimasíðu",
+ "title.share-url": "Deil leinku",
+ "title.tracking-code": "Spori kota"
+}
diff --git a/lib/db.js b/lib/db.js
index e314684e..35948998 100644
--- a/lib/db.js
+++ b/lib/db.js
@@ -31,9 +31,3 @@ if (process.env.NODE_ENV === 'production') {
}
export default prisma;
-
-export async function runQuery(query) {
- return query.catch(e => {
- throw e;
- });
-}
diff --git a/lib/filters.js b/lib/filters.js
index acdab7a3..dcbb9907 100644
--- a/lib/filters.js
+++ b/lib/filters.js
@@ -13,7 +13,7 @@ export const urlFilter = (data, { raw }) => {
const cleanUrl = url => {
try {
- const { pathname, search } = new URL(url);
+ const { pathname, search } = new URL(url, location.origin);
if (search.startsWith('?/')) {
return `${pathname}${search}`;
@@ -30,7 +30,7 @@ export const urlFilter = (data, { raw }) => {
return obj;
}
- const url = cleanUrl(`http://x${x}`);
+ const url = cleanUrl(x);
if (url) {
if (!obj[url]) {
diff --git a/lib/lang.js b/lib/lang.js
index 066df152..d4415895 100644
--- a/lib/lang.js
+++ b/lib/lang.js
@@ -13,6 +13,7 @@ import mnMNMessages from 'lang-compiled/mn-MN.json';
import daMessages from 'lang-compiled/da-DK.json';
import svMessages from 'lang-compiled/sv-SE.json';
import grMessages from 'lang-compiled/el-GR.json';
+import foMessages from 'lang-compiled/fo-FO.json';
export const messages = {
'en-US': enMessages,
@@ -28,6 +29,7 @@ export const messages = {
'da-DK': daMessages,
'sv-SE': svMessages,
'el-GR': grMessages,
+ 'fo-FO': foMessages,
};
export const dateLocales = {
@@ -44,6 +46,7 @@ export const dateLocales = {
'fr-FR': fr,
'mn-MN': enUS,
'el-GR': el,
+ 'fo-FO': da,
};
export const menuOptions = [
@@ -52,6 +55,7 @@ export const menuOptions = [
{ label: 'Dansk', value: 'da-DK', display: 'da' },
{ label: 'Deutsch', value: 'de-DE', display: 'de' },
{ label: 'Español', value: 'es-MX', display: 'es' },
+ { label: 'Føroyskt', value: 'fo-FO', display: 'fo' },
{ label: 'Français', value: 'fr-FR', display: 'fr' },
{ label: 'Ελληνικά', value: 'el-GR', display: 'el' },
{ label: '日本語', value: 'ja-JP', display: 'ja' },
@@ -59,7 +63,7 @@ export const menuOptions = [
{ label: 'Nederlands', value: 'nl-NL', display: 'nl' },
{ label: 'Русский', value: 'ru-RU', display: 'ru' },
{ label: 'Svenska', value: 'sv-SE', display: 'sv' },
- { label: 'Turkish', value: 'tr-TR', display: 'tr' },
+ { label: 'Türkçe', value: 'tr-TR', display: 'tr' },
];
export function dateFormat(date, str, locale) {
diff --git a/lib/queries.js b/lib/queries.js
index 684b30d4..af2b7f5b 100644
--- a/lib/queries.js
+++ b/lib/queries.js
@@ -1,5 +1,5 @@
import moment from 'moment-timezone';
-import prisma, { runQuery } from 'lib/db';
+import prisma from 'lib/db';
import { subMinutes } from 'date-fns';
import { MYSQL, POSTGRESQL, MYSQL_DATE_FORMATS, POSTGRESQL_DATE_FORMATS } from 'lib/constants';
@@ -15,7 +15,27 @@ export function getDatabase() {
return type;
}
-export function getDateQuery(db, field, unit, timezone) {
+export async function runQuery(query) {
+ return query.catch(e => {
+ throw e;
+ });
+}
+
+export async function rawQuery(query, params) {
+ const db = getDatabase();
+
+ if (db !== POSTGRESQL && db !== MYSQL) {
+ return Promise.reject(new Error('Unknown database.'));
+ }
+
+ const sql = db === MYSQL ? query.replace(/\$[0-9]+/g, '?') : query;
+
+ return prisma.$queryRaw.apply(prisma, [sql, ...params]);
+}
+
+export function getDateQuery(field, unit, timezone) {
+ const db = getDatabase();
+
if (db === POSTGRESQL) {
if (timezone) {
return `to_char(date_trunc('${unit}', ${field} at time zone '${timezone}'), '${POSTGRESQL_DATE_FORMATS[unit]}')`;
@@ -34,6 +54,18 @@ export function getDateQuery(db, field, unit, timezone) {
}
}
+export function getTimestampInterval(field) {
+ const db = getDatabase();
+
+ if (db === POSTGRESQL) {
+ return `floor(extract(epoch from max(${field}) - min(${field})))`;
+ }
+
+ if (db === MYSQL) {
+ return `floor(unix_timestamp(max(${field})) - unix_timestamp(min(${field})))`;
+ }
+}
+
export async function getWebsiteById(website_id) {
return runQuery(
prisma.website.findOne({
@@ -253,62 +285,35 @@ export async function createAccount(data) {
);
}
-export function getMetrics(website_id, start_at, end_at) {
- const db = getDatabase();
+export function getMetrics(website_id, start_at, end_at, url) {
+ const params = [website_id, start_at, end_at];
+ let urlFilter = '';
- if (db === POSTGRESQL) {
- return runQuery(
- prisma.$queryRaw(
- `
+ if (url) {
+ urlFilter = `and url=$${params.length + 1}`;
+ params.push(decodeURIComponent(url));
+ }
+
+ return rawQuery(
+ `
select sum(t.c) as "pageviews",
count(distinct t.session_id) as "uniques",
sum(case when t.c = 1 then 1 else 0 end) as "bounces",
sum(t.time) as "totaltime"
from (
select session_id,
- ${getDateQuery(db, 'created_at', 'hour')},
+ ${getDateQuery('created_at', 'hour')},
count(*) c,
- floor(extract(epoch from max(created_at) - min(created_at))) as "time"
+ ${getTimestampInterval('created_at')} as "time"
from pageview
where website_id=$1
and created_at between $2 and $3
+ ${urlFilter}
group by 1, 2
) t
- `,
- website_id,
- start_at,
- end_at,
- ),
- );
- }
-
- if (db === MYSQL) {
- return runQuery(
- prisma.$queryRaw(
- `
- select sum(t.c) as "pageviews",
- count(distinct t.session_id) as "uniques",
- sum(case when t.c = 1 then 1 else 0 end) as "bounces",
- sum(t.time) as "totaltime"
- from (
- select session_id,
- ${getDateQuery(db, 'created_at', 'hour')},
- count(*) c,
- floor(unix_timestamp(max(created_at)) - unix_timestamp(min(created_at))) as "time"
- from pageview
- where website_id=?
- and created_at between ? and ?
- group by 1, 2
- ) t
- `,
- website_id,
- start_at,
- end_at,
- ),
- );
- }
-
- return Promise.reject(new Error('Unknown database.'));
+ `,
+ params,
+ );
}
export function getPageviews(
@@ -318,177 +323,125 @@ export function getPageviews(
timezone = 'utc',
unit = 'day',
count = '*',
+ url,
) {
- const db = getDatabase();
+ const params = [website_id, start_at, end_at];
+ let urlFilter = '';
- if (db === POSTGRESQL) {
- return runQuery(
- prisma.$queryRaw(
- `
- select ${getDateQuery(db, 'created_at', unit, timezone)} t,
- count(${count}) y
- from pageview
- where website_id=$1
- and created_at between $2 and $3
- group by 1
- order by 1
- `,
- website_id,
- start_at,
- end_at,
- ),
- );
+ if (url) {
+ urlFilter = `and url=$${params.length + 1}`;
+ params.push(decodeURIComponent(url));
}
- if (db === MYSQL) {
- return runQuery(
- prisma.$queryRaw(
- `
- select ${getDateQuery(db, 'created_at', unit, timezone)} t,
- count(${count}) y
- from pageview
- where website_id=?
- and created_at between ? and ?
- group by 1
- order by 1
- `,
- website_id,
- start_at,
- end_at,
- ),
- );
- }
-
- return Promise.reject(new Error('Unknown database.'));
+ return rawQuery(
+ `
+ select ${getDateQuery('created_at', unit, timezone)} t,
+ count(${count}) y
+ from pageview
+ where website_id=$1
+ and created_at between $2 and $3
+ ${urlFilter}
+ group by 1
+ order by 1
+ `,
+ params,
+ );
}
-export function getRankings(website_id, start_at, end_at, type, table, domain) {
- const db = getDatabase();
+export function getSessionMetrics(website_id, start_at, end_at, field, url) {
+ const params = [website_id, start_at, end_at];
+ let urlFilter = '';
- const filter = domain ? `and ${type} not like '%${domain}%'` : '';
+ if (url) {
+ urlFilter = `and url=$${params.length + 1}`;
+ params.push(decodeURIComponent(url));
+ }
- if (db === POSTGRESQL) {
- return runQuery(
- prisma.$queryRaw(
- `
- select distinct ${type} x, count(*) y
- from ${table}
+ return rawQuery(
+ `
+ select ${field} x, count(*) y
+ from session
+ where session_id in (
+ select session_id
+ from pageview
where website_id=$1
and created_at between $2 and $3
- ${filter}
- group by 1
- order by 2 desc
- `,
- website_id,
- start_at,
- end_at,
- ),
- );
+ ${urlFilter}
+ )
+ group by 1
+ order by 2 desc
+ `,
+ params,
+ );
+}
+
+export function getPageviewMetrics(website_id, start_at, end_at, field, table, domain, url) {
+ const params = [website_id, start_at, end_at];
+ let domainFilter = '';
+ let urlFilter = '';
+
+ if (domain) {
+ domainFilter = `and referrer not like $${params.length + 1}`;
+ params.push(`%${domain}%`);
}
- if (db === MYSQL) {
- return runQuery(
- prisma.$queryRaw(
- `
- select distinct ${type} x, count(*) y
- from ${table}
- where website_id=?
- and created_at between ? and ?
- ${filter}
- group by 1
- order by 2 desc
- `,
- website_id,
- start_at,
- end_at,
- ),
- );
+ if (url) {
+ urlFilter = `and url=$${params.length + 1}`;
+ params.push(decodeURIComponent(url));
}
- return Promise.reject(new Error('Unknown database.'));
+ return rawQuery(
+ `
+ select ${field} x, count(*) y
+ from ${table}
+ where website_id=$1
+ and created_at between $2 and $3
+ ${domainFilter}
+ ${urlFilter}
+ group by 1
+ order by 2 desc
+ `,
+ params,
+ );
}
export function getActiveVisitors(website_id) {
- const db = getDatabase();
const date = subMinutes(new Date(), 5);
+ const params = [website_id, date];
- if (db === POSTGRESQL) {
- return runQuery(
- prisma.$queryRaw(
- `
+ return rawQuery(
+ `
select count(distinct session_id) x
from pageview
where website_id=$1
and created_at >= $2
`,
- website_id,
- date,
- ),
- );
+ params,
+ );
+}
+
+export function getEvents(website_id, start_at, end_at, timezone = 'utc', unit = 'day', url) {
+ const params = [website_id, start_at, end_at];
+ let urlFilter = '';
+
+ if (url) {
+ urlFilter = `and url=$${params.length + 1}`;
+ params.push(decodeURIComponent(url));
}
- if (db === MYSQL) {
- return runQuery(
- prisma.$queryRaw(
- `
- select count(distinct session_id) x
- from pageview
- where website_id=?
- and created_at >= ?
+ return rawQuery(
+ `
+ select
+ event_value x,
+ ${getDateQuery('created_at', unit, timezone)} t,
+ count(*) y
+ from event
+ where website_id=$1
+ and created_at between $2 and $3
+ ${urlFilter}
+ group by 1, 2
+ order by 2
`,
- website_id,
- date,
- ),
- );
- }
-
- return Promise.reject(new Error('Unknown database.'));
-}
-
-export function getEvents(website_id, start_at, end_at, timezone = 'utc', unit = 'day') {
- const db = getDatabase();
-
- if (db === POSTGRESQL) {
- return runQuery(
- prisma.$queryRaw(
- `
- select
- event_value x,
- ${getDateQuery(db, 'created_at', unit, timezone)} t,
- count(*) y
- from event
- where website_id=$1
- and created_at between $2 and $3
- group by 1, 2
- order by 2
- `,
- website_id,
- start_at,
- end_at,
- ),
- );
- }
-
- if (db === MYSQL) {
- return runQuery(
- prisma.$queryRaw(
- `
- select
- event_value x,
- ${getDateQuery(db, 'created_at', unit, timezone)} t,
- count(*) y
- from event
- where website_id=?
- and created_at between ? and ?
- group by 1, 2
- order by 2
- `,
- website_id,
- start_at,
- end_at,
- ),
- );
- }
-
- return Promise.reject(new Error('Unknown database.'));
+ params,
+ );
}
diff --git a/lib/url.js b/lib/url.js
index d90c390e..49acedf7 100644
--- a/lib/url.js
+++ b/lib/url.js
@@ -13,3 +13,18 @@ export function getDomainName(str) {
return str;
}
}
+
+export function getQueryString(params) {
+ const map = Object.keys(params).reduce((arr, key) => {
+ if (params[key] !== undefined) {
+ return arr.concat(`${key}=${encodeURIComponent(params[key])}`);
+ }
+ return arr;
+ }, []);
+
+ if (map.length) {
+ return `?${map.join('&')}`;
+ }
+
+ return '';
+}
diff --git a/lib/web.js b/lib/web.js
index 82c1e75b..a20a09c8 100644
--- a/lib/web.js
+++ b/lib/web.js
@@ -1,3 +1,5 @@
+import { getQueryString } from './url';
+
export const apiRequest = (method, url, body) =>
fetch(url, {
method,
@@ -20,19 +22,9 @@ export const apiRequest = (method, url, body) =>
return null;
});
-const parseQuery = (url, params = {}) => {
- const query = Object.keys(params).reduce((values, key) => {
- if (params[key] !== undefined) {
- return values.concat(`${key}=${encodeURIComponent(params[key])}`);
- }
- return values;
- }, []);
- return query.length ? `${url}?${query.join('&')}` : url;
-};
+export const get = (url, params) => apiRequest('get', `${url}${getQueryString(params)}`);
-export const get = (url, params) => apiRequest('get', parseQuery(url, params));
-
-export const del = (url, params) => apiRequest('delete', parseQuery(url, params));
+export const del = (url, params) => apiRequest('delete', `${url}${getQueryString(params)}`);
export const post = (url, params) => apiRequest('post', url, JSON.stringify(params));
diff --git a/package.json b/package.json
index 639a5bc5..cd7843f0 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "umami",
- "version": "0.50.0",
+ "version": "0.54.0",
"description": "A simple, fast, website analytics alternative to Google Analytics. ",
"author": "Mike Cao ",
"license": "MIT",
diff --git a/pages/api/website/[id]/events.js b/pages/api/website/[id]/events.js
index 4b9a656d..da610f17 100644
--- a/pages/api/website/[id]/events.js
+++ b/pages/api/website/[id]/events.js
@@ -11,7 +11,7 @@ export default async (req, res) => {
return unauthorized(res);
}
- const { id, start_at, end_at, unit, tz } = req.query;
+ const { id, start_at, end_at, unit, tz, url } = req.query;
if (!moment.tz.zone(tz) || !unitTypes.includes(unit)) {
return badRequest(res);
@@ -21,7 +21,7 @@ export default async (req, res) => {
const startDate = new Date(+start_at);
const endDate = new Date(+end_at);
- const events = await getEvents(websiteId, startDate, endDate, tz, unit);
+ const events = await getEvents(websiteId, startDate, endDate, tz, unit, url);
return ok(res, events);
}
diff --git a/pages/api/website/[id]/metrics.js b/pages/api/website/[id]/metrics.js
index bb5c977b..f7178bf4 100644
--- a/pages/api/website/[id]/metrics.js
+++ b/pages/api/website/[id]/metrics.js
@@ -8,13 +8,13 @@ export default async (req, res) => {
return unauthorized(res);
}
- const { id, start_at, end_at } = req.query;
+ const { id, start_at, end_at, url } = req.query;
const websiteId = +id;
const startDate = new Date(+start_at);
const endDate = new Date(+end_at);
- const metrics = await getMetrics(websiteId, startDate, endDate);
+ const metrics = await getMetrics(websiteId, startDate, endDate, url);
const stats = Object.keys(metrics[0]).reduce((obj, key) => {
obj[key] = Number(metrics[0][key]) || 0;
diff --git a/pages/api/website/[id]/pageviews.js b/pages/api/website/[id]/pageviews.js
index 016c6646..2191a4c4 100644
--- a/pages/api/website/[id]/pageviews.js
+++ b/pages/api/website/[id]/pageviews.js
@@ -11,7 +11,7 @@ export default async (req, res) => {
return unauthorized(res);
}
- const { id, start_at, end_at, unit, tz } = req.query;
+ const { id, start_at, end_at, unit, tz, url } = req.query;
const websiteId = +id;
const startDate = new Date(+start_at);
@@ -22,8 +22,8 @@ export default async (req, res) => {
}
const [pageviews, uniques] = await Promise.all([
- getPageviews(websiteId, startDate, endDate, tz, unit, '*'),
- getPageviews(websiteId, startDate, endDate, tz, unit, 'distinct session_id'),
+ getPageviews(websiteId, startDate, endDate, tz, unit, '*', url),
+ getPageviews(websiteId, startDate, endDate, tz, unit, 'distinct session_id', url),
]);
return ok(res, { pageviews, uniques });
diff --git a/pages/api/website/[id]/rankings.js b/pages/api/website/[id]/rankings.js
index 90a0cd9f..3def9d3a 100644
--- a/pages/api/website/[id]/rankings.js
+++ b/pages/api/website/[id]/rankings.js
@@ -1,4 +1,4 @@
-import { getRankings } from 'lib/queries';
+import { getPageviewMetrics, getSessionMetrics } from 'lib/queries';
import { ok, badRequest, methodNotAllowed, unauthorized } from 'lib/response';
import { DOMAIN_REGEX } from 'lib/constants';
import { allowQuery } from 'lib/auth';
@@ -31,32 +31,35 @@ export default async (req, res) => {
return unauthorized(res);
}
- const { id, type, start_at, end_at, domain } = req.query;
+ const { id, type, start_at, end_at, domain, url } = req.query;
+
+ if (domain && !DOMAIN_REGEX.test(domain)) {
+ return badRequest(res);
+ }
const websiteId = +id;
const startDate = new Date(+start_at);
const endDate = new Date(+end_at);
- if (
- type !== 'event' &&
- !sessionColumns.includes(type) &&
- !pageviewColumns.includes(type) &&
- domain &&
- DOMAIN_REGEX.test(domain)
- ) {
- return badRequest(res);
+ if (sessionColumns.includes(type)) {
+ const data = await getSessionMetrics(websiteId, startDate, endDate, type, url);
+
+ return ok(res, data);
}
- const rankings = await getRankings(
- websiteId,
- startDate,
- endDate,
- getColumn(type),
- getTable(type),
- domain,
- );
+ if (type === 'event' || pageviewColumns.includes(type)) {
+ const data = await getPageviewMetrics(
+ websiteId,
+ startDate,
+ endDate,
+ getColumn(type),
+ getTable(type),
+ domain,
+ type !== 'url' ? url : undefined,
+ );
- return ok(res, rankings);
+ return ok(res, data);
+ }
}
return methodNotAllowed(res);