riddler_admin 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 (98) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/riddler_admin/application.js +0 -1
  3. data/app/assets/javascripts/riddler_admin/preview_contexts.js +2 -0
  4. data/app/assets/javascripts/riddler_admin/publish_requests.js +2 -0
  5. data/app/assets/javascripts/riddler_admin/steps.js +36 -22
  6. data/app/assets/stylesheets/riddler_admin/application.scss +3 -0
  7. data/app/assets/stylesheets/riddler_admin/preview_contexts.css +4 -0
  8. data/app/assets/stylesheets/riddler_admin/publish_requests.css +4 -0
  9. data/app/controllers/riddler_admin/application_controller.rb +24 -2
  10. data/app/controllers/riddler_admin/dashboard_controller.rb +8 -0
  11. data/app/controllers/riddler_admin/elements_controller.rb +6 -2
  12. data/app/controllers/riddler_admin/preview_contexts_controller.rb +60 -0
  13. data/app/controllers/riddler_admin/publish_requests_controller.rb +90 -0
  14. data/app/controllers/riddler_admin/slugs_controller.rb +67 -0
  15. data/app/controllers/riddler_admin/steps_controller.rb +73 -20
  16. data/app/helpers/riddler_admin/application_helper.rb +19 -0
  17. data/app/helpers/riddler_admin/preview_contexts_helper.rb +4 -0
  18. data/app/helpers/riddler_admin/publish_requests_helper.rb +4 -0
  19. data/app/models/riddler_admin/application_record.rb +4 -4
  20. data/app/models/riddler_admin/content_definition.rb +87 -0
  21. data/app/models/riddler_admin/element.rb +52 -10
  22. data/app/models/riddler_admin/elements/heading.rb +6 -0
  23. data/app/models/riddler_admin/elements/image.rb +18 -0
  24. data/app/models/riddler_admin/elements/link.rb +18 -0
  25. data/app/models/riddler_admin/elements/{copy.rb → text.rb} +7 -1
  26. data/app/models/riddler_admin/elements/variant.rb +19 -0
  27. data/app/models/riddler_admin/preview_context.rb +83 -0
  28. data/app/models/riddler_admin/publish_request.rb +57 -0
  29. data/app/models/riddler_admin/slug.rb +90 -0
  30. data/app/models/riddler_admin/step.rb +66 -4
  31. data/app/models/riddler_admin/steps/variant.rb +19 -0
  32. data/app/validators/parseable_predicate_validator.rb +8 -0
  33. data/app/views/layouts/riddler_admin/application.html.erb +11 -4
  34. data/app/views/riddler_admin/_element.html.erb +14 -10
  35. data/app/views/riddler_admin/_element_container.html.erb +11 -0
  36. data/app/views/riddler_admin/_navbar.html.erb +29 -0
  37. data/app/views/riddler_admin/_preview_step.html.erb +39 -0
  38. data/app/views/riddler_admin/_step.html.erb +35 -0
  39. data/app/views/riddler_admin/_step_container.html.erb +11 -0
  40. data/app/views/riddler_admin/dashboard/index.html.erb +44 -0
  41. data/app/views/riddler_admin/elements/_form.html.erb +37 -0
  42. data/app/views/riddler_admin/elements/_new.html.erb +41 -8
  43. data/app/views/riddler_admin/elements/_show.html.erb +3 -1
  44. data/app/views/riddler_admin/elements/edit.js.erb +2 -0
  45. data/app/views/riddler_admin/elements/heading/_detail.html.erb +0 -0
  46. data/app/views/riddler_admin/elements/heading/_form.html.erb +8 -2
  47. data/app/views/riddler_admin/elements/image/_detail.html.erb +0 -0
  48. data/app/views/riddler_admin/elements/image/_form.html.erb +12 -0
  49. data/app/views/riddler_admin/elements/link/_detail.html.erb +0 -0
  50. data/app/views/riddler_admin/elements/link/_form.html.erb +12 -0
  51. data/app/views/riddler_admin/elements/show.js.erb +2 -2
  52. data/app/views/riddler_admin/elements/text/_detail.html.erb +0 -0
  53. data/app/views/riddler_admin/elements/text/_form.html.erb +4 -0
  54. data/app/views/riddler_admin/elements/variant/_detail.html.erb +6 -0
  55. data/app/views/riddler_admin/elements/variant/_form.html.erb +1 -0
  56. data/app/views/riddler_admin/preview_contexts/_form.html.erb +44 -0
  57. data/app/views/riddler_admin/preview_contexts/edit.html.erb +10 -0
  58. data/app/views/riddler_admin/preview_contexts/index.html.erb +43 -0
  59. data/app/views/riddler_admin/preview_contexts/new.html.erb +10 -0
  60. data/app/views/riddler_admin/preview_contexts/show.html.erb +45 -0
  61. data/app/views/riddler_admin/publish_requests/_actions.html.erb +19 -0
  62. data/app/views/riddler_admin/publish_requests/_form.html.erb +53 -0
  63. data/app/views/riddler_admin/publish_requests/approve.js.erb +2 -0
  64. data/app/views/riddler_admin/publish_requests/edit.html.erb +10 -0
  65. data/app/views/riddler_admin/publish_requests/index.html.erb +62 -0
  66. data/app/views/riddler_admin/publish_requests/new.html.erb +10 -0
  67. data/app/views/riddler_admin/publish_requests/publish.js.erb +2 -0
  68. data/app/views/riddler_admin/publish_requests/show.html.erb +25 -0
  69. data/app/views/riddler_admin/slugs/_form.html.erb +48 -0
  70. data/app/views/riddler_admin/slugs/edit.html.erb +10 -0
  71. data/app/views/riddler_admin/slugs/index.html.erb +44 -0
  72. data/app/views/riddler_admin/slugs/new.html.erb +10 -0
  73. data/app/views/riddler_admin/slugs/show.html.erb +18 -0
  74. data/app/views/riddler_admin/steps/_definition.html.erb +11 -0
  75. data/app/views/riddler_admin/steps/_form.html.erb +45 -17
  76. data/app/views/riddler_admin/steps/_new_sub_step.html.erb +56 -0
  77. data/app/views/riddler_admin/steps/_visual_preview.html.erb +24 -0
  78. data/app/views/riddler_admin/steps/content/_show.html.erb +53 -25
  79. data/app/views/riddler_admin/steps/edit.html.erb +15 -4
  80. data/app/views/riddler_admin/steps/index.html.erb +24 -10
  81. data/app/views/riddler_admin/steps/internal_preview.js.erb +5 -0
  82. data/app/views/riddler_admin/steps/new.html.erb +10 -5
  83. data/app/views/riddler_admin/steps/new.js.erb +3 -0
  84. data/app/views/riddler_admin/steps/preview.json.erb +1 -0
  85. data/app/views/riddler_admin/steps/show.html.erb +26 -11
  86. data/app/views/riddler_admin/steps/show.js.erb +4 -0
  87. data/app/views/riddler_admin/steps/variant/_show.html.erb +59 -0
  88. data/config/routes.rb +20 -1
  89. data/db/migrate/20181124201519_riddler_admin_genesis.rb +72 -0
  90. data/lib/riddler_admin.rb +29 -2
  91. data/lib/riddler_admin/configuration.rb +44 -0
  92. data/lib/riddler_admin/version.rb +1 -1
  93. metadata +75 -30
  94. data/app/views/riddler_admin/elements/copy/_class.html.erb +0 -3
  95. data/app/views/riddler_admin/elements/copy/_form.html.erb +0 -2
  96. data/app/views/riddler_admin/elements/heading/_class.html.erb +0 -3
  97. data/app/views/riddler_admin/steps/_preview.html.erb +0 -5
  98. data/db/migrate/20181124201519_create_riddler_admin_steps.rb +0 -20
