diff --git a/assets/arrow-up-right-from-square.svg b/assets/arrow-up-right-from-square.svg
new file mode 100644
index 00000000..90ad457f
--- /dev/null
+++ b/assets/arrow-up-right-from-square.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/components/common/ErrorMessage.module.css b/components/common/ErrorMessage.module.css
index 232b5f84..88769cf5 100644
--- a/components/common/ErrorMessage.module.css
+++ b/components/common/ErrorMessage.module.css
@@ -6,6 +6,8 @@
margin: auto;
display: flex;
z-index: 1;
+ background-color: var(--gray50);
+ padding: 10px;
}
.icon {
diff --git a/components/metrics/FilterTags.js b/components/metrics/FilterTags.js
new file mode 100644
index 00000000..9720525e
--- /dev/null
+++ b/components/metrics/FilterTags.js
@@ -0,0 +1,27 @@
+import React from 'react';
+import classNames from 'classnames';
+import Button from 'components/common/Button';
+import Times from 'assets/times.svg';
+import styles from './FilterTags.module.css';
+
+export default function FilterTags({ params, onClick }) {
+ if (Object.keys(params).filter(key => params[key]).length === 0) {
+ return null;
+ }
+ return (
+
+ {Object.keys(params).map(key => {
+ if (!params[key]) {
+ return null;
+ }
+ return (
+
+ } onClick={() => onClick(key)} variant="action" iconRight>
+ {`${key}: ${params[key]}`}
+
+
+ );
+ })}
+
+ );
+}
diff --git a/components/metrics/FilterTags.module.css b/components/metrics/FilterTags.module.css
new file mode 100644
index 00000000..bb1536e5
--- /dev/null
+++ b/components/metrics/FilterTags.module.css
@@ -0,0 +1,14 @@
+.filters {
+ display: flex;
+ justify-content: flex-start;
+ align-items: flex-start;
+}
+
+.tag {
+ text-align: center;
+ margin-bottom: 10px;
+}
+
+.tag + .tag {
+ margin-left: 20px;
+}
diff --git a/components/metrics/MetricsBar.js b/components/metrics/MetricsBar.js
index 435870a1..7852b96c 100644
--- a/components/metrics/MetricsBar.js
+++ b/components/metrics/MetricsBar.js
@@ -18,7 +18,7 @@ export default function MetricsBar({ websiteId, className }) {
const { startDate, endDate, modified } = dateRange;
const [format, setFormat] = useState(true);
const {
- query: { url },
+ query: { url, ref },
} = usePageQuery();
const { data, error, loading } = useFetch(
@@ -28,10 +28,11 @@ export default function MetricsBar({ websiteId, className }) {
start_at: +startDate,
end_at: +endDate,
url,
+ ref,
},
headers: { [TOKEN_HEADER]: shareToken?.token },
},
- [url, modified],
+ [modified, url, ref],
);
const formatFunc = format
diff --git a/components/metrics/PagesTable.js b/components/metrics/PagesTable.js
index b500e270..6fe8c139 100644
--- a/components/metrics/PagesTable.js
+++ b/components/metrics/PagesTable.js
@@ -16,7 +16,7 @@ export default function PagesTable({ websiteId, websiteDomain, showFilters, ...p
const [filter, setFilter] = useState(FILTER_COMBINED);
const {
resolve,
- query: { url },
+ query: { url: currentUrl },
} = usePageQuery();
const buttons = [
@@ -27,16 +27,16 @@ export default function PagesTable({ websiteId, websiteDomain, showFilters, ...p
{ label: , value: FILTER_RAW },
];
- const renderLink = ({ x }) => {
+ const renderLink = ({ x: url }) => {
return (
-
+
- {safeDecodeURI(x)}
+ {safeDecodeURI(url)}
);
diff --git a/components/metrics/ReferrersTable.js b/components/metrics/ReferrersTable.js
index 4dad8655..f2fa0215 100644
--- a/components/metrics/ReferrersTable.js
+++ b/components/metrics/ReferrersTable.js
@@ -4,6 +4,12 @@ import MetricsTable from './MetricsTable';
import FilterButtons from 'components/common/FilterButtons';
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;
@@ -11,6 +17,10 @@ export const FILTER_RAW = 2;
export default function ReferrersTable({ websiteId, websiteDomain, showFilters, ...props }) {
const [filter, setFilter] = useState(FILTER_COMBINED);
+ const {
+ resolve,
+ query: { ref: currentRef },
+ } = usePageQuery();
const buttons = [
{
@@ -24,13 +34,24 @@ export default function ReferrersTable({ websiteId, websiteDomain, showFilters,
{ label: , value: FILTER_RAW },
];
- const renderLink = ({ w: href, x: url }) => {
- return (href || url).startsWith('http') ? (
-
- {safeDecodeURI(url)}
-
- ) : (
- safeDecodeURI(url)
+ const renderLink = ({ w: link, x: label }) => {
+ console.log({ link, label });
+ return (
+
);
};
diff --git a/components/metrics/ReferrersTable.module.css b/components/metrics/ReferrersTable.module.css
new file mode 100644
index 00000000..238667f3
--- /dev/null
+++ b/components/metrics/ReferrersTable.module.css
@@ -0,0 +1,31 @@
+body .inactive {
+ color: var(--gray500);
+}
+
+body .active {
+ color: var(--gray900);
+ font-weight: 600;
+}
+
+.row {
+ display: flex;
+ justify-content: space-between;
+}
+
+.row .link {
+ display: none;
+}
+
+.row .label {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.row:hover .link {
+ display: block;
+}
+
+.icon {
+ cursor: pointer;
+}
diff --git a/components/metrics/WebsiteChart.js b/components/metrics/WebsiteChart.js
index fca6d541..dea86ae6 100644
--- a/components/metrics/WebsiteChart.js
+++ b/components/metrics/WebsiteChart.js
@@ -5,17 +5,16 @@ 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 ErrorMessage from 'components/common/ErrorMessage';
+import FilterTags from 'components/metrics/FilterTags';
+import useShareToken from 'hooks/useShareToken';
+import { TOKEN_HEADER } from 'lib/constants';
import styles from './WebsiteChart.module.css';
-import ErrorMessage from '../common/ErrorMessage';
-import useShareToken from '../../hooks/useShareToken';
-import { TOKEN_HEADER } from '../../lib/constants';
export default function WebsiteChart({
websiteId,
@@ -33,7 +32,7 @@ export default function WebsiteChart({
const {
router,
resolve,
- query: { url },
+ query: { url, ref },
} = usePageQuery();
const { data, loading, error } = useFetch(
@@ -45,11 +44,12 @@ export default function WebsiteChart({
unit,
tz: timezone,
url,
+ ref,
},
onDataLoad,
headers: { [TOKEN_HEADER]: shareToken?.token },
},
- [url, modified],
+ [modified, url, ref],
);
const chartData = useMemo(() => {
@@ -62,8 +62,8 @@ export default function WebsiteChart({
return { pageviews: [], sessions: [] };
}, [data]);
- function handleCloseFilter() {
- router.push(resolve({ url: undefined }));
+ function handleCloseFilter(param) {
+ router.push(resolve({ [param]: undefined }));
}
return (
@@ -75,7 +75,7 @@ export default function WebsiteChart({
stickyClassName={styles.sticky}
enabled={stickyHeader}
>
- {url && }
+
@@ -90,7 +90,7 @@ export default function WebsiteChart({
-
+
{error &&
}
{!hideChart && (
);
}
-
-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 0e947aea..d0a8ea68 100644
--- a/components/metrics/WebsiteChart.module.css
+++ b/components/metrics/WebsiteChart.module.css
@@ -1,9 +1,14 @@
.container {
+ position: relative;
display: flex;
flex-direction: column;
align-self: stretch;
}
+.chart {
+ position: relative;
+}
+
.title {
font-size: var(--font-size-large);
line-height: 60px;
@@ -37,11 +42,6 @@
align-items: center;
}
-.url {
- text-align: center;
- margin-bottom: 10px;
-}
-
@media only screen and (max-width: 992px) {
.filter {
display: block;
diff --git a/components/pages/WebsiteDetails.js b/components/pages/WebsiteDetails.js
index 0ddc89e2..a8442ea1 100644
--- a/components/pages/WebsiteDetails.js
+++ b/components/pages/WebsiteDetails.js
@@ -118,9 +118,9 @@ export default function WebsiteDetails({ websiteId }) {
showLink={false}
stickyHeader
/>
+ {!chartLoaded && }
- {!chartLoaded &&
}
{chartLoaded && !view && (
diff --git a/lib/filters.js b/lib/filters.js
index bb8b0c38..af54b965 100644
--- a/lib/filters.js
+++ b/lib/filters.js
@@ -95,9 +95,7 @@ export const refFilter = (data, { domain, domainOnly, raw }) => {
const url = cleanUrl(x);
- if (!domainOnly && !raw) {
- links[url] = x;
- }
+ links[url] = x;
if (url) {
if (!obj[url]) {
diff --git a/lib/queries.js b/lib/queries.js
index ff9d557f..78b5bfdb 100644
--- a/lib/queries.js
+++ b/lib/queries.js
@@ -318,14 +318,20 @@ export async function getEvents(websites, start_at) {
export function getWebsiteStats(website_id, start_at, end_at, filters = {}) {
const params = [website_id, start_at, end_at];
- const { url } = filters;
+ const { url, ref } = filters;
let urlFilter = '';
+ let refFilter = '';
if (url) {
urlFilter = `and url=$${params.length + 1}`;
params.push(decodeURIComponent(url));
}
+ if (ref) {
+ refFilter = `and referrer like $${params.length + 1}`;
+ params.push(`%${decodeURIComponent(ref)}%`);
+ }
+
return rawQuery(
`
select sum(t.c) as "pageviews",
@@ -341,6 +347,7 @@ export function getWebsiteStats(website_id, start_at, end_at, filters = {}) {
where website_id=$1
and created_at between $2 and $3
${urlFilter}
+ ${refFilter}
group by 1, 2
) t
`,
@@ -355,16 +362,24 @@ export function getPageviewStats(
timezone = 'utc',
unit = 'day',
count = '*',
- url,
+ filters = {},
) {
const params = [website_id, start_at, end_at];
+ const { url, ref } = filters;
+
let urlFilter = '';
+ let refFilter = '';
if (url) {
urlFilter = `and url=$${params.length + 1}`;
params.push(decodeURIComponent(url));
}
+ if (ref) {
+ refFilter = `and referrer like $${params.length + 1}`;
+ params.push(`%${decodeURIComponent(ref)}%`);
+ }
+
return rawQuery(
`
select ${getDateQuery('created_at', unit, timezone)} t,
@@ -373,6 +388,7 @@ export function getPageviewStats(
where website_id=$1
and created_at between $2 and $3
${urlFilter}
+ ${refFilter}
group by 1
order by 1
`,
@@ -411,7 +427,7 @@ export function getSessionMetrics(website_id, start_at, end_at, field, filters =
export function getPageviewMetrics(website_id, start_at, end_at, field, table, filters = {}) {
const params = [website_id, start_at, end_at];
- const { domain, url } = filters;
+ const { domain, url, ref } = filters;
let domainFilter = '';
let urlFilter = '';
diff --git a/pages/api/website/[id]/pageviews.js b/pages/api/website/[id]/pageviews.js
index 965f28ae..22966ca7 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, url } = req.query;
+ const { id, start_at, end_at, unit, tz, url, ref } = req.query;
const websiteId = +id;
const startDate = new Date(+start_at);
@@ -22,8 +22,11 @@ export default async (req, res) => {
}
const [pageviews, sessions] = await Promise.all([
- getPageviewStats(websiteId, startDate, endDate, tz, unit, '*', url),
- getPageviewStats(websiteId, startDate, endDate, tz, unit, 'distinct session_id', url),
+ getPageviewStats(websiteId, startDate, endDate, tz, unit, '*', { url, ref }),
+ getPageviewStats(websiteId, startDate, endDate, tz, unit, 'distinct session_id', {
+ url,
+ ref,
+ }),
]);
return ok(res, { pageviews, sessions });
diff --git a/pages/api/website/[id]/stats.js b/pages/api/website/[id]/stats.js
index 80dd22ec..9dd82361 100644
--- a/pages/api/website/[id]/stats.js
+++ b/pages/api/website/[id]/stats.js
@@ -8,7 +8,7 @@ export default async (req, res) => {
return unauthorized(res);
}
- const { id, start_at, end_at, url } = req.query;
+ const { id, start_at, end_at, url, ref } = req.query;
const websiteId = +id;
const startDate = new Date(+start_at);
@@ -18,8 +18,8 @@ export default async (req, res) => {
const prevStartDate = new Date(+start_at - distance);
const prevEndDate = new Date(+end_at - distance);
- const metrics = await getWebsiteStats(websiteId, startDate, endDate, { url });
- const prevPeriod = await getWebsiteStats(websiteId, prevStartDate, prevEndDate, { url });
+ const metrics = await getWebsiteStats(websiteId, startDate, endDate, { url, ref });
+ const prevPeriod = await getWebsiteStats(websiteId, prevStartDate, prevEndDate, { url, ref });
const stats = Object.keys(metrics[0]).reduce((obj, key) => {
obj[key] = {