18F/identity-idp

View on GitHub
bin/oncall/otp-deliveries

Summary

Maintainability
Test Coverage
#!/usr/bin/env ruby
# frozen_string_literal: true

Dir.chdir(__dir__) { require 'bundler/setup' }

require 'active_support'
require 'active_support/core_ext/integer/time'
require 'optparse'

$LOAD_PATH.unshift(File.expand_path(File.join(__dir__, '../../lib')))
require 'script_base'
require 'reporting/cloudwatch_client'
require 'reporting/cloudwatch_query_quoting'
require 'reporting/unknown_progress_bar'

class OtpDeliveries
  include Reporting::CloudwatchQueryQuoting

  # @return [OtpDeliveries]
  def self.parse!(argv: ARGV, out: STDOUT)
    show_help = false
    output_format = :table
    filter = nil

    parser = OptionParser.new do |opts|
      opts.banner = <<~EOM
        Usage: #{$PROGRAM_NAME} uuid1 [uuid2...]

        Looks up OTP delivery attempts for SMS/Voice by user UUID, within the last 72 hours

        Options:
      EOM

      opts.on('--csv') do
        output_format = :csv
      end

      opts.on('--table', 'Output format as an ASCII table (default)') do
        output_format = :table
      end

      opts.on('--json') do
        output_format = :json
      end

      opts.on('--filter=FILTER', 'Filters output to be only SMS or VOICE') do |filter_v|
        filter = filter_v
      end

      opts.on('--help', 'Show this help message') do
        show_help = true
      end
    end

    uuids = parser.parse!(argv)

    if uuids.empty? || show_help
      out.puts parser
      exit 1
    end

    new(uuids: uuids, filter: filter, output_format: output_format)
  end

  attr_reader :uuids, :output_format, :filter

  def initialize(uuids:, output_format:, filter: nil, progress_bar: true)
    @uuids = uuids
    @output_format = output_format
    @filter = filter
    @progress_bar = progress_bar
  end

  def progress_bar?
    @progress_bar
  end

  def run(out: STDOUT)
    results = query_data(uuids)

    table = []
    table << %w[user_id timestamp message_id delivery_preference country_code]
    results.each do |result|
      table << [
        result.user_id,
        result.timestamp,
        result.message_id,
        result.delivery_preference,
        result.country_code,
      ]
    end

    ScriptBase.render_output(table, format: output_format, stdout: out)
  end

  Result = Struct.new(
    :user_id,
    :timestamp,
    :message_id,
    :delivery_preference,
    :country_code,
    keyword_init: true,
  )

  # @return [Array<Result>]
  def query_data(uuids)
    Reporting::UnknownProgressBar.wrap(show_bar: progress_bar?, title: 'Querying logs') do
      cloudwatch_client.fetch(
        query: <<~EOS,
          fields
            @timestamp
          , properties.user_id
          , properties.event_properties.telephony_response.message_id
          , properties.event_properties.otp_delivery_preference
          , properties.event_properties.telephony_response.delivery_status
          , properties.event_properties.country_code
          | filter name = 'Telephony: OTP sent'
          #{filter ?
            "| filter properties.event_properties.otp_delivery_preference = '#{filter.downcase}'" :
            nil}
          | filter properties.user_id IN #{quote(uuids)}
          | limit 10000
        EOS
        from: 72.hours.ago,
        to: Time.now,
      ).map do |row|
        Result.new(
          user_id: row['properties.user_id'],
          timestamp: row['@timestamp'],
          message_id: row['properties.event_properties.telephony_response.message_id'],
          delivery_preference: row['properties.event_properties.otp_delivery_preference'],
          country_code: row['properties.event_properties.country_code'],
        )
      end
    end
  end

  def cloudwatch_client
    @cloudwatch_client ||= Reporting::CloudwatchClient.new(
      ensure_complete_logs: false,
      slice_interval: nil,
      progress: false,
    )
  end
end

if $PROGRAM_NAME == __FILE__
  OtpDeliveries.parse!.run
end