Skip to content

Commit

Permalink
Merge pull request #19366 from department-of-veterans-affairs/feature…
Browse files Browse the repository at this point in the history
…/APPEALS-22218
  • Loading branch information
mikefinneran authored Sep 6, 2023
2 parents e9afd00 + e20ceeb commit 4c59f9f
Show file tree
Hide file tree
Showing 35 changed files with 1,090 additions and 65 deletions.
3 changes: 2 additions & 1 deletion app/controllers/help_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ class HelpController < ApplicationController

def feature_toggle_ui_hash(user = current_user)
{
programOfficeTeamManagement: FeatureToggle.enabled?(:program_office_team_management, user: user)
programOfficeTeamManagement: FeatureToggle.enabled?(:program_office_team_management, user: user),
metricsBrowserError: FeatureToggle.enabled?(:metrics_browser_error, user: current_user)
}
end

Expand Down
3 changes: 2 additions & 1 deletion app/controllers/intakes_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,8 @@ def feature_toggle_ui_hash
eduPreDocketAppeals: FeatureToggle.enabled?(:edu_predocket_appeals, user: current_user),
updatedAppealForm: FeatureToggle.enabled?(:updated_appeal_form, user: current_user),
hlrScUnrecognizedClaimants: FeatureToggle.enabled?(:hlr_sc_unrecognized_claimants, user: current_user),
vhaClaimReviewEstablishment: FeatureToggle.enabled?(:vha_claim_review_establishment, user: current_user)
vhaClaimReviewEstablishment: FeatureToggle.enabled?(:vha_claim_review_establishment, user: current_user),
metricsBrowserError: FeatureToggle.enabled?(:metrics_browser_error, user: current_user)
}
end

Expand Down
24 changes: 24 additions & 0 deletions app/controllers/metrics/dashboard_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# frozen_string_literal: true

class Metrics::DashboardController < ApplicationController
before_action :require_demo

def show
no_cache

@metrics = Metric.includes(:user).where("created_at > ?", 1.hour.ago).order(created_at: :desc)

begin
render :show, layout: "plain_application"
rescue StandardError => error
Rails.logger.error(error.full_message)
raise error.full_message
end
end

private

def require_demo
redirect_to "/unauthorized" unless Rails.deploy_env?(:demo)
end
end
44 changes: 44 additions & 0 deletions app/controllers/metrics/v2/logs_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# frozen_string_literal: true

class Metrics::V2::LogsController < ApplicationController
skip_before_action :verify_authentication

def create
metric = Metric.create_metric_from_rest(self, allowed_params, current_user)
failed_metric_info = metric&.errors.inspect || allowed_params[:message]
Rails.logger.info("Failed to create metric #{failed_metric_info}") unless metric&.valid?

if (metric.metric_type === 'error')
error_info = {
name: metric.metric_name,
class: metric.metric_class,
attrs: metric.metric_attributes,
created_at: metric.created_at,
uuid: metric.uuid,
}
error = StandardError.new(error_info)
Raven.capture_exception(error)
end

head :ok
end

def allowed_params
params.require(:metric).permit(:uuid,
:name,
:group,
:message,
:type,
:product,
:app_name,
:metric_attributes,
:additional_info,
:sent_to,
:sent_to_info,
:relevant_tables_info,
:start,
:end,
:duration
)
end
end
3 changes: 2 additions & 1 deletion app/controllers/test/users_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ class Test::UsersController < ApplicationController
stats: "/stats",
jobs: "/jobs",
admin: "/admin",
test_veterans: "/test/data"
test_veterans: "/test/data",
metrics_dashboard: "/metrics/dashboard"
}
}
].freeze
Expand Down
1 change: 0 additions & 1 deletion app/jobs/update_appellant_representation_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ class UpdateAppellantRepresentationJob < CaseflowJob
include ActionView::Helpers::DateHelper
queue_with_priority :low_priority
application_attr :queue

APP_NAME = "caseflow_job"
METRIC_GROUP_NAME = UpdateAppellantRepresentationJob.name.underscore
TOTAL_NUMBER_OF_APPEALS_TO_UPDATE = 1000
Expand Down
105 changes: 105 additions & 0 deletions app/models/metric.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# frozen_string_literal: true

class Metric < CaseflowRecord
belongs_to :user

