Fix analytics dashboard course counts for LMS and exam_prep hierarchies.
Replace stub AnalyticsCourseCounts query and expose lms / exam_prep inventory in the courses section. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
7f8ef3373c
commit
a1696bf1e0
|
|
@ -209,13 +209,34 @@ ORDER BY d.date;
|
||||||
-- =====================
|
-- =====================
|
||||||
-- Course Analytics
|
-- Course Analytics
|
||||||
-- =====================
|
-- =====================
|
||||||
|
-- LMS: programs -> courses -> modules -> lessons; lms_practices attach to course, module, or lesson.
|
||||||
|
-- Exam prep: exam_prep.catalog_courses -> units -> unit_modules -> unit_module_lessons; exam_prep.lesson_practices.
|
||||||
|
-- Legacy dashboard fields map to: programs, courses, modules, and video-bearing lessons (LMS + exam prep).
|
||||||
|
|
||||||
-- name: AnalyticsCourseCounts :one
|
-- name: AnalyticsCourseCounts :one
|
||||||
SELECT
|
SELECT
|
||||||
0::bigint AS total_categories,
|
(SELECT COUNT(*)::bigint FROM programs) AS total_categories,
|
||||||
0::bigint AS total_courses,
|
(SELECT COUNT(*)::bigint FROM courses) AS total_courses,
|
||||||
0::bigint AS total_sub_courses,
|
(SELECT COUNT(*)::bigint FROM modules) AS total_sub_courses,
|
||||||
0::bigint AS total_videos;
|
(
|
||||||
|
COALESCE((SELECT COUNT(*)::bigint FROM lessons WHERE NULLIF(BTRIM(video_url), '') IS NOT NULL), 0::bigint)
|
||||||
|
+ COALESCE((SELECT COUNT(*)::bigint FROM exam_prep.unit_module_lessons WHERE NULLIF(BTRIM(video_url), '') IS NOT NULL), 0::bigint)
|
||||||
|
)::bigint AS total_videos,
|
||||||
|
(SELECT COUNT(*)::bigint FROM programs) AS lms_programs,
|
||||||
|
(SELECT COUNT(*)::bigint FROM courses) AS lms_courses,
|
||||||
|
(SELECT COUNT(*)::bigint FROM modules) AS lms_modules,
|
||||||
|
(SELECT COUNT(*)::bigint FROM lessons) AS lms_lessons,
|
||||||
|
(SELECT COUNT(*)::bigint FROM lessons WHERE NULLIF(BTRIM(video_url), '') IS NOT NULL) AS lms_lessons_with_video,
|
||||||
|
(SELECT COUNT(*)::bigint FROM lms_practices) AS lms_practices,
|
||||||
|
(SELECT COUNT(*)::bigint FROM lms_practices WHERE course_id IS NOT NULL) AS lms_practices_at_course,
|
||||||
|
(SELECT COUNT(*)::bigint FROM lms_practices WHERE module_id IS NOT NULL) AS lms_practices_at_module,
|
||||||
|
(SELECT COUNT(*)::bigint FROM lms_practices WHERE lesson_id IS NOT NULL) AS lms_practices_at_lesson,
|
||||||
|
(SELECT COUNT(*)::bigint FROM exam_prep.catalog_courses) AS exam_prep_catalog_courses,
|
||||||
|
(SELECT COUNT(*)::bigint FROM exam_prep.units) AS exam_prep_units,
|
||||||
|
(SELECT COUNT(*)::bigint FROM exam_prep.unit_modules) AS exam_prep_unit_modules,
|
||||||
|
(SELECT COUNT(*)::bigint FROM exam_prep.unit_module_lessons) AS exam_prep_lessons,
|
||||||
|
(SELECT COUNT(*)::bigint FROM exam_prep.unit_module_lessons WHERE NULLIF(BTRIM(video_url), '') IS NOT NULL) AS exam_prep_lessons_with_video,
|
||||||
|
(SELECT COUNT(*)::bigint FROM exam_prep.lesson_practices) AS exam_prep_lesson_practices;
|
||||||
|
|
||||||
-- =====================
|
-- =====================
|
||||||
-- Content Analytics
|
-- Content Analytics
|
||||||
|
|
|
||||||
62
docs/docs.go
62
docs/docs.go
|
|
@ -9638,7 +9638,14 @@ const docTemplate = `{
|
||||||
"domain.AnalyticsCoursesSection": {
|
"domain.AnalyticsCoursesSection": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"exam_prep": {
|
||||||
|
"$ref": "#/definitions/domain.AnalyticsExamPrepContentCounts"
|
||||||
|
},
|
||||||
|
"lms": {
|
||||||
|
"$ref": "#/definitions/domain.AnalyticsLMSContentCounts"
|
||||||
|
},
|
||||||
"total_categories": {
|
"total_categories": {
|
||||||
|
"description": "Top-level keys preserved for existing clients: map to LMS programs, courses, modules, and all video lessons (LMS + exam prep).",
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"total_courses": {
|
"total_courses": {
|
||||||
|
|
@ -9722,6 +9729,29 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"domain.AnalyticsExamPrepContentCounts": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"catalog_courses": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"lesson_practices": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"lessons": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"lessons_with_video": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"unit_modules": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"units": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"domain.AnalyticsIssuesSection": {
|
"domain.AnalyticsIssuesSection": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
@ -9748,6 +9778,38 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"domain.AnalyticsLMSContentCounts": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"courses": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"lessons": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"lessons_with_video": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"modules": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"practices": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"practices_at_course": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"practices_at_lesson": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"practices_at_module": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"programs": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"domain.AnalyticsLabelAmount": {
|
"domain.AnalyticsLabelAmount": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
|
||||||
|
|
@ -9630,7 +9630,14 @@
|
||||||
"domain.AnalyticsCoursesSection": {
|
"domain.AnalyticsCoursesSection": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"exam_prep": {
|
||||||
|
"$ref": "#/definitions/domain.AnalyticsExamPrepContentCounts"
|
||||||
|
},
|
||||||
|
"lms": {
|
||||||
|
"$ref": "#/definitions/domain.AnalyticsLMSContentCounts"
|
||||||
|
},
|
||||||
"total_categories": {
|
"total_categories": {
|
||||||
|
"description": "Top-level keys preserved for existing clients: map to LMS programs, courses, modules, and all video lessons (LMS + exam prep).",
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"total_courses": {
|
"total_courses": {
|
||||||
|
|
@ -9714,6 +9721,29 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"domain.AnalyticsExamPrepContentCounts": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"catalog_courses": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"lesson_practices": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"lessons": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"lessons_with_video": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"unit_modules": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"units": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"domain.AnalyticsIssuesSection": {
|
"domain.AnalyticsIssuesSection": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
@ -9740,6 +9770,38 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"domain.AnalyticsLMSContentCounts": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"courses": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"lessons": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"lessons_with_video": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"modules": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"practices": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"practices_at_course": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"practices_at_lesson": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"practices_at_module": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"programs": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"domain.AnalyticsLabelAmount": {
|
"domain.AnalyticsLabelAmount": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,13 @@ definitions:
|
||||||
type: object
|
type: object
|
||||||
domain.AnalyticsCoursesSection:
|
domain.AnalyticsCoursesSection:
|
||||||
properties:
|
properties:
|
||||||
|
exam_prep:
|
||||||
|
$ref: '#/definitions/domain.AnalyticsExamPrepContentCounts'
|
||||||
|
lms:
|
||||||
|
$ref: '#/definitions/domain.AnalyticsLMSContentCounts'
|
||||||
total_categories:
|
total_categories:
|
||||||
|
description: 'Top-level keys preserved for existing clients: map to LMS programs,
|
||||||
|
courses, modules, and all video lessons (LMS + exam prep).'
|
||||||
type: integer
|
type: integer
|
||||||
total_courses:
|
total_courses:
|
||||||
type: integer
|
type: integer
|
||||||
|
|
@ -89,6 +95,21 @@ definitions:
|
||||||
year:
|
year:
|
||||||
type: integer
|
type: integer
|
||||||
type: object
|
type: object
|
||||||
|
domain.AnalyticsExamPrepContentCounts:
|
||||||
|
properties:
|
||||||
|
catalog_courses:
|
||||||
|
type: integer
|
||||||
|
lesson_practices:
|
||||||
|
type: integer
|
||||||
|
lessons:
|
||||||
|
type: integer
|
||||||
|
lessons_with_video:
|
||||||
|
type: integer
|
||||||
|
unit_modules:
|
||||||
|
type: integer
|
||||||
|
units:
|
||||||
|
type: integer
|
||||||
|
type: object
|
||||||
domain.AnalyticsIssuesSection:
|
domain.AnalyticsIssuesSection:
|
||||||
properties:
|
properties:
|
||||||
by_status:
|
by_status:
|
||||||
|
|
@ -106,6 +127,27 @@ definitions:
|
||||||
total_issues:
|
total_issues:
|
||||||
type: integer
|
type: integer
|
||||||
type: object
|
type: object
|
||||||
|
domain.AnalyticsLMSContentCounts:
|
||||||
|
properties:
|
||||||
|
courses:
|
||||||
|
type: integer
|
||||||
|
lessons:
|
||||||
|
type: integer
|
||||||
|
lessons_with_video:
|
||||||
|
type: integer
|
||||||
|
modules:
|
||||||
|
type: integer
|
||||||
|
practices:
|
||||||
|
type: integer
|
||||||
|
practices_at_course:
|
||||||
|
type: integer
|
||||||
|
practices_at_lesson:
|
||||||
|
type: integer
|
||||||
|
practices_at_module:
|
||||||
|
type: integer
|
||||||
|
programs:
|
||||||
|
type: integer
|
||||||
|
type: object
|
||||||
domain.AnalyticsLabelAmount:
|
domain.AnalyticsLabelAmount:
|
||||||
properties:
|
properties:
|
||||||
amount:
|
amount:
|
||||||
|
|
|
||||||
|
|
@ -14,22 +14,58 @@ import (
|
||||||
const AnalyticsCourseCounts = `-- name: AnalyticsCourseCounts :one
|
const AnalyticsCourseCounts = `-- name: AnalyticsCourseCounts :one
|
||||||
|
|
||||||
SELECT
|
SELECT
|
||||||
0::bigint AS total_categories,
|
(SELECT COUNT(*)::bigint FROM programs) AS total_categories,
|
||||||
0::bigint AS total_courses,
|
(SELECT COUNT(*)::bigint FROM courses) AS total_courses,
|
||||||
0::bigint AS total_sub_courses,
|
(SELECT COUNT(*)::bigint FROM modules) AS total_sub_courses,
|
||||||
0::bigint AS total_videos
|
(
|
||||||
|
COALESCE((SELECT COUNT(*)::bigint FROM lessons WHERE NULLIF(BTRIM(video_url), '') IS NOT NULL), 0::bigint)
|
||||||
|
+ COALESCE((SELECT COUNT(*)::bigint FROM exam_prep.unit_module_lessons WHERE NULLIF(BTRIM(video_url), '') IS NOT NULL), 0::bigint)
|
||||||
|
)::bigint AS total_videos,
|
||||||
|
(SELECT COUNT(*)::bigint FROM programs) AS lms_programs,
|
||||||
|
(SELECT COUNT(*)::bigint FROM courses) AS lms_courses,
|
||||||
|
(SELECT COUNT(*)::bigint FROM modules) AS lms_modules,
|
||||||
|
(SELECT COUNT(*)::bigint FROM lessons) AS lms_lessons,
|
||||||
|
(SELECT COUNT(*)::bigint FROM lessons WHERE NULLIF(BTRIM(video_url), '') IS NOT NULL) AS lms_lessons_with_video,
|
||||||
|
(SELECT COUNT(*)::bigint FROM lms_practices) AS lms_practices,
|
||||||
|
(SELECT COUNT(*)::bigint FROM lms_practices WHERE course_id IS NOT NULL) AS lms_practices_at_course,
|
||||||
|
(SELECT COUNT(*)::bigint FROM lms_practices WHERE module_id IS NOT NULL) AS lms_practices_at_module,
|
||||||
|
(SELECT COUNT(*)::bigint FROM lms_practices WHERE lesson_id IS NOT NULL) AS lms_practices_at_lesson,
|
||||||
|
(SELECT COUNT(*)::bigint FROM exam_prep.catalog_courses) AS exam_prep_catalog_courses,
|
||||||
|
(SELECT COUNT(*)::bigint FROM exam_prep.units) AS exam_prep_units,
|
||||||
|
(SELECT COUNT(*)::bigint FROM exam_prep.unit_modules) AS exam_prep_unit_modules,
|
||||||
|
(SELECT COUNT(*)::bigint FROM exam_prep.unit_module_lessons) AS exam_prep_lessons,
|
||||||
|
(SELECT COUNT(*)::bigint FROM exam_prep.unit_module_lessons WHERE NULLIF(BTRIM(video_url), '') IS NOT NULL) AS exam_prep_lessons_with_video,
|
||||||
|
(SELECT COUNT(*)::bigint FROM exam_prep.lesson_practices) AS exam_prep_lesson_practices
|
||||||
`
|
`
|
||||||
|
|
||||||
type AnalyticsCourseCountsRow struct {
|
type AnalyticsCourseCountsRow struct {
|
||||||
TotalCategories int64 `json:"total_categories"`
|
TotalCategories int64 `json:"total_categories"`
|
||||||
TotalCourses int64 `json:"total_courses"`
|
TotalCourses int64 `json:"total_courses"`
|
||||||
TotalSubCourses int64 `json:"total_sub_courses"`
|
TotalSubCourses int64 `json:"total_sub_courses"`
|
||||||
TotalVideos int64 `json:"total_videos"`
|
TotalVideos int64 `json:"total_videos"`
|
||||||
|
LmsPrograms int64 `json:"lms_programs"`
|
||||||
|
LmsCourses int64 `json:"lms_courses"`
|
||||||
|
LmsModules int64 `json:"lms_modules"`
|
||||||
|
LmsLessons int64 `json:"lms_lessons"`
|
||||||
|
LmsLessonsWithVideo int64 `json:"lms_lessons_with_video"`
|
||||||
|
LmsPractices int64 `json:"lms_practices"`
|
||||||
|
LmsPracticesAtCourse int64 `json:"lms_practices_at_course"`
|
||||||
|
LmsPracticesAtModule int64 `json:"lms_practices_at_module"`
|
||||||
|
LmsPracticesAtLesson int64 `json:"lms_practices_at_lesson"`
|
||||||
|
ExamPrepCatalogCourses int64 `json:"exam_prep_catalog_courses"`
|
||||||
|
ExamPrepUnits int64 `json:"exam_prep_units"`
|
||||||
|
ExamPrepUnitModules int64 `json:"exam_prep_unit_modules"`
|
||||||
|
ExamPrepLessons int64 `json:"exam_prep_lessons"`
|
||||||
|
ExamPrepLessonsWithVideo int64 `json:"exam_prep_lessons_with_video"`
|
||||||
|
ExamPrepLessonPractices int64 `json:"exam_prep_lesson_practices"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// =====================
|
// =====================
|
||||||
// Course Analytics
|
// Course Analytics
|
||||||
// =====================
|
// =====================
|
||||||
|
// LMS: programs -> courses -> modules -> lessons; lms_practices attach to course, module, or lesson.
|
||||||
|
// Exam prep: exam_prep.catalog_courses -> units -> unit_modules -> unit_module_lessons; exam_prep.lesson_practices.
|
||||||
|
// Legacy dashboard fields map to: programs, courses, modules, and video-bearing lessons (LMS + exam prep).
|
||||||
func (q *Queries) AnalyticsCourseCounts(ctx context.Context) (AnalyticsCourseCountsRow, error) {
|
func (q *Queries) AnalyticsCourseCounts(ctx context.Context) (AnalyticsCourseCountsRow, error) {
|
||||||
row := q.db.QueryRow(ctx, AnalyticsCourseCounts)
|
row := q.db.QueryRow(ctx, AnalyticsCourseCounts)
|
||||||
var i AnalyticsCourseCountsRow
|
var i AnalyticsCourseCountsRow
|
||||||
|
|
@ -38,6 +74,21 @@ func (q *Queries) AnalyticsCourseCounts(ctx context.Context) (AnalyticsCourseCou
|
||||||
&i.TotalCourses,
|
&i.TotalCourses,
|
||||||
&i.TotalSubCourses,
|
&i.TotalSubCourses,
|
||||||
&i.TotalVideos,
|
&i.TotalVideos,
|
||||||
|
&i.LmsPrograms,
|
||||||
|
&i.LmsCourses,
|
||||||
|
&i.LmsModules,
|
||||||
|
&i.LmsLessons,
|
||||||
|
&i.LmsLessonsWithVideo,
|
||||||
|
&i.LmsPractices,
|
||||||
|
&i.LmsPracticesAtCourse,
|
||||||
|
&i.LmsPracticesAtModule,
|
||||||
|
&i.LmsPracticesAtLesson,
|
||||||
|
&i.ExamPrepCatalogCourses,
|
||||||
|
&i.ExamPrepUnits,
|
||||||
|
&i.ExamPrepUnitModules,
|
||||||
|
&i.ExamPrepLessons,
|
||||||
|
&i.ExamPrepLessonsWithVideo,
|
||||||
|
&i.ExamPrepLessonPractices,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -69,11 +69,38 @@ type AnalyticsPaymentsSection struct {
|
||||||
RevenueLast30Days []AnalyticsRevenueTimePoint `json:"revenue_last_30_days"`
|
RevenueLast30Days []AnalyticsRevenueTimePoint `json:"revenue_last_30_days"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AnalyticsLMSContentCounts reflects the LMS hierarchy (Learn English): programs → courses → modules → lessons.
|
||||||
|
type AnalyticsLMSContentCounts struct {
|
||||||
|
Programs int64 `json:"programs"`
|
||||||
|
Courses int64 `json:"courses"`
|
||||||
|
Modules int64 `json:"modules"`
|
||||||
|
Lessons int64 `json:"lessons"`
|
||||||
|
LessonsWithVideo int64 `json:"lessons_with_video"`
|
||||||
|
Practices int64 `json:"practices"`
|
||||||
|
PracticesAtCourse int64 `json:"practices_at_course"`
|
||||||
|
PracticesAtModule int64 `json:"practices_at_module"`
|
||||||
|
PracticesAtLesson int64 `json:"practices_at_lesson"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnalyticsExamPrepContentCounts reflects the exam_prep schema: catalog_courses → units → unit_modules → lessons → lesson_practices.
|
||||||
|
type AnalyticsExamPrepContentCounts struct {
|
||||||
|
CatalogCourses int64 `json:"catalog_courses"`
|
||||||
|
Units int64 `json:"units"`
|
||||||
|
UnitModules int64 `json:"unit_modules"`
|
||||||
|
Lessons int64 `json:"lessons"`
|
||||||
|
LessonsWithVideo int64 `json:"lessons_with_video"`
|
||||||
|
LessonPractices int64 `json:"lesson_practices"`
|
||||||
|
}
|
||||||
|
|
||||||
type AnalyticsCoursesSection struct {
|
type AnalyticsCoursesSection struct {
|
||||||
|
// Top-level keys preserved for existing clients: map to LMS programs, courses, modules, and all video lessons (LMS + exam prep).
|
||||||
TotalCategories int64 `json:"total_categories"`
|
TotalCategories int64 `json:"total_categories"`
|
||||||
TotalCourses int64 `json:"total_courses"`
|
TotalCourses int64 `json:"total_courses"`
|
||||||
TotalSubCourses int64 `json:"total_sub_courses"`
|
TotalSubCourses int64 `json:"total_sub_courses"`
|
||||||
TotalVideos int64 `json:"total_videos"`
|
TotalVideos int64 `json:"total_videos"`
|
||||||
|
|
||||||
|
LMS AnalyticsLMSContentCounts `json:"lms"`
|
||||||
|
ExamPrep AnalyticsExamPrepContentCounts `json:"exam_prep"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AnalyticsContentSection struct {
|
type AnalyticsContentSection struct {
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ func toTime(v interface{}) time.Time {
|
||||||
|
|
||||||
// GetAnalyticsDashboard godoc
|
// GetAnalyticsDashboard godoc
|
||||||
// @Summary Analytics dashboard
|
// @Summary Analytics dashboard
|
||||||
// @Description Platform analytics with optional date filters: all-time (default), year, year+month, or custom from/to range.
|
// @Description Platform analytics with optional date filters: all-time (default), year, year+month, or custom from/to range. The courses section includes LMS (programs→courses→modules→lessons, lms_practices) and exam_prep (catalog_courses→units→unit_modules→lessons, lesson_practices) inventory counts.
|
||||||
// @Tags analytics
|
// @Tags analytics
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param year query int false "Calendar year (e.g. 2025)"
|
// @Param year query int false "Calendar year (e.g. 2025)"
|
||||||
|
|
@ -166,12 +166,7 @@ func (h *Handler) GetAnalyticsDashboard(c *fiber.Ctx) error {
|
||||||
Users: mapUsersSection(usersSummary, usersByRole, usersByStatus, usersByAge, usersByKnowledge, usersByRegion, userRegs),
|
Users: mapUsersSection(usersSummary, usersByRole, usersByStatus, usersByAge, usersByKnowledge, usersByRegion, userRegs),
|
||||||
Subscriptions: mapSubscriptionsSection(subsSummary, subsByStatus, revenueByPlan, newSubs30),
|
Subscriptions: mapSubscriptionsSection(subsSummary, subsByStatus, revenueByPlan, newSubs30),
|
||||||
Payments: mapPaymentsSection(paymentsSummary, paymentsByStatus, paymentsByMethod, revenue30),
|
Payments: mapPaymentsSection(paymentsSummary, paymentsByStatus, paymentsByMethod, revenue30),
|
||||||
Courses: domain.AnalyticsCoursesSection{
|
Courses: mapCoursesSection(courseCounts),
|
||||||
TotalCategories: courseCounts.TotalCategories,
|
|
||||||
TotalCourses: courseCounts.TotalCourses,
|
|
||||||
TotalSubCourses: courseCounts.TotalSubCourses,
|
|
||||||
TotalVideos: courseCounts.TotalVideos,
|
|
||||||
},
|
|
||||||
Content: mapContentSection(questionsCounts, questionsByType, questionSetsByType),
|
Content: mapContentSection(questionsCounts, questionsByType, questionSetsByType),
|
||||||
Notifications: mapNotificationsSection(notifSummary, notifByChannel, notifByType),
|
Notifications: mapNotificationsSection(notifSummary, notifByChannel, notifByType),
|
||||||
Issues: mapIssuesSection(issuesSummary, issuesByStatus, issuesByType),
|
Issues: mapIssuesSection(issuesSummary, issuesByStatus, issuesByType),
|
||||||
|
|
@ -181,6 +176,34 @@ func (h *Handler) GetAnalyticsDashboard(c *fiber.Ctx) error {
|
||||||
return c.JSON(dashboard)
|
return c.JSON(dashboard)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func mapCoursesSection(r dbgen.AnalyticsCourseCountsRow) domain.AnalyticsCoursesSection {
|
||||||
|
return domain.AnalyticsCoursesSection{
|
||||||
|
TotalCategories: r.TotalCategories,
|
||||||
|
TotalCourses: r.TotalCourses,
|
||||||
|
TotalSubCourses: r.TotalSubCourses,
|
||||||
|
TotalVideos: r.TotalVideos,
|
||||||
|
LMS: domain.AnalyticsLMSContentCounts{
|
||||||
|
Programs: r.LmsPrograms,
|
||||||
|
Courses: r.LmsCourses,
|
||||||
|
Modules: r.LmsModules,
|
||||||
|
Lessons: r.LmsLessons,
|
||||||
|
LessonsWithVideo: r.LmsLessonsWithVideo,
|
||||||
|
Practices: r.LmsPractices,
|
||||||
|
PracticesAtCourse: r.LmsPracticesAtCourse,
|
||||||
|
PracticesAtModule: r.LmsPracticesAtModule,
|
||||||
|
PracticesAtLesson: r.LmsPracticesAtLesson,
|
||||||
|
},
|
||||||
|
ExamPrep: domain.AnalyticsExamPrepContentCounts{
|
||||||
|
CatalogCourses: r.ExamPrepCatalogCourses,
|
||||||
|
Units: r.ExamPrepUnits,
|
||||||
|
UnitModules: r.ExamPrepUnitModules,
|
||||||
|
Lessons: r.ExamPrepLessons,
|
||||||
|
LessonsWithVideo: r.ExamPrepLessonsWithVideo,
|
||||||
|
LessonPractices: r.ExamPrepLessonPractices,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func mapUsersSection(
|
func mapUsersSection(
|
||||||
summary dbgen.AnalyticsUsersSummaryRow,
|
summary dbgen.AnalyticsUsersSummaryRow,
|
||||||
byRole []dbgen.AnalyticsUsersByRoleRow,
|
byRole []dbgen.AnalyticsUsersByRoleRow,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user