Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cmd): add google analytics #3599

Merged
merged 40 commits into from
Dec 1, 2023
Merged
Show file tree
Hide file tree
Changes from 34 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
b42cb73
add googla analytics
Jul 26, 2023
d0f43e4
add doNotTrack env var
Jul 26, 2023
6b5a2c7
add changelog
Jul 26, 2023
80e1da0
add anlytics into a cobra command
Jul 27, 2023
00dcb4d
improve analyticcs
Jul 27, 2023
8257a24
run make format
Jul 27, 2023
878ffb4
fix file check
Jul 27, 2023
d2b8536
fix env var
Jul 27, 2023
ed524fd
Merge branch 'main' into feat/ga-analytics
Pantani Jul 31, 2023
5297f56
fix send metrics in parallel
Aug 1, 2023
e64c8b1
Merge branch 'main' into feat/ga-analytics
Pantani Aug 10, 2023
4a22f7f
move vars to const
Aug 28, 2023
95f5c8c
Update ignite/cmd/ignite/analytics.go
Pantani Aug 28, 2023
02cd167
Update ignite/cmd/ignite/main.go
Pantani Aug 28, 2023
4b59abd
Merge branch 'main' into feat/ga-analytics
Pantani Aug 28, 2023
d741d73
improve analytics msg
Aug 28, 2023
37d3486
Merge branch 'main' into feat/ga-analytics
Pantani Oct 17, 2023
80ed5f6
update ga pkg
Oct 17, 2023
2831465
Merge branch 'main' into feat/ga-analytics
Pantani Oct 18, 2023
7cb728d
Merge branch 'main' into feat/ga-analytics
Pantani Oct 19, 2023
5758000
Merge branch 'main' into feat/ga-analytics
Pantani Oct 19, 2023
23b8e18
Merge branch 'main' into feat/ga-analytics
Pantani Oct 31, 2023
5ccae09
Merge branch 'main' into feat/ga-analytics
Pantani Nov 7, 2023
f90577a
improve analytics init
Nov 7, 2023
0806603
Merge remote-tracking branch 'origin/main' into feat/ga-analytics
Nov 27, 2023
17b9f8c
use http post request instate a libraty
Nov 27, 2023
e1fad03
add GA Client ID
Nov 28, 2023
08bc355
use postform instead newrequest
Nov 28, 2023
458cf15
use set instead literals for url.Values
Nov 28, 2023
5ab6089
add ga4 http metric send
Nov 29, 2023
ac742e8
fix url and values
Nov 29, 2023
39ad038
chore: small improvements gacli (#3789)
julienrbrt Nov 29, 2023
bf6f3c4
remove sensitive data
Nov 30, 2023
19b47e0
remove sensitive data and improve the parameters
Nov 30, 2023
c9e3b0f
refactor: good default http client for ga (#3791)
julienrbrt Nov 30, 2023
f30ba51
remove `Pallinder/go-randomdata` pkg
Nov 30, 2023
ae79f49
set the ignite telemetry endpoint
Nov 30, 2023
caa2d07
remove the url from the pkg and change some var names
Dec 1, 2023
c14c612
move telemetry to the internal package
Dec 1, 2023
bbdd493
use snake case for anon identity file
Dec 1, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- [#3561](https://github.com/ignite/cli/pull/3561) Add GetChainInfo method to plugin system API
- [#3626](https://github.com/ignite/cli/pull/3626) Add logging levels to relayer
- [#3476](https://github.com/ignite/cli/pull/3476) Use `buf.build` binary to code generate from proto files
- [#3599](https://github.com/ignite/cli/pull/3599) Add google analytics
- [#3614](https://github.com/ignite/cli/pull/3614) feat: use DefaultBaseappOptions for app.New method
- [#3536](https://github.com/ignite/cli/pull/3536) Change app.go to v2 and add AppWiring feature
- [#3659](https://github.com/ignite/cli/pull/3659) cosmos-sdk `v0.50.x`
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ require (
github.com/99designs/keyring v1.2.2
github.com/AlecAivazis/survey/v2 v2.3.7
github.com/DATA-DOG/go-sqlmock v1.5.0
github.com/Pallinder/go-randomdata v1.2.0
github.com/blang/semver/v4 v4.0.0
github.com/briandowns/spinner v1.23.0
github.com/bufbuild/buf v1.15.1
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8
github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
github.com/OpenPeeDeeP/depguard/v2 v2.1.0 h1:aQl70G173h/GZYhWf36aE5H0KaujXfVMnn/f1kSDVYY=
github.com/OpenPeeDeeP/depguard/v2 v2.1.0/go.mod h1:PUBgk35fX4i7JDmwzlJwJ+GMe6NfO1723wmJMgPThNQ=
github.com/Pallinder/go-randomdata v1.2.0 h1:DZ41wBchNRb/0GfsePLiSwb0PHZmT67XY00lCDlaYPg=
github.com/Pallinder/go-randomdata v1.2.0/go.mod h1:yHmJgulpD2Nfrm0cR9tI/+oAgRqCQQixsA8HyRZfV9Y=
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg=
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
Expand Down
129 changes: 129 additions & 0 deletions ignite/cmd/ignite/analytics.go
Pantani marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package main

import (
"encoding/json"
"os"
"path/filepath"
"runtime"
"strings"

"github.com/Pallinder/go-randomdata"
"github.com/manifoldco/promptui"

"github.com/ignite/cli/ignite/pkg/gacli"
"github.com/ignite/cli/ignite/version"
)

const (
gaID = "G-<ID>"
gaSecret = "<API_SECRET>"
envDoNotTrack = "DO_NOT_TRACK"
igniteDir = ".ignite"
igniteIdentity = "identity.json"
Pantani marked this conversation as resolved.
Show resolved Hide resolved
)

var gaclient gacli.Client

type (
// metric represents an analytics metric.
metric struct {
// err sets metrics type as an error metric.
err error
// command is the command name.
command string
}

// identity represents an analytics identity file.
identity struct {
Pantani marked this conversation as resolved.
Show resolved Hide resolved
// name represents the username.
Name string `json:"name" yaml:"name"`
// doNotTrack represents the user track choice.
DoNotTrack bool `json:"doNotTrack" yaml:"doNotTrack"`
}
)

func addCmdMetric(m metric) {
envDoNotTrackVar := os.Getenv(envDoNotTrack)
if envDoNotTrackVar == "1" || strings.ToLower(envDoNotTrackVar) == "true" {
return
}

if m.command == "ignite version" {
return
}

dntInfo, err := checkDNT()
if err != nil || dntInfo.DoNotTrack {
return
}

met := gacli.Metric{
OS: runtime.GOOS,
Arch: runtime.GOARCH,
FullCmd: m.command,
SessionId: dntInfo.Name,
Version: version.Version,
}

switch {
case m.err == nil:
met.Status = "success"
case m.err != nil:
met.Status = "error"
met.Error = m.err.Error()
}

cmds := strings.Split(m.command, " ")
met.Cmd = cmds[0]
if len(cmds) > 0 {
met.Cmd = cmds[1]
}

go gaclient.SendMetric(met)
}

func checkDNT() (identity, error) {
home, err := os.UserHomeDir()
if err != nil {
return identity{}, err
}
if err := os.Mkdir(filepath.Join(home, igniteDir), 0o700); err != nil && !os.IsExist(err) {
return identity{}, err
}
identityPath := filepath.Join(home, igniteDir, igniteIdentity)
data, err := os.ReadFile(identityPath)
if err != nil && !os.IsNotExist(err) {
return identity{}, err
}

var i identity
if err := json.Unmarshal(data, &i); err == nil {
return i, nil
}

i.Name = randomdata.SillyName()
Pantani marked this conversation as resolved.
Show resolved Hide resolved
i.DoNotTrack = false

prompt := promptui.Select{
Label: "Ignite collects metrics about command usage. " +
"All data is anonymous and helps to improve Ignite. " +
"Ignite respect the DNT rules (consoledonottrack.com). " +
"Would you agree to share these metrics with us?",
Items: []string{"Yes", "No"},
}
resultID, _, err := prompt.Run()
if err != nil {
return identity{}, err
}

if resultID != 0 {
i.DoNotTrack = true
}

data, err = json.Marshal(&i)
if err != nil {
return i, err
}

return i, os.WriteFile(identityPath, data, 0o700)
}
20 changes: 20 additions & 0 deletions ignite/cmd/ignite/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,20 @@ import (
"errors"
"fmt"
"os"
"strings"

ignitecmd "github.com/ignite/cli/ignite/cmd"
chainconfig "github.com/ignite/cli/ignite/config/chain"
"github.com/ignite/cli/ignite/pkg/clictx"
"github.com/ignite/cli/ignite/pkg/cliui/colors"
"github.com/ignite/cli/ignite/pkg/cliui/icons"
"github.com/ignite/cli/ignite/pkg/gacli"
"github.com/ignite/cli/ignite/pkg/validation"
"github.com/ignite/cli/ignite/pkg/xstrings"
)

func main() {
gaclient = gacli.New(gaID, gaSecret)
os.Exit(run())
}

Expand All @@ -24,6 +27,23 @@ func run() int {
exitCodeOK = 0
exitCodeError = 1
)

defer func() {
if r := recover(); r != nil {
addCmdMetric(metric{
err: fmt.Errorf("%v", r),
command: strings.Join(os.Args, " "),
})
fmt.Println(r)
os.Exit(exitCodeError)
}
}()

if len(os.Args) > 1 {
addCmdMetric(metric{
command: strings.Join(os.Args, " "),
})
}
ctx := clictx.From(context.Background())

cmd, cleanUp, err := ignitecmd.New(ctx)
Expand Down
2 changes: 2 additions & 0 deletions ignite/pkg/gacli/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Package gacli is a client for Google Analytics to send data points for hint-type=event.
package gacli
108 changes: 65 additions & 43 deletions ignite/pkg/gacli/gacli.go
Original file line number Diff line number Diff line change
@@ -1,63 +1,85 @@
// Package gacli is a client for Google Analytics to send data points for hint-type=event.
package gacli

import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/url"
)

const (
endpoint = "https://www.google-analytics.com/collect"
endpoint = "https://www.google-analytics.com/mp/collect?measurement_id=%s&api_secret=%s"
)

// Client is an analytics client.
type Client struct {
id string // Google Analytics ID
}

// New creates a new analytics client for Segment.io with Segment's
// endpoint and access key.
func New(id string) *Client {
return &Client{
id: id,
type (
// Client is an analytics client.
Client struct {
id string // Google Analytics measurement ID.
secret string // Google Analytics API secret.
}
}

// Metric represents a data point.
type Metric struct {
Category string
Action string
Label string
Value string
User string
Version string
}

// Send sends metrics to GA.
func (c *Client) Send(metric Metric) error {
v := url.Values{
"v": {"1"},
"tid": {c.id},
"cid": {metric.User},
"t": {"event"},
"ec": {metric.Category},
"ea": {metric.Action},
"ua": {"Opera/9.80 (Windows NT 6.0) Presto/2.12.388 Version/12.14"},
// Body analytics metrics body.
Body struct {
ClientId string `json:"client_id"`

Check warning on line 22 in ignite/pkg/gacli/gacli.go

View workflow job for this annotation

GitHub Actions / Lint Go code

var-naming: struct field ClientId should be ClientID (revive)
Events []Event `json:"events"`
}
if metric.Label != "" {
v.Set("el", metric.Label)
// Event analytics event.
Event struct {
Name string `json:"name"`
Params Metric `json:"params"`
}
if metric.Value != "" {
v.Set("ev", metric.Value)
// Metric represents a data point.
Metric struct {
Status string `json:"status,omitempty"`
OS string `json:"os,omitempty"`
Arch string `json:"arch,omitempty"`
FullCmd string `json:"full_command,omitempty"`
Cmd string `json:"command,omitempty"`
Error string `json:"error,omitempty"`
Version string `json:"version,omitempty"`
SessionId string `json:"session_id,omitempty"`

Check warning on line 39 in ignite/pkg/gacli/gacli.go

View workflow job for this annotation

GitHub Actions / Lint Go code

var-naming: struct field SessionId should be SessionID (revive)
EngagementTimeMsec string `json:"engagement_time_msec,omitempty"`
}
if metric.Version != "" {
v.Set("an", metric.Version)
v.Set("av", metric.Version)
)

// New creates a new analytics client with
// measure id and secret key.
func New(id, secret string) Client {
return Client{
secret: secret,
id: id,
}
resp, err := http.PostForm(endpoint, v)
}

// Send sends metrics to analytics.
func (c Client) Send(body Body) error {
// encode body
encoded, err := json.Marshal(body)
if err != nil {
return err
}

// Create an HTTP request with the payload
url := fmt.Sprintf(endpoint, c.id, c.secret)
resp, err := http.Post(url, "application/json", bytes.NewBuffer(encoded))
if err != nil {
return fmt.Errorf("error creating HTTP request: %w", err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK &&
resp.StatusCode != http.StatusNoContent {
return fmt.Errorf("Error sending event. Status code: %d\n", resp.StatusCode)

Check warning on line 71 in ignite/pkg/gacli/gacli.go

View workflow job for this annotation

GitHub Actions / Lint Go code

error-strings: error strings should not be capitalized or end with punctuation or a newline (revive)
}
return nil
}

func (c Client) SendMetric(metric Metric) error {
metric.EngagementTimeMsec = "100"
return c.Send(Body{
ClientId: metric.SessionId,
Events: []Event{{
Name: metric.Cmd,
Params: metric,
}},
})
}
Loading