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({
|
this.setState({
|
||||||
loading: false,
|
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="Unique visitors" endpoint="visitors" before={state.before} after={state.after} />
|
||||||
<CountWidget title="Page views" endpoint="pageviews" 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="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>
|
</div>
|
||||||
|
|
||||||
<Table endpoint="pageviews" headers={["Top pages", "Views", "Uniques"]} before={state.before} after={state.after} />
|
<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 */
|
/* middleware */
|
||||||
func NewCollectHandler() http.Handler {
|
func NewCollectHandler() http.Handler {
|
||||||
pageviews := make(chan *models.Pageview, 100)
|
pageviews := make(chan *models.Pageview, bufferSize)
|
||||||
go processBuffer(pageviews)
|
go processBuffer(pageviews)
|
||||||
|
|
||||||
return HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
|
return HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
|
||||||
@ -114,6 +114,19 @@ func NewCollectHandler() http.Handler {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
// get pageview details
|
||||||
@ -122,6 +135,7 @@ func NewCollectHandler() http.Handler {
|
|||||||
VisitorID: visitor.ID,
|
VisitorID: visitor.ID,
|
||||||
ReferrerUrl: q.Get("ru"),
|
ReferrerUrl: q.Get("ru"),
|
||||||
ReferrerKeyword: q.Get("rk"),
|
ReferrerKeyword: q.Get("rk"),
|
||||||
|
Bounced: true,
|
||||||
Timestamp: now.Format("2006-01-02 15:04:05"),
|
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/screen-resolutions", api.Authorize(api.GetScreenResolutionsHandler)).Methods("GET")
|
||||||
//r.Handle("/api/countries", api.Authorize(api.GetCountriesHandler)).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/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.Path("/tracker.js").Handler(http.FileServer(http.Dir(webroot + "/js/")))
|
||||||
r.PathPrefix("/").Handler(http.FileServer(http.Dir(webroot)))
|
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)
|
CreateLanguageTotals(lastArchived)
|
||||||
CreateBrowserTotals(lastArchived)
|
CreateBrowserTotals(lastArchived)
|
||||||
CreateReferrerTotals(lastArchived)
|
CreateReferrerTotals(lastArchived)
|
||||||
|
CreateBouncesTotals(lastArchived)
|
||||||
datastore.SetOption("last_archived", time.Now().Format("2006-01-02"))
|
datastore.SetOption("last_archived", time.Now().Format("2006-01-02"))
|
||||||
|
|
||||||
end := time.Now()
|
end := time.Now()
|
||||||
|
@ -32,7 +32,7 @@ func CreatePageviewTotals(since string) {
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = datastore.SavePageviewTotals(totals)
|
err = datastore.SavePageTotals("pageviews", totals)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
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);
|
CREATE INDEX total_referrers_date ON total_referrers(date);
|
||||||
ALTER TABLE total_referrers ADD UNIQUE(value, date);
|
ALTER TABLE total_referrers ADD UNIQUE(value, date);
|
||||||
|
|
||||||
|
|
||||||
-- +migrate Down
|
-- +migrate Down
|
||||||
DROP TABLE IF EXISTS pageviews;
|
DROP TABLE IF EXISTS pageviews;
|
||||||
DROP TABLE if exists visitors;
|
DROP TABLE if exists visitors;
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
-- +migrate Up
|
-- +migrate Up
|
||||||
ALTER TABLE pages ADD COLUMN scheme ENUM("http", "https") DEFAULT "http";
|
ALTER TABLE pages ADD COLUMN scheme ENUM("http", "https") DEFAULT "http";
|
||||||
|
|
||||||
ALTER TABLE pages DROP COLUMN title;
|
ALTER TABLE pages DROP COLUMN title;
|
||||||
|
|
||||||
|
|
||||||
-- +migrate Down
|
-- +migrate Down
|
||||||
ALTER TABLE pages DROP COLUMN scheme;
|
ALTER TABLE pages DROP COLUMN scheme;
|
||||||
|
|
||||||
ALTER TABLE pages ADD COLUMN title VARCHAR(255) NULL;
|
ALTER TABLE pages ADD COLUMN title VARCHAR(255) NULL;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
-- +migrate Up
|
-- +migrate Up
|
||||||
ALTER TABLE visitors DROP COLUMN ip_address;
|
ALTER TABLE visitors DROP COLUMN ip_address;
|
||||||
|
|
||||||
|
|
||||||
-- +migrate Down
|
-- +migrate Down
|
||||||
ALTER TABLE visitors ADD COLUMN ip_address VARCHAR(100) NULL;
|
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
|
package datastore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
"github.com/usefathom/fathom/pkg/models"
|
"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
|
// SavePageviews inserts multiple pageview models into the connected database using a transaction
|
||||||
func SavePageviews(pvs []*models.Pageview) error {
|
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()
|
tx, err := dbx.Begin()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -31,7 +33,7 @@ func SavePageviews(pvs []*models.Pageview) error {
|
|||||||
defer stmt.Close()
|
defer stmt.Close()
|
||||||
|
|
||||||
for _, pv := range pvs {
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -43,6 +45,43 @@ func SavePageviews(pvs []*models.Pageview) error {
|
|||||||
return err
|
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) {
|
func PageviewCountPerPageAndDay(before string, after string) ([]*models.Total, error) {
|
||||||
query := dbx.Rebind(`SELECT
|
query := dbx.Rebind(`SELECT
|
||||||
pv.page_id,
|
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
|
package datastore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/usefathom/fathom/pkg/models"
|
"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
|
// SavePageviewTotals saves the given totals in the connected database
|
||||||
// Differs slightly from the metric specific totals because of the normalized pages (to save storage)
|
// 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()
|
tx, err := dbx.Begin()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 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)
|
stmt, err := tx.Prepare(query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
type Pageview struct {
|
type Pageview struct {
|
||||||
ID int64
|
ID int64 `db:"id"`
|
||||||
PageID int64
|
PageID int64 `db:"page_id"`
|
||||||
VisitorID int64
|
VisitorID int64 `db:"visitor_id"`
|
||||||
ReferrerKeyword string
|
Bounced bool `db:"bounced"`
|
||||||
ReferrerUrl string
|
ReferrerKeyword string `db:"referrer_keyword"`
|
||||||
Timestamp string
|
ReferrerUrl string `db:"referrer_url"`
|
||||||
|
Timestamp string `db:"timestamp"`
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user