govuk_content_models 23.0.0 → 24.0.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 (34) hide show
  1. data/CHANGELOG.md +10 -0
  2. data/app/models/action.rb +8 -1
  3. data/app/models/edition.rb +5 -0
  4. data/app/models/user.rb +28 -2
  5. data/app/models/workflow.rb +20 -66
  6. data/app/traits/recordable_actions.rb +53 -0
  7. data/lib/govuk_content_models.rb +4 -5
  8. data/lib/govuk_content_models/action_processors.rb +23 -0
  9. data/lib/govuk_content_models/action_processors/approve_fact_check_processor.rb +6 -0
  10. data/lib/govuk_content_models/action_processors/approve_review_processor.rb +11 -0
  11. data/lib/govuk_content_models/action_processors/archive_processor.rb +6 -0
  12. data/lib/govuk_content_models/action_processors/assign_processor.rb +12 -0
  13. data/lib/govuk_content_models/action_processors/base_processor.rb +59 -0
  14. data/lib/govuk_content_models/action_processors/cancel_scheduled_publishing_processor.rb +6 -0
  15. data/lib/govuk_content_models/action_processors/create_edition_processor.rb +22 -0
  16. data/lib/govuk_content_models/action_processors/emergency_publish_processor.rb +6 -0
  17. data/lib/govuk_content_models/action_processors/new_version_processor.rb +23 -0
  18. data/lib/govuk_content_models/action_processors/publish_processor.rb +6 -0
  19. data/lib/govuk_content_models/action_processors/receive_fact_check_processor.rb +18 -0
  20. data/lib/govuk_content_models/action_processors/request_amendments_processor.rb +15 -0
  21. data/lib/govuk_content_models/action_processors/request_review_processor.rb +6 -0
  22. data/lib/govuk_content_models/action_processors/schedule_for_publishing_processor.rb +14 -0
  23. data/lib/govuk_content_models/action_processors/send_fact_check_processor.rb +14 -0
  24. data/lib/govuk_content_models/action_processors/skip_fact_check_processor.rb +6 -0
  25. data/lib/govuk_content_models/test_helpers/action_processor_helpers.rb +39 -0
  26. data/lib/govuk_content_models/version.rb +1 -1
  27. data/test/models/action_test.rb +13 -0
  28. data/test/models/edition_test.rb +87 -64
  29. data/test/models/user_test.rb +2 -2
  30. data/test/models/workflow_test.rb +160 -55
  31. data/test/test_helper.rb +2 -0
  32. metadata +25 -7
  33. data/app/models/workflow_actor.rb +0 -157
  34. data/test/models/workflow_actor_test.rb +0 -112
data/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ## 24.0.0
2
+
3
+ * Major clean-up which replaced `WorkflowActor` with `ActionProcessors`.
4
+ This is a breaking change, shouldn't break any existing functionality,
5
+ but may break re-opened classes, and tests relying on workflow helper
6
+ methods which were present in `WorkflowActor`.
7
+ * Added Edition fields `major_change` and `change_note`
8
+ * Improved the audit log for scheduled publishing to show scheduled time,
9
+ which requires storing it in the `action`.
10
+
1
11
  ## 23.0.0
2
12
 
3
13
  * Remove important_notes field from Edition
data/app/models/action.rb CHANGED
@@ -36,6 +36,7 @@ class Action
36
36
  field :comment, type: String
37
37
  field :comment_sanitized, type: Boolean, default: false
38
38
  field :request_type, type: String
39
+ field :request_details, type: Hash, default: {}
39
40
  field :email_addresses, type: String
40
41
  field :customised_message, type: String
41
42
  field :created_at, type: DateTime, default: lambda { Time.zone.now }
@@ -52,7 +53,13 @@ class Action
52
53
  end
53
54
 
54
55
  def to_s
55
- request_type.humanize.capitalize
56
+ if request_type == SCHEDULE_FOR_PUBLISHING
57
+ string = "Scheduled for publishing"
58
+ string += " on #{request_details['scheduled_time'].strftime('%d/%m/%Y %H:%M %Z')}" if request_details['scheduled_time'].present?
59
+ string
60
+ else
61
+ request_type.humanize.capitalize
62
+ end
56
63
  end
57
64
 
58
65
  def is_fact_check_request?
@@ -1,9 +1,11 @@
1
+ require "traits/recordable_actions"
1
2
  require "workflow"
2
3
 
3
4
  class Edition
4
5
  include Mongoid::Document
5
6
  include Mongoid::Timestamps
6
7
  include Workflow
8
+ include RecordableActions
7
9
 
