Skip to content

Commit

Permalink
Merge pull request #5 from pirsch-analytics/v1.6
Browse files Browse the repository at this point in the history
v1.6
  • Loading branch information
Kugelschieber authored Apr 3, 2022
2 parents 43e062c + 8851d13 commit 818a814
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 28 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## 1.6.0

* added hourly visitor statistics, listing events, os and browser versions
* added filter options
* updated return types with new and modified fields
* improved error messages

## 1.5.1

* added endpoint for total visitor statistics
Expand Down
94 changes: 76 additions & 18 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,16 @@ const (
conversionGoalsEndpoint = "/api/v1/statistics/goals"
eventsEndpoint = "/api/v1/statistics/events"
eventMetadataEndpoint = "/api/v1/statistics/event/meta"
listEventsEndpoint = "/api/v1/statistics/event/list"
growthRateEndpoint = "/api/v1/statistics/growth"
activeVisitorsEndpoint = "/api/v1/statistics/active"
timeOfDayEndpoint = "/api/v1/statistics/hours"
languageEndpoint = "/api/v1/statistics/language"
referrerEndpoint = "/api/v1/statistics/referrer"
osEndpoint = "/api/v1/statistics/os"
osVersionEndpoint = "/api/v1/statistics/os/version"
browserEndpoint = "/api/v1/statistics/browser"
browserVersionEndpoint = "/api/v1/statistics/browser/version"
countryEndpoint = "/api/v1/statistics/country"
cityEndpoint = "/api/v1/statistics/city"
platformEndpoint = "/api/v1/statistics/platform"
Expand Down Expand Up @@ -384,6 +387,17 @@ func (client *Client) EventMetadata(filter *Filter) ([]EventStats, error) {
return stats, nil
}

// ListEvents returns a list of all events including metadata.
func (client *Client) ListEvents(filter *Filter) ([]EventListStats, error) {
stats := make([]EventListStats, 0)

if err := client.performGet(client.getStatsRequestURL(listEventsEndpoint, filter), requestRetries, &stats); err != nil {
return nil, err
}

return stats, nil
}

// Growth returns the growth rates for visitors, bounces, ...
func (client *Client) Growth(filter *Filter) (*Growth, error) {
growth := new(Growth)
Expand Down Expand Up @@ -450,6 +464,17 @@ func (client *Client) OS(filter *Filter) ([]OSStats, error) {
return stats, nil
}

// OSVersions returns operating system version statistics.
func (client *Client) OSVersions(filter *Filter) ([]OSVersionStats, error) {
stats := make([]OSVersionStats, 0)

if err := client.performGet(client.getStatsRequestURL(osVersionEndpoint, filter), requestRetries, &stats); err != nil {
return nil, err
}

return stats, nil
}

// Browser returns browser statistics.
func (client *Client) Browser(filter *Filter) ([]BrowserStats, error) {
stats := make([]BrowserStats, 0)
Expand All @@ -461,6 +486,17 @@ func (client *Client) Browser(filter *Filter) ([]BrowserStats, error) {
return stats, nil
}

// BrowserVersions returns browser version statistics.
func (client *Client) BrowserVersions(filter *Filter) ([]BrowserVersionStats, error) {
stats := make([]BrowserVersionStats, 0)

if err := client.performGet(client.getStatsRequestURL(browserVersionEndpoint, filter), requestRetries, &stats); err != nil {
return nil, err
}

return stats, nil
}

// Country returns country statistics.
func (client *Client) Country(filter *Filter) ([]CountryStats, error) {
stats := make([]CountryStats, 0)
Expand Down Expand Up @@ -535,12 +571,8 @@ func (client *Client) getReferrerFromHeaderOrQuery(r *http.Request) string {
func (client *Client) refreshToken() error {
client.m.Lock()
defer client.m.Unlock()

// check token has expired or is about to expire soon
if client.expiresAt.After(time.Now().UTC().Add(-time.Minute)) {
return nil
}

client.accessToken = ""
client.expiresAt = time.Time{}
body := struct {
ClientId string `json:"client_id"`
ClientSecret string `json:"client_secret"`
Expand Down Expand Up @@ -578,6 +610,24 @@ func (client *Client) refreshToken() error {
}

func (client *Client) performPost(url string, body interface{}, retry int) error {
client.m.RLock()
accessToken := client.accessToken
client.m.RUnlock()

if retry > 0 && accessToken == "" {
client.waitBeforeNextRequest(retry)

if err := client.refreshToken(); err != nil {
if client.logger != nil {
client.logger.Printf("error refreshing token: %s", err)
}

return errors.New(fmt.Sprintf("error refreshing token (attempt %d/%d): %s", requestRetries-retry, requestRetries, err))
}

return client.performPost(url, body, retry-1)
}

reqBody, err := json.Marshal(body)

if err != nil {
Expand All @@ -600,16 +650,16 @@ func (client *Client) performPost(url string, body interface{}, retry int) error
return err
}

// refresh access token and retry on 401
if retry > 0 && resp.StatusCode == http.StatusUnauthorized {
time.Sleep(time.Millisecond * time.Duration((requestRetries-retry)*100+50))
// refresh access token and retry
if retry > 0 && resp.StatusCode != http.StatusOK {
client.waitBeforeNextRequest(retry)

if err := client.refreshToken(); err != nil {
if client.logger != nil {
client.logger.Printf("error refreshing token: %s", err)
}

return err
return errors.New(fmt.Sprintf("error refreshing token (attempt %d/%d): %s", requestRetries-retry, requestRetries, err))
}

return client.performPost(url, body, retry-1)
Expand All @@ -624,15 +674,19 @@ func (client *Client) performPost(url string, body interface{}, retry int) error
}

func (client *Client) performGet(url string, retry int, result interface{}) error {
if retry > 0 && client.accessToken == "" {
time.Sleep(time.Millisecond * time.Duration((requestRetries-retry)*100+50))
client.m.RLock()
accessToken := client.accessToken
client.m.RUnlock()

if retry > 0 && accessToken == "" {
client.waitBeforeNextRequest(retry)

if err := client.refreshToken(); err != nil {
if client.logger != nil {
client.logger.Printf("error refreshing token: %s", err)
}

return err
return errors.New(fmt.Sprintf("error refreshing token (attempt %d/%d): %s", requestRetries-retry, requestRetries, err))
}

return client.performGet(url, retry-1, result)
Expand All @@ -646,25 +700,25 @@ func (client *Client) performGet(url string, retry int, result interface{}) erro

client.m.RLock()
req.Header.Set("Authorization", "Bearer "+client.accessToken)
req.Header.Set("Content-Type", "application/json")
client.m.RUnlock()
req.Header.Set("Content-Type", "application/json")
c := http.Client{}
resp, err := c.Do(req)

if err != nil {
return err
}

// refresh access token and retry on 401
if retry > 0 && resp.StatusCode == http.StatusUnauthorized {
time.Sleep(time.Millisecond * time.Duration((requestRetries-retry)*100+50))
// refresh access token and retry
if retry > 0 && resp.StatusCode != http.StatusOK {
client.waitBeforeNextRequest(retry)

if err := client.refreshToken(); err != nil {
if client.logger != nil {
client.logger.Printf("error refreshing token: %s", err)
}

return err
return errors.New(fmt.Sprintf("error refreshing token (attempt %d/%d): %s", requestRetries-retry, requestRetries, err))
}

return client.performGet(url, retry-1, result)
Expand Down Expand Up @@ -728,3 +782,7 @@ func (client *Client) getStatsRequestURL(endpoint string, filter *Filter) string

return u + "?" + v.Encode()
}

func (client *Client) waitBeforeNextRequest(retry int) {
time.Sleep(time.Second * time.Duration(requestRetries-retry))
}
76 changes: 66 additions & 10 deletions types.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,24 @@ import (
"time"
)

const (
// ScaleDay groups results by day.
ScaleDay = "day"

// ScaleWeek groups results by week.
ScaleWeek = "week"

// ScaleMonth groups results by month.
ScaleMonth = "month"

// ScaleYear groups results by year.
ScaleYear = "year"
)

// Scale is used to group results in the Filter.
// Use one of the constants ScaleDay, ScaleWeek, ScaleMonth, ScaleYear.
type Scale string

// Hit are the parameters to send a page hit to Pirsch.
type Hit struct {
Hostname string
Expand Down Expand Up @@ -36,6 +54,8 @@ type Filter struct {
DomainID string `json:"id"`
From time.Time `json:"from"`
To time.Time `json:"to"`
Start int `json:"start,omitempty"`
Scale Scale `json:"scale,omitempty"`
Path string `json:"path,omitempty"`
Pattern string `json:"pattern,omitempty"`
EntryPath string `json:"entry_path,omitempty"`
Expand All @@ -51,6 +71,8 @@ type Filter struct {
Browser string `json:"browser,omitempty"`
Platform string `json:"platform,omitempty"`
ScreenClass string `json:"screen_class,omitempty"`
ScreenWidth string `json:"screen_width,omitempty"`
ScreenHeight string `json:"screen_height,omitempty"`
UTMSource string `json:"utm_source,omitempty"`
UTMMedium string `json:"utm_medium,omitempty"`
UTMCampaign string `json:"utm_campaign,omitempty"`
Expand Down Expand Up @@ -85,8 +107,12 @@ type Domain struct {

// TimeSpentStats is the time spent on the website or specific pages.
type TimeSpentStats struct {
Day time.Time `json:"day"`
Day null.Time `json:"day"`
Week null.Time `json:"week"`
Month null.Time `json:"month"`
Year null.Time `json:"year"`
Path string `json:"path"`
Title string `json:"title"`
AverageTimeSpentSeconds int `json:"average_time_spent_seconds"`
}

Expand Down Expand Up @@ -132,12 +158,15 @@ type TotalVisitorStats struct {
Views int `json:"views"`
Sessions int `json:"sessions"`
Bounces int `json:"bounces"`
BounceRate float64 `db:"bounce_rate" json:"bounce_rate"`
BounceRate float64 `json:"bounce_rate"`
}

// VisitorStats is the result type for visitor statistics.
type VisitorStats struct {
Day time.Time `json:"day"`
Day null.Time `json:"day"`
Week null.Time `json:"week"`
Month null.Time `json:"month"`
Year null.Time `json:"year"`
Visitors int `json:"visitors"`
Views int `json:"views"`
Sessions int `json:"sessions"`
Expand All @@ -160,23 +189,23 @@ type PageStats struct {

// EntryStats is the result type for entry page statistics.
type EntryStats struct {
Path string `db:"entry_path" json:"path"`
Path string `json:"path"`
Title string `json:"title"`
Visitors int `json:"visitors"`
Sessions int `json:"sessions"`
Entries int `json:"entries"`
EntryRate float64 `db:"entry_rate" json:"entry_rate"`
AverageTimeSpentSeconds int `db:"average_time_spent_seconds" json:"average_time_spent_seconds"`
EntryRate float64 `json:"entry_rate"`
AverageTimeSpentSeconds int `json:"average_time_spent_seconds"`
}

// ExitStats is the result type for exit page statistics.
type ExitStats struct {
Path string `db:"exit_path" json:"path"`
Path string `json:"path"`
Title string `json:"title"`
Visitors int `json:"visitors"`
Sessions int `json:"sessions"`
Exits int `json:"exits"`
ExitRate float64 `db:"exit_rate" json:"exit_rate"`
ExitRate float64 `json:"exit_rate"`
}

// ConversionGoal is a conversion goal as configured on the dashboard.
Expand Down Expand Up @@ -204,6 +233,14 @@ type EventStats struct {
MetaValue string `json:"meta_value"`
}

// EventListStats is the result type for a custom event list.
type EventListStats struct {
Name string `json:"name"`
Meta map[string]string `json:"meta"`
Visitors int `json:"visitors"`
Count int `json:"count"`
}

// PageConversionsStats is the result type for page conversions.
type PageConversionsStats struct {
Visitors int `json:"visitors"`
Expand All @@ -229,6 +266,7 @@ type Growth struct {
// ActiveVisitorStats is the result type for active visitor statistics.
type ActiveVisitorStats struct {
Path string `json:"path"`
Title string `json:"title"`
Visitors int `json:"visitors"`
}

Expand All @@ -240,8 +278,12 @@ type ActiveVisitorsData struct {

// VisitorHourStats is the result type for visitor statistics grouped by time of day.
type VisitorHourStats struct {
Hour int `json:"hour"`
Visitors int `json:"visitors"`
Hour int `json:"hour"`
Visitors int `json:"visitors"`
Views int `json:"views"`
Sessions int `json:"sessions"`
Bounces int `json:"bounces"`
BounceRate float64 `json:"bounce_rate"`
}

// LanguageStats is the result type for language statistics.
Expand All @@ -268,12 +310,26 @@ type BrowserStats struct {
Browser string `json:"browser"`
}

// BrowserVersionStats is the result type for browser version statistics.
type BrowserVersionStats struct {
MetaStats
Browser string `json:"browser"`
BrowserVersion string `db:"browser_version" json:"browser_version"`
}

// OSStats is the result type for operating system statistics.
type OSStats struct {
MetaStats
OS string `json:"os"`
}

// OSVersionStats is the result type for operating system version statistics.
type OSVersionStats struct {
MetaStats
OS string `json:"os"`
OSVersion string `db:"os_version" json:"os_version"`
}

// ReferrerStats is the result type for referrer statistics.
type ReferrerStats struct {
Referrer string `json:"referrer"`
Expand Down

0 comments on commit 818a814

Please sign in to comment.