METRIC_TYPES = { error: "error", log: "log", performance: "performance", info: "info" }.freeze
LOG_SYSTEMS = { datadog: "datadog", rails_console: "rails_console", javascript_console: "javascript_console" }
PRODUCT_TYPES = {
queue: "queue",
hearings: "hearings",
intake: "intake",
vha: "vha",
efolder: "efolder",
reader: "reader",
caseflow: "caseflow", # Default product
# Added below because MetricService has usages of this as a service
vacols: "vacols",
bgs: "bgs",
gov_delivery: "gov_delivery",
mpi: "mpi",
pexip: "pexip",
va_dot_gov: "va_dot_gov",
va_notify: "va_notify",
vbms: "vbms",
}.freeze
APP_NAMES = { caseflow: "caseflow", efolder: "efolder" }.freeze
METRIC_GROUPS = { service: "service" }.freeze

validates :metric_type, inclusion: { in: METRIC_TYPES.values }
validates :metric_product, inclusion: { in: PRODUCT_TYPES.values }
validates :metric_group, inclusion: { in: METRIC_GROUPS.values }
validates :app_name, inclusion: { in: APP_NAMES.values }
validate :sent_to_in_log_systems

def self.create_metric(klass, params, user)
create(default_object(klass, params, user))
end

def self.create_metric_from_rest(klass, params, user)
params[:metric_attributes] = JSON.parse(params[:metric_attributes]) if params[:metric_attributes]
params[:additional_info] = JSON.parse(params[:additional_info]) if params[:additional_info]
params[:sent_to_info] = JSON.parse(params[:sent_to_info]) if params[:sent_to_info]
params[:relevant_tables_info] = JSON.parse(params[:relevant_tables_info]) if params[:relevant_tables_info]

create(default_object(klass, params, user))
end

def sent_to_in_log_systems
invalid_systems = sent_to - LOG_SYSTEMS.values
msg = "contains invalid log systems. The following are valid log systems #{LOG_SYSTEMS.values}"
errors.add(:sent_to, msg) if !invalid_systems.empty?
end

def css_id
user.css_id
end

private

# Returns an object with defaults set if below symbols are not found in params default object.
# Looks for these symbols in params parameter
# - uuid
# - name
# - group
# - message
# - type
# - product
# - app_name
# - metric_attributes
# - additional_info
# - sent_to
# - sent_to_info
# - relevant_tables_info
# - start
# - end
# - duration
def self.default_object(klass, params, user)
{
uuid: params[:uuid],
user: user || User.new(full_name: "Stand in user for testing", css_id: SecureRandom.uuid, station_id: 'Metrics'),
metric_name: params[:name] || METRIC_TYPES[:log],
metric_class: klass&.try(:name) || klass&.class.name || self.name,
metric_group: params[:group] || METRIC_GROUPS[:service],
metric_message: params[:message] || METRIC_TYPES[:log],
metric_type: params[:type] || METRIC_TYPES[:log],
metric_product: PRODUCT_TYPES[params[:product]] || PRODUCT_TYPES[:caseflow],
app_name: params[:app_name] || APP_NAMES[:caseflow],
metric_attributes: params[:metric_attributes],
additional_info: params[:additional_info],
sent_to: Array(params[:sent_to]).flatten,
sent_to_info: params[:sent_to_info],
relevant_tables_info: params[:relevant_tables_info],
start: params[:start],
end: params[:end],
duration: calculate_duration(params[:start], params[:end], params[:duration]),
}
end

def self.calculate_duration(start, end_time, duration)
return duration if duration || !start || !end_time

end_time - start
end

end
86 changes: 80 additions & 6 deletions app/services/metrics_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,35 +4,86 @@

# see https://dropwizard.github.io/metrics/3.1.0/getting-started/ for abstractions on metric types
class MetricsService
def self.record(description, service: nil, name: "unknown")
def self.record(description, service: nil, name: "unknown", caller: nil)
return nil unless FeatureToggle.enabled?(:metrics_monitoring, user: current_user)

return_value = nil
app = RequestStore[:application] || "other"
service ||= app
uuid = SecureRandom.uuid
metric_name= 'request_latency'
sent_to = [[Metric::LOG_SYSTEMS[:rails_console]]]
sent_to_info = nil

start = Time.now
Rails.logger.info("STARTED #{description}")
stopwatch = Benchmark.measure do
return_value = yield
end
stopped = Time.now

if service
latency = stopwatch.real
DataDogService.emit_gauge(
sent_to_info = {
metric_group: "service",
metric_name: "request_latency",
metric_name: metric_name,
metric_value: latency,
app_name: app,
attrs: {
service: service,
endpoint: name
endpoint: name,
uuid: uuid
}
)
}
DataDogService.emit_gauge(sent_to_info)

sent_to << Metric::LOG_SYSTEMS[:datadog]
end

Rails.logger.info("FINISHED #{description}: #{stopwatch}")

