verdict 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ec6df6bdd38dc947ff1072e1dce844ab4d3ddd522701c8de578a35b4c9c51be8
4
- data.tar.gz: e6b62b2c579a363f37526079389bea4cb16eb28ed7573dec2d3fa8f8c74ee4ed
3
+ metadata.gz: f05448f375933cba0d01c10a4e302b684242297e779d96cd3bf45cff2f6fea74
4
+ data.tar.gz: d8782ab103377d758a550f36f16fa40cec2caa2662333a99d180aab637985126
5
5
  SHA512:
6
- metadata.gz: 1399fa37c4575888ffb83aea54469784db11ceef1ebc99f5c1fa5de75b936f460b1f34dc717c28f4f19a6e969c3a85a3faad022119113cd94f754ad61b4fdb85
7
- data.tar.gz: 7f1836b8d23ca79a4338f0a4f4ed244ebe7bdbd8b9f03529f2cb00e110c280b1033e112928da900d805112eb0036e0f74278ea021514016541accf01d8c3308c
6
+ metadata.gz: 14317430460d1fcb88748507d20fd8be9f0a271d434951c6deb4b89203eeb3a5675a7c88e5e1d79bd6404c275bc3a9a3b35558a09df51284c100613963c4ba51
7
+ data.tar.gz: 39ad70804de5c962785117525ccd668155efd228860735de13569316d22e6e3ed65e050842753c7283bc21c299ee237a00885686d6963a94e3130594f5c61b21
@@ -1,3 +1,6 @@
1
+ ## v0.14.0
2
+ * Add optional experiment definition method `schedule_stop_new_assignment_timestamp` to support limiting experiment's assignment lifetime with another pre-determined time interval. It allows users to have an assignment cooldown period for stable analysis of the experiment results. Experiment's lifetime now becomes: start experiment -> stop new assignments -> end experiment.
3
+
1
4
  ## v0.13.0
2
5
 
3
6
  * Add optional experiment definition methods `schedule_start_timestamp` and `schedule_end_timestamp` to support limiting experiment's lifetime in a pre-determined time interval.
@@ -52,9 +52,17 @@ class Verdict::Experiment
52
52
  return self
53
53
  end
54
54
 
55
- # Optional: Together with the "end timestamp", limits the experiment run timeline within
56
- # the given time interval. When experiment is not scheduled, subject switch returns nil.
57
- # This is useful when the experimenter requires the experiment to run in a strict timeline.
55
+ # Optional: Together with the "end_timestamp" and "stop_new_assignment_timestamp", limits the experiment run timeline within
56
+ # the given time interval.
57
+ #
58
+ # Timestamps definitions:
59
+ # start_timestamp: Experiment's start time. No assignments are made i.e. switch will return nil before this timestamp.
60
+ # stop_new_assignment_timestamp: Experiment's new assignment stop time. No new assignments are made
61
+ # i.e. switch returns nil for new assignments but the existing assignments are preserved.
62
+ # end_timestamp: Experiment's end time. No assignments are made i.e. switch returns nil after this timestamp.
63
+ #
64
+ # Experiment run timeline:
65
+ # start_timestamp -> (new assignments occur) -> stop_new_assignment_timestamp -> (no new assignments occur) -> end_timestamp
58
66
  def schedule_start_timestamp(timestamp)
59
67
  @schedule_start_timestamp = timestamp
60
68
  end
@@ -63,6 +71,10 @@ class Verdict::Experiment
63
71
  @schedule_end_timestamp = timestamp
64
72
  end
65
73
 
74
+ def schedule_stop_new_assignment_timestamp(timestamp)
75
+ @schedule_stop_new_assignment_timestamp = timestamp
76
+ end
77
+
66
78
  def rollout_percentage(percentage, rollout_group_name = :enabled)
67
79
  groups(Verdict::Segmenters::RolloutSegmenter) do
68
80
  group rollout_group_name, percentage
@@ -136,7 +148,7 @@ class Verdict::Experiment
136
148
  subject_identifier = retrieve_subject_identifier(subject)
137
149
  assignment = if previous_assignment
138
150
  previous_assignment
139
- elsif subject_qualifies?(subject, context)
151
+ elsif subject_qualifies?(subject, context) && is_make_new_assignments?
140
152
  group = segmenter.assign(subject_identifier, subject, context)
141
153
  subject_assignment(subject, group, nil, group.nil?)
142
154
  else