@@ -1,4 +1,23 @@
1
1
  module RiddlerAdmin
2
2
  module ApplicationHelper
3
+ def page_title
4
+ [
5
+ ::RiddlerAdmin.config.main_app_name,
6
+ ::RiddlerAdmin.config.app_name
7
+ ].compact.join " - "
8
+ end
9
+
10
+ def brand_link
11
+ if ::RiddlerAdmin.config.main_app_name.present?
12
+ link_to ::RiddlerAdmin.config.main_app_name, main_app.root_path, class: "navbar-brand"
13
+ else
14
+ link_to ::RiddlerAdmin.config.app_name, riddler_admin.root_path, class: "navbar-brand"
15
+ end
16
+ end
17
+
18
+ def additional_javascript_includes
19
+ return if ::RiddlerAdmin.config.javascript_includes.blank?
20
+ javascript_include_tag *::RiddlerAdmin.config.javascript_includes
21
+ end
3
22
  end
4
23
  end
@@ -0,0 +1,4 @@
1
+ module RiddlerAdmin
2
+ module PreviewContextsHelper
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module RiddlerAdmin
2
+ module PublishRequestsHelper
3
+ end
4
+ end
@@ -1,4 +1,4 @@
1
- require "ksuid"
1
+ require "ulid"
2
2
 
