mirror of
https://github.com/status-im/fathom.git
synced 2025-02-28 19:10:36 +00:00
simple bounce rate implementation. #21
This commit is contained in:
parent
5f626f1143
commit
86e5a7a957
@ -56,7 +56,7 @@ class CountWidget extends Component {
|
||||
|
||||
this.setState({
|
||||
loading: false,
|
||||
value: numbers.formatWithComma(d),
|
||||
value: numbers.formatWithComma(d) + (this.props.format === 'percentage' ? '%' : ''),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -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
18
pkg/api/bounces.go
Normal 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})
|
||||
})
|
@ -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"),
|
||||
}
|
||||
|
||||
|
@ -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
22
pkg/count/bounces.go
Normal 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)
|
||||
}
|
||||
}
|
@ -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()
|
||||
|
@ -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
17
pkg/datastore/bounces.go
Normal 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
|
||||
}
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
20
pkg/datastore/migrations/4_add_bounce_structure.sql
Normal file
20
pkg/datastore/migrations/4_add_bounce_structure.sql
Normal 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;
|
@ -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,
|
||||
|
33
pkg/datastore/total_bounces.go
Normal file
33
pkg/datastore/total_bounces.go
Normal 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
|
||||
}
|
@ -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
|
||||
|
@ -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"`
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user