diff --git a/src/lib/server/cache/cache.interface.ts b/src/lib/server/cache/cache.interface.ts new file mode 100644 index 00000000..a9416e22 --- /dev/null +++ b/src/lib/server/cache/cache.interface.ts @@ -0,0 +1,9 @@ + +export interface ICache { + set(key: string, value: unknown): Promise; + get(key: string): Promise; + has(key: string): Promise; + delete(key: string): Promise; + clear(): Promise; + findAndClear(searchPattern: string): Promise; +} diff --git a/src/lib/server/cache/cache.map.ts b/src/lib/server/cache/cache.map.ts new file mode 100644 index 00000000..9a016d8a --- /dev/null +++ b/src/lib/server/cache/cache.map.ts @@ -0,0 +1,45 @@ + +export class CacheMap { + + private cache: Map; + + constructor() { + this.cache = new Map(); + } + + set(key: string, value: V): void { + this.cache.set(key, value); + } + + get(key: string): V | undefined { + return this.cache.get(key); + } + + has(key: string): boolean { + return this.cache.has(key); + } + + delete(key: string): boolean { + return this.cache.delete(key); + } + + clear(): void { + this.cache.clear(); + } + + findAndClear(searchPattern: string): string[] { + let keys: string[] = []; + for (let key of this.cache.keys()) { + if (key.includes(searchPattern)) { + keys.push(key); + } + } + for (let key of keys) { + this.cache.delete(key); + } + return keys; + } + +} + +//////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/lib/server/cache/cache.md b/src/lib/server/cache/cache.md new file mode 100644 index 00000000..c4502b03 --- /dev/null +++ b/src/lib/server/cache/cache.md @@ -0,0 +1,27 @@ +# Cache + +## Setup KeyDB as a Redis cache +KeyDB is a high-performance fork of Redis with a focus on multithreading and memory efficiency +It is designed to be a drop-in replacement for Redis +KeyDB is fully compatible with Redis and supports all Redis commands + +``` +docker run \ +-d --name keydb \ +-p 6379:6379 \ +-e "CACHE_PASSWORD=your-password" \ +-v /path/to/your/data:/data \ +-v /path/to/your/logs:/logs keydb/keydb \ +eqalpha/keydb +``` + +Process to connect with KeyDB is same as Redis. +1. Run the docker container. +2. Set the password by logging into container + a. First run redis-cli as + ```# redis-cli``` + b. Set the password using + ```# auth ``` +3. Create a client and connect to KeyDB. +4. Use the client to perform operations. +5. Close the connection when done. diff --git a/src/lib/server/cache/cache.service.ts b/src/lib/server/cache/cache.service.ts new file mode 100644 index 00000000..7fe7b621 --- /dev/null +++ b/src/lib/server/cache/cache.service.ts @@ -0,0 +1,64 @@ +import { InMemoryCache } from './inmemory.cache' +import { RedisCache } from './redis.cache'; +import type { ICache } from './cache.interface'; +import { CACHE_TYPE } from '$env/static/private'; +import { building } from '$app/environment'; + +//////////////////////////////////////////////////////////////////////////////////////// + +const getCache = () => { + //code should not be executed during the build step. + if (!building) { + if (CACHE_TYPE === 'in-memory') { + return new InMemoryCache(); + } + return new RedisCache(); + } +}; + +//////////////////////////////////////////////////////////////////////////////////////// + +export class CacheService { + + static _cache: ICache = getCache(); + + static get = async (key: string): Promise => { + console.log('CacheService.get', key); + return CacheService._cache.get(key); + } + + static set = async (key: string, value: any): Promise => { + await CacheService._cache.set(key, value); + } + + static has = async (key: string): Promise => { + return CacheService._cache.has(key); + } + + static delete = async (key: string): Promise => { + return CacheService._cache.delete(key); + } + + static deleteMany = async (keys: string[]): Promise => { + let result = true; + for (let key of keys) { + result = result && await CacheService._cache.delete(key); + } + return result; + } + + static findAndClear = async (searchPatterns: string[]): Promise => { + var keys: string[] = []; + for (var substr of searchPatterns) + { + var removedKeys = await CacheService._cache.findAndClear(substr); + keys.push(...removedKeys); + } + return keys; + } + + static clear = async (): Promise => { + await CacheService._cache.clear(); + } + +} diff --git a/src/lib/server/cache/inmemory.cache.ts b/src/lib/server/cache/inmemory.cache.ts new file mode 100644 index 00000000..d053f202 --- /dev/null +++ b/src/lib/server/cache/inmemory.cache.ts @@ -0,0 +1,38 @@ +import { CacheMap } from "./cache.map"; +import { type ICache } from "./cache.interface"; + +//////////////////////////////////////////////////////////////////////////////////////// + +export class InMemoryCache implements ICache { + + private cache: CacheMap = new CacheMap(); + + constructor() { + this.cache = new CacheMap(); + } + + set = async (key: string, value: any): Promise => { + this.cache.set(key, value); + }; + + get = async (key: string): Promise => { + return this.cache.get(key); + }; + + has = async (key: string): Promise => { + return this.cache.has(key); + }; + + delete = async (key: string): Promise => { + return this.cache.delete(key); + }; + + clear = async (): Promise => { + this.cache.clear(); + }; + + findAndClear = async (searchPattern: string): Promise => { + return this.cache.findAndClear(searchPattern); + } + +} diff --git a/src/lib/server/cache/redis.cache.ts b/src/lib/server/cache/redis.cache.ts new file mode 100644 index 00000000..d68a86b8 --- /dev/null +++ b/src/lib/server/cache/redis.cache.ts @@ -0,0 +1,120 @@ +import { type ICache } from "./cache.interface"; +import { createClient, type RedisClientType } from 'redis'; + +//////////////////////////////////////////////////////////////////////////////////////// +// Using KeyDB as a Redis cache +// KeyDB is a high-performance fork of Redis with a focus on multithreading and memory efficiency +// It is designed to be a drop-in replacement for Redis +// KeyDB is fully compatible with Redis and supports all Redis commands +// +// docker run \ +// -d --name keydb \ +// -p 6379:6379 \ +// -e "CACHE_PASSWORD=your-password" \ +// -v /path/to/your/data:/data \ +// -v /path/to/your/logs:/logs keydb/keydb \ +// eqalpha/keydb + +// Process to connect with KeyDB is same as Redis. +// 1. Run the docker container. +// 2. Set the password by logging into container +// a. First run redis-cli as +// ```# redis-cli``` +// b. Set the password using +// ```# auth ``` +// 3. Create a client and connect to KeyDB. +// 4. Use the client to perform operations. +// 5. Close the connection when done. +// +//////////////////////////////////////////////////////////////////////////////////////// + +export class RedisCache implements ICache { + + private _client: RedisClientType| null = null; + + private _expiry = 60 * 60 * 24 * 2; // 48 hours + + constructor() { + // Create a client and connect to KeyDB + var port = process.env.CACHE_PORT ? parseInt(process.env.CACHE_PORT) : 6379; + this._client = createClient({ + socket: { + host: process.env.CACHE_HOST || 'localhost', + port: port, + }, + password: process.env.CACHE_PASSWORD // if authentication is required + }); + (async () => { + if (this._client) await this._client.connect(); + })(); + } + + set = async (key: string, value: any): Promise => { + if (this._client) { + const exists = await this._client.exists(key); + if (exists === 1) { + await this._client.del(key); + } + await this._client.set(key, JSON.stringify(value), { + EX: this._expiry,// 24 hours + }); + } + }; + + get = async (key: string): Promise => { + if (this._client) { + const val = await this._client.get(key); + if (val) { + const value = JSON.parse(val); + return value; + } + } + return undefined; + }; + + has = async (key: string): Promise => { + if (this._client) { + const exists = await this._client.exists(key); + return exists === 1; + } + return false; + }; + + delete = async (key: string): Promise => { + if (this._client) { + const exists = await this._client.exists(key); + if (exists === 1) { + await this._client.del(key); + return true; + } + } + return false; + }; + + clear = async (): Promise => { + if (this._client) { + console.log('Clearing cache'); + this._client.flushAll(); + } + }; + + // size = async (): Promise => { + // if (this._client) { + // // console.log('Getting cache size'); + // this._client.dbSize(); + // } + // return 0; + // }; + + findAndClear = async (searchPattern: string): Promise => { + if (this._client) { + const keys = await this._client.keys(searchPattern); + if (keys.length > 0) { + await this._client.del(keys); + } + return keys; + } + return []; + } + +} diff --git a/src/lib/utils/time.helper.ts b/src/lib/utils/time.helper.ts index 46db5294..7efd6fbf 100755 --- a/src/lib/utils/time.helper.ts +++ b/src/lib/utils/time.helper.ts @@ -13,4 +13,10 @@ export class TimeHelper { } return date.toISOString().split('T')[0]; }; + + static getYesterdayDate = (): string => { + const today = new Date(); + today.setDate(today.getDate() - 1); + return TimeHelper.getDateString(today, DateStringFormat.YYYY_MM_DD); + }; } diff --git a/src/routes/api/services/reancare/statistics.ts b/src/routes/api/services/reancare/statistics.ts index 5c2c7a9a..7616f155 100644 --- a/src/routes/api/services/reancare/statistics.ts +++ b/src/routes/api/services/reancare/statistics.ts @@ -1,6 +1,10 @@ import { BACKEND_API_URL } from '$env/static/private'; import { USER_ANALYTICS_API_URL } from '$env/static/private'; import { API_CLIENT_INTERNAL_KEY } from '$env/static/private'; +import { CacheService } from '$lib/server/cache/cache.service'; +import { DateStringFormat } from '$lib/types/time.types'; +import { Helper } from '$lib/utils/helper'; +import { TimeHelper } from '$lib/utils/time.helper'; import { SessionManager } from '../../sessions/session.manager'; import { get } from './common.reancare'; @@ -268,12 +272,40 @@ export const getOverallUsers = async (sessionId: string, searchParams?: any) => export const getDailyStatistics = async(sessionId:string)=>{ const url = BACKEND_API_URL + `/daily-stats`; - return await get(sessionId, url, true, API_CLIENT_INTERNAL_KEY); + const today = TimeHelper.getDateString(new Date(), DateStringFormat.YYYY_MM_DD); + const cacheKey = `session-${sessionId}:req-getDailyStatistics:${today}`; + const yesterday = TimeHelper.getYesterdayDate(); + const yesterdayCacheKey = `session-${sessionId}:req-getDailyStatistics:${yesterday}`; + + if (await CacheService.has(yesterdayCacheKey)) { + await CacheService._cache.delete(yesterdayCacheKey); + console.log(`Cleared old key: ${yesterdayCacheKey}`); + } + if (await CacheService.has(cacheKey)) { + return await CacheService.get(cacheKey); + } + const result = await get(sessionId, url, true, API_CLIENT_INTERNAL_KEY); + await CacheService.set(cacheKey, result); + return result; } export const getDailyTenantStatistics = async(sessionId:string, tenantId: string)=>{ const url = BACKEND_API_URL + `/daily-stats/tenants/${tenantId}`; - return await get(sessionId, url, true, API_CLIENT_INTERNAL_KEY); + const today = TimeHelper.getDateString(new Date(), DateStringFormat.YYYY_MM_DD); + const cacheKey = `session-${sessionId}:req-getDailyTenantStatistics:${today}`; + const yesterday = TimeHelper.getYesterdayDate(); + const yesterdayCacheKey = `session-${sessionId}:req-getDailyTenantStatistics:${yesterday}`; + + if (await CacheService.has(yesterdayCacheKey)) { + await CacheService._cache.delete(yesterdayCacheKey); + console.log(`Cleared old key: ${yesterdayCacheKey}`); + } + if (await CacheService.has(cacheKey)) { + return await CacheService.get(cacheKey); + } + const result = await get(sessionId, url, true, API_CLIENT_INTERNAL_KEY); + await CacheService.set(cacheKey, result); + return result; } export const getDailySystemStatistics = async(sessionId:string)=>{ diff --git a/src/routes/api/services/user-analytics/user-analytics.ts b/src/routes/api/services/user-analytics/user-analytics.ts index 7bccac9b..2166e14e 100644 --- a/src/routes/api/services/user-analytics/user-analytics.ts +++ b/src/routes/api/services/user-analytics/user-analytics.ts @@ -1,5 +1,7 @@ import { USER_ANALYTICS_API_URL } from '$env/static/private'; import { API_CLIENT_INTERNAL_KEY } from '$env/static/private'; +import { CacheService } from '$lib/server/cache/cache.service'; +import { TimeHelper } from '$lib/utils/time.helper'; import { get } from '../reancare/common.reancare'; export const getAnalyticsReport = async(format: string) => { @@ -17,7 +19,22 @@ export const getAnalyticsReport = async(format: string) => { } export const getUserAnalytics = async (sessionId, formattedDate) => { - console.log('Formated date: ' + formattedDate); + console.log('Formatted date: ' + formattedDate); const url = USER_ANALYTICS_API_URL + `/analytics/metrics/${formattedDate}-1`; - return await get(sessionId, url, true, API_CLIENT_INTERNAL_KEY); -} + const cacheKey = `session-${sessionId}:req-getUserAnalytics:${formattedDate}-1`; + const yesterday = TimeHelper.getYesterdayDate(); + const yesterdayCacheKey = `session-${sessionId}:req-getUserAnalytics:${yesterday}-1`; + + if (await CacheService.has(yesterdayCacheKey)) { + await CacheService._cache.delete(yesterdayCacheKey); + console.log(`Cleared old key: ${yesterdayCacheKey}`); + } + + if (await CacheService.has(cacheKey)) { + return await CacheService.get(cacheKey); + } + + const result = await get(sessionId, url, true, API_CLIENT_INTERNAL_KEY); + await CacheService.set(cacheKey, result); + return result; +}; diff --git a/src/routes/api/sessions/redis.cache.ts b/src/routes/api/sessions/redis.cache.ts index 2130c6d2..d27ae583 100644 --- a/src/routes/api/sessions/redis.cache.ts +++ b/src/routes/api/sessions/redis.cache.ts @@ -1,7 +1,7 @@ import { type Session } from "./session"; import { type ISessionCache } from "./session.cache.interface"; import { createClient, type RedisClientType } from 'redis'; -import { SESSION_CACHE_HOST, SESSION_CACHE_PASSWORD } from "$env/static/private"; +import { CACHE_HOST, CACHE_PASSWORD } from "$env/static/private"; //////////////////////////////////////////////////////////////////////////////////////// @@ -15,8 +15,8 @@ export class RedisCache implements ISessionCache { // Create a client and connect to redis try { this._client = createClient({ - url: SESSION_CACHE_HOST, - password: SESSION_CACHE_PASSWORD + url: CACHE_HOST, + password: CACHE_PASSWORD }); (async () => { if (this._client) await this._client.connect(); diff --git a/src/routes/api/sessions/session.manager.ts b/src/routes/api/sessions/session.manager.ts index 8b4ef00d..475d74c5 100644 --- a/src/routes/api/sessions/session.manager.ts +++ b/src/routes/api/sessions/session.manager.ts @@ -1,5 +1,5 @@ import type { Session } from './session'; -import { SESSION_CACHE_TYPE } from '$env/static/private'; +import { CACHE_TYPE } from '$env/static/private'; import { InMemoryCache } from './inmemory.cache' import { RedisCache } from './redis.cache'; import type { ISessionCache } from './session.cache.interface'; @@ -10,7 +10,7 @@ import { building } from '$app/environment'; const getCache = () => { //code should not be executed during the build step. if (!building) { - if (SESSION_CACHE_TYPE === 'in-memory') { + if (CACHE_TYPE === 'in-memory') { return new InMemoryCache(); } return new RedisCache(); diff --git a/src/routes/users/[userId]/home/+page.server.ts b/src/routes/users/[userId]/home/+page.server.ts index 86d06f81..ae2c0664 100644 --- a/src/routes/users/[userId]/home/+page.server.ts +++ b/src/routes/users/[userId]/home/+page.server.ts @@ -1,10 +1,12 @@ import type { PageServerLoad } from './$types'; import { error, type RequestEvent } from '@sveltejs/kit'; import { getDailyStatistics, getDailyTenantStatistics } from "../../../api/services/reancare/statistics" +import { TimeHelper } from '$lib/utils/time.helper'; +import { DateStringFormat } from '$lib/types/time.types'; +import { getUserAnalytics } from '$routes/api/services/user-analytics/user-analytics'; ////////////////////////////////////////////////////////////////////////// - export const load: PageServerLoad = async (event: RequestEvent) => { const sessionId = event.cookies.get('sessionId'); let response; @@ -35,6 +37,9 @@ export const load: PageServerLoad = async (event: RequestEvent) => { const deviceDetailsStats = response.Data.DailyStatistics.DashboardStats.UserStatistics.DeviceDetailWiseUsers; const userCountByYears = response.Data.DailyStatistics.DashboardStats.UserStatistics.YearWiseUserCount; const deviceDetailsByYears = response.Data.DailyStatistics.DashboardStats.UserStatistics.YearWiseDeviceDetails; + const today = new Date(); + const formattedDate = TimeHelper.getDateString(today, DateStringFormat.YYYY_MM_DD); + await getUserAnalytics(sessionId, formattedDate) return { sessionId, diff --git a/src/routes/users/[userId]/home/analytics-overview/+page.server.ts b/src/routes/users/[userId]/home/analytics-overview/+page.server.ts index 5906de69..25f6b29c 100644 --- a/src/routes/users/[userId]/home/analytics-overview/+page.server.ts +++ b/src/routes/users/[userId]/home/analytics-overview/+page.server.ts @@ -1,9 +1,10 @@ import { error, type RequestEvent } from '@sveltejs/kit'; import type { PageServerLoad } from './$types'; import { getUserAnalytics } from '../../../../api/services/user-analytics/user-analytics'; -import chalk from 'chalk'; import { redirect } from 'sveltekit-flash-message/server'; import { errorMessage } from '$lib/utils/message.utils'; +import { TimeHelper } from '$lib/utils/time.helper'; +import { DateStringFormat } from '$lib/types/time.types'; //////////////////////////////////////////////////////////////////////////// @@ -11,11 +12,8 @@ export const load: PageServerLoad = async (event: RequestEvent) => { const sessionId = event.cookies.get('sessionId'); const userId = event.params.userId; const today = new Date(); - const yyyy = today.getFullYear(); - const mm = String(today.getMonth() + 1).padStart(2, '0'); // Months are zero-based - const dd = String(today.getDate()).padStart(2, '0'); - - const formattedDate = `${yyyy}-${mm}-${dd}`; + const formattedDate = TimeHelper.getDateString(today, DateStringFormat.YYYY_MM_DD); + const response = await getUserAnalytics(sessionId, formattedDate) if (!response) { @@ -27,9 +25,11 @@ export const load: PageServerLoad = async (event: RequestEvent) => { event ); } + const basicStatistics = response.Data.BasicStatistics; + return { sessionId, - statistics: response.Data, + basicStatistics, title:'Dashboard-Home-Basic' }; }; diff --git a/src/routes/users/[userId]/home/analytics-overview/+page.svelte b/src/routes/users/[userId]/home/analytics-overview/+page.svelte index a64e3220..fdff2877 100644 --- a/src/routes/users/[userId]/home/analytics-overview/+page.svelte +++ b/src/routes/users/[userId]/home/analytics-overview/+page.svelte @@ -15,45 +15,44 @@ let patientDeRegistrationHistoryData; let usersDistributionByRoleData, usersDistributionByRoleLabels; let activeUsersCountAtEndOfMonthData, activeUsersCountAtEndOfMonthLabels; - let dereg = processPatientDeregistrationHistory(data.statistics.BasicStatistics.PatientDeregistrationHistory); + let dereg = processPatientDeregistrationHistory(data.basicStatistics.PatientDeregistrationHistory); - - if (data.statistics.BasicStatistics) { - if (data.statistics.BasicStatistics.PatientRegistrationHistory) { - patientRegistrationHistoryData = data.statistics.BasicStatistics.PatientRegistrationHistory.map( + if (data.basicStatistics) { + if (data.basicStatistics.PatientRegistrationHistory) { + patientRegistrationHistoryData = data.basicStatistics.PatientRegistrationHistory.map( (x) => x.user_count ); - patientRegistrationHistoryLabels = data.statistics.BasicStatistics.PatientRegistrationHistory.map((x) => { + patientRegistrationHistoryLabels = data.basicStatistics.PatientRegistrationHistory.map((x) => { const label = formatLabelOfMonth(x.month); return label ? label : 'Unknown'; }); } - if (data.statistics.BasicStatistics.PatientDeregistrationHistory) { - patientDeRegistrationHistoryData = data.statistics.BasicStatistics.PatientDeregistrationHistory.map( + if (data.basicStatistics.PatientDeregistrationHistory) { + patientDeRegistrationHistoryData = data.basicStatistics.PatientDeregistrationHistory.map( (x) => x.user_count ); - // patientDeRegistrationHistoryLabels = data.statistics.BasicStatistics.PatientDeregistrationHistory.map( + // patientDeRegistrationHistoryLabels = data.basicStatistics.PatientDeregistrationHistory.map( // (x) => { // const label = formatMonthLabel(x.month); // return label ? label : 'Unknown'; // } // ); } - if (data.statistics.BasicStatistics.UsersDistributionByRole) { - usersDistributionByRoleData = data.statistics.BasicStatistics.UsersDistributionByRole.map( + if (data.basicStatistics.UsersDistributionByRole) { + usersDistributionByRoleData = data.basicStatistics.UsersDistributionByRole.map( (x) => x.registration_count ); - usersDistributionByRoleLabels = data.statistics.BasicStatistics.UsersDistributionByRole.map( + usersDistributionByRoleLabels = data.basicStatistics.UsersDistributionByRole.map( (x) => x.role_name ); } - if (data.statistics.BasicStatistics.ActiveUsersCountAtEndOfMonth) { - activeUsersCountAtEndOfMonthData = data.statistics.BasicStatistics.ActiveUsersCountAtEndOfMonth.map( + if (data.basicStatistics.ActiveUsersCountAtEndOfMonth) { + activeUsersCountAtEndOfMonthData = data.basicStatistics.ActiveUsersCountAtEndOfMonth.map( (x) => x.active_user_count ); - activeUsersCountAtEndOfMonthLabels = data.statistics.BasicStatistics.ActiveUsersCountAtEndOfMonth.map( + activeUsersCountAtEndOfMonthLabels = data.basicStatistics.ActiveUsersCountAtEndOfMonth.map( (x) => x.month_end ); } @@ -63,8 +62,8 @@ let result; result = processPatientRegistrationHistory( - data.statistics.BasicStatistics.PatientRegistrationHistory, - data.statistics.BasicStatistics.PatientDeregistrationHistory + data.basicStatistics.PatientRegistrationHistory, + data.basicStatistics.PatientDeregistrationHistory ); // onMount(() => { // }); @@ -91,7 +90,7 @@ style="width:10%;" class="whitespace-nowrap px-3 py-2 text-sm" > - {data.statistics.TenantId || 'Default'} - {formatDate(data.statistics.StartDate)} - {formatDate(data.statistics.EndDate)}
- - {data.statistics.BasicStatistics.TotalUsers}{data.basicStatistics.TotalPatients}{data.basicStatistics.TotalActivePatients} {#each assessmentTableHeaders as header} diff --git a/src/routes/users/[userId]/home/generic-engagement/+page.server.ts b/src/routes/users/[userId]/home/generic-engagement/+page.server.ts index 4122442a..0125a212 100644 --- a/src/routes/users/[userId]/home/generic-engagement/+page.server.ts +++ b/src/routes/users/[userId]/home/generic-engagement/+page.server.ts @@ -4,6 +4,8 @@ import { getUserAnalytics } from '../../../../api/services/user-analytics/user-a import chalk from 'chalk'; import { redirect } from 'sveltekit-flash-message/server'; import { errorMessage } from '$lib/utils/message.utils'; +import { TimeHelper } from '$lib/utils/time.helper'; +import { DateStringFormat } from '$lib/types/time.types'; //////////////////////////////////////////////////////////////////////////// @@ -11,16 +13,8 @@ export const load: PageServerLoad = async (event: RequestEvent) => { const sessionId = event.cookies.get('sessionId'); const userId = event.params.userId; const today = new Date(); - const yyyy = today.getFullYear(); - const mm = String(today.getMonth() + 1).padStart(2, '0'); // Months are zero-based - const dd = String(today.getDate()).padStart(2, '0'); - - const formattedDate = `${yyyy}-${mm}-${dd}`; + const formattedDate = TimeHelper.getDateString(today, DateStringFormat.YYYY_MM_DD); const response = await getUserAnalytics(sessionId, formattedDate) - console.log(chalk.yellow(JSON.stringify(response))); - // const data = JSON.parse(await response); - - if (!response) { throw error(404, 'Daily user statistics data not found'); } @@ -30,11 +24,10 @@ export const load: PageServerLoad = async (event: RequestEvent) => { event ); } - - + const genericMetrics = response.Data.GenericMetrics; return { sessionId, - statistics: response.Data, + genericMetrics, title:'Dashboard-Home-Generic' }; }; diff --git a/src/routes/users/[userId]/home/generic-engagement/+page.svelte b/src/routes/users/[userId]/home/generic-engagement/+page.svelte index 14eb0f82..7072e973 100644 --- a/src/routes/users/[userId]/home/generic-engagement/+page.svelte +++ b/src/routes/users/[userId]/home/generic-engagement/+page.svelte @@ -4,70 +4,70 @@ import { formatMonth, formatMonthLabel, formatDateToDDMMYYYY } from '../analytics-overview/components/functions'; //////////////////////////////////////////////////////////////////////// - export let data; + export let data; let dailyActiveUsersData, dailyActiveUsersLabels; let monthlyActiveUsersData, monthlyActiveUsersLabels; let weeklyActiveUsersData, weeklyActiveUsersLabels; let retentionOnDaysData, retentionOnDaysLabels, retentionOnDaysRate; let retentionOnIntervalData, retentionOnIntervalLabels, retentionOnIntervalRate; - if (data.statistics.GenericMetrics) { - if (data.statistics.GenericMetrics.DailyActiveUsers) { - dailyActiveUsersData = data.statistics.GenericMetrics.DailyActiveUsers.map((x) => x.daily_active_users); - dailyActiveUsersLabels = data.statistics.GenericMetrics.DailyActiveUsers.map((x) => + if (data.genericMetrics) { + if (data.genericMetrics.DailyActiveUsers) { + dailyActiveUsersData = data.genericMetrics.DailyActiveUsers.map((x) => x.daily_active_users); + dailyActiveUsersLabels = data.genericMetrics.DailyActiveUsers.map((x) => formatDateToDDMMYYYY(x.activity_date) ); } - if (data.statistics.GenericMetrics.WeeklyActiveUsers) { - weeklyActiveUsersData = data.statistics.GenericMetrics.WeeklyActiveUsers.map((x) => x.weekly_active_users); - weeklyActiveUsersLabels = data.statistics.GenericMetrics.WeeklyActiveUsers.map( + if (data.genericMetrics.WeeklyActiveUsers) { + weeklyActiveUsersData = data.genericMetrics.WeeklyActiveUsers.map((x) => x.weekly_active_users); + weeklyActiveUsersLabels = data.genericMetrics.WeeklyActiveUsers.map( (x) => `${formatDateToDDMMYYYY(x.week_start_date)}` ); } - if (data.statistics.GenericMetrics.MonthlyActiveUsers) { - monthlyActiveUsersData = data.statistics.GenericMetrics.MonthlyActiveUsers.map( + if (data.genericMetrics.MonthlyActiveUsers) { + monthlyActiveUsersData = data.genericMetrics.MonthlyActiveUsers.map( (x) => x.monthly_active_users ); - monthlyActiveUsersLabels = data.statistics.GenericMetrics.MonthlyActiveUsers.map((x) => + monthlyActiveUsersLabels = data.genericMetrics.MonthlyActiveUsers.map((x) => formatMonth(x.activity_month) ); } - if (data.statistics.GenericMetrics.RetentionRateOnSpecificDays.retention_on_specific_days) { + if (data.genericMetrics.RetentionRateOnSpecificDays.retention_on_specific_days) { retentionOnDaysData = - data.statistics.GenericMetrics.RetentionRateOnSpecificDays.retention_on_specific_days.map( + data.genericMetrics.RetentionRateOnSpecificDays.retention_on_specific_days.map( (x) => x.returning_users ); retentionOnDaysLabels = - data.statistics.GenericMetrics.RetentionRateOnSpecificDays.retention_on_specific_days.map( + data.genericMetrics.RetentionRateOnSpecificDays.retention_on_specific_days.map( (x) => `${x.day}` ); retentionOnDaysRate = - data.statistics.GenericMetrics.RetentionRateOnSpecificDays.retention_on_specific_days.map( + data.genericMetrics.RetentionRateOnSpecificDays.retention_on_specific_days.map( (x) => x.retention_rate ); } - if (data.statistics.GenericMetrics.RetentionRateInSpecificIntervals.retention_in_specific_interval) { + if (data.genericMetrics.RetentionRateInSpecificIntervals.retention_in_specific_interval) { retentionOnIntervalData = - data.statistics.GenericMetrics.RetentionRateInSpecificIntervals.retention_in_specific_interval.map( + data.genericMetrics.RetentionRateInSpecificIntervals.retention_in_specific_interval.map( (x) => x.returning_users ); retentionOnIntervalLabels = - data.statistics.GenericMetrics.RetentionRateInSpecificIntervals.retention_in_specific_interval.map( + data.genericMetrics.RetentionRateInSpecificIntervals.retention_in_specific_interval.map( (x) => x.interval ); retentionOnIntervalRate = - data.statistics.GenericMetrics.RetentionRateInSpecificIntervals.retention_in_specific_interval.map( + data.genericMetrics.RetentionRateInSpecificIntervals.retention_in_specific_interval.map( (x) => x.retention_rate ); } } - const mostFiredEvents = data.statistics.GenericMetrics.MostFiredEvents; - const mostFiredEventsByEventCategory = data.statistics.GenericMetrics.MostFiredEventsByEventCategory; + const mostFiredEvents = data.genericMetrics.MostFiredEvents; + const mostFiredEventsByEventCategory = data.genericMetrics.MostFiredEventsByEventCategory; let selectedGraph = 'daily'; @@ -80,7 +80,7 @@ onMount(() => { // Process the data let monthlyData = {}; - data.statistics.GenericMetrics.MostCommonlyVisitedFeatures.forEach((item) => { + data.genericMetrics.MostCommonlyVisitedFeatures.forEach((item) => { if (!monthlyData[item.month]) { monthlyData[item.month] = { totalUsage: 0, features: {} }; } @@ -88,7 +88,6 @@ monthlyData[item.month].features[item.feature] = item.feature_usage_count; }); - // Find the most used feature for each month and calculate percentages processedData = Object.entries(monthlyData).map(([month, data]) => { let mostUsedFeature = Object.entries(data.features).reduce((a, b) => (b[1] > a[1] ? b : a)); return { @@ -101,182 +100,17 @@ }; }); - // Sort by month processedData.sort((a, b) => new Date(a.month) - new Date(b.month)); - // Extract unique years years = [...new Set(processedData.map((data) => data.year))]; - // Set the initial selected year selectedYear = years[0]; }); - // Filtered data based on the selected year $: filteredData = processedData.filter((item) => item.year === selectedYear);
-
@@ -357,90 +191,6 @@
- -
@@ -507,8 +257,6 @@
- -
{ const sessionId = event.cookies.get('sessionId'); const userId = event.params.userId; const today = new Date(); - const yyyy = today.getFullYear(); - const mm = String(today.getMonth() + 1).padStart(2, '0'); // Months are zero-based - const dd = String(today.getDate()).padStart(2, '0'); - - const formattedDate = `${yyyy}-${mm}-${dd}`; + const formattedDate = TimeHelper.getDateString(today, DateStringFormat.YYYY_MM_DD); const response = await getUserAnalytics(sessionId, formattedDate) - console.log(chalk.yellow(JSON.stringify(response))); - // const data = JSON.parse(await response); - - if (!response) { throw error(404, 'Daily user statistics data not found'); } @@ -31,11 +25,11 @@ export const load: PageServerLoad = async (event: RequestEvent) => { ); } - + const basicStatistics = response.Data.BasicStatistics; return { sessionId, - statistics: response.Data, + basicStatistics, title:'Dashboard-Home-Demographics' }; }; diff --git a/src/routes/users/[userId]/home/patient-demographics/+page.svelte b/src/routes/users/[userId]/home/patient-demographics/+page.svelte index dda49a4c..c5319e1f 100644 --- a/src/routes/users/[userId]/home/patient-demographics/+page.svelte +++ b/src/routes/users/[userId]/home/patient-demographics/+page.svelte @@ -3,19 +3,19 @@ /////////////////////////////////////////////////////////////////////////////////////////// export let data; - const ageGroup = data.statistics.BasicStatistics.PatientDemographics.AgeGroups; - const genderGroups = data.statistics.BasicStatistics.PatientDemographics.GenderGroups; - const locationGroups = data.statistics.BasicStatistics.PatientDemographics.LocationGroups; - const ethinicityGroups = data.statistics.BasicStatistics.PatientDemographics.EthnicityGroups; - const raceGroups = data.statistics.BasicStatistics.PatientDemographics.RaceGroups; - const healthSystemsDistribution = data.statistics.BasicStatistics.PatientDemographics.HealthSystemDistribution; - const hospitalDistribution = data.statistics.BasicStatistics.PatientDemographics.HospitalDistribution; + const ageGroup = data.basicStatistics.PatientDemographics.AgeGroups; + const genderGroups = data.basicStatistics.PatientDemographics.GenderGroups; + const locationGroups = data.basicStatistics.PatientDemographics.LocationGroups; + const ethinicityGroups = data.basicStatistics.PatientDemographics.EthnicityGroups; + const raceGroups = data.basicStatistics.PatientDemographics.RaceGroups; + const healthSystemsDistribution = data.basicStatistics.PatientDemographics.HealthSystemDistribution; + const hospitalDistribution = data.basicStatistics.PatientDemographics.HospitalDistribution; const survivorOrCaregiverDistribution = - data.statistics.BasicStatistics.PatientDemographics.SurvivorOrCareGiverDistribution; + data.basicStatistics.PatientDemographics.SurvivorOrCareGiverDistribution; - // if (data.statistics.BasicStatistics.PatientDemographics) { - // if (data.statistics.BasicStatistics.PatientDemographics.AgeGroups) { - // ageGroup = data.statistics.BasicStatistics.PatientDemographics.AgeGroups.map((x) => x.count); + // if (data.basicStatistics.PatientDemographics) { + // if (data.basicStatistics.PatientDemographics.AgeGroups) { + // ageGroup = data.basicStatistics.PatientDemographics.AgeGroups.map((x) => x.count); // } // } let ageGroupData, ageGroupLabels; @@ -27,54 +27,54 @@ let hospitalGroupData, hospitalGroupLabels; let survivorCareGiverGroupData, survivorCareGiverGroupLabels; - if (data.statistics.BasicStatistics.PatientDemographics) { - if (data.statistics.BasicStatistics.PatientDemographics.AgeGroups) { - ageGroupData = data.statistics.BasicStatistics.PatientDemographics.AgeGroups.map((x) => x.count); - ageGroupLabels = data.statistics.BasicStatistics.PatientDemographics.AgeGroups.map((x) => { + if (data.basicStatistics.PatientDemographics) { + if (data.basicStatistics.PatientDemographics.AgeGroups) { + ageGroupData = data.basicStatistics.PatientDemographics.AgeGroups.map((x) => x.count); + ageGroupLabels = data.basicStatistics.PatientDemographics.AgeGroups.map((x) => { const label = x.age_group; return label ? label : 'Unknown'; }); } - if (data.statistics.BasicStatistics.PatientDemographics.GenderGroups) { - genderGroupData = data.statistics.BasicStatistics.PatientDemographics.GenderGroups.map((x) => x.count); - genderGroupLabels = data.statistics.BasicStatistics.PatientDemographics.GenderGroups.map((x) => { + if (data.basicStatistics.PatientDemographics.GenderGroups) { + genderGroupData = data.basicStatistics.PatientDemographics.GenderGroups.map((x) => x.count); + genderGroupLabels = data.basicStatistics.PatientDemographics.GenderGroups.map((x) => { const label = x.gender; return label ? label : 'Unknown'; }); } - if (data.statistics.BasicStatistics.PatientDemographics.LocationGroups) { - locationGroupData = data.statistics.BasicStatistics.PatientDemographics.LocationGroups.map((x) => x.count); - locationGroupLabels = data.statistics.BasicStatistics.PatientDemographics.LocationGroups.map((x) => { + if (data.basicStatistics.PatientDemographics.LocationGroups) { + locationGroupData = data.basicStatistics.PatientDemographics.LocationGroups.map((x) => x.count); + locationGroupLabels = data.basicStatistics.PatientDemographics.LocationGroups.map((x) => { const label = x.location; return label ? label : 'Unknown'; }); } - if (data.statistics.BasicStatistics.PatientDemographics.EthnicityGroups) { - ethnicityGroupData = data.statistics.BasicStatistics.PatientDemographics.EthnicityGroups.map( + if (data.basicStatistics.PatientDemographics.EthnicityGroups) { + ethnicityGroupData = data.basicStatistics.PatientDemographics.EthnicityGroups.map( (x) => x.count ); - ethnicityGroupLabels = data.statistics.BasicStatistics.PatientDemographics.EthnicityGroups.map((x) => { + ethnicityGroupLabels = data.basicStatistics.PatientDemographics.EthnicityGroups.map((x) => { const label = x.ethnicity; return label ? label : 'Unknown'; }); } - if (data.statistics.BasicStatistics.PatientDemographics.RaceGroups) { - raceGroupData = data.statistics.BasicStatistics.PatientDemographics.RaceGroups.map((x) => x.count); - raceGroupLabels = data.statistics.BasicStatistics.PatientDemographics.RaceGroups.map((x) => { + if (data.basicStatistics.PatientDemographics.RaceGroups) { + raceGroupData = data.basicStatistics.PatientDemographics.RaceGroups.map((x) => x.count); + raceGroupLabels = data.basicStatistics.PatientDemographics.RaceGroups.map((x) => { const label = x.race; return label ? label : 'Unknown'; }); } - if (data.statistics.BasicStatistics.PatientDemographics.HealthSystemDistribution) { - healthSystemGroupData = data.statistics.BasicStatistics.PatientDemographics.HealthSystemDistribution.map( + if (data.basicStatistics.PatientDemographics.HealthSystemDistribution) { + healthSystemGroupData = data.basicStatistics.PatientDemographics.HealthSystemDistribution.map( (x) => x.count ); - healthSystemGroupLabels = data.statistics.BasicStatistics.PatientDemographics.HealthSystemDistribution.map( + healthSystemGroupLabels = data.basicStatistics.PatientDemographics.HealthSystemDistribution.map( (x) => { const label = x.health_system; return label ? label : 'Unknown'; @@ -82,21 +82,21 @@ ); } - if (data.statistics.BasicStatistics.PatientDemographics.HospitalDistribution) { - hospitalGroupData = data.statistics.BasicStatistics.PatientDemographics.HospitalDistribution.map( + if (data.basicStatistics.PatientDemographics.HospitalDistribution) { + hospitalGroupData = data.basicStatistics.PatientDemographics.HospitalDistribution.map( (x) => x.count ); - hospitalGroupLabels = data.statistics.BasicStatistics.PatientDemographics.HospitalDistribution.map((x) => { + hospitalGroupLabels = data.basicStatistics.PatientDemographics.HospitalDistribution.map((x) => { const label = x.hospital; return label ? label : 'Unknown'; }); } - if (data.statistics.BasicStatistics.PatientDemographics.SurvivorOrCareGiverDistribution) { + if (data.basicStatistics.PatientDemographics.SurvivorOrCareGiverDistribution) { survivorCareGiverGroupData = - data.statistics.BasicStatistics.PatientDemographics.SurvivorOrCareGiverDistribution.map((x) => x.count); + data.basicStatistics.PatientDemographics.SurvivorOrCareGiverDistribution.map((x) => x.count); survivorCareGiverGroupLabels = - data.statistics.BasicStatistics.PatientDemographics.SurvivorOrCareGiverDistribution.map((x) => { + data.basicStatistics.PatientDemographics.SurvivorOrCareGiverDistribution.map((x) => { const label = x.caregiver_status; // Check if the label is "Yes" or "No", and assign accordingly if (label === 'No') {
{data.statistics.BasicStatistics.TotalPatients} {data.statistics.BasicStatistics.TotalActivePatients} -
@@ -315,42 +282,6 @@ {/if}
- -
diff --git a/src/routes/users/[userId]/home/feature-engagement/+page.server.ts b/src/routes/users/[userId]/home/feature-engagement/+page.server.ts index 0d737195..32e8ad35 100644 --- a/src/routes/users/[userId]/home/feature-engagement/+page.server.ts +++ b/src/routes/users/[userId]/home/feature-engagement/+page.server.ts @@ -5,6 +5,8 @@ import { getUserAnalytics } from '../../../../api/services/user-analytics/user-a import chalk from 'chalk'; import { redirect } from 'sveltekit-flash-message/server'; import { errorMessage } from '$lib/utils/message.utils'; +import { TimeHelper } from '$lib/utils/time.helper'; +import { DateStringFormat } from '$lib/types/time.types'; //////////////////////////////////////////////////////////////////////////// @@ -12,11 +14,7 @@ export const load: PageServerLoad = async (event: RequestEvent) => { const sessionId = event.cookies.get('sessionId'); const userId = event.params.userId; const today = new Date(); - const yyyy = today.getFullYear(); - const mm = String(today.getMonth() + 1).padStart(2, '0'); // Months are zero-based - const dd = String(today.getDate()).padStart(2, '0'); - - const formattedDate = `${yyyy}-${mm}-${dd}`; + const formattedDate = TimeHelper.getDateString(today, DateStringFormat.YYYY_MM_DD); const response = await getUserAnalytics(sessionId, formattedDate) if (!response) { throw error(404, 'Daily user statistics data not found'); diff --git a/src/routes/users/[userId]/home/feature-engagement/+page.svelte b/src/routes/users/[userId]/home/feature-engagement/+page.svelte index d68cb986..f30a114d 100644 --- a/src/routes/users/[userId]/home/feature-engagement/+page.svelte +++ b/src/routes/users/[userId]/home/feature-engagement/+page.svelte @@ -135,7 +135,8 @@ -
+ +
{#each features as feature}
{header}