@@ -268,12 +280,16 @@ class Verdict::Experiment
268
280
  private
269
281
 
270
282
  def is_scheduled?
271
- if @schedule_start_timestamp and @schedule_start_timestamp > Time.now
283
+ if @schedule_start_timestamp && @schedule_start_timestamp > Time.now
272
284
  return false
273
285
  end
274
- if @schedule_end_timestamp and @schedule_end_timestamp < Time.now
286
+ if @schedule_end_timestamp && @schedule_end_timestamp <= Time.now
275
287
  return false
276
288
  end
277
289
  return true
278
290
  end
291
+
292
+ def is_make_new_assignments?
293
+ return !(@schedule_stop_new_assignment_timestamp && @schedule_stop_new_assignment_timestamp <= Time.now)
294
+ end
279
295
  end
@@ -1,3 +1,3 @@
1
1
  module Verdict
2
- VERSION = "0.13.0"
2
+ VERSION = "0.14.0"
3
3
  end
@@ -565,6 +565,82 @@ class ExperimentTest < Minitest::Test
565
565
  end
566
566
  end
567
567
 
568
+ def test_is_stop_new_assignments
569
+ e = Verdict::Experiment.new('test') do
570
+ groups do
571
+ group :a, :half
572
+ group :b, :half
573
+ end
574
+ schedule_stop_new_assignment_timestamp Time.new(2020, 1, 15)
575
+ end
576
+
577
+ # new assignments stopped after the stop timestamp
578
+ Timecop.freeze(Time.new(2020, 1, 16)) do
579
+ assert !e.send(:is_make_new_assignments?)
580
+ assert_nil e.switch(1)
581
+ end
582
+ # new assignments didn't stop before the stop timestamp
583
+ Timecop.freeze(Time.new(2020, 1, 3)) do
584
+ assert e.send(:is_make_new_assignments?)
585
+ assert :a, e.switch(2)
586
+ end
587
+ end
588
+
589
+ def test_switch_preserves_old_assignments_after_stop_new_assignments_timestamp
590
+ e = Verdict::Experiment.new('test') do
591
+ groups do
592
+ group :a, :half
593
+ group :b, :half
594
+ end
595
+ end
596
+
597
+ assert_equal :a, e.switch(1)
598
+
599
+ e.schedule_stop_new_assignment_timestamp Time.new(2020, 4, 15)
600
+
601
+ # switch respects to stop new assignment timestamp, old assignment preserves, new assignment returns nil
602
+ Timecop.freeze(Time.new(2020, 4, 16)) do
603
+ assert !e.send(:is_make_new_assignments?)
604
+ # old assignment stay the same
605
+ assert_equal :a, e.switch(1)
606
+ # new assignment returns nil
607
+ assert_nil e.switch(2)
608
+ end
609
+ end
610
+
611
+ def test_schedule_start_timestamp_and_stop_new_assignemnt_timestamp_are_inclusive_but_end_timestamp_is_exclusive
612
+ e = Verdict::Experiment.new('test') do
613
+ groups do
614
+ group :a, :half
615
+ group :b, :half
616
+ end
617
+
618
+ schedule_start_timestamp Time.new(2020, 1, 1)
619
+ schedule_stop_new_assignment_timestamp Time.new(2020, 1, 15)
620
+ schedule_end_timestamp Time.new(2020, 1, 31)
621
+ end
622
+
623
+ # start_timestamp is included
624
+ Timecop.freeze(Time.new(2020, 1, 1)) do
625
+ assert e.send(:is_scheduled?)
626
+ assert_equal :a, e.switch(1)
627
+ end
628
+
629
+ # stop_new_assignment_timestamp is included
630
+ Timecop.freeze(Time.new(2020, 1, 15)) do
631
+ assert !e.send(:is_make_new_assignments?)
632
+ # old assignment preserved
633
+ assert_equal :a, e.switch(1)
634
+ # new assignment returns nil
635
+ assert_nil e.switch(2)
636
+ end
637
+
638
+ # end_timestamp is excluded
639
+ Timecop.freeze(Time.new(2020, 1, 31)) do
640
+ assert !e.send(:is_scheduled?)
641
+ assert_nil e.switch(1)
642
+ end
643
+ end
568
644
  private
569
645
 
570
646
  def redis
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: verdict
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.0
4
+ version: 0.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-04-03 00:00:00.000000000 Z
11
+ date: 2020-04-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest