Skip to content

LG-15064: Countdown alerts for expiring phone OTP announces too frequently#11899

Merged
jmdembe merged 35 commits intomainfrom
jd/LG-15064-countdown-timer
Mar 4, 2025
Merged

LG-15064: Countdown alerts for expiring phone OTP announces too frequently#11899
jmdembe merged 35 commits intomainfrom
jd/LG-15064-countdown-timer

Conversation

@jmdembe
Copy link
Contributor

@jmdembe jmdembe commented Feb 19, 2025

🎫 Ticket

Link to the relevant ticket:
LG-15064

🛠 Summary of changes

This ticket addresses a bug where the alert component announces the timer countdown every second. The alert will now announce once every 30 seconds.

📜 Testing Plan

  • Optional: Set otp_valid_for value for anything between 3-5 minutes to make testing more managable.
  • Turn on VoiceOver to see the fix

Account creation

  • Follow steps to create an account
  • When prompted to select an MFA method, select "Text or voice message"
  • Enter a phone number
  • Land on the OTP verification screen. Wait 8 minutes for the announcement to begin
  • See the banner once OTP code expires

👀 Screenshots

finished.screen.recording.mov

@jmdembe jmdembe force-pushed the jd/LG-15064-countdown-timer branch from 23a9f9a to e704197 Compare February 19, 2025 16:11
:tag_options

def initialize(
screen_reader_frequency: nil,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than adding a new option here, did you consider using the approach that we use in the session timeout modal? Namely, having two separate countdowns, one of which is used for assistive technology announces at a slower interval:

<p>
<%= t(
# i18n-tasks-use t('notices.timeout_warning.signed_in.message_html')
# i18n-tasks-use t('notices.timeout_warning.partially_signed_in.message_html')
'message_html',
scope: modal_presenter.translation_scope,
time_left_in_session_html: render(
CountdownComponent.new(
expiration: Time.zone.now,
start_immediately: false,
),
),
) %>
</p>
<p class="usa-sr-only" id="<%= c.description_id %>" role="timer" aria-live="polite" aria-atomic="true">
<%= t(
# i18n-tasks-use t('notices.timeout_warning.signed_in.live_region_message_html')
# i18n-tasks-use t('notices.timeout_warning.partially_signed_in.live_region_message_html')
'live_region_message_html',
scope: modal_presenter.translation_scope,
time_left_in_session_html: render(
CountdownComponent.new(
expiration: Time.zone.now,
update_interval: 30.seconds,
start_immediately: false,
),
),
) %>
</p>

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought that I was going in this direction, but I am pivoting because I originally thought it was to use the one element.

<%= render CountdownComponent.new(
expiration: Time.zone.now,
update_interval: 30.seconds,
start_immediately: true,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing we probably want to be conscious of here is that I don't know that we want the timer to start announcing "9 minutes and 30 seconds remaining" to a screen reader user, and for every 30 seconds thereafter. The alert component above is configured to only start counting down once it gets closer to the timeout (2m30s IIRC).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah the tricky thing that I think is happening is that we are dealing with two timers that need to tick at 2 different paces. Then when the banner shows up, it is counting down by the update_interval of one second while the countdown changes every 5 seconds. There has to be a way in which we silence the alert so that we only hear from the countdown, but I'm thinking of how to do that right now.

This feature works differently from the session timer because the popup shows once and the times are synced together.


<p class="usa-sr-only" role="timer" aria-live="polite" aria-atomic="true">
<%= render CountdownComponent.new(
expiration: Time.zone.now,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if this is temporary, but the expiration will need to be in the future matching @presenter.otp_expiration above.

@jmdembe jmdembe marked this pull request as ready for review February 27, 2025 21:06
@jmdembe
Copy link
Contributor Author

jmdembe commented Feb 27, 2025

During testing, I found it interesting that the screen reader did not announce at the same time it appeared. It may be worth changing the expiration time on the countdown to match the banner. I don't think it is a good practice to have something visible for 30 seconds and after that 30 seconds is over, then the time is announced.

@jmdembe jmdembe marked this pull request as draft February 28, 2025 15:14
@jmdembe
Copy link
Contributor Author

jmdembe commented Feb 28, 2025

What I attempted to do in 51a607e was to get the usa-sr-only banner to appear a configured amount of seconds before the visible banner so that the usa-sr-banner would announce in sync to the visible banner.

@jmdembe jmdembe marked this pull request as ready for review February 28, 2025 17:56
document.body.innerHTML = `
<lg-countdown-alert ${showAtRemaining ? `show-at-remaining="${showAtRemaining}"` : ''}>
<lg-countdown-alert ${showAtRemaining ? `show-at-remaining="${showAtRemaining}"` : ''}
}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a stray close curly brace here? It's not failing or anything but just wondering about the change.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah it looks like it was a stray. It was hard to discern because it was made to be text.

max_mail_events: 2
otp_delivery_blocklist_findtime: 1
otp_delivery_blocklist_maxretry: 2

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason for this break?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, I had that change staged, but not committed. Fixed that in d261989


class CountdownAlertComponent < BaseComponent
attr_reader :show_at_remaining, :alert_options, :countdown_options, :tag_options
attr_reader :show_at_remaining, :alert_options, :countdown_options,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason for this change?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That change was made by the linter. I don't think it needs to be linted in that way, so I will restore the change.

include TwoFactorAuthenticatable
include MfaSetupConcern
include NewDeviceConcern
include SessionTimeoutWarningHelper
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we using anything from that module?

function createElement({ showAtRemaining }: { showAtRemaining?: number } = {}) {
document.body.innerHTML = `
<lg-countdown-alert ${showAtRemaining ? `show-at-remaining="${showAtRemaining}"` : ''}>
<lg-countdown-alert ${showAtRemaining ? `show-at-remaining="${showAtRemaining}"` : ''} >
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor, is the space before the '>' conventional?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My mistake. Addressed this and your above comment in 64f1126

Copy link
Contributor

@vrajmohan vrajmohan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works as specified, looks good.

Co-authored-by: Andrew Duthie <1779930+aduth@users.noreply.github.com>
@@ -1,4 +1,4 @@
<%= tag.div(**tag_options, class: css_class, role:) do %>
<%= tag.div(role:, **tag_options, class: css_class) do %>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be good to include some test coverage for this change, specifically that it's possible to override the default role with another value.

Copy link
Contributor

@kevinsmaster5 kevinsmaster5 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! 👍

@jmdembe jmdembe merged commit 2951b4d into main Mar 4, 2025
2 checks passed
@jmdembe jmdembe deleted the jd/LG-15064-countdown-timer branch March 4, 2025 14:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants