diff --git a/app/jobs/reports/monthly_account_reuse_report.rb b/app/jobs/reports/monthly_account_reuse_report.rb
index e0d8f6a2473..5b9ba305b6e 100644
--- a/app/jobs/reports/monthly_account_reuse_report.rb
+++ b/app/jobs/reports/monthly_account_reuse_report.rb
@@ -6,16 +6,19 @@ class MonthlyAccountReuseReport < BaseReport
attr_reader :report_date
- def perform(report_date)
+ def initialize(report_date = Time.zone.today)
+ @report_date = report_date
+ end
+
+ def perform(report_date = Time.zone.today)
@report_date = report_date
_latest, path = generate_s3_paths(REPORT_NAME, 'json', now: report_date)
- body = report_body
if bucket_name.present?
upload_file_to_s3_bucket(
path: path,
- body: body,
+ body: report_body,
content_type: 'text/csv',
bucket: bucket_name,
)
@@ -124,31 +127,36 @@ def total_reuse_report
def report_csv
monthly_reuse_report = total_reuse_report
- csv_array = []
- csv_array << ["IDV app reuse rate #{stats_month}"]
- csv_array << ['Num. SPs', 'Num. users', 'Percentage']
+ tables_array = []
+ reuse_rate_table = []
+ reuse_rate_table << {
+ title: "IDV app reuse rate #{stats_month}",
+ float_as_percent: true,
+ precision: 4,
+ }
+ reuse_rate_table << ['Num. SPs', 'Num. users', 'Percentage']
monthly_reuse_report[:reuse_stats].each do |result_entry|
- csv_array << [
+ reuse_rate_table << [
result_entry['num_agencies'],
result_entry['num_users'],
result_entry['percentage'],
]
end
- csv_array << [
+ reuse_rate_table << [
'Total (all >1)',
monthly_reuse_report[:total_users],
monthly_reuse_report[:total_percentage],
]
+ tables_array << reuse_rate_table
- csv_array << []
- csv_array << ['Total proofed identities']
- csv_array << [
- "Total proofed identities (#{stats_month})",
- monthly_reuse_report[:total_proofed],
- ]
+ total_proofed_identities_table = []
+ total_proofed_identities_table << { title: 'Total proofed identities' }
+ total_proofed_identities_table << ["Total proofed identities (#{stats_month})"]
+ total_proofed_identities_table << [monthly_reuse_report[:total_proofed]]
+ tables_array << total_proofed_identities_table
- csv_array
+ tables_array
end
def report_body
diff --git a/app/jobs/reports/monthly_key_metrics_report.rb b/app/jobs/reports/monthly_key_metrics_report.rb
new file mode 100644
index 00000000000..eb2ba5e11a9
--- /dev/null
+++ b/app/jobs/reports/monthly_key_metrics_report.rb
@@ -0,0 +1,50 @@
+require 'csv'
+
+module Reports
+ class MonthlyKeyMetricsReport < BaseReport
+ REPORT_NAME = 'monthly-key-metrics-report'.freeze
+
+ attr_reader :report_date
+
+ def perform(date)
+ @report_date = date
+ csv_for_email = monthly_key_metrics_report_array
+ email_message = "Report: #{REPORT_NAME} #{date}"
+ email_addresses = emails.select(&:present?)
+
+ if !email_addresses.empty?
+ ReportMailer.tables_report(
+ email: email_addresses,
+ subject: "Monthly Key Metrics Report - #{date}",
+ message: email_message,
+ tables: csv_for_email,
+ ).deliver_now
+ else
+ Rails.logger.warn 'No email addresses received - Monthly Key Metrics Report NOT SENT'
+ end
+ end
+
+ def emails
+ emails = [IdentityConfig.store.team_agnes_email]
+ if Identity::Hostdata.env == 'prod' && report_date.day == 1
+ emails << IdentityConfig.store.team_all_feds_email
+ end
+ emails
+ end
+
+ def monthly_key_metrics_report_array
+ csv_array = []
+
+ account_reuse_report_csv.each do |row|
+ csv_array << row
+ end
+
+ csv_array
+ end
+
+ # Individual Key Metric Report
+ def account_reuse_report_csv
+ Reports::MonthlyAccountReuseReport.new(report_date).report_csv
+ end
+ end
+end
diff --git a/app/mailers/report_mailer.rb b/app/mailers/report_mailer.rb
index 773ec8c0314..c3b1043d1c4 100644
--- a/app/mailers/report_mailer.rb
+++ b/app/mailers/report_mailer.rb
@@ -43,8 +43,10 @@ def warn_error(email:, error:, env: Rails.env)
# each table can have a first "row" that is a hash with options
# @option opts [Boolean] :float_as_percent whether or not to render floats as percents
# @option opts [Boolean] :title title of the table
- def tables_report(email:, subject:, tables:, env: Identity::Hostdata.env || 'local')
- @tables = tables.each_with_index.map do |table, index|
+ def tables_report(email:, subject:, message:, tables:, env: Identity::Hostdata.env || 'local')
+ @message = message
+
+ @tables = tables.map(&:dup).each_with_index.map do |table, index|
options = table.first.is_a?(Hash) ? table.shift : {}
options[:title] ||= "Table #{index + 1}"
diff --git a/app/views/report_mailer/tables_report.html.erb b/app/views/report_mailer/tables_report.html.erb
index d1928ccaebe..49e24b2b06b 100644
--- a/app/views/report_mailer/tables_report.html.erb
+++ b/app/views/report_mailer/tables_report.html.erb
@@ -1,6 +1,8 @@
<%#
+- @message: text to put in the beginning of the email
- @tables: an array of tables, expects first "row" to be an options hash
%>
+<%= @message %>
<% @tables.each do |table| %>
<% options, header, *rows = table %>
@@ -20,7 +22,7 @@
<% row.each do |cell| %>
>
<% if cell.is_a?(Float) && options[:float_as_percent] %>
- <%= number_to_percentage(cell * 100, precision: 2) %>
+ <%= number_to_percentage(cell * 100, precision: options[:precision] || 2) %>
<% else %>
<%= number_with_delimiter(cell) %>
<% end %>
diff --git a/config/application.yml.default b/config/application.yml.default
index 77a9e73f20e..728986ea5f8 100644
--- a/config/application.yml.default
+++ b/config/application.yml.default
@@ -311,6 +311,8 @@ show_user_attribute_deprecation_warnings: false
otp_min_attempts_remaining_warning_count: 3
system_demand_report_email: 'foo@bar.com'
sp_issuer_user_counts_report_configs: '[]'
+team_agnes_email: ''
+team_all_feds_email: ''
team_ursula_email: ''
test_ssn_allowed_list: ''
totp_code_interval: 30
diff --git a/config/initializers/job_configurations.rb b/config/initializers/job_configurations.rb
index 8125a5a2e17..f1832446985 100644
--- a/config/initializers/job_configurations.rb
+++ b/config/initializers/job_configurations.rb
@@ -189,12 +189,18 @@
cron: cron_24h,
args: -> { [14.days.ago] },
},
- # Weekly report describing account reuse
+ # Monthly report describing account reuse
monthly_account_reuse_report: {
class: 'Reports::MonthlyAccountReuseReport',
cron: cron_1st_of_mo,
args: -> { [Time.zone.today] },
},
+ # Monthly report checking in on key metrics
+ monthly_key_metrics_report: {
+ class: 'Reports::MonthlyKeyMetricsReport',
+ cron: cron_24h,
+ args: -> { [Time.zone.today] },
+ },
# Job to backfill encrypted_pii_recovery_multi_region on profiles
multi_region_kms_migration_profile_migration: {
class: 'MultiRegionKmsMigration::ProfileMigrationJob',
diff --git a/lib/identity_config.rb b/lib/identity_config.rb
index 4205b196fb2..efd6d81840e 100644
--- a/lib/identity_config.rb
+++ b/lib/identity_config.rb
@@ -440,6 +440,8 @@ def self.build_store(config_map)
config.add(:sp_issuer_user_counts_report_configs, type: :json)
config.add(:state_tracking_enabled, type: :boolean)
config.add(:system_demand_report_email, type: :string)
+ config.add(:team_agnes_email, type: :string)
+ config.add(:team_all_feds_email, type: :string)
config.add(:team_ursula_email, type: :string)
config.add(:telephony_adapter, type: :string)
config.add(:test_ssn_allowed_list, type: :comma_separated_string_list)
diff --git a/spec/jobs/reports/monthly_account_reuse_report_spec.rb b/spec/jobs/reports/monthly_account_reuse_report_spec.rb
index 3ffa02b2f7e..77383e43e3c 100644
--- a/spec/jobs/reports/monthly_account_reuse_report_spec.rb
+++ b/spec/jobs/reports/monthly_account_reuse_report_spec.rb
@@ -2,15 +2,17 @@
require 'csv'
RSpec.describe Reports::MonthlyAccountReuseReport do
+ let(:report_date) { Date.new(2021, 3, 1) }
+
subject(:report) { Reports::MonthlyAccountReuseReport.new }
- let(:report_date) { Date.new(2021, 3, 1) }
let(:s3_report_bucket_prefix) { 'reports-bucket' }
let(:s3_report_path) do
'int/monthly-account-reuse-report/2021/2021-03-01.monthly-account-reuse-report.json'
end
before do
+ travel_to report_date
allow(Identity::Hostdata).to receive(:env).and_return('int')
allow(Identity::Hostdata).to receive(:aws_account_id).and_return('1234')
allow(Identity::Hostdata).to receive(:aws_region).and_return('us-west-1')
@@ -28,14 +30,14 @@
it 'uploads a file to S3 based on the report date' do
expect(report).to receive(:upload_file_to_s3_bucket).with(
path: s3_report_path,
- body: kind_of(String),
+ body: anything,
content_type: 'text/csv',
bucket: 'reports-bucket.1234-us-west-1',
).exactly(1).time.and_call_original
expect(report).to receive(:report_body).and_call_original.once
- report.perform(report_date)
+ report.perform
end
context 'with data' do
@@ -122,17 +124,21 @@ def create_identity(id, provider, verified_time)
it 'aggregates by issuer' do
expect(report).to receive(:upload_file_to_s3_bucket).
exactly(1).times do |path:, body:, content_type:, bucket:|
- actual_csv = body # CSV.parse(body, headers: true)
+ actual_csv = body
expected_csv = CSV.generate do |csv|
[
- ['IDV app reuse rate Feb-2021'],
- ['Num. SPs', 'Num. users', 'Percentage'],
- [2, 3, 30.0],
- [3, 2, 20.0],
- ['Total (all >1)', 5, 50.0],
- [],
- ['Total proofed identities'],
- ['Total proofed identities (Feb-2021)', 10],
+ [
+ { title: 'IDV app reuse rate Feb-2021', float_as_percent: true, precision: 4 },
+ ['Num. SPs', 'Num. users', 'Percentage'],
+ [2, 3, 30.0],
+ [3, 2, 20.0],
+ ['Total (all >1)', 5, 50.0],
+ ],
+ [
+ { title: 'Total proofed identities' },
+ ['Total proofed identities (Feb-2021)'],
+ [10],
+ ],
].each do |row|
csv << row
end
@@ -140,7 +146,7 @@ def create_identity(id, provider, verified_time)
expect(actual_csv).to eq(expected_csv)
end
- report.perform(report_date)
+ report.perform
end
end
end
diff --git a/spec/jobs/reports/monthly_key_metrics_report_spec.rb b/spec/jobs/reports/monthly_key_metrics_report_spec.rb
new file mode 100644
index 00000000000..364fdfb7855
--- /dev/null
+++ b/spec/jobs/reports/monthly_key_metrics_report_spec.rb
@@ -0,0 +1,55 @@
+require 'rails_helper'
+
+RSpec.describe Reports::MonthlyKeyMetricsReport do
+ subject(:report) { Reports::MonthlyKeyMetricsReport.new }
+
+ let(:report_date) { Date.new(2021, 3, 2) }
+ let(:name) { 'monthly-key-metrics-report' }
+ let(:agnes_email) { 'fake@agnes_email.com' }
+ let(:feds_email) { 'fake@feds_email.com' }
+
+ before do
+ allow(IdentityConfig.store).to receive(:team_agnes_email).
+ and_return(agnes_email)
+ allow(IdentityConfig.store).to receive(:team_all_feds_email).
+ and_return(feds_email)
+ allow(Identity::Hostdata).to receive(:env).and_return('prod')
+ end
+
+ it 'sends out a report to the email listed with one total user' do
+ expect(ReportMailer).to receive(:tables_report).once.with(
+ message: 'Report: monthly-key-metrics-report 2021-03-02',
+ email: [agnes_email],
+ subject: 'Monthly Key Metrics Report - 2021-03-02',
+ tables: anything,
+ ).and_call_original
+
+ subject.perform(report_date)
+ end
+
+ it 'sends out a report to the emails listed with two users' do
+ first_of_month_date = report_date - 1
+
+ expect(ReportMailer).to receive(:tables_report).once.with(
+ message: 'Report: monthly-key-metrics-report 2021-03-01',
+ email: [agnes_email, feds_email],
+ subject: 'Monthly Key Metrics Report - 2021-03-01',
+ tables: anything,
+ ).and_call_original
+
+ subject.perform(first_of_month_date)
+ end
+
+ it 'does not send out a report with no emails' do
+ allow(IdentityConfig.store).to receive(:team_agnes_email).and_return('')
+
+ expect(ReportMailer).not_to receive(:tables_report).with(
+ message: 'Report: monthly-key-metrics-report 2021-03-02',
+ email: [''],
+ subject: 'Monthly Key Metrics Report - 2021-03-02',
+ tables: anything,
+ ).and_call_original
+
+ subject.perform(report_date)
+ end
+end
diff --git a/spec/mailers/previews/report_mailer_preview.rb b/spec/mailers/previews/report_mailer_preview.rb
index 81e776fd877..f6995e2696e 100644
--- a/spec/mailers/previews/report_mailer_preview.rb
+++ b/spec/mailers/previews/report_mailer_preview.rb
@@ -8,10 +8,36 @@ def warn_error
)
end
+ def monthly_key_metrics_report
+ ReportMailer.tables_report(
+ email: 'test@example.com',
+ subject: 'Example Key Metrics Report',
+ message: 'Key Metrics Report February 2021',
+ tables: [
+ [
+ { title: 'IDV app reuse rate Feb-2021', float_as_percent: true, precision: 4 },
+ ['Num. SPs', 'Num. users', 'Percentage'],
+ [2, 207422, 0.105164],
+ [3, 6700, 0.003397],
+ [4, 254, 0.000129],
+ [5, 26, 0.000013],
+ [6, 1, 0.000001],
+ ['Total (all >1)', 214403, 0.108703],
+ ],
+ [
+ { title: 'Total proofed identities' },
+ ['Total proofed identities (Feb-2021)'],
+ [1972368],
+ ],
+ ],
+ )
+ end
+
def tables_report
ReportMailer.tables_report(
email: 'test@example.com',
subject: 'Example Report',
+ message: 'Sample Message',
tables: [
[
['Some', 'String'],
diff --git a/spec/mailers/report_mailer_spec.rb b/spec/mailers/report_mailer_spec.rb
index 5296cc6ec6c..6649dbeb0ab 100644
--- a/spec/mailers/report_mailer_spec.rb
+++ b/spec/mailers/report_mailer_spec.rb
@@ -60,6 +60,7 @@
ReportMailer.tables_report(
email: 'foo@example.com',
subject: 'My Report',
+ message: 'My Report - Today',
env: env,
tables: [
[
|