simple bounce rate implementation. #21

This commit is contained in:
Danny 2018-05-04 12:20:37 +02:00
parent 5f626f1143
commit 86e5a7a957
17 changed files with 188 additions and 14 deletions

View File

@ -56,7 +56,7 @@ class CountWidget extends Component {
this.setState({
loading: false,
value: numbers.formatWithComma(d),
value: numbers.formatWithComma(d) + (this.props.format === 'percentage' ? '%' : ''),
})
})
}

View File

@ -51,7 +51,7 @@ class Dashboard extends Component {
<CountWidget title="Unique visitors" endpoint="visitors" before={state.before} after={state.after} />
<CountWidget title="Page views" endpoint="pageviews" before={state.before} after={state.after} />
<CountWidget title="Avg time on site" endpoint="time-on-site" format="time" before={state.before} after={state.after} />
<CountWidget title="Bounce rate" endpoint="bounce-rate" format="percentage" before={state.before} after={state.after} />
<CountWidget title="Bounce rate" endpoint="bounces" format="percentage" before={state.before} after={state.after} />
</div>
<Table endpoint="pageviews" headers={["Top pages", "Views", "Uniques"]} before={state.before} after={state.after} />

18
pkg/api/bounces.go Normal file
View File

@ -0,0 +1,18 @@
package api
import (
"net/http"
"github.com/usefathom/fathom/pkg/datastore"
)
// URL: /api/bounces/count
var GetBouncesCountHandler = HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
before, after := getRequestedPeriods(r)
result, err := datastore.TotalBounces(before, after)
if err != nil {
return err
}
return respond(w, envelope{Data: result})
})

View File

