Skip to content

Commit

Permalink
#81 Add new getRecentActivity function to the activity service
Browse files Browse the repository at this point in the history
  • Loading branch information
danielemery committed Nov 26, 2023
1 parent 9c190e3 commit 363c148
Show file tree
Hide file tree
Showing 2 changed files with 180 additions and 2 deletions.
121 changes: 120 additions & 1 deletion src/activity/activity.service.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,128 @@
import { QuizService } from '../quiz/quiz.service';
import { UnhandledError } from '../util/common.errors';
import { ActivityService } from './activity.service';

const sut = new ActivityService();
const mockQuizService = {
getRecentQuizUploads: jest.fn(),
getRecentQuizCompletions: jest.fn(),
};

const sut = new ActivityService(mockQuizService as unknown as QuizService);
describe('activity', () => {
describe('activity.service', () => {
describe('getRecentActivity', () => {
it('must work when there is only an upload', async () => {
mockQuizService.getRecentQuizUploads.mockResolvedValueOnce([
{
date: new Date('2020-06-07'),
uploadedAt: new Date('2021-01-01'),
uploadedBy: { name: 'Grant', email: '[email protected]' },
type: 'SHARK',
},
]);
mockQuizService.getRecentQuizCompletions.mockResolvedValueOnce([]);

const actual = await sut.getRecentActivity(5);

expect(actual).toEqual([
{
date: new Date('2021-01-01'),
text: 'New SHARK quiz from June 7, 2020 uploaded by Grant',
},
]);
});
it('must work when there is only a completion', async () => {
mockQuizService.getRecentQuizUploads.mockResolvedValueOnce([]);
mockQuizService.getRecentQuizCompletions.mockResolvedValueOnce([
{
quizDate: new Date('2020-03-23'),
quizType: 'SHARK',
score: 12,
completedBy: [{ name: 'Master' }, { name: 'Beginner' }, { email: '[email protected]' }],
completionDate: new Date('2021-01-01'),
},
]);

const actual = await sut.getRecentActivity(5);

expect(actual).toEqual([
{
date: new Date('2021-01-01'),
text: 'Master, Beginner & [email protected] scored 12 on the SHARK quiz from March 23, 2020',
},
]);
});
it('must call quizService.getRecentQuizUploads and quizService.getRecentQuizCompletions and combine the results', async () => {
mockQuizService.getRecentQuizUploads.mockResolvedValueOnce([
{
date: new Date('2020-06-07'),
uploadedAt: new Date('2021-01-21'),
uploadedBy: { name: 'Bob', email: '[email protected]' },
type: 'BRAINWAVES',
},
{
date: new Date('2020-08-08'),
uploadedAt: new Date('2021-01-11'),
uploadedBy: { name: 'Tracey', email: '[email protected]' },
type: 'SHARK',
},
{
date: new Date('2020-10-11'),
uploadedAt: new Date('2021-01-02'),
uploadedBy: { name: 'Grant', email: '[email protected]' },
type: 'SHARK',
},
]);
mockQuizService.getRecentQuizCompletions.mockResolvedValueOnce([
{
quizDate: new Date('2020-03-23'),
quizType: 'BRAINWAVES',
score: 19,
completedBy: [{ name: 'Chloe' }],
completionDate: new Date('2021-01-31'),
},
{
quizDate: new Date('2020-03-23'),
quizType: 'SHARK',
score: 12,
completedBy: [{ name: 'Daniel' }],
completionDate: new Date('2021-01-05'),
},
{
quizDate: new Date('2020-03-23'),
quizType: 'SHARK',
score: 9,
completedBy: [{ name: 'Master' }, { name: 'Beginner' }, { email: '[email protected]' }],
completionDate: new Date('2021-01-01'),
},
]);

const actual = await sut.getRecentActivity(5);

expect(actual).toEqual([
{
date: new Date('2021-01-31'),
text: 'Chloe scored 19 on the BRAINWAVES quiz from March 23, 2020',
},
{
date: new Date('2021-01-21'),
text: 'New BRAINWAVES quiz from June 7, 2020 uploaded by Bob',
},
{
date: new Date('2021-01-11'),
text: 'New SHARK quiz from August 8, 2020 uploaded by Tracey',
},
{
date: new Date('2021-01-05'),
text: 'Daniel scored 12 on the SHARK quiz from March 23, 2020',
},
{
date: new Date('2021-01-02'),
text: 'New SHARK quiz from October 11, 2020 uploaded by Grant',
},
]);
});
});
describe('userListToString', () => {
it('must throw an error if the user list is empty', () => {
expect(() => sut.userListToString([])).toThrow(UnhandledError);
Expand Down
61 changes: 60 additions & 1 deletion src/activity/activity.service.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,81 @@
import { QuizService } from '../quiz/quiz.service';
import { UnhandledError } from '../util/common.errors';

export interface RecentActivityItem {
date: Date;
text: string;
action?: {
name: string;
link: string;
};
}

const quizDateFormatter = new Intl.DateTimeFormat('en', {
month: 'long',
day: 'numeric',
year: 'numeric',
});

export class ActivityService {
#quizService: QuizService;
constructor(quizService: QuizService) {
this.#quizService = quizService;
}

/**
* Get the most recent `first` activity items.
*
* These currently include quiz uploads and completions.
*
* @param first The number of activity items to return.
* @returns The most recent `first` activity items.
*/
async getRecentActivity(first = 20) {
const [recentUploads, recentCompletions] = await Promise.all([
this.#quizService.getRecentQuizUploads(first),
this.#quizService.getRecentQuizCompletions(first),
]);

const results: RecentActivityItem[] = [];

let uploadIndex = 0;
let completionIndex = 0;
const end = Math.min(first, recentUploads.length + recentCompletions.length);
while (uploadIndex + completionIndex < end) {
const upload = recentUploads[uploadIndex];
const completion = recentCompletions[completionIndex];

if (!completion || (upload && upload.uploadedAt > completion.completionDate)) {
results.push({
date: upload.uploadedAt,
text: `New ${upload.type} quiz from ${quizDateFormatter.format(
upload.date,
)} uploaded by ${this.userListToString([upload.uploadedBy])}`,
});
uploadIndex++;
} else {
results.push({
date: completion.completionDate,
text: `${this.userListToString(completion.completedBy)} scored ${completion.score} on the ${
completion.quizType
} quiz from ${quizDateFormatter.format(completion.quizDate)}`,
});
completionIndex++;
}
}

return results;
}
/**
* Get a formatted string list of users.
* @param users List of userlike objects (contain and email and optionally a name)
* @returns A formatted string list of users.
*
* // TODO move this to utils or to user service.
*/
userListToString(
users: {
name?: string;
name?: string | null;
email: string;
}[],
) {
Expand Down

0 comments on commit 363c148

Please sign in to comment.