metric_params = {
name: metric_name,
message: description,
type: Metric::METRIC_TYPES[:performance],
product: service,
attrs: {
service: service,
endpoint: name
},
sent_to: sent_to,
sent_to_info: sent_to_info,
start: start,
end: stopped,
duration: stopwatch.total * 1000 # values is in seconds and we want milliseconds
}
store_record_metric(uuid, metric_params, caller)

return_value
rescue StandardError
rescue StandardError => error
Rails.logger.error("#{error.message}\n#{error.backtrace.join("\n")}")
Raven.capture_exception(error, extra: { type: "request_error", service: service, name: name, app: app })

increment_datadog_counter("request_error", service, name, app) if service

metric_params = {
name: "Stand in object if metrics_service.record fails",
message: "Variables not initialized before failure",
type: Metric::METRIC_TYPES[:error],
product: "",
attrs: {
service: "",
endpoint: ""
},
sent_to: [[Metric::LOG_SYSTEMS[:rails_console]]],
sent_to_info: "",
start: 'Time not recorded',
end: 'Time not recorded',
duration: 'Time not recorded'
}

store_record_metric(uuid, metric_params, caller)

# Re-raise the same error. We don't want to interfere at all in normal error handling.
# This is just to capture the metric.
raise
Expand All @@ -51,4 +102,27 @@ def self.record(description, service: nil, name: "unknown")
}
)
end

private

def self.store_record_metric(uuid, params, caller)

name ="caseflow.server.metric.#{params[:name]&.downcase.gsub(/::/, '.')}"
params = {
uuid: uuid,
name: name,
message: params[:message],
type: params[:type],
product: params[:product],
metric_attributes: params[:attrs],
sent_to: params[:sent_to],
sent_to_info: params[:sent_to_info],
start: params[:start],
end: params[:end],
duration: params[:duration],
}
metric = Metric.create_metric(caller || self, params, RequestStore[:current_user])
failed_metric_info = metric&.errors.inspect
Rails.logger.info("Failed to create metric #{failed_metric_info}") unless metric&.valid?
end
end
5 changes: 4 additions & 1 deletion app/views/certifications/v2.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
dropdownUrls: dropdown_urls,
feedbackUrl: feedback_url,
buildDate: build_date,
vacolsId: @certification.vacols_id
vacolsId: @certification.vacols_id,
featureToggles: {
metricsBrowserError: FeatureToggle.enabled?(:metrics_browser_error, user: current_user)
}
}) %>
<% end %>
3 changes: 2 additions & 1 deletion app/views/decision_reviews/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
businessLine: business_line.name,
businessLineUrl: business_line.url,
featureToggles: {
decisionReviewQueueSsnColumn: FeatureToggle.enabled?(:decision_review_queue_ssn_column, user: current_user)
decisionReviewQueueSsnColumn: FeatureToggle.enabled?(:decision_review_queue_ssn_column, user: current_user),
metricsBrowserError: FeatureToggle.enabled?(:metrics_browser_error, user: current_user)
},
baseTasksUrl: business_line.tasks_url,
taskFilterDetails: task_filter_details
Expand Down
7 changes: 5 additions & 2 deletions app/views/dispatch/establish_claims/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
buildDate: build_date,
buttonText: start_text,
userQuota: user_quota && user_quota.to_hash,
currentUserHistoricalTasks: current_user_historical_tasks.map(&:to_hash)
currentUserHistoricalTasks: current_user_historical_tasks.map(&:to_hash),
featureToggles: {
metricsBrowserError: FeatureToggle.enabled?(:metrics_browser_error, user: current_user)
}
}) %>
<% end %>
<% end %>
5 changes: 4 additions & 1 deletion app/views/hearings/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
userIsDvc: current_user.can_view_judge_team_management?,
userIsHearingManagement: current_user.in_hearing_management_team?,
userIsBoardAttorney: current_user.attorney?,
userIsHearingAdmin: current_user.in_hearing_admin_team?
userIsHearingAdmin: current_user.in_hearing_admin_team?,
featureToggles: {
metricsBrowserError: FeatureToggle.enabled?(:metrics_browser_error, user: current_user)
}
}) %>
<% end %>
3 changes: 3 additions & 0 deletions app/views/inbox/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
inbox: {
messages: messages,
pagination: pagination
},
featureToggles: {
metricsBrowserError: FeatureToggle.enabled?(:metrics_browser_error, user: current_user)
}
}) %>
<% end %>
3 changes: 3 additions & 0 deletions app/views/intake_manager/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,8 @@
dropdownUrls: dropdown_urls,
feedbackUrl: feedback_url,
buildDate: build_date
featureToggles: {
metricsBrowserError: FeatureToggle.enabled?(:metrics_browser_error, user: current_user)
}
}) %>
<% end %>
Loading

0 comments on commit 4c59f9f

Please sign in to comment.