Chart component. Update web utils.

This commit is contained in:
Mike Cao 2020-07-26 00:12:42 -07:00
parent f947c7770b
commit 590a70c2ff
6 changed files with 159 additions and 8 deletions

111
components/Chart.js vendored Normal file
View File

@ -0,0 +1,111 @@
import React, { useState, useMemo, useRef, useEffect } from 'react';
import ChartJS from 'chart.js';
import { format, subDays, subHours, startOfHour } from 'date-fns';
import { get } from 'lib/web';
export default function Chart({ websiteId, startDate, endDate }) {
const [data, setData] = useState();
const canvas = useRef();
const chart = useRef();
const metrics = useMemo(() => {
if (data) {
const points = {};
const now = startOfHour(new Date());
for (let i = 0; i <= 168; i++) {
const d = new Date(subHours(now, 168 - i));
const key = format(d, 'yyyy-MM-dd-HH');
points[key] = { t: startOfHour(d).toISOString(), y: 0 };
}
data.pageviews.forEach(e => {
const key = format(new Date(e.created_at), 'yyyy-MM-dd-HH');
points[key].y += 1;
});
return points;
}
}, [data]);
console.log(metrics);
async function loadData() {
setData(
await get(`/api/website/${websiteId}/pageviews`, {
start_at: startDate,
end_at: endDate,
}),
);
}
function draw() {
if (!chart.current && canvas.current) {
chart.current = new ChartJS(canvas.current, {
type: 'bar',
data: {
datasets: [
{
label: 'page views',
data: Object.values(metrics),
lineTension: 0,
},
],
},
options: {
animation: {
duration: 300,
},
tooltips: {
intersect: false,
},
hover: {
animationDuration: 0,
},
scales: {
xAxes: [
{
type: 'time',
distribution: 'series',
time: {
unit: 'hour',
displayFormats: {
hour: 'ddd M/DD',
},
tooltipFormat: 'ddd M/DD hA',
},
ticks: {
autoSkip: true,
minRotation: 0,
maxRotation: 0,
maxTicksLimit: 7,
},
},
],
yAxes: [
{
ticks: {
beginAtZero: true,
},
},
],
},
},
});
}
}
useEffect(() => {
loadData();
}, []);
useEffect(() => {
if (metrics) {
draw();
}
}, [metrics]);
return (
<div>
<canvas ref={canvas} width={960} height={400} />
</div>
);
}

View File

@ -11,7 +11,7 @@ export const prisma = new PrismaClient({
prisma.on('query', e => { prisma.on('query', e => {
if (process.env.LOG_QUERY) { if (process.env.LOG_QUERY) {
console.log(`${e.query} (${e.duration}ms)`); console.log(`${e.params} -> ${e.query} (${e.duration}ms)`);
} }
}); });
@ -113,11 +113,15 @@ export async function getAccount(username = '') {
); );
} }
export async function getPageviews(website_id) { export async function getPageviews(website_id, start_at, end_at) {
return runQuery( return runQuery(
prisma.pageview.findMany({ prisma.pageview.findMany({
where: { where: {
website_id, website_id,
created_at: {
gte: start_at,
lte: end_at,
},
}, },
}), }),
); );

View File

@ -1,13 +1,31 @@
export const post = (url, params) => export const apiRequest = (method, url, body) =>
fetch(url, { fetch(url, {
method: 'post', method,
cache: 'no-cache', cache: 'no-cache',
headers: { headers: {
Accept: 'application/json', Accept: 'application/json',
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
body: JSON.stringify(params), body,
}).then(res => (res.status === 200 ? res.json() : null)); }).then(res => (res.ok ? res.json() : null));
function parseQuery(url, params) {
const query =
params &&
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', parseQuery(url, params));
export const post = (url, params) => apiRequest('post', url, JSON.stringify(params));
export const del = (url, params) => apiRequest('del', parseQuery(url, params));
export const hook = (_this, method, callback) => { export const hook = (_this, method, callback) => {
const orig = _this[method]; const orig = _this[method];

View File

@ -0,0 +1,10 @@
import { getPageviews } from 'lib/db';
export default async (req, res) => {
console.log(req.query);
const { id, start_at, end_at } = req.query;
const pageviews = await getPageviews(+id, new Date(+start_at), new Date(+end_at));
res.status(200).json({ pageviews });
};

View File

@ -2,6 +2,7 @@ import React from 'react';
import Link from 'next/link'; import Link from 'next/link';
import cookies from 'next-cookies'; import cookies from 'next-cookies';
import Layout from 'components/Layout'; import Layout from 'components/Layout';
import Chart from 'components/Chart';
import { verifySecureToken } from 'lib/crypto'; import { verifySecureToken } from 'lib/crypto';
export default function HomePage({ username }) { export default function HomePage({ username }) {
@ -10,6 +11,13 @@ export default function HomePage({ username }) {
<h2> <h2>
You've successfully logged in as <b>{username}</b>. You've successfully logged in as <b>{username}</b>.
</h2> </h2>
<div>
<Chart
websiteId={3}
startDate={Date.now() - 1000 * 60 * 60 * 24 * 7}
endDate={Date.now()}
/>
</div>
<Link href="/logout"> <Link href="/logout">
<a>Logout 🡒</a> <a>Logout 🡒</a>
</Link> </Link>
@ -25,7 +33,7 @@ export async function getServerSideProps(context) {
return { return {
props: { props: {
username: payload.username, ...payload,
}, },
}; };
} catch { } catch {

File diff suppressed because one or more lines are too long