Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
ecb3644
update dev wallet
sukantoraymond Jul 19, 2022
0d2fe15
Merge branch 'master' of https://github.com/onflow/flow-cli
sukantoraymond Jul 20, 2022
f7c0955
log command metric
sukantoraymond Jul 21, 2022
a059459
PR edit
sukantoraymond Jul 21, 2022
9409f68
add tracker for commands count
sukantoraymond Jul 21, 2022
59f1b2d
update based on pr comments
sukantoraymond Jul 22, 2022
dd7a970
update mixpanel pr based on comments
sukantoraymond Jul 22, 2022
40be7f6
add replace flowkit in go mod
sukantoraymond Jul 22, 2022
2a1803b
udpate command.go
sukantoraymond Jul 22, 2022
bcdd0e2
handle mixpanel errors
sukantoraymond Jul 22, 2022
89ebc10
enable update user opt in status
sukantoraymond Jul 26, 2022
64fcce3
update flow tracking command and hashing
sukantoraymond Jul 26, 2022
90aa2db
remove default user location tracking
sukantoraymond Jul 26, 2022
3a7710d
implement checking user tracking opt in status
sukantoraymond Jul 26, 2022
72935cb
move tracking to config
sukantoraymond Jul 27, 2022
fd490b2
update data collection documentation
sukantoraymond Jul 28, 2022
3425792
Merge branch 'master' into feature/event-tracking-mixpanel
sukantoraymond Jul 28, 2022
829b9b5
update cd with mixpanel tokens
sukantoraymond Jul 28, 2022
04b7cc5
update cd go build
sukantoraymond Jul 28, 2022
fd7ec73
update go install
sukantoraymond Jul 28, 2022
6b2e3e2
default should be true for opt in
sukantoraymond Jul 28, 2022
96ff9a0
use dapper labs mixpanel
sukantoraymond Jul 28, 2022
78c5e61
update mixpaneluser.go
sukantoraymond Jul 28, 2022
2acaca8
handle missing mixpanel tokens
sukantoraymond Aug 2, 2022
841d787
address pr comments
sukantoraymond Aug 2, 2022
44fcafe
Merge remote-tracking branch 'origin/master' into feature/event-track…
sukantoraymond Aug 2, 2022
9dc1eeb
update keys generate error in create.go
sukantoraymond Aug 2, 2022
8e8436c
change to bytes encoding
sukantoraymond Aug 2, 2022
c394549
Update internal/config/metrics-settings.go
sukantoraymond Aug 3, 2022
cd63765
Update internal/config/metrics-settings.go
sukantoraymond Aug 3, 2022
874d2a5
fix linter
sukantoraymond Aug 3, 2022
d8122d0
fix lint
sukantoraymond Aug 3, 2022
d5f8149
fix lint
sukantoraymond Aug 3, 2022
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
2 changes: 2 additions & 0 deletions .github/workflows/cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ jobs:
go-version: '1.18'
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
MIXPANEL_PROJECT_TOKEN: ${{ secrets.MIXPANEL_PROJECT_TOKEN }}
MIXPANEL_SERVICE_ACCOUNT_SECRET: ${{ secrets.MIXPANEL_SERVICE_ACCOUNT_SECRET }}
build:
runs-on: ubuntu-latest
steps:
Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,14 @@ install:
GO111MODULE=on go install \
-trimpath \
-ldflags \
"-X github.com/onflow/flow-cli/build.commit=$(COMMIT) -X github.com/onflow/flow-cli/build.semver=$(VERSION)" \
"-X github.com/onflow/flow-cli/build.commit=$(COMMIT) -X github.com/onflow/flow-cli/build.semver=$(VERSION) -X github.com/onflow/flow-cli/pkg/flowkit/util.MIXPANEL_SERVICE_ACCOUNT_SECRET=$(MIXPANEL_SERVICE_ACCOUNT_SECRET) -X github.com/onflow/flow-cli/pkg/flowkit/util.MIXPANEL_PROJECT_TOKEN=$(MIXPANEL_PROJECT_TOKEN)" \
./cmd/flow

$(BINARY):
GO111MODULE=on go build \
-trimpath \
-ldflags \
"-X github.com/onflow/flow-cli/build.commit=$(COMMIT) -X github.com/onflow/flow-cli/build.semver=$(VERSION)" \
"-X github.com/onflow/flow-cli/build.commit=$(COMMIT) -X github.com/onflow/flow-cli/build.semver=$(VERSION) -X github.com/onflow/flow-cli/pkg/flowkit/util.MIXPANEL_SERVICE_ACCOUNT_SECRET=$(MIXPANEL_SERVICE_ACCOUNT_SECRET) -X github.com/onflow/flow-cli/pkg/flowkit/util.MIXPANEL_PROJECT_TOKEN=$(MIXPANEL_PROJECT_TOKEN)"\
-o $(BINARY) ./cmd/flow

