Skip to content
This repository was archived by the owner on Jul 28, 2020. It is now read-only.

Commit a610845

Browse files
xtreme-stevehiehnAnthony Emengo
authored andcommitted
Support custom analytics prompt
Signed-off-by: Anthony Emengo <[email protected]>
1 parent 7800f83 commit a610845

21 files changed

+588
-412
lines changed

cfanalytics/analyticsd_darwin.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,4 @@ func (a *AnalyticsD) DaemonSpec() daemon.DaemonSpec {
1717
StdoutPath: path.Join(a.Config.CFDevHome, "analyticsd.stdout.log"),
1818
StderrPath: path.Join(a.Config.CFDevHome, "analyticsd.stderr.log"),
1919
}
20-
}
20+
}

cfanalytics/analyticsd_windows.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,4 @@ func (a *AnalyticsD) DaemonSpec() daemon.DaemonSpec {
1313
SessionType: "Background",
1414
ProgramArguments: []string{os.Getenv("CFDEV_MODE")},
1515
}
16-
}
16+
}

cfanalytics/cfanalytics.go

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,37 @@
11
package cfanalytics
22

33
import (
4+
"code.cloudfoundry.org/cfdev/errors"
45
"runtime"
56
"strings"
67
"time"
78

8-
"code.cloudfoundry.org/cfdev/errors"
99
"github.com/denisbrodbeck/machineid"
1010
"gopkg.in/segmentio/analytics-go.v3"
1111
)
1212

1313
const (
14-
START_BEGIN = "start_begin"
15-
START_END = "start_end"
14+
START_BEGIN = "start_begin"
15+
START_END = "start_end"
1616
SELECTED_SERVICE = "selected_service"
17-
STOP = "stop"
18-
BOSH_ENV = "bosh"
19-
ERROR = "error"
20-
UNINSTALL = "uninstall"
17+
STOP = "stop"
18+
BOSH_ENV = "bosh"
19+
ERROR = "error"
20+
UNINSTALL = "uninstall"
2121
)
2222

2323
//go:generate mockgen -package mocks -destination mocks/analytics_client.go gopkg.in/segmentio/analytics-go.v3 Client
2424

2525
//go:generate mockgen -package mocks -destination mocks/toggle.go code.cloudfoundry.org/cfdev/cfanalytics Toggle
2626
type Toggle interface {
2727
Defined() bool
28-
Get() bool
29-
Set(value bool) error
28+
CustomAnalyticsDefined() bool
29+
Enabled() bool
30+
IsCustom() bool
31+
SetCFAnalyticsEnabled(value bool) error
32+
SetCustomAnalyticsEnabled(value bool) error
3033
GetProps() map[string]interface{}
34+
SetProp(k, v string) error
3135
}
3236

3337
//go:generate mockgen -package mocks -destination mocks/ui.go code.cloudfoundry.org/cfdev/cfanalytics UI
@@ -67,7 +71,7 @@ func (a *Analytics) Close() {
6771
}
6872

