mirror of
https://github.com/kremalicious/umami.git
synced 2024-12-18 15:23:38 +01:00
commit
681852b225
10
README.md
10
README.md
@ -82,6 +82,16 @@ To build the umami container and start up a Postgres database, run:
|
|||||||
docker-compose up
|
docker-compose up
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Getting updates
|
||||||
|
|
||||||
|
To get the latest features, simply do a pull, install any new dependencies, and rebuild:
|
||||||
|
|
||||||
|
```
|
||||||
|
git pull
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
MIT
|
MIT
|
@ -1,19 +1,28 @@
|
|||||||
import React from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { setDateRange } from 'redux/actions/websites';
|
import { setDateRange } from 'redux/actions/websites';
|
||||||
import Button from './Button';
|
import Button from './Button';
|
||||||
import Refresh from 'assets/redo.svg';
|
import Refresh from 'assets/redo.svg';
|
||||||
|
import Dots from 'assets/ellipsis-h.svg';
|
||||||
import { useDateRange } from 'hooks/useDateRange';
|
import { useDateRange } from 'hooks/useDateRange';
|
||||||
|
import { getDateRange } from '../../lib/date';
|
||||||
|
|
||||||
export default function RefreshButton({ websiteId }) {
|
export default function RefreshButton({ websiteId }) {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const dateRange = useDateRange(websiteId);
|
const dateRange = useDateRange(websiteId);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const completed = useSelector(state => state.queries[`/api/website/${websiteId}/metrics`]);
|
||||||
|
|
||||||
function handleClick() {
|
function handleClick() {
|
||||||
if (dateRange) {
|
if (dateRange) {
|
||||||
dispatch(setDateRange(websiteId, dateRange));
|
setLoading(true);
|
||||||
|
dispatch(setDateRange(websiteId, getDateRange(dateRange.value)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Button icon={<Refresh />} size="small" onClick={handleClick} />;
|
useEffect(() => {
|
||||||
|
setLoading(false);
|
||||||
|
}, [completed]);
|
||||||
|
|
||||||
|
return <Button icon={loading ? <Dots /> : <Refresh />} size="small" onClick={handleClick} />;
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import classNames from 'classnames';
|
|||||||
import ChartJS from 'chart.js';
|
import ChartJS from 'chart.js';
|
||||||
import styles from './BarChart.module.css';
|
import styles from './BarChart.module.css';
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
import { formatLongNumber } from '../../lib/format';
|
import { formatLongNumber } from 'lib/format';
|
||||||
|
|
||||||
export default function BarChart({
|
export default function BarChart({
|
||||||
chartId,
|
chartId,
|
||||||
@ -22,30 +22,39 @@ export default function BarChart({
|
|||||||
const chart = useRef();
|
const chart = useRef();
|
||||||
const [tooltip, setTooltip] = useState({});
|
const [tooltip, setTooltip] = useState({});
|
||||||
|
|
||||||
const renderXLabel = (label, index, values) => {
|
function renderXLabel(label, index, values) {
|
||||||
const d = new Date(values[index].value);
|
const d = new Date(values[index].value);
|
||||||
const n = records;
|
const w = canvas.current.width;
|
||||||
|
|
||||||
switch (unit) {
|
switch (unit) {
|
||||||
case 'hour':
|
case 'hour':
|
||||||
return format(d, 'ha');
|
return format(d, 'ha');
|
||||||
case 'day':
|
case 'day':
|
||||||
if (n >= 15) {
|
if (records > 31) {
|
||||||
return index % ~~(n / 15) === 0 ? format(d, 'MMM d') : '';
|
if (w <= 500) {
|
||||||
|
return index % 10 === 0 ? format(d, 'M/d') : '';
|
||||||
|
}
|
||||||
|
return index % 5 === 0 ? format(d, 'M/d') : '';
|
||||||
|
}
|
||||||
|
if (w <= 500) {
|
||||||
|
return index % 2 === 0 ? format(d, 'MMM d') : '';
|
||||||
}
|
}
|
||||||
return format(d, 'EEE M/d');
|
return format(d, 'EEE M/d');
|
||||||
case 'month':
|
case 'month':
|
||||||
|
if (w <= 660) {
|
||||||
|
return format(d, 'MMM');
|
||||||
|
}
|
||||||
return format(d, 'MMMM');
|
return format(d, 'MMMM');
|
||||||
default:
|
default:
|
||||||
return label;
|
return label;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const renderYLabel = label => {
|
function renderYLabel(label) {
|
||||||
return +label > 1 ? formatLongNumber(label) : label;
|
return +label > 1 ? formatLongNumber(label) : label;
|
||||||
};
|
}
|
||||||
|
|
||||||
const renderTooltip = model => {
|
function renderTooltip(model) {
|
||||||
const { opacity, title, body, labelColors } = model;
|
const { opacity, title, body, labelColors } = model;
|
||||||
|
|
||||||
if (!opacity) {
|
if (!opacity) {
|
||||||
@ -60,9 +69,9 @@ export default function BarChart({
|
|||||||
labelColor: labelColors[0].backgroundColor,
|
labelColor: labelColors[0].backgroundColor,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const createChart = () => {
|
function createChart() {
|
||||||
const options = {
|
const options = {
|
||||||
animation: {
|
animation: {
|
||||||
duration: animationDuration,
|
duration: animationDuration,
|
||||||
@ -119,9 +128,9 @@ export default function BarChart({
|
|||||||
},
|
},
|
||||||
options,
|
options,
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
const updateChart = () => {
|
function updateChart() {
|
||||||
const { options } = chart.current;
|
const { options } = chart.current;
|
||||||
|
|
||||||
options.scales.xAxes[0].time.unit = unit;
|
options.scales.xAxes[0].time.unit = unit;
|
||||||
@ -129,7 +138,7 @@ export default function BarChart({
|
|||||||
options.animation.duration = animationDuration;
|
options.animation.duration = animationDuration;
|
||||||
|
|
||||||
onUpdate(chart.current);
|
onUpdate(chart.current);
|
||||||
};
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (datasets) {
|
if (datasets) {
|
||||||
|
@ -21,7 +21,7 @@ export default function WebsiteHeader({ websiteId, title, showLink = false }) {
|
|||||||
<Button
|
<Button
|
||||||
icon={<Arrow />}
|
icon={<Arrow />}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
router.push('/website/[...id]', `/website/${websiteId}/${name}`, {
|
router.push('/website/[...id]', `/website/${websiteId}/${title}`, {
|
||||||
shallow: true,
|
shallow: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
import { get } from 'lib/web';
|
import { get } from 'lib/web';
|
||||||
|
import { updateQuery } from 'redux/actions/queries';
|
||||||
|
|
||||||
export default function useFetch(url, params = {}, options = {}) {
|
export default function useFetch(url, params = {}, options = {}) {
|
||||||
|
const dispatch = useDispatch();
|
||||||
const [data, setData] = useState();
|
const [data, setData] = useState();
|
||||||
const [error, setError] = useState();
|
const [error, setError] = useState();
|
||||||
const keys = Object.keys(params)
|
const keys = Object.keys(params)
|
||||||
@ -12,7 +15,11 @@ export default function useFetch(url, params = {}, options = {}) {
|
|||||||
async function loadData() {
|
async function loadData() {
|
||||||
try {
|
try {
|
||||||
setError(null);
|
setError(null);
|
||||||
|
const time = performance.now();
|
||||||
const data = await get(url, params);
|
const data = await get(url, params);
|
||||||
|
|
||||||
|
dispatch(updateQuery({ url, time: performance.now() - time, completed: Date.now() }));
|
||||||
|
|
||||||
setData(data);
|
setData(data);
|
||||||
onDataLoad(data);
|
onDataLoad(data);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -34,7 +34,6 @@ export default prisma;
|
|||||||
|
|
||||||
export async function runQuery(query) {
|
export async function runQuery(query) {
|
||||||
return query.catch(e => {
|
return query.catch(e => {
|
||||||
console.error(e);
|
|
||||||
throw e;
|
throw e;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,11 @@ export const urlFilter = (data, { raw }) => {
|
|||||||
|
|
||||||
const cleanUrl = url => {
|
const cleanUrl = url => {
|
||||||
try {
|
try {
|
||||||
const { pathname, searchParams } = new URL(url);
|
const { pathname, search, searchParams } = new URL(url);
|
||||||
|
|
||||||
|
if (search.startsWith('?/')) {
|
||||||
|
return `${pathname}${search}`;
|
||||||
|
}
|
||||||
|
|
||||||
const path = removeTrailingSlash(pathname);
|
const path = removeTrailingSlash(pathname);
|
||||||
const ref = searchParams.get('ref');
|
const ref = searchParams.get('ref');
|
||||||
|
116
lib/queries.js
116
lib/queries.js
@ -252,8 +252,9 @@ export function getMetrics(website_id, start_at, end_at) {
|
|||||||
const db = getDatabase();
|
const db = getDatabase();
|
||||||
|
|
||||||
if (db === POSTGRESQL) {
|
if (db === POSTGRESQL) {
|
||||||
return prisma.$queryRaw(
|
return runQuery(
|
||||||
`
|
prisma.$queryRaw(
|
||||||
|
`
|
||||||
select sum(t.c) as "pageviews",
|
select sum(t.c) as "pageviews",
|
||||||
count(distinct t.session_id) as "uniques",
|
count(distinct t.session_id) as "uniques",
|
||||||
sum(case when t.c = 1 then 1 else 0 end) as "bounces",
|
sum(case when t.c = 1 then 1 else 0 end) as "bounces",
|
||||||
@ -269,15 +270,17 @@ export function getMetrics(website_id, start_at, end_at) {
|
|||||||
group by 1, 2
|
group by 1, 2
|
||||||
) t
|
) t
|
||||||
`,
|
`,
|
||||||
website_id,
|
website_id,
|
||||||
start_at,
|
start_at,
|
||||||
end_at,
|
end_at,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (db === MYSQL) {
|
if (db === MYSQL) {
|
||||||
return prisma.$queryRaw(
|
return runQuery(
|
||||||
`
|
prisma.$queryRaw(
|
||||||
|
`
|
||||||
select sum(t.c) as "pageviews",
|
select sum(t.c) as "pageviews",
|
||||||
count(distinct t.session_id) as "uniques",
|
count(distinct t.session_id) as "uniques",
|
||||||
sum(case when t.c = 1 then 1 else 0 end) as "bounces",
|
sum(case when t.c = 1 then 1 else 0 end) as "bounces",
|
||||||
@ -293,9 +296,10 @@ export function getMetrics(website_id, start_at, end_at) {
|
|||||||
group by 1, 2
|
group by 1, 2
|
||||||
) t
|
) t
|
||||||
`,
|
`,
|
||||||
website_id,
|
website_id,
|
||||||
start_at,
|
start_at,
|
||||||
end_at,
|
end_at,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -313,8 +317,9 @@ export function getPageviews(
|
|||||||
const db = getDatabase();
|
const db = getDatabase();
|
||||||
|
|
||||||
if (db === POSTGRESQL) {
|
if (db === POSTGRESQL) {
|
||||||
return prisma.$queryRaw(
|
return runQuery(
|
||||||
`
|
prisma.$queryRaw(
|
||||||
|
`
|
||||||
select ${getDateQuery(db, 'created_at', unit, timezone)} t,
|
select ${getDateQuery(db, 'created_at', unit, timezone)} t,
|
||||||
count(${count}) y
|
count(${count}) y
|
||||||
from pageview
|
from pageview
|
||||||
@ -323,15 +328,17 @@ export function getPageviews(
|
|||||||
group by 1
|
group by 1
|
||||||
order by 1
|
order by 1
|
||||||
`,
|
`,
|
||||||
website_id,
|
website_id,
|
||||||
start_at,
|
start_at,
|
||||||
end_at,
|
end_at,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (db === MYSQL) {
|
if (db === MYSQL) {
|
||||||
return prisma.$queryRaw(
|
return runQuery(
|
||||||
`
|
prisma.$queryRaw(
|
||||||
|
`
|
||||||
select ${getDateQuery(db, 'created_at', unit, timezone)} t,
|
select ${getDateQuery(db, 'created_at', unit, timezone)} t,
|
||||||
count(${count}) y
|
count(${count}) y
|
||||||
from pageview
|
from pageview
|
||||||
@ -340,9 +347,10 @@ export function getPageviews(
|
|||||||
group by 1
|
group by 1
|
||||||
order by 1
|
order by 1
|
||||||
`,
|
`,
|
||||||
website_id,
|
website_id,
|
||||||
start_at,
|
start_at,
|
||||||
end_at,
|
end_at,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -355,8 +363,9 @@ export function getRankings(website_id, start_at, end_at, type, table, domain) {
|
|||||||
const filter = domain ? `and ${type} not like '%${domain}%'` : '';
|
const filter = domain ? `and ${type} not like '%${domain}%'` : '';
|
||||||
|
|
||||||
if (db === POSTGRESQL) {
|
if (db === POSTGRESQL) {
|
||||||
return prisma.$queryRaw(
|
return runQuery(
|
||||||
`
|
prisma.$queryRaw(
|
||||||
|
`
|
||||||
select distinct ${type} x, count(*) y
|
select distinct ${type} x, count(*) y
|
||||||
from ${table}
|
from ${table}
|
||||||
where website_id=$1
|
where website_id=$1
|
||||||
@ -365,15 +374,17 @@ export function getRankings(website_id, start_at, end_at, type, table, domain) {
|
|||||||
group by 1
|
group by 1
|
||||||
order by 2 desc
|
order by 2 desc
|
||||||
`,
|
`,
|
||||||
website_id,
|
website_id,
|
||||||
start_at,
|
start_at,
|
||||||
end_at,
|
end_at,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (db === MYSQL) {
|
if (db === MYSQL) {
|
||||||
return prisma.$queryRaw(
|
return runQuery(
|
||||||
`
|
prisma.$queryRaw(
|
||||||
|
`
|
||||||
select distinct ${type} x, count(*) y
|
select distinct ${type} x, count(*) y
|
||||||
from ${table}
|
from ${table}
|
||||||
where website_id=?
|
where website_id=?
|
||||||
@ -382,9 +393,10 @@ export function getRankings(website_id, start_at, end_at, type, table, domain) {
|
|||||||
group by 1
|
group by 1
|
||||||
order by 2 desc
|
order by 2 desc
|
||||||
`,
|
`,
|
||||||
website_id,
|
website_id,
|
||||||
start_at,
|
start_at,
|
||||||
end_at,
|
end_at,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -396,28 +408,32 @@ export function getActiveVisitors(website_id) {
|
|||||||
const date = subMinutes(new Date(), 5);
|
const date = subMinutes(new Date(), 5);
|
||||||
|
|
||||||
if (db === POSTGRESQL) {
|
if (db === POSTGRESQL) {
|
||||||
return prisma.$queryRaw(
|
return runQuery(
|
||||||
`
|
prisma.$queryRaw(
|
||||||
|
`
|
||||||
select count(distinct session_id) x
|
select count(distinct session_id) x
|
||||||
from pageview
|
from pageview
|
||||||
where website_id=$1
|
where website_id=$1
|
||||||
and created_at >= $2
|
and created_at >= $2
|
||||||
`,
|
`,
|
||||||
website_id,
|
website_id,
|
||||||
date,
|
date,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (db === MYSQL) {
|
if (db === MYSQL) {
|
||||||
return prisma.$queryRaw(
|
return runQuery(
|
||||||
`
|
prisma.$queryRaw(
|
||||||
|
`
|
||||||
select count(distinct session_id) x
|
select count(distinct session_id) x
|
||||||
from pageview
|
from pageview
|
||||||
where website_id=?
|
where website_id=?
|
||||||
and created_at >= ?
|
and created_at >= ?
|
||||||
`,
|
`,
|
||||||
website_id,
|
website_id,
|
||||||
date,
|
date,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -428,8 +444,9 @@ export function getEvents(website_id, start_at, end_at, timezone = 'utc', unit =
|
|||||||
const db = getDatabase();
|
const db = getDatabase();
|
||||||
|
|
||||||
if (db === POSTGRESQL) {
|
if (db === POSTGRESQL) {
|
||||||
return prisma.$queryRaw(
|
return runQuery(
|
||||||
`
|
prisma.$queryRaw(
|
||||||
|
`
|
||||||
select
|
select
|
||||||
event_value x,
|
event_value x,
|
||||||
${getDateQuery(db, 'created_at', unit, timezone)} t,
|
${getDateQuery(db, 'created_at', unit, timezone)} t,
|
||||||
@ -440,15 +457,17 @@ export function getEvents(website_id, start_at, end_at, timezone = 'utc', unit =
|
|||||||
group by 1, 2
|
group by 1, 2
|
||||||
order by 2
|
order by 2
|
||||||
`,
|
`,
|
||||||
website_id,
|
website_id,
|
||||||
start_at,
|
start_at,
|
||||||
end_at,
|
end_at,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (db === MYSQL) {
|
if (db === MYSQL) {
|
||||||
return prisma.$queryRaw(
|
return runQuery(
|
||||||
`
|
prisma.$queryRaw(
|
||||||
|
`
|
||||||
select
|
select
|
||||||
event_value x,
|
event_value x,
|
||||||
${getDateQuery(db, 'created_at', unit, timezone)} t,
|
${getDateQuery(db, 'created_at', unit, timezone)} t,
|
||||||
@ -459,9 +478,10 @@ export function getEvents(website_id, start_at, end_at, timezone = 'utc', unit =
|
|||||||
group by 1, 2
|
group by 1, 2
|
||||||
order by 2
|
order by 2
|
||||||
`,
|
`,
|
||||||
website_id,
|
website_id,
|
||||||
start_at,
|
start_at,
|
||||||
end_at,
|
end_at,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
20
package.json
20
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "umami",
|
"name": "umami",
|
||||||
"version": "0.20.0",
|
"version": "0.21.0",
|
||||||
"description": "A simple, fast, website analytics alternative to Google Analytics. ",
|
"description": "A simple, fast, website analytics alternative to Google Analytics. ",
|
||||||
"author": "Mike Cao <mike@mikecao.com>",
|
"author": "Mike Cao <mike@mikecao.com>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@ -39,14 +39,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@prisma/client": "2.5.1",
|
"@prisma/client": "2.6.1",
|
||||||
"@reduxjs/toolkit": "^1.4.0",
|
"@reduxjs/toolkit": "^1.4.0",
|
||||||
"bcrypt": "^5.0.0",
|
"bcrypt": "^5.0.0",
|
||||||
"chart.js": "^2.9.3",
|
"chart.js": "^2.9.3",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"cookie": "^0.4.1",
|
"cookie": "^0.4.1",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"date-fns": "^2.16.0",
|
"date-fns": "^2.16.1",
|
||||||
"date-fns-tz": "^1.0.10",
|
"date-fns-tz": "^1.0.10",
|
||||||
"detect-browser": "^5.1.1",
|
"detect-browser": "^5.1.1",
|
||||||
"dotenv": "^8.2.0",
|
"dotenv": "^8.2.0",
|
||||||
@ -57,7 +57,7 @@
|
|||||||
"jose": "^1.28.0",
|
"jose": "^1.28.0",
|
||||||
"maxmind": "^4.1.4",
|
"maxmind": "^4.1.4",
|
||||||
"moment-timezone": "^0.5.31",
|
"moment-timezone": "^0.5.31",
|
||||||
"next": "^9.5.2",
|
"next": "^9.5.3",
|
||||||
"promise-polyfill": "^8.1.3",
|
"promise-polyfill": "^8.1.3",
|
||||||
"react": "^16.13.1",
|
"react": "^16.13.1",
|
||||||
"react-dom": "^16.13.1",
|
"react-dom": "^16.13.1",
|
||||||
@ -75,30 +75,30 @@
|
|||||||
"uuid": "^8.3.0"
|
"uuid": "^8.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@prisma/cli": "2.5.1",
|
"@prisma/cli": "2.6.1",
|
||||||
"@rollup/plugin-buble": "^0.21.3",
|
"@rollup/plugin-buble": "^0.21.3",
|
||||||
"@rollup/plugin-node-resolve": "^9.0.0",
|
"@rollup/plugin-node-resolve": "^9.0.0",
|
||||||
"@rollup/plugin-replace": "^2.3.3",
|
"@rollup/plugin-replace": "^2.3.3",
|
||||||
"@svgr/webpack": "^5.4.0",
|
"@svgr/webpack": "^5.4.0",
|
||||||
"cross-env": "^7.0.2",
|
"cross-env": "^7.0.2",
|
||||||
"dotenv-cli": "^3.2.0",
|
"dotenv-cli": "^3.2.0",
|
||||||
"eslint": "^7.7.0",
|
"eslint": "^7.8.1",
|
||||||
"eslint-config-prettier": "^6.11.0",
|
"eslint-config-prettier": "^6.11.0",
|
||||||
"eslint-plugin-prettier": "^3.1.3",
|
"eslint-plugin-prettier": "^3.1.3",
|
||||||
"eslint-plugin-react": "^7.20.6",
|
"eslint-plugin-react": "^7.20.6",
|
||||||
"eslint-plugin-react-hooks": "^4.1.0",
|
"eslint-plugin-react-hooks": "^4.1.0",
|
||||||
"husky": "^4.2.5",
|
"husky": "^4.2.5",
|
||||||
"lint-staged": "^10.2.13",
|
"lint-staged": "^10.3.0",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"postcss-flexbugs-fixes": "^4.2.1",
|
"postcss-flexbugs-fixes": "^4.2.1",
|
||||||
"postcss-import": "^12.0.1",
|
"postcss-import": "^12.0.1",
|
||||||
"postcss-preset-env": "^6.7.0",
|
"postcss-preset-env": "^6.7.0",
|
||||||
"prettier": "^2.1.1",
|
"prettier": "^2.1.1",
|
||||||
"prettier-eslint": "^11.0.0",
|
"prettier-eslint": "^11.0.0",
|
||||||
"rollup": "^2.26.6",
|
"rollup": "^2.26.9",
|
||||||
"rollup-plugin-hashbang": "^2.2.2",
|
"rollup-plugin-hashbang": "^2.2.2",
|
||||||
"rollup-plugin-terser": "^7.0.0",
|
"rollup-plugin-terser": "^7.0.1",
|
||||||
"stylelint": "^13.6.0",
|
"stylelint": "^13.7.0",
|
||||||
"stylelint-config-css-modules": "^2.2.0",
|
"stylelint-config-css-modules": "^2.2.0",
|
||||||
"stylelint-config-prettier": "^8.0.1",
|
"stylelint-config-prettier": "^8.0.1",
|
||||||
"stylelint-config-recommended": "^3.0.0"
|
"stylelint-config-recommended": "^3.0.0"
|
||||||
|
17
redux/actions/queries.js
Normal file
17
redux/actions/queries.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { createSlice } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
|
const queries = createSlice({
|
||||||
|
name: 'queries',
|
||||||
|
initialState: {},
|
||||||
|
reducers: {
|
||||||
|
updateQuery(state, action) {
|
||||||
|
const { url, ...data } = action.payload;
|
||||||
|
state[url] = data;
|
||||||
|
return state;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const { updateQuery } = queries.actions;
|
||||||
|
|
||||||
|
export default queries.reducer;
|
@ -1,33 +1,29 @@
|
|||||||
import { createSlice } from '@reduxjs/toolkit';
|
import { createSlice } from '@reduxjs/toolkit';
|
||||||
import produce from 'immer';
|
|
||||||
|
|
||||||
const websites = createSlice({
|
const websites = createSlice({
|
||||||
name: 'user',
|
name: 'websites',
|
||||||
initialState: {},
|
initialState: {},
|
||||||
reducers: {
|
reducers: {
|
||||||
updateWebsites(state, action) {
|
updateWebsites(state, action) {
|
||||||
state = action.payload;
|
state = action.payload;
|
||||||
return state;
|
return state;
|
||||||
},
|
},
|
||||||
|
updateWebsite(state, action) {
|
||||||
|
const { websiteId, ...data } = action.payload;
|
||||||
|
state[websiteId] = data;
|
||||||
|
return state;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const { updateWebsites } = websites.actions;
|
export const { updateWebsites, updateWebsite } = websites.actions;
|
||||||
|
|
||||||
export default websites.reducer;
|
export default websites.reducer;
|
||||||
|
|
||||||
export function setDateRange(websiteId, dateRange) {
|
export function setDateRange(websiteId, dateRange) {
|
||||||
return (dispatch, getState) => {
|
return dispatch => {
|
||||||
const state = getState();
|
return dispatch(
|
||||||
let { websites = {} } = state;
|
updateWebsite({ websiteId, dateRange: { ...dateRange, modified: Date.now() } }),
|
||||||
|
);
|
||||||
websites = produce(websites, draft => {
|
|
||||||
if (!draft[websiteId]) {
|
|
||||||
draft[websiteId] = {};
|
|
||||||
}
|
|
||||||
draft[websiteId].dateRange = { ...dateRange, modified: Date.now() };
|
|
||||||
});
|
|
||||||
|
|
||||||
return dispatch(updateWebsites(websites));
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { combineReducers } from 'redux';
|
import { combineReducers } from 'redux';
|
||||||
import user from './actions/user';
|
import user from './actions/user';
|
||||||
import websites from './actions/websites';
|
import websites from './actions/websites';
|
||||||
|
import queries from './actions/queries';
|
||||||
|
|
||||||
export default combineReducers({ user, websites });
|
export default combineReducers({ user, websites, queries });
|
||||||
|
@ -61,12 +61,13 @@ import { removeTrailingSlash } from '../lib/url';
|
|||||||
const handlePush = (state, title, navigatedURL) => {
|
const handlePush = (state, title, navigatedURL) => {
|
||||||
removeEvents();
|
removeEvents();
|
||||||
currentRef = currentUrl;
|
currentRef = currentUrl;
|
||||||
|
const newUrl = navigatedURL.toString();
|
||||||
|
|
||||||
if (navigatedURL.toString().startsWith('http')) {
|
if (newUrl.startsWith('http')) {
|
||||||
const url = new URL(navigatedURL.toString());
|
const url = new URL(newUrl);
|
||||||
currentUrl = `${url.pathname}${url.search}`;
|
currentUrl = `${url.pathname}${url.search}`;
|
||||||
} else {
|
} else {
|
||||||
currentUrl = navigatedURL.toString();
|
currentUrl = newUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
pageView();
|
pageView();
|
||||||
@ -101,4 +102,8 @@ import { removeTrailingSlash } from '../lib/url';
|
|||||||
/* Start */
|
/* Start */
|
||||||
|
|
||||||
pageView();
|
pageView();
|
||||||
|
|
||||||
|
if (!window.umami) {
|
||||||
|
window.umami = event_value => collect('event', { event_type: 'custom', event_value });
|
||||||
|
}
|
||||||
})(window);
|
})(window);
|
||||||
|
Loading…
Reference in New Issue
Block a user