3
3
  module RiddlerAdmin
4
4
  class ApplicationRecord < ActiveRecord::Base
@@ -9,15 +9,15 @@ module RiddlerAdmin
9
9
  private
10
10
 
11
11
  def id_length
12
- return 21 unless self.class.const_defined? :ID_LENGTH
12
+ return 16 unless self.class.const_defined? :ID_LENGTH
13
13
  self.class.const_get :ID_LENGTH
14
14
  end
15
15
 
16
16
  def generate_id
17
17
  return if id.present?
18
18
  model_key = self.class.const_get :MODEL_KEY
19
- ksuid = KSUID.new.to_s
20
- id_string = ksuid[0..(5+id_length)]
19
+ ulid = ULID.generate
20
+ id_string = ulid[0..(9+id_length)]
21
21
  self.id = [model_key, id_string].join "_"
22
22
  end
23
23
  end
@@ -0,0 +1,87 @@
1
+ require "riddler/protobuf/content_definition_pb"
2
+ require "riddler/protobuf/content_management_services_pb"
3
+
4
+ module RiddlerAdmin
5
+ class ContentDefinition < ::RiddlerAdmin::ApplicationRecord
6
+ MODEL_KEY = "cdef".freeze
7
+ ID_LENGTH = 5 # 916_132_832 per second
8
+
9
+ DEFINITION_SCHEMA_VERSION = 1
10
+
11
+ # The Step or Element being defined
12
+ belongs_to :content, polymorphic: true
13
+
14
+ # The PublishRequest that created this
15
+ belongs_to :publish_request
16
+
17
+ validates :definition, presence: true
18
+
19
+ validates_uniqueness_of :version, scope: [:content_type, :content_id]
20
+
21
+ before_validation :add_fields
22
+
23
+ def description
24
+ publish_request.title
25
+ end
26
+
27
+ def title
28
+ "#{content.title} v#{version} - #{description}"
29
+ end
30
+
31
+ def to_proto
32
+ ::Riddler::Protobuf::ContentDefinition.new \
33
+ id: id,
34
+ created_at: created_at_proto,
35
+ content_type: content.content_type.upcase.to_sym,
36
+ content_id: content.id,
37
+ title: content.title,
38
+ description: description,
39
+ version: version,
40
+ definition_schema_version: DEFINITION_SCHEMA_VERSION,
41
+ definition_string: definition.to_json
42
+ end
43
+
44
+ def publish_to_remote
45
+ content_management_grpc.create_content_definition create_request_proto
46
+ end
47
+
48
+ private
49
+
50
+ def created_at_proto
51
+ ::Google::Protobuf::Timestamp.new seconds: created_at.to_i,
52
+ nanos: created_at.nsec
53
+ end
54
+
55
+ def create_request_proto
56
+ ::Riddler::Protobuf::CreateContentDefinitionRequest.new \
57
+ content_definition: self.to_proto
58
+ end
59
+
60
+ def content_management_grpc
61
+ ::Riddler::Protobuf::ContentManagement::Stub.new \
62
+ ::RiddlerAdmin.configuration.riddler_grpc_address,
63
+ :this_channel_is_insecure
64
+ end
65
+
66
+ def add_fields
67
+ return if self.persisted? || content.blank?
68
+
69
+ self.definition_schema_version = DEFINITION_SCHEMA_VERSION
70
+ self.version = next_version
71
+ self.definition = generate_definition self.version
72
+ end
73
+
74
+ def next_version
75
+ previous_max = self.class.where(content: content).maximum(:version) || 0
76
+ previous_max + 1
77
+ end
78
+
79
+ def generate_definition version
80
+ generated_definition = content.definition_hash.merge({
81
+ "definition_id" => id,
82
+ "definition_schema_version" => DEFINITION_SCHEMA_VERSION,
83
+ "version" => version
84
+ })
85
+ end
86
+ end
87
+ end
@@ -1,28 +1,41 @@
1
1
  module RiddlerAdmin
