govuk_content_models 8.1.0 → 8.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/app/models/action.rb CHANGED
@@ -4,17 +4,19 @@ class Action
4
4
  include Mongoid::Document
5
5
 
6
6
  STATUS_ACTIONS = [
7
- CREATE = "create",
8
- REQUEST_REVIEW = "request_review",
9
- APPROVE_REVIEW = "approve_review",
10
- APPROVE_FACT_CHECK = "approve_fact_check",
11
- REQUEST_AMENDMENTS = "request_amendments",
12
- SEND_FACT_CHECK = "send_fact_check",
13
- RECEIVE_FACT_CHECK = "receive_fact_check",
14
- SKIP_FACT_CHECK = "skip_fact_check",
15
- PUBLISH = "publish",
16
- ARCHIVE = "archive",
17
- NEW_VERSION = "new_version",
7
+ CREATE = "create",
8
+ REQUEST_REVIEW = "request_review",
9
+ APPROVE_REVIEW = "approve_review",
10
+ APPROVE_FACT_CHECK = "approve_fact_check",
11
+ REQUEST_AMENDMENTS = "request_amendments",
12
+ SEND_FACT_CHECK = "send_fact_check",
13
+ RECEIVE_FACT_CHECK = "receive_fact_check",
14
+ SKIP_FACT_CHECK = "skip_fact_check",
15
+ SCHEDULE_FOR_PUBLISHING = "schedule_for_publishing",
16
+ CANCEL_SCHEDULED_PUBLISHING = "cancel_scheduled_publishing",
17
+ PUBLISH = "publish",
18
+ ARCHIVE = "archive",
19
+ NEW_VERSION = "new_version",
18
20
  ]
19
21
 
