-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* #66 Add generic arguments and documentation to the cache logic * #66 Add persistence function to load completions and quiz types for a user * #66 Add new quiz functions that will be needed for statistics calculations * #66 Add new statistics service to compute statistics for every quizlord user * #66 Expose new statistics resolver to get individual user statistics * #66 Populate the cache on a successful call to getIndividualUserStatistics * Allow provided empty string for sentry (for local development)
- Loading branch information
1 parent
3a71c74
commit a5e7c4a
Showing
12 changed files
with
259 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
export interface IndividualUserStatistic { | ||
name?: string; | ||
email: string; | ||
totalQuizCompletions: number; | ||
averageScorePercentage: number; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { QuizlordContext } from '..'; | ||
import { authorisationService, statisticsService } from '../service.locator'; | ||
import { IndividualUserStatistic } from './statistics.dto'; | ||
|
||
async function individualUserStatistics( | ||
_p: unknown, | ||
_: void, | ||
context: QuizlordContext, | ||
): Promise<IndividualUserStatistic[]> { | ||
authorisationService.requireUserRole(context, 'USER'); | ||
return statisticsService.getIndividualUserStatistics(); | ||
} | ||
|
||
export const statisticsQueries = { | ||
individualUserStatistics, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
import { QuizService } from '../quiz/quiz.service'; | ||
import { UserService } from '../user/user.service'; | ||
import { Cache } from '../util/cache'; | ||
import { IndividualUserStatistic } from './statistics.dto'; | ||
|
||
const INDIVIDUAL_STATISTICS_CACHE_KEY = 'invidual-user-statistics'; | ||
const INDIVIDUAL_STATISTICS_CACHE_TTL = 60 * 60 * 1000; // 24 hours | ||
|
||
export class StatisticsService { | ||
#userService: UserService; | ||
#quizService: QuizService; | ||
#cache: Cache; | ||
constructor(userService: UserService, quizService: QuizService, cache: Cache) { | ||
this.#userService = userService; | ||
this.#quizService = quizService; | ||
this.#cache = cache; | ||
} | ||
|
||
/** | ||
* Gets the individual statistics for all users. | ||
* @returns An array of users with their statistics. | ||
* | ||
* @tags worker | ||
*/ | ||
async getIndividualUserStatistics(): Promise<IndividualUserStatistic[]> { | ||
const cachedResult = await this.#cache.getItem<IndividualUserStatistic[]>(INDIVIDUAL_STATISTICS_CACHE_KEY); | ||
if (cachedResult) { | ||
return cachedResult; | ||
} | ||
|
||
const results: IndividualUserStatistic[] = []; | ||
let hasMoreRows = true; | ||
let cursor: string | undefined = undefined; | ||
while (hasMoreRows) { | ||
const { data, hasMoreRows: moreRows } = await this.#userService.getUsers({ | ||
currentUserId: '1', // Current user id isn't valid here and isn't used for sorting by EMAIL_ASC | ||
first: 100, | ||
afterId: cursor, | ||
sortedBy: 'EMAIL_ASC', | ||
}); | ||
|
||
for (const user of data) { | ||
const { totalQuizCompletions, averageScorePercentage } = await this.#getStatisticsForUser(user.email); | ||
results.push({ | ||
email: user.email, | ||
name: user.name, | ||
totalQuizCompletions, | ||
averageScorePercentage, | ||
}); | ||
} | ||
|
||
cursor = data[data.length - 1]?.id; | ||
hasMoreRows = moreRows; | ||
} | ||
|
||
await this.#cache.setItem(INDIVIDUAL_STATISTICS_CACHE_KEY, results, INDIVIDUAL_STATISTICS_CACHE_TTL); | ||
return results; | ||
} | ||
|
||
/** | ||
* Get quiz completion statistics for a single user. | ||
* @param userEmail The email of the user to get statistics for. | ||
* @returns Total quiz completions and average score percentage for the user. | ||
* | ||
* @tags worker | ||
*/ | ||
async #getStatisticsForUser(userEmail: string) { | ||
let hasMoreRows = true; | ||
let cursor: string | undefined = undefined; | ||
const completionsScores: number[] = []; | ||
while (hasMoreRows) { | ||
const { stats, cursor: latestCursor } = await this.#quizService.quizScorePercentagesForUser({ | ||
email: userEmail, | ||
first: 100, | ||
afterId: cursor, | ||
}); | ||
|
||
completionsScores.push(...stats); | ||
|
||
cursor = latestCursor; | ||
hasMoreRows = !!latestCursor; | ||
} | ||
|
||
return { | ||
totalQuizCompletions: completionsScores.length, | ||
averageScorePercentage: completionsScores.reduce((a, b) => a + b, 0) / completionsScores.length, | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters