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: [ [