Add users by country to analytics dashboard.
Expose by_country breakdown on GET /api/v1/analytics/dashboard from users.country. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
e957eacf80
commit
56089fa8fd
|
|
@ -108,6 +108,16 @@ WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR u.created_at >= sqlc.nar
|
||||||
GROUP BY COALESCE(NULLIF(TRIM(u.language_challange), ''), 'unknown')
|
GROUP BY COALESCE(NULLIF(TRIM(u.language_challange), ''), 'unknown')
|
||||||
ORDER BY count DESC;
|
ORDER BY count DESC;
|
||||||
|
|
||||||
|
-- name: AnalyticsUsersByCountry :many
|
||||||
|
SELECT
|
||||||
|
COALESCE(NULLIF(TRIM(u.country), ''), 'unknown')::text AS country,
|
||||||
|
COUNT(*)::bigint AS count
|
||||||
|
FROM users u
|
||||||
|
WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR u.created_at >= sqlc.narg('range_start')::timestamptz)
|
||||||
|
AND (sqlc.narg('range_end')::timestamptz IS NULL OR u.created_at < sqlc.narg('range_end')::timestamptz)
|
||||||
|
GROUP BY COALESCE(NULLIF(TRIM(u.country), ''), 'unknown')
|
||||||
|
ORDER BY count DESC;
|
||||||
|
|
||||||
-- name: AnalyticsUsersByRegion :many
|
-- name: AnalyticsUsersByRegion :many
|
||||||
SELECT
|
SELECT
|
||||||
COALESCE(u.region, 'unknown') AS region,
|
COALESCE(u.region, 'unknown') AS region,
|
||||||
|
|
|
||||||
|
|
@ -1080,6 +1080,47 @@ func (q *Queries) AnalyticsUsersByAgeGroup(ctx context.Context, arg AnalyticsUse
|
||||||
return items, nil
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const AnalyticsUsersByCountry = `-- name: AnalyticsUsersByCountry :many
|
||||||
|
SELECT
|
||||||
|
COALESCE(NULLIF(TRIM(u.country), ''), 'unknown')::text AS country,
|
||||||
|
COUNT(*)::bigint AS count
|
||||||
|
FROM users u
|
||||||
|
WHERE ($1::timestamptz IS NULL OR u.created_at >= $1::timestamptz)
|
||||||
|
AND ($2::timestamptz IS NULL OR u.created_at < $2::timestamptz)
|
||||||
|
GROUP BY COALESCE(NULLIF(TRIM(u.country), ''), 'unknown')
|
||||||
|
ORDER BY count DESC
|
||||||
|
`
|
||||||
|
|
||||||
|
type AnalyticsUsersByCountryParams struct {
|
||||||
|
RangeStart pgtype.Timestamptz `json:"range_start"`
|
||||||
|
RangeEnd pgtype.Timestamptz `json:"range_end"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AnalyticsUsersByCountryRow struct {
|
||||||
|
Country string `json:"country"`
|
||||||
|
Count int64 `json:"count"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) AnalyticsUsersByCountry(ctx context.Context, arg AnalyticsUsersByCountryParams) ([]AnalyticsUsersByCountryRow, error) {
|
||||||
|
rows, err := q.db.Query(ctx, AnalyticsUsersByCountry, arg.RangeStart, arg.RangeEnd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []AnalyticsUsersByCountryRow
|
||||||
|
for rows.Next() {
|
||||||
|
var i AnalyticsUsersByCountryRow
|
||||||
|
if err := rows.Scan(&i.Country, &i.Count); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
const AnalyticsUsersByEducationLevel = `-- name: AnalyticsUsersByEducationLevel :many
|
const AnalyticsUsersByEducationLevel = `-- name: AnalyticsUsersByEducationLevel :many
|
||||||
SELECT
|
SELECT
|
||||||
COALESCE(NULLIF(TRIM(u.education_level), ''), 'unknown')::text AS education_level,
|
COALESCE(NULLIF(TRIM(u.education_level), ''), 'unknown')::text AS education_level,
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,7 @@ type AnalyticsUsersSection struct {
|
||||||
ByLearningGoal []AnalyticsLabelCount `json:"by_learning_goal"`
|
ByLearningGoal []AnalyticsLabelCount `json:"by_learning_goal"`
|
||||||
ByLanguageChallange []AnalyticsLabelCount `json:"by_language_challange"`
|
ByLanguageChallange []AnalyticsLabelCount `json:"by_language_challange"`
|
||||||
ByKnowledgeLevel []AnalyticsLabelCount `json:"by_knowledge_level"`
|
ByKnowledgeLevel []AnalyticsLabelCount `json:"by_knowledge_level"`
|
||||||
|
ByCountry []AnalyticsLabelCount `json:"by_country"`
|
||||||
ByRegion []AnalyticsLabelCount `json:"by_region"`
|
ByRegion []AnalyticsLabelCount `json:"by_region"`
|
||||||
|
|
||||||
RegistrationsLast30Days []AnalyticsTimePoint `json:"registrations_last_30_days"`
|
RegistrationsLast30Days []AnalyticsTimePoint `json:"registrations_last_30_days"`
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,10 @@ func (h *Handler) GetAnalyticsDashboard(c *fiber.Ctx) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch users by knowledge level")
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch users by knowledge level")
|
||||||
}
|
}
|
||||||
|
usersByCountry, err := h.analyticsDB.AnalyticsUsersByCountry(ctx, p.UsersByCountry)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch users by country")
|
||||||
|
}
|
||||||
usersByRegion, err := h.analyticsDB.AnalyticsUsersByRegion(ctx, p.UsersByRegion)
|
usersByRegion, err := h.analyticsDB.AnalyticsUsersByRegion(ctx, p.UsersByRegion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch users by region")
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch users by region")
|
||||||
|
|
@ -198,7 +202,7 @@ func (h *Handler) GetAnalyticsDashboard(c *fiber.Ctx) error {
|
||||||
Users: mapUsersSection(
|
Users: mapUsersSection(
|
||||||
usersSummary, usersByRole, usersByStatus, usersByAge,
|
usersSummary, usersByRole, usersByStatus, usersByAge,
|
||||||
usersByEducation, usersByOccupation, usersByLearningGoal, usersByLanguageChallange,
|
usersByEducation, usersByOccupation, usersByLearningGoal, usersByLanguageChallange,
|
||||||
usersByKnowledge, usersByRegion, userRegs,
|
usersByKnowledge, usersByCountry, usersByRegion, userRegs,
|
||||||
),
|
),
|
||||||
Subscriptions: mapSubscriptionsSection(subsSummary, subsByStatus, revenueByPlan, newSubs30),
|
Subscriptions: mapSubscriptionsSection(subsSummary, subsByStatus, revenueByPlan, newSubs30),
|
||||||
Payments: mapPaymentsSection(paymentsSummary, paymentsByStatus, paymentsByMethod, revenue30, revenueMonthlyRows, monthlyRevenueYear),
|
Payments: mapPaymentsSection(paymentsSummary, paymentsByStatus, paymentsByMethod, revenue30, revenueMonthlyRows, monthlyRevenueYear),
|
||||||
|
|
@ -250,6 +254,7 @@ func mapUsersSection(
|
||||||
byLearningGoal []dbgen.AnalyticsUsersByLearningGoalRow,
|
byLearningGoal []dbgen.AnalyticsUsersByLearningGoalRow,
|
||||||
byLanguageChallange []dbgen.AnalyticsUsersByLanguageChallangeRow,
|
byLanguageChallange []dbgen.AnalyticsUsersByLanguageChallangeRow,
|
||||||
byKnowledge []dbgen.AnalyticsUsersByKnowledgeLevelRow,
|
byKnowledge []dbgen.AnalyticsUsersByKnowledgeLevelRow,
|
||||||
|
byCountry []dbgen.AnalyticsUsersByCountryRow,
|
||||||
byRegion []dbgen.AnalyticsUsersByRegionRow,
|
byRegion []dbgen.AnalyticsUsersByRegionRow,
|
||||||
regs []dbgen.AnalyticsUserRegistrationsLast30DaysRow,
|
regs []dbgen.AnalyticsUserRegistrationsLast30DaysRow,
|
||||||
) domain.AnalyticsUsersSection {
|
) domain.AnalyticsUsersSection {
|
||||||
|
|
@ -285,6 +290,10 @@ func mapUsersSection(
|
||||||
for i, r := range byKnowledge {
|
for i, r := range byKnowledge {
|
||||||
knowledge[i] = domain.AnalyticsLabelCount{Label: r.KnowledgeLevel, Count: r.Count}
|
knowledge[i] = domain.AnalyticsLabelCount{Label: r.KnowledgeLevel, Count: r.Count}
|
||||||
}
|
}
|
||||||
|
countries := make([]domain.AnalyticsLabelCount, len(byCountry))
|
||||||
|
for i, r := range byCountry {
|
||||||
|
countries[i] = domain.AnalyticsLabelCount{Label: r.Country, Count: r.Count}
|
||||||
|
}
|
||||||
regions := make([]domain.AnalyticsLabelCount, len(byRegion))
|
regions := make([]domain.AnalyticsLabelCount, len(byRegion))
|
||||||
for i, r := range byRegion {
|
for i, r := range byRegion {
|
||||||
regions[i] = domain.AnalyticsLabelCount{Label: r.Region, Count: r.Count}
|
regions[i] = domain.AnalyticsLabelCount{Label: r.Region, Count: r.Count}
|
||||||
|
|
@ -306,6 +315,7 @@ func mapUsersSection(
|
||||||
ByLearningGoal: learningGoals,
|
ByLearningGoal: learningGoals,
|
||||||
ByLanguageChallange: languageChallanges,
|
ByLanguageChallange: languageChallanges,
|
||||||
ByKnowledgeLevel: knowledge,
|
ByKnowledgeLevel: knowledge,
|
||||||
|
ByCountry: countries,
|
||||||
ByRegion: regions,
|
ByRegion: regions,
|
||||||
RegistrationsLast30Days: timePoints,
|
RegistrationsLast30Days: timePoints,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ type analyticsQueryParams struct {
|
||||||
UsersByLearningGoal dbgen.AnalyticsUsersByLearningGoalParams
|
UsersByLearningGoal dbgen.AnalyticsUsersByLearningGoalParams
|
||||||
UsersByLanguageChallange dbgen.AnalyticsUsersByLanguageChallangeParams
|
UsersByLanguageChallange dbgen.AnalyticsUsersByLanguageChallangeParams
|
||||||
UsersByKnowledgeLevel dbgen.AnalyticsUsersByKnowledgeLevelParams
|
UsersByKnowledgeLevel dbgen.AnalyticsUsersByKnowledgeLevelParams
|
||||||
|
UsersByCountry dbgen.AnalyticsUsersByCountryParams
|
||||||
UsersByRegion dbgen.AnalyticsUsersByRegionParams
|
UsersByRegion dbgen.AnalyticsUsersByRegionParams
|
||||||
UserRegistrationsSeries dbgen.AnalyticsUserRegistrationsLast30DaysParams
|
UserRegistrationsSeries dbgen.AnalyticsUserRegistrationsLast30DaysParams
|
||||||
|
|
||||||
|
|
@ -68,6 +69,7 @@ func newAnalyticsQueryParams(f domain.AnalyticsDateFilter) analyticsQueryParams
|
||||||
UsersByLearningGoal: dbgen.AnalyticsUsersByLearningGoalParams{RangeStart: rs, RangeEnd: re},
|
UsersByLearningGoal: dbgen.AnalyticsUsersByLearningGoalParams{RangeStart: rs, RangeEnd: re},
|
||||||
UsersByLanguageChallange: dbgen.AnalyticsUsersByLanguageChallangeParams{RangeStart: rs, RangeEnd: re},
|
UsersByLanguageChallange: dbgen.AnalyticsUsersByLanguageChallangeParams{RangeStart: rs, RangeEnd: re},
|
||||||
UsersByKnowledgeLevel: dbgen.AnalyticsUsersByKnowledgeLevelParams{RangeStart: rs, RangeEnd: re},
|
UsersByKnowledgeLevel: dbgen.AnalyticsUsersByKnowledgeLevelParams{RangeStart: rs, RangeEnd: re},
|
||||||
|
UsersByCountry: dbgen.AnalyticsUsersByCountryParams{RangeStart: rs, RangeEnd: re},
|
||||||
UsersByRegion: dbgen.AnalyticsUsersByRegionParams{RangeStart: rs, RangeEnd: re},
|
UsersByRegion: dbgen.AnalyticsUsersByRegionParams{RangeStart: rs, RangeEnd: re},
|
||||||
UserRegistrationsSeries: series,
|
UserRegistrationsSeries: series,
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user