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.
- 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
|