riddler 0.1.0 → 0.2.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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +105 -6
  4. data/lib/riddler.rb +52 -3
  5. data/lib/riddler/configuration.rb +11 -0
  6. data/lib/riddler/context.rb +40 -2
  7. data/lib/riddler/context_builder.rb +35 -0
  8. data/lib/riddler/context_builders/faraday_builder.rb +23 -0
  9. data/lib/riddler/context_builders/user_agent.rb +34 -0
  10. data/lib/riddler/context_director.rb +67 -0
  11. data/lib/riddler/drops/hash_drop.rb +29 -0
  12. data/lib/riddler/element.rb +15 -14
  13. data/lib/riddler/elements/heading.rb +0 -1
  14. data/lib/riddler/elements/image.rb +21 -0
  15. data/lib/riddler/elements/link.rb +30 -0
  16. data/lib/riddler/elements/text.rb +17 -0
  17. data/lib/riddler/elements/variant.rb +21 -0
  18. data/lib/riddler/includeable.rb +19 -0
  19. data/lib/riddler/protobuf/content_definition.proto +51 -0
  20. data/lib/riddler/protobuf/content_definition_pb.rb +33 -0
  21. data/lib/riddler/protobuf/content_management.proto +35 -0
  22. data/lib/riddler/protobuf/content_management_pb.rb +43 -0
  23. data/lib/riddler/protobuf/content_management_services_pb.rb +27 -0
  24. data/lib/riddler/protobuf/slug.proto +61 -0
  25. data/lib/riddler/protobuf/slug_pb.rb +52 -0
  26. data/lib/riddler/step.rb +16 -16
  27. data/lib/riddler/steps/content.rb +5 -5
  28. data/lib/riddler/steps/variant.rb +23 -0
  29. data/lib/riddler/test_generator.rb +73 -0
  30. data/lib/riddler/use_cases.rb +7 -0
  31. data/lib/riddler/use_cases/admin_preview_step.rb +32 -0
  32. data/lib/riddler/use_cases/complete_interaction.rb +20 -0
  33. data/lib/riddler/use_cases/dismiss_interaction.rb +20 -0
  34. data/lib/riddler/use_cases/preview_context.rb +28 -0
  35. data/lib/riddler/use_cases/preview_step.rb +16 -5
  36. data/lib/riddler/use_cases/show_content_definition.rb +42 -0
  37. data/lib/riddler/use_cases/show_slug.rb +138 -0
  38. data/lib/riddler/version.rb +1 -1
  39. data/riddler.gemspec +12 -8
  40. metadata +100 -10
  41. data/.gitignore +0 -8
  42. data/.travis.yml +0 -7
  43. data/Gemfile +0 -6
  44. data/Gemfile.lock +0 -58
  45. data/Rakefile +0 -10
  46. data/bin/console +0 -14
  47. data/bin/setup +0 -8
