UNPKG

@memberjunction/actions-bizapps-lms

Version:

LMS system integration actions for MemberJunction

339 lines 16.3 kB
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; import { RegisterClass } from '@memberjunction/global'; import { LearnWorldsBaseAction } from '../learnworlds-base.action.js'; import { BaseAction } from '@memberjunction/actions'; /** * Action to retrieve comprehensive analytics for a LearnWorlds course */ let GetCourseAnalyticsAction = class GetCourseAnalyticsAction extends LearnWorldsBaseAction { // ---------------------------------------------------------------- // Typed public method – can be called directly from code // ---------------------------------------------------------------- /** * Get course performance analytics. * Throws on any error. */ async GetCourseAnalytics(params, contextUser) { this.SetCompanyContext(params.CompanyID); const { CourseID: courseId, DateFrom: dateFrom, DateTo: dateTo, IncludeUserBreakdown: includeUserBreakdown = false, IncludeModuleStats: includeModuleStatsRaw, IncludeRevenue: includeRevenueRaw, } = params; const includeModuleStats = includeModuleStatsRaw !== false; const includeRevenue = includeRevenueRaw !== false; if (!courseId) { throw new Error('CourseID is required'); } this.validatePathSegment(courseId, 'CourseID'); // Build date query string const queryString = this.buildDateQueryString(dateFrom, dateTo); // Fetch core analytics const analyticsData = await this.fetchCoreAnalytics(courseId, queryString, contextUser); // Build the typed analytics object const analytics = this.buildBaseAnalytics(courseId, dateFrom, dateTo, analyticsData); // Optionally fetch revenue data if (includeRevenue) { analytics.revenue = await this.fetchRevenueData(courseId, queryString, contextUser); } // Optionally fetch module stats if (includeModuleStats) { analytics.moduleStats = await this.fetchModuleStats(courseId, contextUser); } // Optionally fetch user breakdown if (includeUserBreakdown) { analytics.userBreakdown = await this.fetchUserBreakdown(courseId, contextUser); } // Build summary const summary = this.buildAnalyticsSummary(courseId, analytics); return { CourseAnalytics: analytics, Summary: summary, }; } // ---------------------------------------------------------------- // Framework wrapper – thin delegation to the public method // ---------------------------------------------------------------- async InternalRunAction(params) { const { Params, ContextUser } = params; this.params = Params; try { const typedParams = this.extractCourseAnalyticsParams(Params); const result = await this.GetCourseAnalytics(typedParams, ContextUser); this.setOutputParam(Params, 'CourseAnalytics', result.CourseAnalytics); this.setOutputParam(Params, 'Summary', result.Summary); return this.buildSuccessResult('Course analytics retrieved successfully', Params); } catch (error) { const msg = error instanceof Error ? error.message : 'Unknown error'; return this.buildErrorResult('ERROR', `Error retrieving course analytics: ${msg}`, Params); } } // ---------------------------------------------------------------- // Private helpers – parameter extraction // ---------------------------------------------------------------- extractCourseAnalyticsParams(params) { return { CompanyID: this.getRequiredStringParam(params, 'CompanyID'), CourseID: this.getRequiredStringParam(params, 'CourseID'), DateFrom: this.getOptionalStringParam(params, 'DateFrom'), DateTo: this.getOptionalStringParam(params, 'DateTo'), IncludeUserBreakdown: this.getOptionalBooleanParam(params, 'IncludeUserBreakdown', undefined), IncludeModuleStats: this.getOptionalBooleanParam(params, 'IncludeModuleStats', undefined), IncludeRevenue: this.getOptionalBooleanParam(params, 'IncludeRevenue', undefined), }; } // ---------------------------------------------------------------- // Private helpers – API calls // ---------------------------------------------------------------- buildDateQueryString(dateFrom, dateTo) { const queryParams = {}; const parsedFrom = this.safeParseDateToISO(dateFrom); if (parsedFrom) { queryParams.date_from = parsedFrom.split('T')[0]; } const parsedTo = this.safeParseDateToISO(dateTo); if (parsedTo) { queryParams.date_to = parsedTo.split('T')[0]; } const keys = Object.keys(queryParams); if (keys.length === 0) return ''; return '?' + new URLSearchParams(queryParams).toString(); } async fetchCoreAnalytics(courseId, queryString, contextUser) { const response = await this.makeLearnWorldsRequest(`/courses/${courseId}/analytics${queryString}`, 'GET', null, contextUser); if (response.success === false) { throw new Error(response.message || 'Failed to retrieve analytics'); } return response.data || {}; } async fetchRevenueData(courseId, queryString, contextUser) { const response = await this.makeLearnWorldsRequest(`/courses/${courseId}/revenue${queryString}`, 'GET', null, contextUser); if (response.success !== false && response.data) { return { totalRevenue: response.data.total_revenue || 0, currency: response.data.currency || 'USD', averageOrderValue: response.data.average_order_value || 0, totalOrders: response.data.total_orders || 0, revenueTrend: response.data.revenue_trend || [], topMarkets: response.data.top_markets || [], }; } return undefined; } async fetchModuleStats(courseId, contextUser) { const response = await this.makeLearnWorldsRequest(`/courses/${courseId}/modules/analytics`, 'GET', null, contextUser); if (response.success !== false && response.data) { const rawModules = Array.isArray(response.data) ? response.data : response.data.data; if (rawModules) { return this.formatModuleStats(rawModules); } } return undefined; } async fetchUserBreakdown(courseId, contextUser) { const enrollmentQs = '?' + new URLSearchParams({ limit: '1000', include: 'progress' }).toString(); const response = await this.makeLearnWorldsRequest(`/courses/${courseId}/enrollments${enrollmentQs}`, 'GET', null, contextUser); if (response.success !== false && response.data) { const enrollments = Array.isArray(response.data) ? response.data : response.data.data; if (enrollments) { return this.calculateUserBreakdown(enrollments); } } return undefined; } // ---------------------------------------------------------------- // Private helpers – data building // ---------------------------------------------------------------- buildBaseAnalytics(courseId, dateFrom, dateTo, data) { return { courseId, period: { from: dateFrom || 'all-time', to: dateTo || 'current', }, enrollment: { totalEnrollments: data.total_enrollments || 0, newEnrollments: data.new_enrollments || 0, activeStudents: data.active_students || 0, enrollmentTrend: data.enrollment_trend || [], }, progress: { averageProgressPercentage: data.average_progress || 0, completionRate: data.completion_rate || 0, totalCompletions: data.total_completions || 0, inProgressCount: data.in_progress_count || 0, notStartedCount: data.not_started_count || 0, dropoutRate: data.dropout_rate || 0, }, engagement: { averageTimeSpent: data.average_time_spent || 0, averageTimeSpentText: this.formatDuration(data.average_time_spent || 0), totalTimeSpent: data.total_time_spent || 0, totalTimeSpentText: this.formatDuration(data.total_time_spent || 0), averageSessionDuration: data.average_session_duration || 0, lastActivityDate: data.last_activity_date, dailyActiveUsers: data.daily_active_users || [], }, performance: { averageQuizScore: data.average_quiz_score || 0, passRate: data.pass_rate || 0, certificatesIssued: data.certificates_issued || 0, averageTimeToComplete: data.average_time_to_complete || 0, averageTimeToCompleteText: this.formatDuration(data.average_time_to_complete || 0), }, }; } // ---------------------------------------------------------------- // Private helpers – module stats formatting // ---------------------------------------------------------------- formatModuleStats(modules) { return modules.map((module) => ({ moduleId: module.id || '', moduleTitle: module.title || '', completionRate: module.completion_rate || 0, averageProgress: module.average_progress || 0, averageTimeSpent: module.average_time_spent || 0, averageTimeSpentText: this.formatDuration(module.average_time_spent || 0), studentsStarted: module.students_started || 0, studentsCompleted: module.students_completed || 0, lessons: (module.lessons || []).map((lesson) => ({ lessonId: lesson.id || '', lessonTitle: lesson.title || '', completionRate: lesson.completion_rate || 0, averageTimeSpent: lesson.average_time_spent || 0, viewCount: lesson.view_count || 0, })), })); } // ---------------------------------------------------------------- // Private helpers – user breakdown // ---------------------------------------------------------------- calculateUserBreakdown(enrollments) { const buckets = { notStarted: 0, under25: 0, between25And50: 0, between50And75: 0, between75And99: 0, completed: 0, }; for (const enrollment of enrollments) { const progress = enrollment.progress_percentage || 0; if (progress === 0) buckets.notStarted++; else if (progress < 25) buckets.under25++; else if (progress < 50) buckets.between25And50++; else if (progress < 75) buckets.between50And75++; else if (progress < 100) buckets.between75And99++; else buckets.completed++; } const total = enrollments.length; const pct = (count) => (total > 0 ? ((count / total) * 100).toFixed(1) : '0.0'); return { total, progressDistribution: buckets, percentageDistribution: { notStarted: pct(buckets.notStarted), under25: pct(buckets.under25), between25And50: pct(buckets.between25And50), between50And75: pct(buckets.between50And75), between75And99: pct(buckets.between75And99), completed: pct(buckets.completed), }, }; } // ---------------------------------------------------------------- // Private helpers – trend calculations // ---------------------------------------------------------------- calculateGrowthRate(trend) { if (!trend || trend.length < 2) return 'insufficient-data'; const recent = trend[trend.length - 1]?.value || 0; const previous = trend[trend.length - 2]?.value || 0; if (previous === 0) return 'new'; const growthRate = ((recent - previous) / previous) * 100; if (growthRate > 10) return 'high-growth'; if (growthRate > 0) return 'growing'; if (growthRate === 0) return 'stable'; return 'declining'; } calculateEngagementTrend(dailyActiveUsers) { if (!dailyActiveUsers || dailyActiveUsers.length < 7) return 'insufficient-data'; const recentAvg = dailyActiveUsers.slice(-7).reduce((sum, day) => sum + (day.value || 0), 0) / 7; const previousAvg = dailyActiveUsers.slice(-14, -7).reduce((sum, day) => sum + (day.value || 0), 0) / 7; if (previousAvg === 0) return 'new'; const change = ((recentAvg - previousAvg) / previousAvg) * 100; if (change > 5) return 'increasing'; if (change < -5) return 'decreasing'; return 'stable'; } // ---------------------------------------------------------------- // Private helpers – summary // ---------------------------------------------------------------- buildAnalyticsSummary(courseId, analytics) { return { courseId, period: analytics.period, keyMetrics: { totalEnrollments: analytics.enrollment.totalEnrollments, completionRate: analytics.progress.completionRate, averageProgress: analytics.progress.averageProgressPercentage, activeStudents: analytics.enrollment.activeStudents, averageTimeSpent: analytics.engagement.averageTimeSpentText, certificatesIssued: analytics.performance.certificatesIssued, }, trends: { enrollmentGrowth: this.calculateGrowthRate(analytics.enrollment.enrollmentTrend), engagementTrend: this.calculateEngagementTrend(analytics.engagement.dailyActiveUsers), completionTrend: 'stable', // Would calculate from historical data }, }; } // ---------------------------------------------------------------- // Params & Description metadata // ---------------------------------------------------------------- /** * Define the parameters this action expects */ get Params() { const baseParams = this.getCommonLMSParams(); const specificParams = [ { Name: 'CourseID', Type: 'Input', Value: null }, { Name: 'DateFrom', Type: 'Input', Value: null }, { Name: 'DateTo', Type: 'Input', Value: null }, { Name: 'IncludeUserBreakdown', Type: 'Input', Value: false }, { Name: 'IncludeModuleStats', Type: 'Input', Value: true }, { Name: 'IncludeRevenue', Type: 'Input', Value: true }, { Name: 'CourseAnalytics', Type: 'Output', Value: null }, { Name: 'Summary', Type: 'Output', Value: null }, ]; return [...baseParams, ...specificParams]; } /** * Metadata about this action */ get Description() { return 'Retrieves comprehensive analytics and performance metrics for a LearnWorlds course'; } }; GetCourseAnalyticsAction = __decorate([ RegisterClass(BaseAction, 'GetCourseAnalyticsAction') ], GetCourseAnalyticsAction); export { GetCourseAnalyticsAction }; //# sourceMappingURL=get-course-analytics.action.js.map