20
22
  NON_STATUS_ACTIONS = [
@@ -1,4 +1,3 @@
1
- require "slug_validator"
2
1
  require "plek"
3
2
  require "traits/taggable"
4
3
  require "artefact_action" # Require this when running outside Rails
@@ -1,4 +1,3 @@
1
- require "slug_validator"
2
1
  require "traits/taggable"
3
2
  require "safe_html"
4
3
 
@@ -13,6 +13,7 @@ class Edition
13
13
 
14
14
  field :title, type: String
15
15
  field :created_at, type: DateTime, default: lambda { Time.zone.now }
16
+ field :publish_at, type: DateTime
16
17
  field :overview, type: String
17
18
  field :alternative_title, type: String
18
19
  field :slug, type: String
@@ -30,16 +31,12 @@ class Edition
30
31
 
31
32
  belongs_to :assigned_to, class_name: "User"
32
33
 
33
- scope :draft, where(state: "draft")
34
- scope :amends_needed, where(state: "amends_needed")
35
- scope :in_review, where(state: "in_review")
36
- scope :fact_check, where(state: "fact_check")
37
- scope :fact_check_received, where(state: "fact_check_received")
38
- scope :ready, where(state: "ready")
39
- scope :published, where(state: "published")
40
- scope :archived, where(state: "archived")
41
- scope :in_progress, where(:state.nin => ["archived", "published"])
42
- scope :assigned_to, lambda { |user|
34
+ # state_machine comes from Workflow
35
+ state_machine.states.map(&:name).each do |state|
36
+ scope state, where(state: state)
37
+ end
38
+ scope :in_progress, where(:state.nin => ["archived", "published"])
39
+ scope :assigned_to, lambda { |user|
43
40
  if user
44
41
  where(assigned_to_id: user.id)
45
42
  else
@@ -50,6 +47,7 @@ class Edition
50
47
  validates :title, presence: true
51
48
  validates :version_number, presence: true
52
49
  validates :panopticon_id, presence: true
50
+ validate :publish_at_is_in_the_future
53
51
  validates_with SafeHtml
54
52
 
55
53
  before_save :check_for_archived_artefact
@@ -286,4 +284,10 @@ class Edition
286
284
  Artefact.find(self.panopticon_id).destroy
287
285
  end
288
286
  end
287
+
288
+ private
289
+
290
+ def publish_at_is_in_the_future
291
+ errors.add(:publish_at, "can't be a time in the past") if publish_at.present? && publish_at < Time.zone.now
292
+ end
289
293
  end
@@ -22,6 +22,14 @@ module Workflow
22
22
  edition.mark_as_rejected
23
23
  end
24
24
 
25
+ before_transition on: :schedule_for_publishing do |edition, transition|
26
+ edition.publish_at = transition.args.first
27
+ end
28
+
29
+ before_transition on: [:publish, :cancel_scheduled_publishing] do |edition, transition|
30
+ edition.publish_at = nil
31
+ end
32
+
25
33
  after_transition on: :publish do |edition, transition|
26
34
  edition.was_published
27
35
  end
@@ -60,8 +68,16 @@ module Workflow
60
68
  transition fact_check: :fact_check_received
61
69
  end
62
70
 
71
+ event :schedule_for_publishing do
72
+ transition ready: :scheduled_for_publishing
73
+ end
74
+
75
+ event :cancel_scheduled_publishing do
76
+ transition scheduled_for_publishing: :ready
77
+ end
78
+
63
79
  event :publish do
64
- transition ready: :published
80
+ transition [:ready, :scheduled_for_publishing] => :published
65
81
  end
66
82
 
67
83
  event :emergency_publish do
@@ -71,6 +87,10 @@ module Workflow
71
87
  event :archive do
72
88
  transition all => :archived, :unless => :archived?
73
89
  end
90
+
91
+ state :scheduled_for_publishing do
92
+ validates_presence_of :publish_at
93
+ end
74
94
  end
75
95
  end
76
96
 
@@ -78,8 +98,10 @@ module Workflow
78
98
  (self.actions.where(request_type: Action::APPROVE_FACT_CHECK).count > 0)
79
99
  end
80
100
 
81
- def capitalized_state_name
82
- self.human_state_name.capitalize
101
+ def status_text
102
+ text = human_state_name.capitalize
103
+ text += ' on ' + publish_at.strftime("%d/%m/%Y %H:%M") if scheduled_for_publishing?
104
+ text
83
105
  end
84
106
 
85
107
  def update_user_action(property, statuses)
@@ -146,26 +168,8 @@ module Workflow
146
168
  self.actions.sort_by(&:created_at).reverse.find(&blk)
147
169
  end
148
170
 
149
- def not_editing_published_item
150
- if changed? and ! state_changed?
151
- if archived?
152
- errors.add(:base, "Archived editions can't be edited")
153
- end
154
- if published?
155
- changes_allowed_when_published = ["slug", "section",
156
- "department", "business_proposition"]
157
- illegal_changes = changes.keys - changes_allowed_when_published
158
- if illegal_changes.empty?
159
- # Allow it
160
- else
161
- errors.add(:base, "Published editions can't be edited")
162
- end
163
- end
164
- end
165
- end
166
-
167
171
  def can_destroy?
168
- ! published? and ! archived?
172
+ ! scheduled_for_publishing? && ! published? && ! archived?
169
173
  end
170
174
 
171
175
  def check_can_delete_and_notify
@@ -206,4 +210,25 @@ module Workflow
206
210
  def in_progress?
207
211
  ! ["archived", "published"].include? self.state
208
212
  end
213
+
214
+ private
215
+
216
+ def not_editing_published_item
217
+ if changed? and ! state_changed?
218
+ if archived?
219
+ errors.add(:base, "Archived editions can't be edited")
220
+ end
221
+ if scheduled_for_publishing? || published?
222
+ changes_allowed_when_published = ["slug", "section",
223
+ "department", "business_proposition"]
224
+ illegal_changes = changes.keys - changes_allowed_when_published
225
+ if illegal_changes.empty?
226
+ # Allow it
227
+ else
228
+ edition_description = published? ? 'Published editions' : 'Editions scheduled for publishing'
229
+ errors.add(:base, "#{edition_description} can't be edited")
230
+ end
231
+ end
232
+ end
233
+ end
209
234
  end
@@ -6,8 +6,14 @@ require "programme_edition"
6
6
  require "transaction_edition"
7
7
 
8
8
  module WorkflowActor
9
- SIMPLE_WORKFLOW_ACTIONS = %W[request_review
10
- request_amendments approve_review approve_fact_check archive]
9
+ SIMPLE_WORKFLOW_ACTIONS = %w(
10
+ request_review
11
+ request_amendments
12
+ approve_review
13
+ approve_fact_check
14
+ archive
15
+ cancel_scheduled_publishing
16
+ )
11
17
 
12
18
  def record_action(edition, type, options={})
13
19
  type = Action.const_get(type.to_s.upcase)
@@ -27,8 +33,8 @@ module WorkflowActor
27
33
  respond_to?(:"can_#{action}?") ? __send__(:"can_#{action}?", edition) : true
28
34
  end
29
35
 
30
- def take_action(edition, action, details = {})
31
- if can_take_action(action, edition) and edition.send(action)
36
+ def take_action(edition, action, details = {}, action_parameters = [])
37
+ if can_take_action(action, edition) && edition.send(action, *action_parameters)
32
38
  record_action(edition, action, details)
33
39
  edition
34
40
  else
@@ -36,8 +42,8 @@ module WorkflowActor
36
42
  end
37
43
  end
38
44
 
39
- def take_action!(edition, action, details = {})
40
- edition = take_action(edition, action, details)
45
+ def take_action!(edition, action, details = {}, action_parameters = [])
46
+ edition = take_action(edition, action, details, action_parameters)
41
47
  edition.save if edition
42
48
  end
43
49
 
@@ -109,6 +115,11 @@ module WorkflowActor
109
115
  end
110
116
  end
111
117
 
118
+ def schedule_for_publishing(edition, details)
119
+ publish_at = details.delete(:publish_at)
120
+ take_action(edition, __method__, details, [publish_at])
121
+ end
122
+
112
123
  def publish(edition, details)
113
124
  if edition.published_edition
114
125
  details.merge!({ diff: edition.edition_changes })
@@ -118,16 +129,16 @@ module WorkflowActor
118
129
  end
119
130
 
120
131
  def can_approve_review?(edition)
121
- # To accommodate latest_status_action being nil, we'll always return true in
122
- # those cases
123
- # This is intended as a v.temporary fix until we can remedy the root cause
124
- if edition.latest_status_action
125
- edition.latest_status_action.requester_id != self.id
132
+ requester_different?(edition)
133
+ end
134
+
135
+ def can_request_amendments?(edition)
136
+ if edition.in_review?
137
+ requester_different?(edition)
126
138
  else
127
139
  true
128
140
  end
129
141
  end
130
- alias :can_request_amendments? :can_approve_review?
131
142
 
132
143
  def assign(edition, recipient)
133
144
  edition.assigned_to_id = recipient.id
@@ -138,4 +149,17 @@ module WorkflowActor
138
149
  edition.save! and edition.reload
139
150
  record_action edition, __method__, recipient: recipient
140
151
  end
152
+
153
+ private
154
+
155
+ def requester_different?(edition)
156
+ # To accommodate latest_status_action being nil, we'll always return true in
157
+ # those cases
158
+ # This is intended as a v.temporary fix until we can remedy the root cause
159
+ if edition.latest_status_action
160
+ edition.latest_status_action.requester_id != self.id
161
+ else
162
+ true
163
+ end
164
+ end
141
165
  end
@@ -3,13 +3,12 @@ require "active_model"
3
3
  require "mongoid"
4
4
  require "govuk_content_models"
5
5
 
6
- %w[ app/models app/validators app/repositories app/traits lib ].each do |path|
7
- full_path = File.expand_path(
8
- "#{File.dirname(__FILE__)}/../../#{path}", __FILE__)
6
+ root_path = "#{File.dirname(__FILE__)}/../.."
7
+ %w[ app/models app/validators app/traits lib ].each do |path|
8
+ full_path = File.expand_path("#{root_path}/#{path}")
9
9
  $LOAD_PATH.unshift full_path unless $LOAD_PATH.include?(full_path)
10
10
  end
11
11
 
12
- # Require everything under app
13
- Dir.glob("#{File.dirname(__FILE__)}/../../app/**/*.rb").each do |file|
14
- require file
15
- end
12
+ # Require validators first, then other files in app
13
+ Dir.glob("#{root_path}/app/validators/*.rb").each {|f| require f }
14
+ Dir.glob("#{root_path}/app/**/*.rb").each {|f| require f }
@@ -81,6 +81,11 @@ FactoryGirl.define do
81
81
  section "test:subsection test"
82
82
 
83
83
  association :assigned_to, factory: :user
84
+
85
+ trait :scheduled_for_publishing do
86
+ state 'scheduled_for_publishing'
87
+ publish_at 1.day.from_now
88
+ end
84
89
  end
85
90
  factory :answer_edition, parent: :edition do
86
91
  end
@@ -1,4 +1,4 @@
1
1
  module GovukContentModels
2
2
  # Changing this causes Jenkins to tag and release the gem into the wild
3
- VERSION = "8.1.0"
3
+ VERSION = "8.2.0"
4
4
  end
@@ -0,0 +1,84 @@
1
+ require "test_helper"
2
+
3
+ class EditionScheduledForPublishingTest < ActiveSupport::TestCase
4
+ context "#schedule_for_publishing" do
5
+ context "when publish_at is not specified" do
6
+ setup do
7
+ @edition = FactoryGirl.create(:edition, state: 'ready')
8
+ @edition.schedule_for_publishing
9
+ @edition.reload
10
+ end
11
+
12
+ should "return an error" do
13
+ assert_includes @edition.errors[:publish_at], "can't be blank"
14
+ end
15
+
16
+ should "not complete the transition to scheduled_for_publishing" do
17
+ assert_equal 'ready', @edition.state
18
+ end
19
+ end
20
+
21
+ context "when publish_at is specified" do
22
+ setup do
23
+ @edition = FactoryGirl.create(:edition, state: 'ready')
24
+ @publish_when = 1.day.from_now
25
+ @edition.schedule_for_publishing(@publish_when)
26
+ @edition.reload
27
+ end
28
+
29
+ should "save publish_at against the edition" do
30
+ assert_equal @publish_when.to_i, @edition.publish_at.to_i
31
+ end
32
+
33
+ should "complete the transition to scheduled_for_publishing" do
34
+ assert_equal 'scheduled_for_publishing', @edition.state
35
+ end
36
+ end
37
+ end
38
+
39
+ context "when scheduled_for_publishing" do
40
+ should "not allow editing fields like title" do
41
+ edition = FactoryGirl.create(:edition, :scheduled_for_publishing)
42
+
43
+ edition.title = 'a new title'
44
+
45
+ refute edition.valid?
46
+ assert_includes edition.errors.full_messages, "Editions scheduled for publishing can't be edited"
47
+ end
48
+
49
+ should "allow editing fields like section" do
50
+ edition = FactoryGirl.create(:edition, :scheduled_for_publishing)
51
+
52
+ edition.section = 'new section'
53
+
54
+ assert edition.save
55
+ assert_equal edition.reload.section, 'new section'
56
+ end
57
+
58
+ should "return false for #can_destroy?" do
59
+ edition = FactoryGirl.create(:edition, :scheduled_for_publishing)
60
+ refute edition.can_destroy?
61
+ end
62
+
63
+ should "allow transition to published state" do
64
+ edition = FactoryGirl.create(:edition, :scheduled_for_publishing)
65
+ assert edition.can_publish?
66
+ end
67
+ end
68
+
69
+ context "#cancel_scheduled_publishing" do
70
+ setup do
71
+ @edition = FactoryGirl.create(:edition, :scheduled_for_publishing)
72
+ @edition.cancel_scheduled_publishing
73
+ @edition.reload
74
+ end
75
+
76
+ should "remove the publish_at stored against the edition" do
77
+ assert_nil @edition.publish_at
78
+ end
79
+
80
+ should "complete the transition back to ready" do
81
+ assert_equal 'ready', @edition.state
82
+ end
83
+ end
84
+ end
@@ -46,6 +46,15 @@ class EditionTest < ActiveSupport::TestCase
46
46
  assert a.errors[:title].any?
47
47
  end
48
48
 
49
+ context "#publish_at" do
50
+ should "not be a time in the past" do
51
+ edition = FactoryGirl.build(:edition, publish_at: 1.minute.ago)
52
+
53
+ refute edition.valid?
54
+ assert_includes edition.errors[:publish_at], "can't be a time in the past"
55
+ end
56
+ end
57
+
49
58
  test "it should give a friendly (legacy supporting) description of its format" do
50
59
  a = LocalTransactionEdition.new
51
60
  assert_equal "LocalTransaction", a.format
@@ -629,6 +638,15 @@ class EditionTest < ActiveSupport::TestCase
629
638
  assert_equal 2, GuideEdition.where(panopticon_id: edition.panopticon_id, state: "archived").count
630
639
  end
631
640
 
641
+ test "when an edition is published, publish_at is cleared" do
642
+ user = FactoryGirl.create(:user)
643
+ edition = FactoryGirl.create(:edition, :scheduled_for_publishing)
644
+
645
+ user.publish edition, comment: "First publication"
646
+
647
+ assert_nil edition.reload.publish_at
648
+ end
649
+
632
650
  test "edition can return latest status action of a specified request type" do
633
651
  edition = FactoryGirl.create(:guide_edition, panopticon_id: @artefact.id, state: "draft")
634
652
  user = User.create(name: "George")
@@ -58,4 +58,32 @@ class WorkflowActorTest < ActiveSupport::TestCase
58
58
  end
59
59
  end
60
60
 
61
+ context "#schedule_for_publishing" do
62
+ setup do
63
+ @user = FactoryGirl.build(:user)
64
+ @publish_at = 1.day.from_now
65
+ @activity_details = { publish_at: @publish_at, comment: "Go schedule !" }
66
+ end
67
+
68
+ should "return false when scheduling an already published edition" do
69
+ edition = FactoryGirl.create(:edition, state: 'published')
70
+ refute @user.schedule_for_publishing(edition, @activity_details)
71
+ end
72
+
73
+ should "schedule an edition for publishing if it is ready" do
74
+ edition = FactoryGirl.create(:edition, state: 'ready')
75
+
76
+ edition = @user.schedule_for_publishing(edition, @activity_details)
77
+
78
+ assert edition.scheduled_for_publishing?
79
+ assert_equal @publish_at.to_i, edition.publish_at.to_i
80
+ end
81
+
82
+ should "record the action" do
83
+ edition = FactoryGirl.create(:edition, state: 'ready')
84
+ @user.expects(:record_action).with(edition, :schedule_for_publishing, { comment: "Go schedule !" })
85
+
86
+ @user.schedule_for_publishing(edition, @activity_details)
87
+ end
88
+ end
61
89
  end
@@ -56,6 +56,19 @@ class WorkflowTest < ActiveSupport::TestCase
56
56
  return user, transaction
57
57
  end
58
58
 
59
+ context "#status_text" do
60
+ should "return a capitalized text representation of the state" do
61
+ assert_equal 'Ready', FactoryGirl.build(:edition, state: 'ready').status_text
62
+ end
63
+
64
+ should "also return scheduled publishing time when the state is scheduled for publishing" do
65
+ edition = FactoryGirl.build(:edition, :scheduled_for_publishing)
66
+ expected_status_text = 'Scheduled for publishing on ' + edition.publish_at.strftime("%d/%m/%Y %H:%M")
67
+
68
+ assert_equal expected_status_text, edition.status_text
69
+ end
70
+ end
71
+
59
72
  test "permits the creation of new editions" do
60
73
  user, transaction = template_user_and_published_transaction
61
74
  assert transaction.persisted?
@@ -219,6 +232,7 @@ class WorkflowTest < ActiveSupport::TestCase
219
232
  assert edition.can_request_review?
220
233
  user.request_review(edition,{comment: "Review this guide please."})
221
234
  refute user.request_amendments(edition, {comment: "Well Done, but work harder"})
235
+ refute user.can_request_amendments?(edition)
222
236
  end
223
237
 
224
238
  test "user should not be able to okay a guide they requested review for" do
@@ -347,4 +361,19 @@ class WorkflowTest < ActiveSupport::TestCase
347
361
  bs.valid?
348
362
  assert_equal "Published editions can't be edited", bs.errors[:base].first
349
363
  end
364
+
365
+ test "User can request amendments for an edition they just approved" do
366
+ user_1, user_2 = template_users
367
+ edition = user_1.create_edition(:answer, panopticon_id: @artefact.id, title: "Answer foo", slug: "answer-foo")
368
+ edition.body = "body content"
369
+ user_1.assign(edition, user_2)
370
+ user_1.request_review(edition,{comment: "Review this guide please."})
371
+ assert edition.in_review?
372
+
373
+ user_2.approve_review(edition, {comment: "Looks good just now"})
374
+ assert edition.ready?
375
+
376
+ user_2.request_amendments(edition, {comment: "More work needed"})
377
+ assert edition.amends_needed?
378
+ end
350
379
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: govuk_content_models
3
3
  version: !ruby/object:Gem::Version
4
- version: 8.1.0
4
+ version: 8.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-03-04 00:00:00.000000000 Z
12
+ date: 2014-03-05 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bson_ext
@@ -417,6 +417,7 @@ files:
417
417
  - test/models/business_support_edition_test.rb
418
418
  - test/models/campaign_edition_test.rb
419
419
  - test/models/curated_list_test.rb
420
+ - test/models/edition_scheduled_for_publishing_test.rb
420
421
  - test/models/edition_test.rb
421
422
  - test/models/fact_check_address_test.rb
422
423
  - test/models/help_page_edition_test.rb
@@ -457,7 +458,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
457
458
  version: '0'
458
459
  segments:
459
460
  - 0
460
- hash: 484371821541707473
461
+ hash: 4108751101128219238
461
462
  required_rubygems_version: !ruby/object:Gem::Requirement
462
463
  none: false
463
464
  requirements:
@@ -466,7 +467,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
466
467
  version: '0'
467
468
  segments:
468
469
  - 0
469
- hash: 484371821541707473
470
+ hash: 4108751101128219238
470
471
  requirements: []
471
472
  rubyforge_project:
472
473
  rubygems_version: 1.8.23
@@ -490,6 +491,7 @@ test_files:
490
491
  - test/models/business_support_edition_test.rb
491
492
  - test/models/campaign_edition_test.rb
492
493
  - test/models/curated_list_test.rb
494
+ - test/models/edition_scheduled_for_publishing_test.rb
493
495
  - test/models/edition_test.rb
494
496
  - test/models/fact_check_address_test.rb
495
497
  - test/models/help_page_edition_test.rb