Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
4 changes: 4 additions & 0 deletions client/e2e/application-review-workflow.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
getBody,
getToAddresses,
assertSentFromApp,
assertEmailFooter,
findBySubject,
} from './mailpit'

Expand Down Expand Up @@ -63,6 +64,7 @@ test.describe('Application Review Workflow', () => {
const rejectionEmail = findBySubject(newEmails, 'Thesis Application Rejection')
expect(rejectionEmail, 'Rejection email with correct subject should be sent').toBeDefined()
assertSentFromApp(rejectionEmail!)
assertEmailFooter(rejectionEmail!)
expect(getToAddresses(rejectionEmail!)).toContain('student4@test.local')

// Body should greet the student by first name and reference the topic title
Expand Down Expand Up @@ -133,6 +135,7 @@ test.describe('Application Review Workflow', () => {
const acceptanceEmail = findBySubject(newEmails, 'Thesis Application Acceptance')
expect(acceptanceEmail, 'Acceptance email should be sent').toBeDefined()
assertSentFromApp(acceptanceEmail!)
assertEmailFooter(acceptanceEmail!)
expect(getToAddresses(acceptanceEmail!)).toContain('student5@test.local')

// Body should greet student, mention the supervisor, and include a thesis link
Expand All @@ -145,6 +148,7 @@ test.describe('Application Review Workflow', () => {
const thesisEmail = findBySubject(newEmails, 'Thesis Created')
expect(thesisEmail, 'Thesis creation email should be sent').toBeDefined()
assertSentFromApp(thesisEmail!)
assertEmailFooter(thesisEmail!)
expect(getToAddresses(thesisEmail!)).toContain('student5@test.local')

// Body should contain the thesis title and a link to the thesis
Expand Down
3 changes: 3 additions & 0 deletions client/e2e/application-workflow.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
getBody,
getToAddresses,
assertSentFromApp,
assertEmailFooter,
findBySubject,
} from './mailpit'

Expand Down Expand Up @@ -121,6 +122,7 @@ test.describe('Application Workflow - Student submits application', () => {
const studentEmail = findBySubject(newStudentEmails, 'Thesis Application Confirmation')
expect(studentEmail, 'Student confirmation email should be sent').toBeDefined()
assertSentFromApp(studentEmail!)
assertEmailFooter(studentEmail!)
expect(getToAddresses(studentEmail!)).toContain('student@test.local')

const studentBody = getBody(studentEmail!)
Expand All @@ -141,6 +143,7 @@ test.describe('Application Workflow - Student submits application', () => {
'Examiner (examiner@test.local) should receive "New Thesis Application" email',
).toBeDefined()
assertSentFromApp(examinerChairEmail!)
assertEmailFooter(examinerChairEmail!)

const examinerBody = getBody(examinerChairEmail!)
expect(examinerBody, 'Examiner email should mention the applicant').toContain('Student')
Expand Down
13 changes: 13 additions & 0 deletions client/e2e/mailpit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,19 @@ export function assertBodyContains(message: MailpitMessage, expectedText: string
expect(body, `Expected email body to contain "${expectedText}"`).toContain(expectedText)
}

/**
* Assert that the email contains the standard footer with notification settings link.
*/
export function assertEmailFooter(message: MailpitMessage): void {
const body = getBody(message)
expect(body, 'Expected email to contain notification settings link').toContain(
'notification settings in Thesis Management',
)
expect(body, 'Expected email to contain /settings/notifications link').toContain(
'/settings/notifications',
)
}

/**
* Assert that the email was sent from the application sender.
* Verifies the From header matches the expected format: "ThesisManagement <...@...>"
Expand Down
2 changes: 2 additions & 0 deletions client/e2e/presentation-workflow.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
getBody,
getToAddresses,
assertSentFromApp,
assertEmailFooter,
} from './mailpit'

// Thesis d000-0003 is in SUBMITTED state, assigned to student3, has abstract text set
Expand Down Expand Up @@ -166,6 +167,7 @@ test.describe.serial('Presentation Workflow', () => {
const privateEmail = newEmails.find((e) => getSubject(e) === 'New Presentation scheduled')
expect(privateEmail, 'Presentation scheduled email should be sent').toBeDefined()
assertSentFromApp(privateEmail!)
assertEmailFooter(privateEmail!)
expect(getToAddresses(privateEmail!)).toContain('student3@test.local')

// Body should contain: greeting, thesis title, presentation location,
Expand Down
3 changes: 3 additions & 0 deletions client/e2e/proposal-feedback-workflow.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
getBody,
getToAddresses,
assertSentFromApp,
assertEmailFooter,
hasAttachment,
} from './mailpit'

Expand Down Expand Up @@ -59,6 +60,7 @@ test.describe('Proposal Upload - Student uploads proposal', () => {
const proposalEmail = newEmails.find((e) => getSubject(e) === 'Thesis Proposal Added')
expect(proposalEmail, 'Proposal upload email with correct subject should be sent').toBeDefined()
assertSentFromApp(proposalEmail!)
assertEmailFooter(proposalEmail!)
expect(getToAddresses(proposalEmail!)).toContain('supervisor@test.local')

// Body should reference the thesis title, the uploader name, and include a link
Expand Down Expand Up @@ -128,6 +130,7 @@ test.describe('Proposal Feedback - Supervisor requests changes', () => {
'Change request email with correct subject should be sent',
).toBeDefined()
assertSentFromApp(changeRequestEmail!)
assertEmailFooter(changeRequestEmail!)
expect(getToAddresses(changeRequestEmail!)).toContain('student2@test.local')

// Body should greet the student, reference the thesis title, mention the reviewer,
Expand Down
2 changes: 2 additions & 0 deletions client/e2e/thesis-grading-workflow.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
getBody,
getToAddresses,
assertSentFromApp,
assertEmailFooter,
} from './mailpit'

// Thesis d000-0003: SUBMITTED state, student3, supervisor2, examiner2 (DSA group)
Expand Down Expand Up @@ -174,6 +175,7 @@ test.describe.serial('Thesis Grading Workflow', () => {
const gradeEmail = newEmails.find((e) => getSubject(e) === 'Final Grade available for Thesis')
expect(gradeEmail, 'Final grade email should be sent').toBeDefined()
assertSentFromApp(gradeEmail!)
assertEmailFooter(gradeEmail!)
expect(getToAddresses(gradeEmail!)).toContain('student3@test.local')

// Body should contain: greeting, thesis title, examiner name, final grade,
Expand Down
2 changes: 2 additions & 0 deletions client/e2e/thesis-workflow.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
getBody,
getToAddresses,
assertSentFromApp,
assertEmailFooter,
} from './mailpit'

test.describe('Thesis Workflow - Examiner creates a thesis', () => {
Expand Down Expand Up @@ -66,6 +67,7 @@ test.describe('Thesis Workflow - Examiner creates a thesis', () => {
const creationEmail = newEmails.find((e) => getSubject(e) === 'Thesis Created')
expect(creationEmail, 'Thesis creation email should be sent').toBeDefined()
assertSentFromApp(creationEmail!)
assertEmailFooter(creationEmail!)
expect(getToAddresses(creationEmail!)).toContain('student4@test.local')

// Body should contain: greeting, thesis title, examiner/supervisor/student names, and link
Expand Down
1 change: 1 addition & 0 deletions client/src/requests/responses/researchGroupSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export interface IResearchGroupSettingsPhase {

export interface IResearchGroupSettingsEmail {
applicationNotificationEmail?: string | null
emailSignature?: string | null
}

export interface IResearchGroupSettingsWritingGuide {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ public ResponseEntity<ResearchGroupSettingsDTO> createOrUpdateRejectSettings(@Pa
String validatedEmail = RequestValidator.validateEmailAllowNull(
newSettings.emailSettings().applicationNotificationEmail() == null ? null : newSettings.emailSettings().applicationNotificationEmail().trim());
toSave.setApplicationNotificationEmail(validatedEmail);
String signature = newSettings.emailSettings().emailSignature();
toSave.setEmailSignature(signature != null && !signature.trim().isEmpty() ? signature.trim() : null);
Comment thread
krusche marked this conversation as resolved.
}
if (newSettings.writingGuideSettings() != null) {
String link = newSettings.writingGuideSettings().scientificWritingGuideLink();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
import de.tum.cit.aet.thesis.entity.ResearchGroupSettings;

public record ResearchGroupSettingsEmailDTO(
String applicationNotificationEmail
String applicationNotificationEmail,
String emailSignature
) {
public static ResearchGroupSettingsEmailDTO fromEntity(ResearchGroupSettings settings) {
return new ResearchGroupSettingsEmailDTO(settings.getApplicationNotificationEmail());
return new ResearchGroupSettingsEmailDTO(settings.getApplicationNotificationEmail(), settings.getEmailSignature());
}
Comment thread
krusche marked this conversation as resolved.
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,7 @@ public class ResearchGroupSettings {

@Column(name = "include_application_data_in_email", nullable = false)
private boolean includeApplicationDataInEmail = false;

@Column(name = "email_signature")
private String emailSignature;
}
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ public void sendApplicationCreatedEmail(Application application) {
"en");
MailBuilder studentMailBuilder = new MailBuilder(config,
studentEmailTemplate.getSubject(), studentEmailTemplate.getBodyHtml());
applyGroupSignature(studentMailBuilder, application.getResearchGroup());
studentMailBuilder
.addPrimaryRecipient(application.getUser())
.addStoredAttachment(application.getUser().getCvFilename(),
Expand All @@ -123,7 +124,7 @@ public void sendApplicationCreatedEmail(Application application) {
}

private MailBuilder prepareApplicationCreatedMailBuilder(Application application, EmailTemplate template) {
return new MailBuilder(config, template.getSubject(), template.getBodyHtml())
MailBuilder builder = new MailBuilder(config, template.getSubject(), template.getBodyHtml())
.addStoredAttachment(application.getUser().getCvFilename(),
getUserFilename(application.getUser(), "CV",
application.getUser().getCvFilename()))
Expand All @@ -134,6 +135,8 @@ private MailBuilder prepareApplicationCreatedMailBuilder(Application application
getUserFilename(application.getUser(), "Degree Report",
application.getUser().getDegreeFilename()))
.fillApplicationPlaceholders(application);
applyGroupSignature(builder, application.getResearchGroup());
return builder;
}

private MailBuilder prepareMinimalApplicationMailBuilder(Application application) {
Expand Down Expand Up @@ -167,7 +170,9 @@ private MailBuilder prepareMinimalApplicationMailBuilder(Application application
+ "<a target=\"_blank\" rel=\"noopener noreferrer nofollow\" href=\"" + applicationUrl + "\">"
+ applicationUrl + "</a></p>";
Comment thread
krusche marked this conversation as resolved.
Outdated

return new MailBuilder(config, subject, body);
MailBuilder builder = new MailBuilder(config, subject, body);
applyGroupSignature(builder, application.getResearchGroup());
return builder;
}

/**
Expand Down Expand Up @@ -228,6 +233,7 @@ public void sendApplicationAcceptanceEmail(Application application, Thesis thesi
templateCase,
"en");
MailBuilder mailBuilder = new MailBuilder(config, emailTemplate.getSubject(), emailTemplate.getBodyHtml());
applyGroupSignature(mailBuilder, application.getResearchGroup());
mailBuilder
.addPrimaryRecipient(application.getUser())
.addSecondaryRecipient(advisor)
Expand All @@ -250,6 +256,7 @@ public void sendApplicationRejectionEmail(Application application, ApplicationRe
reason.getTemplateCase(),
"en");
MailBuilder mailBuilder = new MailBuilder(config, emailTemplate.getSubject(), emailTemplate.getBodyHtml());
applyGroupSignature(mailBuilder, application.getResearchGroup());
mailBuilder
.addPrimaryRecipient(application.getUser())
.addDefaultBccRecipients(application.getResearchGroup().getHead().getEmail())
Expand All @@ -269,6 +276,7 @@ public void sendApplicationReminderEmail(User user, long unreviewedApplications)
"APPLICATION_REMINDER",
"en");
MailBuilder mailBuilder = new MailBuilder(config, emailTemplate.getSubject(), emailTemplate.getBodyHtml());
applyGroupSignature(mailBuilder, user.getResearchGroup());
mailBuilder
.addPrimaryRecipient(user)
.addNotificationName("unreviewed-application-reminder")
Expand All @@ -294,6 +302,7 @@ public void sendApplicationAutomaticRejectReminderEmail(User user, List<Applicat
model.put("clientHost", config.getClientHost());

MailBuilder mailBuilder = new MailBuilder(config, emailTemplate.getSubject(), emailTemplate.getBodyHtml());
applyGroupSignature(mailBuilder, user.getResearchGroup());
mailBuilder
.addPrimaryRecipient(user)
.addNotificationName(emailTemplate.getSubject())
Expand All @@ -314,6 +323,7 @@ public void sendInterviewInvitationEmail(Interviewee interviewee, Boolean firstI
"en");

MailBuilder mailBuilder = new MailBuilder(config, emailTemplate.getSubject(), emailTemplate.getBodyHtml());
applyGroupSignature(mailBuilder, interviewee.getApplication().getResearchGroup());
mailBuilder
.addPrimaryRecipient(interviewee.getApplication().getUser())
.addNotificationName(emailTemplate.getSubject())
Expand Down Expand Up @@ -342,6 +352,7 @@ public void sendInterviewSlotConfirmationEmail(InterviewSlot slot, String type)
User supervisor = slot.getInterviewProcess().getTopic().getSupervisors().getFirst();

MailBuilder mailBuilder = new MailBuilder(config, emailTemplate.getSubject(), emailTemplate.getBodyHtml());
applyGroupSignature(mailBuilder, slot.getInterviewee().getApplication().getResearchGroup());
mailBuilder
.addPrimaryRecipient(slot.getInterviewee().getApplication().getUser())
.addSecondaryRecipient(supervisor)
Expand All @@ -364,6 +375,7 @@ public void sendThesisCreatedEmail(User creatingUser, Thesis thesis) {
"THESIS_CREATED",
"en");
MailBuilder mailBuilder = new MailBuilder(config, emailTemplate.getSubject(), emailTemplate.getBodyHtml());
applyGroupSignature(mailBuilder, thesis.getResearchGroup());
mailBuilder
.sendToThesisStudents(thesis)
.addDefaultBccRecipients(thesis.getResearchGroup().getHead().getEmail())
Expand All @@ -385,6 +397,7 @@ public void sendThesisClosedEmail(User deletingUser, Thesis thesis) {
"THESIS_CLOSED",
"en");
MailBuilder mailBuilder = new MailBuilder(config, emailTemplate.getSubject(), emailTemplate.getBodyHtml());
applyGroupSignature(mailBuilder, thesis.getResearchGroup());
mailBuilder
.sendToThesisStudents(thesis)
.addDefaultBccRecipients(thesis.getResearchGroup().getHead().getEmail())
Expand All @@ -405,6 +418,7 @@ public void sendProposalUploadedEmail(ThesisProposal proposal) {
"THESIS_PROPOSAL_UPLOADED",
"en");
MailBuilder mailBuilder = new MailBuilder(config, emailTemplate.getSubject(), emailTemplate.getBodyHtml());
applyGroupSignature(mailBuilder, proposal.getResearchGroup());
mailBuilder
.addPrimarySender(proposal.getCreatedBy())
.sendToThesisSupervisors(proposal.getThesis())
Expand All @@ -425,6 +439,7 @@ public void sendProposalAcceptedEmail(ThesisProposal proposal) {
"THESIS_PROPOSAL_ACCEPTED",
"en");
MailBuilder mailBuilder = new MailBuilder(config, emailTemplate.getSubject(), emailTemplate.getBodyHtml());
applyGroupSignature(mailBuilder, proposal.getResearchGroup());
mailBuilder
.addPrimarySender(proposal.getApprovedBy())
.sendToThesisStudents(proposal.getThesis())
Expand All @@ -446,6 +461,7 @@ public void sendProposalChangeRequestEmail(User reviewingUser, Thesis thesis) {
"THESIS_PROPOSAL_REJECTED",
"en");
MailBuilder mailBuilder = new MailBuilder(config, emailTemplate.getSubject(), emailTemplate.getBodyHtml());
applyGroupSignature(mailBuilder, thesis.getResearchGroup());
mailBuilder
.sendToThesisStudents(thesis)
.addNotificationName(NOTIFICATION_NAME_START + thesis.getId())
Expand Down Expand Up @@ -473,6 +489,7 @@ public void sendNewCommentEmail(ThesisComment comment) {
"THESIS_COMMENT_POSTED",
"en");
MailBuilder mailBuilder = new MailBuilder(config, emailTemplate.getSubject(), emailTemplate.getBodyHtml());
applyGroupSignature(mailBuilder, comment.getResearchGroup());

if (comment.getType() == ThesisCommentType.SUPERVISOR) {
mailBuilder.sendToThesisSupervisors(comment.getThesis());
Expand Down Expand Up @@ -509,6 +526,7 @@ public void sendScheduledPresentationEmail(String action, ThesisPresentation pre
"en");
MailBuilder privateMailBuilder = new MailBuilder(config, privateEmailTemplate.getSubject(),
privateEmailTemplate.getBodyHtml());
applyGroupSignature(privateMailBuilder, presentation.getResearchGroup());
privateMailBuilder
.addPrimarySender(presentation.getCreatedBy())
.sendToThesisStudents(presentation.getThesis())
Expand All @@ -523,6 +541,7 @@ public void sendScheduledPresentationEmail(String action, ThesisPresentation pre
"en");
MailBuilder publicMailBuilder = new MailBuilder(config, publicEmailTemplate.getSubject(),
publicEmailTemplate.getBodyHtml());
applyGroupSignature(publicMailBuilder, presentation.getResearchGroup());
publicMailBuilder
.addPrimaryRecipient(presentation.getThesis().getStudents().getFirst())
.fillThesisPresentationPlaceholders(presentation);
Expand Down Expand Up @@ -559,6 +578,7 @@ public void sendPresentationDeletedEmail(User deletingUser, ThesisPresentation p
"en");
MailBuilder mailBuilder = new MailBuilder(config, emailTemplate.getSubject(),
emailTemplate.getBodyHtml());
applyGroupSignature(mailBuilder, presentation.getResearchGroup());
mailBuilder
.sendToThesisStudents(presentation.getThesis())
.addNotificationName(NOTIFICATION_NAME_START + presentation.getThesis().getId())
Expand All @@ -573,6 +593,7 @@ public void sendPresentationDeletedEmail(User deletingUser, ThesisPresentation p
"en");
MailBuilder publicMailBuilder = new MailBuilder(config, publicEmailTemplate.getSubject(),
publicEmailTemplate.getBodyHtml());
applyGroupSignature(publicMailBuilder, presentation.getResearchGroup());
publicMailBuilder
.addPrimaryRecipient(presentation.getThesis().getStudents().getFirst())
.fillThesisPresentationPlaceholders(presentation);
Expand All @@ -597,6 +618,7 @@ public void sendFinalSubmissionEmail(Thesis thesis) {
"en");
MailBuilder mailBuilder = new MailBuilder(config, emailTemplate.getSubject(),
emailTemplate.getBodyHtml());
applyGroupSignature(mailBuilder, thesis.getResearchGroup());
mailBuilder
.sendToThesisSupervisors(thesis)
.addNotificationName(NOTIFICATION_NAME_START + thesis.getId())
Expand All @@ -618,6 +640,7 @@ public void sendAssessmentAddedEmail(ThesisAssessment assessment) {
"en");
MailBuilder mailBuilder = new MailBuilder(config, emailTemplate.getSubject(),
emailTemplate.getBodyHtml());
applyGroupSignature(mailBuilder, assessment.getThesis().getResearchGroup());
mailBuilder
.addPrimarySender(assessment.getCreatedBy())
.sendToThesisExaminers(assessment.getThesis())
Expand All @@ -638,6 +661,7 @@ public void sendFinalGradeEmail(Thesis thesis) {
"en");
MailBuilder mailBuilder = new MailBuilder(config, emailTemplate.getSubject(),
emailTemplate.getBodyHtml());
applyGroupSignature(mailBuilder, thesis.getResearchGroup());
mailBuilder
.sendToThesisStudents(thesis)
.addNotificationName(NOTIFICATION_NAME_START + thesis.getId())
Expand Down Expand Up @@ -721,12 +745,21 @@ public void sendThesisAnonymizationReminderEmail(ResearchGroup researchGroup, Li
model.put("theses", thesisTitles);

MailBuilder mailBuilder = new MailBuilder(config, emailTemplate.getSubject(), emailTemplate.getBodyHtml());
applyGroupSignature(mailBuilder, researchGroup);
mailBuilder
.addPrimaryRecipient(researchGroup.getHead())
.fillPlaceholders(model)
.send(javaMailSender, uploadService);
}

private void applyGroupSignature(MailBuilder builder, ResearchGroup group) {
if (group != null && group.getResearchGroupSettings() != null
&& group.getResearchGroupSettings().getEmailSignature() != null
&& !group.getResearchGroupSettings().getEmailSignature().isBlank()) {
builder.withSignature(group.getResearchGroupSettings().getEmailSignature());
Comment thread
krusche marked this conversation as resolved.
}
}

private String getThesisFilename(Thesis thesis, String name, String originalFilename) {
StringBuilder builder = new StringBuilder();

Expand Down
Loading
Loading