diff --git a/app/controllers/downtimes_controller.rb b/app/controllers/downtimes_controller.rb
index f7c9fc5ef..80c91440b 100644
--- a/app/controllers/downtimes_controller.rb
+++ b/app/controllers/downtimes_controller.rb
@@ -1,75 +1,7 @@
class DowntimesController < ApplicationController
before_action :require_govuk_editor
- before_action :load_edition, except: [:index]
- before_action :process_params, only: %i[create update]
def index
@transactions = TransactionEdition.published.order_by(%i[title asc])
end
-
- def new
- @downtime = Downtime.new(artefact: @edition.artefact)
- end
-
- def create
- @downtime = Downtime.new(downtime_params)
- if @downtime.save
- DowntimeScheduler.schedule_publish_and_expiry(@downtime)
- flash[:success] = "#{edition_link} downtime message scheduled (from #{view_context.downtime_datetime(@downtime)})".html_safe
- redirect_to downtimes_path
- else
- render :new
- end
- end
-
- def edit
- @downtime = Downtime.for(@edition.artefact)
- end
-
- def update
- @downtime = Downtime.for(@edition.artefact)
-
- if params["commit"] == "Cancel downtime"
- DowntimeRemover.destroy_immediately(@downtime)
- flash[:success] = "#{edition_link} downtime message cancelled".html_safe
- redirect_to downtimes_path
- elsif @downtime.update(downtime_params)
- DowntimeScheduler.schedule_publish_and_expiry(@downtime)
- flash[:success] = "#{edition_link} downtime message re-scheduled (from #{view_context.downtime_datetime(@downtime)})".html_safe
- redirect_to downtimes_path
- else
- render :edit
- end
- end
-
-private
-
- def downtime_params
- params[:downtime].permit([
- "artefact_id",
- "message",
- "end_time(1i)",
- "end_time(2i)",
- "end_time(3i)",
- "end_time(4i)",
- "end_time(5i)",
- "start_time(1i)",
- "start_time(2i)",
- "start_time(3i)",
- "start_time(4i)",
- "start_time(5i)",
- ])
- end
-
- def load_edition
- @edition = Edition.find(params[:edition_id])
- end
-
- def process_params
- squash_multiparameter_datetime_attributes(downtime_params, %w[start_time end_time])
- end
-
- def edition_link
- view_context.link_to(@edition.title, edit_edition_downtime_path(@edition), class: "link-inherit bold")
- end
end
diff --git a/app/controllers/legacy_downtimes_controller.rb b/app/controllers/legacy_downtimes_controller.rb
new file mode 100644
index 000000000..6932ae0da
--- /dev/null
+++ b/app/controllers/legacy_downtimes_controller.rb
@@ -0,0 +1,75 @@
+class LegacyDowntimesController < ApplicationController
+ before_action :require_govuk_editor
+ before_action :load_edition, except: [:index]
+ before_action :process_params, only: %i[create update]
+
+ def index
+ @transactions = TransactionEdition.published.order_by(%i[title asc])
+ end
+
+ def new
+ @downtime = Downtime.new(artefact: @edition.artefact)
+ end
+
+ def create
+ @downtime = Downtime.new(downtime_params)
+ if @downtime.save
+ DowntimeScheduler.schedule_publish_and_expiry(@downtime)
+ flash[:success] = "#{edition_link} downtime message scheduled (from #{view_context.downtime_datetime(@downtime)})".html_safe
+ redirect_to downtimes_path
+ else
+ render :new
+ end
+ end
+
+ def edit
+ @downtime = Downtime.for(@edition.artefact)
+ end
+
+ def update
+ @downtime = Downtime.for(@edition.artefact)
+
+ if params["commit"] == "Cancel downtime"
+ DowntimeRemover.destroy_immediately(@downtime)
+ flash[:success] = "#{edition_link} downtime message cancelled".html_safe
+ redirect_to downtimes_path
+ elsif @downtime.update(downtime_params)
+ DowntimeScheduler.schedule_publish_and_expiry(@downtime)
+ flash[:success] = "#{edition_link} downtime message re-scheduled (from #{view_context.downtime_datetime(@downtime)})".html_safe
+ redirect_to downtimes_path
+ else
+ render :edit
+ end
+ end
+
+private
+
+ def downtime_params
+ params[:downtime].permit([
+ "artefact_id",
+ "message",
+ "end_time(1i)",
+ "end_time(2i)",
+ "end_time(3i)",
+ "end_time(4i)",
+ "end_time(5i)",
+ "start_time(1i)",
+ "start_time(2i)",
+ "start_time(3i)",
+ "start_time(4i)",
+ "start_time(5i)",
+ ])
+ end
+
+ def load_edition
+ @edition = Edition.find(params[:edition_id])
+ end
+
+ def process_params
+ squash_multiparameter_datetime_attributes(downtime_params, %w[start_time end_time])
+ end
+
+ def edition_link
+ view_context.link_to(@edition.title, edit_edition_downtime_path(@edition), class: "link-inherit bold")
+ end
+end
diff --git a/app/views/layouts/design_system.html.erb b/app/views/layouts/design_system.html.erb
index 918c46d57..3ab1b11ae 100644
--- a/app/views/layouts/design_system.html.erb
+++ b/app/views/layouts/design_system.html.erb
@@ -18,6 +18,19 @@
+ <% [:success, :info, :warning, :danger, :notice, :alert].select { |k| flash[k].present? }.each do |k| %>
+ <%
+ case k
+ when :notice
+ alert_class = "success"
+ when :alert
+ alert_class = "danger"
+ else
+ alert_class = k
+ end
+ %>
+ <%= flash[k] %>
+ <% end %>
<%= yield %>
diff --git a/app/views/legacy_downtimes/_form.html.erb b/app/views/legacy_downtimes/_form.html.erb
new file mode 100644
index 000000000..b64bfc91d
--- /dev/null
+++ b/app/views/legacy_downtimes/_form.html.erb
@@ -0,0 +1,88 @@
+<%= f.hidden_field :artefact_id %>
+
+<%= render :partial => 'shared/error_summary', locals: { object: @downtime} %>
+
+
+
+
+
+
+ <%= f.label :start_time, "Hour", for: "downtime_start_time_4i" %>
+ <%= select_hour @downtime.start_time&.hour || 1.hour.from_now.beginning_of_hour, { field_name: "start_time(4i)", prefix: "downtime" }, { class: "form-control date" } %>
+
+
+
+ <%= f.label :start_time, "Minute", for: "downtime_start_time_5i" %>
+ <%= select_minute @downtime.start_time&.minute, { field_name: "start_time(5i)", prefix: "downtime" }, { class: "form-control date" } %>
+
+
+
+
+
+ <%= f.label :start_time, "Day", for: "downtime_start_time_3i" %>
+ <%= select_day @downtime.start_time&.day || Date.tomorrow.day, { field_name: "start_time(3i)", prefix: "downtime" }, { class: "form-control date" } %>
+
+
+ <%= f.label :start_time, "Month", for: "downtime_start_time_2i" %>
+ <%= select_month@downtime.start_time&.month || Date.tomorrow.month, { field_name: "start_time(2i)", prefix: "downtime" }, { class: "form-control date" } %>
+
+
+
+ <%= f.label :start_time, "Year", for: "downtime_start_time_1i" %>
+ <%= select_year @downtime.start_time&.year || Date.tomorrow.year, { field_name: "start_time(1i)", prefix: "downtime" }, { class: "form-control date" } %>
+
+
+
+
+
+
+
+
+ <%= f.label :end_time, "Hour", for: "downtime_end_time_4i" %>
+ <%= select_hour @downtime.end_time&.hour || 1.hour.from_now.beginning_of_hour, { field_name: "end_time(4i)", prefix: "downtime" }, { class: "form-control date" } %>
+
+
+
+ <%= f.label :end_time, "Minute", for: "downtime_end_time_5i" %>
+ <%= select_minute @downtime.end_time&.minute, { field_name: "end_time(5i)", prefix: "downtime" }, { class: "form-control date" } %>
+
+
+
+
+
+ <%= f.label :start_time, "Day", for: "downtime_end_time_3i" %>
+ <%= select_day @downtime.end_time&.day || Date.tomorrow.day, { field_name: "end_time(3i)", prefix: "downtime" }, { class: "form-control date" } %>
+
+
+
+ <%= f.label :end_time, "Month", for: "downtime_end_time_2i" %>
+ <%= select_month @downtime.end_time&.month || Date.tomorrow.month, { field_name: "end_time(2i)", prefix: "downtime" }, { class: "form-control date" } %>
+
+
+
+ <%= f.label :end_time, "Year", for: "downtime_end_time_1i" %>
+ <%= select_year @downtime.end_time&.year || Date.tomorrow.year, { field_name: "end_time(1i)", prefix: "downtime" }, { class: "form-control date" } %>
+
+
+
+
+
+
+<%= form_group(f, :message) do %>
+ <%= f.text_area :message, class: 'form-control input-md-6 js-downtime-message', rows: 5 %>
+<% end %>
+
+
+
+ Messages appear on the start page one day before the downtime is due.
+
+
+
diff --git a/app/views/legacy_downtimes/edit.html.erb b/app/views/legacy_downtimes/edit.html.erb
new file mode 100644
index 000000000..9d263c669
--- /dev/null
+++ b/app/views/legacy_downtimes/edit.html.erb
@@ -0,0 +1,19 @@
+<% content_for :page_title, 'Re-schedule downtime message' %>
+
+
+ - <%= link_to 'Downtime', downtimes_path %>
+ - <%= @downtime.artefact.name %>
+
+
+
+
+ <%= @downtime.artefact.name %>
+ Re-schedule downtime message
+
+
+
+<%= form_for @downtime, url: edition_downtime_path(@edition), html: { class: 'form well remove-top-margin', 'data-module': 'downtime-message' } do |f| %>
+ <%= render 'form', f: f %>
+ <%= f.submit 'Re-schedule downtime message', class: 'js-submit btn btn-success' %>
+ <%= f.submit 'Cancel downtime', class: 'add-left-margin btn btn-danger' %>
+<% end %>
diff --git a/app/views/legacy_downtimes/index.html.erb b/app/views/legacy_downtimes/index.html.erb
new file mode 100644
index 000000000..56db37586
--- /dev/null
+++ b/app/views/legacy_downtimes/index.html.erb
@@ -0,0 +1,56 @@
+<% content_for :page_title, 'Downtime messages' %>
+
+
+
Downtime messages
+
Show a message on a published transaction start page for a specific time.
+
+
+
+
+ Services
+
+
+
+
+
+
+ <% @transactions.each do |transaction| %>
+
+
+
+ <%= link_to transaction.title, Downtime.for(transaction.artefact).present? ?
+ edit_edition_downtime_path(transaction) :
+ new_edition_downtime_path(transaction) %>
+
+ <%= link_to "/#{transaction.slug}", "#{Plek.website_root}/#{transaction.slug}", class: 'link-muted' %>
+ |
+ <% if downtime = Downtime.for(transaction.artefact) %>
+
+ Scheduled downtime
+ <%= downtime_datetime(downtime) %>
+ |
+
+ <%= link_to 'Edit downtime', edit_edition_downtime_path(transaction), class: 'btn btn-info' %>
+ |
+ <% else %>
+ Live |
+
+ <%= link_to 'Add downtime', new_edition_downtime_path(transaction), class: 'btn btn-default' %>
+ |
+ <% end %>
+
+ <% end %>
+
+
diff --git a/app/views/legacy_downtimes/new.html.erb b/app/views/legacy_downtimes/new.html.erb
new file mode 100644
index 000000000..e7e64545c
--- /dev/null
+++ b/app/views/legacy_downtimes/new.html.erb
@@ -0,0 +1,18 @@
+<% content_for :page_title, 'Schedule downtime' %>
+
+
+ - <%= link_to 'Downtime', downtimes_path %>
+ - <%= @downtime.artefact.name %>
+
+
+
+
+ <%= @downtime.artefact.name %>
+ Schedule a downtime message
+
+
+
+<%= form_for @downtime, url: edition_downtime_path(@edition), html: { class: 'form well remove-top-margin', 'data-module' => 'downtime-message' } do |f| %>
+ <%= render 'form', f: f %>
+ <%= f.submit 'Schedule downtime message', class: 'js-submit btn btn-success' %>
+<% end %>
diff --git a/config/features.rb b/config/features.rb
index e023058ef..b032678ea 100644
--- a/config/features.rb
+++ b/config/features.rb
@@ -12,4 +12,8 @@
feature :design_system_reports_page,
default: false,
description: "A transition of the reports page to use the GOV.UK Design System"
+
+ feature :design_system_downtime_index_page,
+ default: false,
+ description: "A transition of the downtime index page to use the GOV.UK Design System"
end
diff --git a/config/routes.rb b/config/routes.rb
index af094be48..06d693829 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -12,7 +12,10 @@
put "resolve", on: :member
end
- get "downtimes" => "downtimes#index"
+ constraints FeatureConstraint.new("design_system_downtime_index_page") do
+ get "downtimes" => "downtimes#index"
+ end
+ get "downtimes" => "legacy_downtimes#index"
resources :artefacts, only: %i[new create update]
@@ -42,7 +45,7 @@
}
end
- resource :downtime, only: %i[new create edit update destroy]
+ resource :downtime, controller: "legacy_downtimes", only: %i[new create edit update destroy]
end
constraints FeatureConstraint.new("design_system_reports_page") do
diff --git a/test/functional/legacy_downtimes_controller_test.rb b/test/functional/legacy_downtimes_controller_test.rb
new file mode 100644
index 000000000..373cb7d13
--- /dev/null
+++ b/test/functional/legacy_downtimes_controller_test.rb
@@ -0,0 +1,81 @@
+require "test_helper"
+
+class LegacyDowntimesControllerTest < ActionController::TestCase
+ setup do
+ login_as_stub_user
+ end
+
+ context "#index" do
+ should "list all published transaction editions" do
+ unpublished_transaction_edition = FactoryBot.create(:transaction_edition)
+ transaction_editions = FactoryBot.create_list(:transaction_edition, 2, :published)
+
+ get :index
+
+ assert_response :ok
+ assert_select "h3.publication-table-title", count: 0, text: unpublished_transaction_edition.title
+ transaction_editions.each do |edition|
+ assert_select "h3.publication-table-title", text: edition.title
+ end
+ end
+
+ should "redirect to root page if welsh_editor" do
+ login_as_welsh_editor
+
+ get :index
+
+ assert_response :redirect
+ assert_redirected_to controller: "root", action: "index"
+ assert_includes flash[:danger], "do not have permission"
+ end
+ end
+
+ def edition
+ @edition ||= FactoryBot.create(:transaction_edition)
+ end
+
+ def downtime
+ @downtime ||= FactoryBot.create(:downtime, artefact: edition.artefact)
+ end
+
+ def create_downtime
+ downtime
+ end
+
+ def next_year
+ (Time.zone.now + 1.year).year
+ end
+
+ def last_year
+ (Time.zone.now - 1.year).year
+ end
+
+ def downtime_params
+ {
+ 'artefact_id': edition.artefact.id,
+ 'start_time(4i)': 11,
+ 'start_time(5i)': 0,
+ 'start_time(3i)': 14,
+ 'start_time(2i)': 3,
+ 'start_time(1i)': next_year,
+ 'end_time(4i)': 15,
+ 'end_time(5i)': 0,
+ 'end_time(3i)': 14,
+ 'end_time(2i)': 3,
+ 'end_time(1i)': next_year,
+ 'message': "foo",
+ }
+ end
+
+ def invalid_params
+ downtime_params.merge('end_time(1i)': last_year)
+ end
+
+ def assert_a_downtime_is_created
+ assert_equal 1, Downtime.count
+ end
+
+ def assert_that_no_downtime_exists
+ assert_equal 0, Downtime.count
+ end
+end
diff --git a/test/integration/downtime_integration_test.rb b/test/integration/downtime_integration_test.rb
index eb2cbf2e8..4c624046c 100644
--- a/test/integration/downtime_integration_test.rb
+++ b/test/integration/downtime_integration_test.rb
@@ -14,6 +14,9 @@ class DowntimeIntegrationTest < JavascriptIntegrationTest
WebMock.reset!
stub_any_publishing_api_put_content
stub_any_publishing_api_publish
+
+ test_strategy = Flipflop::FeatureSet.current.test!
+ test_strategy.switch!(:design_system_downtime_index_page, true)
end
test "Scheduling new downtime" do
diff --git a/test/integration/legacy_downtime_integration_test.rb b/test/integration/legacy_downtime_integration_test.rb
new file mode 100644
index 000000000..05bf29049
--- /dev/null
+++ b/test/integration/legacy_downtime_integration_test.rb
@@ -0,0 +1,118 @@
+require "integration_test_helper"
+
+class LegacyDowntimeIntegrationTest < JavascriptIntegrationTest
+ setup do
+ setup_users
+
+ @edition = FactoryBot.create(
+ :transaction_edition,
+ :published,
+ title: "Apply to become a driving instructor",
+ slug: "apply-to-become-a-driving-instructor",
+ )
+
+ WebMock.reset!
+ stub_any_publishing_api_put_content
+ stub_any_publishing_api_publish
+ end
+
+ test "Scheduling new downtime" do
+ DowntimeScheduler.stubs(:schedule_publish_and_expiry)
+
+ visit root_path
+ click_link "Downtime"
+ click_link "Apply to become a driving instructor"
+
+ enter_start_time first_of_july_next_year_at_midday_bst
+ enter_end_time first_of_july_next_year_at_six_pm_bst
+
+ assert_match("midday to 6pm on #{day} 1 July", page.find_field("Message").value)
+ click_button "Schedule downtime message"
+
+ assert page.has_content?("downtime message scheduled")
+ assert page.has_content?("Scheduled downtime")
+ assert page.has_content?("midday to 6pm on 1 July")
+ end
+
+ test "Rescheduling downtime" do
+ DowntimeScheduler.stubs(:schedule_publish_and_expiry)
+ create_downtime
+
+ visit root_path
+ click_link "Downtime"
+ click_link "Edit downtime"
+ enter_end_time first_of_july_next_year_at_nine_thirty_pm_bst
+
+ assert_match("This service will be unavailable from midday to 9:30pm on #{day} 1 July.", page.find_field("Message").value)
+ click_on "Re-schedule downtime message"
+
+ assert page.has_content?("downtime message re-scheduled")
+ assert page.has_content?("midday to 9:30pm on 1 July")
+ end
+
+ test "Cancelling downtime" do
+ PublishingApiWorkflowBypassPublisher.stubs(:call)
+ create_downtime
+
+ visit root_path
+ click_link "Downtime"
+ click_link "Edit downtime"
+ click_on "Cancel downtime"
+
+ assert page.has_content?("downtime message cancelled")
+ assert_no_downtime_scheduled
+ end
+
+ def enter_start_time(start_time)
+ complete_date_inputs("downtime_start_time", start_time)
+ end
+
+ def enter_end_time(end_time)
+ complete_date_inputs("downtime_end_time", end_time)
+ end
+
+ def complete_date_inputs(input_id, time)
+ select time.year.to_s, from: "#{input_id}_1i"
+ select time.strftime("%B"), from: "#{input_id}_2i"
+ select time.day.to_s, from: "#{input_id}_3i"
+ select time.hour.to_s, from: "#{input_id}_4i"
+ select time.strftime("%M"), from: "#{input_id}_5i"
+ end
+
+ def next_year
+ Time.zone.now.next_year.year
+ end
+
+ def date_in_the_past
+ Time.zone.local(Time.zone.now.last_year.year, 1, 1, 12, 0)
+ end
+
+ def first_of_july_next_year_at_midday_bst
+ Time.zone.local(next_year, 7, 1, 12, 0)
+ end
+
+ def first_of_july_next_year_at_six_pm_bst
+ Time.zone.local(next_year, 7, 1, 18, 0)
+ end
+
+ def first_of_july_next_year_at_nine_thirty_pm_bst
+ Time.zone.local(next_year, 7, 1, 21, 30)
+ end
+
+ def day
+ first_of_july_next_year_at_six_pm_bst.strftime("%A")
+ end
+
+ def create_downtime
+ Downtime.create!(
+ artefact: @edition.artefact,
+ start_time: first_of_july_next_year_at_midday_bst,
+ end_time: first_of_july_next_year_at_six_pm_bst,
+ message: "foo",
+ )
+ end
+
+ def assert_no_downtime_scheduled
+ assert_equal 0, Downtime.count
+ end
+end
diff --git a/test/integration/routes_test.rb b/test/integration/routes_test.rb
index 984213dfe..c814aadc9 100644
--- a/test/integration/routes_test.rb
+++ b/test/integration/routes_test.rb
@@ -14,4 +14,18 @@ class RoutesTest < ActionDispatch::IntegrationTest
assert_routing("/reports", controller: "legacy_reports", action: "index")
end
+
+ should "route to new downtimes controller index action when 'design_system_downtime_index_page' toggle is enabled" do
+ test_strategy = Flipflop::FeatureSet.current.test!
+ test_strategy.switch!(:design_system_downtime_index_page, true)
+
+ assert_routing("/downtimes", controller: "downtimes", action: "index")
+ end
+
+ should "route to legacy downtimes controller index action when 'design_system_downtime_index_page' toggle is disabled" do
+ test_strategy = Flipflop::FeatureSet.current.test!
+ test_strategy.switch!(:design_system_downtime_index_page, false)
+
+ assert_routing("/downtimes", controller: "legacy_downtimes", action: "index")
+ end
end