@@ -0,0 +1,52 @@
1
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
2
+ # source: riddler/protobuf/slug.proto
3
+
4
+ require 'google/protobuf'
5
+
6
+ require 'google/protobuf/timestamp_pb'
7
+ Google::Protobuf::DescriptorPool.generated_pool.build do
8
+ add_message "riddler.protobuf.Slug" do
9
+ optional :id, :string, 1
10
+ optional :created_at, :message, 2, "google.protobuf.Timestamp"
11
+ optional :updated_at, :message, 3, "google.protobuf.Timestamp"
12
+ optional :name, :string, 4
13
+ optional :status, :enum, 5, "riddler.protobuf.SlugStatus"
14
+ optional :content_definition_id, :string, 6
15
+ optional :interaction_identity, :string, 7
16
+ optional :target_predicate, :string, 8
17
+ end
18
+ add_message "riddler.protobuf.EventCount" do
19
+ optional :event_name, :string, 1
20
+ optional :count, :int32, 2
21
+ end
22
+ add_message "riddler.protobuf.SlugStats" do
23
+ optional :interval, :enum, 1, "riddler.protobuf.Interval"
24
+ repeated :event_counts, :message, 2, "riddler.protobuf.EventCount"
25
+ end
26
+ add_enum "riddler.protobuf.SlugStatus" do
27
+ value :UNKNOWN_SLUG_STATUS, 0
28
+ value :LIVE, 1
29
+ value :PAUSED, 2
30
+ end
31
+ add_enum "riddler.protobuf.Interval" do
32
+ value :UNKNOWN_INTERVAL, 0
33
+ value :SECOND, 1
34
+ value :MINUTE, 2
35
+ value :HOUR, 3
36
+ value :DAY, 4
37
+ value :WEEK, 5
38
+ value :MONTH, 6
39
+ value :QUARTER, 7
40
+ value :YEAR, 8
41
+ end
42
+ end
43
+
44
+ module Riddler
45
+ module Protobuf
46
+ Slug = Google::Protobuf::DescriptorPool.generated_pool.lookup("riddler.protobuf.Slug").msgclass
47
+ EventCount = Google::Protobuf::DescriptorPool.generated_pool.lookup("riddler.protobuf.EventCount").msgclass
48
+ SlugStats = Google::Protobuf::DescriptorPool.generated_pool.lookup("riddler.protobuf.SlugStats").msgclass
49
+ SlugStatus = Google::Protobuf::DescriptorPool.generated_pool.lookup("riddler.protobuf.SlugStatus").enummodule
50
+ Interval = Google::Protobuf::DescriptorPool.generated_pool.lookup("riddler.protobuf.Interval").enummodule
51
+ end
52
+ end
@@ -1,14 +1,21 @@
1
1
  module Riddler
2
-
3
2
  class Step
4
- attr_reader :definition, :context
3
+ include ::Riddler::Includeable
4
+
5
+ attr_reader :definition, :context, :preview_enabled
6
+
7
+ def self.subclasses
8
+ @@subclasses ||= []
9
+ end
10
+
11
+ def self.inherited subclass
12
+ self.subclasses << subclass
13
+ end
5
14
 
6
15
  def self.for definition, context
7
- # This should be "type" not "object"
8
- step_type = definition["object"]
16
+ step_type = definition["type"]
9
17
 
10
- # Maybe this should be a registry
11
- klass = subclasses.detect { |klass| klass.type == step_type }
18
+ klass = subclasses.detect { |k| k.type == step_type }
12
19
 
13
20
  klass.new definition, context
14
21
  end
@@ -18,20 +25,13 @@ module Riddler
18
25
  @context = context
19
26
  end
20
27
 
21
- #def id
22
- # definition["id"]
23
- #end
24
-
25
- #def type
26
- # self.class.type
27
- #end
28
-
29
28
  def to_hash
30
29
  {
30
+ content_type: "step",
31
31
  type: self.class.type,
32
- id: definition["id"]
32
+ id: definition["id"],
33
+ name: definition["name"]
33
34
  }
34
35
  end
35
36
  end
36
-
37
37
  end
@@ -1,15 +1,16 @@
1
1
  module Riddler
2
2
  module Steps
3
-
4
3
  class Content < ::Riddler::Step
5
4
  def self.type
6
5
  "content"
7
6
  end
8
7
 
9
8
  def elements
10
- @elements ||= definition["elements"].map do |element_definition|
11
- ::Riddler::Element.for element_definition, context
12
- end
9
+ @elements ||= definition["elements"]
10
+ .map do |element_definition|
11
+ ::Riddler::Element.for element_definition, context
12
+ end
13
+ .select(&:include?)
13
14
  end
14
15
 
15
16
  def to_hash
@@ -20,6 +21,5 @@ module Riddler
20
21
  hash
21
22
  end
22
23
  end
23
-
24
24
  end
25
25
  end
