Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 159 additions & 0 deletions src/web/services/feedback-session-csv.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import { TestBed } from '@angular/core/testing';
import { FeedbackSessionCsvService } from './feedback-session-csv.service';

describe('FeedbackSessionCsvService', () => {
let service: FeedbackSessionCsvService;

beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(FeedbackSessionCsvService);
});

it('should generate submission link csv', () => {
const result: string = service.getCsvForSubmissionLinks({
courseId: 'CS101',
courseName: 'Programming',
feedbackSessionName: 'Week 1 Feedback',
deadline: 'Fri, 10 Jul 2026, 11:59 PM',
sessionInstructions: 'Please answer all questions.',
coOwnerEmails: 'instructor@example.com',
supportEmail: 'teammates@example.com',
recipients: [
{
email: 'student@example.com',
name: 'Alex Tan',
submissionLink: 'https://example.com/submit',
},
],
});

expect(result).toEqual(
[
'Email,Name,Submission Link,Course Name,Course ID,Feedback Session Name,Deadline,Session Instructions,Co-owner Emails,Support Email',
'student@example.com,Alex Tan,https://example.com/submit,Programming,CS101,Week 1 Feedback,"Fri, 10 Jul 2026, 11:59 PM",Please answer all questions.,instructor@example.com,teammates@example.com',
].join('\r\n'),
);
});

it('should generate result link csv', () => {
const result: string = service.getCsvForResultLinks({
courseId: 'CS101',
courseName: 'Programming',
feedbackSessionName: 'Week 1 Feedback',
deadline: 'Fri, 10 Jul 2026, 11:59 PM',
coOwnerEmails: 'instructor@example.com',
supportEmail: 'teammates@example.com',
recipients: [
{
email: 'student@example.com',
name: 'Alex Tan',
resultLink: 'https://example.com/result',
},
],
});

expect(result).toEqual(
[
'Email,Name,Result Link,Course Name,Course ID,Feedback Session Name,Deadline,Co-owner Emails,Support Email',
'student@example.com,Alex Tan,https://example.com/result,Programming,CS101,Week 1 Feedback,"Fri, 10 Jul 2026, 11:59 PM",instructor@example.com,teammates@example.com',
].join('\r\n'),
);
});

it('should generate one data row for each recipient', () => {
const result: string = service.getCsvForResultLinks({
courseId: 'CS101',
courseName: 'Programming',
feedbackSessionName: 'Week 1 Feedback',
deadline: 'Fri, 10 Jul 2026, 11:59 PM',
recipients: [
{
email: 'student1@example.com',
name: 'Alex Tan',
resultLink: 'https://example.com/result-1',
},
{
email: 'student2@example.com',
name: 'Bala Lee',
resultLink: 'https://example.com/result-2',
},
],
});

expect(result).toEqual(
[
'Email,Name,Result Link,Course Name,Course ID,Feedback Session Name,Deadline,Co-owner Emails,Support Email',
'student1@example.com,Alex Tan,https://example.com/result-1,Programming,CS101,Week 1 Feedback,"Fri, 10 Jul 2026, 11:59 PM",,',
'student2@example.com,Bala Lee,https://example.com/result-2,Programming,CS101,Week 1 Feedback,"Fri, 10 Jul 2026, 11:59 PM",,',
].join('\r\n'),
);
});

it('should use recipient-specific deadlines when present', () => {
const result: string = service.getCsvForSubmissionLinks({
courseId: 'CS101',
courseName: 'Programming',
feedbackSessionName: 'Week 1 Feedback',
deadline: 'Default deadline',
sessionInstructions: 'Please answer all questions.',
recipients: [
{
email: 'student@example.com',
name: 'Alex Tan',
submissionLink: 'https://example.com/submit',
deadline: 'Extended deadline',
},
],
});

expect(result).toContain('Extended deadline');
expect(result).not.toContain('Default deadline');
});

