feat(reports): filtro por inbox no Relatório do Bot
Hoje as métricas e séries do BotReports agregam toda a conta — não dá pra ver "a Jasmine da PrimeAL está errando mais que a do Qnn01". Cada unidade tem prompt próprio, então um sintoma localizado precisa de medição localizada. Backend: - Inbox#has_many :reporting_events (relação inversa que faltava) - BotMetricsBuilder aceita inbox_id e filtra bot_conversations + base_reporting_events - bot_metrics endpoint passa inbox_id pelos params permitidos - count_report_builder já suporta scope=inbox; agora funciona pra bot_resolutions_count e bot_handoffs_count graças à relação acima Frontend: - BotReports.vue: ReportFilters com filter-type='inboxes' e dropdown ativo - Quando uma inbox é escolhida, requestPayload inclui inboxId/type/id e os fetches (BotMetrics + ReportContainer) passam o filtro - API client getBotMetrics aceita inboxId; getBotSummary aceita type+id - Sem inbox selecionada: comportamento antigo (agregação da conta) Bonus na rake task de retroativo: - rebuild_bot_resolved.rake: Message.unscope(:order) pra evitar conflito PG::InvalidColumnReference (DISTINCT + ORDER BY default scope) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7cd2ea1258
commit
e9b8b6e587
@ -18,8 +18,15 @@ class V2::Reports::BotMetricsBuilder
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def filter_inbox_id
|
||||||
|
@filter_inbox_id ||= params[:inbox_id].presence&.to_i
|
||||||
|
end
|
||||||
|
|
||||||
def bot_activated_inbox_ids
|
def bot_activated_inbox_ids
|
||||||
@bot_activated_inbox_ids ||= account.inboxes.filter(&:active_bot?).map(&:id)
|
@bot_activated_inbox_ids ||= begin
|
||||||
|
ids = account.inboxes.filter(&:active_bot?).map(&:id)
|
||||||
|
filter_inbox_id ? ids & [filter_inbox_id] : ids
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def bot_conversations
|
def bot_conversations
|
||||||
@ -30,14 +37,18 @@ class V2::Reports::BotMetricsBuilder
|
|||||||
@bot_messages ||= account.messages.outgoing.where(conversation_id: bot_conversations.ids).where(created_at: range)
|
@bot_messages ||= account.messages.outgoing.where(conversation_id: bot_conversations.ids).where(created_at: range)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def base_reporting_events
|
||||||
|
scope = account.reporting_events.where(account_id: account.id, created_at: range)
|
||||||
|
scope = scope.where(inbox_id: filter_inbox_id) if filter_inbox_id
|
||||||
|
scope
|
||||||
|
end
|
||||||
|
|
||||||
def bot_resolutions_count
|
def bot_resolutions_count
|
||||||
account.reporting_events.joins(:conversation).select(:conversation_id).where(account_id: account.id, name: :conversation_bot_resolved,
|
base_reporting_events.joins(:conversation).select(:conversation_id).where(name: :conversation_bot_resolved).distinct.count
|
||||||
created_at: range).distinct.count
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def bot_handoffs_count
|
def bot_handoffs_count
|
||||||
account.reporting_events.joins(:conversation).select(:conversation_id).where(account_id: account.id, name: :conversation_bot_handoff,
|
base_reporting_events.joins(:conversation).select(:conversation_id).where(name: :conversation_bot_handoff).distinct.count
|
||||||
created_at: range).distinct.count
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def bot_resolution_rate
|
def bot_resolution_rate
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
# rubocop:disable Metrics/ClassLength
|
||||||
class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController
|
class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController
|
||||||
include Api::V2::Accounts::ReportsHelper
|
include Api::V2::Accounts::ReportsHelper
|
||||||
include Api::V2::Accounts::HeatmapHelper
|
include Api::V2::Accounts::HeatmapHelper
|
||||||
@ -58,7 +59,7 @@ class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def bot_metrics
|
def bot_metrics
|
||||||
bot_metrics = V2::Reports::BotMetricsBuilder.new(Current.account, params).metrics
|
bot_metrics = V2::Reports::BotMetricsBuilder.new(Current.account, bot_metrics_params).metrics
|
||||||
render json: bot_metrics
|
render json: bot_metrics
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -196,6 +197,14 @@ class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def bot_metrics_params
|
||||||
|
{
|
||||||
|
inbox_id: params[:inbox_id],
|
||||||
|
since: params[:since],
|
||||||
|
until: params[:until]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
def inbox_leads_summary_params
|
def inbox_leads_summary_params
|
||||||
{
|
{
|
||||||
inbox_id: params[:inbox_id],
|
inbox_id: params[:inbox_id],
|
||||||
@ -205,3 +214,4 @@ class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
# rubocop:enable Metrics/ClassLength
|
||||||
|
|||||||
@ -91,18 +91,19 @@ class ReportsAPI extends ApiClient {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getBotMetrics({ from, to } = {}) {
|
getBotMetrics({ from, to, inboxId } = {}) {
|
||||||
return axios.get(`${this.url}/bot_metrics`, {
|
return axios.get(`${this.url}/bot_metrics`, {
|
||||||
params: { since: from, until: to },
|
params: { since: from, until: to, inbox_id: inboxId },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getBotSummary({ from, to, groupBy, businessHours } = {}) {
|
getBotSummary({ from, to, groupBy, businessHours, type, id } = {}) {
|
||||||
return axios.get(`${this.url}/bot_summary`, {
|
return axios.get(`${this.url}/bot_summary`, {
|
||||||
params: {
|
params: {
|
||||||
since: from,
|
since: from,
|
||||||
until: to,
|
until: to,
|
||||||
type: 'account',
|
type: type || 'account',
|
||||||
|
id,
|
||||||
group_by: groupBy,
|
group_by: groupBy,
|
||||||
business_hours: businessHours,
|
business_hours: businessHours,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -20,6 +20,7 @@ export default {
|
|||||||
from: 0,
|
from: 0,
|
||||||
to: 0,
|
to: 0,
|
||||||
groupBy: GROUP_BY_FILTER[1],
|
groupBy: GROUP_BY_FILTER[1],
|
||||||
|
inboxId: null,
|
||||||
reportKeys: {
|
reportKeys: {
|
||||||
BOT_RESOLUTION_COUNT: 'bot_resolutions_count',
|
BOT_RESOLUTION_COUNT: 'bot_resolutions_count',
|
||||||
BOT_HANDOFF_COUNT: 'bot_handoffs_count',
|
BOT_HANDOFF_COUNT: 'bot_handoffs_count',
|
||||||
@ -32,6 +33,7 @@ export default {
|
|||||||
return {
|
return {
|
||||||
from: this.from,
|
from: this.from,
|
||||||
to: this.to,
|
to: this.to,
|
||||||
|
inboxId: this.inboxId,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -60,24 +62,35 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
getRequestPayload() {
|
getRequestPayload() {
|
||||||
const { from, to, groupBy, businessHours } = this;
|
const { from, to, groupBy, businessHours, inboxId } = this;
|
||||||
|
const payload = {
|
||||||
return {
|
|
||||||
from,
|
from,
|
||||||
to,
|
to,
|
||||||
groupBy: groupBy?.period,
|
groupBy: groupBy?.period,
|
||||||
businessHours,
|
businessHours,
|
||||||
};
|
};
|
||||||
|
if (inboxId) {
|
||||||
|
payload.type = 'inbox';
|
||||||
|
payload.id = inboxId;
|
||||||
|
}
|
||||||
|
return payload;
|
||||||
},
|
},
|
||||||
onFilterChange({ from, to, groupBy, businessHours }) {
|
onFilterChange({ from, to, groupBy, businessHours, inboxes }) {
|
||||||
this.from = from;
|
this.from = from;
|
||||||
this.to = to;
|
this.to = to;
|
||||||
this.groupBy = groupBy;
|
this.groupBy = groupBy;
|
||||||
this.businessHours = businessHours;
|
this.businessHours = businessHours;
|
||||||
|
this.inboxId = inboxes?.id || null;
|
||||||
this.fetchAllData();
|
this.fetchAllData();
|
||||||
|
|
||||||
useTrack(REPORTS_EVENTS.FILTER_REPORT, {
|
useTrack(REPORTS_EVENTS.FILTER_REPORT, {
|
||||||
filterValue: { from, to, groupBy, businessHours },
|
filterValue: {
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
groupBy,
|
||||||
|
businessHours,
|
||||||
|
inboxId: this.inboxId,
|
||||||
|
},
|
||||||
reportType: 'bots',
|
reportType: 'bots',
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -89,7 +102,7 @@ export default {
|
|||||||
<ReportHeader :header-title="$t('BOT_REPORTS.HEADER')" />
|
<ReportHeader :header-title="$t('BOT_REPORTS.HEADER')" />
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
<ReportFilters
|
<ReportFilters
|
||||||
:show-entity-filter="false"
|
filter-type="inboxes"
|
||||||
show-group-by
|
show-group-by
|
||||||
:show-business-hours="false"
|
:show-business-hours="false"
|
||||||
@filter-change="onFilterChange"
|
@filter-change="onFilterChange"
|
||||||
|
|||||||
@ -180,6 +180,8 @@ export const actions = {
|
|||||||
to: reportObj.to,
|
to: reportObj.to,
|
||||||
groupBy: reportObj.groupBy,
|
groupBy: reportObj.groupBy,
|
||||||
businessHours: reportObj.businessHours,
|
businessHours: reportObj.businessHours,
|
||||||
|
type: reportObj.type,
|
||||||
|
id: reportObj.id,
|
||||||
})
|
})
|
||||||
.then(botSummary => {
|
.then(botSummary => {
|
||||||
commit(types.default.SET_BOT_SUMMARY, botSummary.data);
|
commit(types.default.SET_BOT_SUMMARY, botSummary.data);
|
||||||
|
|||||||
@ -77,6 +77,7 @@ class Inbox < ApplicationRecord
|
|||||||
has_many :conversations, dependent: :destroy_async
|
has_many :conversations, dependent: :destroy_async
|
||||||
has_many :messages, dependent: :destroy_async
|
has_many :messages, dependent: :destroy_async
|
||||||
has_many :scheduled_messages, dependent: :destroy_async
|
has_many :scheduled_messages, dependent: :destroy_async
|
||||||
|
has_many :reporting_events, dependent: :nullify
|
||||||
|
|
||||||
has_one :inbox_assignment_policy, dependent: :destroy
|
has_one :inbox_assignment_policy, dependent: :destroy
|
||||||
has_one :assignment_policy, through: :inbox_assignment_policy
|
has_one :assignment_policy, through: :inbox_assignment_policy
|
||||||
|
|||||||
@ -38,6 +38,7 @@ namespace :reports do
|
|||||||
base_scope = base_scope.where(account_id: account_id) if account_id
|
base_scope = base_scope.where(account_id: account_id) if account_id
|
||||||
|
|
||||||
affected_conversation_ids = Message
|
affected_conversation_ids = Message
|
||||||
|
.unscope(:order)
|
||||||
.where(message_type: :outgoing, sender_id: nil)
|
.where(message_type: :outgoing, sender_id: nil)
|
||||||
.distinct
|
.distinct
|
||||||
.pluck(:conversation_id)
|
.pluck(:conversation_id)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user