fix(retention): cohort endpoint 500 — Pundit policy + SQL binding
Dois bugs que faziam o cohort retornar 500 e a página de Retenção mostrar "Falha ao carregar cohort": 1. `Captain::AssistantPolicy` não tinha `cohort?` → Pundit batia em NoMethodError no `check_authorization`. Adicionado como leitura pública da assistente, igual `show?`/`playground?`. 2. `RetentionCohortService#cohort_activity` chamava `exec_query(sql, name, [@account.id])` passando array de valores onde a API espera bind objects. A SQL ainda interpolava `account_id = $1` direto na string (sem placeholder ligado). Migrado pra `ActiveRecord::Base.sanitize_sql_array` com `?`, igual ao resto da base. Mantém parametrização e remove acoplamento com posicional. Validado em prod via hot-patch (USR2): GET retention/cohort agora 200 com 3 cohorts. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d831ee4d33
commit
21f5fcce6a
@ -34,4 +34,8 @@ class Captain::AssistantPolicy < ApplicationPolicy
|
|||||||
def revenue?
|
def revenue?
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def cohort?
|
||||||
|
true
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -74,7 +74,7 @@ class Captain::Reports::RetentionCohortService
|
|||||||
end_month = Time.current.beginning_of_month
|
end_month = Time.current.beginning_of_month
|
||||||
|
|
||||||
sql = activity_sql(start_month, end_month)
|
sql = activity_sql(start_month, end_month)
|
||||||
rows = ActiveRecord::Base.connection.exec_query(sql, 'RetentionCohort', [@account.id])
|
rows = ActiveRecord::Base.connection.exec_query(sql, 'RetentionCohort')
|
||||||
|
|
||||||
hash = Hash.new { |h, k| h[k] = {} }
|
hash = Hash.new { |h, k| h[k] = {} }
|
||||||
|
|
||||||
@ -89,37 +89,45 @@ class Captain::Reports::RetentionCohortService
|
|||||||
|
|
||||||
# rubocop:disable Metrics/MethodLength
|
# rubocop:disable Metrics/MethodLength
|
||||||
def activity_sql(start_month, end_month)
|
def activity_sql(start_month, end_month)
|
||||||
<<~SQL.squish
|
ActiveRecord::Base.sanitize_sql_array([
|
||||||
WITH contact_cohorts AS (
|
<<~SQL.squish,
|
||||||
SELECT id AS contact_id,
|
WITH contact_cohorts AS (
|
||||||
DATE_TRUNC('month', first_interaction_at) AS cohort_month
|
SELECT id AS contact_id,
|
||||||
FROM contacts
|
DATE_TRUNC('month', first_interaction_at) AS cohort_month
|
||||||
WHERE first_interaction_at IS NOT NULL
|
FROM contacts
|
||||||
AND first_interaction_at >= '#{start_month.iso8601}'
|
WHERE first_interaction_at IS NOT NULL
|
||||||
AND account_id = $1
|
AND first_interaction_at >= ?
|
||||||
),
|
AND account_id = ?
|
||||||
contact_activity AS (
|
),
|
||||||
SELECT DISTINCT
|
contact_activity AS (
|
||||||
conversations.contact_id,
|
SELECT DISTINCT
|
||||||
DATE_TRUNC('month', messages.created_at) AS activity_month
|
conversations.contact_id,
|
||||||
FROM messages
|
DATE_TRUNC('month', messages.created_at) AS activity_month
|
||||||
INNER JOIN conversations ON conversations.id = messages.conversation_id
|
FROM messages
|
||||||
WHERE messages.sender_type = 'Contact'
|
INNER JOIN conversations ON conversations.id = messages.conversation_id
|
||||||
AND messages.private = false
|
WHERE messages.sender_type = 'Contact'
|
||||||
AND (messages.status IS NULL OR messages.status <> #{failed_status_value})
|
AND messages.private = false
|
||||||
AND conversations.account_id = $1
|
AND (messages.status IS NULL OR messages.status <> ?)
|
||||||
AND messages.created_at >= '#{start_month.iso8601}'
|
AND conversations.account_id = ?
|
||||||
AND messages.created_at < '#{(end_month + 1.month).iso8601}'
|
AND messages.created_at >= ?
|
||||||
)
|
AND messages.created_at < ?
|
||||||
SELECT c.cohort_month,
|
)
|
||||||
a.activity_month,
|
SELECT c.cohort_month,
|
||||||
COUNT(DISTINCT c.contact_id) AS active_contacts
|
a.activity_month,
|
||||||
FROM contact_cohorts c
|
COUNT(DISTINCT c.contact_id) AS active_contacts
|
||||||
INNER JOIN contact_activity a ON a.contact_id = c.contact_id
|
FROM contact_cohorts c
|
||||||
WHERE a.activity_month >= c.cohort_month
|
INNER JOIN contact_activity a ON a.contact_id = c.contact_id
|
||||||
GROUP BY c.cohort_month, a.activity_month
|
WHERE a.activity_month >= c.cohort_month
|
||||||
ORDER BY c.cohort_month, a.activity_month
|
GROUP BY c.cohort_month, a.activity_month
|
||||||
SQL
|
ORDER BY c.cohort_month, a.activity_month
|
||||||
|
SQL
|
||||||
|
start_month,
|
||||||
|
@account.id,
|
||||||
|
failed_status_value,
|
||||||
|
@account.id,
|
||||||
|
start_month,
|
||||||
|
end_month + 1.month
|
||||||
|
])
|
||||||
end
|
end
|
||||||
# rubocop:enable Metrics/MethodLength
|
# rubocop:enable Metrics/MethodLength
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user