8
10
  field :panopticon_id, type: String
9
11
  field :version_number, type: Integer, default: 1
@@ -28,6 +30,8 @@ class Edition
28
30
  field :creator, type: String
29
31
  field :publisher, type: String
30
32
  field :archiver, type: String
33
+ field :major_change, type: Boolean, default: false
34
+ field :change_note, type: String
31
35
 
32
36
  GOVSPEAK_FIELDS = []
33
37
 
@@ -52,6 +56,7 @@ class Edition
52
56
  validates_with SafeHtml
53
57
  validates_with LinkValidator, on: :update
54
58
  validates_with TopicValidator, BrowsePageValidator
59
+ validates_presence_of :change_note, if: :major_change
55
60
 
56
61
  before_save :check_for_archived_artefact
57
62
  before_destroy :destroy_artefact
data/app/models/user.rb CHANGED
@@ -1,14 +1,12 @@
1
1
  require "digest/md5"
2
2
  require "cgi"
3
3
  require "gds-sso/user"
4
- require "workflow_actor"
5
4
  require "safe_html"
6
5
 
7
6
  class User
8
7
  include Mongoid::Document
9
8
  include Mongoid::Timestamps
10
9
  include GDS::SSO::User
11
- include WorkflowActor
12
10
 
13
11
  # Let an app configure the collection name to use, e.g. set a constant in an
14
12
  # initializer
@@ -46,4 +44,32 @@ class User
46
44
  opts[:s] ? "?s=#{CGI.escape(opts[:s])}" : ""
47
45
  ]
48
46
  end
47
+
48
+ def progress(edition, action_attributes)
49
+ request_type = action_attributes.delete(:request_type)
50
+
51
+ processor = GovukContentModels::ActionProcessors::REQUEST_TYPE_TO_PROCESSOR[request_type.to_sym]
52
+ edition = GovukContentModels::ActionProcessors::const_get(processor).new(self, edition, action_attributes, {}).processed_edition
53
+ edition.save if edition
54
+ end
55
+
56
+ def record_note(edition, comment, type = Action::NOTE)
57
+ edition.new_action(self, type, comment: comment)
58
+ end
59
+
60
+ def resolve_important_note(edition)
61
+ record_note(edition, nil, Action::IMPORTANT_NOTE_RESOLVED)
62
+ end
63
+
64
+ def create_edition(format, attributes = {})
65
+ GovukContentModels::ActionProcessors::CreateEditionProcessor.new(self, nil, {}, { format: format, edition_attributes: attributes }).processed_edition
66
+ end
67
+
68
+ def new_version(edition, convert_to = nil)
69
+ GovukContentModels::ActionProcessors::NewVersionProcessor.new(self, edition, {}, { convert_to: convert_to }).processed_edition
70
+ end
71
+
72
+ def assign(edition, recipient)
73
+ GovukContentModels::ActionProcessors::AssignProcessor.new(self, edition, { recipient_id: recipient.id }).processed_edition
74
+ end
49
75
  end
@@ -1,5 +1,4 @@
1
1
  require "state_machine"
2
- require "action"
3
2
 
4
3
  module Workflow
5
4
  class CannotDeletePublishedPublication < RuntimeError; end
@@ -15,7 +14,6 @@ module Workflow
15
14
 
16
15
  field :state, type: String, default: "draft"
17
16
  belongs_to :assigned_to, class_name: "User"
18
- embeds_many :actions
19
17
 
20
18
  state_machine initial: :draft do
21
19
  after_transition on: :request_amendments do |edition, transition|
@@ -105,18 +103,6 @@ module Workflow
105
103
  text
106
104
  end
107
105
 
108
- def update_user_action(property, statuses)
109
- actions.where(:request_type.in => statuses).limit(1).each do |action|
110
- # This can be invoked by Panopticon when it updates an artefact and associated
111
- # editions. The problem is that Panopticon and Publisher users live in different
112
- # collections, but share a model and relationships with eg actions.
113
- # Therefore, Panopticon might not find a user for an action.
114
- if action.requester
115
- set(property, action.requester.name)
116
- end
117
- end
118
- end
119
-
120
106
  def denormalise_users!
121
107
  set(:assignee, assigned_to.name) if assigned_to
122
108
  update_user_action("creator", [Action::CREATE, Action::NEW_VERSION])
@@ -125,50 +111,6 @@ module Workflow
125
111
  self
126
112
  end
127
113
 