2
- class Element < ApplicationRecord
2
+ class Element < ::RiddlerAdmin::ApplicationRecord
3
3
  MODEL_KEY = "el".freeze
4
4
  ID_LENGTH = 6 # 56_800_235_584 per second
5
5
 
6
+ after_initialize :set_defaults
7
+
6
8
  belongs_to :container, polymorphic: true, validate: true
7
9
 
8
10
  acts_as_list scope: [:container_type, :container_id]
9
11
 
12
+ validates_presence_of :name
13
+
14
+ # Alphanumeric and underscore only - no whitespace.
15
+ # We might consider doing lowercase only for snake casing.
16
+ validates_format_of :name, with: /\A[a-z][a-zA-Z0-9_]*\z/, message: "cannot start with a capital letter or a number."
17
+ validates_uniqueness_of :name, scope: [:container_type, :container_id], unless: :is_variant?
18
+
19
+ validates :include_predicate, parseable_predicate: true
20
+
10
21
  def self.available_classes
11
- [ Elements::Heading, Elements::Copy ]
22
+ [
23
+ Elements::Heading,
24
+ Elements::Image,
25
+ Elements::Link,
26
+ Elements::Text,
27
+ Elements::Variant
28
+ ]
12
29
  end
13
30
 
14
31
  def self.default_class
15
- Elements::Copy
32
+ Elements::Text
16
33
  end
17
34
 
18
35
  def self.short_name
19
36
  name.demodulize
20
37
  end
21
38
 
22
- def self.to_partial_path
23
- "#{name.underscore}/class"
24
- end
25
-
26
39
  def to_partial_path detail=nil
27
40
  [self.class.name.underscore, detail].compact.join "/"
28
41
  end
@@ -36,20 +49,49 @@ module RiddlerAdmin
36
49
  type.demodulize.underscore
37
50
  end
38
51
 
52
+ def content_type
53
+ "element"
54
+ end
55
+
56
+ def content_id
57
+ id
58
+ end
59
+
39
60
  def definition_hash options=nil
40
61
  options ||= {}
41
- serializable_hash options.merge(serializable_hash_options)
62
+ hash = serializable_hash options.merge(serializable_hash_options)
63
+ hash["type"] = object
64
+ hash.delete "include_predicate" if hash["include_predicate"].blank?
65
+ hash
42
66
  end
43
67
 
