diff --git a/components/WebsiteSummary.js b/components/WebsiteSummary.js index afa1aeeb..cee9f044 100644 --- a/components/WebsiteSummary.js +++ b/components/WebsiteSummary.js @@ -1,11 +1,12 @@ import React, { useState, useEffect } from 'react'; import MetricCard from './MetricCard'; import { get } from '../lib/web'; +import { formatShortTime } from 'lib/format'; import styles from './WebsiteSummary.module.css'; export default function WebsiteSummary({ websiteId, startDate, endDate }) { const [data, setData] = useState({}); - const { pageviews, uniques, bounces } = data; + const { pageviews, uniques, bounces, totaltime } = data; async function loadData() { setData( @@ -29,6 +30,11 @@ export default function WebsiteSummary({ websiteId, startDate, endDate }) { value={uniques ? (bounces / uniques) * 100 : 0} format={n => Number(n).toFixed(0) + '%'} /> + formatShortTime(n, ['m', 's'], ' ')} + /> ); } diff --git a/lib/db.js b/lib/db.js index b829c101..102f100e 100644 --- a/lib/db.js +++ b/lib/db.js @@ -184,28 +184,21 @@ export async function getSummary(website_id, start_at, end_at) { return runQuery( prisma.queryRaw( ` - select - (select count(*) - from pageview - where website_id=${website_id} - and created_at between '${start_at}' and '${end_at}' - ) as "pageviews", - (select - count(distinct session_id) - from pageview - where website_id=${website_id} - and created_at between '${start_at}' and '${end_at}' - ) as "uniques", - (select sum(t.c) from - (select count(*) c - from pageview - where website_id=${website_id} - and created_at between '${start_at}' and '${end_at}' - group by session_id - having count(*) = 1 - ) t - ) as "bounces" - `, + select sum(t.c) as "pageviews", + count(distinct t.session_id) as "uniques", + sum(case when t.c = 1 then t.c else 0 end) as "bounces", + sum(t.time) as "totaltime" + from ( + select session_id, + date_trunc('hour', created_at), + count(*) c, + floor(extract(epoch from max(created_at) - min(created_at))) as "time" + from pageview + where website_id=${website_id} + and created_at between '${start_at}' and '${end_at}' + group by 1, 2 + ) t; + `, ), ); } diff --git a/lib/format.js b/lib/format.js new file mode 100644 index 00000000..c9480f31 --- /dev/null +++ b/lib/format.js @@ -0,0 +1,41 @@ +export function parseTime(val) { + const days = ~~(val / 86400); + const hours = ~~(val / 3600) - days * 24; + const minutes = ~~(val / 60) - days * 1440 - hours * 60; + const seconds = ~~val - days * 86400 - hours * 3600 - minutes * 60; + const ms = (val - ~~val) * 1000; + + return { + days, + hours, + minutes, + seconds, + ms, + }; +} + +export function formatTime(val) { + const { hour, minutes, seconds } = parseTime(val); + const h = hour > 0 ? `${hour}:` : ''; + const m = hour > 0 ? minutes.toString().padStart(2, '0') : minutes; + const s = seconds.toString().padStart(2, '0'); + + return `${h}${m}:${s}`; +} + +export function formatShortTime(val, formats = ['m', 's'], space = '') { + if (val === 0) { + return `0${formats[formats.length - 1]}`; + } + + const { days, hours, minutes, seconds, ms } = parseTime(val); + let t = ''; + + if (days > 0 && formats.indexOf('d') !== -1) t += `${days}d${space}`; + if (hours > 0 && formats.indexOf('h') !== -1) t += `${hours}h${space}`; + if (minutes > 0 && formats.indexOf('m') !== -1) t += `${minutes}m${space}`; + if (seconds > 0 && formats.indexOf('s') !== -1) t += `${seconds}s${space}`; + if (ms > 0 && formats.indexOf('ms') !== -1) t += `${ms}ms`; + + return t; +} diff --git a/pages/api/website/[id]/summary.js b/pages/api/website/[id]/summary.js index a440b793..a1882ad5 100644 --- a/pages/api/website/[id]/summary.js +++ b/pages/api/website/[id]/summary.js @@ -13,5 +13,10 @@ export default async (req, res) => { format(new Date(+end_at), 'yyyy-MM-dd hh:mm:ss'), ); - return res.status(200).json(summary[0]); + const stats = Object.keys(summary[0]).reduce((obj, key) => { + obj[key] = +summary[0][key]; + return obj; + }, {}); + + return res.status(200).json(stats); };