Browse Source

CLDC-2324 assign data protection officer (#1710)

* Add mailer

* Instantiate logs only where needed

* Update banner to handle all possible permutations

* wip

* Send confirmation email to user

* Address PO review
pull/1711/head
Jack 2 years ago committed by GitHub
parent
commit
9406b491ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 12
      app/components/data_protection_confirmation_banner_component.html.erb
  2. 52
      app/components/data_protection_confirmation_banner_component.rb
  3. 15
      app/mailers/data_protection_confirmation_mailer.rb
  4. 13
      app/models/user.rb
  5. 128
      spec/components/data_protection_confirmation_banner_component_spec.rb
  6. 27
      spec/mailers/data_protection_confirmation_mailer_spec.rb
  7. 122
      spec/models/user_spec.rb

12
app/components/data_protection_confirmation_banner_component.html.erb

@ -1,16 +1,8 @@
<% if display_banner? %> <% if display_banner? %>
<%= govuk_notification_banner(title_text: "Important") do %> <%= govuk_notification_banner(title_text: "Important") do %>
<p class="govuk-notification-banner__heading govuk-!-width-full" style="max-width: fit-content"> <p class="govuk-notification-banner__heading govuk-!-width-full" style="max-width: fit-content">
<%= I18n.t("validations.organisation.data_sharing_agreement_not_signed") %> <%= header_text %>
<p> <p>
<% if user.is_dpo? %> <%= banner_text %>
<%= govuk_link_to(
"Read the Data Sharing Agreement",
data_sharing_agreement_href,
class: "govuk-notification-banner__link govuk-!-font-weight-bold",
) %>
<% else %>
<%= data_protection_officers_text %>
<% end %>
<% end %> <% end %>
<% end %> <% end %>

52
app/components/data_protection_confirmation_banner_component.rb

@ -3,6 +3,8 @@ class DataProtectionConfirmationBannerComponent < ViewComponent::Base
attr_reader :user, :organisation attr_reader :user, :organisation
HELPDESK_URL = "https://digital.dclg.gov.uk/jira/servicedesk/customer/portal/4/group/21".freeze
def initialize(user:, organisation: nil) def initialize(user:, organisation: nil)
@user = user @user = user
@organisation = organisation @organisation = organisation
@ -13,26 +15,62 @@ class DataProtectionConfirmationBannerComponent < ViewComponent::Base
def display_banner? def display_banner?
return false unless FeatureToggle.new_data_protection_confirmation? return false unless FeatureToggle.new_data_protection_confirmation?
return false if user.support? && organisation.blank? return false if user.support? && organisation.blank?
return true if org_without_dpo?
!org_or_user_org.data_protection_confirmed?
end
def header_text
if org_without_dpo?
"To create logs your organisation must state a data protection officer. They must sign the Data Sharing Agreement."
elsif user.is_dpo?
"Your organisation must accept the Data Sharing Agreement before you can create any logs."
else
"Your data protection officer must accept the Data Sharing Agreement on CORE before you can create any logs."
end
end
!DataProtectionConfirmation.exists?( def banner_text
organisation: org_or_user_org, if org_without_dpo? || user.is_dpo?
confirmed: true, govuk_link_to(
) link_text,
link_href,
class: "govuk-notification-banner__link govuk-!-font-weight-bold",
)
else
tag.p data_protection_officers_text
end
end end
private
def data_protection_officers_text def data_protection_officers_text
if org_or_user_org.data_protection_officers.any? if org_or_user_org.data_protection_officers.any?
"You can ask: #{org_or_user_org.data_protection_officers.map(&:name).sort_by(&:downcase).join(', ')}" "You can ask: #{org_or_user_org.data_protection_officers.map(&:name).sort_by(&:downcase).join(', ')}"
end end
end end
def data_sharing_agreement_href def link_text
data_sharing_agreement_organisation_path(org_or_user_org) if dpo_required?
"Contact helpdesk to assign a data protection officer"
else
"Read the Data Sharing Agreement"
end
end end
private def link_href
dpo_required? ? HELPDESK_URL : data_sharing_agreement_organisation_path(org_or_user_org)
end
def dpo_required?
org_or_user_org.data_protection_officers.empty?
end
def org_or_user_org def org_or_user_org
organisation.presence || user.organisation organisation.presence || user.organisation
end end
def org_without_dpo?
org_or_user_org.data_protection_officers.empty?
end
end end

15
app/mailers/data_protection_confirmation_mailer.rb

@ -0,0 +1,15 @@
class DataProtectionConfirmationMailer < NotifyMailer
include Rails.application.routes.url_helpers
EMAIL_TEMPLATE_ID = "3dbf78fe-a2c8-4d65-aa19-e4d62695d4a9".freeze
def send_confirmation_email(user)
send_email(
user.email,
EMAIL_TEMPLATE_ID,
{
organisation_name: user.organisation.name,
link: data_sharing_agreement_organisation_url(user.organisation),
},
)
end
end

13
app/models/user.rb

@ -20,6 +20,9 @@ class User < ApplicationRecord
validates :password, presence: { if: :password_required? } validates :password, presence: { if: :password_required? }
validates :password, confirmation: { if: :password_required? } validates :password, confirmation: { if: :password_required? }
validates :password, length: { within: Devise.password_length, allow_blank: true } validates :password, length: { within: Devise.password_length, allow_blank: true }
after_validation :send_data_protection_confirmation_reminder, if: :is_dpo_changed?
validates :organisation_id, presence: true validates :organisation_id, presence: true
has_paper_trail ignore: %w[last_sign_in_at has_paper_trail ignore: %w[last_sign_in_at
@ -179,4 +182,14 @@ protected
def password_required? def password_required?
!persisted? || !password.nil? || !password_confirmation.nil? !persisted? || !password.nil? || !password_confirmation.nil?
end end
private
def send_data_protection_confirmation_reminder
return unless FeatureToggle.new_data_protection_confirmation?
return unless persisted?
return unless is_dpo?
DataProtectionConfirmationMailer.send_confirmation_email(self).deliver_later
end
end end

128
spec/components/data_protection_confirmation_banner_component_spec.rb

@ -1,6 +1,8 @@
require "rails_helper" require "rails_helper"
RSpec.describe DataProtectionConfirmationBannerComponent, type: :component do RSpec.describe DataProtectionConfirmationBannerComponent, type: :component do
include GovukComponent
let(:component) { described_class.new(user:, organisation:) } let(:component) { described_class.new(user:, organisation:) }
let(:render) { render_inline(component) } let(:render) { render_inline(component) }
let(:user) { create(:user) } let(:user) { create(:user) }
@ -13,114 +15,74 @@ RSpec.describe DataProtectionConfirmationBannerComponent, type: :component do
it "does not display banner" do it "does not display banner" do
expect(component.display_banner?).to eq(false) expect(component.display_banner?).to eq(false)
expect(render.content).to be_empty
end end
end end
context "when flag enabled" do context "when flag enabled", :aggregate_failures do
before do before do
allow(FeatureToggle).to receive(:new_data_protection_confirmation?).and_return(true) allow(FeatureToggle).to receive(:new_data_protection_confirmation?).and_return(true)
end end
describe "#data_protection_officers_text" do context "when user is support and organisation is blank" do
it "returns the correct text" do let(:user) { create(:user, :support) }
expect(component.data_protection_officers_text).to eq("You can ask: Danny Rojas") let(:organisation) { nil }
end
context "with two DPOs" do
before do
create(:user, organisation:, is_dpo: true, name: "Test McTest")
end
it "returns the correct list of names, in alphabetical order)" do it "does not display banner" do
expect(component.data_protection_officers_text).to eq("You can ask: Danny Rojas, Test McTest") expect(component.display_banner?).to eq(false)
end expect(render.content).to be_empty
end end
end end
context "when user is not support and not dpo" do context "when org does not have a DPO" do
let(:user) { create(:user) } before do
organisation.users.where(is_dpo: true).destroy_all
context "when org blank" do end
let(:organisation) { nil }
before do it "displays the banner" do
allow(DataProtectionConfirmation).to receive(:exists?).and_call_original expect(component.display_banner?).to eq(true)
end expect(render).to have_link(
"Contact helpdesk to assign a data protection officer",
href: "https://digital.dclg.gov.uk/jira/servicedesk/customer/portal/4/group/21",
)
expect(render).to have_selector("p", text: "To create logs your organisation must state a data protection officer. They must sign the Data Sharing Agreement.")
end
end
context "when data sharing agreement present" do context "when org has a DPO" do
it "does not display banner" do context "when org does not have a signed data sharing agreement" do
expect(component.display_banner?).to eq(false) context "when user is not a DPO" do
end let(:organisation) { create(:organisation, :without_dpc) }
let(:user) { create(:user, organisation:) }
let!(:dpo) { create(:user, :data_protection_officer, organisation:) }
it "verifies DSA exists for organisation" do it "displays the banner and shows DPOs" do
render expect(component.display_banner?).to eq(true)
expect(DataProtectionConfirmation).to have_received(:exists?).with(organisation: user.organisation, confirmed: true) expect(render.css("a")).to be_empty
expect(render).to have_selector("p", text: "Your data protection officer must accept the Data Sharing Agreement on CORE before you can create any logs.")
expect(render).to have_selector("p", text: "You can ask: #{dpo.name}")
end end
end end
context "when data sharing agreement not present" do context "when user is a DPO" do
let(:user) { create(:user, organisation: create(:organisation, :without_dpc)) } let(:organisation) { create(:organisation, :without_dpc) }
let(:user) { create(:user, :data_protection_officer, organisation:) }
it "displays the banner" do it "displays the banner and asks to sign" do
expect(component.display_banner?).to eq(true) expect(component.display_banner?).to eq(true)
end expect(render).to have_link(
"Read the Data Sharing Agreement",
it "produces the correct link" do href: "/organisations/#{organisation.id}/data-sharing-agreement",
render )
expect(component.data_sharing_agreement_href).to eq("/organisations/#{user.organisation.id}/data-sharing-agreement") expect(render).to have_selector("p", text: "Your organisation must accept the Data Sharing Agreement before you can create any logs.")
end
it "verifies DSA exists for organisation" do
render
expect(DataProtectionConfirmation).to have_received(:exists?).with(organisation: user.organisation, confirmed: true)
end end
end end
end end
end
context "when user is support" do
let(:user) { create(:user, :support) }
context "when org blank" do
let(:organisation) { nil }
context "when org has a signed data sharing agremeent" do
it "does not display banner" do it "does not display banner" do
expect(component.display_banner?).to eq(false) expect(component.display_banner?).to eq(false)
end expect(render.content).to be_empty
end
context "when org present" do
before do
allow(DataProtectionConfirmation).to receive(:exists?).and_call_original
end
context "when data sharing agreement present" do
it "does not display banner" do
expect(component.display_banner?).to eq(false)
end
it "verifies DSA exists for organisation" do
render
expect(DataProtectionConfirmation).to have_received(:exists?).with(organisation:, confirmed: true)
end
end
context "when data sharing agreement not present" do
let(:organisation) { create(:organisation, :without_dpc) }
it "displays the banner" do
expect(component.display_banner?).to eq(true)
end
it "produces the correct link" do
render
expect(component.data_sharing_agreement_href).to eq("/organisations/#{organisation.id}/data-sharing-agreement")
end
it "verifies DSA exists for organisation" do
render
expect(DataProtectionConfirmation).to have_received(:exists?).with(organisation:, confirmed: true)
end
end end
end end
end end

27
spec/mailers/data_protection_confirmation_mailer_spec.rb

@ -0,0 +1,27 @@
require "rails_helper"
RSpec.describe DataProtectionConfirmationMailer do
describe "#send_csv_download_mail" do
let(:notify_client) { instance_double(Notifications::Client) }
let(:user) { create(:user, email: "user@example.com") }
let(:organisation) { user.organisation }
before do
allow(Notifications::Client).to receive(:new).and_return(notify_client)
allow(notify_client).to receive(:send_email).and_return(true)
end
it "sends confirmation email to user" do
expect(notify_client).to receive(:send_email).with(
email_address: user.email,
template_id: "3dbf78fe-a2c8-4d65-aa19-e4d62695d4a9",
personalisation: {
organisation_name: organisation.name,
link: "#{ENV['APP_HOST']}/organisations/#{organisation.id}/data-sharing-agreement",
},
)
described_class.new.send_confirmation_email(user)
end
end
end

122
spec/models/user_spec.rb

@ -4,36 +4,60 @@ RSpec.describe User, type: :model do
describe "#new" do describe "#new" do
let(:user) { create(:user, old_user_id: "3") } let(:user) { create(:user, old_user_id: "3") }
let(:other_organisation) { create(:organisation) } let(:other_organisation) { create(:organisation) }
let!(:owned_lettings_log) do
create(
:lettings_log,
:completed,
managing_organisation: other_organisation,
created_by: user,
)
end
let!(:managed_lettings_log) do
create(
:lettings_log,
created_by: user,
owning_organisation: other_organisation,
)
end
it "belongs to an organisation" do it "belongs to an organisation" do
expect(user.organisation).to be_a(Organisation) expect(user.organisation).to be_a(Organisation)
end end
it "has owned lettings logs through their organisation" do describe "#owned_lettings_logs" do
expect(user.owned_lettings_logs.first).to eq(owned_lettings_log) let!(:owned_lettings_log) do
create(
:lettings_log,
:completed,
managing_organisation: other_organisation,
created_by: user,
)
end
it "has owned lettings logs through their organisation" do
expect(user.owned_lettings_logs.first).to eq(owned_lettings_log)
end
end end
it "has managed lettings logs through their organisation" do describe "#managed_lettings_logs" do
expect(user.managed_lettings_logs.first).to eq(managed_lettings_log) let!(:managed_lettings_log) do
create(
:lettings_log,
created_by: user,
owning_organisation: other_organisation,
)
end
it "has managed lettings logs through their organisation" do
expect(user.managed_lettings_logs.first).to eq(managed_lettings_log)
end
end end
it "has lettings logs through their organisation" do describe "#lettings_logs" do
expect(user.lettings_logs.to_a).to match_array([owned_lettings_log, managed_lettings_log]) let!(:owned_lettings_log) do
create(
:lettings_log,
:completed,
managing_organisation: other_organisation,
created_by: user,
)
end
let!(:managed_lettings_log) do
create(
:lettings_log,
created_by: user,
owning_organisation: other_organisation,
)
end
it "has lettings logs through their organisation" do
expect(user.lettings_logs.to_a).to match_array([owned_lettings_log, managed_lettings_log])
end
end end
it "has a role" do it "has a role" do
@ -136,6 +160,21 @@ RSpec.describe User, type: :model do
context "when the user is a Customer Support person" do context "when the user is a Customer Support person" do
let(:user) { create(:user, :support) } let(:user) { create(:user, :support) }
let!(:other_orgs_log) { create(:lettings_log) } let!(:other_orgs_log) { create(:lettings_log) }
let!(:owned_lettings_log) do
create(
:lettings_log,
:completed,
managing_organisation: other_organisation,
created_by: user,
)
end
let!(:managed_lettings_log) do
create(
:lettings_log,
created_by: user,
owning_organisation: other_organisation,
)
end
it "has access to logs from all organisations" do it "has access to logs from all organisations" do
expect(user.lettings_logs.to_a).to match_array([owned_lettings_log, managed_lettings_log, other_orgs_log]) expect(user.lettings_logs.to_a).to match_array([owned_lettings_log, managed_lettings_log, other_orgs_log])
@ -330,4 +369,45 @@ RSpec.describe User, type: :model do
end end
end end
end end
describe "#send_data_protection_confirmation_reminder" do
context "when updating to dpo" do
let!(:user) { create(:user, is_dpo: false) }
it "sends the email" do
expect { user.update!(is_dpo: true) }.to enqueue_job(ActionMailer::MailDeliveryJob).with(
"DataProtectionConfirmationMailer",
"send_confirmation_email",
"deliver_now",
args: [user],
)
end
context "when feature flag disabled" do
before do
allow(FeatureToggle).to receive(:new_data_protection_confirmation?).and_return(false)
end
it "does not send the email" do
expect { user.update!(is_dpo: true) }.not_to enqueue_job(ActionMailer::MailDeliveryJob)
end
end
end
context "when updating to non dpo" do
let!(:user) { create(:user, is_dpo: true) }
it "does not send the email" do
expect { user.update!(is_dpo: false) }.not_to enqueue_job(ActionMailer::MailDeliveryJob)
end
end
context "when updating something else" do
let!(:user) { create(:user) }
it "does not send the email" do
expect { user.update!(name: "foobar") }.not_to enqueue_job(ActionMailer::MailDeliveryJob)
end
end
end
end end

Loading…
Cancel
Save