44
68
  def serializable_hash_options
45
69
  {
46
- methods: :object,
70
+ methods: [:content_type],
47
71
  except: excluded_attrs
48
72
  }
49
73
  end
50
74
 
51
75
  def excluded_attrs
52
- [:created_at, :updated_at, :container_type, :container_id, :position, :name]
76
+ [:created_at, :updated_at, :container_type, :container_id, :position, :url, :text]
77
+ end
78
+
79
+ def step
80
+ if container.kind_of? ::RiddlerAdmin::Step
81
+ container
82
+ else
83
+ container.step
84
+ end
85
+ end
86
+
87
+ private
88
+
89
+ def is_variant?
90
+ container.kind_of? ::RiddlerAdmin::Elements::Variant
91
+ end
92
+
93
+ def set_defaults
94
+ self.name = short_name.underscore if name.blank?
53
95
  end
54
96
  end
55
97
  end
@@ -6,6 +6,12 @@ module RiddlerAdmin
6
6
  def self.model_name
7
7
  Element.model_name
8
8
  end
9
+
10
+ def definition_hash options=nil
11
+ hash = super
12
+ hash["text"] = text
13
+ hash
14
+ end
9
15
  end
10
16
  end
11
17
  end
@@ -0,0 +1,18 @@
1
+ module RiddlerAdmin
2
+ module Elements
3
+ class Image < Element
4
+ validates_presence_of :url
5
+
6
+ def self.model_name
7
+ Element.model_name
8
+ end
9
+
10
+ def definition_hash options=nil
11
+ hash = super
12
+ hash["src"] = url
13
+ hash["alt"] = text
14
+ hash
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ module RiddlerAdmin
2
+ module Elements
3
+ class Link < Element
4
+ validates_presence_of :url
5
+
6
+ def self.model_name
7
+ Element.model_name
8
+ end
9
+
10
+ def definition_hash options=nil
11
+ hash = super
12
+ hash["href"] = url
13
+ hash["text"] = text
14
+ hash
15
+ end
16
+ end
17
+ end
18
+ end
@@ -1,11 +1,17 @@
1
1
  module RiddlerAdmin
2
2
  module Elements
3
- class Copy < Element
3
+ class Text < Element
4
4
  validates_presence_of :text
5
5
 
6
6
  def self.model_name
7
7
  Element.model_name
8
8
  end
9
+
10
+ def definition_hash options=nil
11
+ hash = super
12
+ hash["text"] = text
13
+ hash
14
+ end
9
15
  end
10
16
  end
11
17
  end
