Skip to content

Commit 7483c85

Browse files
authored
Merge pull request #3718 from ActiveState/miked/PIF-1051
Allow configuration of analytics pixel URL
2 parents 495860d + fee4843 commit 7483c85

File tree

6 files changed

+132
-13
lines changed

6 files changed

+132
-13
lines changed

internal/analytics/analytics.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ import (
66
configMediator "github.com/ActiveState/cli/internal/mediators/config"
77
)
88

9+
func init() {
10+
configMediator.RegisterOption(constants.AnalyticsPixelOverrideConfig, configMediator.String, "")
11+
}
12+
913
// Dispatcher describes a struct that can send analytics event in the background
1014
type Dispatcher interface {
1115
Event(category, action string, dim ...*dimensions.Values)

internal/analytics/client/sync/client.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,12 +123,12 @@ func New(source string, cfg *config.Instance, auth *authentication.Auth, out out
123123
// Register reporters
124124
if condition.InTest() {
125125
logging.Debug("Using test reporter")
126-
a.NewReporter(reporters.NewTestReporter(reporters.TestReportFilepath()))
126+
a.NewReporter(reporters.NewTestReporter(reporters.TestReportFilepath(), a.cfg))
127127
logging.Debug("Using test reporter as instructed by env")
128128
} else if v := os.Getenv(constants.AnalyticsLogEnvVarName); v != "" {
129-
a.NewReporter(reporters.NewTestReporter(v))
129+
a.NewReporter(reporters.NewTestReporter(v, a.cfg))
130130
} else {
131-
a.NewReporter(reporters.NewPixelReporter())
131+
a.NewReporter(reporters.NewPixelReporter(a.cfg))
132132
}
133133

134134
return a

internal/analytics/client/sync/reporters/pixel.go

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,26 @@ import (
77
"os"
88

99
"github.com/ActiveState/cli/internal/analytics/dimensions"
10+
"github.com/ActiveState/cli/internal/config"
1011
"github.com/ActiveState/cli/internal/constants"
1112
"github.com/ActiveState/cli/internal/errs"
13+
14+
configMediator "github.com/ActiveState/cli/internal/mediators/config"
1215
)
1316

1417
type PixelReporter struct {
1518
url string
1619
}
1720

18-
func NewPixelReporter() *PixelReporter {
19-
var pixelUrl string
20-
21-
// Attempt to get the value for the pixel URL from the environment. Fall back to default if that fails
22-
if pixelUrl = os.Getenv(constants.AnalyticsPixelOverrideEnv); pixelUrl == "" {
23-
pixelUrl = constants.DefaultAnalyticsPixel
21+
func NewPixelReporter(cfg *config.Instance) *PixelReporter {
22+
reporter := &PixelReporter{
23+
url: sourcePixelURL(cfg),
2424
}
25-
return &PixelReporter{pixelUrl}
25+
26+
configMediator.AddListener(constants.AnalyticsPixelOverrideConfig, func() {
27+
reporter.url = sourcePixelURL(cfg)
28+
})
29+
return reporter
2630
}
2731

2832
func (r *PixelReporter) ID() string {
@@ -55,3 +59,23 @@ func (r *PixelReporter) Event(category, action, source, label string, d *dimensi
5559

5660
return nil
5761
}
62+
63+
func sourcePixelURL(cfg *config.Instance) string {
64+
var (
65+
pixelUrl string
66+
67+
envUrl = os.Getenv(constants.AnalyticsPixelOverrideEnv)
68+
cfgUrl = cfg.GetString(constants.AnalyticsPixelOverrideConfig)
69+
)
70+
71+
switch {
72+
case envUrl != "":
73+
pixelUrl = envUrl
74+
case cfgUrl != "":
75+
pixelUrl = cfgUrl
76+
default:
77+
pixelUrl = constants.DefaultAnalyticsPixel
78+
}
79+
80+
return pixelUrl
81+
}

internal/analytics/client/sync/reporters/test.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,18 @@ import (
55
"path/filepath"
66

77
"github.com/ActiveState/cli/internal/analytics/dimensions"
8+
"github.com/ActiveState/cli/internal/config"
9+
"github.com/ActiveState/cli/internal/constants"
810
"github.com/ActiveState/cli/internal/errs"
911
"github.com/ActiveState/cli/internal/fileutils"
1012
"github.com/ActiveState/cli/internal/installation/storage"
1113
"github.com/ActiveState/cli/internal/logging"
14+
configMediator "github.com/ActiveState/cli/internal/mediators/config"
1215
)
1316

1417
type TestReporter struct {
1518
path string
19+
cfg *config.Instance
1620
}
1721

1822
const TestReportFilename = "analytics.log"
@@ -23,8 +27,12 @@ func TestReportFilepath() string {
2327
return filepath.Join(appdata, TestReportFilename)
2428
}
2529

26-
func NewTestReporter(path string) *TestReporter {
27-
return &TestReporter{path}
30+
func NewTestReporter(path string, cfg *config.Instance) *TestReporter {
31+
reporter := &TestReporter{path, cfg}
32+
configMediator.AddListener(constants.AnalyticsPixelOverrideConfig, func() {
33+
reporter.cfg = cfg
34+
})
35+
return reporter
2836
}
2937

3038
func (r *TestReporter) ID() string {
@@ -36,11 +44,13 @@ type TestLogEntry struct {
3644
Action string
3745
Source string
3846
Label string
47+
URL string
3948
Dimensions *dimensions.Values
4049
}
4150

4251
func (r *TestReporter) Event(category, action, source, label string, d *dimensions.Values) error {
43-
b, err := json.Marshal(TestLogEntry{category, action, source, label, d})
52+
url := r.cfg.GetString(constants.AnalyticsPixelOverrideConfig)
53+
b, err := json.Marshal(TestLogEntry{category, action, source, label, url, d})
4454
if err != nil {
4555
return errs.Wrap(err, "Could not marshal test log entry")
4656
}

internal/constants/constants.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,9 @@ const SecurityPromptConfig = "security.prompt.enabled"
412412
// SecurityPromptLevelConfig is the config key used to determine the level of security prompts
413413
const SecurityPromptLevelConfig = "security.prompt.level"
414414

415+
// AnalyticsPixelOverrideConfig is the config key used to override the analytics pixel url
416+
const AnalyticsPixelOverrideConfig = "report.analytics.endpoint"
417+
415418
// UpdateEndpointConfig is the config key used to determine the update endpoint to use
416419
const UpdateEndpointConfig = "update.endpoint"
417420

test/integration/analytics_int_test.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,14 @@ import (
1212

1313
"github.com/ActiveState/termtest"
1414
"github.com/thoas/go-funk"
15+
"golang.org/x/net/context"
1516

17+
"github.com/ActiveState/cli/internal/errs"
18+
"github.com/ActiveState/cli/internal/ipc"
1619
"github.com/ActiveState/cli/internal/runbits/runtime/trigger"
20+
"github.com/ActiveState/cli/internal/svcctl"
1721
"github.com/ActiveState/cli/internal/testhelpers/suite"
22+
"github.com/ActiveState/cli/pkg/platform/model"
1823

1924
"github.com/ActiveState/cli/internal/analytics/client/sync/reporters"
2025
anaConst "github.com/ActiveState/cli/internal/analytics/constants"
@@ -644,6 +649,79 @@ func (suite *AnalyticsIntegrationTestSuite) TestCIAndInteractiveDimensions() {
644649
}
645650
}
646651

652+
func (suite *AnalyticsIntegrationTestSuite) TestAnalyticsPixelOverride() {
653+
suite.OnlyRunForTags(tagsuite.Analytics)
654+
655+
ts := e2e.New(suite.T(), false)
656+
defer ts.Close()
657+
658+
testURL := "https://example.com"
659+
cp := ts.Spawn("config", "set", constants.AnalyticsPixelOverrideConfig, testURL)
660+
cp.Expect("Successfully set config key")
661+
cp.ExpectExitCode(0)
662+
663+
// Create IPC client using the test's socket directory to connect to the same state-svc instance
664+
// that the spawned state tool commands are using.
665+
// We make a request to the service directly to ensure we're hitting an endpoint that will send an event.
666+
sockPath := &ipc.SockPath{
667+
RootDir: ts.Dirs.SockRoot,
668+
AppName: constants.CommandName,
669+
AppChannel: constants.ChannelName,
670+
}
671+
ipcClient := ipc.NewClient(sockPath)
672+
673+
svcPort, err := svcctl.LocateHTTP(ipcClient)
674+
suite.Require().NoError(err, errs.JoinMessage(err))
675+
676+
svcmodel := model.NewSvcModel(svcPort)
677+
_, err = svcmodel.LocalProjects(context.Background())
678+
suite.Require().NoError(err, errs.JoinMessage(err))
679+
680+
time.Sleep(time.Second) // Ensure state-svc has time to report events
681+
682+
suite.eventsfile = filepath.Join(ts.Dirs.Config, reporters.TestReportFilename)
683+
events := parseAnalyticsEvents(suite, ts)
684+
suite.Require().NotEmpty(events)
685+
686+
// Some events will fire before the config is updated, so we expect to
687+
// find at least one event with the new configuration values after the service is restarted.
688+
found := false
689+
for _, e := range events {
690+
// Specifically check an event sent via the state-svc and ensure that the URL is the one we set in the config
691+
if e.Category == anaConst.CatStateSvc && e.Action == "endpoint" && e.Label == "Projects" {
692+
suite.Assert().Equal(testURL, e.URL)
693+
found = true
694+
}
695+
}
696+
suite.Assert().True(found, "Should find at least one state-svc endpoint Projects event")
697+
698+
// Check that all events after the config set event have the correct URL.
699+
configSetEventIndex := -1
700+
for i, e := range events {
701+
if e.Category == anaConst.CatConfig && e.Action == anaConst.ActConfigSet && e.Label == constants.AnalyticsPixelOverrideConfig {
702+
configSetEventIndex = i
703+
break
704+
}
705+
}
706+
suite.Require().NotEqual(-1, configSetEventIndex, "Should find the config set event")
707+
708+
// Check all events after config set, skipping the immediate update event
709+
for i, e := range events {
710+
if i > configSetEventIndex {
711+
// Skip the immediate update event right after config set.
712+
// The update event is sent before the config is updated, so it will have an empty URL.
713+
if i == configSetEventIndex+1 && e.Category == "updates" {
714+
continue
715+
}
716+
// All other events with URLs should have the correct one.
717+
// This includes events from the state tool and the state-svc.
718+
if e.URL != testURL {
719+
suite.Equal(testURL, e.URL, "Event after config set (%s:%s) should have the updated URL", e.Category, e.Action)
720+
}
721+
}
722+
}
723+
}
724+
647725
func TestAnalyticsIntegrationTestSuite(t *testing.T) {
648726
suite.Run(t, new(AnalyticsIntegrationTestSuite))
649727
}

0 commit comments

Comments
 (0)