riddler 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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