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.
- data/CHANGELOG.md +10 -0
- data/app/models/action.rb +8 -1
- data/app/models/edition.rb +5 -0
- data/app/models/user.rb +28 -2
- data/app/models/workflow.rb +20 -66
- data/app/traits/recordable_actions.rb +53 -0
- data/lib/govuk_content_models.rb +4 -5
- data/lib/govuk_content_models/action_processors.rb +23 -0
- data/lib/govuk_content_models/action_processors/approve_fact_check_processor.rb +6 -0
- data/lib/govuk_content_models/action_processors/approve_review_processor.rb +11 -0
- data/lib/govuk_content_models/action_processors/archive_processor.rb +6 -0
- data/lib/govuk_content_models/action_processors/assign_processor.rb +12 -0
- data/lib/govuk_content_models/action_processors/base_processor.rb +59 -0
- data/lib/govuk_content_models/action_processors/cancel_scheduled_publishing_processor.rb +6 -0
- data/lib/govuk_content_models/action_processors/create_edition_processor.rb +22 -0
- data/lib/govuk_content_models/action_processors/emergency_publish_processor.rb +6 -0
- data/lib/govuk_content_models/action_processors/new_version_processor.rb +23 -0
- data/lib/govuk_content_models/action_processors/publish_processor.rb +6 -0
- data/lib/govuk_content_models/action_processors/receive_fact_check_processor.rb +18 -0
- data/lib/govuk_content_models/action_processors/request_amendments_processor.rb +15 -0
- data/lib/govuk_content_models/action_processors/request_review_processor.rb +6 -0
- data/lib/govuk_content_models/action_processors/schedule_for_publishing_processor.rb +14 -0
- data/lib/govuk_content_models/action_processors/send_fact_check_processor.rb +14 -0
- data/lib/govuk_content_models/action_processors/skip_fact_check_processor.rb +6 -0
- data/lib/govuk_content_models/test_helpers/action_processor_helpers.rb +39 -0
- data/lib/govuk_content_models/version.rb +1 -1
- data/test/models/action_test.rb +13 -0
- data/test/models/edition_test.rb +87 -64
- data/test/models/user_test.rb +2 -2
- data/test/models/workflow_test.rb +160 -55
- data/test/test_helper.rb +2 -0
- metadata +25 -7
- data/app/models/workflow_actor.rb +0 -157
- 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
|
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?
|
data/app/models/edition.rb
CHANGED
@@ -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
|
data/app/models/workflow.rb
CHANGED
@@ -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
|
data/lib/govuk_content_models.rb
CHANGED
@@ -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
|
-
|
6
|
-
|
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,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
|