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)]", - }, - }), - }, - ], - } %> - -
-
- <%= render "govuk_publishing_components/components/button", { - text: "Accept and publish", - name: "accept_and_publish", - value: "Accept and publish", - type: "submit", - } %> - <% end %> -
-
- <%= render partial: "content_block_manager/content_block/shared/cancel_delete_button", - locals: { - url: content_block_manager.content_block_manager_content_block_edition_path( - @content_block_edition, - redirect_path: content_block_manager.content_block_manager_content_block_document_path(@content_block_edition.document), - ), - } %> -
-
-
-
+ 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)]", + }, + }), + }, + ], + } %> + +
+
+ <%= render "govuk_publishing_components/components/button", { + text: "Accept and publish", + name: "accept_and_publish", + value: "Accept and publish", + type: "submit", + } %> + <% end %> +
+
+ <%= render partial: "content_block_manager/content_block/shared/cancel_delete_button", + locals: { + url: content_block_manager.content_block_manager_content_block_edition_path( + @content_block_edition, + redirect_path: content_block_manager.content_block_manager_content_block_document_path(@content_block_edition.document), + ), + } %> +
+
+
+
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,