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: convert telemetry user_id from ulid to uuid #34

Merged
merged 5 commits into from
May 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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