@ -47,7 +47,7 @@ func processBuffer(pv chan *models.Pageview) {
/* middleware */
func NewCollectHandler() http.Handler {
pageviews := make(chan *models.Pageview, 100)
pageviews := make(chan *models.Pageview, bufferSize)
go processBuffer(pageviews)
return HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
@ -114,6 +114,19 @@ func NewCollectHandler() http.Handler {
if err != nil {
return err
}
} else {
lastPageview, err := datastore.GetLastPageviewForVisitor(visitor.ID)
if err != nil && err != datastore.ErrNoResults {
return err
}
if lastPageview != nil {
lastPageview.Bounced = false
err := datastore.UpdatePageview(lastPageview)
if err != nil {
return err
}
}
}
// get pageview details
@ -122,6 +135,7 @@ func NewCollectHandler() http.Handler {
VisitorID: visitor.ID,
ReferrerUrl: q.Get("ru"),
ReferrerKeyword: q.Get("rk"),
Bounced: true,
Timestamp: now.Format("2006-01-02 15:04:05"),
}

View File

@ -29,6 +29,7 @@ func Server(port int, webroot string) {
r.Handle("/api/screen-resolutions", api.Authorize(api.GetScreenResolutionsHandler)).Methods("GET")
//r.Handle("/api/countries", api.Authorize(api.GetCountriesHandler)).Methods("GET")
r.Handle("/api/browsers", api.Authorize(api.GetBrowsersHandler)).Methods("GET")
r.Handle("/api/bounces/count", api.Authorize(api.GetBouncesCountHandler)).Methods("GET")
r.Path("/tracker.js").Handler(http.FileServer(http.Dir(webroot + "/js/")))
r.PathPrefix("/").Handler(http.FileServer(http.Dir(webroot)))

22
pkg/count/bounces.go Normal file
View File

@ -0,0 +1,22 @@
package count
import (
"log"
"time"
"github.com/usefathom/fathom/pkg/datastore"
)
// CreateBouncesTotals aggregates pageview data for each page into daily totals
func CreateBouncesTotals(since string) {
tomorrow := time.Now().AddDate(0, 0, 1).Format("2006-01-02")
totals, err := datastore.BouncesCountPerPageAndDay(tomorrow, since)
if err != nil {
log.Fatal(err)
}
err = datastore.SavePageTotals("bounced", totals)
if err != nil {
log.Fatal(err)
}
}

View File

@ -29,6 +29,7 @@ func Archive() {
CreateLanguageTotals(lastArchived)
CreateBrowserTotals(lastArchived)
CreateReferrerTotals(lastArchived)
CreateBouncesTotals(lastArchived)
datastore.SetOption("last_archived", time.Now().Format("2006-01-02"))
end := time.Now()

View File

@ -32,7 +32,7 @@ func CreatePageviewTotals(since string) {
log.Fatal(err)
}
err = datastore.SavePageviewTotals(totals)
err = datastore.SavePageTotals("pageviews", totals)
if err != nil {
log.Fatal(err)
}

17
pkg/datastore/bounces.go Normal file
View File

@ -0,0 +1,17 @@
package datastore
import "github.com/usefathom/fathom/pkg/models"
func BouncesCountPerPageAndDay(before string, after string) ([]*models.Total, error) {
query := dbx.Rebind(`SELECT
pv.page_id,
( COUNT(*) * 100 ) DIV ( SELECT ( COUNT(*) ) FROM pageviews WHERE page_id = pv.page_id AND bounced IS NOT NULL ) AS count,
( COUNT(DISTINCT(pv.visitor_id)) * 100 ) DIV ( SELECT ( COUNT(*) ) FROM pageviews WHERE page_id = pv.page_id AND bounced IS NOT NULL ) AS count_unique,
DATE_FORMAT(pv.timestamp, '%Y-%m-%d') AS date_group
FROM pageviews pv
WHERE pv.bounced = 1 AND pv.bounced IS NOT NULL AND pv.timestamp < ? AND pv.timestamp > ?
GROUP BY pv.page_id, date_group`)
var results []*models.Total
err := dbx.Select(&results, query, before, after)
return results, err
}

View File

@ -102,6 +102,7 @@ CREATE TABLE total_referrers (
CREATE INDEX total_referrers_date ON total_referrers(date);
ALTER TABLE total_referrers ADD UNIQUE(value, date);
-- +migrate Down
DROP TABLE IF EXISTS pageviews;
DROP TABLE if exists visitors;

View File

@ -1,7 +1,10 @@
-- +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;

View File

@ -1,5 +1,6 @@
-- +migrate Up
ALTER TABLE visitors DROP COLUMN ip_address;
-- +migrate Down
ALTER TABLE visitors ADD COLUMN ip_address VARCHAR(100) NULL;

View File

@ -0,0 +1,20 @@
-- +migrate Up
ALTER TABLE pageviews ADD COLUMN bounced TINYINT(1) NULL;
CREATE TABLE total_bounced (
id INT AUTO_INCREMENT PRIMARY KEY NOT NULL,
page_id INTEGER NOT NULL,
count INTEGER NOT NULL DEFAULT 0,
count_unique INTEGER NOT NULL DEFAULT 0,
date DATE NOT NULL
);
CREATE INDEX total_bounced_date ON total_bounced(date);
ALTER TABLE total_bounced ADD UNIQUE(page_id, date);
-- +migrate Down
DROP TABLE total_bounced;
ALTER TABLE pageviews DROP COLUMN bounced;

View File

@ -1,6 +1,8 @@
package datastore
import (
"database/sql"
"github.com/usefathom/fathom/pkg/models"
)
@ -18,7 +20,7 @@ func SavePageview(pv *models.Pageview) error {
// SavePageviews inserts multiple pageview models into the connected database using a transaction
func SavePageviews(pvs []*models.Pageview) error {
query := dbx.Rebind(`INSERT INTO pageviews(page_id, visitor_id, referrer_url, referrer_keyword, timestamp ) VALUES( ?, ?, ?, ?, ? )`)
query := dbx.Rebind(`INSERT INTO pageviews(page_id, visitor_id, referrer_url, referrer_keyword, bounced, timestamp ) VALUES( ?, ?, ?, ?, ?, ? )`)
tx, err := dbx.Begin()
if err != nil {
return err
@ -31,7 +33,7 @@ func SavePageviews(pvs []*models.Pageview) error {
defer stmt.Close()
for _, pv := range pvs {
result, err := stmt.Exec(pv.PageID, pv.VisitorID, pv.ReferrerUrl, pv.ReferrerKeyword, pv.Timestamp)
result, err := stmt.Exec(pv.PageID, pv.VisitorID, pv.ReferrerUrl, pv.ReferrerKeyword, pv.Bounced, pv.Timestamp)
if err != nil {
return err
}
@ -43,6 +45,43 @@ func SavePageviews(pvs []*models.Pageview) error {
return err
}
// UpdatePageview updates an existing pageview
func UpdatePageview(p *models.Pageview) error {
query := dbx.Rebind(`UPDATE pageviews SET bounced = ? WHERE id = ?`)
_, err := dbx.Exec(query, p.Bounced, p.ID)
return err
}
// GetPageview retrieves a pageview by its ID
func GetPageview(ID int64) (*models.Pageview, error) {
p := &models.Pageview{}
query := dbx.Rebind(`SELECT * FROM pageviews WHERE id = ? LIMIT 1`)
err := dbx.Get(p, query, ID)
if err != nil {
return nil, ErrNoResults
}
return p, nil
}
func GetLastPageviewForVisitor(visitorID int64) (*models.Pageview, error) {
p := &models.Pageview{}
query := dbx.Rebind(`SELECT * FROM pageviews WHERE visitor_id = ? ORDER BY id DESC LIMIT 1`)
err := dbx.Get(p, query, visitorID)
if err != nil {
if err == sql.ErrNoRows {
return nil, ErrNoResults
}
return nil, err
}
return p, nil
}
func PageviewCountPerPageAndDay(before string, after string) ([]*models.Total, error) {
query := dbx.Rebind(`SELECT
pv.page_id,

View File

@ -0,0 +1,33 @@
package datastore
// TotalBounces returns the total number of pageviews between the given timestamps
func TotalBounces(before int64, after int64) (int64, error) {
var total int64
query := dbx.Rebind(`
SELECT COALESCE(ROUND(AVG(t.count), 0), 0)
FROM total_bounced 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
}
// TotalUniqueBounces returns the total number of unique pageviews between the given timestamps
func TotalUniqueBounces(before int64, after int64) (int64, error) {
var total int64
query := dbx.Rebind(`
SELECT COALESCE(AVG(t.count_unique), 0)
FROM total_bounced 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
}

View File

@ -1,6 +1,8 @@
package datastore
import (
"fmt"
"github.com/usefathom/fathom/pkg/models"
)
@ -82,13 +84,14 @@ func TotalPageviewsPerPage(before int64, after int64, limit int64) ([]*models.To
// 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 {
func SavePageTotals(metric string, totals []*models.Total) error {
tx, err := dbx.Begin()
if err != nil {
return nil
}
query := dbx.Rebind(`INSERT INTO total_pageviews( page_id, count, count_unique, date ) VALUES( ?, ?, ?, ? ) ON DUPLICATE KEY UPDATE count = ?, count_unique = ?`)
query := fmt.Sprintf(`INSERT INTO total_%s( page_id, count, count_unique, date ) VALUES( ?, ?, ?, ? ) ON DUPLICATE KEY UPDATE count = ?, count_unique = ?`, metric)
query = dbx.Rebind(query)
stmt, err := tx.Prepare(query)
if err != nil {
return err

View File

@ -1,10 +1,11 @@
package models
type Pageview struct {
ID int64
PageID int64
VisitorID int64
ReferrerKeyword string
ReferrerUrl string
Timestamp string
ID int64 `db:"id"`
PageID int64 `db:"page_id"`
VisitorID int64 `db:"visitor_id"`
Bounced bool `db:"bounced"`
ReferrerKeyword string `db:"referrer_keyword"`
ReferrerUrl string `db:"referrer_url"`
Timestamp string `db:"timestamp"`
}