it('should leave optional contact fields empty when omitted', () => {
const result: string = service.getCsvForResultLinks({
courseId: 'CS101',
courseName: 'Programming',
feedbackSessionName: 'Week 1 Feedback',
deadline: 'Fri, 10 Jul 2026, 11:59 PM',
recipients: [
{
email: 'student@example.com',
name: 'Alex Tan',
resultLink: 'https://example.com/result',
},
],
});

expect(result).toEqual(
[
'Email,Name,Result Link,Course Name,Course ID,Feedback Session Name,Deadline,Co-owner Emails,Support Email',
'student@example.com,Alex Tan,https://example.com/result,Programming,CS101,Week 1 Feedback,"Fri, 10 Jul 2026, 11:59 PM",,',
].join('\r\n'),
);
});

it('should escape csv fields', () => {
const result: string = service.getCsvForSubmissionLinks({
courseId: 'CS101',
courseName: 'Programming, "Basics"',
feedbackSessionName: 'Week 1\r\nFeedback',
deadline: 'Fri, 10 Jul 2026, 11:59 PM',
sessionInstructions: 'Line 1\nLine 2',
recipients: [
{
email: 'student@example.com',
name: 'Alex "A" Tan',
submissionLink: 'https://example.com/submit?a=1,b=2',
},
],
});

expect(result).toEqual(
[
'Email,Name,Submission Link,Course Name,Course ID,Feedback Session Name,Deadline,Session Instructions,Co-owner Emails,Support Email',
'student@example.com,"Alex ""A"" Tan","https://example.com/submit?a=1,b=2","Programming, ""Basics""",CS101,"Week 1\r\nFeedback","Fri, 10 Jul 2026, 11:59 PM","Line 1\nLine 2",,',
].join('\r\n'),
);
});
});
102 changes: 102 additions & 0 deletions src/web/services/feedback-session-csv.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { Injectable } from '@angular/core';
import { CsvHelper } from './csv-helper';

export interface FeedbackSessionCsvData {
courseId: string;
courseName: string;
feedbackSessionName: string;
deadline: string;
coOwnerEmails?: string;
supportEmail?: string;
}

export interface FeedbackSessionSubmissionCsvData extends FeedbackSessionCsvData {
sessionInstructions: string;
recipients: FeedbackSessionSubmissionCsvRecipient[];
}

export interface FeedbackSessionResultCsvData extends FeedbackSessionCsvData {
recipients: FeedbackSessionResultCsvRecipient[];
}

export interface FeedbackSessionSubmissionCsvRecipient {
email: string;
name: string;
submissionLink: string;
deadline?: string;
}

export interface FeedbackSessionResultCsvRecipient {
email: string;
name: string;
resultLink: string;
deadline?: string;
}

/**
* Service to generate CSV data for feedback session emails.
*/
@Injectable({
providedIn: 'root',
})
export class FeedbackSessionCsvService {
getCsvForSubmissionLinks(data: FeedbackSessionSubmissionCsvData): string {
const csvRows: string[][] = [
[
'Email',
'Name',
'Submission Link',
'Course Name',
'Course ID',
'Feedback Session Name',
'Deadline',
'Session Instructions',
'Co-owner Emails',
'Support Email',
],
...data.recipients.map((recipient: FeedbackSessionSubmissionCsvRecipient) => [
recipient.email,
recipient.name,
recipient.submissionLink,
data.courseName,
data.courseId,
data.feedbackSessionName,
recipient.deadline ?? data.deadline,
data.sessionInstructions,
data.coOwnerEmails ?? '',
data.supportEmail ?? '',
]),
];

return CsvHelper.convertCsvContentsToCsvString(csvRows);
}

getCsvForResultLinks(data: FeedbackSessionResultCsvData): string {
const csvRows: string[][] = [
[
'Email',
'Name',
'Result Link',
'Course Name',
'Course ID',
'Feedback Session Name',
'Deadline',
'Co-owner Emails',
'Support Email',
],
...data.recipients.map((recipient: FeedbackSessionResultCsvRecipient) => [
recipient.email,
recipient.name,
recipient.resultLink,
data.courseName,
data.courseId,
data.feedbackSessionName,
recipient.deadline ?? data.deadline,
data.coOwnerEmails ?? '',
data.supportEmail ?? '',
]),
];

return CsvHelper.convertCsvContentsToCsvString(csvRows);
}
}
Loading