128
- def created_by
129
- creation = actions.detect do |a|
130
- a.request_type == Action::CREATE || a.request_type == Action::NEW_VERSION
131
- end
132
- creation.requester if creation
133
- end
134
-
135
- def published_by
136
- publication = actions.where(request_type: Action::PUBLISH).first
137
- publication.requester if publication
138
- end
139
-
140
- def archived_by
141
- publication = actions.where(request_type: Action::ARCHIVE).first
142
- publication.requester if publication
143
- end
144
-
145
- def latest_status_action(type = nil)
146
- if type
147
- self.actions.where(request_type: type).last
148
- else
149
- most_recent_action(&:status_action?)
150
- end
151
- end
152
-
153
- def last_fact_checked_at
154
- last_fact_check = actions.reverse.find(&:is_fact_check_request?)
155
- last_fact_check ? last_fact_check.created_at : NullTimestamp.new
156
- end
157
-
158
- def new_action(user, type, options={})
159
- actions.create!(options.merge(requester_id: user.id, request_type: type))
160
- end
161
-
162
- def new_action_without_validation(user, type, options={})
163
- action = actions.build(options.merge(requester_id: user.id, request_type: type))
164
- save(validate: false)
165
- action
166
- end
167
-
168
- def most_recent_action(&blk)
169
- self.actions.sort_by(&:created_at).reverse.find(&blk)
170
- end
171
-
172
114
  def can_destroy?
173
115
  ! scheduled_for_publishing? && ! published? && ! archived?
174
116
  end
@@ -189,14 +131,6 @@ module Workflow
189
131
  siblings.update_all(sibling_in_progress: self.version_number)
190
132
  end
191
133
 
192
- def notify_siblings_of_published_edition
193
- siblings.update_all(sibling_in_progress: nil)
194
- end
195
-
196
- def update_sibling_in_progress(version_number_or_nil)
197
- update_attribute(:sibling_in_progress, version_number_or_nil)
198
- end
199
-
200
134
  def in_progress?
201
135
  ! ["archived", "published"].include? self.state
202
136
  end
@@ -223,6 +157,26 @@ module Workflow
223
157
 
224
158
  private
225
159
 
160
+ def notify_siblings_of_published_edition
161
+ siblings.update_all(sibling_in_progress: nil)
162
+ end
163
+
164
+ def update_sibling_in_progress(version_number_or_nil)
165
+ update_attribute(:sibling_in_progress, version_number_or_nil)
166
+ end
167
+
168
+ def update_user_action(property, statuses)
169
+ actions.where(:request_type.in => statuses).limit(1).each do |action|
170
+ # This can be invoked by Panopticon when it updates an artefact and associated
171
+ # editions. The problem is that Panopticon and Publisher users live in different
172
+ # collections, but share a model and relationships with eg actions.
173
+ # Therefore, Panopticon might not find a user for an action.
174
+ if action.requester
175
+ set(property, action.requester.name)
176
+ end
177
+ end
178
+ end
179
+
226
180
  def publish_at_is_in_the_future
227
181
  errors.add(:publish_at, "can't be a time in the past") if publish_at.present? && publish_at < Time.zone.now
228
182
  end
@@ -0,0 +1,53 @@
1
+ require "action"
2
+
3
+ module RecordableActions
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ embeds_many :actions
8
+
9
+ def latest_status_action(type = nil)
10
+ if type
11
+ self.actions.where(request_type: type).last
12
+ else
13
+ most_recent_action(&:status_action?)
14
+ end
15
+ end
16
+
17
+ def last_fact_checked_at
18
+ last_fact_check = actions.reverse.find(&:is_fact_check_request?)
19
+ last_fact_check ? last_fact_check.created_at : NullTimestamp.new
20
+ end
21
+
22
+ def new_action(user, type, options={})
23
+ actions.create!(options.merge(requester_id: user.id, request_type: type))
24
+ end
25
+
26
+ def new_action_without_validation(user, type, options={})
27
+ action = actions.build(options.merge(requester_id: user.id, request_type: type))
28
+ save(validate: false)
29
+ action
30
+ end
31
+
32
+ def most_recent_action(&blk)
33
+ self.actions.sort_by(&:created_at).reverse.find(&blk)
34
+ end
35
+
36
+ def created_by
37
+ creation = actions.detect do |a|
38
+ a.request_type == Action::CREATE || a.request_type == Action::NEW_VERSION
39
+ end
40
+ creation.requester if creation
41
+ end
42
+
43
+ def published_by
44
+ publication = actions.where(request_type: Action::PUBLISH).first
45
+ publication.requester if publication
46
+ end
47
+
48
+ def archived_by
49
+ publication = actions.where(request_type: Action::ARCHIVE).first
50
+ publication.requester if publication
51
+ end
52
+ end
53
+ end
@@ -1,13 +1,12 @@
1
1
  require "govuk_content_models/version"
2
2
  require "mongoid"
3
3
  require "mongoid/monkey_patches"
4
+ require "govuk_content_models/action_processors"
4
5
 
5
- begin
6
- module GovukContentModels
6
+ module GovukContentModels
7
+ if defined?(Rails)
7
8
  class Engine < Rails::Engine
9
+ config.autoload_paths << File.expand_path('govuk_content_models/action_processors', __FILE__)
8
10
  end
9
11
  end
10
- rescue NameError
11
- module GovukContentModels
12
- end
13
12
  end
@@ -0,0 +1,23 @@
1
+ require "govuk_content_models/action_processors/base_processor"
2
+ Dir[File.join(File.dirname(__FILE__), 'action_processors', '*.rb')].each { |f| require f }
3
+
4
+ module GovukContentModels
5
+ module ActionProcessors
6
+ REQUEST_TYPE_TO_PROCESSOR = {
7
+ assign: 'AssignProcessor',
8
+ create_edition: 'CreateEditionProcessor',
9
+ request_review: 'RequestReviewProcessor',
10
+ approve_review: 'ApproveReviewProcessor',
11
+ send_fact_check: 'SendFactCheckProcessor',
12
+ receive_fact_check: 'ReceiveFactCheckProcessor',
13
+ approve_fact_check: 'ApproveFactCheckProcessor',
14
+ skip_fact_check: 'SkipFactCheckProcessor',
15
+ request_amendments: 'RequestAmendmentsProcessor',
16
+ schedule_for_publishing: 'ScheduleForPublishingProcessor',
17
+ cancel_scheduled_publishing: 'CancelScheduledPublishingProcessor',
18
+ publish: 'PublishProcessor',
19
+ archive: 'ArchiveProcessor',
20
+ new_version: 'NewVersionProcessor',
21
+ }
22
+ end
23
+ end
@@ -0,0 +1,6 @@
1
+ module GovukContentModels
2
+ module ActionProcessors
3
+ class ApproveFactCheckProcessor < BaseProcessor
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,11 @@
1
+ module GovukContentModels
2
+ module ActionProcessors
3
+ class ApproveReviewProcessor < BaseProcessor
4
+
5
+ def process?
6
+ requester_different?
7
+ end
8
+
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,6 @@
1
+ module GovukContentModels
2
+ module ActionProcessors
3
+ class ArchiveProcessor < BaseProcessor
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,12 @@
1
+ module GovukContentModels
2
+ module ActionProcessors
3
+ class AssignProcessor < BaseProcessor
4
+
5
+ def process
6
+ edition.set(:assigned_to_id, action_attributes[:recipient_id])
7
+ edition.reload
8
+ end
9
+
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,59 @@
1
+ module GovukContentModels
2
+ module ActionProcessors
3
+ class BaseProcessor
4
+ attr_accessor :actor, :edition, :action_attributes, :event_attributes
5
+
6
+ def initialize(actor, edition, action_attributes={}, event_attributes={})
7
+ @actor = actor
8
+ @edition = edition
9
+ @action_attributes = action_attributes
10
+ @event_attributes = event_attributes
11
+ end
12
+
13
+ def processed_edition
14
+ if process? && process
15
+ record_action if record_action?
16
+ edition
17
+ end
18
+ end
19
+
20
+ protected
21
+
22
+ def process?
23
+ true
24
+ end
25
+
26
+ def process
27
+ edition.send(action_name)
28
+ end
29
+
30
+ def record_action?
31
+ true
32
+ end
33
+
34
+ def action_name
35
+ REQUEST_TYPE_TO_PROCESSOR.invert[self.class.name.slice(/.*::(.*)/, 1)]
36
+ end
37
+
38
+ def record_action
39
+ new_action = edition.new_action(actor, action_name, action_attributes || {})
40
+ edition.denormalise_users!
41
+ new_action
42
+ end
43
+
44
+ def record_action_without_validation
45
+ new_action = edition.new_action_without_validation(actor, action_name, action_attributes || {})
46
+ edition.denormalise_users!
47
+ new_action
48
+ end
49
+
50
+ def requester_different?
51
+ if edition.latest_status_action
52
+ edition.latest_status_action.requester_id != actor.id
53
+ else
54
+ true
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,6 @@
1
+ module GovukContentModels
2
+ module ActionProcessors
3
+ class CancelScheduledPublishingProcessor < BaseProcessor
4
+ end
5
+ end
6
+ end