diff --git a/components/WebsiteDetails.js b/components/WebsiteDetails.js index 46d439a3..4f901efc 100644 --- a/components/WebsiteDetails.js +++ b/components/WebsiteDetails.js @@ -1,7 +1,6 @@ import React, { useEffect, useState } from 'react'; import classNames from 'classnames'; import WebsiteChart from 'components/metrics/WebsiteChart'; -import MetricsTable from 'components/metrics/MetricsTable'; import WorldMap from 'components/common/WorldMap'; import Page from 'components/layout/Page'; import WebsiteHeader from 'components/metrics/WebsiteHeader'; @@ -53,6 +52,17 @@ export default function WebsiteDetails({ websiteId, defaultDateRange = '7day' }) }, ]; + const tableProps = { + websiteId, + startDate, + endDate, + limit: 10, + onExpand: handleExpand, + websiteDomain: data?.domain, + }; + + const DetailsComponent = expand?.component; + async function loadData() { setData(await get(`/api/website/${websiteId}`)); } @@ -73,10 +83,6 @@ export default function WebsiteDetails({ websiteId, defaultDateRange = '7day' }) setExpand(menuOptions.find(e => e.value === value)); } - function getMetricLabel(type) { - return type === 'url' || type === 'referrer' ? 'Views' : 'Visitors'; - } - useEffect(() => { if (websiteId) { loadData(); @@ -87,15 +93,6 @@ export default function WebsiteDetails({ websiteId, defaultDateRange = '7day' }) return null; } - const tableProps = { - websiteId, - startDate, - endDate, - limit: 10, - onExpand: handleExpand, - websiteDomain: data?.domain, - }; - return (
@@ -149,7 +146,7 @@ export default function WebsiteDetails({ websiteId, defaultDateRange = '7day' }) selectedOption={expand.value} onMenuSelect={handleMenuSelect} > - {expand.component({ ...tableProps, limit: false })} + )} diff --git a/components/common/ButtonGroup.js b/components/common/ButtonGroup.js new file mode 100644 index 00000000..26707fb9 --- /dev/null +++ b/components/common/ButtonGroup.js @@ -0,0 +1,29 @@ +import React from 'react'; +import classNames from 'classnames'; +import Button from './Button'; +import styles from './ButtonGroup.module.css'; + +export default function ButtonGroup({ + items = [], + selectedItem, + className, + size, + icon, + onClick = () => {}, +}) { + return ( +
+ {items.map(item => ( + + ))} +
+ ); +} diff --git a/components/common/ButtonGroup.module.css b/components/common/ButtonGroup.module.css new file mode 100644 index 00000000..b93189e5 --- /dev/null +++ b/components/common/ButtonGroup.module.css @@ -0,0 +1,25 @@ +.group { + display: inline-flex; + border-radius: 4px; + overflow: hidden; + border: 1px solid var(--gray400); +} + +.group .button { + border-radius: 0; + background: var(--gray50); + border-left: 1px solid var(--gray400); + padding: 4px 8px; +} + +.group .button:first-child { + border: 0; +} + +.group .button + .button { + margin: 0; +} + +.selected { + font-weight: 600; +} diff --git a/components/layout/Header.js b/components/layout/Header.js index e4abb2df..76509a16 100644 --- a/components/layout/Header.js +++ b/components/layout/Header.js @@ -16,7 +16,7 @@ export default function Header() {
} size="large" className={styles.logo} /> - {user ? umami : 'umami'} + umami
{user && ( diff --git a/components/metrics/MetricsTable.js b/components/metrics/MetricsTable.js index ba4e518f..bc41aa3d 100644 --- a/components/metrics/MetricsTable.js +++ b/components/metrics/MetricsTable.js @@ -38,7 +38,7 @@ export default function MetricsTable({ return items; } return []; - }, [data]); + }, [data, dataFilter, filterOptions]); async function loadData() { const data = await get(`/api/website/${websiteId}/rankings`, { diff --git a/components/metrics/PagesTable.js b/components/metrics/PagesTable.js index 620b96bf..48ba85c3 100644 --- a/components/metrics/PagesTable.js +++ b/components/metrics/PagesTable.js @@ -1,6 +1,7 @@ -import React from 'react'; +import React, { useState } from 'react'; import MetricsTable from './MetricsTable'; import { urlFilter } from 'lib/filters'; +import ButtonGroup from '../common/ButtonGroup'; export default function PagesTable({ websiteId, @@ -10,19 +11,32 @@ export default function PagesTable({ limit, onExpand, }) { + const [filter, setFilter] = useState('Combined'); + return ( } websiteId={websiteId} startDate={startDate} endDate={endDate} limit={limit} dataFilter={urlFilter} - filterOptions={{ domain: websiteDomain }} + filterOptions={{ domain: websiteDomain, raw: filter === 'Raw' }} onExpand={onExpand} /> ); } + +const FilterButtons = ({ selected, onClick }) => { + return ( + + ); +}; diff --git a/components/metrics/QuickButtons.js b/components/metrics/QuickButtons.js index 515fd668..74ebebbd 100644 --- a/components/metrics/QuickButtons.js +++ b/components/metrics/QuickButtons.js @@ -1,31 +1,28 @@ import React from 'react'; -import classNames from 'classnames'; -import Button from '../common/Button'; +import ButtonGroup from 'components/common/ButtonGroup'; import { getDateRange } from 'lib/date'; import styles from './QuickButtons.module.css'; const options = { - '24hour': '24h', - '7day': '7d', - '30day': '30d', + '24h': '24hour', + '7d': '7day', + '30d': '30day', }; export default function QuickButtons({ value, onChange }) { + const selectedItem = Object.keys(options).find(key => options[key] === value); + function handleClick(value) { - onChange(getDateRange(value)); + onChange(getDateRange(options[value])); } return ( -
- {Object.keys(options).map(key => ( - - ))} -
+ ); } diff --git a/components/metrics/ReferrersTable.js b/components/metrics/ReferrersTable.js index 516fbd98..fbcfff87 100644 --- a/components/metrics/ReferrersTable.js +++ b/components/metrics/ReferrersTable.js @@ -1,6 +1,7 @@ -import React from 'react'; +import React, { useState } from 'react'; import MetricsTable from './MetricsTable'; import { refFilter } from 'lib/filters'; +import ButtonGroup from '../common/ButtonGroup'; export default function Referrers({ websiteId, @@ -10,19 +11,36 @@ export default function Referrers({ limit, onExpand = () => {}, }) { + const [filter, setFilter] = useState('Combined'); + return ( } websiteId={websiteId} startDate={startDate} endDate={endDate} limit={limit} dataFilter={refFilter} - filterOptions={{ domain: websiteDomain }} + filterOptions={{ + domain: websiteDomain, + domainOnly: filter === 'Domain only', + raw: filter === 'Raw', + }} onExpand={onExpand} /> ); } + +const FilterButtons = ({ selected, onClick }) => { + return ( + + ); +}; diff --git a/lib/filters.js b/lib/filters.js index f892d0fe..dac64312 100644 --- a/lib/filters.js +++ b/lib/filters.js @@ -5,11 +5,15 @@ import { removeTrailingSlash } from './format'; export const browserFilter = data => data.map(({ x, ...props }) => ({ x: BROWSERS[x] || x, ...props })); -export const urlFilter = (data, { domain }) => { +export const urlFilter = (data, { domain, raw }) => { const isValidUrl = url => { return url !== '' && !url.startsWith('#'); }; + if (raw) { + return data.filter(({ x }) => isValidUrl(x)); + } + const cleanUrl = url => { try { const { pathname, searchParams } = new URL(url); @@ -47,11 +51,16 @@ export const urlFilter = (data, { domain }) => { .sort(firstBy('y', -1).thenBy('x')); }; -export const refFilter = (data, { domain, domainsOnly }) => { +export const refFilter = (data, { domain, domainOnly, raw }) => { const isValidRef = ref => { return ref !== '' && !ref.startsWith('/') && !ref.startsWith('#'); }; + if (raw) { + const regex = new RegExp(`http[s]?://([^.]+.)?${domain}`); + return data.filter(({ x }) => isValidRef(x) && !regex.test(x)); + } + const cleanUrl = url => { try { const { hostname, origin, pathname, searchParams, protocol } = new URL(url); @@ -60,7 +69,7 @@ export const refFilter = (data, { domain, domainsOnly }) => { return null; } - if (domainsOnly && hostname) { + if (domainOnly && hostname) { return hostname; } diff --git a/package.json b/package.json index 8aced06d..f54aadff 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "umami", - "version": "0.15.1", + "version": "0.16.0", "description": "A simple, fast, website analytics alternative to Google Analytics. ", "author": "Mike Cao ", "license": "MIT", @@ -49,7 +49,6 @@ "date-fns-tz": "^1.0.10", "detect-browser": "^5.1.1", "dotenv": "^8.2.0", - "escape-string-regexp": "^4.0.0", "formik": "^2.1.5", "geolite2-redist": "^1.0.7", "is-localhost-ip": "^1.4.0", diff --git a/yarn.lock b/yarn.lock index d446278e..b0a5a28f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3500,11 +3500,6 @@ escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= -escape-string-regexp@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" - integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== - eslint-config-prettier@^6.11.0: version "6.11.0" resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-6.11.0.tgz#f6d2238c1290d01c859a8b5c1f7d352a0b0da8b1"