6973
func (a *Analytics) Event(event string, data ...map[string]interface{}) error {
70-
if !a.toggle.Get() {
74+
if !a.toggle.Enabled() {
7175
return nil
7276
}
7377

@@ -81,12 +85,12 @@ func (a *Analytics) Event(event string, data ...map[string]interface{}) error {
8185
properties.Set("os_version", a.osVersion)
8286
for k, v := range a.toggle.GetProps() {
8387
properties.Set(k, v)
84-
}
88+
}
8589
for _, d := range data {
8690
for k, v := range d {
8791
properties.Set(k, v)
92+
}
8893
}
89-
}
9094

9195
return a.client.Enqueue(analytics.Track{
9296
UserId: a.userId,
@@ -96,14 +100,20 @@ func (a *Analytics) Event(event string, data ...map[string]interface{}) error {
96100
})
97101
}
98102

99-
func (a *Analytics) PromptOptIn() error {
100-
if !a.toggle.Defined() {
101-
response := a.ui.Ask(`
102-
CF Dev collects anonymous usage data to help us improve your user experience. We intend to share these anonymous usage analytics with user community by publishing quarterly reports at :
103+
func (a *Analytics) PromptOptInIfNeeded(customMessage string) error {
104+
useCustom := customMessage != ""
103105

104-
https://github.com/pivotal-cf/cfdev/wiki/Telemetry
106+
if !a.toggle.Defined() || (useCustom && !a.toggle.CustomAnalyticsDefined()) {
105107

106-
Are you ok with CF Dev periodically capturing anonymized telemetry [y/N]?`)
108+
message := `CF Dev collects anonymous usage data to help us improve your user experience. We intend to share these anonymous usage analytics with user community by publishing quarterly reports at :
109+
110+
https://github.com/pivotal-cf/cfdev/wiki/Telemetry
111+
112+
Are you ok with CF Dev periodically capturing anonymized telemetry [y/N]?`
113+
if useCustom {
114+
message = customMessage
115+
}
116+
response := a.ui.Ask(message)
107117

108118
select {
109119
case <-a.exit:
@@ -113,8 +123,15 @@ Are you ok with CF Dev periodically capturing anonymized telemetry [y/N]?`)
113123

114124
response = strings.ToLower(response)
115125
enabled := response == "y" || response == "yes"
116-
if err := a.toggle.Set(enabled); err != nil {
117-
return err
126+
127+
if useCustom {
128+
if err := a.toggle.SetCustomAnalyticsEnabled(enabled); err != nil {
129+
return err
130+
}
131+
} else {
132+
if err := a.toggle.SetCFAnalyticsEnabled(enabled); err != nil {
133+
return err
134+
}
118135
}
119136
}
120137
return nil

cfanalytics/cfanalytics_test.go

Lines changed: 109 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
package cfanalytics_test
22

33
import (
4-
"runtime"
5-
"time"
6-
74
"code.cloudfoundry.org/cfdev/cfanalytics"
85
"code.cloudfoundry.org/cfdev/cfanalytics/mocks"
96
"github.com/denisbrodbeck/machineid"
107
"github.com/golang/mock/gomock"
118
. "github.com/onsi/ginkgo"
129
. "github.com/onsi/gomega"
1310
"github.com/onsi/gomega/gstruct"
14-
analytics "gopkg.in/segmentio/analytics-go.v3"
11+
"gopkg.in/segmentio/analytics-go.v3"
12+
"runtime"
13+
"time"
1514
)
1615

1716
var _ = Describe("Analytics", func() {
@@ -35,35 +34,35 @@ var _ = Describe("Analytics", func() {
3534
mockController.Finish()
3635
})
3736

38-
Describe("PromptOptIn", func() {
37+
Describe("PromptOptInIfNeeded with empty message", func() {
3938
Context("When user has NOT yet answered optin prompt", func() {
4039
BeforeEach(func() {
4140
mockToggle.EXPECT().Defined().Return(false).AnyTimes()
4241
})
4342
It("prompts user", func() {
44-
mockToggle.EXPECT().Set(gomock.Any()).AnyTimes()
43+
mockToggle.EXPECT().SetCFAnalyticsEnabled(gomock.Any()).AnyTimes()
4544
mockUI.EXPECT().Ask(gomock.Any()).Do(func(prompt string) {
4645
Expect(prompt).To(ContainSubstring("Are you ok with CF Dev periodically capturing anonymized telemetry [y/N]?"))
4746
})
48-
Expect(subject.PromptOptIn()).To(Succeed())
47+
Expect(subject.PromptOptInIfNeeded("")).To(Succeed())
4948
})
5049
for _, answer := range []string{"yes", "y", "yEs"} {
5150
Context("user answers "+answer, func() {
5251
BeforeEach(func() { mockUI.EXPECT().Ask(gomock.Any()).Return(answer) })
5352
It("saves optin", func() {
54-
mockToggle.EXPECT().Set(true)
53+
mockToggle.EXPECT().SetCFAnalyticsEnabled(true)
5554

56-
Expect(subject.PromptOptIn()).To(Succeed())
55+
Expect(subject.PromptOptInIfNeeded("")).To(Succeed())
5756
})
5857
})
5958
}
6059
for _, answer := range []string{"no", "N", "anything", ""} {
6160
Context("user answers "+answer, func() {
6261
BeforeEach(func() { mockUI.EXPECT().Ask(gomock.Any()).Return(answer) })
6362
It("saves optout", func() {
64-
mockToggle.EXPECT().Set(false)
63+
mockToggle.EXPECT().SetCFAnalyticsEnabled(false)
6564

66-
Expect(subject.PromptOptIn()).To(Succeed())
65+
Expect(subject.PromptOptInIfNeeded("")).To(Succeed())
6766
})
6867
})
6968
}
@@ -73,7 +72,7 @@ var _ = Describe("Analytics", func() {
7372
exitChan <- struct{}{}
7473
})
7574
It("does not write set a value on toggle", func() {
76-
Expect(subject.PromptOptIn()).To(MatchError("Exit while waiting for telemetry prompt"))
75+
Expect(subject.PromptOptInIfNeeded("")).To(MatchError("Exit while waiting for telemetry prompt"))
7776
})
7877
})
7978
})
@@ -82,22 +81,117 @@ var _ = Describe("Analytics", func() {
8281
mockToggle.EXPECT().Defined().AnyTimes().Return(true)
8382
})
8483
It("does not ask again", func() {
85-
Expect(subject.PromptOptIn()).To(Succeed())
84+
Expect(subject.PromptOptInIfNeeded("")).To(Succeed())
85+
})
86+
})
87+
})
88+
Describe("PromptOptInIfNeeded with custom message", func() {
89+
Context("When user has NOT yet answered any optin prompt at all", func() {
90+
BeforeEach(func() {
91+
mockToggle.EXPECT().Defined().Return(false).AnyTimes()
92+
mockToggle.EXPECT().CustomAnalyticsDefined().Return(false).AnyTimes()
93+
})
94+
It("prompts user", func() {
95+
mockToggle.EXPECT().SetCustomAnalyticsEnabled(gomock.Any()).AnyTimes()
96+
mockUI.EXPECT().Ask(gomock.Any()).Do(func(prompt string) {
97+
Expect(prompt).To(ContainSubstring("some-custom-message"))
98+
})
99+
Expect(subject.PromptOptInIfNeeded("some-custom-message")).To(Succeed())
100+
})
101+
for _, answer := range []string{"yes", "y", "yEs"} {
102+
Context("user answers "+answer, func() {
103+
BeforeEach(func() { mockUI.EXPECT().Ask(gomock.Any()).Return(answer) })
104+
It("saves optin", func() {
105+
mockToggle.EXPECT().SetCustomAnalyticsEnabled(true)
106+
107+
Expect(subject.PromptOptInIfNeeded("some-custom-message")).To(Succeed())
108+
})
109+
})
110+
}
111+
for _, answer := range []string{"no", "N", "anything", ""} {
112+
Context("user answers "+answer, func() {
113+
BeforeEach(func() { mockUI.EXPECT().Ask(gomock.Any()).Return(answer) })
114+
It("saves optout", func() {
115+
mockToggle.EXPECT().SetCustomAnalyticsEnabled(false)
116+
117+
Expect(subject.PromptOptInIfNeeded("some-custom-message")).To(Succeed())
118+
})
119+
})
120+
}
121+
Context("user hits ctrl-c", func() {
122+
BeforeEach(func() {
123+
mockUI.EXPECT().Ask(gomock.Any()).Return("")
124+
exitChan <- struct{}{}
125+
})
126+
It("does not write set a value on toggle", func() {
127+
Expect(subject.PromptOptInIfNeeded("some-custom-message")).To(MatchError("Exit while waiting for telemetry prompt"))
128+
})
129+
})
130+
})
131+
Context("When user has answered custom optin prompt already", func() {
132+
BeforeEach(func() {
133+
mockToggle.EXPECT().Defined().AnyTimes().Return(true)
134+
mockToggle.EXPECT().CustomAnalyticsDefined().Return(true).AnyTimes()
135+
})
136+
It("does not ask again", func() {
137+
Expect(subject.PromptOptInIfNeeded("some-custom-message")).To(Succeed())
138+
})
139+
})
140+
Context("When user has answered standard optin prompt but not custom prompt", func() {
141+
BeforeEach(func() {
142+
mockToggle.EXPECT().Defined().AnyTimes().Return(true)
143+
mockToggle.EXPECT().CustomAnalyticsDefined().Return(false).AnyTimes()
144+
})
145+
It("prompts user", func() {
146+
mockToggle.EXPECT().SetCustomAnalyticsEnabled(gomock.Any()).AnyTimes()
147+
mockUI.EXPECT().Ask(gomock.Any()).Do(func(prompt string) {
148+
Expect(prompt).To(ContainSubstring("some-custom-message"))
149+
})
150+
Expect(subject.PromptOptInIfNeeded("some-custom-message")).To(Succeed())
151+
})
152+
for _, answer := range []string{"yes", "y", "yEs"} {
153+
Context("user answers "+answer, func() {
154+
BeforeEach(func() { mockUI.EXPECT().Ask(gomock.Any()).Return(answer) })
155+
It("saves optin", func() {
156+
mockToggle.EXPECT().SetCustomAnalyticsEnabled(true)
157+
158+
Expect(subject.PromptOptInIfNeeded("some-custom-message")).To(Succeed())
159+
})
160+
})
161+
}
162+
for _, answer := range []string{"no", "N", "anything", ""} {
163+
Context("user answers "+answer, func() {
164+
BeforeEach(func() { mockUI.EXPECT().Ask(gomock.Any()).Return(answer) })
165+
It("saves optout", func() {
166+
mockToggle.EXPECT().SetCustomAnalyticsEnabled(false)
167+
168+
Expect(subject.PromptOptInIfNeeded("some-custom-message")).To(Succeed())
169+
})
170+
})
171+
}
172+
Context("user hits ctrl-c", func() {
173+
BeforeEach(func() {
174+
mockUI.EXPECT().Ask(gomock.Any()).Return("")
175+
exitChan <- struct{}{}
176+
})
177+
It("does not write set a value on toggle", func() {
178+
Expect(subject.PromptOptInIfNeeded("some-custom-message")).To(MatchError("Exit while waiting for telemetry prompt"))
179+
})
86180
})
87181
})
88182
})
89183
Describe("Event", func() {
90184
Context("opt out", func() {
91185
BeforeEach(func() {
92-
mockToggle.EXPECT().Get().AnyTimes().Return(false)
186+
mockToggle.EXPECT().Enabled().AnyTimes().Return(false)
93187
})
94188
It("does nothing and succeeds", func() {
95189
Expect(subject.Event("anevent", map[string]interface{}{"mykey": "myval"})).To(Succeed())
96190
})
97191
})
98192
Context("opt in", func() {
99193
BeforeEach(func() {
100-
mockToggle.EXPECT().Get().AnyTimes().Return(true)
194+
mockToggle.EXPECT().Enabled().AnyTimes().Return(true)
101195
mockToggle.EXPECT().GetProps().AnyTimes().Return(map[string]interface{}{
102196
"type": "cf.1.2.3.iso",
103197
})

0 commit comments

Comments
 (0)