mirror of https://github.com/status-im/fathom.git
universal handling of retrieving totals from datastore, incl. uniques.
This commit is contained in:
parent
50556c44e9
commit
47d8347ef1
|
@ -30,19 +30,19 @@ class CountWidget extends Component {
|
|||
}
|
||||
|
||||
componentWillReceiveProps(newProps, prevState) {
|
||||
this.setState({
|
||||
before: newProps.before,
|
||||
after: newProps.after,
|
||||
});
|
||||
if(newProps.before == prevState.before && newProps.after == prevState.after) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(newProps.before != prevState.before || newProps.after != prevState.after) {
|
||||
this.fetchData();
|
||||
}
|
||||
this.setState({
|
||||
before: newProps.before,
|
||||
after: newProps.after,
|
||||
});
|
||||
this.fetchData();
|
||||
}
|
||||
|
||||
@bind
|
||||
fetchData() {
|
||||
console.log(this.state);
|
||||
this.setState({ loading: true })
|
||||
|
||||
Client.request(`${this.props.endpoint}/count?before=${this.state.before}&after=${this.state.after}`)
|
||||
|
|
|
@ -14,7 +14,7 @@ class Table extends Component {
|
|||
|
||||
this.state = {
|
||||
records: [],
|
||||
limit: 100,
|
||||
limit: 15,
|
||||
loading: true,
|
||||
before: props.before,
|
||||
after: props.after,
|
||||
|
@ -26,14 +26,15 @@ class Table extends Component {
|
|||
}
|
||||
|
||||
componentWillReceiveProps(newProps, prevState) {
|
||||
if(newProps.before == prevState.before && newProps.after == prevState.after) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
before: newProps.before,
|
||||
after: newProps.after,
|
||||
});
|
||||
|
||||
if(newProps.before != prevState.before || newProps.after != prevState.after) {
|
||||
this.fetchRecords();
|
||||
}
|
||||
this.fetchRecords();
|
||||
}
|
||||
|
||||
@bind
|
||||
|
@ -44,19 +45,27 @@ class Table extends Component {
|
|||
.then((d) => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
records: d
|
||||
records: d,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
render(props, state) {
|
||||
const tableRows = state.records !== null ? state.records.map((p, i) => (
|
||||
<div class="table-row">
|
||||
<div class="cell main-col"><a href={"http://"+p.hostname+p.path}>{p.path||p.label}</a></div>
|
||||
<div class="cell">{p.count||p.value}</div>
|
||||
<div class="cell">{p.count_unique||p.unique_value||"-"}</div>
|
||||
const tableRows = state.records !== null ? state.records.map((p, i) => {
|
||||
let ahref = document.createElement('a'); ahref.href = p.value;
|
||||
let classes = "table-row w" + Math.round(p.percentage_of_total);
|
||||
let label = ahref.pathname;
|
||||
if( props.showHostname ) {
|
||||
label = ahref.hostname.replace('www.', '') + (ahref.pathname.length > 1 ? ahref.pathname : '');
|
||||
}
|
||||
|
||||
return(
|
||||
<div class={classes}>
|
||||
<div class="cell main-col"><a href={ahref.href}>{label}</a></div>
|
||||
<div class="cell">{p.count}</div>
|
||||
<div class="cell">{p.count_unique||"-"}</div>
|
||||
</div>
|
||||
)) : <div class="table-row">Nothing here, yet.</div>;
|
||||
)}) : <div class="table-row">Nothing here, yet.</div>;
|
||||
|
||||
const loadingOverlay = state.loading ? <div class="loading-overlay"><div></div></div> : '';
|
||||
|
||||
|
|
|
@ -20,15 +20,23 @@ Client.request = function(resource, args) {
|
|||
}
|
||||
|
||||
return fetch(`/api/${resource}`, args)
|
||||
.then(handleRequestErrors)
|
||||
.then(parseJSON)
|
||||
.then(checkData)
|
||||
.then(parseData)
|
||||
}
|
||||
|
||||
function parseJSON(r) {
|
||||
return r.json()
|
||||
}
|
||||
|
||||
function checkData(d) {
|
||||
function handleRequestErrors(r) {
|
||||
if (!r.ok) {
|
||||
throw new Error(r.statusText);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
function parseData(d) {
|
||||
if(d.Error) {
|
||||
throw new Error(d.Error)
|
||||
}
|
||||
|
|
|
@ -22,14 +22,13 @@ class Dashboard extends Component {
|
|||
|
||||
@bind
|
||||
changePeriod(s) {
|
||||
console.log(s)
|
||||
this.setState({ period: s.period, before: s.before, after: s.after })
|
||||
window.history.replaceState(this.state, null, `#!${s.period}`)
|
||||
}
|
||||
|
||||
render(props, state) {
|
||||
return (
|
||||
<div class="rapper">
|
||||
<div class="wrapper">
|
||||
|
||||
<header class="section">
|
||||
<nav class="main-nav animated fadeInDown">
|
||||
|
@ -56,7 +55,7 @@ class Dashboard extends Component {
|
|||
</div>
|
||||
|
||||
<Table endpoint="pageviews" headers={["Top pages", "Views", "Uniques"]} before={state.before} after={state.after} />
|
||||
<Table endpoint="referrers" headers={["Top referrers", "Views", "Uniques"]} before={state.before} after={state.after} />
|
||||
<Table endpoint="referrers" headers={["Top referrers", "Views", "Uniques"]} before={state.before} after={state.after} showHostname="true" />
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
@ -1,114 +1,151 @@
|
|||
/*
|
||||
|
||||
TODO: move fonts to local hosting - only host the needed weights/styles not all weights styles
|
||||
|
||||
overpass 200, 500, 600
|
||||
purple #533feb
|
||||
green #88ffc6
|
||||
|
||||
dark #46494d
|
||||
medium #98a0a6
|
||||
light #f5f7fa
|
||||
|
||||
padding 8, 16, 20, 32, 64, 128, 256, 512, 1024
|
||||
font size 12, 16, 64
|
||||
*/
|
||||
|
||||
@import url('//overpass-30e2.kxcdn.com/overpass.css');
|
||||
* { font: 400 16px/1 'overpass', sans-serif; padding: 0; margin: 0; border: 0; outline: 0; border-radius: 0; border: none; vertical-align: baseline; -webkit-appearance: none; appearance: none; list-style: none; box-sizing: border-box; }
|
||||
|
||||
::selection { background: #a0ffd1; }
|
||||
::-moz-selection { background: #a0ffd1; }
|
||||
|
||||
TODO: move fonts to local hosting - only host the needed weights/styles not all weights styles
|
||||
|
||||
@keyframes fadeInUp {
|
||||
0% { opacity: 0; transform: translateY(20px); }
|
||||
100% { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
@keyframes fadeInDown {
|
||||
0% { opacity: 0; transform: translateY(-20px); }
|
||||
100% { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.animated { animation-duration: .4s; animation-fill-mode: both; }
|
||||
|
||||
.delayed_02s { animation-delay: .2s; }
|
||||
.delayed_03s { animation-delay: .3s; }
|
||||
.delayed_04s { animation-delay: .4s; }
|
||||
.delayed_05s { animation-delay: .5s; }
|
||||
.delayed_06s { animation-delay: .6s; }
|
||||
|
||||
|
||||
.fadeInUp { animation-name: fadeInUp; }
|
||||
.fadeInDown { animation-name: fadeInDown; }
|
||||
|
||||
|
||||
html {}
|
||||
body { background: #f5f7fa; text-align: center; padding: 8px; }
|
||||
|
||||
.rapper { max-width: 1180px; margin: 0 auto; text-align: left; }
|
||||
.section { margin-bottom: 32px; }
|
||||
header {}
|
||||
section {}
|
||||
footer {}
|
||||
|
||||
.boxes { display: flex; margin: 8px 0; flex-wrap: wrap; flex-direction: row; justify-content: flex-start; align-items: stretch; width: 100%; }
|
||||
.box { border-radius: 4px; margin-bottom: 8px; box-shadow: 0 2px 8px 0 rgba(70,73,77,.16); padding: 24px 0; flex: 1; flex-basis: 100%; }
|
||||
.box-totals { background: #46494d; color: #fff; padding: 32px 16px 0 16px; }
|
||||
.box-pages { background: #fff; }
|
||||
.box-referrers { background: #fff; }
|
||||
|
||||
nav.main-nav ul { width: 100%; text-align: right; margin-top: 4px; }
|
||||
nav li { display: inline-block; }
|
||||
nav li a { transition: color .2s ease; position: relative; display: inline-block; padding: 0 8px 0 0; }
|
||||
nav.main-nav li a { padding: 6px 8px 6px 0; }
|
||||
nav li a:hover { color: #98a0a6; }
|
||||
nav.date-nav li.active a:after { content:""; background: #88ffc6; display: block; width: 100%; height: 3px; position: absolute; top: 4px; z-index: -1; margin: 0 0 0 -4px; transition: all .4s ease; }
|
||||
overpass 200, 500, 600
|
||||
purple #533feb
|
||||
green #88ffc6
|
||||
|
||||
nav.date-nav li a { color: #46494d; }
|
||||
nav.date-nav li a:hover { color: #98a0a6; }
|
||||
nav.date-nav li.active a:hover { color: #46494d; }
|
||||
dark #46494d
|
||||
medium #98a0a6
|
||||
light #f5f7fa
|
||||
|
||||
padding 8, 16, 20, 32, 64, 128, 256, 512, 1024
|
||||
font size 12, 16, 64
|
||||
*/
|
||||
|
||||
@import url('//overpass-30e2.kxcdn.com/overpass.css');
|
||||
|
||||
* {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
border-radius: 0;
|
||||
border: none;
|
||||
vertical-align: baseline;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
list-style: none;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
::selection { background: #a0ffd1; }
|
||||
::-moz-selection { background: #a0ffd1; }
|
||||
|
||||
@keyframes fadeInUp {
|
||||
0% { opacity: 0; transform: translateY(20px); }
|
||||
100% { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
@keyframes fadeInDown {
|
||||
0% { opacity: 0; transform: translateY(-20px); }
|
||||
100% { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.animated { animation-duration: .4s; animation-fill-mode: both; }
|
||||
|
||||
.delayed_02s { animation-delay: .2s; }
|
||||
.delayed_03s { animation-delay: .3s; }
|
||||
.delayed_04s { animation-delay: .4s; }
|
||||
.delayed_05s { animation-delay: .5s; }
|
||||
.delayed_06s { animation-delay: .6s; }
|
||||
|
||||
|
||||
.fadeInUp { animation-name: fadeInUp; }
|
||||
.fadeInDown { animation-name: fadeInDown; }
|
||||
|
||||
|
||||
html {}
|
||||
body {
|
||||
font: 400 16px/1 'overpass', sans-serif;
|
||||
background: #f5f7fa;
|
||||
text-align: center;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.wrapper { max-width: 1180px; margin: 0 auto; text-align: left; }
|
||||
.section { margin-bottom: 32px; }
|
||||
header {}
|
||||
section {}
|
||||
footer {}
|
||||
|
||||
.boxes {
|
||||
display: flex;
|
||||
margin: 8px 0;
|
||||
flex-wrap: wrap;
|
||||
flex-direction: row;
|
||||
justify-content:
|
||||
flex-start; align-items:
|
||||
stretch; width: 100%;
|
||||
}
|
||||
|
||||
.box {
|
||||
border-radius: 4px;
|
||||
margin-bottom: 8px;
|
||||
box-shadow: 0 2px 8px 0 rgba(70,73,77,.16);
|
||||
padding: 24px 0;
|
||||
flex: 1;
|
||||
flex-basis: 100%;
|
||||
min-width: 40px;
|
||||
}
|
||||
.box-totals { background: #46494d; color: #fff; padding: 32px 16px 0 16px; }
|
||||
.box-pages { background: #fff; }
|
||||
.box-referrers { background: #fff; }
|
||||
|
||||
nav.main-nav ul { width: 100%; text-align: right; margin-top: 4px; }
|
||||
nav li { display: inline-block; }
|
||||
nav li a { transition: color .2s ease; position: relative; display: inline-block; padding: 0 8px 0 0; }
|
||||
nav.main-nav li a { padding: 6px 8px 6px 0; }
|
||||
nav li a:hover { color: #98a0a6; }
|
||||
nav.date-nav li.active a:after { content:""; background: #88ffc6; display: block; width: 100%; height: 3px; position: absolute; top: 4px; z-index: -1; margin: 0 0 0 -4px; transition: all .4s ease; }
|
||||
|
||||
nav.date-nav li a { color: #46494d; }
|
||||
nav.date-nav li a:hover { color: #98a0a6; }
|
||||
nav.date-nav li.active a:hover { color: #46494d; }
|
||||
|
||||
nav li.visitors { color: #533feb; }
|
||||
nav li.signout a { padding-right: 0; }
|
||||
nav li.logo { float: left; }
|
||||
nav li.logo a { color: #533feb; display: inline-block; background: url("data:image/svg+xml;charset=UTF-8,%3csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' viewBox='0 0 48 48' xml:space='preserve'%3e%3cpath style='fill:%23533feb;' d='M47.882,26.381C47.96,25.598,48,24.804,48,24c0.001-6.623-2.688-12.632-7.029-16.971 C36.632,2.688,30.623-0.001,24,0C17.377-0.001,11.368,2.688,7.029,7.029C2.688,11.368-0.001,17.377,0,24 c0,3.917,0.941,7.624,2.609,10.892c0,0,0,0,0,0c1.985,3.891,4.998,7.165,8.682,9.47C14.975,46.667,19.338,48.001,24,48 c6.221,0.001,11.901-2.372,16.162-6.258C44.424,37.858,47.284,32.45,47.882,26.381C47.882,26.381,47.882,26.381,47.882,26.381 C47.882,26.381,47.882,26.381,47.882,26.381C47.882,26.381,47.882,26.381,47.882,26.381C47.882,26.381,47.882,26.381,47.882,26.381 z M24,2.824c5.852,0.001,11.137,2.368,14.974,6.202c3.596,3.599,5.902,8.472,6.175,13.891l-8.386-8.386 c-0.263-0.263-0.627-0.414-0.998-0.414s-0.735,0.151-0.998,0.413L22.588,26.709l-5.59-5.59c-0.551-0.551-1.445-0.551-1.997,0 l-10.69,10.69C3.353,29.394,2.824,26.762,2.824,24c0.001-5.852,2.368-11.137,6.202-14.974C12.863,5.192,18.148,2.824,24,2.824z'/%3e%3cpath style='fill:%23fff;' d='M4.312,31.809l10.69-10.69c0.551-0.551,1.445-0.551,1.997,0l5.59,5.59l12.178-12.178 c0.263-0.263,0.626-0.413,0.998-0.413s0.735,0.151,0.998,0.414l8.386,8.386c-0.273-5.42-2.579-10.293-6.175-13.891 C35.137,5.192,29.852,2.824,24,2.824C18.148,2.824,12.863,5.192,9.026,9.026C5.192,12.863,2.824,18.148,2.824,24 C2.824,26.762,3.353,29.394,4.312,31.809z'/%3e%3c/svg%3e") top left no-repeat; background-size: 24px 24px; height: 24px; padding: 6px 0 6px 32px; }
|
||||
|
||||
|
||||
.main-nav ul { display: inline-block; }
|
||||
.spacer { color: #98a0a6; padding: 0 8px; }
|
||||
|
||||
svg { width: 24px; height: 24px; display: inline-block; vertical-align: top; }
|
||||
|
||||
.header div, .date-nav a, .total-heading { font-size: 12px; text-transform: uppercase; color: #98a0a6; }
|
||||
|
||||
p, li, .cell { }
|
||||
.total-numbers { font-size: 44px; letter-spacing: -3px; margin-bottom: 32px; font-weight: 200; }
|
||||
.totals-detail { width: 48%; display: inline-block; }
|
||||
.total-heading { color: #fff; opacity: .6; }
|
||||
|
||||
.table-row { display: flex; flex-direction: row; flex-grow: 0; flex-wrap: wrap; width: 100%; position: relative; margin-bottom: 2px; padding: 0 16px; }
|
||||
.cell { flex-grow: 1; width: 20%; text-align: left; padding: 8px 0; position: relative; z-index: 2; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
|
||||
.main-col { width: 56%; margin-right: 4%; }
|
||||
|
||||
.header:after { display: none; }
|
||||
.table-row:after { content: ""; background: #88ffc6; position: absolute; height: 30px; top: 0; left: 0; opacity: .2; border-right: 2px solid #45ce8c; }
|
||||
.w100:after{width:99%}.w99:after{width:99%}.w98:after{width:98%}.w97:after{width:97%}.w96:after{width:96%}.w95:after{width:95%}.w94:after{width:94%}.w93:after{width:93%}.w92:after{width:92%}.w91:after{width:91%}.w90:after{width:90%}.w89:after{width:89%}.w88:after{width:88%}.w87:after{width:87%}.w86:after{width:86%}.w85:after{width:85%}.w84:after{width:84%}.w83:after{width:83%}.w82:after{width:82%}.w81:after{width:81%}.w80:after{width:80%}.w79:after{width:79%}.w78:after{width:78%}.w77:after{width:77%}.w76:after{width:76%}.w75:after{width:75%}.w74:after{width:74%}.w73:after{width:73%}.w72:after{width:72%}.w71:after{width:71%}.w70:after{width:70%}.w69:after{width:69%}.w68:after{width:68%}.w67:after{width:67%}.w66:after{width:66%}.w65:after{width:65%}.w64:after{width:64%}.w63:after{width:63%}.w62:after{width:62%}.w61:after{width:61%}.w60:after{width:60%}.w59:after{width:59%}.w58:after{width:58%}.w57:after{width:57%}.w56:after{width:56%}.w55:after{width:55%}.w54:after{width:54%}.w53:after{width:53%}.w52:after{width:52%}.w51:after{width:51%}.w50:after{width:50%}.w49:after{width:49%}.w48:after{width:48%}.w47:after{width:47%}.w46:after{width:46%}.w45:after{width:45%}.w44:after{width:44%}.w43:after{width:43%}.w42:after{width:42%}.w41:after{width:41%}.w40:after{width:40%}.w39:after{width:39%}.w38:after{width:38%}.w37:after{width:37%}.w36:after{width:36%}.w35:after{width:35%}.w34:after{width:34%}.w33:after{width:33%}.w32:after{width:32%}.w31:after{width:31%}.w30:after{width:30%}.w29:after{width:29%}.w28:after{width:28%}.w27:after{width:27%}.w26:after{width:26%}.w25:after{width:25%}.w24:after{width:24%}.w23:after{width:23%}.w22:after{width:22%}.w21:after{width:21%}.w20:after{width:20%}.w19:after{width:19%}.w18:after{width:18%}.w17:after{width:17%}.w16:after{width:16%}.w15:after{width:15%}.w14:after{width:14%}.w13:after{width:13%}.w12:after{width:12%}.w11:after{width:11%}.w10:after{width:10%}.w09:after{width:9%}.w08:after{width:8%}.w07:after{width:7%}.w06:after{width:6%}.w05:after{width:5%}.w04:after{width:4%}.w03:after{width:3%}.w02:after{width:2%}.w01:after{width:1%}.w00:after{width:0}
|
||||
|
||||
|
||||
a { color: #46494d; text-decoration: none; transition: all .4s ease; }
|
||||
a:hover {}
|
||||
.cell a:hover { color: #533feb; }
|
||||
|
||||
nav li.visitors { color: #533feb; }
|
||||
nav li.signout a { padding-right: 0; }
|
||||
nav li.logo { float: left; }
|
||||
nav li.logo a { color: #533feb; display: inline-block; background: url("data:image/svg+xml;charset=UTF-8,%3csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' viewBox='0 0 48 48' xml:space='preserve'%3e%3cpath style='fill:%23533feb;' d='M47.882,26.381C47.96,25.598,48,24.804,48,24c0.001-6.623-2.688-12.632-7.029-16.971 C36.632,2.688,30.623-0.001,24,0C17.377-0.001,11.368,2.688,7.029,7.029C2.688,11.368-0.001,17.377,0,24 c0,3.917,0.941,7.624,2.609,10.892c0,0,0,0,0,0c1.985,3.891,4.998,7.165,8.682,9.47C14.975,46.667,19.338,48.001,24,48 c6.221,0.001,11.901-2.372,16.162-6.258C44.424,37.858,47.284,32.45,47.882,26.381C47.882,26.381,47.882,26.381,47.882,26.381 C47.882,26.381,47.882,26.381,47.882,26.381C47.882,26.381,47.882,26.381,47.882,26.381C47.882,26.381,47.882,26.381,47.882,26.381 z M24,2.824c5.852,0.001,11.137,2.368,14.974,6.202c3.596,3.599,5.902,8.472,6.175,13.891l-8.386-8.386 c-0.263-0.263-0.627-0.414-0.998-0.414s-0.735,0.151-0.998,0.413L22.588,26.709l-5.59-5.59c-0.551-0.551-1.445-0.551-1.997,0 l-10.69,10.69C3.353,29.394,2.824,26.762,2.824,24c0.001-5.852,2.368-11.137,6.202-14.974C12.863,5.192,18.148,2.824,24,2.824z'/%3e%3cpath style='fill:%23fff;' d='M4.312,31.809l10.69-10.69c0.551-0.551,1.445-0.551,1.997,0l5.59,5.59l12.178-12.178 c0.263-0.263,0.626-0.413,0.998-0.413s0.735,0.151,0.998,0.414l8.386,8.386c-0.273-5.42-2.579-10.293-6.175-13.891 C35.137,5.192,29.852,2.824,24,2.824C18.148,2.824,12.863,5.192,9.026,9.026C5.192,12.863,2.824,18.148,2.824,24 C2.824,26.762,3.353,29.394,4.312,31.809z'/%3e%3c/svg%3e") top left no-repeat; background-size: 24px 24px; height: 24px; padding: 6px 0 6px 32px; }
|
||||
|
||||
|
||||
.main-nav ul { display: inline-block; }
|
||||
.spacer { color: #98a0a6; padding: 0 8px; }
|
||||
|
||||
svg { width: 24px; height: 24px; display: inline-block; vertical-align: top; }
|
||||
|
||||
.header div, .date-nav a, .total-heading { font-size: 12px; text-transform: uppercase; color: #98a0a6; }
|
||||
|
||||
p, li, .cell { }
|
||||
.total-numbers { font-size: 44px; letter-spacing: -3px; margin-bottom: 32px; font-weight: 200; }
|
||||
.totals-detail { width: 48%; display: inline-block; }
|
||||
.total-heading { color: #fff; opacity: .6; }
|
||||
|
||||
.table-row { display: flex; flex-direction: row; flex-grow: 0; flex-wrap: wrap; width: 100%; position: relative; margin-bottom: 2px; padding: 0 16px; }
|
||||
.cell { flex-grow: 1; width: 20%; text-align: left; padding: 8px 0; position: relative; z-index: 2; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
|
||||
.main-col { width: 56%; margin-right: 4%; }
|
||||
|
||||
.header:after { display: none; }
|
||||
.table-row:after { content: ""; background: #88ffc6; position: absolute; height: 30px; top: 0; left: 0; opacity: .2; border-right: 2px solid #45ce8c; }
|
||||
|
||||
|
||||
a { color: #46494d; text-decoration: none; transition: all .4s ease; }
|
||||
a:hover {}
|
||||
.cell a:hover { color: #533feb; }
|
||||
|
||||
@media ( min-width: 1220px ) {
|
||||
nav.main-nav ul { margin-top: 24px; }
|
||||
|
||||
.boxes { justify-content: space-between; flex-wrap: nowrap; }
|
||||
.box { margin: 0 4px; }
|
||||
.box-totals { max-width: 230px; margin-left: 0; }
|
||||
.box-referrers { margin-right: 0; }
|
||||
|
||||
.totals-detail { width: 100%; }
|
||||
.total-numbers { font-size: 64px; }
|
||||
}
|
||||
|
||||
.w100:after{width:99%}.w99:after{width:99%}.w98:after{width:98%}.w97:after{width:97%}.w96:after{width:96%}.w95:after{width:95%}.w94:after{width:94%}.w93:after{width:93%}.w92:after{width:92%}.w91:after{width:91%}.w90:after{width:90%}.w89:after{width:89%}.w88:after{width:88%}.w87:after{width:87%}.w86:after{width:86%}.w85:after{width:85%}.w84:after{width:84%}.w83:after{width:83%}.w82:after{width:82%}.w81:after{width:81%}.w80:after{width:80%}.w79:after{width:79%}.w78:after{width:78%}.w77:after{width:77%}.w76:after{width:76%}.w75:after{width:75%}.w74:after{width:74%}.w73:after{width:73%}.w72:after{width:72%}.w71:after{width:71%}.w70:after{width:70%}.w69:after{width:69%}.w68:after{width:68%}.w67:after{width:67%}.w66:after{width:66%}.w65:after{width:65%}.w64:after{width:64%}.w63:after{width:63%}.w62:after{width:62%}.w61:after{width:61%}.w60:after{width:60%}.w59:after{width:59%}.w58:after{width:58%}.w57:after{width:57%}.w56:after{width:56%}.w55:after{width:55%}.w54:after{width:54%}.w53:after{width:53%}.w52:after{width:52%}.w51:after{width:51%}.w50:after{width:50%}.w49:after{width:49%}.w48:after{width:48%}.w47:after{width:47%}.w46:after{width:46%}.w45:after{width:45%}.w44:after{width:44%}.w43:after{width:43%}.w42:after{width:42%}.w41:after{width:41%}.w40:after{width:40%}.w39:after{width:39%}.w38:after{width:38%}.w37:after{width:37%}.w36:after{width:36%}.w35:after{width:35%}.w34:after{width:34%}.w33:after{width:33%}.w32:after{width:32%}.w31:after{width:31%}.w30:after{width:30%}.w29:after{width:29%}.w28:after{width:28%}.w27:after{width:27%}.w26:after{width:26%}.w25:after{width:25%}.w24:after{width:24%}.w23:after{width:23%}.w22:after{width:22%}.w21:after{width:21%}.w20:after{width:20%}.w19:after{width:19%}.w18:after{width:18%}.w17:after{width:17%}.w16:after{width:16%}.w15:after{width:15%}.w14:after{width:14%}.w13:after{width:13%}.w12:after{width:12%}.w11:after{width:11%}.w10:after{width:10%}.w09:after{width:9%}.w08:after{width:8%}.w07:after{width:7%}.w06:after{width:6%}.w05:after{width:5%}.w04:after{width:4%}.w03:after{width:3%}.w02:after{width:2%}.w01:after{width:1%}.w00:after{width:0}
|
||||
|
||||
@media ( min-width: 1220px ) {
|
||||
nav.main-nav ul { margin-top: 24px; }
|
||||
|
||||
.boxes { justify-content: space-between; flex-wrap: nowrap; }
|
||||
.box { margin: 0 4px; }
|
||||
.box-totals { max-width: 230px; margin-left: 0; }
|
||||
.box-referrers { margin-right: 0; }
|
||||
|
||||
.totals-detail { width: 100%; }
|
||||
.total-numbers { font-size: 64px; }
|
||||
}
|
||||
|
||||
|
|
40
gulpfile.js
40
gulpfile.js
|
@ -20,25 +20,25 @@ if( ! debug ) {
|
|||
gulp.task('default', defaultTasks);
|
||||
|
||||
gulp.task('browserify', function () {
|
||||
return browserify({
|
||||
entries: './assets/js/script.js',
|
||||
debug: debug
|
||||
})
|
||||
.transform("babelify", {
|
||||
presets: ["es2015"],
|
||||
plugins: [
|
||||
"transform-decorators-legacy",
|
||||
["transform-react-jsx", { "pragma":"h" } ]
|
||||
]
|
||||
})
|
||||
.bundle()
|
||||
.on('error', function(err){
|
||||
console.log(err.message);
|
||||
this.emit('end');
|
||||
})
|
||||
.pipe(source('script.js'))
|
||||
.pipe(buffer())
|
||||
.pipe(gulp.dest('./build/js/'))
|
||||
return browserify({
|
||||
entries: './assets/js/script.js',
|
||||
debug: debug
|
||||
})
|
||||
.transform("babelify", {
|
||||
presets: ["es2015"],
|
||||
plugins: [
|
||||
"transform-decorators-legacy",
|
||||
["transform-react-jsx", { "pragma":"h" } ]
|
||||
]
|
||||
})
|
||||
.bundle()
|
||||
.on('error', function(err){
|
||||
console.log(err.message);
|
||||
this.emit('end');
|
||||
})
|
||||
.pipe(source('script.js'))
|
||||
.pipe(buffer())
|
||||
.pipe(gulp.dest('./build/js/'))
|
||||
});
|
||||
|
||||
gulp.task('minify', function(cb) {
|
||||
|
@ -79,5 +79,5 @@ gulp.task('watch', ['default'], function() {
|
|||
gulp.watch(['./assets/js/**/*.js'], ['browserify', 'tracker'] );
|
||||
gulp.watch(['./assets/sass/**/**/*.scss'], ['sass'] );
|
||||
gulp.watch(['./assets/**/*.html'], ['html'] );
|
||||
gulp.watch(['./assets/img/**/*'], ['img'] );
|
||||
gulp.watch(['./assets/img/**/*'], ['img'] );
|
||||
});
|
||||
|
|
|
@ -3,13 +3,14 @@ package api
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/usefathom/fathom/pkg/count"
|
||||
"github.com/usefathom/fathom/pkg/datastore"
|
||||
)
|
||||
|
||||
// URL: /api/pageviews
|
||||
var GetPageviewsHandler = HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
|
||||
before, after := getRequestedPeriods(r)
|
||||
results, err := datastore.TotalPageviewsPerPage(before, after, defaultLimit)
|
||||
results, err := count.Pageviews(before, after, defaultLimit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -9,18 +9,18 @@ import (
|
|||
)
|
||||
|
||||
// Browsers returns a point slice containing browser data per browser name
|
||||
func Browsers(before int64, after int64, limit int64) ([]*models.Point, error) {
|
||||
func Browsers(before int64, after int64, limit int64) ([]*models.Total, error) {
|
||||
points, err := datastore.TotalsPerBrowser(before, after, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
total, err := datastore.TotalUniqueBrowsers(before, after)
|
||||
total, err := datastore.TotalBrowsers(before, after)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
points = calculatePointPercentages(points, total)
|
||||
points = calculatePercentagesOfTotal(points, total)
|
||||
return points, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -35,11 +35,11 @@ func Archive() {
|
|||
log.Infof("finished aggregating metrics. ran for %dms.", (end.UnixNano()-start.UnixNano())/1000000)
|
||||
}
|
||||
|
||||
func calculatePointPercentages(points []*models.Point, total int) []*models.Point {
|
||||
func calculatePercentagesOfTotal(totals []*models.Total, total int) []*models.Total {
|
||||
// calculate percentage values for each point
|
||||
for _, p := range points {
|
||||
p.PercentageValue = float64(p.Value) / float64(total) * 100.00
|
||||
for _, p := range totals {
|
||||
p.PercentageOfTotal = float64(p.Count) / float64(total) * 100.00
|
||||
}
|
||||
|
||||
return points
|
||||
return totals
|
||||
}
|
||||
|
|
|
@ -8,16 +8,16 @@ import (
|
|||
)
|
||||
|
||||
func TestCalculatePointPercentages(t *testing.T) {
|
||||
points := []*models.Point{
|
||||
&models.Point{
|
||||
Label: "Foo",
|
||||
Value: 5,
|
||||
totals := []*models.Total{
|
||||
&models.Total{
|
||||
Value: "Foo",
|
||||
Count: 5,
|
||||
},
|
||||
}
|
||||
|
||||
points = calculatePointPercentages(points, 100)
|
||||
totals = calculatePercentagesOfTotal(totals, 100)
|
||||
|
||||
if points[0].PercentageValue != 5.00 {
|
||||
t.Errorf("Percentage value should be 5.00, is %.2f", points[0].PercentageValue)
|
||||
if totals[0].PercentageOfTotal != 5.00 {
|
||||
t.Errorf("Percentage value should be 5.00, is %.2f", totals[0].PercentageOfTotal)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,18 +9,18 @@ import (
|
|||
)
|
||||
|
||||
// Languages returns a point slice containing language data per language
|
||||
func Languages(before int64, after int64, limit int64) ([]*models.Point, error) {
|
||||
func Languages(before int64, after int64, limit int64) ([]*models.Total, error) {
|
||||
points, err := datastore.TotalsPerLanguage(before, after, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
total, err := datastore.TotalUniqueLanguages(before, after)
|
||||
total, err := datastore.TotalLanguages(before, after)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
points = calculatePointPercentages(points, total)
|
||||
points = calculatePercentagesOfTotal(points, total)
|
||||
return points, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -5,8 +5,25 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/usefathom/fathom/pkg/datastore"
|
||||
"github.com/usefathom/fathom/pkg/models"
|
||||
)
|
||||
|
||||
// Pageviews returns a point slice containing language data per language
|
||||
func Pageviews(before int64, after int64, limit int64) ([]*models.Total, error) {
|
||||
points, err := datastore.TotalPageviewsPerPage(before, after, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
total, err := datastore.TotalPageviews(before, after)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
points = calculatePercentagesOfTotal(points, total)
|
||||
return points, nil
|
||||
}
|
||||
|
||||
// CreatePageviewTotals aggregates pageview data for each page into daily totals
|
||||
func CreatePageviewTotals(since string) {
|
||||
tomorrow := time.Now().AddDate(0, 0, 1).Format("2006-01-02")
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
)
|
||||
|
||||
// Referrers returns a point slice containing browser data per browser name
|
||||
func Referrers(before int64, after int64, limit int64) ([]*models.Point, error) {
|
||||
func Referrers(before int64, after int64, limit int64) ([]*models.Total, error) {
|
||||
points, err := datastore.TotalsPerReferrer(before, after, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -20,7 +20,7 @@ func Referrers(before int64, after int64, limit int64) ([]*models.Point, error)
|
|||
return nil, err
|
||||
}
|
||||
|
||||
points = calculatePointPercentages(points, total)
|
||||
points = calculatePercentagesOfTotal(points, total)
|
||||
return points, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -9,18 +9,18 @@ import (
|
|||
)
|
||||
|
||||
// Screens returns a point slice containing screen data per size
|
||||
func Screens(before int64, after int64, limit int64) ([]*models.Point, error) {
|
||||
func Screens(before int64, after int64, limit int64) ([]*models.Total, error) {
|
||||
points, err := datastore.TotalsPerScreen(before, after, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
total, err := datastore.TotalUniqueScreens(before, after)
|
||||
total, err := datastore.TotalScreens(before, after)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
points = calculatePointPercentages(points, total)
|
||||
points = calculatePercentagesOfTotal(points, total)
|
||||
return points, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
-- +migrate Up
|
||||
ALTER TABLE pages ADD COLUMN scheme ENUM("http", "https") DEFAULT "http";
|
||||
ALTER TABLE pages DROP COLUMN title;
|
||||
|
||||
-- +migrate Down
|
||||
ALTER TABLE pages DROP COLUMN scheme;
|
||||
ALTER TABLE pages ADD COLUMN title VARCHAR(255) NULL;
|
|
@ -23,8 +23,8 @@ func GetPageByHostnameAndPath(hostname, path string) (*models.Page, error) {
|
|||
|
||||
// SavePage inserts the page model in the connected database
|
||||
func SavePage(p *models.Page) error {
|
||||
query := dbx.Rebind(`INSERT INTO pages(hostname, path, title) VALUES(?, ?, ?)`)
|
||||
result, err := dbx.Exec(query, p.Hostname, p.Path, p.Title)
|
||||
query := dbx.Rebind(`INSERT INTO pages(scheme, hostname, path) VALUES(?, ?, ?)`)
|
||||
result, err := dbx.Exec(query, p.Scheme, p.Hostname, p.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -7,10 +7,8 @@ import (
|
|||
)
|
||||
|
||||
func SaveTotals(metric string, totals []*models.Total) error {
|
||||
query := dbx.Rebind(fmt.Sprintf(`
|
||||
INSERT INTO total_%s( value, count, count_unique, date)
|
||||
VALUES( ?, ?, ?, ? ) ON DUPLICATE KEY UPDATE count = ?, count_unique = ?
|
||||
`, metric))
|
||||
query := fmt.Sprintf(`INSERT INTO total_%s( value, count, count_unique, date) VALUES( ?, ?, ?, ? ) ON DUPLICATE KEY UPDATE count = ?, count_unique = ?`, metric)
|
||||
query = dbx.Rebind(query)
|
||||
|
||||
tx, err := dbx.Begin()
|
||||
if err != nil {
|
||||
|
|
|
@ -2,6 +2,19 @@ package datastore
|
|||
|
||||
import "github.com/usefathom/fathom/pkg/models"
|
||||
|
||||
// TotalBrowsers returns the total # of browsers between two given timestamps
|
||||
func TotalBrowsers(before int64, after int64) (int, error) {
|
||||
var total int
|
||||
|
||||
query := dbx.Rebind(`
|
||||
SELECT
|
||||
COALESCE(SUM(t.count), 0)
|
||||
FROM total_browser_names t
|
||||
WHERE UNIX_TIMESTAMP(t.date) <= ? AND UNIX_TIMESTAMP(t.date) >= ?`)
|
||||
err := dbx.Get(&total, query, before, after)
|
||||
return total, err
|
||||
}
|
||||
|
||||
// TotalUniqueBrowsers returns the total # of unique browsers between two given timestamps
|
||||
func TotalUniqueBrowsers(before int64, after int64) (int, error) {
|
||||
var total int
|
||||
|
@ -15,17 +28,18 @@ func TotalUniqueBrowsers(before int64, after int64) (int, error) {
|
|||
return total, err
|
||||
}
|
||||
|
||||
func TotalsPerBrowser(before int64, after int64, limit int64) ([]*models.Point, error) {
|
||||
var results []*models.Point
|
||||
func TotalsPerBrowser(before int64, after int64, limit int64) ([]*models.Total, error) {
|
||||
var results []*models.Total
|
||||
|
||||
query := dbx.Rebind(`
|
||||
SELECT
|
||||
t.value AS label,
|
||||
COALESCE(SUM(t.count_unique), 0) AS value
|
||||
t.value AS value,
|
||||
COALESCE(SUM(t.count), 0) AS count,
|
||||
COALESCE(SUM(t.count_unique), 0) AS count_unique
|
||||
FROM total_browser_names t
|
||||
WHERE UNIX_TIMESTAMP(t.date) <= ? AND UNIX_TIMESTAMP(t.date) >= ?
|
||||
GROUP BY label
|
||||
ORDER BY value DESC
|
||||
GROUP BY t.value
|
||||
ORDER BY count DESC
|
||||
LIMIT ?`)
|
||||
|
||||
err := dbx.Select(&results, query, before, after, limit)
|
||||
|
|
|
@ -2,6 +2,20 @@ package datastore
|
|||
|
||||
import "github.com/usefathom/fathom/pkg/models"
|
||||
|
||||
// TotalLanguages returns the total # of browser languages between two given timestamps
|
||||
func TotalLanguages(before int64, after int64) (int, error) {
|
||||
var total int
|
||||
|
||||
query := dbx.Rebind(`
|
||||
SELECT
|
||||
COALESCE(SUM(t.count), 0)
|
||||
FROM total_browser_languages t
|
||||
WHERE UNIX_TIMESTAMP(t.date) <= ? AND UNIX_TIMESTAMP(t.date) >= ?`)
|
||||
|
||||
err := dbx.Get(&total, query, before, after)
|
||||
return total, err
|
||||
}
|
||||
|
||||
// TotalUniqueLanguages returns the total # of unique browser languages between two given timestamps
|
||||
func TotalUniqueLanguages(before int64, after int64) (int, error) {
|
||||
var total int
|
||||
|
@ -16,17 +30,18 @@ func TotalUniqueLanguages(before int64, after int64) (int, error) {
|
|||
return total, err
|
||||
}
|
||||
|
||||
func TotalsPerLanguage(before int64, after int64, limit int64) ([]*models.Point, error) {
|
||||
var results []*models.Point
|
||||
func TotalsPerLanguage(before int64, after int64, limit int64) ([]*models.Total, error) {
|
||||
var results []*models.Total
|
||||
|
||||
query := dbx.Rebind(`
|
||||
SELECT
|
||||
t.value AS label,
|
||||
COALESCE(SUM(t.count_unique), 0) AS value
|
||||
t.value AS value,
|
||||
COALESCE(SUM(t.count), 0) AS count,
|
||||
COALESCE(SUM(t.count_unique), 0) AS count_unique
|
||||
FROM total_browser_languages t
|
||||
WHERE UNIX_TIMESTAMP(t.date) <= ? AND UNIX_TIMESTAMP(t.date) >= ?
|
||||
GROUP BY label
|
||||
ORDER BY value DESC
|
||||
GROUP BY t.value
|
||||
ORDER BY count DESC
|
||||
LIMIT ?`)
|
||||
|
||||
err := dbx.Select(&results, query, before, after, limit)
|
||||
|
|
|
@ -20,17 +20,35 @@ func TotalPageviews(before int64, after int64) (int, error) {
|
|||
return total, nil
|
||||
}
|
||||
|
||||
// TotalUniquePageviews returns the total number of unique pageviews between the given timestamps
|
||||
func TotalUniquePageviews(before int64, after int64) (int, error) {
|
||||
var total int
|
||||
|
||||
query := dbx.Rebind(`
|
||||
SELECT COALESCE(SUM(t.count_unique), 0)
|
||||
FROM total_pageviews t
|
||||
WHERE UNIX_TIMESTAMP(t.date) <= ? AND UNIX_TIMESTAMP(t.date) >= ?`)
|
||||
err := dbx.Get(&total, query, before, after)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return total, nil
|
||||
}
|
||||
|
||||
// TotalPageviewsPerDay returns a slice of data points representing the number of pageviews per day
|
||||
func TotalPageviewsPerDay(before int64, after int64) ([]*models.Point, error) {
|
||||
var results []*models.Point
|
||||
func TotalPageviewsPerDay(before int64, after int64) ([]*models.Total, error) {
|
||||
var results []*models.Total
|
||||
|
||||
query := dbx.Rebind(`
|
||||
SELECT
|
||||
COALESCE(SUM(t.count), 0) AS value,
|
||||
CONCAT(p.scheme, "://", p.hostname, p.path) AS value,
|
||||
COALESCE(SUM(t.count), 0) AS count,
|
||||
COALESCE(SUM(t.count_unique), 0) AS count_unique,
|
||||
DATE_FORMAT(t.date, '%Y-%m-%d') AS label
|
||||
FROM total_pageviews t
|
||||
WHERE UNIX_TIMESTAMP(t.date) <= ? AND UNIX_TIMESTAMP(t.date) >= ?
|
||||
GROUP BY label`)
|
||||
GROUP BY label, p.hostname, p.path, p.scheme`)
|
||||
|
||||
err := dbx.Select(&results, query, before, after)
|
||||
if err != nil {
|
||||
|
@ -41,18 +59,17 @@ func TotalPageviewsPerDay(before int64, after int64) ([]*models.Point, error) {
|
|||
}
|
||||
|
||||
// TotalPageviewsPerPage returns a set of pageview counts, grouped by page (hostname + path)
|
||||
func TotalPageviewsPerPage(before int64, after int64, limit int64) ([]*models.PageviewCount, error) {
|
||||
var results []*models.PageviewCount
|
||||
func TotalPageviewsPerPage(before int64, after int64, limit int64) ([]*models.Total, error) {
|
||||
var results []*models.Total
|
||||
query := dbx.Rebind(`
|
||||
SELECT
|
||||
p.hostname,
|
||||
p.path,
|
||||
CONCAT(p.scheme, "://", p.hostname, p.path) AS value,
|
||||
COALESCE(SUM(t.count), 0) AS count,
|
||||
COALESCE(SUM(t.count_unique), 0) AS countunique
|
||||
COALESCE(SUM(t.count_unique), 0) AS count_unique
|
||||
FROM total_pageviews t
|
||||
LEFT JOIN pages p ON p.id = t.page_id
|
||||
WHERE UNIX_TIMESTAMP(t.date) <= ? AND UNIX_TIMESTAMP(t.date) >= ?
|
||||
GROUP BY p.path, p.hostname
|
||||
GROUP BY p.hostname, p.path, p.scheme
|
||||
ORDER BY count DESC
|
||||
LIMIT ?`)
|
||||
err := dbx.Select(&results, query, before, after, limit)
|
||||
|
@ -63,6 +80,8 @@ func TotalPageviewsPerPage(before int64, after int64, limit int64) ([]*models.Pa
|
|||
return results, nil
|
||||
}
|
||||
|
||||
// SavePageviewTotals saves the given totals in the connected database
|
||||
// Differs slightly from the metric specific totals because of the normalized pages (to save storage)
|
||||
func SavePageviewTotals(totals []*models.Total) error {
|
||||
tx, err := dbx.Begin()
|
||||
if err != nil {
|
||||
|
|
|
@ -16,17 +16,32 @@ func TotalReferrers(before int64, after int64) (int, error) {
|
|||
return total, err
|
||||
}
|
||||
|
||||
func TotalsPerReferrer(before int64, after int64, limit int64) ([]*models.Point, error) {
|
||||
var results []*models.Point
|
||||
// TotalUniqueReferrers returns the total # of unique referrers between two given timestamps
|
||||
func TotalUniqueReferrers(before int64, after int64) (int, error) {
|
||||
var total int
|
||||
|
||||
query := dbx.Rebind(`
|
||||
SELECT
|
||||
COALESCE(SUM(t.count_unique), 0)
|
||||
FROM total_referrers t
|
||||
WHERE UNIX_TIMESTAMP(t.date) <= ? AND UNIX_TIMESTAMP(t.date) >= ?`)
|
||||
|
||||
err := dbx.Get(&total, query, before, after)
|
||||
return total, err
|
||||
}
|
||||
|
||||
func TotalsPerReferrer(before int64, after int64, limit int64) ([]*models.Total, error) {
|
||||
var results []*models.Total
|
||||
|
||||
query := dbx.Rebind(`
|
||||
SELECT
|
||||
t.value AS label,
|
||||
COALESCE(SUM(t.count), 0) AS value
|
||||
t.value,
|
||||
COALESCE(SUM(t.count), 0) AS count,
|
||||
COALESCE(SUM(t.count_unique), 0) AS count_unique
|
||||
FROM total_referrers t
|
||||
WHERE UNIX_TIMESTAMP(t.date) <= ? AND UNIX_TIMESTAMP(t.date) >= ?
|
||||
GROUP BY label
|
||||
ORDER BY value DESC
|
||||
GROUP BY t.value
|
||||
ORDER BY count DESC
|
||||
LIMIT ?`)
|
||||
|
||||
err := dbx.Select(&results, query, before, after, limit)
|
||||
|
|
|
@ -2,7 +2,20 @@ package datastore
|
|||
|
||||
import "github.com/usefathom/fathom/pkg/models"
|
||||
|
||||
// TotalUniqueScreens returns the total # of screens between two given timestamps
|
||||
// TotalScreens returns the total # of screens between two given timestamps
|
||||
func TotalScreens(before int64, after int64) (int, error) {
|
||||
var total int
|
||||
|
||||
query := dbx.Rebind(`
|
||||
SELECT
|
||||
COALESCE(SUM(t.count), 0)
|
||||
FROM total_screens t
|
||||
WHERE UNIX_TIMESTAMP(t.date) <= ? AND UNIX_TIMESTAMP(t.date) >= ?`)
|
||||
err := dbx.Get(&total, query, before, after)
|
||||
return total, err
|
||||
}
|
||||
|
||||
// TotalUniqueScreens returns the total # of unique screens between two given timestamps
|
||||
func TotalUniqueScreens(before int64, after int64) (int, error) {
|
||||
var total int
|
||||
|
||||
|
@ -15,17 +28,18 @@ func TotalUniqueScreens(before int64, after int64) (int, error) {
|
|||
return total, err
|
||||
}
|
||||
|
||||
func TotalsPerScreen(before int64, after int64, limit int64) ([]*models.Point, error) {
|
||||
var results []*models.Point
|
||||
func TotalsPerScreen(before int64, after int64, limit int64) ([]*models.Total, error) {
|
||||
var results []*models.Total
|
||||
|
||||
query := dbx.Rebind(`
|
||||
SELECT
|
||||
t.value AS label,
|
||||
COALESCE(SUM(t.count_unique), 0) AS value
|
||||
t.value AS value,
|
||||
COALESCE(SUM(t.count), 0) AS count,
|
||||
COALESCE(SUM(t.count_unique), 0) AS count_unique
|
||||
FROM total_screens t
|
||||
WHERE UNIX_TIMESTAMP(t.date) <= ? AND UNIX_TIMESTAMP(t.date) >= ?
|
||||
GROUP BY t.value
|
||||
ORDER BY value DESC
|
||||
ORDER BY count DESC
|
||||
LIMIT ?`)
|
||||
|
||||
err := dbx.Select(&results, query, before, after, limit)
|
||||
|
|
|
@ -15,11 +15,12 @@ func TotalVisitors(before int64, after int64) (int, error) {
|
|||
}
|
||||
|
||||
// TotalVisitorsPerDay returns a point slice containing visitor data per day
|
||||
func TotalVisitorsPerDay(before int64, after int64) ([]*models.Point, error) {
|
||||
var results []*models.Point
|
||||
func TotalVisitorsPerDay(before int64, after int64) ([]*models.Total, error) {
|
||||
var results []*models.Total
|
||||
|
||||
query := dbx.Rebind(`SELECT
|
||||
COALESCE(SUM(t.count), 0) AS value,
|
||||
COALESCE(SUM(t.count), 0) AS count,
|
||||
COALESCE(SUM(t.count_unique), 0) AS count_unique,
|
||||
DATE_FORMAT(t.date, '%Y-%m-%d') AS label
|
||||
FROM total_visitors t
|
||||
WHERE UNIX_TIMESTAMP(t.date) <= ? AND UNIX_TIMESTAMP(t.date) >= ?
|
||||
|
@ -29,6 +30,7 @@ func TotalVisitorsPerDay(before int64, after int64) ([]*models.Point, error) {
|
|||
return results, err
|
||||
}
|
||||
|
||||
// SaveVisitorTotals saves the given totals in the connected datastore
|
||||
func SaveVisitorTotals(totals []*models.Total) error {
|
||||
tx, err := dbx.Begin()
|
||||
if err != nil {
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
package models
|
||||
|
||||
type Count struct {
|
||||
URL string `json:"url"`
|
||||
Views int64 `json:"views"`
|
||||
Uniques int64 `json:"uniques"`
|
||||
PercentOfTotal float64 `json:"percent_of_total"`
|
||||
}
|
|
@ -1,8 +1,9 @@
|
|||
package models
|
||||
|
||||
type Page struct {
|
||||
ID int64
|
||||
Hostname string
|
||||
Path string
|
||||
Title string
|
||||
ID int64 `json:"-"`
|
||||
Scheme string `json:"scheme"`
|
||||
Hostname string `json:"hostname"`
|
||||
Path string `json:"path"`
|
||||
Title string `json:"title"`
|
||||
}
|
||||
|
|
|
@ -8,10 +8,3 @@ type Pageview struct {
|
|||
ReferrerUrl string
|
||||
Timestamp string
|
||||
}
|
||||
|
||||
type PageviewCount struct {
|
||||
Hostname string `json:"hostname"`
|
||||
Path string `json:"path"`
|
||||
Count int `json:"count"`
|
||||
CountUnique int `json:"count_unique"`
|
||||
}
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
package models
|
||||
|
||||
// Point represents a data point, will always have a Label and Value
|
||||
type Point struct {
|
||||
Label string `json:"label"`
|
||||
Value int `json:"value"`
|
||||
PercentageValue float64 `json:"perc_value,omitempty"`
|
||||
UniqueValue int `json:"unique_value,omitempty"`
|
||||
}
|
|
@ -2,10 +2,11 @@ package models
|
|||
|
||||
// Total represents a daily aggregated total for a metric
|
||||
type Total struct {
|
||||
ID int64
|
||||
PageID int64 `db:"page_id"`
|
||||
Value string
|
||||
Count int64
|
||||
CountUnique int64 `db:"count_unique"`
|
||||
Date string `db:"date_group"`
|
||||
ID int64 `json:"-"`
|
||||
PageID int64 `db:"page_id" json:"-"`
|
||||
Value string `db:"value" json:"value"`
|
||||
Count int64 `db:"count" json:"count"`
|
||||
CountUnique int64 `db:"count_unique" json:"count_unique"`
|
||||
PercentageOfTotal float64 `db:"-" json:"percentage_of_total"`
|
||||
Date string `db:"date_group" json:"date,omitempty"`
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue