govuk_content_models 23.0.0 → 24.0.0

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