.PHONY: versioned-binaries
Expand Down
29 changes: 29 additions & 0 deletions docs/data-collection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
title: Data Collection
description: Data collected from Flow CLI usage
---

Flow CLI tracks flow command usage count using Mixpanel.

Data collection is enabled by default. Users can opt out of our data collection through running `flow config tracking disable`.
To opt back in, users can run `flow config tracking enable`.

## Why do we collect data about flow cli usage?

Collecting aggregate command count allow us to prioritise features and fixes based on how users use flow cli.

## What data do we collect?

We only collect the number of times a command is executed.

We don't keep track of the values of arguments, flags used
and the values of the flags used. We also don't associate any commands to any particular user.

The only property that we collect from our users are their preferences for opting in / out of data collection.
The analytics user ID is specific to Mixpanel and does not permit Flow CLI maintainers to e.g. track you across websites you visit.

Further details regarding the data collected can be found under Mixpanel's data collection page in `Ingestion API`
section of https://help.mixpanel.com/hc/en-us/articles/115004613766-Default-Properties-Collected-by-Mixpanel.

Please note that although Mixpanel's page above mentions that geolocation properties are recorded by default,
we have turned off geolocation data reporting to Mixpanel.
2 changes: 1 addition & 1 deletion internal/accounts/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ func createInteractive(state *flowkit.State, loader flowkit.ReaderWriter) (*flow

service := services.NewServices(gw, state, output.NewStdoutLogger(output.NoneLog))

key, err := service.Keys.Generate("", crypto.ECDSA_P256)
key, err := service.Keys.Generate("", "", crypto.ECDSA_P256)
if err != nil {
return nil, err
}
Expand Down
17 changes: 17 additions & 0 deletions internal/command/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,22 @@ const (
logLevelNone = "none"
)

func (c Command) handleUserTracking() {
if util.MIXPANEL_SERVICE_ACCOUNT_SECRET == "" || util.MIXPANEL_PROJECT_TOKEN == "" {
return
}
optedIn, err := util.IsUserOptedIn()
if err != nil {
sentry.CaptureException(err)
}
if optedIn {
err = util.TrackCommandUsage(c.Cmd)
if err != nil {
sentry.CaptureException(err)
}
}
}

// AddToParent add new command to main parent cmd
// and initializes all necessary things as well as take care of errors and output
// here we can do all boilerplate code that is else copied in each command and make sure
Expand All @@ -91,6 +107,7 @@ func (c Command) AddToParent(parent *cobra.Command) {
defer sentry.Flush(2 * time.Second)
defer sentry.Recover()
}
c.handleUserTracking()

// initialize file loader used in commands
loader := &afero.Afero{Fs: afero.NewOsFs()}
Expand Down
1 change: 1 addition & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ func init() {
InitCommand.AddToParent(Cmd)
Cmd.AddCommand(AddCmd)
Cmd.AddCommand(RemoveCmd)
MetricsSettings.AddToParent(Cmd)
}

type Result struct {
Expand Down
63 changes: 63 additions & 0 deletions internal/config/metrics-settings.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Flow CLI
*
* Copyright 2019 Dapper Labs, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package config

import (
"fmt"

"github.com/onflow/flow-cli/pkg/flowkit"
"github.com/onflow/flow-cli/pkg/flowkit/services"
"github.com/onflow/flow-cli/pkg/flowkit/util"

"github.com/onflow/flow-cli/internal/command"

"github.com/spf13/cobra"
)

type flagsCommandMetrics struct{}

var commandMetricsFlags = flagsCommandMetrics{}

var MetricsSettings = &command.Command{
Cmd: &cobra.Command{
Use: "metrics",
Short: "Configure command usage metrics settings",
Example: "flow config metrics disable",
Args: cobra.ExactArgs(1),
},
Flags: &commandMetricsFlags,
Run: handleMetricsSettings,
}

func handleMetricsSettings(
args []string,
_ flowkit.ReaderWriter,
_ command.GlobalFlags,
_ *services.Services,
) (command.Result, error) {
disabled := args[0] == "disable"
err := util.SetUserMetricsSettings(disabled)
if err != nil {
return nil, err
}

return &Result{
fmt.Sprintf("Metrics have been %sd", args[0]),
}, nil
}
169 changes: 169 additions & 0 deletions pkg/flowkit/util/mixpanel.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
/*
* Flow CLI
*
* Copyright 2019 Dapper Labs, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package util

import (
"bytes"
"encoding/json"
"fmt"
"github.com/spf13/cobra"
"io/ioutil"
"net/http"
"net/url"
"strings"
)

const (
MIXPANEL_TRACK_URL = "https://api.mixpanel.com/track"
MIXPANEL_QUERY_URL = "https://mixpanel.com/api/2.0/engage?project_id=2154593"
MIXPANEL_PROFILE_URL = "https://api.mixpanel.com/engage#profile-set"
)

var MIXPANEL_PROJECT_TOKEN = ""
var MIXPANEL_SERVICE_ACCOUNT_SECRET = ""

type MixpanelClient struct {
token string
baseUrl string
}

func TrackCommandUsage(command *cobra.Command) error {
mixpanelEvent := newEvent(command)
mixpanelEvent.setUpEvent(MIXPANEL_PROJECT_TOKEN, FLOW_CLI)
eventPayload, err := encodePayload(mixpanelEvent)
if err != nil {
return err
}
payload := bytes.NewReader(eventPayload)
req, err := http.NewRequest("POST", MIXPANEL_TRACK_URL, payload)
if err != nil {
return err
}
req.Header.Add("Accept", "text/plain")
req.Header.Add("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
_, err = ioutil.ReadAll(res.Body)

if err != nil {
return err
}
if res.StatusCode >= 400 {
return fmt.Errorf("invalid response status code %d for tracking command usage", res.StatusCode)
}

return nil
}

func encodePayload(obj any) ([]byte, error) {
b, err := json.Marshal([]any{obj})
if err != nil {
return nil, err
}
return b, nil
}
func SetUserMetricsSettings(enable bool) error {
mixpanelUser, err := getMixPanelUser()
if err != nil {
return err
}
mixpanelUser.configureUserTracking(enable)

userPayload, err := encodePayload(mixpanelUser)
if err != nil {
return err
}

payload := bytes.NewReader(userPayload)
req, err := http.NewRequest("POST", MIXPANEL_PROFILE_URL, payload)
if err != nil {
return err
}
req.Header.Add("Accept", "text/plain")
req.Header.Add("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
_, err = ioutil.ReadAll(res.Body)

if err != nil {
return err
}
if res.StatusCode >= 400 {
return fmt.Errorf("invalid response status code %d for tracking command usage", res.StatusCode)
}

return nil
}

type MixPanelResponse struct {
Results []struct {
Properties struct {
OptIn bool `json:"opt_in"`
} `json:"$properties"`
} `json:"results"`
}

//User is opted in by default
//If distinct id can't be found through query api, return true to reflect that user is opted in
func IsUserOptedIn() (bool, error) {
distinctId, err := generateNewDistinctId()
if err != nil {
return false, err
}

queryPayload := "distinct_id=" + url.QueryEscape(distinctId)
payload := strings.NewReader(queryPayload)
req, err := http.NewRequest("POST", MIXPANEL_QUERY_URL, payload)
if err != nil {
return false, err
}

mixpanelAuth := "Basic " + MIXPANEL_SERVICE_ACCOUNT_SECRET
req.Header.Add("Accept", "application/json")
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
req.Header.Add("Authorization", mixpanelAuth)

res, err := http.DefaultClient.Do(req)
if err != nil {
return false, err
}

defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)

var queryResponse MixPanelResponse
err = json.Unmarshal(body, &queryResponse)

if err != nil {
return false, err
}
if res.StatusCode >= 400 {
return false, fmt.Errorf("invalid response status code %d for tracking command usage", res.StatusCode)
}
if len(queryResponse.Results) == 0 {
return true, nil
}
return queryResponse.Results[0].Properties.OptIn, nil
}
46 changes: 46 additions & 0 deletions pkg/flowkit/util/mixpanelEvent.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Flow CLI
*
* Copyright 2019 Dapper Labs, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package util

import (
"github.com/spf13/cobra"
)

const (
MIXPANEL_EVENT_PROJECT_TOKEN = "token"
MIXPANEL_EVENT_CALLER = "caller"
FLOW_CLI = "flow-cli"
)

type event struct {
Name string `json:"event"`
Properties map[string]interface{} `json:"properties"`
}

func newEvent(command *cobra.Command) *event {
return &event{
command.CommandPath(),
make(map[string]interface{}),
}
}

func (e *event) setUpEvent(token string, caller string) {
e.Properties[MIXPANEL_EVENT_PROJECT_TOKEN] = token
e.Properties[MIXPANEL_EVENT_CALLER] = caller
}
Loading