diff --git a/features/support/sidekiq.rb b/features/support/sidekiq.rb
index 15199882ad3..5be9471b541 100644
--- a/features/support/sidekiq.rb
+++ b/features/support/sidekiq.rb
@@ -14,3 +14,9 @@
block.call
end
end
+
+Around("@enable-sidekiq-test-mode") do |_scenario, block|
+ Sidekiq::Testing.fake! do
+ block.call
+ end
+end
diff --git a/lib/engines/content_block_manager/app/components/content_block_manager/content_block/document/show/summary_list_component.rb b/lib/engines/content_block_manager/app/components/content_block_manager/content_block/document/show/summary_list_component.rb
index 5d2638b6660..0151b884369 100644
--- a/lib/engines/content_block_manager/app/components/content_block_manager/content_block/document/show/summary_list_component.rb
+++ b/lib/engines/content_block_manager/app/components/content_block_manager/content_block/document/show/summary_list_component.rb
@@ -72,6 +72,11 @@ def status_item
{
field: "Status",
value: scheduled_value,
+ edit: {
+ href: helpers.content_block_manager.content_block_manager_content_block_document_schedule_edit_path(content_block_document),
+ link_text: sanitize("Edit schedule"),
+ link_text_no_enhance: true,
+ },
}
else
{
diff --git a/lib/engines/content_block_manager/app/controllers/concerns/can_schedule_or_publish.rb b/lib/engines/content_block_manager/app/controllers/concerns/can_schedule_or_publish.rb
new file mode 100644
index 00000000000..b1842405a83
--- /dev/null
+++ b/lib/engines/content_block_manager/app/controllers/concerns/can_schedule_or_publish.rb
@@ -0,0 +1,27 @@
+module CanScheduleOrPublish
+ extend ActiveSupport::Concern
+
+ def schedule_or_publish(template = "content_block_manager/content_block/editions/workflow/schedule_publishing")
+ @schema = ContentBlockManager::ContentBlock::Schema.find_by_block_type(@content_block_edition.document.block_type)
+
+ if params[:schedule_publishing].blank?
+ @content_block_edition.errors.add(:schedule_publishing, "cannot be blank")
+ raise ActiveRecord::RecordInvalid, @content_block_edition
+ elsif params[:schedule_publishing] == "schedule"
+ ContentBlockManager::ScheduleEditionService.new(@schema).call(@content_block_edition, scheduled_publication_params)
+ else
+ publish and return
+ end
+
+ redirect_to content_block_manager.content_block_manager_content_block_workflow_path(id: @content_block_edition.id,
+ step: :confirmation,
+ is_scheduled: true)
+ rescue ActiveRecord::RecordInvalid
+ render template
+ end
+
+ def publish
+ new_edition = ContentBlockManager::PublishEditionService.new.call(@content_block_edition)
+ redirect_to content_block_manager.content_block_manager_content_block_workflow_path(id: new_edition.id, step: :confirmation)
+ end
+end
diff --git a/lib/engines/content_block_manager/app/controllers/content_block_manager/content_block/documents/schedule_controller.rb b/lib/engines/content_block_manager/app/controllers/content_block_manager/content_block/documents/schedule_controller.rb
new file mode 100644
index 00000000000..72aa3572a2f
--- /dev/null
+++ b/lib/engines/content_block_manager/app/controllers/content_block_manager/content_block/documents/schedule_controller.rb
@@ -0,0 +1,14 @@
+class ContentBlockManager::ContentBlock::Documents::ScheduleController < ContentBlockManager::BaseController
+ include CanScheduleOrPublish
+
+ def edit
+ document = ContentBlockManager::ContentBlock::Document.find(params[:document_id])
+ @content_block_edition = document.latest_edition
+ end
+
+ def update
+ document = ContentBlockManager::ContentBlock::Document.find(params[:document_id])
+ @content_block_edition = document.latest_edition
+ schedule_or_publish("content_block_manager/content_block/documents/schedule/edit")
+ end
+end
diff --git a/lib/engines/content_block_manager/app/controllers/content_block_manager/content_block/editions/workflow_controller.rb b/lib/engines/content_block_manager/app/controllers/content_block_manager/content_block/editions/workflow_controller.rb
index f080dbe3461..11c07002037 100644
--- a/lib/engines/content_block_manager/app/controllers/content_block_manager/content_block/editions/workflow_controller.rb
+++ b/lib/engines/content_block_manager/app/controllers/content_block_manager/content_block/editions/workflow_controller.rb
@@ -1,4 +1,6 @@
class ContentBlockManager::ContentBlock::Editions::WorkflowController < ContentBlockManager::BaseController
+ include CanScheduleOrPublish
+
NEW_BLOCK_STEPS = {
review: "review",
edit_draft: "edit_draft",
@@ -115,27 +117,8 @@ def schedule_publishing
render :schedule_publishing
end
- def schedule_or_publish
- @content_block_edition = ContentBlockManager::ContentBlock::Edition.find(params[:id])
- @schema = ContentBlockManager::ContentBlock::Schema.find_by_block_type(@content_block_edition.document.block_type)
-
- if params[:schedule_publishing].blank?
- @content_block_edition.errors.add(:schedule_publishing, "cannot be blank")
- raise ActiveRecord::RecordInvalid, @content_block_edition
- elsif params[:schedule_publishing] == "schedule"
- ContentBlockManager::ScheduleEditionService.new(@schema).call(@content_block_edition, scheduled_publication_params)
- else
- publish and return
- end
-
- redirect_to content_block_manager.content_block_manager_content_block_workflow_path(id: @content_block_edition.id,
- step: :confirmation,
- is_scheduled: true)
- rescue ActiveRecord::RecordInvalid
- render "content_block_manager/content_block/editions/workflow/schedule_publishing"
- end
-
REVIEW_ERROR = Data.define(:attribute, :full_message)
+
def publish
if params[:step] == NEW_BLOCK_STEPS[:review] && params[:is_confirmed].blank?
@confirm_error_copy = "Confirm details are correct"
diff --git a/lib/engines/content_block_manager/app/lib/content_block_manager/publishable.rb b/lib/engines/content_block_manager/app/lib/content_block_manager/publishable.rb
deleted file mode 100644
index 9d8292c88ea..00000000000
--- a/lib/engines/content_block_manager/app/lib/content_block_manager/publishable.rb
+++ /dev/null
@@ -1,85 +0,0 @@
-module ContentBlockManager
- module Publishable
- class PublishingFailureError < StandardError; end
-
- def publish_with_rollback(content_block_edition)
- document = content_block_edition.document
- schema = ContentBlockManager::ContentBlock::Schema.find_by_block_type(document.block_type)
- content_id = document.content_id
- content_id_alias = document.content_id_alias
-
- create_publishing_api_edition(
- content_id:,
- content_id_alias:,
- schema_id: schema.id,
- title: content_block_edition.title,
- details: content_block_edition.details,
- instructions_to_publishers: content_block_edition.instructions_to_publishers,
- links: {
- primary_publishing_organisation: [
- content_block_edition.lead_organisation.content_id,
- ],
- },
- )
- publish_publishing_api_edition(content_id:)
- update_content_block_document_with_latest_edition(content_block_edition)
- content_block_edition.public_send(:publish!)
- content_block_edition
- rescue PublishingFailureError => e
- discard_publishing_api_edition(content_id:)
- raise e
- end
-
- def schedule_with_rollback
- raise ArgumentError, "Local database changes not given" unless block_given?
-
- ActiveRecord::Base.transaction do
- content_block_edition = yield
-
- content_block_edition.schedule!
- ContentBlockManager::SchedulePublishingWorker.queue(content_block_edition)
- end
- end
-
- def update_content_block_document(new_content_block_edition:, update_document_params:)
- # Updates to a Document should never change its block type
- update_document_params.delete(:block_type)
-
- new_content_block_edition.document.update!(update_document_params)
- new_content_block_edition
- end
-
- private
-
- def create_publishing_api_edition(content_id:, content_id_alias:, schema_id:, title:, instructions_to_publishers:, details:, links:)
- Services.publishing_api.put_content(content_id, {
- schema_name: schema_id,
- document_type: schema_id,
- publishing_app: Whitehall::PublishingApp::WHITEHALL,
- title:,
- instructions_to_publishers:,
- content_id_alias:,
- details:,
- links:,
- update_type: "major",
- })
- end
-
- def publish_publishing_api_edition(content_id:)
- Services.publishing_api.publish(content_id, "content_block")
- rescue GdsApi::HTTPErrorResponse => e
- raise PublishingFailureError, "Could not publish #{content_id} because: #{e.message}"
- end
-
- def discard_publishing_api_edition(content_id:)
- Services.publishing_api.discard_draft(content_id)
- end
-
- def update_content_block_document_with_latest_edition(content_block_edition)
- content_block_edition.document.update!(
- latest_edition_id: content_block_edition.id,
- live_edition_id: content_block_edition.id,
- )
- end
- end
-end
diff --git a/lib/engines/content_block_manager/app/models/content_block_manager/content_block/edition/workflow.rb b/lib/engines/content_block_manager/app/models/content_block_manager/content_block/edition/workflow.rb
index ef88a506dd8..e72ab4604fd 100644
--- a/lib/engines/content_block_manager/app/models/content_block_manager/content_block/edition/workflow.rb
+++ b/lib/engines/content_block_manager/app/models/content_block_manager/content_block/edition/workflow.rb
@@ -5,7 +5,7 @@ module ContentBlock::Edition::Workflow
module ClassMethods
def valid_state?(state)
- %w[draft published scheduled].include?(state)
+ %w[draft published scheduled superseded].include?(state)
end
end
@@ -20,6 +20,7 @@ def valid_state?(state)
state :draft
state :published
state :scheduled
+ state :superseded
event :publish do
transitions from: %i[draft scheduled], to: :published
@@ -27,6 +28,9 @@ def valid_state?(state)
event :schedule do
transitions from: %i[draft], to: :scheduled
end
+ event :supersede do
+ transitions from: %i[scheduled], to: :superseded
+ end
end
end
end
diff --git a/lib/engines/content_block_manager/app/services/content_block_manager/concerns/dequeueable.rb b/lib/engines/content_block_manager/app/services/content_block_manager/concerns/dequeueable.rb
new file mode 100644
index 00000000000..b282a2fec9d
--- /dev/null
+++ b/lib/engines/content_block_manager/app/services/content_block_manager/concerns/dequeueable.rb
@@ -0,0 +1,20 @@
+module ContentBlockManager
+ module Concerns
+ module Dequeueable
+ extend ActiveSupport::Concern
+
+ def dequeue_all_previously_queued_editions(content_block_edition)
+ content_block_edition.document.editions.where(state: :scheduled).find_each do |edition|
+ next if content_block_edition.id == edition.id
+
+ ContentBlockManager::SchedulePublishingWorker.dequeue(edition)
+ edition.supersede!
+ end
+ end
+
+ def dequeue_current_edition_if_previously_scheduled(content_block_edition)
+ ContentBlockManager::SchedulePublishingWorker.dequeue(content_block_edition) if content_block_edition.scheduled?
+ end
+ end
+ end
+end
diff --git a/lib/engines/content_block_manager/app/services/content_block_manager/create_edition_service.rb b/lib/engines/content_block_manager/app/services/content_block_manager/create_edition_service.rb
index 6c2856b3105..1140242be8f 100644
--- a/lib/engines/content_block_manager/app/services/content_block_manager/create_edition_service.rb
+++ b/lib/engines/content_block_manager/app/services/content_block_manager/create_edition_service.rb
@@ -1,7 +1,5 @@
module ContentBlockManager
class CreateEditionService
- include Publishable
-
def initialize(schema)
@schema = schema
end
diff --git a/lib/engines/content_block_manager/app/services/content_block_manager/publish_edition_service.rb b/lib/engines/content_block_manager/app/services/content_block_manager/publish_edition_service.rb
index ab00fa7d1af..af91ead3b13 100644
--- a/lib/engines/content_block_manager/app/services/content_block_manager/publish_edition_service.rb
+++ b/lib/engines/content_block_manager/app/services/content_block_manager/publish_edition_service.rb
@@ -1,9 +1,74 @@
module ContentBlockManager
class PublishEditionService
- include Publishable
+ class PublishingFailureError < StandardError; end
+
+ include Concerns::Dequeueable
def call(edition)
publish_with_rollback(edition)
end
+
+ private
+
+ def publish_with_rollback(content_block_edition)
+ document = content_block_edition.document
+ schema = ContentBlockManager::ContentBlock::Schema.find_by_block_type(document.block_type)
+ content_id = document.content_id
+ content_id_alias = document.content_id_alias
+
+ create_publishing_api_edition(
+ content_id:,
+ content_id_alias:,
+ schema_id: schema.id,
+ title: content_block_edition.title,
+ details: content_block_edition.details,
+ instructions_to_publishers: content_block_edition.instructions_to_publishers,
+ links: {
+ primary_publishing_organisation: [
+ content_block_edition.lead_organisation.content_id,
+ ],
+ },
+ )
+ dequeue_current_edition_if_previously_scheduled(content_block_edition)
+ dequeue_all_previously_queued_editions(content_block_edition)
+ publish_publishing_api_edition(content_id:)
+ update_content_block_document_with_latest_edition(content_block_edition)
+ content_block_edition.public_send(:publish!)
+ content_block_edition
+ rescue PublishingFailureError => e
+ discard_publishing_api_edition(content_id:)
+ raise e
+ end
+
+ def create_publishing_api_edition(content_id:, content_id_alias:, schema_id:, title:, instructions_to_publishers:, details:, links:)
+ Services.publishing_api.put_content(content_id, {
+ schema_name: schema_id,
+ document_type: schema_id,
+ publishing_app: Whitehall::PublishingApp::WHITEHALL,
+ title:,
+ instructions_to_publishers:,
+ content_id_alias:,
+ details:,
+ links:,
+ update_type: "major",
+ })
+ end
+
+ def publish_publishing_api_edition(content_id:)
+ Services.publishing_api.publish(content_id, "content_block")
+ rescue GdsApi::HTTPErrorResponse => e
+ raise PublishingFailureError, "Could not publish #{content_id} because: #{e.message}"
+ end
+
+ def update_content_block_document_with_latest_edition(content_block_edition)
+ content_block_edition.document.update!(
+ latest_edition_id: content_block_edition.id,
+ live_edition_id: content_block_edition.id,
+ )
+ end
+
+ def discard_publishing_api_edition(content_id:)
+ Services.publishing_api.discard_draft(content_id)
+ end
end
end
diff --git a/lib/engines/content_block_manager/app/services/content_block_manager/schedule_edition_service.rb b/lib/engines/content_block_manager/app/services/content_block_manager/schedule_edition_service.rb
index 2e9997f6ec9..83674ffd5be 100644
--- a/lib/engines/content_block_manager/app/services/content_block_manager/schedule_edition_service.rb
+++ b/lib/engines/content_block_manager/app/services/content_block_manager/schedule_edition_service.rb
@@ -1,6 +1,6 @@
module ContentBlockManager
class ScheduleEditionService
- include Publishable
+ include Concerns::Dequeueable
def initialize(schema)
@schema = schema
@@ -18,6 +18,20 @@ def call(edition, scheduled_publication_params)
private
+ def schedule_with_rollback
+ raise ArgumentError, "Local database changes not given" unless block_given?
+
+ ActiveRecord::Base.transaction do
+ content_block_edition = yield
+
+ dequeue_current_edition_if_previously_scheduled(content_block_edition)
+ content_block_edition.schedule! unless content_block_edition.scheduled?
+
+ dequeue_all_previously_queued_editions(content_block_edition)
+ ContentBlockManager::SchedulePublishingWorker.queue(content_block_edition)
+ end
+ end
+
def send_publish_intents_for_host_documents(content_block_edition:)
host_content_items = ContentBlockManager::GetHostContentItems.by_embedded_document(
content_block_document: content_block_edition.document,
diff --git a/lib/engines/content_block_manager/app/views/content_block_manager/content_block/documents/schedule/edit.html.erb b/lib/engines/content_block_manager/app/views/content_block_manager/content_block/documents/schedule/edit.html.erb
new file mode 100644
index 00000000000..6d7af832bfa
--- /dev/null
+++ b/lib/engines/content_block_manager/app/views/content_block_manager/content_block/documents/schedule/edit.html.erb
@@ -0,0 +1,5 @@
+<%= render "content_block_manager/content_block/shared/schedule_publishing", {
+ context: "Edit a content block",
+ back_link: content_block_manager.content_block_manager_content_block_document_path(id: @content_block_edition.document.id),
+ form_url: content_block_manager.content_block_manager_content_block_document_update_schedule_path(document_id: @content_block_edition.document.id),
+} %>
diff --git a/lib/engines/content_block_manager/app/views/content_block_manager/content_block/editions/workflow/schedule_publishing.html.erb b/lib/engines/content_block_manager/app/views/content_block_manager/content_block/editions/workflow/schedule_publishing.html.erb
index c497b0b9fda..dcdd840858d 100644
--- a/lib/engines/content_block_manager/app/views/content_block_manager/content_block/editions/workflow/schedule_publishing.html.erb
+++ b/lib/engines/content_block_manager/app/views/content_block_manager/content_block/editions/workflow/schedule_publishing.html.erb
@@ -1,114 +1,11 @@
-<% content_for :context, context %>
-<% content_for :title, "When do you want to publish the change?" %>
-<% content_for :title_margin_bottom, 4 %>
-<% content_for :back_link do %>
- <%= render "govuk_publishing_components/components/back_link", {
- href: content_block_manager.content_block_manager_content_block_workflow_path(
- edition_id: @content_block_edition.id,
- step: ContentBlockManager::ContentBlock::Editions::WorkflowController::UPDATE_BLOCK_STEPS[:review_links],
+<%= render "content_block_manager/content_block/shared/schedule_publishing", {
+ context:,
+ back_link: content_block_manager.content_block_manager_content_block_workflow_path(
+ edition_id: @content_block_edition.id,
+ step: ContentBlockManager::ContentBlock::Editions::WorkflowController::UPDATE_BLOCK_STEPS[:review_links],
),
- } %>
-<% end %>
-
-<%
- year_param = params.dig("scheduled_at", "scheduled_publication(1i)")
- month_param = params.dig("scheduled_at", "scheduled_publication(2i)")
- day_param = params.dig("scheduled_at", "scheduled_publication(3i)")
- hour_param = params.dig("scheduled_at", "scheduled_publication(4i)")
- minute_param = params.dig("scheduled_at", "scheduled_publication(5i)")
- is_scheduled_param = params["schedule_publishing"]
-%>
-
-
Choose when you would like the change to be made.
-
-<% content_for :error_summary, render(Admin::ErrorSummaryComponent.new(object: @content_block_edition)) %>
-
-
-
- <%= form_with url: content_block_manager.content_block_manager_content_block_workflow_path(
- edition_id: @content_block_edition.id,
- step: ContentBlockManager::ContentBlock::Editions::WorkflowController::UPDATE_BLOCK_STEPS[:schedule_publishing],
- ),
- method: :put do %>
-
- <%= render "govuk_publishing_components/components/radio", {
- name: "schedule_publishing",
- id: "schedule_publishing",
- heading_size: "xl",
- error_items: errors_for(@content_block_edition.errors, :schedule_publishing),
- items: [
- {
- value: "now",
- checked: is_scheduled_param == "now",
- text: "Publish the change now",
- },
- {
- value: "schedule",
- checked: is_scheduled_param == "schedule",
- text: "Schedule the change for the future",
- conditional: render("components/datetime_fields", {
- heading_size: "s",
- field_name: "scheduled_publication",
- prefix: "scheduled_at",
- date_heading: "Date",
- date_hint: "For example, 01 08 2025",
- time_hint: "For example, 09:30 or 19:30",
- error_items: errors_for(@content_block_edition.errors, :scheduled_publication),
- year: {
- value: year_param.blank? ? nil : year_param.to_i,
- id: "scheduled_at_scheduled_publication_1i",
- name: "scheduled_at[scheduled_publication(1i)]",
- label: "Year",
- width: 4,
- },
- month: {
- value: month_param.blank? ? nil : month_param.to_i,
- id: "scheduled_at_scheduled_publication_2i",
- name: "scheduled_at[scheduled_publication(2i)]",
- label: "Month",
- width: 2,
- },
- day: {
- value: day_param.blank? ? nil : day_param.to_i,
- id: "content_block_manager/content_block/edition_scheduled_publication",
- name: "scheduled_at[scheduled_publication(3i)]",
- label: "Day",
- width: 2,
- },
- hour: {
- value: hour_param.blank? ? nil : hour_param.to_i,
- id: "scheduled_at_scheduled_publication_4i",
- name: "scheduled_at[scheduled_publication(4i)]",
- },
- minute: {
- value: minute_param.blank? ? nil : minute_param.to_i,
- id: "scheduled_at_scheduled_publication_5i",
- name: "scheduled_at[scheduled_publication(5i)]",
- },
- }),
- },
- ],
- } %>
-
-
-
-
+ form_url: content_block_manager.content_block_manager_content_block_workflow_path(
+ edition_id: @content_block_edition.id,
+ step: ContentBlockManager::ContentBlock::Editions::WorkflowController::UPDATE_BLOCK_STEPS[:schedule_publishing],
+ ),
+} %>
diff --git a/lib/engines/content_block_manager/app/views/content_block_manager/content_block/shared/_schedule_publishing.html.erb b/lib/engines/content_block_manager/app/views/content_block_manager/content_block/shared/_schedule_publishing.html.erb
new file mode 100644
index 00000000000..7c0e0a259d9
--- /dev/null
+++ b/lib/engines/content_block_manager/app/views/content_block_manager/content_block/shared/_schedule_publishing.html.erb
@@ -0,0 +1,107 @@
+<% content_for :context, context %>
+<% content_for :title, "When do you want to publish the change?" %>
+<% content_for :title_margin_bottom, 4 %>
+<% content_for :back_link do %>
+ <%= render "govuk_publishing_components/components/back_link", {
+ href: back_link,
+ } %>
+<% end %>
+
+<%
+ year_param = params.dig("scheduled_at", "scheduled_publication(1i)")
+ month_param = params.dig("scheduled_at", "scheduled_publication(2i)")
+ day_param = params.dig("scheduled_at", "scheduled_publication(3i)")
+ hour_param = params.dig("scheduled_at", "scheduled_publication(4i)")
+ minute_param = params.dig("scheduled_at", "scheduled_publication(5i)")
+ is_scheduled_param = params["schedule_publishing"]
+%>
+
+Choose when you would like the change to be made.
+
+<% content_for :error_summary, render(Admin::ErrorSummaryComponent.new(object: @content_block_edition)) %>
+
+
+
+ <%= form_with url: form_url, method: :put do %>
+
+ <%= render "govuk_publishing_components/components/radio", {
+ name: "schedule_publishing",
+ id: "schedule_publishing",
+ heading_size: "xl",
+ error_items: errors_for(@content_block_edition.errors, :schedule_publishing),
+ items: [
+ {
+ value: "now",
+ checked: is_scheduled_param == "now",
+ text: "Publish the change now",
+ },
+ {
+ value: "schedule",
+ checked: is_scheduled_param == "schedule",
+ text: "Schedule the change for the future",
+ conditional: render("components/datetime_fields", {
+ heading_size: "s",
+ field_name: "scheduled_publication",
+ prefix: "scheduled_at",
+ date_heading: "Date",
+ date_hint: "For example, 01 08 2025",
+ time_hint: "For example, 09:30 or 19:30",
+ error_items: errors_for(@content_block_edition.errors, :scheduled_publication),
+ year: {
+ value: year_param.blank? ? nil : year_param.to_i,
+ id: "scheduled_at_scheduled_publication_1i",
+ name: "scheduled_at[scheduled_publication(1i)]",
+ label: "Year",
+ width: 4,
+ },
+ month: {
+ value: month_param.blank? ? nil : month_param.to_i,
+ id: "scheduled_at_scheduled_publication_2i",
+ name: "scheduled_at[scheduled_publication(2i)]",
+ label: "Month",
+ width: 2,
+ },
+ day: {
+ value: day_param.blank? ? nil : day_param.to_i,
+ id: "content_block_manager/content_block/edition_scheduled_publication",
+ name: "scheduled_at[scheduled_publication(3i)]",
+ label: "Day",
+ width: 2,
+ },
+ hour: {
+ value: hour_param.blank? ? nil : hour_param.to_i,
+ id: "scheduled_at_scheduled_publication_4i",
+ name: "scheduled_at[scheduled_publication(4i)]",
+ },
+ minute: {
+ value: minute_param.blank? ? nil : minute_param.to_i,
+ id: "scheduled_at_scheduled_publication_5i",
+ name: "scheduled_at[scheduled_publication(5i)]",
+ },
+ }),
+ },
+ ],
+ } %>
+
+
+
+
diff --git a/lib/engines/content_block_manager/app/workers/content_block_manager/schedule_publishing_worker.rb b/lib/engines/content_block_manager/app/workers/content_block_manager/schedule_publishing_worker.rb
index d0961dd4fbb..e800816d184 100644
--- a/lib/engines/content_block_manager/app/workers/content_block_manager/schedule_publishing_worker.rb
+++ b/lib/engines/content_block_manager/app/workers/content_block_manager/schedule_publishing_worker.rb
@@ -49,7 +49,7 @@ def perform(edition_id)
ContentBlockManager::ContentBlock::Edition::HasAuditTrail.acting_as(publishing_robot) do
ContentBlockManager::PublishEditionService.new.call(edition)
end
- rescue ContentBlockManager::Publishable::PublishingFailureError => e
+ rescue ContentBlockManager::PublishEditionService::PublishingFailureError => e
raise SchedulingFailure, e.message
end
diff --git a/lib/engines/content_block_manager/config/routes.rb b/lib/engines/content_block_manager/config/routes.rb
index 3fcfbc080f8..46d9164da59 100644
--- a/lib/engines/content_block_manager/config/routes.rb
+++ b/lib/engines/content_block_manager/config/routes.rb
@@ -8,6 +8,9 @@
post :new_document_options_redirect
end
resources :editions, only: %i[new create]
+ get "schedule/edit", to: "documents/schedule#edit", as: :schedule_edit
+ put "schedule", to: "documents/schedule#update", as: :update_schedule
+ patch "schedule", to: "documents/schedule#update"
end
resources :editions, only: %i[new create destroy], path_names: { new: ":block_type/new" } do
member do
diff --git a/lib/engines/content_block_manager/features/edit_object.feature b/lib/engines/content_block_manager/features/edit_object.feature
index 1c847d4467f..090ca621f8c 100644
--- a/lib/engines/content_block_manager/features/edit_object.feature
+++ b/lib/engines/content_block_manager/features/edit_object.feature
@@ -81,6 +81,7 @@ Feature: Edit a content object
| my email address | xxxxx | Ministry of Example |
Then I should see a message that the "email_address" field is an invalid "email"
+ @enable-sidekiq-test-mode
Scenario: GDS editor can override a previously scheduled object
When I am updating a content block
And I am asked when I want to publish the change
diff --git a/lib/engines/content_block_manager/features/schedule_object.feature b/lib/engines/content_block_manager/features/schedule_object.feature
index 7ae57bc216d..cba32407e2f 100644
--- a/lib/engines/content_block_manager/features/schedule_object.feature
+++ b/lib/engines/content_block_manager/features/schedule_object.feature
@@ -7,6 +7,7 @@ Feature: Schedule a content object
| email_address | string | email | true |
And an email address content block has been created
+ @enable-sidekiq-test-mode
Scenario: GDS Editor schedules a content object
When I am updating a content block
Then I am asked when I want to publish the change
@@ -16,12 +17,58 @@ Feature: Schedule a content object
And I should see the scheduled date on the object
And I should see the scheduled event on the timeline
+ @disable-sidekiq-test-mode
+ Scenario: GDS Editor immediately publishes a scheduled content object
+ When I am updating a content block
+ Then I am asked when I want to publish the change
+ And I schedule the change for 7 days in the future
+ When I click to view the content block
+ And I click to edit the schedule
+ And I choose to publish the change now
+ And I accept and publish
+ When I click to view the content block
+ Then the published state of the object should be shown
+ And there should be no jobs scheduled
+
+ @disable-sidekiq-test-mode
+ Scenario: GDS Editor reschedules a content object
+ When I am updating a content block
+ Then I am asked when I want to publish the change
+ And I schedule the change for 7 days in the future
+ When I click to view the content block
+ And I click to edit the schedule
+ And I schedule the change for 5 days in the future
+ When I click to view the content block
+ Then I should see the scheduled date on the object
+ And there should only be one job scheduled
+
+ @disable-sidekiq-test-mode
+ Scenario: GDS Editor publishes a new version of a previously scheduled content object
+ When I am updating a content block
+ Then I am asked when I want to publish the change
+ And I schedule the change for 7 days in the future
+ When I am updating a content block
+ And I choose to publish the change now
+ And I accept and publish
+ Then there should be no jobs scheduled
+
+ @disable-sidekiq-test-mode
+ Scenario: GDS Editor schedules a new version of a previously scheduled content block
+ When I am updating a content block
+ Then I am asked when I want to publish the change
+ And I schedule the change for 7 days in the future
+ When I click to view the content block
+ And I click to edit the schedule
+ And I schedule the change for 5 days in the future
+ When I click to view the content block
+ Then there should only be one job scheduled
+
Scenario: A scheduled content object is published
When I am updating a content block
Then I am asked when I want to publish the change
When I choose to schedule the change
And the block is scheduled and published
- Then published state of the object is shown
+ Then the published state of the object should be shown
And I should see the publish event on the timeline
Scenario: GDS Editor does not provide date for scheduling
diff --git a/lib/engines/content_block_manager/features/step_definitions/content_block_manager_steps.rb b/lib/engines/content_block_manager/features/step_definitions/content_block_manager_steps.rb
index 3da073fb356..5c22f079e75 100644
--- a/lib/engines/content_block_manager/features/step_definitions/content_block_manager_steps.rb
+++ b/lib/engines/content_block_manager/features/step_definitions/content_block_manager_steps.rb
@@ -617,14 +617,12 @@ def should_show_edit_form_for_email_address_content_block(document_title, email_
choose "Schedule the change for the future"
end
-When("I schedule the change for 7 days in the future") do
+And(/^I schedule the change for (\d+) days in the future$/) do |number_of_days|
choose "Schedule the change for the future"
- @future_date = 7.days.since(Time.zone.now)
+ @future_date = number_of_days.days.since(Time.zone.now)
fill_in_date_and_time_field(@future_date)
- Sidekiq::Testing.fake! do
- click_on "Accept and publish"
- end
+ click_on "Accept and publish"
end
When("I enter an invalid date") do
@@ -655,7 +653,7 @@ def should_show_edit_form_for_email_address_content_block(document_title, email_
end
end
-Then("published state of the object is shown") do
+Then("the published state of the object should be shown") do
visit content_block_manager.content_block_manager_content_block_document_path(@content_block.document)
expect(page).to have_selector(".govuk-summary-list__key", text: "Status")
expect(page).to have_selector(".govuk-summary-list__value", text: "Published")
@@ -730,3 +728,17 @@ def click_save_and_continue
clip_text = page.evaluate_async_script("navigator.clipboard.readText().then(arguments[0])")
expect(clip_text).to eq(@embed_code)
end
+
+When("I click to edit the schedule") do
+ find("a", text: "Edit schedule").click
+end
+
+Then(/^there should only be one job scheduled$/) do
+ jobs = Sidekiq::ScheduledSet.new.select { |job| job.item["class"] == ContentBlockManager::SchedulePublishingWorker.to_s }
+ expect(jobs.count).to eq(1)
+end
+
+Then(/^there should be no jobs scheduled$/) do
+ jobs = Sidekiq::ScheduledSet.new.select { |job| job.item["class"] == ContentBlockManager::SchedulePublishingWorker.to_s }
+ expect(jobs.count).to eq(0)
+end
diff --git a/lib/engines/content_block_manager/features/support/stubs.rb b/lib/engines/content_block_manager/features/support/stubs.rb
index a6f4932e3de..afeec020711 100644
--- a/lib/engines/content_block_manager/features/support/stubs.rb
+++ b/lib/engines/content_block_manager/features/support/stubs.rb
@@ -1,3 +1,12 @@
Before do
stub_publishing_api_has_embedded_content_for_any_content_id(total: 0, results: [], order: ContentBlockManager::GetHostContentItems::DEFAULT_ORDER)
end
+
+Before("@disable_sidekiq") do
+ Sidekiq::Testing.fake!
+end
+
+Before("@enable_sidekiq") do
+ Sidekiq::Testing.disable!
+ Sidekiq::ScheduledSet.new.map(&:delete)
+end
diff --git a/lib/engines/content_block_manager/test/components/content_block/document/show/summary_list_component_test.rb b/lib/engines/content_block_manager/test/components/content_block/document/show/summary_list_component_test.rb
index 4d3b8775b32..1101e45c078 100644
--- a/lib/engines/content_block_manager/test/components/content_block/document/show/summary_list_component_test.rb
+++ b/lib/engines/content_block_manager/test/components/content_block/document/show/summary_list_component_test.rb
@@ -2,6 +2,7 @@
class ContentBlockManager::ContentBlock::Document::Show::SummaryListComponentTest < ViewComponent::TestCase
extend Minitest::Spec::DSL
+ include ContentBlockManager::Engine.routes.url_helpers
let(:organisation) { create(:organisation, name: "Department for Example") }
let(:content_block_edition) do
@@ -60,6 +61,8 @@ class ContentBlockManager::ContentBlock::Document::Show::SummaryListComponentTes
assert_selector ".govuk-summary-list__key", text: "Status"
assert_selector ".govuk-summary-list__value", text: "Scheduled for publication at #{I18n.l(content_block_edition.scheduled_publication, format: :long_ordinal)}"
+ assert_selector ".govuk-summary-list__actions", text: "Edit schedule"
+ assert_selector ".govuk-summary-list__actions a[href='#{content_block_manager_content_block_document_schedule_edit_path(content_block_document)}']"
end
describe "when there are instructions to publishers" do
diff --git a/lib/engines/content_block_manager/test/unit/app/models/content_block_edition/workflow_test.rb b/lib/engines/content_block_manager/test/unit/app/models/content_block_edition/workflow_test.rb
index 5fb0ba37bf7..97cdf817fbe 100644
--- a/lib/engines/content_block_manager/test/unit/app/models/content_block_edition/workflow_test.rb
+++ b/lib/engines/content_block_manager/test/unit/app/models/content_block_edition/workflow_test.rb
@@ -34,4 +34,10 @@ class ContentBlockManager::WorkflowTest < ActiveSupport::TestCase
edition.schedule!
assert edition.scheduled?
end
+
+ test "superseding a scheduled edition transitions it into the superseded state" do
+ edition = create(:content_block_edition, :email_address, scheduled_publication: 7.days.since(Time.zone.now).to_date, state: "scheduled")
+ edition.supersede!
+ assert edition.superseded?
+ end
end
diff --git a/lib/engines/content_block_manager/test/unit/app/services/publish_edition_service_test.rb b/lib/engines/content_block_manager/test/unit/app/services/publish_edition_service_test.rb
index 3ada2502559..df0f407ec35 100644
--- a/lib/engines/content_block_manager/test/unit/app/services/publish_edition_service_test.rb
+++ b/lib/engines/content_block_manager/test/unit/app/services/publish_edition_service_test.rb
@@ -29,11 +29,21 @@ class ContentBlockManager::PublishEditionServiceTest < ActiveSupport::TestCase
end
it "publishes the Edition in Whitehall" do
+ ContentBlockManager::SchedulePublishingWorker.expects(:dequeue).never
+
ContentBlockManager::PublishEditionService.new.call(edition)
assert_equal "published", edition.state
assert_equal edition.id, document.live_edition_id
end
+ it "removes any existing queues if the edition is already scheduled" do
+ edition.expects(:scheduled?).returns(true)
+ ContentBlockManager::SchedulePublishingWorker.expects(:dequeue).with(edition)
+
+ ContentBlockManager::PublishEditionService.new.call(edition)
+ assert_equal "published", edition.state
+ end
+
it "creates an Edition in the Publishing API" do
fake_put_content_response = GdsApi::Response.new(
stub("http_response", code: 200, body: {}),
@@ -129,5 +139,22 @@ class ContentBlockManager::PublishEditionServiceTest < ActiveSupport::TestCase
assert_nil document.live_edition_id
end
end
+
+ it "supersedes any previously scheduled editions" do
+ scheduled_editions = create_list(:content_block_edition, 2,
+ document:,
+ scheduled_publication: 7.days.from_now,
+ state: "scheduled")
+
+ scheduled_editions.each do |scheduled_edition|
+ ContentBlockManager::SchedulePublishingWorker.expects(:dequeue).with(scheduled_edition)
+ end
+
+ ContentBlockManager::PublishEditionService.new.call(edition)
+
+ scheduled_editions.each do |scheduled_edition|
+ assert scheduled_edition.reload.superseded?
+ end
+ end
end
end
diff --git a/lib/engines/content_block_manager/test/unit/app/services/schedule_edition_service_test.rb b/lib/engines/content_block_manager/test/unit/app/services/schedule_edition_service_test.rb
index 290c46f556b..1eb6f4b5414 100644
--- a/lib/engines/content_block_manager/test/unit/app/services/schedule_edition_service_test.rb
+++ b/lib/engines/content_block_manager/test/unit/app/services/schedule_edition_service_test.rb
@@ -15,6 +15,8 @@ class ContentBlockManager::ScheduleEditionServiceTest < ActiveSupport::TestCase
end
setup do
+ ContentBlockManager::ContentBlock::Schema.stubs(:find_by_block_type)
+ .returns(schema)
stub_publishing_api_has_embedded_content(content_id:, total: 0, results: [], order: ContentBlockManager::GetHostContentItems::DEFAULT_ORDER)
end
@@ -30,6 +32,8 @@ class ContentBlockManager::ScheduleEditionServiceTest < ActiveSupport::TestCase
end
it "schedules a new Edition via the Content Block Worker" do
+ ContentBlockManager::SchedulePublishingWorker.expects(:dequeue).never
+
ContentBlockManager::SchedulePublishingWorker.expects(:queue).with do |expected_edition|
expected_edition.id = edition.id &&
expected_edition.scheduled_publication.year == scheduled_publication_params[:"scheduled_publication(1i)"].to_i &&
@@ -47,6 +51,46 @@ class ContentBlockManager::ScheduleEditionServiceTest < ActiveSupport::TestCase
assert updated_edition.scheduled?
end
+ it "dequeues existing jobs if the edition is already scheduled" do
+ ContentBlockManager::SchedulePublishingWorker.expects(:dequeue).with do |expected_edition|
+ expected_edition.id = edition.id
+ end
+
+ ContentBlockManager::SchedulePublishingWorker.expects(:queue).with do |expected_edition|
+ expected_edition.id = edition.id
+ end
+
+ edition.update!(scheduled_publication_params)
+ edition.schedule!
+
+ ContentBlockManager::ScheduleEditionService
+ .new(schema)
+ .call(edition, scheduled_publication_params)
+ end
+
+ it "supersedes any previously scheduled editions" do
+ scheduled_editions = create_list(:content_block_edition, 2,
+ document: edition.document,
+ scheduled_publication: 7.days.from_now,
+ state: "scheduled")
+
+ ContentBlockManager::SchedulePublishingWorker.expects(:queue).with do |expected_edition|
+ expected_edition.id = edition.id
+ end
+
+ scheduled_editions.each do |scheduled_edition|
+ ContentBlockManager::SchedulePublishingWorker.expects(:dequeue).with(scheduled_edition)
+ end
+
+ ContentBlockManager::ScheduleEditionService
+ .new(schema)
+ .call(edition, scheduled_publication_params)
+
+ scheduled_editions.each do |scheduled_edition|
+ assert scheduled_edition.reload.superseded?
+ end
+ end
+
it "does not persist the changes if the Worker request fails" do
exception = GdsApi::HTTPErrorResponse.new(
422,