From 8c8666ed5cb54f352e02310d8f2672e95353569e Mon Sep 17 00:00:00 2001 From: Fabrice Date: Sat, 20 Aug 2016 22:37:20 +0200 Subject: [PATCH] Add support for Repository Traffic API Fixes #417 Closes #418 --- github/github.go | 3 + github/repos_traffic.go | 173 +++++++++++++++++++++++++++++++++++ github/repos_traffic_test.go | 148 ++++++++++++++++++++++++++++++ 3 files changed, 324 insertions(+) create mode 100644 github/repos_traffic.go create mode 100644 github/repos_traffic_test.go diff --git a/github/github.go b/github/github.go index 0b58e73..8448982 100644 --- a/github/github.go +++ b/github/github.go @@ -84,6 +84,9 @@ const ( // https://developer.github.com/changes/2016-07-06-github-pages-preiew-api/ mediaTypePagesPreview = "application/vnd.github.mister-fantastic-preview+json" + + // https://developer.github.com/v3/repos/traffic/ + mediaTypeTrafficPreview = "application/vnd.github.spiderman-preview+json" ) // A Client manages communication with the GitHub API. diff --git a/github/repos_traffic.go b/github/repos_traffic.go new file mode 100644 index 0000000..73ac485 --- /dev/null +++ b/github/repos_traffic.go @@ -0,0 +1,173 @@ +// Copyright 2016 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "fmt" + "strconv" + "time" +) + +// TrafficReferrer represent information about traffic from a referrer . +type TrafficReferrer struct { + Referrer *string `json:"referrer,omitempty"` + Count *int `json:"count,omitempty"` + Uniques *int `json:"uniques,omitempty"` +} + +// TrafficPath represent information about the traffic on a path of the repo. +type TrafficPath struct { + Path *string `json:"path,omitempty"` + Title *string `json:"title,omitempty"` + Count *int `json:"count,omitempty"` + Uniques *int `json:"uniques,omitempty"` +} + +// TimestampMS represents a timestamp as used in datapoint. +// +// It's only used to parse the result given by the API which are unix timestamp in milliseonds. +type TimestampMS struct { + time.Time +} + +// UnmarshalJSON parse unix timestamp. +func (t *TimestampMS) UnmarshalJSON(b []byte) error { + s := string(b) + i, err := strconv.ParseInt(s, 10, 64) + if err != nil { + return err + } + // We can drop the reaminder as returned values are days and it will always be 0 + *t = TimestampMS{time.Unix(i/1000, 0)} + return nil +} + +// TrafficData represent information about a specific timestamp in views or clones list. +type TrafficData struct { + Timestamp *TimestampMS `json:"timestamp,omitempty"` + Count *int `json:"count,omitempty"` + Uniques *int `json:"uniques,omitempty"` +} + +// TrafficViews represent information about the number of views in the last 14 days. +type TrafficViews struct { + Views *[]TrafficData `json:"views,omitempty"` + Count *int `json:"count,omitempty"` + Uniques *int `json:"uniques,omitempty"` +} + +// TrafficClones represent information about the number of clones in the last 14 days. +type TrafficClones struct { + Clones *[]TrafficData `json:"clones,omitempty"` + Count *int `json:"count,omitempty"` + Uniques *int `json:"uniques,omitempty"` +} + +// TrafficBreakdownOptions specifies the parameters to methods that support breakdown per day or week. +// Can be one of: day, week. Default: day. +type TrafficBreakdownOptions struct { + Per string `url:"per,omitempty"` +} + +// ListTrafficReferrers list the top 10 referrers over the last 14 days. +// +// GitHub API docs: https://developer.github.com/v3/repos/traffic/#list-referrers +func (s *RepositoriesService) ListTrafficReferrers(owner, repo string) ([]*TrafficReferrer, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/traffic/popular/referrers", owner, repo) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeTrafficPreview) + + trafficReferrers := new([]*TrafficReferrer) + resp, err := s.client.Do(req, &trafficReferrers) + if err != nil { + return nil, resp, err + } + + return *trafficReferrers, resp, err +} + +// ListTrafficPaths list the top 10 popular content over the last 14 days. +// +// GitHub API docs: https://developer.github.com/v3/repos/traffic/#list-paths +func (s *RepositoriesService) ListTrafficPaths(owner, repo string) ([]*TrafficPath, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/traffic/popular/paths", owner, repo) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeTrafficPreview) + + var paths = new([]*TrafficPath) + resp, err := s.client.Do(req, &paths) + if err != nil { + return nil, resp, err + } + + return *paths, resp, err +} + +// ListTrafficViews get total number of views for the last 14 days and breaks it down either per day or week. +// +// GitHub API docs: https://developer.github.com/v3/repos/traffic/#views +func (s *RepositoriesService) ListTrafficViews(owner, repo string, opt *TrafficBreakdownOptions) (*TrafficViews, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/traffic/views", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeTrafficPreview) + + trafficViews := new(TrafficViews) + resp, err := s.client.Do(req, &trafficViews) + if err != nil { + return nil, resp, err + } + + return trafficViews, resp, err +} + +// ListTrafficClones get total number of clones for the last 14 days and breaks it down either per day or week for the last 14 days. +// +// GitHub API docs: https://developer.github.com/v3/repos/traffic/#views +func (s *RepositoriesService) ListTrafficClones(owner, repo string, opt *TrafficBreakdownOptions) (*TrafficClones, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/traffic/clones", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeTrafficPreview) + + trafficClones := new(TrafficClones) + resp, err := s.client.Do(req, &trafficClones) + if err != nil { + return nil, resp, err + } + + return trafficClones, resp, err +} diff --git a/github/repos_traffic_test.go b/github/repos_traffic_test.go new file mode 100644 index 0000000..f809dca --- /dev/null +++ b/github/repos_traffic_test.go @@ -0,0 +1,148 @@ +// Copyright 2016 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "fmt" + "net/http" + "reflect" + "testing" + "time" +) + +func TestRepositoriesService_ListTrafficReferrers(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/traffic/popular/referrers", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeTrafficPreview) + fmt.Fprintf(w, `[{ + "referrer": "Google", + "count": 4, + "uniques": 3 + }]`) + }) + referrers, _, err := client.Repositories.ListTrafficReferrers("o", "r") + if err != nil { + t.Errorf("Repositories.ListPaths returned error: %+v", err) + } + + want := []*TrafficReferrer{{ + Referrer: String("Google"), + Count: Int(4), + Uniques: Int(3), + }} + if !reflect.DeepEqual(referrers, want) { + t.Errorf("Repositories.ListReferrers returned %+v, want %+v", referrers, want) + } + +} + +func TestRepositoriesService_ListTrafficPaths(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/traffic/popular/paths", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeTrafficPreview) + fmt.Fprintf(w, `[{ + "path": "/github/hubot", + "title": "github/hubot: A customizable life embetterment robot.", + "count": 3542, + "uniques": 2225 + }]`) + }) + paths, _, err := client.Repositories.ListTrafficPaths("o", "r") + if err != nil { + t.Errorf("Repositories.ListPaths returned error: %+v", err) + } + + want := []*TrafficPath{{ + Path: String("/github/hubot"), + Title: String("github/hubot: A customizable life embetterment robot."), + Count: Int(3542), + Uniques: Int(2225), + }} + if !reflect.DeepEqual(paths, want) { + t.Errorf("Repositories.ListPaths returned %+v, want %+v", paths, want) + } + +} + +func TestRepositoriesService_ListTrafficViews(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/traffic/views", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeTrafficPreview) + fmt.Fprintf(w, `{"count": 7, + "uniques": 6, + "views": [{ + "timestamp": 1464710400000, + "count": 7, + "uniques": 6 + }]}`) + }) + + views, _, err := client.Repositories.ListTrafficViews("o", "r", nil) + if err != nil { + t.Errorf("Repositories.ListPaths returned error: %+v", err) + } + + want := &TrafficViews{ + Views: &[]TrafficData{{ + Timestamp: &TimestampMS{time.Unix(1464710400, 0)}, + Count: Int(7), + Uniques: Int(6), + }}, + Count: Int(7), + Uniques: Int(6), + } + + if !reflect.DeepEqual(views, want) { + t.Errorf("Repositories.ListViews returned %+v, want %+v", views, want) + } + +} + +func TestRepositoriesService_ListTrafficClones(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/traffic/clones", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeTrafficPreview) + fmt.Fprintf(w, `{"count": 7, + "uniques": 6, + "clones": [{ + "timestamp": 1464710400000, + "count": 7, + "uniques": 6 + }]}`) + }) + + clones, _, err := client.Repositories.ListTrafficClones("o", "r", nil) + if err != nil { + t.Errorf("Repositories.ListPaths returned error: %+v", err) + } + + want := &TrafficClones{ + Clones: &[]TrafficData{{ + Timestamp: &TimestampMS{time.Unix(1464710400, 0)}, + Count: Int(7), + Uniques: Int(6), + }}, + Count: Int(7), + Uniques: Int(6), + } + + if !reflect.DeepEqual(clones, want) { + t.Errorf("Repositories.ListViews returned %+v, want %+v", clones, want) + } + +}