mirror of
https://github.com/safedep/pmg.git
synced 2026-08-03 07:24:09 +02:00
feat: add posthog analytics support (#52)
* feat: add posthog analytics events * Update internal/analytics/analytics.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Sahil Bansal <bansalsahil315@gmail.com> --------- Signed-off-by: Sahil Bansal <bansalsahil315@gmail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -243,3 +243,13 @@ malware detection. This limitation applies to direct installations and transitiv
|
|||||||
from non-PyPI locations.
|
from non-PyPI locations.
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
## Telemetry
|
||||||
|
|
||||||
|
`pmg` collects anonymous telemetry to help us understand how it is used and
|
||||||
|
improve the product. To disable telemetry, set `PMG_DISABLE_TELEMETRY` environment
|
||||||
|
variable to `true`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export PMG_DISABLE_TELEMETRY=true
|
||||||
|
```
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
"github.com/safedep/dry/log"
|
"github.com/safedep/dry/log"
|
||||||
"github.com/safedep/pmg/config"
|
"github.com/safedep/pmg/config"
|
||||||
|
"github.com/safedep/pmg/internal/analytics"
|
||||||
"github.com/safedep/pmg/internal/flows"
|
"github.com/safedep/pmg/internal/flows"
|
||||||
"github.com/safedep/pmg/internal/ui"
|
"github.com/safedep/pmg/internal/ui"
|
||||||
"github.com/safedep/pmg/packagemanager"
|
"github.com/safedep/pmg/packagemanager"
|
||||||
@@ -29,6 +30,7 @@ func NewNpmCommand() *cobra.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func executeNpmFlow(ctx context.Context, args []string) error {
|
func executeNpmFlow(ctx context.Context, args []string) error {
|
||||||
|
analytics.TrackCommandNpm()
|
||||||
packageManager, err := packagemanager.NewNpmPackageManager(packagemanager.DefaultNpmPackageManagerConfig())
|
packageManager, err := packagemanager.NewNpmPackageManager(packagemanager.DefaultNpmPackageManagerConfig())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ui.Fatalf("Failed to create npm package manager proxy: %s", err)
|
ui.Fatalf("Failed to create npm package manager proxy: %s", err)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
"github.com/safedep/dry/log"
|
"github.com/safedep/dry/log"
|
||||||
"github.com/safedep/pmg/config"
|
"github.com/safedep/pmg/config"
|
||||||
|
"github.com/safedep/pmg/internal/analytics"
|
||||||
"github.com/safedep/pmg/internal/flows"
|
"github.com/safedep/pmg/internal/flows"
|
||||||
"github.com/safedep/pmg/internal/ui"
|
"github.com/safedep/pmg/internal/ui"
|
||||||
"github.com/safedep/pmg/packagemanager"
|
"github.com/safedep/pmg/packagemanager"
|
||||||
@@ -29,6 +30,7 @@ func NewPnpmCommand() *cobra.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func executePnpmFlow(ctx context.Context, args []string) error {
|
func executePnpmFlow(ctx context.Context, args []string) error {
|
||||||
|
analytics.TrackCommandPnpm()
|
||||||
packageManager, err := packagemanager.NewNpmPackageManager(packagemanager.DefaultPnpmPackageManagerConfig())
|
packageManager, err := packagemanager.NewNpmPackageManager(packagemanager.DefaultPnpmPackageManagerConfig())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ui.Fatalf("Failed to create pnpm package manager proxy: %s", err)
|
ui.Fatalf("Failed to create pnpm package manager proxy: %s", err)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
"github.com/safedep/dry/log"
|
"github.com/safedep/dry/log"
|
||||||
"github.com/safedep/pmg/config"
|
"github.com/safedep/pmg/config"
|
||||||
|
"github.com/safedep/pmg/internal/analytics"
|
||||||
"github.com/safedep/pmg/internal/flows"
|
"github.com/safedep/pmg/internal/flows"
|
||||||
"github.com/safedep/pmg/internal/ui"
|
"github.com/safedep/pmg/internal/ui"
|
||||||
"github.com/safedep/pmg/packagemanager"
|
"github.com/safedep/pmg/packagemanager"
|
||||||
@@ -29,6 +30,7 @@ func NewPipCommand() *cobra.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func executePipFlow(ctx context.Context, args []string) error {
|
func executePipFlow(ctx context.Context, args []string) error {
|
||||||
|
analytics.TrackCommandPip()
|
||||||
packageManager, err := packagemanager.NewPipPackageManager(packagemanager.DefaultPipPackageManagerConfig())
|
packageManager, err := packagemanager.NewPipPackageManager(packagemanager.DefaultPipPackageManagerConfig())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create pip package manager: %w", err)
|
return fmt.Errorf("failed to create pip package manager: %w", err)
|
||||||
|
|||||||
@@ -10,7 +10,9 @@ require (
|
|||||||
github.com/Masterminds/semver v1.5.0
|
github.com/Masterminds/semver v1.5.0
|
||||||
github.com/fatih/color v1.18.0
|
github.com/fatih/color v1.18.0
|
||||||
github.com/google/osv-scalibr v0.2.1
|
github.com/google/osv-scalibr v0.2.1
|
||||||
|
github.com/google/uuid v1.6.0
|
||||||
github.com/jedib0t/go-pretty/v6 v6.6.7
|
github.com/jedib0t/go-pretty/v6 v6.6.7
|
||||||
|
github.com/posthog/posthog-go v1.5.12
|
||||||
github.com/safedep/dry v0.0.0-20250514080944-bb77f30c7175
|
github.com/safedep/dry v0.0.0-20250514080944-bb77f30c7175
|
||||||
github.com/spf13/cobra v1.9.1
|
github.com/spf13/cobra v1.9.1
|
||||||
github.com/spf13/pflag v1.0.6
|
github.com/spf13/pflag v1.0.6
|
||||||
|
|||||||
@@ -343,6 +343,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
|
|||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/polyfloyd/go-errorlint v1.7.1 h1:RyLVXIbosq1gBdk/pChWA8zWYLsq9UEw7a1L5TVMCnA=
|
github.com/polyfloyd/go-errorlint v1.7.1 h1:RyLVXIbosq1gBdk/pChWA8zWYLsq9UEw7a1L5TVMCnA=
|
||||||
github.com/polyfloyd/go-errorlint v1.7.1/go.mod h1:aXjNb1x2TNhoLsk26iv1yl7a+zTnXPhwEMtEXukiLR8=
|
github.com/polyfloyd/go-errorlint v1.7.1/go.mod h1:aXjNb1x2TNhoLsk26iv1yl7a+zTnXPhwEMtEXukiLR8=
|
||||||
|
github.com/posthog/posthog-go v1.5.12 h1:nxK/z5QLCFxwzxV8GNvVd4Y1wJ++zJSWMGEtzU+/HLM=
|
||||||
|
github.com/posthog/posthog-go v1.5.12/go.mod h1:ZPCind3bz8xDLK0Zhvpv1fQav6WfRcQDqTMfMXmna98=
|
||||||
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||||
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
||||||
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
|
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
package analytics
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/posthog/posthog-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
postHogApiKey = "phc_ckrojb22DtoIMBhIC3k3hh7tmRw0ng11gSaLUXSqwSt" // gitleaks:allow
|
||||||
|
postHogEventEndpoint = "https://us.i.posthog.com"
|
||||||
|
|
||||||
|
telemetryDisableEnvKey = "PMG_DISABLE_TELEMETRY"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
globalPosthogClient posthog.Client
|
||||||
|
|
||||||
|
// This is the distinct ID to anonymously identify an invocation of the CLI.
|
||||||
|
globalDistinctId string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if isTelemetryDisabled() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := posthog.NewWithConfig(postHogApiKey, posthog.Config{
|
||||||
|
Endpoint: postHogEventEndpoint,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to initialize posthog client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
globalPosthogClient = client
|
||||||
|
|
||||||
|
randomUUID, err := uuid.NewRandom()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to generate random UUID: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
globalDistinctId = randomUUID.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func isTelemetryDisabled() bool {
|
||||||
|
val := os.Getenv(telemetryDisableEnvKey)
|
||||||
|
if booleanVal, err := strconv.ParseBool(val); err == nil {
|
||||||
|
return booleanVal
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsDisabled() bool {
|
||||||
|
return isTelemetryDisabled()
|
||||||
|
}
|
||||||
|
|
||||||
|
// We want to ensure that we do not collect any telemetry if the user has disabled them.
|
||||||
|
func Track(distinctId string, event string, properties posthog.Properties) {
|
||||||
|
if isTelemetryDisabled() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if globalPosthogClient == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = globalPosthogClient.Enqueue(&posthog.Capture{
|
||||||
|
DistinctId: distinctId,
|
||||||
|
Event: event,
|
||||||
|
Properties: properties,
|
||||||
|
Timestamp: time.Now(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TrackEvent(event string) {
|
||||||
|
Track(globalDistinctId, event,
|
||||||
|
posthog.NewProperties().
|
||||||
|
Set("$process_person_profile", false))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Close() {
|
||||||
|
if globalPosthogClient == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = globalPosthogClient.Close()
|
||||||
|
globalPosthogClient = nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package analytics
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIsDisabled(t *testing.T) {
|
||||||
|
t.Run("returns true if PMG_DISABLE_TELEMETRY is set to true", func(t *testing.T) {
|
||||||
|
os.Setenv(telemetryDisableEnvKey, "true")
|
||||||
|
defer os.Unsetenv(telemetryDisableEnvKey)
|
||||||
|
|
||||||
|
assert.True(t, IsDisabled())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("returns false if PMG_DISABLE_TELEMETRY is not set", func(t *testing.T) {
|
||||||
|
assert.False(t, IsDisabled())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCloseIsImmutable(t *testing.T) {
|
||||||
|
Close()
|
||||||
|
assert.Nil(t, globalPosthogClient)
|
||||||
|
|
||||||
|
Close()
|
||||||
|
assert.Nil(t, globalPosthogClient)
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package analytics
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
type environmentType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
environmentTypeGitHubActions environmentType = "github_actions"
|
||||||
|
environmentTypeGitLabCI environmentType = "gitlab_ci"
|
||||||
|
environmentTypeDocker environmentType = "docker"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Map of environment variables which if set will determine environments
|
||||||
|
var ciEnvVars = map[string]environmentType{
|
||||||
|
"GITHUB_ACTION": environmentTypeGitHubActions,
|
||||||
|
"GITHUB_WORKFLOW": environmentTypeGitHubActions,
|
||||||
|
"GITLAB_CI": environmentTypeGitLabCI,
|
||||||
|
"CI_COMMIT_BRANCH": environmentTypeGitLabCI,
|
||||||
|
}
|
||||||
|
|
||||||
|
func TrackCI() {
|
||||||
|
uniqueTypes := make(map[environmentType]bool)
|
||||||
|
for envVar, envVarType := range ciEnvVars {
|
||||||
|
if os.Getenv(envVar) != "" {
|
||||||
|
uniqueTypes[envVarType] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for envVarType := range uniqueTypes {
|
||||||
|
trackCiEvent(envVarType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func trackCiEvent(envVarType environmentType) {
|
||||||
|
switch envVarType {
|
||||||
|
case environmentTypeGitHubActions:
|
||||||
|
TrackCommandGenerateEnvGitHubActions()
|
||||||
|
case environmentTypeGitLabCI:
|
||||||
|
TrackCommandGenerateEnvGitLabCI()
|
||||||
|
case environmentTypeDocker:
|
||||||
|
TrackCommandGenerateEnvDocker()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
// analytics package is for internal utility functions for tracking
|
||||||
|
// anonymous usage analytics.
|
||||||
|
package analytics
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package analytics
|
||||||
|
|
||||||
|
const (
|
||||||
|
eventRun = "pmg_command_run"
|
||||||
|
eventCommandNpm = "pmg_command_npm"
|
||||||
|
eventCommandPnpm = "pmg_command_pnpm"
|
||||||
|
eventCommandPip = "pmg_command_pip"
|
||||||
|
|
||||||
|
eventPmgGenerateEnvDocker = "pmg_command_generate_env_docker"
|
||||||
|
eventPmgGenerateEnvGitHubActions = "pmg_command_generate_env_github_actions"
|
||||||
|
eventPmgGenerateEnvGitLabCI = "pmg_command_generate_env_gitlab_ci"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TrackCommandRun() {
|
||||||
|
TrackEvent(eventRun)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TrackCommandNpm() {
|
||||||
|
TrackEvent(eventCommandNpm)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TrackCommandPnpm() {
|
||||||
|
TrackEvent(eventCommandPnpm)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TrackCommandPip() {
|
||||||
|
TrackEvent(eventCommandPip)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TrackCommandGenerateEnvDocker() {
|
||||||
|
TrackEvent(eventPmgGenerateEnvDocker)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TrackCommandGenerateEnvGitHubActions() {
|
||||||
|
TrackEvent(eventPmgGenerateEnvGitHubActions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TrackCommandGenerateEnvGitLabCI() {
|
||||||
|
TrackEvent(eventPmgGenerateEnvGitLabCI)
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/safedep/pmg/cmd/setup"
|
"github.com/safedep/pmg/cmd/setup"
|
||||||
"github.com/safedep/pmg/cmd/version"
|
"github.com/safedep/pmg/cmd/version"
|
||||||
"github.com/safedep/pmg/config"
|
"github.com/safedep/pmg/config"
|
||||||
|
"github.com/safedep/pmg/internal/analytics"
|
||||||
"github.com/safedep/pmg/internal/ui"
|
"github.com/safedep/pmg/internal/ui"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
@@ -86,6 +87,11 @@ func main() {
|
|||||||
cmd.AddCommand(setup.NewSetupCommand())
|
cmd.AddCommand(setup.NewSetupCommand())
|
||||||
cmd.AddCommand(setup.NewRemoveCommand())
|
cmd.AddCommand(setup.NewRemoveCommand())
|
||||||
|
|
||||||
|
defer analytics.Close()
|
||||||
|
|
||||||
|
analytics.TrackCommandRun()
|
||||||
|
analytics.TrackCI()
|
||||||
|
|
||||||
if err := cmd.Execute(); err != nil {
|
if err := cmd.Execute(); err != nil {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user