From d844c52f06c8c0a12ca67e3cef3c9e6aae275694 Mon Sep 17 00:00:00 2001 From: Claudio Costa Date: Thu, 4 Jul 2019 21:26:41 +0200 Subject: [PATCH] MM-12389 Update segment library to v3 (#11472) * Update analytics-go dependency * Migrate from segment library v2 to v3 * Reinclude gorilla/handlers module * Fix missing module * Remove reference to outdated analytics module --- Makefile | 3 - app/diagnostics.go | 2 +- app/server.go | 17 +- go.mod | 3 +- go.sum | 6 +- .../github.com/jehiah/go-strftime/README.md | 26 - .../github.com/jehiah/go-strftime/strftime.go | 72 --- .../analytics-go}/.gitignore | 10 + .../segmentio/analytics-go/.gitmodules | 6 + .../segmentio/analytics-go/History.md | 19 +- .../analytics-go/License.md} | 12 +- .../segmentio/analytics-go/Makefile | 33 +- .../segmentio/analytics-go/Readme.md | 55 +- .../segmentio/analytics-go/alias.go | 38 ++ .../segmentio/analytics-go/analytics.go | 587 +++++++++--------- .../segmentio/analytics-go/circle.yml | 18 - .../segmentio/analytics-go/config.go | 173 ++++++ .../segmentio/analytics-go/context.go | 148 +++++ .../segmentio/analytics-go/error.go | 60 ++ .../segmentio/analytics-go/executor.go | 53 ++ .../segmentio/analytics-go/group.go | 40 ++ .../segmentio/analytics-go/identify.go | 31 + .../segmentio/analytics-go/integrations.go | 44 ++ .../github.com/segmentio/analytics-go/json.go | 87 +++ .../segmentio/analytics-go/logger.go | 47 ++ .../segmentio/analytics-go/message.go | 128 ++++ .../github.com/segmentio/analytics-go/page.go | 32 + .../segmentio/analytics-go/properties.go | 117 ++++ .../segmentio/analytics-go/screen.go | 32 + .../segmentio/analytics-go/timeout_15.go | 16 + .../segmentio/analytics-go/timeout_16.go | 10 + .../segmentio/analytics-go/track.go | 40 ++ .../segmentio/analytics-go/traits.go | 89 +++ vendor/modules.txt | 4 +- 34 files changed, 1599 insertions(+), 459 deletions(-) delete mode 100644 vendor/github.com/jehiah/go-strftime/README.md delete mode 100644 vendor/github.com/jehiah/go-strftime/strftime.go rename vendor/github.com/{jehiah/go-strftime => segmentio/analytics-go}/.gitignore (82%) create mode 100644 vendor/github.com/segmentio/analytics-go/.gitmodules rename vendor/github.com/{jehiah/go-strftime/LICENSE => segmentio/analytics-go/License.md} (86%) create mode 100644 vendor/github.com/segmentio/analytics-go/alias.go delete mode 100644 vendor/github.com/segmentio/analytics-go/circle.yml create mode 100644 vendor/github.com/segmentio/analytics-go/config.go create mode 100644 vendor/github.com/segmentio/analytics-go/context.go create mode 100644 vendor/github.com/segmentio/analytics-go/error.go create mode 100644 vendor/github.com/segmentio/analytics-go/executor.go create mode 100644 vendor/github.com/segmentio/analytics-go/group.go create mode 100644 vendor/github.com/segmentio/analytics-go/identify.go create mode 100644 vendor/github.com/segmentio/analytics-go/integrations.go create mode 100644 vendor/github.com/segmentio/analytics-go/json.go create mode 100644 vendor/github.com/segmentio/analytics-go/logger.go create mode 100644 vendor/github.com/segmentio/analytics-go/message.go create mode 100644 vendor/github.com/segmentio/analytics-go/page.go create mode 100644 vendor/github.com/segmentio/analytics-go/properties.go create mode 100644 vendor/github.com/segmentio/analytics-go/screen.go create mode 100644 vendor/github.com/segmentio/analytics-go/timeout_15.go create mode 100644 vendor/github.com/segmentio/analytics-go/timeout_16.go create mode 100644 vendor/github.com/segmentio/analytics-go/track.go create mode 100644 vendor/github.com/segmentio/analytics-go/traits.go diff --git a/Makefile b/Makefile index b36c4c7d92..93053a4ebc 100644 --- a/Makefile +++ b/Makefile @@ -576,9 +576,6 @@ update-dependencies: ## Uses go get -u to update all the dependencies while hold # Update all dependencies (does not update across major versions) go get -u - # Keep back because of breaking API changes - go get -u github.com/segmentio/analytics-go@2.1.1 - # Tidy up go mod tidy diff --git a/app/diagnostics.go b/app/diagnostics.go index 09b01c3dd9..e51e2bcf23 100644 --- a/app/diagnostics.go +++ b/app/diagnostics.go @@ -75,7 +75,7 @@ func (a *App) sendDailyDiagnostics(override bool) { } func (a *App) SendDiagnostic(event string, properties map[string]interface{}) { - a.Srv.diagnosticClient.Track(&analytics.Track{ + a.Srv.diagnosticClient.Enqueue(&analytics.Track{ Event: event, UserId: a.DiagnosticId(), Properties: properties, diff --git a/app/server.go b/app/server.go index 6335f86174..e6a7b9d78d 100644 --- a/app/server.go +++ b/app/server.go @@ -103,7 +103,7 @@ type Server struct { limitedClientConfig map[string]string diagnosticId string - diagnosticClient *analytics.Client + diagnosticClient analytics.Client phase2PermissionsMigrationComplete bool @@ -745,15 +745,16 @@ func (s *Server) StartElasticsearch() { func (s *Server) initDiagnostics(endpoint string) { if s.diagnosticClient == nil { - client := analytics.New(SEGMENT_KEY) - client.Logger = s.Log.StdLog(mlog.String("source", "segment")) + config := analytics.Config{} + config.Logger = analytics.StdLogger(s.Log.StdLog(mlog.String("source", "segment"))) // For testing if endpoint != "" { - client.Endpoint = endpoint - client.Verbose = true - client.Size = 1 + config.Endpoint = endpoint + config.Verbose = true + config.BatchSize = 1 } - client.Identify(&analytics.Identify{ + client, _ := analytics.NewWithConfig(SEGMENT_KEY, config) + client.Enqueue(&analytics.Identify{ UserId: s.diagnosticId, }) @@ -761,7 +762,7 @@ func (s *Server) initDiagnostics(endpoint string) { } } -// ShutdownDiagnostics closes the diagnostic client. +// shutdownDiagnostics closes the diagnostic client. func (s *Server) shutdownDiagnostics() error { if s.diagnosticClient != nil { return s.diagnosticClient.Close() diff --git a/go.mod b/go.mod index e5d8a08828..ce2a7f59ce 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,6 @@ require ( github.com/icrowley/fake v0.0.0-20180203215853-4178557ae428 github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/jaytaylor/html2text v0.0.0-20190311042500-a93a6c6ea053 - github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 // indirect github.com/jmoiron/sqlx v1.2.0 github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect github.com/lib/pq v1.0.0 @@ -61,7 +60,7 @@ require ( github.com/prometheus/procfs v0.0.0-20190322151404-55ae3d9d5573 // indirect github.com/rs/cors v1.6.0 github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd - github.com/segmentio/analytics-go v2.0.1-0.20160426181448-2d840d861c32+incompatible + github.com/segmentio/analytics-go v3.0.1+incompatible github.com/segmentio/backo-go v0.0.0-20160424052352-204274ad699c // indirect github.com/sirupsen/logrus v1.4.0 github.com/smartystreets/assertions v0.0.0-20190401211740-f487f9de1cd3 // indirect diff --git a/go.sum b/go.sum index 60b3325276..15eb5bc088 100644 --- a/go.sum +++ b/go.sum @@ -171,8 +171,6 @@ github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NH github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jaytaylor/html2text v0.0.0-20190311042500-a93a6c6ea053 h1:vAR93++rxlMlJRMK0hKD3l5La7FjpmUIxO1jnJmgTbI= github.com/jaytaylor/html2text v0.0.0-20190311042500-a93a6c6ea053/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk= -github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 h1:IPJ3dvxmJ4uczJe5YQdrYB16oTJlGSC/OyZDqUk9xX4= -github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869/go.mod h1:cJ6Cj7dQo+O6GJNiMx+Pa94qKj+TG8ONdKHgMNIyyag= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= @@ -304,8 +302,8 @@ github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFo github.com/satori/go.uuid v0.0.0-20180103174451-36e9d2ebbde5/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/segmentio/analytics-go v2.0.1-0.20160426181448-2d840d861c32+incompatible h1:VcfKSKy3OxvnkQzsZPD1170acL2qldNDZCiRJ7CzZwE= -github.com/segmentio/analytics-go v2.0.1-0.20160426181448-2d840d861c32+incompatible/go.mod h1:C7CYBtQWk4vRk2RyLu0qOcbHJ18E3F1HV2C/8JvKN48= +github.com/segmentio/analytics-go v3.0.1+incompatible h1:W7T3ieNQjPFMb+SE8SAVYo6mPkKK/Y37wYdiNf5lCVg= +github.com/segmentio/analytics-go v3.0.1+incompatible/go.mod h1:C7CYBtQWk4vRk2RyLu0qOcbHJ18E3F1HV2C/8JvKN48= github.com/segmentio/backo-go v0.0.0-20160424052352-204274ad699c h1:rsRTAcCR5CeNLkvgBVSjQoDGRRt6kggsE6XYBqCv2KQ= github.com/segmentio/backo-go v0.0.0-20160424052352-204274ad699c/go.mod h1:kJ9mm9YmoWSkk+oQ+5Cj8DEoRCX2JT6As4kEtIIOp1M= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= diff --git a/vendor/github.com/jehiah/go-strftime/README.md b/vendor/github.com/jehiah/go-strftime/README.md deleted file mode 100644 index 10b06f4af0..0000000000 --- a/vendor/github.com/jehiah/go-strftime/README.md +++ /dev/null @@ -1,26 +0,0 @@ -go-strftime -=========== - -go implementation of strftime - -## Example - -``` -package main - -import ( - "fmt" - "time" - - strftime "github.com/jehiah/go-strftime" -) - -func main() { - t := time.Unix(1340244776, 0) - utc, _ := time.LoadLocation("UTC") - t = t.In(utc) - fmt.Println(strftime.Format("%Y-%m-%d %H:%M:%S", t)) - // Output: - // 2012-06-21 02:12:56 -} -``` diff --git a/vendor/github.com/jehiah/go-strftime/strftime.go b/vendor/github.com/jehiah/go-strftime/strftime.go deleted file mode 100644 index bf2eeab486..0000000000 --- a/vendor/github.com/jehiah/go-strftime/strftime.go +++ /dev/null @@ -1,72 +0,0 @@ -// go implementation of strftime -package strftime - -import ( - "strings" - "time" -) - -// taken from time/format.go -var conversion = map[rune]string{ - /*stdLongMonth */ 'B': "January", - /*stdMonth */ 'b': "Jan", - // stdNumMonth */ 'm': "1", - /*stdZeroMonth */ 'm': "01", - /*stdLongWeekDay */ 'A': "Monday", - /*stdWeekDay */ 'a': "Mon", - // stdDay */ 'd': "2", - // stdUnderDay */ 'd': "_2", - /*stdZeroDay */ 'd': "02", - /*stdHour */ 'H': "15", - // stdHour12 */ 'I': "3", - /*stdZeroHour12 */ 'I': "03", - // stdMinute */ 'M': "4", - /*stdZeroMinute */ 'M': "04", - // stdSecond */ 'S': "5", - /*stdZeroSecond */ 'S': "05", - /*stdLongYear */ 'Y': "2006", - /*stdYear */ 'y': "06", - /*stdPM */ 'p': "PM", - // stdpm */ 'p': "pm", - /*stdTZ */ 'Z': "MST", - // stdISO8601TZ */ 'z': "Z0700", // prints Z for UTC - // stdISO8601ColonTZ */ 'z': "Z07:00", // prints Z for UTC - /*stdNumTZ */ 'z': "-0700", // always numeric - // stdNumShortTZ */ 'b': "-07", // always numeric - // stdNumColonTZ */ 'b': "-07:00", // always numeric - /* nonStdMilli */ 'L': ".000", -} - -// This is an alternative to time.Format because no one knows -// what date 040305 is supposed to create when used as a 'layout' string -// this takes standard strftime format options. For a complete list -// of format options see http://strftime.org/ -func Format(format string, t time.Time) string { - retval := make([]byte, 0, len(format)) - for i, ni := 0, 0; i < len(format); i = ni + 2 { - ni = strings.IndexByte(format[i:], '%') - if ni < 0 { - ni = len(format) - } else { - ni += i - } - retval = append(retval, []byte(format[i:ni])...) - if ni+1 < len(format) { - c := format[ni+1] - if c == '%' { - retval = append(retval, '%') - } else { - if layoutCmd, ok := conversion[rune(c)]; ok { - retval = append(retval, []byte(t.Format(layoutCmd))...) - } else { - retval = append(retval, '%', c) - } - } - } else { - if ni < len(format) { - retval = append(retval, '%') - } - } - } - return string(retval) -} diff --git a/vendor/github.com/jehiah/go-strftime/.gitignore b/vendor/github.com/segmentio/analytics-go/.gitignore similarity index 82% rename from vendor/github.com/jehiah/go-strftime/.gitignore rename to vendor/github.com/segmentio/analytics-go/.gitignore index 00268614f0..942678bd91 100644 --- a/vendor/github.com/jehiah/go-strftime/.gitignore +++ b/vendor/github.com/segmentio/analytics-go/.gitignore @@ -20,3 +20,13 @@ _cgo_export.* _testmain.go *.exe +*.test +*.prof + +# Emacs +*~ +\#* +.\#* + +# Artifacts +tmp/* diff --git a/vendor/github.com/segmentio/analytics-go/.gitmodules b/vendor/github.com/segmentio/analytics-go/.gitmodules new file mode 100644 index 0000000000..b2150b3427 --- /dev/null +++ b/vendor/github.com/segmentio/analytics-go/.gitmodules @@ -0,0 +1,6 @@ +[submodule "vendor/github.com/segmentio/backo-go"] + path = vendor/github.com/segmentio/backo-go + url = https://github.com/segmentio/backo-go +[submodule "vendor/github.com/xtgo/uuid"] + path = vendor/github.com/xtgo/uuid + url = https://github.com/xtgo/uuid diff --git a/vendor/github.com/segmentio/analytics-go/History.md b/vendor/github.com/segmentio/analytics-go/History.md index e8538a0ccb..42bc0f654b 100644 --- a/vendor/github.com/segmentio/analytics-go/History.md +++ b/vendor/github.com/segmentio/analytics-go/History.md @@ -1,9 +1,20 @@ - -v2.1.1 / 2016-04-26 +v3.0.1 / 2018-10-02 =================== - * Fix blocking the goroutine when Close is the first call. - * Fix blocking the goroutine when the message queue fills up. +* Migrate from Circle V1 format to Circle V2 +* Adds CLI for sending segment events +* Vendor packages back-go and uuid instead of using gitsubmodules + + +v3.0.0 / 2016-06-02 +=================== + + * 3.0 is a significant rewrite with multiple breaking changes. + * [Quickstart](https://segment.com/docs/sources/server/go/quickstart/). + * [Documentation](https://segment.com/docs/sources/server/go/). + * [GoDocs](https://godoc.org/gopkg.in/segmentio/analytics-go.v3). + * [What's New in v3](https://segment.com/docs/sources/server/go/#what-s-new-in-v3). + v2.1.0 / 2015-12-28 =================== diff --git a/vendor/github.com/jehiah/go-strftime/LICENSE b/vendor/github.com/segmentio/analytics-go/License.md similarity index 86% rename from vendor/github.com/jehiah/go-strftime/LICENSE rename to vendor/github.com/segmentio/analytics-go/License.md index a49216f132..f452c5d0fd 100644 --- a/vendor/github.com/jehiah/go-strftime/LICENSE +++ b/vendor/github.com/segmentio/analytics-go/License.md @@ -1,4 +1,6 @@ -Copyright (c) 2012 Jehiah Czebotar +The MIT License (MIT) + +Copyright (c) 2016 Segment, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -7,13 +9,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/segmentio/analytics-go/Makefile b/vendor/github.com/segmentio/analytics-go/Makefile index e896b1b5f0..c9debaaf43 100644 --- a/vendor/github.com/segmentio/analytics-go/Makefile +++ b/vendor/github.com/segmentio/analytics-go/Makefile @@ -1,10 +1,31 @@ +ifndef CIRCLE_ARTIFACTS +CIRCLE_ARTIFACTS=tmp +endif + +bootstrap: + .buildscript/bootstrap.sh + +dependencies: + @go get -v -t ./... + vet: - @godep go vet ./... + @go vet ./... -build: - @godep go build +test: vet + @mkdir -p ${CIRCLE_ARTIFACTS} + @go test -race -coverprofile=${CIRCLE_ARTIFACTS}/cover.out . + @go tool cover -func ${CIRCLE_ARTIFACTS}/cover.out -o ${CIRCLE_ARTIFACTS}/cover.txt + @go tool cover -html ${CIRCLE_ARTIFACTS}/cover.out -o ${CIRCLE_ARTIFACTS}/cover.html -test: - @godep go test -race -cover ./... +build: test + @go build ./... -.PHONY: vet build test +e2e: + @if [ "$(RUN_E2E_TESTS)" != "true" ]; then \ + echo "Skipping end to end tests."; else \ + go get github.com/segmentio/library-e2e-tester/cmd/tester; \ + tester -segment-write-key=$(SEGMENT_WRITE_KEY) -webhook-auth-username=$(WEBHOOK_AUTH_USERNAME) -webhook-bucket=$(WEBHOOK_BUCKET) -path='cli'; fi + +ci: dependencies test e2e + +.PHONY: bootstrap dependencies vet test e2e ci diff --git a/vendor/github.com/segmentio/analytics-go/Readme.md b/vendor/github.com/segmentio/analytics-go/Readme.md index b521cbe630..7d369c16e8 100644 --- a/vendor/github.com/segmentio/analytics-go/Readme.md +++ b/vendor/github.com/segmentio/analytics-go/Readme.md @@ -1,8 +1,55 @@ -# analytics-go +# analytics-go [![Circle CI](https://circleci.com/gh/segmentio/analytics-go/tree/master.svg?style=shield)](https://circleci.com/gh/segmentio/analytics-go/tree/master) [![go-doc](https://godoc.org/github.com/segmentio/analytics-go?status.svg)](https://godoc.org/github.com/segmentio/analytics-go) - Segment analytics client for Go. For additional documentation - visit [https://segment.com/docs/libraries/go](https://segment.com/docs/libraries/go/) or view the [godocs](http://godoc.org/github.com/segmentio/analytics-go). +Segment analytics client for Go. + +## Installation + +The package can be simply installed via go get, we recommend that you use a +package version management system like the Go vendor directory or a tool like +Godep to avoid issues related to API breaking changes introduced between major +versions of the library. + +To install it in the GOPATH: +``` +go get https://github.com/segmentio/analytics-go +``` + +## Documentation + +The links bellow should provide all the documentation needed to make the best +use of the library and the Segment API: + +- [Documentation](https://segment.com/docs/libraries/go/) +- [godoc](https://godoc.org/gopkg.in/segmentio/analytics-go.v3) +- [API](https://segment.com/docs/libraries/http/) +- [Specs](https://segment.com/docs/spec/) + +## Usage + +```go +package main + +import ( + "os" + + "github.com/segmentio/analytics-go" +) + +func main() { + // Instantiates a client to use send messages to the segment API. + client := analytics.New(os.Getenv("SEGMENT_WRITE_KEY")) + + // Enqueues a track event that will be sent asynchronously. + client.Enqueue(analytics.Track{ + UserId: "test-user", + Event: "test-snippet", + }) + + // Flushes any queued messages and closes the client. + client.Close() +} +``` ## License - MIT +The library is released under the [MIT license](License.md). diff --git a/vendor/github.com/segmentio/analytics-go/alias.go b/vendor/github.com/segmentio/analytics-go/alias.go new file mode 100644 index 0000000000..0084d20b4d --- /dev/null +++ b/vendor/github.com/segmentio/analytics-go/alias.go @@ -0,0 +1,38 @@ +package analytics + +import "time" + +// This type represents object sent in a alias call as described in +// https://segment.com/docs/libraries/http/#alias +type Alias struct { + // This field is exported for serialization purposes and shouldn't be set by + // the application, its value is always overwritten by the library. + Type string `json:"type,omitempty"` + + MessageId string `json:"messageId,omitempty"` + PreviousId string `json:"previousId"` + UserId string `json:"userId"` + Timestamp time.Time `json:"timestamp,omitempty"` + Context *Context `json:"context,omitempty"` + Integrations Integrations `json:"integrations,omitempty"` +} + +func (msg Alias) validate() error { + if len(msg.UserId) == 0 { + return FieldError{ + Type: "analytics.Alias", + Name: "UserId", + Value: msg.UserId, + } + } + + if len(msg.PreviousId) == 0 { + return FieldError{ + Type: "analytics.Alias", + Name: "PreviousId", + Value: msg.PreviousId, + } + } + + return nil +} diff --git a/vendor/github.com/segmentio/analytics-go/analytics.go b/vendor/github.com/segmentio/analytics-go/analytics.go index 6ec93fcbf5..793df10b88 100644 --- a/vendor/github.com/segmentio/analytics-go/analytics.go +++ b/vendor/github.com/segmentio/analytics-go/analytics.go @@ -2,307 +2,248 @@ package analytics import ( "fmt" + "io" "io/ioutil" - "os" "sync" "bytes" "encoding/json" - "errors" - "log" "net/http" "time" - - "github.com/jehiah/go-strftime" - "github.com/segmentio/backo-go" - "github.com/xtgo/uuid" ) // Version of the client. -const Version = "2.1.0" +const Version = "3.0.0" -// Endpoint for the Segment API. -const Endpoint = "https://api.segment.io" +// This interface is the main API exposed by the analytics package. +// Values that satsify this interface are returned by the client constructors +// provided by the package and provide a way to send messages via the HTTP API. +type Client interface { + io.Closer -// DefaultContext of message batches. -var DefaultContext = map[string]interface{}{ - "library": map[string]interface{}{ - "name": "analytics-go", - "version": Version, - }, + // Queues a message to be sent by the client when the conditions for a batch + // upload are met. + // This is the main method you'll be using, a typical flow would look like + // this: + // + // client := analytics.New(writeKey) + // ... + // client.Enqueue(analytics.Track{ ... }) + // ... + // client.Close() + // + // The method returns an error if the message queue not be queued, which + // happens if the client was already closed at the time the method was + // called or if the message was malformed. + Enqueue(Message) error } -// Backoff policy. -var Backo = backo.DefaultBacko() +type client struct { + Config + key string -// Message interface. -type message interface { - setMessageId(string) - setTimestamp(string) -} + // This channel is where the `Enqueue` method writes messages so they can be + // picked up and pushed by the backend goroutine taking care of applying the + // batching rules. + msgs chan Message -// Message fields common to all. -type Message struct { - Type string `json:"type,omitempty"` - MessageId string `json:"messageId,omitempty"` - Timestamp string `json:"timestamp,omitempty"` - SentAt string `json:"sentAt,omitempty"` -} - -// Batch message. -type Batch struct { - Context map[string]interface{} `json:"context,omitempty"` - Messages []interface{} `json:"batch"` - Message -} - -// Identify message. -type Identify struct { - Context map[string]interface{} `json:"context,omitempty"` - Integrations map[string]interface{} `json:"integrations,omitempty"` - Traits map[string]interface{} `json:"traits,omitempty"` - AnonymousId string `json:"anonymousId,omitempty"` - UserId string `json:"userId,omitempty"` - Message -} - -// Group message. -type Group struct { - Context map[string]interface{} `json:"context,omitempty"` - Integrations map[string]interface{} `json:"integrations,omitempty"` - Traits map[string]interface{} `json:"traits,omitempty"` - AnonymousId string `json:"anonymousId,omitempty"` - UserId string `json:"userId,omitempty"` - GroupId string `json:"groupId"` - Message -} - -// Track message. -type Track struct { - Context map[string]interface{} `json:"context,omitempty"` - Integrations map[string]interface{} `json:"integrations,omitempty"` - Properties map[string]interface{} `json:"properties,omitempty"` - AnonymousId string `json:"anonymousId,omitempty"` - UserId string `json:"userId,omitempty"` - Event string `json:"event"` - Message -} - -// Page message. -type Page struct { - Context map[string]interface{} `json:"context,omitempty"` - Integrations map[string]interface{} `json:"integrations,omitempty"` - Traits map[string]interface{} `json:"properties,omitempty"` - AnonymousId string `json:"anonymousId,omitempty"` - UserId string `json:"userId,omitempty"` - Category string `json:"category,omitempty"` - Name string `json:"name,omitempty"` - Message -} - -// Alias message. -type Alias struct { - PreviousId string `json:"previousId"` - UserId string `json:"userId"` - Message -} - -// Client which batches messages and flushes at the given Interval or -// when the Size limit is exceeded. Set Verbose to true to enable -// logging output. -type Client struct { - Endpoint string - // Interval represents the duration at which messages are flushed. It may be - // configured only before any messages are enqueued. - Interval time.Duration - Size int - Logger *log.Logger - Verbose bool - Client http.Client - key string - msgs chan interface{} + // These two channels are used to synchronize the client shutting down when + // `Close` is called. + // The first channel is closed to signal the backend goroutine that it has + // to stop, then the second one is closed by the backend goroutine to signal + // that it has finished flushing all queued messages. quit chan struct{} shutdown chan struct{} - uid func() string - now func() time.Time - once sync.Once - wg sync.WaitGroup - // These synchronization primitives are used to control how many goroutines - // are spawned by the client for uploads. - upmtx sync.Mutex - upcond sync.Cond - upcount int + // This HTTP client is used to send requests to the backend, it uses the + // HTTP transport provided in the configuration. + http http.Client } -// New client with write key. -func New(key string) *Client { - c := &Client{ - Endpoint: Endpoint, - Interval: 5 * time.Second, - Size: 250, - Logger: log.New(os.Stderr, "segment ", log.LstdFlags), - Verbose: false, - Client: *http.DefaultClient, - key: key, - msgs: make(chan interface{}, 100), - quit: make(chan struct{}), - shutdown: make(chan struct{}), - now: time.Now, - uid: uid, - } - - c.upcond.L = &c.upmtx +// Instantiate a new client that uses the write key passed as first argument to +// send messages to the backend. +// The client is created with the default configuration. +func New(writeKey string) Client { + // Here we can ignore the error because the default config is always valid. + c, _ := NewWithConfig(writeKey, Config{}) return c } -// Alias buffers an "alias" message. -func (c *Client) Alias(msg *Alias) error { - if msg.UserId == "" { - return errors.New("You must pass a 'userId'.") +// Instantiate a new client that uses the write key and configuration passed as +// arguments to send messages to the backend. +// The function will return an error if the configuration contained impossible +// values (like a negative flush interval for example). +// When the function returns an error the returned client will always be nil. +func NewWithConfig(writeKey string, config Config) (cli Client, err error) { + if err = config.validate(); err != nil { + return } - if msg.PreviousId == "" { - return errors.New("You must pass a 'previousId'.") + c := &client{ + Config: makeConfig(config), + key: writeKey, + msgs: make(chan Message, 100), + quit: make(chan struct{}), + shutdown: make(chan struct{}), + http: makeHttpClient(config.Transport), } - msg.Type = "alias" - c.queue(msg) - - return nil -} - -// Page buffers an "page" message. -func (c *Client) Page(msg *Page) error { - if msg.UserId == "" && msg.AnonymousId == "" { - return errors.New("You must pass either an 'anonymousId' or 'userId'.") - } - - msg.Type = "page" - c.queue(msg) - - return nil -} - -// Group buffers an "group" message. -func (c *Client) Group(msg *Group) error { - if msg.GroupId == "" { - return errors.New("You must pass a 'groupId'.") - } - - if msg.UserId == "" && msg.AnonymousId == "" { - return errors.New("You must pass either an 'anonymousId' or 'userId'.") - } - - msg.Type = "group" - c.queue(msg) - - return nil -} - -// Identify buffers an "identify" message. -func (c *Client) Identify(msg *Identify) error { - if msg.UserId == "" && msg.AnonymousId == "" { - return errors.New("You must pass either an 'anonymousId' or 'userId'.") - } - - msg.Type = "identify" - c.queue(msg) - - return nil -} - -// Track buffers an "track" message. -func (c *Client) Track(msg *Track) error { - if msg.Event == "" { - return errors.New("You must pass 'event'.") - } - - if msg.UserId == "" && msg.AnonymousId == "" { - return errors.New("You must pass either an 'anonymousId' or 'userId'.") - } - - msg.Type = "track" - c.queue(msg) - - return nil -} - -func (c *Client) startLoop() { go c.loop() + + cli = c + return } -// Queue message. -func (c *Client) queue(msg message) { - c.once.Do(c.startLoop) - msg.setMessageId(c.uid()) - msg.setTimestamp(timestamp(c.now())) +func makeHttpClient(transport http.RoundTripper) http.Client { + httpClient := http.Client{ + Transport: transport, + } + if supportsTimeout(transport) { + httpClient.Timeout = 10 * time.Second + } + return httpClient +} + +func (c *client) Enqueue(msg Message) (err error) { + if err = msg.validate(); err != nil { + return + } + + var id = c.uid() + var ts = c.now() + + switch m := msg.(type) { + case Alias: + m.Type = "alias" + m.MessageId = makeMessageId(m.MessageId, id) + m.Timestamp = makeTimestamp(m.Timestamp, ts) + msg = m + + case Group: + m.Type = "group" + m.MessageId = makeMessageId(m.MessageId, id) + m.Timestamp = makeTimestamp(m.Timestamp, ts) + msg = m + + case Identify: + m.Type = "identify" + m.MessageId = makeMessageId(m.MessageId, id) + m.Timestamp = makeTimestamp(m.Timestamp, ts) + msg = m + + case Page: + m.Type = "page" + m.MessageId = makeMessageId(m.MessageId, id) + m.Timestamp = makeTimestamp(m.Timestamp, ts) + msg = m + + case Screen: + m.Type = "screen" + m.MessageId = makeMessageId(m.MessageId, id) + m.Timestamp = makeTimestamp(m.Timestamp, ts) + msg = m + + case Track: + m.Type = "track" + m.MessageId = makeMessageId(m.MessageId, id) + m.Timestamp = makeTimestamp(m.Timestamp, ts) + msg = m + } + + defer func() { + // When the `msgs` channel is closed writing to it will trigger a panic. + // To avoid letting the panic propagate to the caller we recover from it + // and instead report that the client has been closed and shouldn't be + // used anymore. + if recover() != nil { + err = ErrClosed + } + }() + c.msgs <- msg + return } // Close and flush metrics. -func (c *Client) Close() error { - c.once.Do(c.startLoop) - c.quit <- struct{}{} - close(c.msgs) +func (c *client) Close() (err error) { + defer func() { + // Always recover, a panic could be raised if `c`.quit was closed which + // means the method was called more than once. + if recover() != nil { + err = ErrClosed + } + }() + close(c.quit) <-c.shutdown - return nil + return } -func (c *Client) sendAsync(msgs []interface{}) { - c.upmtx.Lock() - for c.upcount == 1000 { - c.upcond.Wait() +// Asychronously send a batched requests. +func (c *client) sendAsync(msgs []message, wg *sync.WaitGroup, ex *executor) { + wg.Add(1) + + if !ex.do(func() { + defer wg.Done() + defer func() { + // In case a bug is introduced in the send function that triggers + // a panic, we don't want this to ever crash the application so we + // catch it here and log it instead. + if err := recover(); err != nil { + c.errorf("panic - %s", err) + } + }() + c.send(msgs) + }) { + wg.Done() + c.errorf("sending messages failed - %s", ErrTooManyRequests) + c.notifyFailure(msgs, ErrTooManyRequests) } - c.upcount++ - c.upmtx.Unlock() - c.wg.Add(1) - go func() { - err := c.send(msgs) - if err != nil { - c.logf(err.Error()) - } - c.upmtx.Lock() - c.upcount-- - c.upcond.Signal() - c.upmtx.Unlock() - c.wg.Done() - }() } // Send batch request. -func (c *Client) send(msgs []interface{}) error { - if len(msgs) == 0 { - return nil - } +func (c *client) send(msgs []message) { + const attempts = 10 - batch := new(Batch) - batch.Messages = msgs - batch.MessageId = c.uid() - batch.SentAt = timestamp(c.now()) - batch.Context = DefaultContext + b, err := json.Marshal(batch{ + MessageId: c.uid(), + SentAt: c.now(), + Messages: msgs, + Context: c.DefaultContext, + }) - b, err := json.Marshal(batch) if err != nil { - return fmt.Errorf("error marshalling msgs: %s", err) + c.errorf("marshalling messages - %s", err) + c.notifyFailure(msgs, err) + return } - for i := 0; i < 10; i++ { + for i := 0; i != attempts; i++ { if err = c.upload(b); err == nil { - return nil + c.notifySuccess(msgs) + return + } + + // Wait for either a retry timeout or the client to be closed. + select { + case <-time.After(c.RetryAfter(i)): + case <-c.quit: + c.errorf("%d messages dropped because they failed to be sent and the client was closed", len(msgs)) + c.notifyFailure(msgs, err) + return } - Backo.Sleep(i) } - return err + c.errorf("%d messages dropped because they failed to be sent after %d attempts", len(msgs), attempts) + c.notifyFailure(msgs, err) } // Upload serialized batch message. -func (c *Client) upload(b []byte) error { +func (c *client) upload(b []byte) error { url := c.Endpoint + "/v1/batch" req, err := http.NewRequest("POST", url, bytes.NewReader(b)) if err != nil { - return fmt.Errorf("error creating request: %s", err) + c.errorf("creating request - %s", err) + return err } req.Header.Add("User-Agent", "analytics-go (version: "+Version+")") @@ -310,98 +251,138 @@ func (c *Client) upload(b []byte) error { req.Header.Add("Content-Length", string(len(b))) req.SetBasicAuth(c.key, "") - res, err := c.Client.Do(req) + res, err := c.http.Do(req) + if err != nil { - return fmt.Errorf("error sending request: %s", err) + c.errorf("sending request - %s", err) + return err } + defer res.Body.Close() + return c.report(res) +} - if res.StatusCode < 400 { - c.verbose("response %s", res.Status) - return nil +// Report on response body. +func (c *client) report(res *http.Response) (err error) { + var body []byte + + if res.StatusCode < 300 { + c.debugf("response %s", res.Status) + return } - body, err := ioutil.ReadAll(res.Body) - if err != nil { - return fmt.Errorf("error reading response body: %s", err) + if body, err = ioutil.ReadAll(res.Body); err != nil { + c.errorf("response %d %s - %s", res.StatusCode, res.Status, err) + return } - return fmt.Errorf("response %s: %d – %s", res.Status, res.StatusCode, string(body)) + c.logf("response %d %s – %s", res.StatusCode, res.Status, string(body)) + return fmt.Errorf("%d %s", res.StatusCode, res.Status) } // Batch loop. -func (c *Client) loop() { - var msgs []interface{} +func (c *client) loop() { + defer close(c.shutdown) + + wg := &sync.WaitGroup{} + defer wg.Wait() + tick := time.NewTicker(c.Interval) + defer tick.Stop() + + ex := newExecutor(c.maxConcurrentRequests) + defer ex.close() + + mq := messageQueue{ + maxBatchSize: c.BatchSize, + maxBatchBytes: c.maxBatchBytes(), + } for { select { case msg := <-c.msgs: - c.verbose("buffer (%d/%d) %v", len(msgs), c.Size, msg) - msgs = append(msgs, msg) - if len(msgs) == c.Size { - c.verbose("exceeded %d messages – flushing", c.Size) - c.sendAsync(msgs) - msgs = make([]interface{}, 0, c.Size) - } + c.push(&mq, msg, wg, ex) + case <-tick.C: - if len(msgs) > 0 { - c.verbose("interval reached - flushing %d", len(msgs)) - c.sendAsync(msgs) - msgs = make([]interface{}, 0, c.Size) - } else { - c.verbose("interval reached – nothing to send") - } + c.flush(&mq, wg, ex) + case <-c.quit: - tick.Stop() - c.verbose("exit requested – draining msgs") - // drain the msg channel. + c.debugf("exit requested – draining messages") + + // Drain the msg channel, we have to close it first so no more + // messages can be pushed and otherwise the loop would never end. + close(c.msgs) for msg := range c.msgs { - c.verbose("buffer (%d/%d) %v", len(msgs), c.Size, msg) - msgs = append(msgs, msg) + c.push(&mq, msg, wg, ex) } - c.verbose("exit requested – flushing %d", len(msgs)) - c.sendAsync(msgs) - c.wg.Wait() - c.verbose("exit") - c.shutdown <- struct{}{} + + c.flush(&mq, wg, ex) + c.debugf("exit") return } } } -// Verbose log. -func (c *Client) verbose(msg string, args ...interface{}) { +func (c *client) push(q *messageQueue, m Message, wg *sync.WaitGroup, ex *executor) { + var msg message + var err error + + if msg, err = makeMessage(m, maxMessageBytes); err != nil { + c.errorf("%s - %v", err, m) + c.notifyFailure([]message{{m, nil}}, err) + return + } + + c.debugf("buffer (%d/%d) %v", len(q.pending), c.BatchSize, m) + + if msgs := q.push(msg); msgs != nil { + c.debugf("exceeded messages batch limit with batch of %d messages – flushing", len(msgs)) + c.sendAsync(msgs, wg, ex) + } +} + +func (c *client) flush(q *messageQueue, wg *sync.WaitGroup, ex *executor) { + if msgs := q.flush(); msgs != nil { + c.debugf("flushing %d messages", len(msgs)) + c.sendAsync(msgs, wg, ex) + } +} + +func (c *client) debugf(format string, args ...interface{}) { if c.Verbose { - c.Logger.Printf(msg, args...) + c.logf(format, args...) } } -// Unconditional log. -func (c *Client) logf(msg string, args ...interface{}) { - c.Logger.Printf(msg, args...) +func (c *client) logf(format string, args ...interface{}) { + c.Logger.Logf(format, args...) } -// Set message timestamp if one is not already set. -func (m *Message) setTimestamp(s string) { - if m.Timestamp == "" { - m.Timestamp = s +func (c *client) errorf(format string, args ...interface{}) { + c.Logger.Errorf(format, args...) +} + +func (c *client) maxBatchBytes() int { + b, _ := json.Marshal(batch{ + MessageId: c.uid(), + SentAt: c.now(), + Context: c.DefaultContext, + }) + return maxBatchBytes - len(b) +} + +func (c *client) notifySuccess(msgs []message) { + if c.Callback != nil { + for _, m := range msgs { + c.Callback.Success(m.msg) + } } } -// Set message id. -func (m *Message) setMessageId(s string) { - if m.MessageId == "" { - m.MessageId = s +func (c *client) notifyFailure(msgs []message, err error) { + if c.Callback != nil { + for _, m := range msgs { + c.Callback.Failure(m.msg, err) + } } } - -// Return formatted timestamp. -func timestamp(t time.Time) string { - return strftime.Format("%Y-%m-%dT%H:%M:%S%z", t) -} - -// Return uuid string. -func uid() string { - return uuid.NewRandom().String() -} diff --git a/vendor/github.com/segmentio/analytics-go/circle.yml b/vendor/github.com/segmentio/analytics-go/circle.yml deleted file mode 100644 index 11733e196f..0000000000 --- a/vendor/github.com/segmentio/analytics-go/circle.yml +++ /dev/null @@ -1,18 +0,0 @@ -machine: - environment: - IMPORT_PATH: "github.com/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME" - -dependencies: - pre: - - go get github.com/tools/godep - - override: - - mkdir -p "$GOPATH/src/$IMPORT_PATH" - - rsync -azC --delete ./ "$GOPATH/src/$IMPORT_PATH/" - -test: - pre: - - make vet - - override: - - make test diff --git a/vendor/github.com/segmentio/analytics-go/config.go b/vendor/github.com/segmentio/analytics-go/config.go new file mode 100644 index 0000000000..c167757304 --- /dev/null +++ b/vendor/github.com/segmentio/analytics-go/config.go @@ -0,0 +1,173 @@ +package analytics + +import ( + "net/http" + "time" + + "github.com/segmentio/backo-go" + "github.com/xtgo/uuid" +) + +// Instances of this type carry the different configuration options that may +// be set when instantiating a client. +// +// Each field's zero-value is either meaningful or interpreted as using the +// default value defined by the library. +type Config struct { + + // The endpoint to which the client connect and send their messages, set to + // `DefaultEndpoint` by default. + Endpoint string + + // The flushing interval of the client. Messages will be sent when they've + // been queued up to the maximum batch size or when the flushing interval + // timer triggers. + Interval time.Duration + + // The HTTP transport used by the client, this allows an application to + // redefine how requests are being sent at the HTTP level (for example, + // to change the connection pooling policy). + // If none is specified the client uses `http.DefaultTransport`. + Transport http.RoundTripper + + // The logger used by the client to output info or error messages when that + // are generated by background operations. + // If none is specified the client uses a standard logger that outputs to + // `os.Stderr`. + Logger Logger + + // The callback object that will be used by the client to notify the + // application when messages sends to the backend API succeeded or failed. + Callback Callback + + // The maximum number of messages that will be sent in one API call. + // Messages will be sent when they've been queued up to the maximum batch + // size or when the flushing interval timer triggers. + // Note that the API will still enforce a 500KB limit on each HTTP request + // which is independent from the number of embedded messages. + BatchSize int + + // When set to true the client will send more frequent and detailed messages + // to its logger. + Verbose bool + + // The default context set on each message sent by the client. + DefaultContext *Context + + // The retry policy used by the client to resend requests that have failed. + // The function is called with how many times the operation has been retried + // and is expected to return how long the client should wait before trying + // again. + // If not set the client will fallback to use a default retry policy. + RetryAfter func(int) time.Duration + + // A function called by the client to generate unique message identifiers. + // The client uses a UUID generator if none is provided. + // This field is not exported and only exposed internally to let unit tests + // mock the id generation. + uid func() string + + // A function called by the client to get the current time, `time.Now` is + // used by default. + // This field is not exported and only exposed internally to let unit tests + // mock the current time. + now func() time.Time + + // The maximum number of goroutines that will be spawned by a client to send + // requests to the backend API. + // This field is not exported and only exposed internally to let unit tests + // mock the current time. + maxConcurrentRequests int +} + +// This constant sets the default endpoint to which client instances send +// messages if none was explictly set. +const DefaultEndpoint = "https://api.segment.io" + +// This constant sets the default flush interval used by client instances if +// none was explicitly set. +const DefaultInterval = 5 * time.Second + +// This constant sets the default batch size used by client instances if none +// was explicitly set. +const DefaultBatchSize = 250 + +// Verifies that fields that don't have zero-values are set to valid values, +// returns an error describing the problem if a field was invalid. +func (c *Config) validate() error { + if c.Interval < 0 { + return ConfigError{ + Reason: "negative time intervals are not supported", + Field: "Interval", + Value: c.Interval, + } + } + + if c.BatchSize < 0 { + return ConfigError{ + Reason: "negative batch sizes are not supported", + Field: "BatchSize", + Value: c.BatchSize, + } + } + + return nil +} + +// Given a config object as argument the function will set all zero-values to +// their defaults and return the modified object. +func makeConfig(c Config) Config { + if len(c.Endpoint) == 0 { + c.Endpoint = DefaultEndpoint + } + + if c.Interval == 0 { + c.Interval = DefaultInterval + } + + if c.Transport == nil { + c.Transport = http.DefaultTransport + } + + if c.Logger == nil { + c.Logger = newDefaultLogger() + } + + if c.BatchSize == 0 { + c.BatchSize = DefaultBatchSize + } + + if c.DefaultContext == nil { + c.DefaultContext = &Context{} + } + + if c.RetryAfter == nil { + c.RetryAfter = backo.DefaultBacko().Duration + } + + if c.uid == nil { + c.uid = uid + } + + if c.now == nil { + c.now = time.Now + } + + if c.maxConcurrentRequests == 0 { + c.maxConcurrentRequests = 1000 + } + + // We always overwrite the 'library' field of the default context set on the + // client because we want this information to be accurate. + c.DefaultContext.Library = LibraryInfo{ + Name: "analytics-go", + Version: Version, + } + return c +} + +// This function returns a string representation of a UUID, it's the default +// function used for generating unique IDs. +func uid() string { + return uuid.NewRandom().String() +} diff --git a/vendor/github.com/segmentio/analytics-go/context.go b/vendor/github.com/segmentio/analytics-go/context.go new file mode 100644 index 0000000000..7a30c1b98d --- /dev/null +++ b/vendor/github.com/segmentio/analytics-go/context.go @@ -0,0 +1,148 @@ +package analytics + +import ( + "encoding/json" + "net" + "reflect" +) + +// This type provides the representation of the `context` object as defined in +// https://segment.com/docs/spec/common/#context +type Context struct { + App AppInfo `json:"app,omitempty"` + Campaign CampaignInfo `json:"campaign,omitempty"` + Device DeviceInfo `json:"device,omitempty"` + Library LibraryInfo `json:"library,omitempty"` + Location LocationInfo `json:"location,omitempty"` + Network NetworkInfo `json:"network,omitempty"` + OS OSInfo `json:"os,omitempty"` + Page PageInfo `json:"page,omitempty"` + Referrer ReferrerInfo `json:"referrer,omitempty"` + Screen ScreenInfo `json:"screen,omitempty"` + IP net.IP `json:"ip,omitempty"` + Locale string `json:"locale,omitempty"` + Timezone string `json:"timezone,omitempty"` + UserAgent string `json:"userAgent,omitempty"` + Traits Traits `json:"traits,omitempty"` + + // This map is used to allow extensions to the context specifications that + // may not be documented or could be introduced in the future. + // The fields of this map are inlined in the serialized context object, + // there is no actual "extra" field in the JSON representation. + Extra map[string]interface{} `json:"-"` +} + +// This type provides the representation of the `context.app` object as defined +// in https://segment.com/docs/spec/common/#context +type AppInfo struct { + Name string `json:"name,omitempty"` + Version string `json:"version,omitempty"` + Build string `json:"build,omitempty"` + Namespace string `json:"namespace,omitempty"` +} + +// This type provides the representation of the `context.campaign` object as +// defined in https://segment.com/docs/spec/common/#context +type CampaignInfo struct { + Name string `json:"name,omitempty"` + Source string `json:"source,omitempty"` + Medium string `json:"medium,omitempty"` + Term string `json:"term,omitempty"` + Content string `json:"content,omitempty"` +} + +// This type provides the representation of the `context.device` object as +// defined in https://segment.com/docs/spec/common/#context +type DeviceInfo struct { + Id string `json:"id,omitempty"` + Manufacturer string `json:"manufacturer,omitempty"` + Model string `json:"model,omitempty"` + Name string `json:"name,omitempty"` + Type string `json:"type,omitempty"` + Version string `json:"version,omitempty"` + AdvertisingID string `json:"advertisingId,omitempty"` +} + +// This type provides the representation of the `context.library` object as +// defined in https://segment.com/docs/spec/common/#context +type LibraryInfo struct { + Name string `json:"name,omitempty"` + Version string `json:"version,omitempty"` +} + +// This type provides the representation of the `context.location` object as +// defined in https://segment.com/docs/spec/common/#context +type LocationInfo struct { + City string `json:"city,omitempty"` + Country string `json:"country,omitempty"` + Region string `json:"region,omitempty"` + Latitude float64 `json:"latitude,omitempty"` + Longitude float64 `json:"longitude,omitempty"` + Speed float64 `json:"speed,omitempty"` +} + +// This type provides the representation of the `context.network` object as +// defined in https://segment.com/docs/spec/common/#context +type NetworkInfo struct { + Bluetooth bool `json:"bluetooth,omitempty"` + Cellular bool `json:"cellular,omitempty"` + WIFI bool `json:"wifi,omitempty"` + Carrier string `json:"carrier,omitempty"` +} + +// This type provides the representation of the `context.os` object as defined +// in https://segment.com/docs/spec/common/#context +type OSInfo struct { + Name string `json:"name,omitempty"` + Version string `json:"version,omitempty"` +} + +// This type provides the representation of the `context.page` object as +// defined in https://segment.com/docs/spec/common/#context +type PageInfo struct { + Hash string `json:"hash,omitempty"` + Path string `json:"path,omitempty"` + Referrer string `json:"referrer,omitempty"` + Search string `json:"search,omitempty"` + Title string `json:"title,omitempty"` + URL string `json:"url,omitempty"` +} + +// This type provides the representation of the `context.referrer` object as +// defined in https://segment.com/docs/spec/common/#context +type ReferrerInfo struct { + Type string `json:"type,omitempty"` + Name string `json:"name,omitempty"` + URL string `json:"url,omitempty"` + Link string `json:"link,omitempty"` +} + +// This type provides the representation of the `context.screen` object as +// defined in https://segment.com/docs/spec/common/#context +type ScreenInfo struct { + Density int `json:"density,omitempty"` + Width int `json:"width,omitempty"` + Height int `json:"height,omitempty"` +} + +// Satisfy the `json.Marshaler` interface. We have to flatten out the `Extra` +// field but the standard json package doesn't support it yet. +// Implementing this interface allows us to override the default marshaling of +// the context object and to the inlining ourselves. +// +// Related discussion: https://github.com/golang/go/issues/6213 +func (ctx Context) MarshalJSON() ([]byte, error) { + v := reflect.ValueOf(ctx) + n := v.NumField() + m := make(map[string]interface{}, n+len(ctx.Extra)) + + // Copy the `Extra` map into the map representation of the context, it is + // important to do this operation before going through the actual struct + // fields so the latter take precendence and override duplicated values + // that would be set in the extensions. + for name, value := range ctx.Extra { + m[name] = value + } + + return json.Marshal(structToMap(v, m)) +} diff --git a/vendor/github.com/segmentio/analytics-go/error.go b/vendor/github.com/segmentio/analytics-go/error.go new file mode 100644 index 0000000000..d5503864e9 --- /dev/null +++ b/vendor/github.com/segmentio/analytics-go/error.go @@ -0,0 +1,60 @@ +package analytics + +import ( + "errors" + "fmt" +) + +// Returned by the `NewWithConfig` function when the one of the configuration +// fields was set to an impossible value (like a negative duration). +type ConfigError struct { + + // A human-readable message explaining why the configuration field's value + // is invalid. + Reason string + + // The name of the configuration field that was carrying an invalid value. + Field string + + // The value of the configuration field that caused the error. + Value interface{} +} + +func (e ConfigError) Error() string { + return fmt.Sprintf("analytics.NewWithConfig: %s (analytics.Config.%s: %#v)", e.Reason, e.Field, e.Value) +} + +// Instances of this type are used to represent errors returned when a field was +// no initialize properly in a structure passed as argument to one of the +// functions of this package. +type FieldError struct { + + // The human-readable representation of the type of structure that wasn't + // initialized properly. + Type string + + // The name of the field that wasn't properly initialized. + Name string + + // The value of the field that wasn't properly initialized. + Value interface{} +} + +func (e FieldError) Error() string { + return fmt.Sprintf("%s.%s: invalid field value: %#v", e.Type, e.Name, e.Value) +} + +var ( + // This error is returned by methods of the `Client` interface when they are + // called after the client was already closed. + ErrClosed = errors.New("the client was already closed") + + // This error is used to notify the application that too many requests are + // already being sent and no more messages can be accepted. + ErrTooManyRequests = errors.New("too many requests are already in-flight") + + // This error is used to notify the client callbacks that a message send + // failed because the JSON representation of a message exceeded the upper + // limit. + ErrMessageTooBig = errors.New("the message exceeds the maximum allowed size") +) diff --git a/vendor/github.com/segmentio/analytics-go/executor.go b/vendor/github.com/segmentio/analytics-go/executor.go new file mode 100644 index 0000000000..405ee98eef --- /dev/null +++ b/vendor/github.com/segmentio/analytics-go/executor.go @@ -0,0 +1,53 @@ +package analytics + +import "sync" + +type executor struct { + queue chan func() + mutex sync.Mutex + size int + cap int +} + +func newExecutor(cap int) *executor { + e := &executor{ + queue: make(chan func(), 1), + cap: cap, + } + go e.loop() + return e +} + +func (e *executor) do(task func()) (ok bool) { + e.mutex.Lock() + + if e.size != e.cap { + e.queue <- task + e.size++ + ok = true + } + + e.mutex.Unlock() + return +} + +func (e *executor) close() { + close(e.queue) +} + +func (e *executor) loop() { + for task := range e.queue { + go e.run(task) + } +} + +func (e *executor) run(task func()) { + defer e.done() + task() +} + +func (e *executor) done() { + e.mutex.Lock() + e.size-- + e.mutex.Unlock() +} diff --git a/vendor/github.com/segmentio/analytics-go/group.go b/vendor/github.com/segmentio/analytics-go/group.go new file mode 100644 index 0000000000..e8b7af6b8f --- /dev/null +++ b/vendor/github.com/segmentio/analytics-go/group.go @@ -0,0 +1,40 @@ +package analytics + +import "time" + +// This type represents object sent in a group call as described in +// https://segment.com/docs/libraries/http/#group +type Group struct { + // This field is exported for serialization purposes and shouldn't be set by + // the application, its value is always overwritten by the library. + Type string `json:"type,omitempty"` + + MessageId string `json:"messageId,omitempty"` + AnonymousId string `json:"anonymousId,omitempty"` + UserId string `json:"userId,omitempty"` + GroupId string `json:"groupId"` + Timestamp time.Time `json:"timestamp,omitempty"` + Context *Context `json:"context,omitempty"` + Traits Traits `json:"traits,omitempty"` + Integrations Integrations `json:"integrations,omitempty"` +} + +func (msg Group) validate() error { + if len(msg.GroupId) == 0 { + return FieldError{ + Type: "analytics.Group", + Name: "GroupId", + Value: msg.GroupId, + } + } + + if len(msg.UserId) == 0 && len(msg.AnonymousId) == 0 { + return FieldError{ + Type: "analytics.Group", + Name: "UserId", + Value: msg.UserId, + } + } + + return nil +} diff --git a/vendor/github.com/segmentio/analytics-go/identify.go b/vendor/github.com/segmentio/analytics-go/identify.go new file mode 100644 index 0000000000..96cbda0ec7 --- /dev/null +++ b/vendor/github.com/segmentio/analytics-go/identify.go @@ -0,0 +1,31 @@ +package analytics + +import "time" + +// This type represents object sent in an identify call as described in +// https://segment.com/docs/libraries/http/#identify +type Identify struct { + // This field is exported for serialization purposes and shouldn't be set by + // the application, its value is always overwritten by the library. + Type string `json:"type,omitempty"` + + MessageId string `json:"messageId,omitempty"` + AnonymousId string `json:"anonymousId,omitempty"` + UserId string `json:"userId,omitempty"` + Timestamp time.Time `json:"timestamp,omitempty"` + Context *Context `json:"context,omitempty"` + Traits Traits `json:"traits,omitempty"` + Integrations Integrations `json:"integrations,omitempty"` +} + +func (msg Identify) validate() error { + if len(msg.UserId) == 0 && len(msg.AnonymousId) == 0 { + return FieldError{ + Type: "analytics.Identify", + Name: "UserId", + Value: msg.UserId, + } + } + + return nil +} diff --git a/vendor/github.com/segmentio/analytics-go/integrations.go b/vendor/github.com/segmentio/analytics-go/integrations.go new file mode 100644 index 0000000000..407b4912bc --- /dev/null +++ b/vendor/github.com/segmentio/analytics-go/integrations.go @@ -0,0 +1,44 @@ +package analytics + +// This type is used to represent integrations in messages that support it. +// It is a free-form where values are most often booleans that enable or +// disable integrations. +// Here's a quick example of how this type is meant to be used: +// +// analytics.Track{ +// UserId: "0123456789", +// Integrations: analytics.NewIntegrations() +// .EnableAll() +// .Disable("Salesforce") +// .Disable("Marketo"), +// } +// +// The specifications can be found at https://segment.com/docs/spec/common/#integrations +type Integrations map[string]interface{} + +func NewIntegrations() Integrations { + return make(Integrations, 10) +} + +func (i Integrations) EnableAll() Integrations { + return i.Enable("all") +} + +func (i Integrations) DisableAll() Integrations { + return i.Disable("all") +} + +func (i Integrations) Enable(name string) Integrations { + return i.Set(name, true) +} + +func (i Integrations) Disable(name string) Integrations { + return i.Set(name, false) +} + +// Sets an integration named by the first argument to the specified value, any +// value other than `false` will be interpreted as enabling the integration. +func (i Integrations) Set(name string, value interface{}) Integrations { + i[name] = value + return i +} diff --git a/vendor/github.com/segmentio/analytics-go/json.go b/vendor/github.com/segmentio/analytics-go/json.go new file mode 100644 index 0000000000..cd3b1752ef --- /dev/null +++ b/vendor/github.com/segmentio/analytics-go/json.go @@ -0,0 +1,87 @@ +package analytics + +import ( + "reflect" + "strings" +) + +// Imitate what what the JSON package would do when serializing a struct value, +// the only difference is we we don't serialize zero-value struct fields as well. +// Note that this function doesn't recursively convert structures to maps, only +// the value passed as argument is transformed. +func structToMap(v reflect.Value, m map[string]interface{}) map[string]interface{} { + t := v.Type() + n := t.NumField() + + if m == nil { + m = make(map[string]interface{}, n) + } + + for i := 0; i != n; i++ { + field := t.Field(i) + value := v.Field(i) + name, omitempty := parseJsonTag(field.Tag.Get("json"), field.Name) + + if name != "-" && !(omitempty && isZeroValue(value)) { + m[name] = value.Interface() + } + } + + return m +} + +// Parses a JSON tag the way the json package would do it, returing the expected +// name of the field once serialized and if empty values should be omitted. +func parseJsonTag(tag string, defName string) (name string, omitempty bool) { + args := strings.Split(tag, ",") + + if len(args) == 0 || len(args[0]) == 0 { + name = defName + } else { + name = args[0] + } + + if len(args) > 1 && args[1] == "omitempty" { + omitempty = true + } + + return +} + +// Checks if the value given as argument is a zero-value, it is based on the +// isEmptyValue function in https://golang.org/src/encoding/json/encode.go +// but also checks struct types recursively. +func isZeroValue(v reflect.Value) bool { + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + + case reflect.Bool: + return !v.Bool() + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + + case reflect.Interface, reflect.Ptr: + return v.IsNil() + + case reflect.Struct: + for i, n := 0, v.NumField(); i != n; i++ { + if !isZeroValue(v.Field(i)) { + return false + } + } + return true + + case reflect.Invalid: + return true + } + + return false +} diff --git a/vendor/github.com/segmentio/analytics-go/logger.go b/vendor/github.com/segmentio/analytics-go/logger.go new file mode 100644 index 0000000000..54190c5f39 --- /dev/null +++ b/vendor/github.com/segmentio/analytics-go/logger.go @@ -0,0 +1,47 @@ +package analytics + +import ( + "log" + "os" +) + +// Instances of types implementing this interface can be used to define where +// the analytics client logs are written. +type Logger interface { + + // Analytics clients call this method to log regular messages about the + // operations they perform. + // Messages logged by this method are usually tagged with an `INFO` log + // level in common logging libraries. + Logf(format string, args ...interface{}) + + // Analytics clients call this method to log errors they encounter while + // sending events to the backend servers. + // Messages logged by this method are usually tagged with an `ERROR` log + // level in common logging libraries. + Errorf(format string, args ...interface{}) +} + +// This function instantiate an object that statisfies the analytics.Logger +// interface and send logs to standard logger passed as argument. +func StdLogger(logger *log.Logger) Logger { + return stdLogger{ + logger: logger, + } +} + +type stdLogger struct { + logger *log.Logger +} + +func (l stdLogger) Logf(format string, args ...interface{}) { + l.logger.Printf("INFO: "+format, args...) +} + +func (l stdLogger) Errorf(format string, args ...interface{}) { + l.logger.Printf("ERROR: "+format, args...) +} + +func newDefaultLogger() Logger { + return StdLogger(log.New(os.Stderr, "segment ", log.LstdFlags)) +} diff --git a/vendor/github.com/segmentio/analytics-go/message.go b/vendor/github.com/segmentio/analytics-go/message.go new file mode 100644 index 0000000000..2e7b2c96a4 --- /dev/null +++ b/vendor/github.com/segmentio/analytics-go/message.go @@ -0,0 +1,128 @@ +package analytics + +import ( + "encoding/json" + "time" +) + +// Values implementing this interface are used by analytics clients to notify +// the application when a message send succeeded or failed. +// +// Callback methods are called by a client's internal goroutines, there are no +// guarantees on which goroutine will trigger the callbacks, the calls can be +// made sequentially or in parallel, the order doesn't depend on the order of +// messages were queued to the client. +// +// Callback methods must return quickly and not cause long blocking operations +// to avoid interferring with the client's internal work flow. +type Callback interface { + + // This method is called for every message that was successfully sent to + // the API. + Success(Message) + + // This method is called for every message that failed to be sent to the + // API and will be discarded by the client. + Failure(Message, error) +} + +// This interface is used to represent analytics objects that can be sent via +// a client. +// +// Types like analytics.Track, analytics.Page, etc... implement this interface +// and therefore can be passed to the analytics.Client.Send method. +type Message interface { + + // Validates the internal structure of the message, the method must return + // nil if the message is valid, or an error describing what went wrong. + validate() error +} + +// Takes a message id as first argument and returns it, unless it's the zero- +// value, in that case the default id passed as second argument is returned. +func makeMessageId(id string, def string) string { + if len(id) == 0 { + return def + } + return id +} + +// Returns the time value passed as first argument, unless it's the zero-value, +// in that case the default value passed as second argument is returned. +func makeTimestamp(t time.Time, def time.Time) time.Time { + if t == (time.Time{}) { + return def + } + return t +} + +// This structure represents objects sent to the /v1/batch endpoint. We don't +// export this type because it's only meant to be used internally to send groups +// of messages in one API call. +type batch struct { + MessageId string `json:"messageId"` + SentAt time.Time `json:"sentAt"` + Messages []message `json:"batch"` + Context *Context `json:"context"` +} + +type message struct { + msg Message + json []byte +} + +func makeMessage(m Message, maxBytes int) (msg message, err error) { + if msg.json, err = json.Marshal(m); err == nil { + if len(msg.json) > maxBytes { + err = ErrMessageTooBig + } else { + msg.msg = m + } + } + return +} + +func (m message) MarshalJSON() ([]byte, error) { + return m.json, nil +} + +func (m message) size() int { + // The `+ 1` is for the comma that sits between each items of a JSON array. + return len(m.json) + 1 +} + +type messageQueue struct { + pending []message + bytes int + maxBatchSize int + maxBatchBytes int +} + +func (q *messageQueue) push(m message) (b []message) { + if (q.bytes + m.size()) > q.maxBatchBytes { + b = q.flush() + } + + if q.pending == nil { + q.pending = make([]message, 0, q.maxBatchSize) + } + + q.pending = append(q.pending, m) + q.bytes += len(m.json) + + if b == nil && len(q.pending) == q.maxBatchSize { + b = q.flush() + } + + return +} + +func (q *messageQueue) flush() (msgs []message) { + msgs, q.pending, q.bytes = q.pending, nil, 0 + return +} + +const ( + maxBatchBytes = 500000 + maxMessageBytes = 15000 +) diff --git a/vendor/github.com/segmentio/analytics-go/page.go b/vendor/github.com/segmentio/analytics-go/page.go new file mode 100644 index 0000000000..f7c64a2a3a --- /dev/null +++ b/vendor/github.com/segmentio/analytics-go/page.go @@ -0,0 +1,32 @@ +package analytics + +import "time" + +// This type represents object sent in a page call as described in +// https://segment.com/docs/libraries/http/#page +type Page struct { + // This field is exported for serialization purposes and shouldn't be set by + // the application, its value is always overwritten by the library. + Type string `json:"type,omitempty"` + + MessageId string `json:"messageId,omitempty"` + AnonymousId string `json:"anonymousId,omitempty"` + UserId string `json:"userId,omitempty"` + Name string `json:"name,omitempty"` + Timestamp time.Time `json:"timestamp,omitempty"` + Context *Context `json:"context,omitempty"` + Properties Properties `json:"properties,omitempty"` + Integrations Integrations `json:"integrations,omitempty"` +} + +func (msg Page) validate() error { + if len(msg.UserId) == 0 && len(msg.AnonymousId) == 0 { + return FieldError{ + Type: "analytics.Page", + Name: "UserId", + Value: msg.UserId, + } + } + + return nil +} diff --git a/vendor/github.com/segmentio/analytics-go/properties.go b/vendor/github.com/segmentio/analytics-go/properties.go new file mode 100644 index 0000000000..8b218aebdc --- /dev/null +++ b/vendor/github.com/segmentio/analytics-go/properties.go @@ -0,0 +1,117 @@ +package analytics + +// This type is used to represent properties in messages that support it. +// It is a free-form object so the application can set any value it sees fit but +// a few helper method are defined to make it easier to instantiate properties with +// common fields. +// Here's a quick example of how this type is meant to be used: +// +// analytics.Page{ +// UserId: "0123456789", +// Properties: analytics.NewProperties() +// .SetRevenue(10.0) +// .SetCurrency("USD"), +// } +// +type Properties map[string]interface{} + +func NewProperties() Properties { + return make(Properties, 10) +} + +func (p Properties) SetRevenue(revenue float64) Properties { + return p.Set("revenue", revenue) +} + +func (p Properties) SetCurrency(currency string) Properties { + return p.Set("currency", currency) +} + +func (p Properties) SetValue(value float64) Properties { + return p.Set("value", value) +} + +func (p Properties) SetPath(path string) Properties { + return p.Set("path", path) +} + +func (p Properties) SetReferrer(referrer string) Properties { + return p.Set("referrer", referrer) +} + +func (p Properties) SetTitle(title string) Properties { + return p.Set("title", title) +} + +func (p Properties) SetURL(url string) Properties { + return p.Set("url", url) +} + +func (p Properties) SetName(name string) Properties { + return p.Set("name", name) +} + +func (p Properties) SetCategory(category string) Properties { + return p.Set("category", category) +} + +func (p Properties) SetSKU(sku string) Properties { + return p.Set("sku", sku) +} + +func (p Properties) SetPrice(price float64) Properties { + return p.Set("price", price) +} + +func (p Properties) SetProductId(id string) Properties { + return p.Set("id", id) +} + +func (p Properties) SetOrderId(id string) Properties { + return p.Set("orderId", id) +} + +func (p Properties) SetTotal(total float64) Properties { + return p.Set("total", total) +} + +func (p Properties) SetSubtotal(subtotal float64) Properties { + return p.Set("subtotal", subtotal) +} + +func (p Properties) SetShipping(shipping float64) Properties { + return p.Set("shipping", shipping) +} + +func (p Properties) SetTax(tax float64) Properties { + return p.Set("tax", tax) +} + +func (p Properties) SetDiscount(discount float64) Properties { + return p.Set("discount", discount) +} + +func (p Properties) SetCoupon(coupon string) Properties { + return p.Set("coupon", coupon) +} + +func (p Properties) SetProducts(products ...Product) Properties { + return p.Set("products", products) +} + +func (p Properties) SetRepeat(repeat bool) Properties { + return p.Set("repeat", repeat) +} + +func (p Properties) Set(name string, value interface{}) Properties { + p[name] = value + return p +} + +// This type represents products in the E-commerce API. +type Product struct { + ID string `json:"id,omitempty"` + SKU string `json:"sky,omitempty"` + Name string `json:"name,omitempty"` + Price float64 `json:"price"` +} diff --git a/vendor/github.com/segmentio/analytics-go/screen.go b/vendor/github.com/segmentio/analytics-go/screen.go new file mode 100644 index 0000000000..9e9849eb00 --- /dev/null +++ b/vendor/github.com/segmentio/analytics-go/screen.go @@ -0,0 +1,32 @@ +package analytics + +import "time" + +// This type represents object sent in a screen call as described in +// https://segment.com/docs/libraries/http/#screen +type Screen struct { + // This field is exported for serialization purposes and shouldn't be set by + // the application, its value is always overwritten by the library. + Type string `json:"type,omitempty"` + + MessageId string `json:"messageId,omitempty"` + AnonymousId string `json:"anonymousId,omitempty"` + UserId string `json:"userId,omitempty"` + Name string `json:"name,omitempty"` + Timestamp time.Time `json:"timestamp,omitempty"` + Context *Context `json:"context,omitempty"` + Properties Properties `json:"properties,omitempty"` + Integrations Integrations `json:"integrations,omitempty"` +} + +func (msg Screen) validate() error { + if len(msg.UserId) == 0 && len(msg.AnonymousId) == 0 { + return FieldError{ + Type: "analytics.Screen", + Name: "UserId", + Value: msg.UserId, + } + } + + return nil +} diff --git a/vendor/github.com/segmentio/analytics-go/timeout_15.go b/vendor/github.com/segmentio/analytics-go/timeout_15.go new file mode 100644 index 0000000000..12b963eddb --- /dev/null +++ b/vendor/github.com/segmentio/analytics-go/timeout_15.go @@ -0,0 +1,16 @@ +// +build !go1.6 + +package analytics + +import "net/http" + +// http clients on versions of go before 1.6 only support timeout if the +// transport implements the `CancelRequest` method. +func supportsTimeout(transport http.RoundTripper) bool { + _, ok := transport.(requestCanceler) + return ok +} + +type requestCanceler interface { + CancelRequest(*http.Request) +} diff --git a/vendor/github.com/segmentio/analytics-go/timeout_16.go b/vendor/github.com/segmentio/analytics-go/timeout_16.go new file mode 100644 index 0000000000..1115cafb77 --- /dev/null +++ b/vendor/github.com/segmentio/analytics-go/timeout_16.go @@ -0,0 +1,10 @@ +// +build go1.6 + +package analytics + +import "net/http" + +// http clients on versions of go after 1.6 always support timeout. +func supportsTimeout(transport http.RoundTripper) bool { + return true +} diff --git a/vendor/github.com/segmentio/analytics-go/track.go b/vendor/github.com/segmentio/analytics-go/track.go new file mode 100644 index 0000000000..7d6e40bb1f --- /dev/null +++ b/vendor/github.com/segmentio/analytics-go/track.go @@ -0,0 +1,40 @@ +package analytics + +import "time" + +// This type represents object sent in a track call as described in +// https://segment.com/docs/libraries/http/#track +type Track struct { + // This field is exported for serialization purposes and shouldn't be set by + // the application, its value is always overwritten by the library. + Type string `json:"type,omitempty"` + + MessageId string `json:"messageId,omitempty"` + AnonymousId string `json:"anonymousId,omitempty"` + UserId string `json:"userId,omitempty"` + Event string `json:"event"` + Timestamp time.Time `json:"timestamp,omitempty"` + Context *Context `json:"context,omitempty"` + Properties Properties `json:"properties,omitempty"` + Integrations Integrations `json:"integrations,omitempty"` +} + +func (msg Track) validate() error { + if len(msg.Event) == 0 { + return FieldError{ + Type: "analytics.Track", + Name: "Event", + Value: msg.Event, + } + } + + if len(msg.UserId) == 0 && len(msg.AnonymousId) == 0 { + return FieldError{ + Type: "analytics.Track", + Name: "UserId", + Value: msg.UserId, + } + } + + return nil +} diff --git a/vendor/github.com/segmentio/analytics-go/traits.go b/vendor/github.com/segmentio/analytics-go/traits.go new file mode 100644 index 0000000000..d4e82f07a7 --- /dev/null +++ b/vendor/github.com/segmentio/analytics-go/traits.go @@ -0,0 +1,89 @@ +package analytics + +import "time" + +// This type is used to represent traits in messages that support it. +// It is a free-form object so the application can set any value it sees fit but +// a few helper method are defined to make it easier to instantiate traits with +// common fields. +// Here's a quick example of how this type is meant to be used: +// +// analytics.Identify{ +// UserId: "0123456789", +// Traits: analytics.NewTraits() +// .SetFirstName("Luke") +// .SetLastName("Skywalker") +// .Set("Role", "Jedi"), +// } +// +// The specifications can be found at https://segment.com/docs/spec/identify/#traits +type Traits map[string]interface{} + +func NewTraits() Traits { + return make(Traits, 10) +} + +func (t Traits) SetAddress(address string) Traits { + return t.Set("address", address) +} + +func (t Traits) SetAge(age int) Traits { + return t.Set("age", age) +} + +func (t Traits) SetAvatar(url string) Traits { + return t.Set("avatar", url) +} + +func (t Traits) SetBirthday(date time.Time) Traits { + return t.Set("birthday", date) +} + +func (t Traits) SetCreatedAt(date time.Time) Traits { + return t.Set("createdAt", date) +} + +func (t Traits) SetDescription(desc string) Traits { + return t.Set("description", desc) +} + +func (t Traits) SetEmail(email string) Traits { + return t.Set("email", email) +} + +func (t Traits) SetFirstName(firstName string) Traits { + return t.Set("firstName", firstName) +} + +func (t Traits) SetGender(gender string) Traits { + return t.Set("gender", gender) +} + +func (t Traits) SetLastName(lastName string) Traits { + return t.Set("lastName", lastName) +} + +func (t Traits) SetName(name string) Traits { + return t.Set("name", name) +} + +func (t Traits) SetPhone(phone string) Traits { + return t.Set("phone", phone) +} + +func (t Traits) SetTitle(title string) Traits { + return t.Set("title", title) +} + +func (t Traits) SetUsername(username string) Traits { + return t.Set("username", username) +} + +func (t Traits) SetWebsite(url string) Traits { + return t.Set("website", url) +} + +func (t Traits) Set(field string, value interface{}) Traits { + t[field] = value + return t +} diff --git a/vendor/modules.txt b/vendor/modules.txt index b68e5345f2..4586f8a0ad 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -99,8 +99,6 @@ github.com/icrowley/fake github.com/inconshreveable/mousetrap # github.com/jaytaylor/html2text v0.0.0-20190311042500-a93a6c6ea053 github.com/jaytaylor/html2text -# github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 -github.com/jehiah/go-strftime # github.com/jmoiron/sqlx v1.2.0 github.com/jmoiron/sqlx github.com/jmoiron/sqlx/reflectx @@ -195,7 +193,7 @@ github.com/rwcarlsen/goexif/exif github.com/rwcarlsen/goexif/tiff # github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 github.com/sean-/seed -# github.com/segmentio/analytics-go v2.0.1-0.20160426181448-2d840d861c32+incompatible +# github.com/segmentio/analytics-go v3.0.1+incompatible github.com/segmentio/analytics-go # github.com/segmentio/backo-go v0.0.0-20160424052352-204274ad699c github.com/segmentio/backo-go