diff --git a/app/services/maintenance_window.rb b/app/services/maintenance_window.rb new file mode 100644 index 00000000000..7cd636775a0 --- /dev/null +++ b/app/services/maintenance_window.rb @@ -0,0 +1,19 @@ +class MaintenanceWindow + attr_reader :start, :finish, :now + + def initialize(start:, finish:, now: nil, display_time_zone: 'America/New_York') + @start = parse(start, display_time_zone: display_time_zone) + @finish = parse(finish, display_time_zone: display_time_zone) + @now = now || Time.zone.now + end + + def active? + (start...finish).cover?(now) if start && finish + end + + private + + def parse(time_str, display_time_zone:) + Time.zone.parse(time_str).in_time_zone(display_time_zone) if time_str.present? + end +end diff --git a/app/views/devise/sessions/new.html.erb b/app/views/devise/sessions/new.html.erb index 9fcd6053aeb..8ecdc2c48fc 100644 --- a/app/views/devise/sessions/new.html.erb +++ b/app/views/devise/sessions/new.html.erb @@ -1,6 +1,8 @@ <% title t('titles.visitors.index') %> <% request_id = params[:request_id] || sp_session[:request_id] %> +<%= render 'shared/maintenance_window_alert' %> + <% if decorated_session.sp_name %>
<%= image_tag(asset_url('user-access.svg'), width: '280', alt: '') %> diff --git a/app/views/idv/cac/choose_method.html.erb b/app/views/idv/cac/choose_method.html.erb index c5111b51521..79b296e4956 100644 --- a/app/views/idv/cac/choose_method.html.erb +++ b/app/views/idv/cac/choose_method.html.erb @@ -1,23 +1,24 @@ -

<%= t('cac_proofing.headings.choose_method') %>

-<%= t('cac_proofing.info.choose_method') %> -
-
-<%= image_tag asset_url('dod-logo.jpg'), height: 150 %> -
-<%= simple_form_for :cac, - url: url_for, - method: 'put', - html: { autocomplete: 'off', role: 'form', class: 'mt2' } do |f| %> - +<%= render 'shared/maintenance_window_alert' do %> +

<%= t('cac_proofing.headings.choose_method') %>

+ <%= t('cac_proofing.info.choose_method') %> +
+
+ <%= image_tag asset_url('dod-logo.jpg'), height: 150 %> +
+ <%= simple_form_for :cac, + url: url_for, + method: 'put', + html: { autocomplete: 'off', role: 'form', class: 'mt2' } do |f| %> + + <% end %> +
+
+
+ <%= image_tag asset_url('state-id-confirm@3x.png'), height: 150 %> +
+
+ <%= link_to t('cac_proofing.buttons.use_doc_auth'), idv_doc_auth_path, class: 'btn btn-primary' %> +
+
+
<%= link_to t('links.cancel'), idv_cancel_path %>
<% end %> -
-
-
-<%= image_tag asset_url('state-id-confirm@3x.png'), height: 150 %> -
-
-<%= link_to t('cac_proofing.buttons.use_doc_auth'), idv_doc_auth_path, class: 'btn btn-primary' %> -
-
-
<%= link_to t('links.cancel'), idv_cancel_path %>
- diff --git a/app/views/idv/doc_auth/welcome.html.erb b/app/views/idv/doc_auth/welcome.html.erb index a0efe8fe3e5..351b1438fad 100644 --- a/app/views/idv/doc_auth/welcome.html.erb +++ b/app/views/idv/doc_auth/welcome.html.erb @@ -3,113 +3,115 @@
<%= flow_session[:error_message] %>
<% end %> -

<%= t('doc_auth.headings.welcome') %>

-

<%= t('doc_auth.info.welcome') %>

-

<%= t('doc_auth.instructions.welcome') %>

+<%= render 'shared/maintenance_window_alert' do %> +

<%= t('doc_auth.headings.welcome') %>

+

<%= t('doc_auth.info.welcome') %>

+

<%= t('doc_auth.instructions.welcome') %>