@@ -0,0 +1,19 @@
1
+ module RiddlerAdmin
2
+ module Elements
3
+ class Variant < Element
4
+ has_many :elements, -> { order position: :asc },
5
+ dependent: :destroy,
6
+ as: :container
7
+
8
+ def self.model_name
9
+ Element.model_name
10
+ end
11
+
12
+ def definition_hash options=nil
13
+ hash = super
14
+ hash["elements"] = elements.map { |e| e.definition_hash }
15
+ hash
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,83 @@
1
+ module RiddlerAdmin
2
+ class PreviewContext < ::RiddlerAdmin::ApplicationRecord
3
+ MODEL_KEY = "pctx".freeze
4
+ ID_LENGTH = 5 # 916_132_832 per second
5
+
6
+ validates :title, presence: true, uniqueness: true
7
+
8
+ def self.convert_headers input_headers
9
+ original_headers = input_headers.to_h.
10
+ select{|k,v| k.starts_with? "HTTP_"}.
11
+ map{|k,v| [k.downcase.gsub(/^http_/, ""), v] }
12
+
13
+ Hash[original_headers]
14
+ end
15
+
16
+ def data
17
+ yaml_string = yaml
18
+
19
+ if ::RiddlerAdmin.configuration.encrypt_preview_contexts? &&
20
+ encrypted_yaml.present?
21
+
22
+ yaml_string = decrypt encrypted_yaml
23
+ end
24
+
25
+ return {} if yaml_string.to_s.strip == ""
26
+ YAML.safe_load yaml_string
27
+ end
28
+
29
+ def refresh_data input_headers: {}
30
+ use_case_headers = merge_headers input_headers
31
+
32
+ use_case = ::Riddler::UseCases::PreviewContext.new \
33
+ params: params_hash,
34
+ headers: use_case_headers
35
+
36
+ hash = use_case.process
37
+ update_data hash
38
+ end
39
+
40
+ def params_hash
41
+ params_array = (params || "").split("\n").map do |line|
42
+ line.split(":").map &:strip
43
+ end
44
+ Hash[params_array]
45
+ end
46
+
47
+ def headers_hash
48
+ headers_array = (headers || "").split("\n").map do |line|
49
+ key, val = *line.split(":").map(&:strip)
50
+ [key.downcase.gsub(/[^0-9a-z]/i, "_"), val]
51
+ end
52
+ Hash[headers_array]
53
+ end
54
+
55
+ def merge_headers input_headers
56
+ request_headers = self.class.convert_headers input_headers
57
+ request_headers.merge headers_hash
58
+ end
59
+
60
+ def update_data hash
61
+ yaml = hash.to_yaml
62
+
63
+ if ::RiddlerAdmin.configuration.encrypt_preview_contexts?
64
+ encrypted_yaml = encrypt yaml
65
+ update_attribute :encrypted_yaml, encrypted_yaml
66
+ else
67
+ update_attribute :yaml, yaml
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ def encrypt plaintext
74
+ ::RiddlerAdmin.encrypt plaintext,
75
+ key: ::RiddlerAdmin.configuration.preview_context_transit_key
76
+ end
77
+
78
+ def decrypt ciphertext
79
+ ::RiddlerAdmin.decrypt ciphertext,
80
+ key: ::RiddlerAdmin.configuration.preview_context_transit_key
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,57 @@
1
+ module RiddlerAdmin
2
+ class PublishRequest < ::RiddlerAdmin::ApplicationRecord
3
+ MODEL_KEY = "pr".freeze
4
+ ID_LENGTH = 5 # 916_132_832 per second
5
+
6
+ # The Step or Element being published
7
+ belongs_to :content, polymorphic: true
8
+
9
+ # The ContentDefinition created as a result of this request
10
+ has_one :content_definition
11
+
12
+ validates :title, presence: true
13
+
14
+ after_initialize :set_defaults
15
+
16
+ scope :unapproved, -> { where "approved_at is null" }
17
+
18
+ def approve riddler_user = nil, approved_at = Time.now.utc
19
+ attrs = {status: "approved", approved_at: approved_at}
20
+ if riddler_user.present?
21
+ attrs[:approved_by_name] = ::RiddlerAdmin.config.user_name_block.call riddler_user
22
+ attrs[:approved_by_id] = riddler_user.public_send ::RiddlerAdmin.config.user_id_method
23
+ end
24
+
25
+ update_attributes attrs
26
+ end
27
+
28
+ def approved?
29
+ approved_at.present?
30
+ end
31
+
32
+ def publish published_at = Time.now.utc
33
+ raise "ERROR: Attempt to publish an unapproved definition" unless approved?
34
+ create_content_definition! content: content
35
+ content_definition.publish_to_remote
36
+
37
+ update_attributes published_at: published_at,
38
+ status: "published"
39
+ end
40
+
41
+ def published?
42
+ published_at.present?
43
+ end
44
+
45
+ private
46
+
47
+ def set_defaults
48
+ return if title.present? || content.blank?
49
+
50
+ if content.published?
51
+ self.title = "Update #{content.title}"
52
+ else
53
+ self.title = "Create #{content.title}"
54
+ end
55
+ end
56
+ end
57
+ end