@@ -0,0 +1,23 @@
1
+ module Riddler
2
+ module Steps
3
+ class Variant < ::Riddler::Step
4
+ def self.type
5
+ "variant"
6
+ end
7
+
8
+ def steps
9
+ @steps ||= definition["steps"].map do |hash|
10
+ ::Riddler::Step.for hash, context
11
+ end
12
+ end
13
+
14
+ def included_step
15
+ steps.detect &:include?
16
+ end
17
+
18
+ def to_hash
19
+ included_step&.to_hash
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,73 @@
1
+ require "yaml"
2
+
3
+ module ::Riddler
4
+ class TestGenerator
5
+ include ERB::Util
6
+ attr_reader :project_root, :input_filename, :test_case
7
+
8
+ def initialize project_root, input_filename
9
+ @project_root = project_root
10
+ @input_filename = input_filename
11
+ @test_case = YAML.safe_load File.read input_filename
12
+ end
13
+
14
+ def test_case_name
15
+ test_case["name"]
16
+ end
17
+
18
+ def class_name
19
+ classify test_case_name
20
+ end
21
+
22
+ def definition
23
+ test_case["definition"]
24
+ end
25
+
26
+ def tests
27
+ test_case["tests"]
28
+ end
29
+
30
+ def generate
31
+ folder = input_filename.dirname.basename.to_s
32
+ output_filename = project_root.join *%W[ test cases #{folder} #{test_case_name}_test.rb ]
33
+ puts "Generating #{output_filename.relative_path_from(project_root)}"
34
+ output_filename.dirname.mkdir unless output_filename.dirname.exist?
35
+ File.write output_filename, render
36
+ end
37
+
38
+ private
39
+
40
+ def render
41
+ ERB.new(test_class_template).result(binding)
42
+ end
43
+
44
+ def classify string
45
+ string.split('_').collect!{ |w| w.capitalize }.join
46
+ end
47
+
48
+ def test_class_template
49
+ <<~TEMPLATE
50
+ require "test_helper"
51
+
52
+ class <%= class_name %>Test < ::Minitest::Test
53
+ attr_reader :definition
54
+
55
+ def setup
56
+ @definition = <%= definition %>
57
+ end
58
+ <% tests.each do |test| %>
59
+ def test_<%= test["name"] %>
60
+ expected_result = <% if test["result"].nil? %>nil<% else %><%= test["result"] %><% end %>
61
+ context = <% if test["context"].nil? %>nil<% else %><%= test["context"] %><% end %>
62
+
63
+ result = ::Riddler.render definition, context
64
+ result&.transform_keys! { |k| k.to_s }
65
+
66
+ assert_equal_hash expected_result, result
67
+ end
68
+ <% end %>
69
+ end
70
+ TEMPLATE
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,7 @@
1
+ require "riddler/use_cases/admin_preview_step"
2
+ require "riddler/use_cases/complete_interaction"
3
+ require "riddler/use_cases/dismiss_interaction"
4
+ require "riddler/use_cases/preview_context"
5
+ require "riddler/use_cases/preview_step"
6
+ require "riddler/use_cases/show_content_definition"
7
+ require "riddler/use_cases/show_slug"
@@ -0,0 +1,32 @@
1
+ module Riddler
2
+ module UseCases
3
+ class AdminPreviewStep
4
+ attr_reader :definition, :preview_context_data, :context, :step
5
+
6
+ def initialize definition, preview_context_data: {}
7
+ @definition = definition
8
+ @preview_context_data = preview_context_data
9
+ @context = ::Riddler::Context.new preview_context_data
10
+ @step = ::Riddler::Step.for definition, context
11
+ end
12
+
13
+ def process
14
+ if step.include?
15
+ hash = step.to_hash
16
+ return hash unless hash.nil?
17
+
18
+ {
19
+ response_code: 204,
20
+ message: "There was no step to include"
21
+ }
22
+ else
23
+ {
24
+ response_code: 204,
25
+ include_predicate: step.include_predicate,
26
+ message: "Excluded - the include_predicate returned false"
27
+ }
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,20 @@
1
+ module Riddler
2
+ module UseCases
3
+ class CompleteInteraction
4
+ attr_reader :interaction_repo, :interaction_id
5
+
6
+ def initialize interaction_repo:, interaction_id:
7
+ @interaction_repo = interaction_repo
8
+ @interaction_id = interaction_id
9
+ end
10
+
11
+ def process
12
+ interaction = interaction_repo.find_by id: interaction_id
13
+ return if interaction.nil?
14
+
15
+ interaction.complete
16
+ interaction_repo.update interaction
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ module Riddler
2
+ module UseCases
3
+ class DismissInteraction
4
+ attr_reader :interaction_repo, :interaction_id, :interaction
5
+
6
+ def initialize interaction_repo:, interaction_id:
7
+ @interaction_repo = interaction_repo
8
+ @interaction_id = interaction_id
9
+ end
10
+
11
+ def process
12
+ @interaction = interaction_repo.find_by id: interaction_id
13
+ return if interaction.nil?
14
+
15
+ interaction.dismiss
16
+ interaction_repo.update interaction
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,28 @@
1
+ module Riddler
2
+ module UseCases
3
+ class PreviewContext
4
+ attr_reader :params, :headers
5
+
6
+ def initialize params: {}, headers: {}
7
+ @params = params
8
+ @headers = headers
9
+ end
10
+
11
+ def context
12
+ @context ||= generate_context
13
+ end
14
+
15
+ def process
16
+ context.to_hash
17
+ end
18
+
19
+ private
20
+
21
+ def generate_context
22
+ director = ::Riddler::ContextDirector.new params: params,
23
+ headers: headers
24
+ director.context
25
+ end
26
+ end
27
+ end
28
+ end
@@ -1,19 +1,30 @@
1
1
  module Riddler
2
2
  module UseCases
3
-
4
3
  class PreviewStep
5
- attr_reader :definition, :context, :step
4
+ attr_reader :definition, :params, :headers, :step
6
5
 
7
- def initialize definition, context = ::Riddler::Context.new
6
+ def initialize definition, params: {}, headers: {}
8
7
  @definition = definition
9
- @context = context
8
+ @params = params
9
+ @headers = headers
10
10
  @step = ::Riddler::Step.for definition, context
11
11
  end
12
12
 
13
+ def context
14
+ @context ||= generate_context
15
+ end
16
+
13
17
  def process
14
18
  step.to_hash
15
19
  end
16
- end
17
20
 
21
+ private
22
+
23
+ def generate_context
24
+ director = ::Riddler::ContextDirector.new params: params,
25
+ headers: headers
26
+ director.context
27
+ end
28
+ end
18
29
  end
19
30
  end
@@ -0,0 +1,42 @@
1
+ module Riddler
2
+ module UseCases
3
+ class ShowContentDefinition
4
+ attr_reader :content_definition_repo,
5
+ :content_definition_id, :context_director,
6
+ :content_definition, :step
7
+
8
+ def initialize content_definition_repo:, content_definition_id:, context_director:
9
+ @content_definition_repo = content_definition_repo
10
+ @content_definition_id = content_definition_id
11
+ @context_director = context_director
12
+
13
+ @content_definition = lookup_content_definition
14
+ @step = ::Riddler::Step.for content_definition.definition, context
15
+ end
16
+
17
+ def context
18
+ context_director.context
19
+ end
20
+
21
+ def process
22
+ step.to_hash
23
+ end
24
+
25
+ def excluded?
26
+ !step.include?
27
+ end
28
+
29
+ private
30
+
31
+ def lookup_content_definition
32
+ content_definition_repo.find_by id: content_definition_id
33
+ end
34
+
35
+ def generate_context
36
+ director = ::Riddler::ContextDirector.new params: params,
37
+ headers: headers
38
+ director.context
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,138 @@
1
+ module Riddler
2
+ module UseCases
3
+ class ShowSlug
4
+ attr_reader :content_definition_repo, :slug_repo, :interaction_repo,
5
+ :interaction_class, :slug_name, :context_director, :slug, :interaction
6
+
7
+ def initialize content_definition_repo:, slug_repo:, interaction_repo:,
8
+ interaction_class:, slug_name:, params: {}, headers: {}
9
+
10
+ @content_definition_repo = content_definition_repo
11
+ @slug_repo = slug_repo
12
+ @interaction_repo = interaction_repo
13
+ @interaction_class = interaction_class
14
+ @context_director = ::Riddler::ContextDirector.new params: params, headers: headers
15
+ @slug_name = slug_name
16
+ @slug = lookup_slug
17
+ end
18
+
19
+ def exists?
20
+ !slug.nil?
21
+ end
22
+
23
+ def paused?
24
+ return true if slug.nil?
25
+ slug.status == "PAUSED"
26
+ end
27
+
28
+ def dismissed?
29
+ return false unless slug_defines_identity?
30
+ find_interaction
31
+ return false if @interaction.nil?
32
+ @interaction.status == "DISMISSED"
33
+ end
34
+
35
+ def completed?
36
+ return false unless slug_defines_identity?
37
+ find_interaction
38
+ return false if @interaction.nil?
39
+ @interaction.status == "COMPLETED"
40
+ end
41
+
42
+ def targeted?
43
+ slug_targeted?
44
+ end
45
+
46
+ def excluded?
47
+ definition_use_case.excluded?
48
+ end
49
+
50
+ def process
51
+ find_interaction || create_interaction
52
+ context.assign "interaction", interaction.to_hash
53
+
54
+ content_hash = definition_use_case.process.merge \
55
+ interaction_id: interaction.id,
56
+ dismiss_url: "/interactions/#{interaction.id}/dismiss"
57
+
58
+ interaction.content_type = content_hash[:content_type]
59
+ interaction.content_id = content_hash[:id]
60
+
61
+ interaction_repo.update interaction
62
+
63
+ content_hash
64
+ end
65
+
66
+ private
67
+
68
+ def find_interaction
69
+ return nil unless request_is_unique?
70
+
71
+ @interaction ||= interaction_repo.last_by slug_id: slug.id,
72
+ identity: identity
73
+ end
74
+
75
+ def create_interaction
76
+ @interaction = interaction_class.new \
77
+ slug_name: slug.name,
78
+ slug_id: slug.id,
79
+ status: "ACTIVE",
80
+ content_definition_id: slug.content_definition_id,
81
+ identifiers: context.ids
82
+
83
+ @interaction.identity = identity if request_is_unique?
84
+
85
+ interaction_repo.create interaction
86
+ end
87
+
88
+ def definition_use_case
89
+ @definition_use_case ||= ShowContentDefinition.new \
90
+ content_definition_repo: content_definition_repo,
91
+ content_definition_id: slug.content_definition_id,
92
+ context_director: context_director
93
+ end
94
+
95
+ def context
96
+ context_director.context
97
+ end
98
+
99
+ # Only has IDs extracted - context builders have not been processed
100
+ def simple_context
101
+ context_director.simple_context
102
+ end
103
+
104
+ def identity
105
+ @identity ||= simple_context.render slug.interaction_identity
106
+ end
107
+
108
+ def blank_interaction_identity
109
+ @blank_interaction_identity ||= ::Riddler::Context.new.render slug.interaction_identity
110
+ end
111
+
112
+ def request_is_unique?
113
+ slug_defines_identity? && !interaction_identity_is_blank?
114
+ end
115
+
116
+ def interaction_identity_is_blank?
117
+ identity == blank_interaction_identity
118
+ end
119
+
120
+ def slug_defines_identity?
121
+ !(slug.interaction_identity.nil? || slug.interaction_identity == "")
122
+ end
123
+
124
+ def lookup_slug
125
+ slug_repo.find_by name: slug_name
126
+ end
127
+
128
+ def slug_targeted?
129
+ return true unless slug_has_target_predicate?
130
+ Predicator.evaluate slug.target_predicate, context.to_liquid
131
+ end
132
+
133
+ def slug_has_target_predicate?
134
+ slug.target_predicate.to_s.strip != ""
135
+ end
136
+ end
137
+ end
138
+ end