Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
b1f14036
Commit
b1f14036
authored
Nov 23, 2021
by
Jay
Committed by
Doug Stull
Nov 23, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Ensure variant when creating ExperimentSubjects
parent
9d8d82ec
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
70 additions
and
58 deletions
+70
-58
app/experiments/application_experiment.rb
app/experiments/application_experiment.rb
+2
-2
spec/experiments/application_experiment_spec.rb
spec/experiments/application_experiment_spec.rb
+68
-56
No files found.
app/experiments/application_experiment.rb
View file @
b1f14036
...
...
@@ -32,8 +32,8 @@ class ApplicationExperiment < Gitlab::Experiment # rubocop:disable Gitlab/Namesp
subject
=
value
[
:namespace
]
||
value
[
:group
]
||
value
[
:project
]
||
value
[
:user
]
||
value
[
:actor
]
return
unless
ExperimentSubject
.
valid_subject?
(
subject
)
variant
=
:experimental
if
@variant_name
!=
:control
Experiment
.
add_subject
(
name
,
variant:
variant
||
:control
,
subject:
subject
)
variant
_name
=
:experimental
if
variant
&
.
name
!=
'control'
Experiment
.
add_subject
(
name
,
variant:
variant
_name
||
:control
,
subject:
subject
)
end
def
record!
...
...
spec/experiments/application_experiment_spec.rb
View file @
b1f14036
...
...
@@ -3,7 +3,7 @@
require
'spec_helper'
RSpec
.
describe
ApplicationExperiment
,
:experiment
do
subject
{
described_class
.
new
(
'namespaced/stub'
,
**
context
)
}
subject
(
:application_experiment
)
{
described_class
.
new
(
'namespaced/stub'
,
**
context
)
}
let
(
:context
)
{
{}
}
let
(
:feature_definition
)
{
{
name:
'namespaced_stub'
,
type:
'experiment'
,
default_enabled:
false
}
}
...
...
@@ -15,7 +15,7 @@ RSpec.describe ApplicationExperiment, :experiment do
end
before
do
allow
(
subjec
t
).
to
receive
(
:enabled?
).
and_return
(
true
)
allow
(
application_experimen
t
).
to
receive
(
:enabled?
).
and_return
(
true
)
end
it
"doesn't raise an exception without a defined control"
do
...
...
@@ -26,7 +26,7 @@ RSpec.describe ApplicationExperiment, :experiment do
describe
"#enabled?"
do
before
do
allow
(
subjec
t
).
to
receive
(
:enabled?
).
and_call_original
allow
(
application_experimen
t
).
to
receive
(
:enabled?
).
and_call_original
allow
(
Feature
::
Definition
).
to
receive
(
:get
).
and_return
(
'_instance_'
)
allow
(
Gitlab
).
to
receive
(
:dev_env_or_com?
).
and_return
(
true
)
...
...
@@ -34,25 +34,25 @@ RSpec.describe ApplicationExperiment, :experiment do
end
it
"is enabled when all criteria are met"
do
expect
(
subjec
t
).
to
be_enabled
expect
(
application_experimen
t
).
to
be_enabled
end
it
"isn't enabled if the feature definition doesn't exist"
do
expect
(
Feature
::
Definition
).
to
receive
(
:get
).
with
(
'namespaced_stub'
).
and_return
(
nil
)
expect
(
subjec
t
).
not_to
be_enabled
expect
(
application_experimen
t
).
not_to
be_enabled
end
it
"isn't enabled if we're not in dev or dotcom environments"
do
expect
(
Gitlab
).
to
receive
(
:dev_env_or_com?
).
and_return
(
false
)
expect
(
subjec
t
).
not_to
be_enabled
expect
(
application_experimen
t
).
not_to
be_enabled
end
it
"isn't enabled if the feature flag state is :off"
do
expect
(
Feature
).
to
receive
(
:get
).
with
(
'namespaced_stub'
).
and_return
(
double
(
state: :off
))
expect
(
subjec
t
).
not_to
be_enabled
expect
(
application_experimen
t
).
not_to
be_enabled
end
end
...
...
@@ -60,11 +60,11 @@ RSpec.describe ApplicationExperiment, :experiment do
let
(
:should_track
)
{
true
}
before
do
allow
(
subjec
t
).
to
receive
(
:should_track?
).
and_return
(
should_track
)
allow
(
application_experimen
t
).
to
receive
(
:should_track?
).
and_return
(
should_track
)
end
it
"tracks the assignment"
,
:snowplow
do
subjec
t
.
publish
application_experimen
t
.
publish
expect_snowplow_event
(
category:
'namespaced/stub'
,
...
...
@@ -74,24 +74,24 @@ RSpec.describe ApplicationExperiment, :experiment do
end
it
"publishes to the client"
do
expect
(
subjec
t
).
to
receive
(
:publish_to_client
)
expect
(
application_experimen
t
).
to
receive
(
:publish_to_client
)
subjec
t
.
publish
application_experimen
t
.
publish
end
it
"publishes to the database if we've opted for that"
do
subjec
t
.
record!
application_experimen
t
.
record!
expect
(
subjec
t
).
to
receive
(
:publish_to_database
)
expect
(
application_experimen
t
).
to
receive
(
:publish_to_database
)
subjec
t
.
publish
application_experimen
t
.
publish
end
context
'when we should not track'
do
let
(
:should_track
)
{
false
}
it
'does not track an event to Snowplow'
,
:snowplow
do
subjec
t
.
publish
application_experimen
t
.
publish
expect_no_snowplow_event
end
...
...
@@ -102,13 +102,13 @@ RSpec.describe ApplicationExperiment, :experiment do
signature
=
{
key:
'86208ac54ca798e11f127e8b23ec396a'
,
variant:
'control'
}
expect
(
Gon
).
to
receive
(
:push
).
with
({
experiment:
{
'namespaced/stub'
=>
hash_including
(
signature
)
}
},
true
)
subjec
t
.
publish_to_client
application_experimen
t
.
publish_to_client
end
it
"handles when Gon raises exceptions (like when it can't be pushed into)"
do
expect
(
Gon
).
to
receive
(
:push
).
and_raise
(
NoMethodError
)
expect
{
subjec
t
.
publish_to_client
}.
not_to
raise_error
expect
{
application_experimen
t
.
publish_to_client
}.
not_to
raise_error
end
context
'when we should not track'
do
...
...
@@ -117,7 +117,7 @@ RSpec.describe ApplicationExperiment, :experiment do
it
'returns early'
do
expect
(
Gon
).
not_to
receive
(
:push
)
subjec
t
.
publish_to_client
application_experimen
t
.
publish_to_client
end
end
end
...
...
@@ -125,13 +125,15 @@ RSpec.describe ApplicationExperiment, :experiment do
describe
'#publish_to_database'
do
using
RSpec
::
Parameterized
::
TableSyntax
let
(
:publish_to_database
)
{
application_experiment
.
publish_to_database
}
shared_examples
'does not record to the database'
do
it
'does not create an experiment record'
do
expect
{
subject
.
publish_to_database
}.
not_to
change
(
Experiment
,
:count
)
expect
{
publish_to_database
}.
not_to
change
(
Experiment
,
:count
)
end
it
'does not create an experiment subject record'
do
expect
{
subject
.
publish_to_database
}.
not_to
change
(
ExperimentSubject
,
:count
)
expect
{
publish_to_database
}.
not_to
change
(
ExperimentSubject
,
:count
)
end
end
...
...
@@ -139,16 +141,16 @@ RSpec.describe ApplicationExperiment, :experiment do
let
(
:context
)
{
{
context_key
=>
context_value
}
}
where
(
:context_key
,
:context_value
,
:object_type
)
do
:namespace
|
build
(
:namespace
)
|
:namespace
:group
|
build
(
:namespace
)
|
:namespace
:project
|
build
(
:project
)
|
:project
:user
|
build
(
:user
)
|
:user
:actor
|
build
(
:user
)
|
:user
:namespace
|
build
(
:namespace
,
id:
non_existing_record_id
)
|
:namespace
:group
|
build
(
:namespace
,
id:
non_existing_record_id
)
|
:namespace
:project
|
build
(
:project
,
id:
non_existing_record_id
)
|
:project
:user
|
build
(
:user
,
id:
non_existing_record_id
)
|
:user
:actor
|
build
(
:user
,
id:
non_existing_record_id
)
|
:user
end
with_them
do
it
'creates an experiment and experiment subject record'
do
expect
{
subject
.
publish_to_database
}.
to
change
(
Experiment
,
:count
).
by
(
1
)
expect
{
publish_to_database
}.
to
change
(
Experiment
,
:count
).
by
(
1
)
expect
(
Experiment
.
last
.
name
).
to
eq
(
'namespaced/stub'
)
expect
(
ExperimentSubject
.
last
.
send
(
object_type
)).
to
eq
(
context
[
context_key
])
...
...
@@ -156,6 +158,16 @@ RSpec.describe ApplicationExperiment, :experiment do
end
end
context
"when experiment hasn't ran"
do
let
(
:context
)
{
{
user:
create
(
:user
)
}
}
it
'sets a variant on the experiment subject'
do
publish_to_database
expect
(
ExperimentSubject
.
last
.
variant
).
to
eq
(
'control'
)
end
end
context
'when there is not a usable subject'
do
let
(
:context
)
{
{
context_key
=>
context_value
}
}
...
...
@@ -183,15 +195,15 @@ RSpec.describe ApplicationExperiment, :experiment do
end
it
"doesn't track if we shouldn't track"
do
allow
(
subjec
t
).
to
receive
(
:should_track?
).
and_return
(
false
)
allow
(
application_experimen
t
).
to
receive
(
:should_track?
).
and_return
(
false
)
subjec
t
.
track
(
:action
)
application_experimen
t
.
track
(
:action
)
expect_no_snowplow_event
end
it
"tracks the event with the expected arguments and merged contexts"
do
subjec
t
.
track
(
:action
,
property:
'_property_'
,
context:
[
fake_context
])
application_experimen
t
.
track
(
:action
,
property:
'_property_'
,
context:
[
fake_context
])
expect_snowplow_event
(
category:
'namespaced/stub'
,
...
...
@@ -233,7 +245,7 @@ RSpec.describe ApplicationExperiment, :experiment do
describe
"#key_for"
do
it
"generates MD5 hashes"
do
expect
(
subjec
t
.
key_for
(
foo: :bar
)).
to
eq
(
'6f9ac12afdb9b58c2f19a136d09f9153'
)
expect
(
application_experimen
t
.
key_for
(
foo: :bar
)).
to
eq
(
'6f9ac12afdb9b58c2f19a136d09f9153'
)
end
end
...
...
@@ -256,26 +268,26 @@ RSpec.describe ApplicationExperiment, :experiment do
with_them
do
it
"returns the url or nil if invalid"
do
allow
(
Gitlab
).
to
receive
(
:dev_env_or_com?
).
and_return
(
true
)
expect
(
subjec
t
.
process_redirect_url
(
url
)).
to
eq
(
processed_url
)
expect
(
application_experimen
t
.
process_redirect_url
(
url
)).
to
eq
(
processed_url
)
end
it
"considers all urls invalid when not on dev or com"
do
allow
(
Gitlab
).
to
receive
(
:dev_env_or_com?
).
and_return
(
false
)
expect
(
subjec
t
.
process_redirect_url
(
url
)).
to
be_nil
expect
(
application_experimen
t
.
process_redirect_url
(
url
)).
to
be_nil
end
end
it
"generates the correct urls based on where the engine was mounted"
do
url
=
Rails
.
application
.
routes
.
url_helpers
.
experiment_redirect_url
(
subjec
t
,
url:
'https://docs.gitlab.com'
)
expect
(
url
).
to
include
(
"/-/experiment/namespaced%2Fstub:
#{
subjec
t
.
context
.
key
}
?https://docs.gitlab.com"
)
url
=
Rails
.
application
.
routes
.
url_helpers
.
experiment_redirect_url
(
application_experimen
t
,
url:
'https://docs.gitlab.com'
)
expect
(
url
).
to
include
(
"/-/experiment/namespaced%2Fstub:
#{
application_experimen
t
.
context
.
key
}
?https://docs.gitlab.com"
)
end
end
context
"when resolving variants"
do
it
"uses the default value as specified in the yaml"
do
expect
(
Feature
).
to
receive
(
:enabled?
).
with
(
'namespaced_stub'
,
subjec
t
,
type: :experiment
,
default_enabled: :yaml
)
expect
(
Feature
).
to
receive
(
:enabled?
).
with
(
'namespaced_stub'
,
application_experimen
t
,
type: :experiment
,
default_enabled: :yaml
)
expect
(
subjec
t
.
variant
.
name
).
to
eq
(
'control'
)
expect
(
application_experimen
t
.
variant
.
name
).
to
eq
(
'control'
)
end
context
"when rolled out to 100%"
do
...
...
@@ -284,10 +296,10 @@ RSpec.describe ApplicationExperiment, :experiment do
end
it
"returns the first variant name"
do
subjec
t
.
try
(
:variant1
)
{}
subjec
t
.
try
(
:variant2
)
{}
application_experimen
t
.
try
(
:variant1
)
{}
application_experimen
t
.
try
(
:variant2
)
{}
expect
(
subjec
t
.
variant
.
name
).
to
eq
(
'variant1'
)
expect
(
application_experimen
t
.
variant
.
name
).
to
eq
(
'variant1'
)
end
end
end
...
...
@@ -298,18 +310,18 @@ RSpec.describe ApplicationExperiment, :experiment do
before
do
allow
(
Gitlab
::
Experiment
::
Configuration
).
to
receive
(
:cache
).
and_call_original
cache
.
clear
(
key:
subjec
t
.
name
)
cache
.
clear
(
key:
application_experimen
t
.
name
)
subjec
t
.
use
{
}
# setup the control
subjec
t
.
try
{
}
# setup the candidate
application_experimen
t
.
use
{
}
# setup the control
application_experimen
t
.
try
{
}
# setup the candidate
end
it
"caches the variant determined by the variant resolver"
do
expect
(
subjec
t
.
variant
.
name
).
to
eq
(
'candidate'
)
# we should be in the experiment
expect
(
application_experimen
t
.
variant
.
name
).
to
eq
(
'candidate'
)
# we should be in the experiment
subjec
t
.
run
application_experimen
t
.
run
expect
(
subjec
t
.
cache
.
read
).
to
eq
(
'candidate'
)
expect
(
application_experimen
t
.
cache
.
read
).
to
eq
(
'candidate'
)
end
it
"doesn't cache a variant if we don't explicitly provide one"
do
...
...
@@ -320,11 +332,11 @@ RSpec.describe ApplicationExperiment, :experiment do
# the control.
stub_feature_flags
(
namespaced_stub:
false
)
# simulate being not rolled out
expect
(
subjec
t
.
variant
.
name
).
to
eq
(
'control'
)
# if we ask, it should be control
expect
(
application_experimen
t
.
variant
.
name
).
to
eq
(
'control'
)
# if we ask, it should be control
subjec
t
.
run
application_experimen
t
.
run
expect
(
subjec
t
.
cache
.
read
).
to
be_nil
expect
(
application_experimen
t
.
cache
.
read
).
to
be_nil
end
it
"caches a control variant if we assign it specifically"
do
...
...
@@ -332,27 +344,27 @@ RSpec.describe ApplicationExperiment, :experiment do
# that this context will always get the control variant unless we delete
# the field from the cache (or clear the entire experiment cache) -- or
# write code that would specify a different variant.
subjec
t
.
run
(
:control
)
application_experimen
t
.
run
(
:control
)
expect
(
subjec
t
.
cache
.
read
).
to
eq
(
'control'
)
expect
(
application_experimen
t
.
cache
.
read
).
to
eq
(
'control'
)
end
context
"arbitrary attributes"
do
before
do
subject
.
cache
.
store
.
clear
(
key:
subjec
t
.
name
+
'_attrs'
)
application_experiment
.
cache
.
store
.
clear
(
key:
application_experimen
t
.
name
+
'_attrs'
)
end
it
"sets and gets attributes about an experiment"
do
subjec
t
.
cache
.
attr_set
(
:foo
,
:bar
)
application_experimen
t
.
cache
.
attr_set
(
:foo
,
:bar
)
expect
(
subjec
t
.
cache
.
attr_get
(
:foo
)).
to
eq
(
'bar'
)
expect
(
application_experimen
t
.
cache
.
attr_get
(
:foo
)).
to
eq
(
'bar'
)
end
it
"increments a value for an experiment"
do
expect
(
subjec
t
.
cache
.
attr_get
(
:foo
)).
to
be_nil
expect
(
application_experimen
t
.
cache
.
attr_get
(
:foo
)).
to
be_nil
expect
(
subjec
t
.
cache
.
attr_inc
(
:foo
)).
to
eq
(
1
)
expect
(
subjec
t
.
cache
.
attr_inc
(
:foo
)).
to
eq
(
2
)
expect
(
application_experimen
t
.
cache
.
attr_inc
(
:foo
)).
to
eq
(
1
)
expect
(
application_experimen
t
.
cache
.
attr_inc
(
:foo
)).
to
eq
(
2
)
end
end
end
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment