riddler_admin 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 (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