Chore: Remove redis backed reporting (#669)
This commit is contained in:
3
Gemfile
3
Gemfile
@@ -54,9 +54,6 @@ gem 'pundit'
|
|||||||
# https://karolgalanciak.com/blog/2019/11/30/from-activerecord-callbacks-to-publish-slash-subscribe-pattern-and-event-driven-design/
|
# https://karolgalanciak.com/blog/2019/11/30/from-activerecord-callbacks-to-publish-slash-subscribe-pattern-and-event-driven-design/
|
||||||
gem 'wisper', '2.0.0'
|
gem 'wisper', '2.0.0'
|
||||||
|
|
||||||
##--- gems for reporting ---##
|
|
||||||
gem 'nightfury'
|
|
||||||
|
|
||||||
##--- gems for billing ---##
|
##--- gems for billing ---##
|
||||||
gem 'chargebee'
|
gem 'chargebee'
|
||||||
|
|
||||||
|
|||||||
@@ -282,7 +282,6 @@ GEM
|
|||||||
multi_xml (0.6.0)
|
multi_xml (0.6.0)
|
||||||
multipart-post (2.1.1)
|
multipart-post (2.1.1)
|
||||||
netrc (0.11.0)
|
netrc (0.11.0)
|
||||||
nightfury (1.0.1)
|
|
||||||
nio4r (2.5.2)
|
nio4r (2.5.2)
|
||||||
nokogiri (1.10.9)
|
nokogiri (1.10.9)
|
||||||
mini_portile2 (~> 2.4.0)
|
mini_portile2 (~> 2.4.0)
|
||||||
@@ -533,8 +532,7 @@ DEPENDENCIES
|
|||||||
letter_opener
|
letter_opener
|
||||||
listen
|
listen
|
||||||
mini_magick
|
mini_magick
|
||||||
mock_redis!
|
mock_redis
|
||||||
nightfury
|
|
||||||
pg
|
pg
|
||||||
pry-rails
|
pry-rails
|
||||||
puma
|
puma
|
||||||
|
|||||||
@@ -1,77 +0,0 @@
|
|||||||
class ReportBuilder
|
|
||||||
include CustomExceptions::Report
|
|
||||||
|
|
||||||
# Usage
|
|
||||||
# rb = ReportBuilder.new a, { metric: 'conversations_count', type: :account, id: 1}
|
|
||||||
# rb = ReportBuilder.new a, { metric: 'avg_first_response_time', type: :agent, id: 1}
|
|
||||||
|
|
||||||
IDENTITY_MAPPING = {
|
|
||||||
account: AccountIdentity,
|
|
||||||
agent: AgentIdentity
|
|
||||||
}.freeze
|
|
||||||
|
|
||||||
def initialize(account, params)
|
|
||||||
@account = account
|
|
||||||
@params = params
|
|
||||||
@identity = get_identity
|
|
||||||
@start_time, @end_time = validate_times
|
|
||||||
end
|
|
||||||
|
|
||||||
def build
|
|
||||||
metric = @identity.send(@params[:metric])
|
|
||||||
if metric.get.nil?
|
|
||||||
metric.delete
|
|
||||||
result = {}
|
|
||||||
else
|
|
||||||
result = metric.get_padded_range(@start_time, @end_time) || {}
|
|
||||||
end
|
|
||||||
formatted_hash(result)
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def get_identity
|
|
||||||
identity_class = IDENTITY_MAPPING[@params[:type]]
|
|
||||||
raise InvalidIdentity if identity_class.nil?
|
|
||||||
|
|
||||||
@params[:id] = @account.id if identity_class == AccountIdentity
|
|
||||||
identity_id = @params[:id]
|
|
||||||
raise IdentityNotFound if identity_id.nil?
|
|
||||||
|
|
||||||
tags = identity_class == AccountIdentity ? nil : { account_id: @account.id }
|
|
||||||
identity = identity_class.new(identity_id, tags: tags)
|
|
||||||
raise MetricNotFound if @params[:metric].blank?
|
|
||||||
raise MetricNotFound unless identity.respond_to?(@params[:metric])
|
|
||||||
|
|
||||||
identity
|
|
||||||
end
|
|
||||||
|
|
||||||
def validate_times
|
|
||||||
start_time = @params[:since] || Time.now.end_of_day - 30.days
|
|
||||||
end_time = @params[:until] || Time.now.end_of_day
|
|
||||||
start_time = begin
|
|
||||||
parse_date_time(start_time)
|
|
||||||
rescue StandardError
|
|
||||||
raise(InvalidStartTime)
|
|
||||||
end
|
|
||||||
end_time = begin
|
|
||||||
parse_date_time(end_time)
|
|
||||||
rescue StandardError
|
|
||||||
raise(InvalidEndTime)
|
|
||||||
end
|
|
||||||
[start_time, end_time]
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse_date_time(datetime)
|
|
||||||
return datetime if datetime.is_a?(DateTime)
|
|
||||||
return datetime.to_datetime if datetime.is_a?(Time) || datetime.is_a?(Date)
|
|
||||||
|
|
||||||
DateTime.strptime(datetime, '%s')
|
|
||||||
end
|
|
||||||
|
|
||||||
def formatted_hash(hash)
|
|
||||||
hash.each_with_object([]) do |p, arr|
|
|
||||||
arr << { value: p[1], timestamp: p[0] }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
class Api::V1::Accounts::ReportsController < Api::BaseController
|
|
||||||
include CustomExceptions::Report
|
|
||||||
include Constants::Report
|
|
||||||
|
|
||||||
around_action :report_exception
|
|
||||||
|
|
||||||
def account
|
|
||||||
builder = ReportBuilder.new(current_account, account_report_params)
|
|
||||||
data = builder.build
|
|
||||||
render json: data
|
|
||||||
end
|
|
||||||
|
|
||||||
def agent
|
|
||||||
builder = ReportBuilder.new(current_account, agent_report_params)
|
|
||||||
data = builder.build
|
|
||||||
render json: data
|
|
||||||
end
|
|
||||||
|
|
||||||
def account_summary
|
|
||||||
render json: account_summary_metrics
|
|
||||||
end
|
|
||||||
|
|
||||||
def agent_summary
|
|
||||||
render json: agent_summary_metrics
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def report_exception
|
|
||||||
yield
|
|
||||||
rescue InvalidIdentity, IdentityNotFound, MetricNotFound, InvalidStartTime, InvalidEndTime => e
|
|
||||||
render_error_response(e)
|
|
||||||
end
|
|
||||||
|
|
||||||
def current_account
|
|
||||||
current_user.account
|
|
||||||
end
|
|
||||||
|
|
||||||
def account_summary_metrics
|
|
||||||
summary_metrics(ACCOUNT_METRICS, :account_summary_params, AVG_ACCOUNT_METRICS)
|
|
||||||
end
|
|
||||||
|
|
||||||
def agent_summary_metrics
|
|
||||||
summary_metrics(AGENT_METRICS, :agent_summary_params, AVG_AGENT_METRICS)
|
|
||||||
end
|
|
||||||
|
|
||||||
def summary_metrics(metrics, calc_function, avg_metrics)
|
|
||||||
metrics.each_with_object({}) do |metric, result|
|
|
||||||
data = ReportBuilder.new(current_account, send(calc_function, metric)).build
|
|
||||||
result[metric] = calculate_metric(data, metric, avg_metrics)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def calculate_metric(data, metric, avg_metrics)
|
|
||||||
sum = data.inject(0) { |val, hash| val + hash[:value].to_i }
|
|
||||||
if avg_metrics.include?(metric)
|
|
||||||
sum /= data.length unless sum.zero?
|
|
||||||
end
|
|
||||||
sum
|
|
||||||
end
|
|
||||||
|
|
||||||
def account_summary_params(metric)
|
|
||||||
{
|
|
||||||
metric: metric.to_s,
|
|
||||||
type: :account,
|
|
||||||
since: params[:since],
|
|
||||||
until: params[:until]
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def agent_summary_params(metric)
|
|
||||||
{
|
|
||||||
metric: metric.to_s,
|
|
||||||
type: :agent,
|
|
||||||
since: params[:since],
|
|
||||||
until: params[:until],
|
|
||||||
id: params[:id]
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def account_report_params
|
|
||||||
{
|
|
||||||
metric: params[:metric],
|
|
||||||
type: :account,
|
|
||||||
since: params[:since],
|
|
||||||
until: params[:until]
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def agent_report_params
|
|
||||||
{
|
|
||||||
metric: params[:metric],
|
|
||||||
type: :agent,
|
|
||||||
id: params[:id],
|
|
||||||
since: params[:since],
|
|
||||||
until: params[:until]
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -9,7 +9,7 @@ class AsyncDispatcher < BaseDispatcher
|
|||||||
end
|
end
|
||||||
|
|
||||||
def listeners
|
def listeners
|
||||||
listeners = [EventListener.instance, ReportingListener.instance, WebhookListener.instance]
|
listeners = [EventListener.instance, WebhookListener.instance]
|
||||||
listeners << SubscriptionListener.instance if ENV['BILLING_ENABLED']
|
listeners << SubscriptionListener.instance if ENV['BILLING_ENABLED']
|
||||||
listeners
|
listeners
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
class AccountIdentity < Nightfury::Identity::Base
|
|
||||||
metric :conversations_count, :count_time_series, store_as: :b, step: :day
|
|
||||||
metric :incoming_messages_count, :count_time_series, step: :day
|
|
||||||
metric :outgoing_messages_count, :count_time_series, step: :day
|
|
||||||
metric :avg_first_response_time, :avg_time_series, store_as: :d, step: :day
|
|
||||||
metric :avg_resolution_time, :avg_time_series, store_as: :f, step: :day
|
|
||||||
metric :resolutions_count, :count_time_series, store_as: :g, step: :day
|
|
||||||
end
|
|
||||||
|
|
||||||
AccountIdentity.store_as = :ci
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
class AgentIdentity < Nightfury::Identity::Base
|
|
||||||
metric :avg_first_response_time, :avg_time_series, store_as: :d, step: :day
|
|
||||||
metric :avg_resolution_time, :avg_time_series, store_as: :f, step: :day
|
|
||||||
metric :resolutions_count, :count_time_series, store_as: :g, step: :day
|
|
||||||
tag :account_id, store_as: :co
|
|
||||||
end
|
|
||||||
|
|
||||||
AgentIdentity.store_as = :ai
|
|
||||||
@@ -3,7 +3,6 @@ app_redis_config = {
|
|||||||
password: ENV.fetch('REDIS_PASSWORD', nil).presence
|
password: ENV.fetch('REDIS_PASSWORD', nil).presence
|
||||||
}
|
}
|
||||||
redis = Rails.env.test? ? MockRedis.new : Redis.new(app_redis_config)
|
redis = Rails.env.test? ? MockRedis.new : Redis.new(app_redis_config)
|
||||||
Nightfury.redis = Redis::Namespace.new('reports', redis: redis)
|
|
||||||
|
|
||||||
# Alfred - Used currently for round robin and conversation emails.
|
# Alfred - Used currently for round robin and conversation emails.
|
||||||
# Add here as you use it for more features
|
# Add here as you use it for more features
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
defaults: &defaults
|
|
||||||
host: 0.0.0.0
|
|
||||||
port: 6379
|
|
||||||
namespace: "local"
|
|
||||||
|
|
||||||
development:
|
|
||||||
<<: *defaults
|
|
||||||
|
|
||||||
slave:
|
|
||||||
<<: *defaults
|
|
||||||
|
|
||||||
test:
|
|
||||||
<<: *defaults
|
|
||||||
|
|
||||||
staging:
|
|
||||||
<<: *defaults
|
|
||||||
|
|
||||||
production:
|
|
||||||
<<: *defaults
|
|
||||||
@@ -79,17 +79,6 @@ Rails.application.routes.draw do
|
|||||||
resources :notifications, only: [:index, :update]
|
resources :notifications, only: [:index, :update]
|
||||||
resource :notification_settings, only: [:show, :update]
|
resource :notification_settings, only: [:show, :update]
|
||||||
|
|
||||||
resources :reports, only: [] do
|
|
||||||
collection do
|
|
||||||
get :account
|
|
||||||
get :agent
|
|
||||||
end
|
|
||||||
member do
|
|
||||||
get :account_summary
|
|
||||||
get :agent_summary
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# this block is only required if subscription via chargebee is enabled
|
# this block is only required if subscription via chargebee is enabled
|
||||||
resources :subscriptions, only: [:index] do
|
resources :subscriptions, only: [:index] do
|
||||||
collection do
|
collection do
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module Constants::Report
|
|
||||||
ACCOUNT_METRICS = [:conversations_count,
|
|
||||||
:incoming_messages_count,
|
|
||||||
:outgoing_messages_count,
|
|
||||||
:avg_first_response_time,
|
|
||||||
:avg_resolution_time,
|
|
||||||
:resolutions_count].freeze
|
|
||||||
|
|
||||||
AVG_ACCOUNT_METRICS = [:avg_first_response_time, :avg_resolution_time].freeze
|
|
||||||
|
|
||||||
AGENT_METRICS = [:avg_first_response_time,
|
|
||||||
:avg_resolution_time,
|
|
||||||
:resolutions_count].freeze
|
|
||||||
|
|
||||||
AVG_AGENT_METRICS = [:avg_first_response_time, :avg_resolution_time].freeze
|
|
||||||
end
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module CustomExceptions::Report
|
|
||||||
class InvalidIdentity < CustomExceptions::Base
|
|
||||||
def message
|
|
||||||
'Invalid type'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class IdentityNotFound < CustomExceptions::Base
|
|
||||||
def message
|
|
||||||
'Type with the specified id not found'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class MetricNotFound < CustomExceptions::Base
|
|
||||||
def message
|
|
||||||
'Metric for the specified type not found'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class InvalidStartTime < CustomExceptions::Base
|
|
||||||
def message
|
|
||||||
'Invalid start_time'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class InvalidEndTime < CustomExceptions::Base
|
|
||||||
def message
|
|
||||||
'Invalid end_time'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
require 'rails_helper'
|
|
||||||
describe ReportingListener do
|
|
||||||
let(:listener) { described_class.instance }
|
|
||||||
let!(:account) { create(:account) }
|
|
||||||
let(:report_identity) { Reports::UpdateAccountIdentity.new(account, Time.zone.now) }
|
|
||||||
let!(:user) { create(:user, account: account) }
|
|
||||||
let!(:inbox) { create(:inbox, account: account) }
|
|
||||||
let!(:conversation) { create(:conversation, account: account, inbox: inbox, assignee: user) }
|
|
||||||
|
|
||||||
describe '#message_created' do
|
|
||||||
let(:event_name) { :'conversation.created' }
|
|
||||||
|
|
||||||
context 'when user activity message' do
|
|
||||||
it 'does not increment messages count' do
|
|
||||||
activity_message = create(:message, message_type: 'activity', account: account, inbox: inbox, conversation: conversation)
|
|
||||||
event = Events::Base.new(event_name, Time.zone.now, message: activity_message)
|
|
||||||
|
|
||||||
allow(Reports::UpdateAccountIdentity).to receive(:new).and_return(report_identity)
|
|
||||||
allow(report_identity).to receive(:incr_outgoing_messages_count).exactly(0).times
|
|
||||||
allow(report_identity).to receive(:incr_incoming_messages_count).exactly(0).times
|
|
||||||
|
|
||||||
listener.message_created(event)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when user conversation message' do
|
|
||||||
it 'increments messages count' do
|
|
||||||
conversation_message = create(:message, message_type: 'outgoing', account: account, inbox: inbox, conversation: conversation)
|
|
||||||
event = Events::Base.new(event_name, Time.zone.now, message: conversation_message)
|
|
||||||
|
|
||||||
allow(Reports::UpdateAccountIdentity).to receive(:new).and_return(report_identity)
|
|
||||||
allow(report_identity).to receive(:incr_outgoing_messages_count).once
|
|
||||||
allow(report_identity).to receive(:incr_incoming_messages_count).exactly(0).times
|
|
||||||
|
|
||||||
listener.message_created(event)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
Reference in New Issue
Block a user