diff --git a/dpc-portal/app/components/page/credential_delegate/list_component.html.erb b/dpc-portal/app/components/page/credential_delegate/list_component.html.erb
index 7fdd4cd821..511da77fad 100644
--- a/dpc-portal/app/components/page/credential_delegate/list_component.html.erb
+++ b/dpc-portal/app/components/page/credential_delegate/list_component.html.erb
@@ -27,5 +27,18 @@
There are no pending credential delegates.
<% end %>
+
+
Expired invitations
+
These invites expired. You can resend the invite to give them more time to accept.
+ <% if @expired_invitations.present? %>
+ <%= render(Core::Table::TableComponent.new(id: 'expired-invitation-table', additional_classes: ['width-full'], sortable: false)) do %>
+ <%= render(Core::Table::HeaderComponent.new(caption: 'Expired Invitation Table',
+ columns: ['Name', 'Email', 'Expired on'])) %>
+ <%= render(Core::Table::RowComponent.with_collection(@expired_invitations, keys: ['full_name', 'email', 'expired_at'], obj_name: 'expired invitation')) %>
+ <% end %>
+ <% else %>
+
You have no expired invitations.
+ <% end %>
+
<% end %>
diff --git a/dpc-portal/app/components/page/credential_delegate/list_component.rb b/dpc-portal/app/components/page/credential_delegate/list_component.rb
index 2e45714085..ec09181d57 100644
--- a/dpc-portal/app/components/page/credential_delegate/list_component.rb
+++ b/dpc-portal/app/components/page/credential_delegate/list_component.rb
@@ -6,11 +6,12 @@ module CredentialDelegate
class ListComponent < ViewComponent::Base
attr_reader :organization, :active_credential_delegates, :pending_credential_delegates
- def initialize(organization, cd_invitations, credential_delegates)
+ def initialize(organization, pending_invitations, expired_invitations, credential_delegates)
super
@organization = organization
@active_credential_delegates = credential_delegates.map(&:show_attributes)
- @pending_credential_delegates = cd_invitations.map(&:show_attributes)
+ @pending_credential_delegates = pending_invitations.map(&:show_attributes)
+ @expired_invitations = expired_invitations.map(&:show_attributes)
end
end
end
diff --git a/dpc-portal/app/components/page/credential_delegate/list_component_preview.rb b/dpc-portal/app/components/page/credential_delegate/list_component_preview.rb
index a0c763ac3b..f848da271c 100644
--- a/dpc-portal/app/components/page/credential_delegate/list_component_preview.rb
+++ b/dpc-portal/app/components/page/credential_delegate/list_component_preview.rb
@@ -11,24 +11,34 @@ module CredentialDelegate
#
class ListComponentPreview < ViewComponent::Preview
def empty
- render(Page::CredentialDelegate::ListComponent.new(org, [], []))
+ render(Page::CredentialDelegate::ListComponent.new(org, [], [], []))
end
def active
user1 = User.new(given_name: 'Bob', family_name: 'Hodges', email: 'bob@example.com')
user2 = User.new(given_name: 'Lisa', family_name: 'Franklin', email: 'lisa@example.com')
cds = [CdOrgLink.new(user: user1, created_at: 1.week.ago), CdOrgLink.new(user: user2, created_at: 2.weeks.ago)]
- render(Page::CredentialDelegate::ListComponent.new(org, [], cds))
+ render(Page::CredentialDelegate::ListComponent.new(org, [], [], cds))
end
def pending
cds = [
Invitation.new(invited_given_name: 'Bob', invited_family_name: 'Hodges', invited_email: 'bob@example.com',
- id: 2),
+ id: 2, created_at: 1.day.ago),
Invitation.new(invited_given_name: 'Lisa', invited_family_name: 'Franklin',
- invited_email: 'lisa@example.com', id: 3)
+ invited_email: 'lisa@example.com', id: 3, created_at: 1.day.ago)
]
- render(Page::CredentialDelegate::ListComponent.new(org, cds, []))
+ render(Page::CredentialDelegate::ListComponent.new(org, cds, [], []))
+ end
+
+ def expired
+ expired_invites = [
+ Invitation.new(invited_given_name: 'Bob', invited_family_name: 'Hodges', invited_email: 'bob@example.com',
+ id: 2, created_at: 3.days.ago),
+ Invitation.new(invited_given_name: 'Lisa', invited_family_name: 'Franklin',
+ invited_email: 'lisa@example.com', id: 3, created_at: 3.days.ago)
+ ]
+ render(Page::CredentialDelegate::ListComponent.new(org, [], expired_invites, []))
end
private
diff --git a/dpc-portal/app/components/page/organization/compound_show_component.html.erb b/dpc-portal/app/components/page/organization/compound_show_component.html.erb
index 733a80cc2d..f8622261d0 100644
--- a/dpc-portal/app/components/page/organization/compound_show_component.html.erb
+++ b/dpc-portal/app/components/page/organization/compound_show_component.html.erb
@@ -2,6 +2,6 @@
<%= @organization.name %>
NPI: <%= @organization.npi %>
<%= render(Core::Navigation::TabbedComponent.new('organization_nav', @links)) if @show_cds %>
- <%= render(Page::CredentialDelegate::ListComponent.new(@organization, @active_credential_delegates, @pending_credential_delegates)) if @show_cds %>
+ <%= render(Page::CredentialDelegate::ListComponent.new(@organization, @pending_credential_delegates, @expired_cd_invitations, @active_credential_delegates,)) if @show_cds %>
<%= render(Page::Organization::CredentialsComponent.new(@organization)) %>
diff --git a/dpc-portal/app/components/page/organization/compound_show_component.rb b/dpc-portal/app/components/page/organization/compound_show_component.rb
index 5b1b2ae5c0..9e44bb1a4d 100644
--- a/dpc-portal/app/components/page/organization/compound_show_component.rb
+++ b/dpc-portal/app/components/page/organization/compound_show_component.rb
@@ -4,13 +4,14 @@ module Page
module Organization
# Shows tabbed credential delegates and credentials
class CompoundShowComponent < ViewComponent::Base
- def initialize(organization, cd_invitations, credential_delegates, show_cds)
+ def initialize(organization, cd_invitations, expired_cd_invitations, credential_delegates, show_cds)
super
@links = [['User Access', '#credential_delegates', true],
['Credentials', '#credentials', false]]
@organization = organization
@active_credential_delegates = credential_delegates
@pending_credential_delegates = cd_invitations
+ @expired_cd_invitations = expired_cd_invitations
@show_cds = show_cds
end
end
diff --git a/dpc-portal/app/components/page/organization/compound_show_component_preview.rb b/dpc-portal/app/components/page/organization/compound_show_component_preview.rb
index 9949960a8e..81b37a7dc1 100644
--- a/dpc-portal/app/components/page/organization/compound_show_component_preview.rb
+++ b/dpc-portal/app/components/page/organization/compound_show_component_preview.rb
@@ -6,12 +6,12 @@ module Organization
class CompoundShowComponentPreview < ViewComponent::Preview
def authorized_official
org = ProviderOrganization.new(name: 'Health Hut', npi: '1111111111', id: 2)
- render(Page::Organization::CompoundShowComponent.new(org, [], [], true))
+ render(Page::Organization::CompoundShowComponent.new(org, [], [], [], true))
end
def credential_delegate
org = ProviderOrganization.new(name: 'Health Hut', npi: '1111111111', id: 2)
- render(Page::Organization::CompoundShowComponent.new(org, [], [], false))
+ render(Page::Organization::CompoundShowComponent.new(org, [], [], [], false))
end
end
end
diff --git a/dpc-portal/app/controllers/organizations_controller.rb b/dpc-portal/app/controllers/organizations_controller.rb
index 232bac9db0..c42a0cb265 100644
--- a/dpc-portal/app/controllers/organizations_controller.rb
+++ b/dpc-portal/app/controllers/organizations_controller.rb
@@ -19,12 +19,17 @@ def index
def show
show_cds = current_user.ao?(@organization)
if show_cds
- @invitations = Invitation.where(provider_organization: @organization,
- invited_by: current_user,
- status: :pending)
+ # Invitation expiration is determined in relation to the `created_at` field; the `status` field will
+ # never be `'expired'`. Therefore, we need to further filter out expired invitations from this query.
+ @pending_invitations = Invitation.where(provider_organization: @organization,
+ invited_by: current_user,
+ status: :pending).reject(&:expired?)
+ @expired_invitations = Invitation.where(provider_organization: @organization,
+ invited_by: current_user).select(&:expired?)
@cds = CdOrgLink.where(provider_organization: @organization, disabled_at: nil)
end
- render(Page::Organization::CompoundShowComponent.new(@organization, @cds, @invitations, show_cds))
+ render(Page::Organization::CompoundShowComponent.new(@organization, @pending_invitations, @expired_invitations,
+ @cds, show_cds))
end
def new
diff --git a/dpc-portal/app/models/invitation.rb b/dpc-portal/app/models/invitation.rb
index 8d7c4b5709..6bf0dfae5f 100644
--- a/dpc-portal/app/models/invitation.rb
+++ b/dpc-portal/app/models/invitation.rb
@@ -22,6 +22,7 @@ class Invitation < ApplicationRecord
def show_attributes
{ full_name: "#{invited_given_name} #{invited_family_name}",
email: invited_email,
+ expired_at: expired_at.to_s,
id: }.with_indifferent_access
end
@@ -30,7 +31,17 @@ def invited_by_full_name
end
def expired?
- created_at < 2.days.ago
+ Time.now > expiration_date
+ end
+
+ def expired_at
+ return unless expired?
+
+ expiration_date
+ end
+
+ def expiration_date
+ created_at + 2.days
end
def accept!
diff --git a/dpc-portal/spec/components/page/credential_delegate/list_component_spec.rb b/dpc-portal/spec/components/page/credential_delegate/list_component_spec.rb
index f2fc069eaa..527191dca4 100644
--- a/dpc-portal/spec/components/page/credential_delegate/list_component_spec.rb
+++ b/dpc-portal/spec/components/page/credential_delegate/list_component_spec.rb
@@ -12,14 +12,15 @@
let(:org) { ComponentSupport::MockOrg.new }
- let(:component) { described_class.new(org, invitations, credential_delegates) }
+ let(:component) { described_class.new(org, pending_invitations, expired_invitations, credential_delegates) }
before do
render_inline(component)
end
context 'No credential delegates' do
- let(:invitations) { [] }
+ let(:pending_invitations) { [] }
+ let(:expired_invitations) { [] }
let(:credential_delegates) { [] }
let(:expected_html) do
@@ -45,6 +46,11 @@
Pending invitations
There are no pending credential delegates.
+
+
Expired invitations
+
These invites expired. You can resend the invite to give them more time to accept.
+
You have no expired invitations.
+
@@ -59,7 +65,8 @@
let(:invitation) do
Invitation.new(invited_given_name: 'Bob', invited_family_name: 'Hodges', invited_email: 'bob@example.com')
end
- let(:invitations) { [] }
+ let(:pending_invitations) { [] }
+ let(:expired_invitations) { [] }
let(:credential_delegates) { [CdOrgLink.new(user:, invitation:)] }
it 'has a table' do
@@ -102,10 +109,11 @@
end
context 'Pending credential delegate' do
- let(:invitations) do
+ let(:pending_invitations) do
[Invitation.new(invited_given_name: 'Bob', invited_family_name: 'Hodges', invited_email: 'bob@example.com',
- id: 3)]
+ id: 3, created_at: 1.day.ago)]
end
+ let(:expired_invitations) { [] }
let(:credential_delegates) { [] }
it 'has a table' do
@@ -148,6 +156,61 @@
expected_html = 'There are no active credential delegates.
'
is_expected.to include(normalize_space(expected_html))
end
+
+ it 'has no expired invitations' do
+ expected_html = 'You have no expired invitations.
'
+ is_expected.to include(normalize_space(expected_html))
+ end
+ end
+
+ context 'Expired invitations' do
+ let(:pending_invitations) { [] }
+ let(:expired_invitations) do
+ [Invitation.new(invited_given_name: 'Bob', invited_family_name: 'Hodges', invited_email: 'bob@example.com',
+ id: 3, created_at: 3.days.ago)]
+ end
+ let(:credential_delegates) { [] }
+
+ it 'has a table' do
+ expired_at = expired_invitations.first.expired_at
+ expected_html = <<~HTML
+
+ Expired Invitation Table
+
+
+ Name |
+ Email |
+ Expired on |
+
+
+
+
+ Bob Hodges |
+ bob@example.com |
+ #{expired_at} |
+
+
+
+ HTML
+ is_expected.to include(normalize_space(expected_html))
+ end
+
+ it 'has a row' do
+ expired_at = expired_invitations.first.expired_at
+ expected_html = <<~HTML
+
+ Bob Hodges |
+ bob@example.com |
+ #{expired_at} |
+
+ HTML
+ is_expected.to include(normalize_space(expected_html))
+ end
+
+ it 'has no pending credential delegates' do
+ expected_html = 'There are no pending credential delegates.
'
+ is_expected.to include(normalize_space(expected_html))
+ end
end
end
end
diff --git a/dpc-portal/spec/components/page/organization/compound_show_component_spec.rb b/dpc-portal/spec/components/page/organization/compound_show_component_spec.rb
index 7c4b0ff32d..a52f20ca67 100644
--- a/dpc-portal/spec/components/page/organization/compound_show_component_spec.rb
+++ b/dpc-portal/spec/components/page/organization/compound_show_component_spec.rb
@@ -10,7 +10,7 @@
normalize_space(rendered_content)
end
let(:org) { build(:provider_organization, name: 'Health Hut', npi: '11111111', id: 2) }
- let(:component) { described_class.new(org, [], [], show_cds) }
+ let(:component) { described_class.new(org, [], [], [], show_cds) }
before do
render_inline(component)
diff --git a/dpc-portal/spec/requests/organizations_spec.rb b/dpc-portal/spec/requests/organizations_spec.rb
index 65c7cd22c9..e487341ffd 100644
--- a/dpc-portal/spec/requests/organizations_spec.rb
+++ b/dpc-portal/spec/requests/organizations_spec.rb
@@ -220,7 +220,7 @@
it 'does not assign invitations even if exist' do
create(:invitation, :cd, provider_organization: org, invited_by: user)
get "/organizations/#{org.id}"
- expect(assigns(:invitations)).to be_nil
+ expect(assigns(:pending_invitations)).to be_nil
end
end
end
@@ -260,24 +260,50 @@
expect(response.body).to include('Credential delegates
')
expect(response.body).to include('Pending invitations
')
expect(response.body).to include('Active
')
+ expect(response.body).to include('Expired invitations
')
end
- context :invitations do
+ context :pending_invitations do
it 'assigns if exist' do
create(:invitation, :cd, provider_organization: org, invited_by: user)
get "/organizations/#{org.id}"
- expect(assigns(:invitations).size).to eq 1
+ expect(assigns(:pending_invitations).size).to eq 1
end
it 'does not assign if not exist' do
get "/organizations/#{org.id}"
- expect(assigns(:invitations).size).to eq 0
+ expect(assigns(:pending_invitations).size).to eq 0
end
it 'does not assign if only accepted exists' do
create(:invitation, :cd, provider_organization: org, invited_by: user, status: :accepted)
get "/organizations/#{org.id}"
- expect(assigns(:invitations).size).to eq 0
+ expect(assigns(:pending_invitations).size).to eq 0
+ end
+
+ it 'does not assign if expired' do
+ create(:invitation, :cd, provider_organization: org, invited_by: user, created_at: 3.days.ago)
+ get "/organizations/#{org.id}"
+ expect(assigns(:pending_invitations).size).to eq 0
+ end
+ end
+
+ context :expired_invitations do
+ it 'assigns if exist' do
+ create(:invitation, :cd, provider_organization: org, invited_by: user, created_at: 3.days.ago)
+ get "/organizations/#{org.id}"
+ expect(assigns(:expired_invitations).size).to eq 1
+ end
+
+ it 'does not assign if not exist' do
+ get "/organizations/#{org.id}"
+ expect(assigns(:pending_invitations).size).to eq 0
+ end
+
+ it 'does not assign if invitation is not expired' do
+ create(:invitation, :cd, provider_organization: org, invited_by: user)
+ get "/organizations/#{org.id}"
+ expect(assigns(:expired_invitations).size).to eq 0
end
end