Skip to content

Commit

Permalink
feat: convert telemetry user_id from ulid to uuid (#34)
Browse files Browse the repository at this point in the history
  • Loading branch information
colesnodgrass authored May 31, 2024
1 parent 72a2c26 commit 0070b8c
Show file tree
Hide file tree
Showing 7 changed files with 359 additions and 96 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ jobs:
build
ci
refactor
revert
design
style
perf
subjectPattern: ^(?![A-Z]).+$
subjectPatternError: |
The subject "{subject}" found in the pull request title "{title}"
Expand Down
11 changes: 10 additions & 1 deletion internal/telemetry/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,11 @@ func Get(opts ...GetOption) Client {
getOrCreateConfigFile := func(getCfg getConfig) (Config, error) {
configPath := filepath.Join(getCfg.userHome, ConfigFile)

// if no file exists, create a new one
analyticsCfg, err := loadConfigFromFile(configPath)
if errors.Is(err, os.ErrNotExist) {
// file not found, create a new one
analyticsCfg = Config{UserID: NewULID()}
analyticsCfg = Config{UserUUID: NewUUID()}
if err := writeConfigToFile(configPath, analyticsCfg); err != nil {
return analyticsCfg, fmt.Errorf("could not write file to %s: %w", configPath, err)
}
Expand All @@ -108,6 +109,14 @@ func Get(opts ...GetOption) Client {
return Config{}, fmt.Errorf("could not load config from %s: %w", configPath, err)
}

// if a file exists but doesn't have a uuid, create a new uuid
if analyticsCfg.UserUUID.IsZero() {
analyticsCfg.UserUUID = NewUUID()
if err := writeConfigToFile(configPath, analyticsCfg); err != nil {
return analyticsCfg, fmt.Errorf("could not write file to %s: %w", configPath, err)
}
}

return analyticsCfg, nil
}

Expand Down
40 changes: 40 additions & 0 deletions internal/telemetry/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,46 @@ func TestGet(t *testing.T) {
if !strings.Contains(string(data), "Airbyte") {
t.Error("expected config file to contain 'Airbyte'")
}

if !strings.Contains(string(data), "anonymous_user_uuid") {
t.Error("expected config file to contain 'anonymous_user_uuid'")
}

if strings.Contains(string(data), "anonymous_user_id") {
t.Error("config file should not contain 'anonymous_user_id'")
}
}

func TestGet_WithExistingULID(t *testing.T) {
instance = nil
home := t.TempDir()

// write a config with a ulid only
cfg := Config{UserID: NewULID()}
if err := writeConfigToFile(filepath.Join(home, ConfigFile), cfg); err != nil {
t.Fatal("failed writing config", err)
}

cli := Get(WithUserHome(home))
if _, ok := cli.(*SegmentClient); !ok {
t.Error(fmt.Sprintf("expected SegmentClient; received: %T", cli))
}

// verify configuration file was created
data, err := os.ReadFile(filepath.Join(home, ConfigFile))
if err != nil {
t.Error("reading config file", err)
}

// and has some data
if !strings.Contains(string(data), "Airbyte") {
t.Error("expected config file to contain 'Airbyte'")
}

if !strings.Contains(string(data), "anonymous_user_uuid") {
t.Error("expected config file to contain 'anonymous_user_uuid'")
}

if !strings.Contains(string(data), "anonymous_user_id") {
t.Error("expected config file to contain 'anonymous_user_id'")
}
Expand Down
49 changes: 45 additions & 4 deletions internal/telemetry/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package telemetry

import (
"fmt"
"github.com/google/uuid"
"github.com/oklog/ulid/v2"
"gopkg.in/yaml.v3"
"os"
Expand All @@ -16,7 +17,45 @@ Anonymous usage reporting is currently enabled. For more information, please see

var ConfigFile = filepath.Join(".airbyte", "analytics.yml")

// UUID is a wrapper around uuid.UUID so that we can implement the yaml interfaces.
type UUID uuid.UUID

// NewUUID returns a new randomized UUID.
func NewUUID() UUID {
return UUID(uuid.New())
}

// String returns a string representation of this UUID.
func (u UUID) String() string {
return uuid.UUID(u).String()
}

func (u *UUID) UnmarshalYAML(node *yaml.Node) error {
var s string
if err := node.Decode(&s); err != nil {
return fmt.Errorf("could not unmarshal yaml: %w", err)
}

parsed, err := uuid.Parse(s)
if err != nil {
return fmt.Errorf("could not parse uuid (%s): %w", s, err)
}

*u = UUID(parsed)
return nil
}

func (u UUID) MarshalYAML() (any, error) {
return uuid.UUID(u).String(), nil
}

// IsZero implements the yaml interface, used to treat a uuid.Nil as empty for yaml purposes
func (u UUID) IsZero() bool {
return u.String() == uuid.Nil.String()
}

// ULID is a wrapper around ulid.ULID so that we can implement the yaml interfaces.
// Deprecated: use UUID instead
type ULID ulid.ULID

// NewULID returns a new randomized ULID.
Expand Down Expand Up @@ -50,16 +89,18 @@ func (u *ULID) UnmarshalYAML(node *yaml.Node) error {
}

// MarshalYAML allows for converting a ULID into a yaml field.
//
//goland:noinspection GoMixedReceiverTypes
func (u ULID) MarshalYAML() (any, error) {
//panic("test")
return ulid.ULID(u).String(), nil
}

func (u ULID) IsZero() bool {
return u.String() == "00000000000000000000000000"
}

// Config represents the analytics.yaml file.
type Config struct {
UserID ULID `yaml:"anonymous_user_id"`
UserID ULID `yaml:"anonymous_user_id,omitempty"`
UserUUID UUID `yaml:"anonymous_user_uuid,omitempty"`
}

// permissions sets the file and directory permission level for the telemetry files that may be created.
Expand Down
Loading

0 comments on commit 0070b8c

Please sign in to comment.