- -<%= simple_form_for :doc_auth, - url: url_for, - method: 'put', - html: { autocomplete: 'off', role: 'form', class: 'mt2 js-consent-form' } do |f| %> -
- - <%= f.button :submit, t('doc_auth.buttons.continue'), class: 'btn btn-primary btn-wide sm-col-6 col-6 no-auto-enable' %> -<% end %> + <%= simple_form_for :doc_auth, + url: url_for, + method: 'put', + html: { autocomplete: 'off', role: 'form', class: 'mt2 js-consent-form' } do |f| %> +
+ + <%= f.button :submit, t('doc_auth.buttons.continue'), class: 'btn btn-primary btn-wide sm-col-6 col-6 no-auto-enable' %> + <% end %> -
+
-<% if user_fully_authenticated? %> - <%= render 'shared/cancel', link: idv_cancel_path %> -<% else %> -
- <%= link_to(t('two_factor_authentication.choose_another_option'), two_factor_options_path) %> -
-<% end %> + <% if user_fully_authenticated? %> + <%= render 'shared/cancel', link: idv_cancel_path %> + <% else %> +
+ <%= link_to(t('two_factor_authentication.choose_another_option'), two_factor_options_path) %> +
+ <% end %> -<%= javascript_pack_tag('clipboard') %> -<%= javascript_pack_tag('ial2-consent-button') %> -<%= javascript_pack_tag('document-capture-welcome') if FeatureManagement.document_capture_step_enabled? %> + <%= javascript_pack_tag('clipboard') %> + <%= javascript_pack_tag('ial2-consent-button') %> + <%= javascript_pack_tag('document-capture-welcome') if FeatureManagement.document_capture_step_enabled? %> +<% end %> diff --git a/app/views/shared/_alert.html.erb b/app/views/shared/_alert.html.erb index 2d8347be0d3..c4387e47bec 100644 --- a/app/views/shared/_alert.html.erb +++ b/app/views/shared/_alert.html.erb @@ -1,6 +1,7 @@ <% type = local_assigns.fetch(:type, 'other') role = type === 'error' ? 'alert' : 'status' + text_tag = local_assigns.fetch(:text_tag, 'p') classes = [ 'usa-alert', @@ -12,6 +13,6 @@ <%= tag.div class: classes, role: role do %>
-

<%= message %>

+ <%= content_tag(text_tag, message, class: 'usa-alert__text') %>
<% end %> diff --git a/app/views/shared/_maintenance_window_alert.html.erb b/app/views/shared/_maintenance_window_alert.html.erb new file mode 100644 index 00000000000..2df102a8f9a --- /dev/null +++ b/app/views/shared/_maintenance_window_alert.html.erb @@ -0,0 +1,22 @@ +<% + maintenance_window = MaintenanceWindow.new( + start: Figaro.env.acuant_maintenance_window_start, + finish: Figaro.env.acuant_maintenance_window_finish, + now: local_assigns[:now], + ) +%> +<% if maintenance_window.active? %> + <%= render 'shared/alert', { type: 'warning', class: 'margin-bottom-2', text_tag: 'div' } do %> +

+ <%= t('notices.maintenance.currently_under_maintenance_html', + finish: l(maintenance_window.finish, + format: t('time.formats.event_timestamp_with_zone'))) %> +

+

+ <%= t('notices.maintenance.need_assistance') %> + <%= link_to(t('notices.maintenance.contact_us'), MarketingSite.contact_url) %> +

+ <% end %> +<% else %> + <%= yield %> +<% end %> diff --git a/config/application.yml.default b/config/application.yml.default index f6ea9890736..120d6be62b3 100644 --- a/config/application.yml.default +++ b/config/application.yml.default @@ -25,6 +25,8 @@ aamva_cert_enabled: 'true' aamva_sp_banlist_issuers: '[]' account_reset_token_valid_for_days: '1' account_reset_wait_period_days: '1' +acuant_maintenance_window_start: '2020-09-19T03:00:00Z' # 9/18 11pm Eastern +acuant_maintenance_window_finish: '2020-09-19T05:00:00Z' # 9/19 1am Eastern acuant_assure_id_password: '' acuant_assure_id_subscription_id: '' acuant_assure_id_username: '' @@ -351,6 +353,8 @@ test: aamva_public_key: 123abc aamva_verification_url: https://example.org:12345/verification/url account_reset_auth_token: test + acuant_maintenance_window_start: '' + acuant_maintenance_window_finish: '' acuant_assure_id_url: https://example.com acuant_facial_match_url: https://facial_match.example.com acuant_max_attempts: '4' diff --git a/config/locales/notices/en.yml b/config/locales/notices/en.yml index daad5082220..9dcb7edaf9a 100644 --- a/config/locales/notices/en.yml +++ b/config/locales/notices/en.yml @@ -15,6 +15,11 @@ en: use_diff_email: link: create a new account text_html: Or, %{link} using a different email address. + maintenance: + contact_us: Contact us + currently_under_maintenance_html: We are currently under maintenance until + %{finish} for some of our services. + need_assistance: Need assistance? password_changed: You changed your password. phone_confirmed: Phone confirmed successfully. piv_cac_configured: PIV/CAC card linked successfully. diff --git a/config/locales/notices/es.yml b/config/locales/notices/es.yml index 1278a9b952c..1db17c619dc 100644 --- a/config/locales/notices/es.yml +++ b/config/locales/notices/es.yml @@ -15,6 +15,11 @@ es: use_diff_email: link: crear una cuenta nueva text_html: O, %{link} utilizando un email diferente. + maintenance: + contact_us: Contacta con nosotros + currently_under_maintenance_html: Actualmente estamos en mantenimiento hasta + el %{finish} para algunos de nuestros servicios. + need_assistance: "¿Necesita ayuda?" password_changed: Ha cambiado su contraseña. phone_confirmed: Teléfono confirmado con éxito. piv_cac_configured: Tarjeta PIV/CAC vinculada con éxito. diff --git a/config/locales/notices/fr.yml b/config/locales/notices/fr.yml index 99e36c9a3c9..d4cd3388aa0 100644 --- a/config/locales/notices/fr.yml +++ b/config/locales/notices/fr.yml @@ -16,6 +16,11 @@ fr: use_diff_email: link: Créer un nouveau compte text_html: Ou, %{link} en utilisant une adresse courriel différente. + maintenance: + contact_us: Nous contacter + currently_under_maintenance_html: Nous sommes actuellement en maintenance + jusqu'au %{finish} pour certains de nos services. + need_assistance: Besoin d'assistance? password_changed: Vous avez changé votre mot de passe. phone_confirmed: Téléphone confirmé avec succès. piv_cac_configured: Carte PIV/CAC liée avec succès. diff --git a/config/locales/time/en.yml b/config/locales/time/en.yml index d5efd70acf9..b15740420f0 100644 --- a/config/locales/time/en.yml +++ b/config/locales/time/en.yml @@ -20,4 +20,5 @@ en: formats: event_timestamp: "%B %-d, %Y at %-l:%M %p" event_timestamp_utc: "%B %-d, %Y at %-l:%M %p UTC" + event_timestamp_with_zone: "%B %-d, %Y at %-l:%M %p %Z" pm: PM diff --git a/config/locales/time/es.yml b/config/locales/time/es.yml index 4ed7e229f29..fc1794b5a7f 100644 --- a/config/locales/time/es.yml +++ b/config/locales/time/es.yml @@ -20,4 +20,5 @@ es: formats: event_timestamp: "%e de %B de %Y a las %H:%M" event_timestamp_utc: "%e de %B de %Y a las %H:%M UTC" + event_timestamp_with_zone: "%e de %B de %Y a las %H:%M %Z" pm: PM diff --git a/config/locales/time/fr.yml b/config/locales/time/fr.yml index bcdd20aa47a..8174de4d1b7 100644 --- a/config/locales/time/fr.yml +++ b/config/locales/time/fr.yml @@ -20,4 +20,5 @@ fr: formats: event_timestamp: "%e %B %Y à %H:%M" event_timestamp_utc: "%e %B %Y à %H:%M UTC" + event_timestamp_with_zone: "%e %B %Y à %H:%M %Z" pm: P.M. diff --git a/spec/features/idv/doc_auth/welcome_step_spec.rb b/spec/features/idv/doc_auth/welcome_step_spec.rb index ba78b615556..a8376a56dc2 100644 --- a/spec/features/idv/doc_auth/welcome_step_spec.rb +++ b/spec/features/idv/doc_auth/welcome_step_spec.rb @@ -28,4 +28,29 @@ def expect_doc_auth_first_step it_behaves_like 'ial2 consent without js' end + + context 'during the acuant maintenance window' do + context 'during the acuant maintenance window' do + let(:start) { Time.zone.parse('2020-01-01T00:00:00Z') } + let(:now) { Time.zone.parse('2020-01-01T12:00:00Z') } + let(:finish) { Time.zone.parse('2020-01-01T23:59:59Z') } + + before do + allow(Figaro.env).to receive(:acuant_maintenance_window_start).and_return(start.iso8601) + allow(Figaro.env).to receive(:acuant_maintenance_window_finish).and_return(finish.iso8601) + + sign_in_and_2fa_user + complete_doc_auth_steps_before_welcome_step + end + + around do |ex| + Timecop.travel(now) { ex.run } + end + + it 'renders the warning banner but no other content' do + expect(page).to have_content('We are currently under maintenance') + expect(page).to_not have_content(t('doc_auth.headings.welcome')) + end + end + end end diff --git a/spec/services/maintenance_window_spec.rb b/spec/services/maintenance_window_spec.rb new file mode 100644 index 00000000000..408206c8de6 --- /dev/null +++ b/spec/services/maintenance_window_spec.rb @@ -0,0 +1,60 @@ +require 'rails_helper' + +RSpec.describe MaintenanceWindow do + subject(:maintenance_window) do + MaintenanceWindow.new( + start: start, + finish: finish, + now: now, + display_time_zone: display_time_zone, + ) + end + + let(:start) { '2020-01-01T00:00:00Z' } + let(:finish) { '2020-01-01T23:59:59Z' } + let(:now) { nil } + let(:display_time_zone) { 'America/Los_Angeles' } + + describe '#active?' do + context 'when now is during the maintenance window' do + let(:now) { '2020-01-01T12:00:00Z' } + it { expect(maintenance_window.active?).to eq(true) } + end + + context 'when now is outside the maintenance window' do + let(:now) { '2020-12-31T00:00:00Z' } + it { expect(maintenance_window.active?).to eq(false) } + end + + context 'when both start and finish are empty' do + let(:start) { '' } + let(:finish) { '' } + + it 'is falsey' do + expect(maintenance_window.active?).to be_falsey + end + end + end + + describe '#start' do + it 'is formatted in the display_time_zone' do + expect(maintenance_window.start.time_zone.name).to eq(display_time_zone) + end + + context 'with an empty value' do + let(:start) { '' } + it { expect(maintenance_window.start).to eq(nil) } + end + end + + describe '#finish' do + it 'is formatted in the display_time_zone' do + expect(maintenance_window.finish.time_zone.name).to eq(display_time_zone) + end + + context 'with an empty value' do + let(:finish) { '' } + it { expect(maintenance_window.finish).to eq(nil) } + end + end +end diff --git a/spec/views/devise/sessions/new.html.erb_spec.rb b/spec/views/devise/sessions/new.html.erb_spec.rb index ae7ec34624a..0e44ec5197f 100644 --- a/spec/views/devise/sessions/new.html.erb_spec.rb +++ b/spec/views/devise/sessions/new.html.erb_spec.rb @@ -131,4 +131,26 @@ ) end end + + context 'during the acuant maintenance window' do + let(:start) { Time.zone.parse('2020-01-01T00:00:00Z') } + let(:now) { Time.zone.parse('2020-01-01T12:00:00Z') } + let(:finish) { Time.zone.parse('2020-01-01T23:59:59Z') } + + before do + allow(Figaro.env).to receive(:acuant_maintenance_window_start).and_return(start.iso8601) + allow(Figaro.env).to receive(:acuant_maintenance_window_finish).and_return(finish.iso8601) + end + + around do |ex| + Timecop.travel(now) { ex.run } + end + + it 'renders the warning banner and the normal form' do + render + + expect(rendered).to have_content('We are currently under maintenance') + expect(rendered).to have_selector('input.email') + end + end end diff --git a/spec/views/idv/doc_auth/welcome.html.erb_spec.rb b/spec/views/idv/doc_auth/welcome.html.erb_spec.rb index 9a2e1e1e0ef..d213799627a 100644 --- a/spec/views/idv/doc_auth/welcome.html.erb_spec.rb +++ b/spec/views/idv/doc_auth/welcome.html.erb_spec.rb @@ -52,4 +52,26 @@ expect(rendered).to_not have_text(t('doc_auth.instructions.bullet1a')) end end + + context 'during the acuant maintenance window' do + let(:start) { Time.zone.parse('2020-01-01T00:00:00Z') } + let(:now) { Time.zone.parse('2020-01-01T12:00:00Z') } + let(:finish) { Time.zone.parse('2020-01-01T23:59:59Z') } + + before do + allow(Figaro.env).to receive(:acuant_maintenance_window_start).and_return(start.iso8601) + allow(Figaro.env).to receive(:acuant_maintenance_window_finish).and_return(finish.iso8601) + end + + around do |ex| + Timecop.travel(now) { ex.run } + end + + it 'renders the warning banner but no other content' do + render template: 'idv/doc_auth/welcome.html.erb' + + expect(rendered).to have_content('We are currently under maintenance') + expect(rendered).to_not have_content(t('doc_auth.headings.welcome')) + end + end end diff --git a/spec/views/shared/_alert.html.erb_spec.rb b/spec/views/shared/_alert.html.erb_spec.rb index fe1343a8941..4f1f0ee9651 100644 --- a/spec/views/shared/_alert.html.erb_spec.rb +++ b/spec/views/shared/_alert.html.erb_spec.rb @@ -25,6 +25,19 @@ expect(rendered).to have_selector('.usa-alert.usa-alert--success') end + it 'defaults to

tag for text' do + render 'shared/alert', { type: 'success', message: 'Hooray!' } + + expect(rendered).to have_selector('p.usa-alert__text') + end + + it 'accepts text_tag param' do + render 'shared/alert', { type: 'success', message: 'Hooray!', text_tag: 'div' } + + expect(rendered).to have_selector('div.usa-alert__text') + expect(rendered).to_not have_selector('p.usa-alert__text') + end + it 'accepts custom class names' do render 'shared/alert', { message: 'FYI', class: 'my-custom-class' } diff --git a/spec/views/shared/_maintenance_window_alert.html.erb_spec.rb b/spec/views/shared/_maintenance_window_alert.html.erb_spec.rb new file mode 100644 index 00000000000..9e54e3d2eea --- /dev/null +++ b/spec/views/shared/_maintenance_window_alert.html.erb_spec.rb @@ -0,0 +1,48 @@ +require 'rails_helper' + +RSpec.describe 'shared/_maintenance_window_alert.html.erb' do + let(:start) { Time.zone.parse('2020-01-01T00:00:00Z') } + let(:finish) { Time.zone.parse('2020-01-01T23:59:59Z') } + + before do + allow(Figaro.env).to receive(:acuant_maintenance_window_start).and_return(start.iso8601) + allow(Figaro.env).to receive(:acuant_maintenance_window_finish).and_return(finish.iso8601) + end + + subject(:render_partial) do + render( + 'shared/maintenance_window_alert', + now: now, + ) { 'contents of block' } + end + + context 'during the maintenance window' do + let(:now) { Time.zone.parse('2020-01-01T12:00:00Z') } + + it 'renders a warning and not the contents of the block' do + render_partial + + expect(rendered).to have_content('We are currently under maintenance') + + formatted_finish = l( + finish.in_time_zone('America/New_York'), + format: t('time.formats.event_timestamp_with_zone'), + ) + expect(rendered).to have_content(formatted_finish) + + expect(rendered).to_not have_content('contents of block') + end + end + + context 'outside the maintenance window' do + let(:now) { Time.zone.parse('2020-01-03T00:00:00Z') } + + it 'renders the contents of the block but no warning' do + render_partial + + expect(rendered).to have_content('contents of block') + + expect(rendered).to_not have_content('We are currently under maintenance') + end + end +end