-
Notifications
You must be signed in to change notification settings - Fork 55
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Heap integration [CLI-158] (#302)
* Add Heap integration * Add opt-out mechanism * Fix linter warning * Move comment * Add comment
- Loading branch information
Showing
28 changed files
with
1,251 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
package analytics | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"net/http" | ||
"os" | ||
"runtime" | ||
"strings" | ||
"sync" | ||
"time" | ||
|
||
"github.com/auth0/auth0-cli/internal/buildinfo" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
const ( | ||
eventNamePrefix = "CLI" | ||
analyticsEndpoint = "https://heapanalytics.com/api/track" | ||
appID = "1279799279" | ||
versionKey = "version" | ||
osKey = "os" | ||
archKey = "arch" | ||
) | ||
|
||
type Tracker struct { | ||
wg sync.WaitGroup | ||
} | ||
|
||
type event struct { | ||
App string `json:"app_id"` | ||
ID string `json:"identity"` | ||
Event string `json:"event"` | ||
Timestamp int64 `json:"timestamp"` | ||
Properties map[string]string `json:"properties"` | ||
} | ||
|
||
func NewTracker() *Tracker { | ||
return &Tracker{} | ||
} | ||
|
||
func (t *Tracker) TrackFirstLogin(id string) { | ||
eventName := fmt.Sprintf("%s - Auth0 - First Login", eventNamePrefix) | ||
t.track(eventName, id) | ||
} | ||
|
||
func (t *Tracker) TrackCommandRun(cmd *cobra.Command, id string) { | ||
eventName := generateRunEventName(cmd.CommandPath()) | ||
t.track(eventName, id) | ||
} | ||
|
||
func (t *Tracker) Wait(ctx context.Context) { | ||
ch := make(chan struct{}) | ||
|
||
go func() { | ||
t.wg.Wait() | ||
close(ch) | ||
}() | ||
|
||
select { | ||
case <-ch: // waitgroup is done | ||
return | ||
case <-ctx.Done(): | ||
return | ||
} | ||
} | ||
|
||
func (t *Tracker) track(eventName string, id string) { | ||
if !shouldTrack() { | ||
return | ||
} | ||
|
||
event := newEvent(eventName, id) | ||
|
||
t.wg.Add(1) | ||
go t.sendEvent(event) | ||
} | ||
|
||
func (t *Tracker) sendEvent(event *event) { | ||
jsonEvent, err := json.Marshal(event) | ||
if err != nil { | ||
return | ||
} | ||
|
||
req, err := http.NewRequest("POST", analyticsEndpoint, bytes.NewBuffer(jsonEvent)) | ||
if err != nil { | ||
return | ||
} | ||
|
||
req.Header.Set("Content-Type", "application/json") | ||
|
||
client := &http.Client{} | ||
resp, err := client.Do(req) | ||
if err != nil { | ||
println(err.Error()) | ||
return | ||
} | ||
|
||
// defers execute in LIFO order | ||
defer t.wg.Done() | ||
defer resp.Body.Close() | ||
} | ||
|
||
func newEvent(eventName string, id string) *event { | ||
return &event{ | ||
App: appID, | ||
ID: id, | ||
Event: eventName, | ||
Timestamp: timestamp(), | ||
Properties: map[string]string{ | ||
versionKey: buildinfo.Version, | ||
osKey: runtime.GOOS, | ||
archKey: runtime.GOARCH, | ||
}, | ||
} | ||
} | ||
|
||
func generateRunEventName(command string) string { | ||
return generateEventName(command, "Run") | ||
} | ||
|
||
func generateEventName(command string, action string) string { | ||
commands := strings.Split(command, " ") | ||
|
||
for i := range commands { | ||
commands[i] = strings.Title(commands[i]) | ||
} | ||
|
||
if len(commands) == 1 { // the root command | ||
return fmt.Sprintf("%s - %s - %s", eventNamePrefix, commands[0], action) | ||
} else if len(commands) == 2 { // a top-level command e.g. auth0 apps | ||
return fmt.Sprintf("%s - %s - %s - %s", eventNamePrefix, commands[0], commands[1], action) | ||
} else if len(commands) >= 3 { | ||
return fmt.Sprintf("%s - %s - %s - %s", eventNamePrefix, commands[1], strings.Join(commands[2:], " "), action) | ||
} | ||
|
||
return eventNamePrefix | ||
} | ||
|
||
func shouldTrack() bool { | ||
if os.Getenv("AUTH0_CLI_ANALYTICS") == "false" || buildinfo.Version == "" { // Do not track debug builds | ||
return false | ||
} | ||
|
||
return true | ||
} | ||
|
||
func timestamp() int64 { | ||
t := time.Now() | ||
s := t.Unix() * 1e3 | ||
ms := int64(t.Nanosecond()) / 1e6 | ||
return s + ms | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
package analytics | ||
|
||
import ( | ||
"runtime" | ||
"testing" | ||
"time" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestGenerateEventName(t *testing.T) { | ||
t.Run("generates from root command run", func(t *testing.T) { | ||
want := "CLI - Auth0 - Action" | ||
got := generateEventName("auth0", "Action") | ||
assert.Equal(t, want, got) | ||
}) | ||
|
||
t.Run("generates from top-level command run", func(t *testing.T) { | ||
want := "CLI - Auth0 - Apps - Action" | ||
got := generateEventName("auth0 apps", "Action") | ||
assert.Equal(t, want, got) | ||
}) | ||
|
||
t.Run("generates from subcommand run", func(t *testing.T) { | ||
want := "CLI - Apps - List - Action" | ||
got := generateEventName("auth0 apps list", "Action") | ||
assert.Equal(t, want, got) | ||
}) | ||
|
||
t.Run("generates from deep subcommand run", func(t *testing.T) { | ||
want := "CLI - Apis - Scopes List - Action" | ||
got := generateEventName("auth0 apis scopes list", "Action") | ||
assert.Equal(t, want, got) | ||
}) | ||
} | ||
|
||
func TestGenerateRunEventName(t *testing.T) { | ||
t.Run("generates from root command run", func(t *testing.T) { | ||
want := "CLI - Auth0 - Run" | ||
got := generateRunEventName("auth0") | ||
assert.Equal(t, want, got) | ||
}) | ||
|
||
t.Run("generates from top-level command run", func(t *testing.T) { | ||
want := "CLI - Auth0 - Apps - Run" | ||
got := generateRunEventName("auth0 apps") | ||
assert.Equal(t, want, got) | ||
}) | ||
|
||
t.Run("generates from subcommand run", func(t *testing.T) { | ||
want := "CLI - Apps - List - Run" | ||
got := generateRunEventName("auth0 apps list") | ||
assert.Equal(t, want, got) | ||
}) | ||
|
||
t.Run("generates from deep subcommand run", func(t *testing.T) { | ||
want := "CLI - Apis - Scopes List - Run" | ||
got := generateRunEventName("auth0 apis scopes list") | ||
assert.Equal(t, want, got) | ||
}) | ||
} | ||
|
||
func TestNewEvent(t *testing.T) { | ||
t.Run("creates a new event instance", func(t *testing.T) { | ||
event := newEvent("event", "id") | ||
// Assert that the interval between the event timestamp and now is within 1 second | ||
assert.WithinDuration(t, time.Now(), time.Unix(0, event.Timestamp * int64(1000000)), 1 * time.Second) | ||
assert.Equal(t, event.App, appID) | ||
assert.Equal(t, event.Event, "event") | ||
assert.Equal(t, event.ID, "id") | ||
assert.Equal(t, event.Properties[osKey], runtime.GOOS) | ||
assert.Equal(t, event.Properties[archKey], runtime.GOARCH) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.