add loading indicator to most widgets

This commit is contained in:
Danny van Kooten 2016-12-10 18:19:44 +01:00
parent c79e8407a0
commit f304b082a5
9 changed files with 76 additions and 26 deletions

View File

@ -16,9 +16,8 @@ var GetPageviewsHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.R
p.hostname,
p.path,
COUNT(*) AS pageviews,
COUNT(DISTINCT(v.id)) AS pageviews_unique
COUNT(DISTINCT(pv.visitor_id)) AS pageviews_unique
FROM pageviews pv
LEFT JOIN visitors v ON v.id = pv.visitor_id
LEFT JOIN pages p ON pv.page_id = p.id
WHERE UNIX_TIMESTAMP(pv.timestamp) <= ? AND UNIX_TIMESTAMP(pv.timestamp) >= ?
GROUP BY p.path, p.hostname

View File

@ -29,8 +29,6 @@ var GetScreenResolutionsHandler = http.HandlerFunc(func(w http.ResponseWriter, r
FROM pageviews pv
LEFT JOIN visitors v ON v.id = pv.visitor_id
WHERE UNIX_TIMESTAMP(pv.timestamp) <= ? AND UNIX_TIMESTAMP(pv.timestamp) >= ?
AND v.screen_resolution IS NOT NULL
AND v.screen_resolution != ""
GROUP BY v.screen_resolution
ORDER BY count DESC
LIMIT ?`)

View File

@ -11,7 +11,8 @@ class CountWidget extends Component {
this.state = {
count: 0,
previousCount: 0
previousCount: 0,
loading: false
}
this.fetchData = this.fetchData.bind(this);
@ -27,9 +28,10 @@ class CountWidget extends Component {
fetchData(period) {
const before = Math.round((+new Date() ) / 1000);
const after = before - ( period * dayInSeconds );
this.setState({ loading: true })
Client.request(`${this.props.endpoint}/count?before=${before}&after=${after}`)
.then((d) => { this.setState({ count: d })})
.then((d) => { this.setState({ loading: false, count: d })})
// query previous period
const previousBefore = after;
@ -50,8 +52,10 @@ class CountWidget extends Component {
}
render() {
const loadingOverlay = this.state.loading ? <div class="loading-overlay"><div></div></div> : '';
return (
<div class="block center-text">
{loadingOverlay}
<h4 class="">{this.props.title}</h4>
<div class="big tiny-margin">{numbers.formatWithComma(this.state.count)} {this.renderPercentage()}</div>
<div class="muted">last {this.props.period} days</div>

View File

@ -12,13 +12,14 @@ class Pageviews extends Component {
super(props)
this.state = {
records: []
records: [],
loading: false
}
this.fetchRecords = this.fetchRecords.bind(this);
}
componentDidMount() {
this.fetchRecords(this.props.period);
this.fetchRecords(this.props.period)
}
componentWillReceiveProps(newProps) {
@ -30,13 +31,15 @@ class Pageviews extends Component {
fetchRecords(period) {
const before = Math.round((+new Date() ) / 1000);
const after = before - ( period * dayInSeconds );
this.setState({ loading: true })
Client.request(`/pageviews?before=${before}&after=${after}`)
.then((d) => { this.setState({ records: d })})
.then((d) => { this.setState({ loading: false, records: d })})
.catch((e) => { console.log(e) })
}
render() {
const loadingOverlay = this.state.loading ? <div class="loading-overlay"><div></div></div> : '';
const tableRows = this.state.records.map( (p, i) => (
<tr>
<td class="muted">{i+1}</td>
@ -48,6 +51,7 @@ class Pageviews extends Component {
return (
<div class="block">
{loadingOverlay}
<h3>Pageviews</h3>
<table class="table pageviews">
<thead>

View File

@ -12,7 +12,8 @@ class Table extends Component {
this.state = {
records: [],
limit: 5
limit: 5,
loading: true
}
this.tableHeaders = props.headers.map(heading => <th>{heading}</th>)
@ -46,11 +47,12 @@ class Table extends Component {
}
fetchRecords(period, limit) {
this.setState({ loading: true });
const before = Math.round((+new Date() ) / 1000);
const after = before - ( period * dayInSeconds );
Client.request(`${this.props.endpoint}?before=${before}&after=${after}&limit=${limit}`)
.then((d) => { this.setState({ records: d })})
.then((d) => { this.setState({ loading: false, records: d })})
.catch((e) => { console.log(e) })
}
@ -64,8 +66,11 @@ class Table extends Component {
</tr>
));
const loadingOverlay = this.state.loading ? <div class="loading-overlay"><div></div></div> : '';
return (
<div class="block">
{loadingOverlay}
<div class="clearfix">
<h3 class="pull-left">{this.props.title}</h3>
<div class="pull-right">

View File

@ -69,6 +69,7 @@ a {
background: white;
padding: 20px;
margin-bottom: 20px;
position: relative;
h1, h2, h3 {
margin-bottom: 20px;
@ -80,6 +81,37 @@ a {
float: left;
}
.loading-overlay {
position: absolute;
background: rgba( 255, 255, 255, 0.9 );
width: 100%;
height: 100%;
z-index: 10;
font-weight: bold;
margin: -20px;
display: flex;
align-items: center;
justify-content: center;
div {
margin: 50px;
height: 28px;
width: 28px;
animation: rotate 0.8s infinite linear;
border: 8px solid #AAA;
border-right-color: transparent;
border-radius: 50%;
}
}
@keyframes rotate {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.count {
font-weight: bold;
font-size: 120%;

View File

@ -1,11 +1,3 @@
CREATE TABLE pageviews(
`id` INTEGER UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`page_id` INTEGER UNSIGNED NOT NULL,
`visitor_id` INTEGER UNSIGNED NOT NULL,
`referrer_keyword` TEXT NULL,
`referrer_url` TEXT NULL,
`timestamp` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE visitors(
`id` INTEGER UNSIGNED AUTO_INCREMENT PRIMARY KEY,
@ -21,6 +13,18 @@ CREATE TABLE visitors(
ALTER TABLE visitors ADD UNIQUE(`visitor_key`);
CREATE TABLE pageviews(
`id` INTEGER UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`page_id` INTEGER UNSIGNED NOT NULL,
`visitor_id` INTEGER UNSIGNED NOT NULL,
`referrer_keyword` TEXT NULL,
`referrer_url` TEXT NULL,
`timestamp` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
ALTER TABLE pageviews ADD FOREIGN KEY(`visitor_id`) REFERENCES visitors(`id`);
CREATE INDEX pageview_timestamp ON pageviews(timestamp(11));
CREATE TABLE pages(
`id` INTEGER UNSIGNED AUTO_INCREMENT PRIMARY KEY NOT NULL,
`hostname` VARCHAR(63) NOT NULL,

View File

@ -91,6 +91,9 @@ func seedPages() []models.Page {
func Seed(n int) {
pages := seedPages()
stmtVisitor, _ := Conn.Prepare("SELECT v.id FROM visitors v WHERE v.visitor_key = ? LIMIT 1")
defer stmtVisitor.Close()
// insert X random hits
for i := 0; i < n; i++ {
@ -107,11 +110,9 @@ func Seed(n int) {
ScreenResolution: randSliceElement(screenResolutions),
Country: randomdata.Country(randomdata.TwoCharCountry),
}
visitor.GenerateKey()
visitor.Key = visitor.GenerateKey()
stmt, _ := Conn.Prepare("SELECT v.id FROM visitors v WHERE v.visitor_key = ? LIMIT 1")
defer stmt.Close()
err := stmt.QueryRow(visitor.Key).Scan(&visitor.ID)
err := stmtVisitor.QueryRow(visitor.Key).Scan(&visitor.ID)
if err != nil {
visitor.Save(Conn)
}
@ -127,12 +128,16 @@ func Seed(n int) {
Timestamp: timestamp,
}
Conn.Exec("START TRANSACTION")
// insert between 1-4 pageviews for this visitor
for j := 0; j < randInt(1, 4); j++ {
for j := 0; j <= randInt(1, 4); j++ {
page := pages[randInt(0, len(pages))]
pv.PageID = page.ID
pv.Save(Conn)
}
Conn.Exec("COMMIT")
}
}

View File

@ -56,6 +56,5 @@ func (v *Visitor) Save(conn *sql.DB) error {
// GenerateKey generates the "unique" visitor key
func( v *Visitor) GenerateKey() string {
byteKey := md5.Sum([]byte(v.IpAddress + v.DeviceOS + v.BrowserName + v.ScreenResolution))
v.Key = hex.EncodeToString(byteKey[:])
return v.Key
return hex.EncodeToString(byteKey[:])
}