Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

927 recruitment rejection email #1184

Merged
merged 38 commits into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
5988260
new try fixing check error
JohanneD Nov 2, 2023
8874a84
Merge branch 'master' of github.com:Samfundet/Samfundet4
JohanneD Nov 9, 2023
2f630ae
Merge branch 'master' of github.com:Samfundet/Samfundet4
JohanneD Nov 30, 2023
8add66e
Merge branch 'master' of github.com:Samfundet/Samfundet4
JohanneD Jan 18, 2024
41f760e
Merge branch 'master' of github.com:Samfundet/Samfundet4
JohanneD Jan 25, 2024
b317b11
added frontend for rejection mail
JohanneD Jan 25, 2024
eb108cc
Merge branch 'master' of github.com:Samfundet/Samfundet4
JohanneD Feb 8, 2024
939668d
Merge branch 'master' into 927-recruitment-rejection-email
JohanneD Feb 8, 2024
3166cab
made RejectedApplicantsView for getting rejected applicants
JohanneD Apr 11, 2024
b8043d5
made route for rejected_applicants
JohanneD Apr 11, 2024
fb749e5
Made route and button to RejectionMail page
hei98 Apr 11, 2024
740bf63
Add rejectedUsers to RejectionMail page
hei98 Apr 25, 2024
9b8561e
feat: SendRejectionMail
emsoraffa Aug 20, 2024
9f13c9b
Merge branch 'master' into 927-recruitment-rejection-email
emsoraffa Sep 3, 2024
d95441d
feat: rejection email test
emsoraffa Sep 3, 2024
b5d611b
feat: error handling
emsoraffa Sep 3, 2024
d73dc89
Fix: test mail to file
emsoraffa Sep 10, 2024
39c2661
Merge branch 'master' into 927-recruitment-rejection-email
emsoraffa Sep 10, 2024
7adcdfd
Delete frontend/src/AppRoutes.tsx
emsoraffa Sep 10, 2024
5927b65
feat: postrejection mail
emsoraffa Sep 10, 2024
f45672a
Merge remote-tracking branch 'refs/remotes/origin/927-recruitment-rej…
emsoraffa Sep 10, 2024
066266c
Formatting and tests
emsoraffa Sep 10, 2024
963d28b
linting and formatting
emsoraffa Sep 21, 2024
af07a69
Merge branch 'master' into 927-recruitment-rejection-email
emsoraffa Sep 21, 2024
580fc41
removed accidental autoformatting
emsoraffa Sep 21, 2024
1e04f6b
removed unneccessary change in routes.py
emsoraffa Sep 24, 2024
f56ec17
linting and formatting
emsoraffa Sep 24, 2024
eae1de1
Merge branch 'master' into 927-recruitment-rejection-email
emsoraffa Sep 24, 2024
4a4742c
temp remove views.py to force git to detect ruff formatting change
emsoraffa Sep 24, 2024
78bdfb5
re-add views.py
emsoraffa Sep 24, 2024
0263f42
Merge branch 'master' into 927-recruitment-rejection-email
emsoraffa Oct 1, 2024
1849ac1
Feat: translations
emsoraffa Oct 1, 2024
358b450
Fix: Magic Number
emsoraffa Oct 1, 2024
fee88f9
Fix: Logic for filtering out users
emsoraffa Oct 1, 2024
b762b29
Fix: added check for withdrawn applications
emsoraffa Oct 1, 2024
f2324be
Pipeline
emsoraffa Oct 1, 2024
a10bf02
Merge branch 'master' into 927-recruitment-rejection-email
emsoraffa Oct 1, 2024
1f2b6d5
Merge branch 'master' into 927-recruitment-rejection-email
emsoraffa Oct 3, 2024
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
1 change: 1 addition & 0 deletions backend/root/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@

LOGFILENAME = BASE_DIR / 'logs' / '.log'
SQL_LOG_FILE = BASE_DIR / 'logs' / 'sql.log'
TEST_EMAIL_FILE = BASE_DIR / 'logs' / 'test_email.txt'

LOGGING = {
'version': 1,
Expand Down
29 changes: 17 additions & 12 deletions backend/samfundet/tests/test_email.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,34 @@

from django.core import mail

from root.settings.base import TEST_EMAIL_FILE

def test_send_email():
# Send email

def test_send_email_and_save_to_file():
subject = 'Subject here'
message = 'Here is the message.'
from_email = '[email protected]'
recievers = ['[email protected]']
recipients = ['[email protected]']

mail.send_mail(
subject,
message,
from_email,
recievers,
recipients,
fail_silently=False,
)

# Check that one message has been sent
assert len(mail.outbox) == 1
email = mail.outbox[0]

# Check the subject of the first message
assert mail.outbox[0].subject == subject

# Check the recipient of the first message
assert mail.outbox[0].to == recievers
assert email.subject == subject
assert email.from_email == from_email
assert email.to == recipients
assert email.body == message

# Check the body of the first message
assert mail.outbox[0].body == message
# Writing email to a file for inspection
with open(TEST_EMAIL_FILE, 'w') as f:
f.write(f'Subject: {email.subject}\n')
f.write(f'From: {email.from_email}\n')
f.write(f"To: {', '.join(email.to)}\n")
f.write(f'Message: {email.body}\n')
1 change: 1 addition & 0 deletions backend/samfundet/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@
name='recruitment_withdraw_application_recruiter',
),
path('active-recruitment-positions/', views.ActiveRecruitmentPositionsView.as_view(), name='active_recruitment_positions'),
path('rejected-applicants/', views.SendRejectionMailView.as_view(), name='rejected_applicants/'),
path('recruitment-applicants-without-interviews/<int:pk>/', views.ApplicantsWithoutInterviewsView.as_view(), name='applicants_without_interviews'),
path(
'recruitment-applicants-without-three-interview-criteria/<int:pk>/',
Expand Down
43 changes: 43 additions & 0 deletions backend/samfundet/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@
from rest_framework.exceptions import PermissionDenied
from rest_framework.permissions import AllowAny, BasePermission, IsAuthenticated, DjangoModelPermissions, DjangoModelPermissionsOrAnonReadOnly

from django.conf import settings
from django.http import QueryDict, HttpResponse
from django.utils import timezone
from django.core.mail import send_mail
from django.db.models import Q, Count, QuerySet
from django.shortcuts import get_object_or_404
from django.contrib.auth import login, logout
Expand Down Expand Up @@ -714,6 +716,47 @@ def get_queryset(self) -> Response | None:
return None


class SendRejectionMailView(APIView):
robines marked this conversation as resolved.
Show resolved Hide resolved
def post(self, request: Request) -> Response:
try:
subject = request.data.get('subject')
text = request.data.get('text')

recruitment = request.data.get('recruitment')
if recruitment is None:
return Response(status=status.HTTP_400_BAD_REQUEST)

# Only users who have never been contacted with an offer should get a rejection mail
# Retrieve all users who has a non-withdrawn rejected application in current recruitment
rejected_users = User.objects.filter(
recruitmentapplication__recruitment=recruitment,
recruitmentapplication__recruiter_status=RecruitmentStatusChoices.REJECTION,
recruitmentapplication__withdrawn=False,
)

# Retrieve all users who have been contacted with an offer
contacted_users = User.objects.filter(
recruitmentapplication__recruitment=recruitment,
recruitmentapplication__recruiter_status__in=[RecruitmentStatusChoices.CALLED_AND_ACCEPTED, RecruitmentStatusChoices.CALLED_AND_REJECTED],
)

# Remove users who have been contacted with an offer from the rejected users list
final_rejected_users = rejected_users.exclude(id__in=contacted_users.values('id'))

rejected_user_mails = list(final_rejected_users.values_list('email', flat=True))

send_mail(
subject,
text,
settings.EMAIL_HOST_USER,
rejected_user_mails,
fail_silently=False,
)
return Response(status=status.HTTP_200_OK)
except Exception as e:
return Response(str(e), status=status.HTTP_500_INTERNAL_SERVER_ERROR)


@method_decorator(ensure_csrf_cookie, 'dispatch')
class RecruitmentUnprocessedApplicationsPerRecruitment(ListAPIView):
permission_classes = [IsAuthenticated]
Expand Down
11 changes: 11 additions & 0 deletions frontend/src/Components/RejectionMail/RejectionMail.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { ComponentMeta, ComponentStory } from '@storybook/react';
import { RejectionMail } from './RejectionMail';

export default {
title: 'Components/RejectionMail',
component: RejectionMail,
} as ComponentMeta<typeof RejectionMail>;

const Template: ComponentStory<typeof RejectionMail> = () => <RejectionMail />;

export const Basic = Template.bind({});
37 changes: 37 additions & 0 deletions frontend/src/Components/RejectionMail/RejectionMail.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { t } from 'i18next';
import { useState } from 'react';
emilte marked this conversation as resolved.
Show resolved Hide resolved
import { useParams } from 'react-router-dom';
import { toast } from 'react-toastify';
import { postRejectionMail } from '~/api';
import { KEY } from '~/i18n/constants';
import { Button } from '../Button';
import { InputField } from '../InputField';
import { TextAreaField } from '../TextAreaField';

export function RejectionMail() {
const [text, setText] = useState('');
const [subject, setSubject] = useState('');
const recruitmentId = useParams().recruitmentId;

function handleSubmit() {
if (recruitmentId) {
postRejectionMail(recruitmentId, { subject, text });
toast.success(t(KEY.common_save_successful));
} else {
toast.error(t(KEY.common_something_went_wrong));
console.error('Recruitment id cannot be null');
}
}

return (
<>
<label>{t(KEY.common_email_subject)}</label>
<InputField type="text" value={subject} onChange={setSubject} />
<label>{t(KEY.common_email)}</label>
<TextAreaField value={text} onChange={setText} />
<Button theme="green" onClick={handleSubmit}>
Submit
</Button>
</>
);
}
1 change: 1 addition & 0 deletions frontend/src/Components/RejectionMail/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { RejectionMail } from './RejectionMail';
15 changes: 15 additions & 0 deletions frontend/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import type {
InterviewDto,
InterviewRoomDto,
KeyValueDto,
MailDto,
MenuDto,
MenuItemDto,
OccupiedTimeslotDto,
Expand Down Expand Up @@ -1008,3 +1009,17 @@ export async function postFeedback(feedbackData: FeedbackDto): Promise<AxiosResp

return response;
}

export async function postRejectionMail(recruitmentId: string, rejectionMail: MailDto): Promise<AxiosResponse> {
const url =
BACKEND_DOMAIN +
reverse({
pattern: ROUTES.backend.samfundet__rejected_applicants,
queryParams: {
recruitment: recruitmentId,
},
});
const response = await axios.post(url, rejectionMail, { withCredentials: true });

return response;
}
5 changes: 5 additions & 0 deletions frontend/src/dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,11 @@ export type FoodCategoryDto = {
order?: number;
};

export type MailDto = {
subject: string;
text: string;
};

export type MenuItemDto = {
id?: number;
name_nb?: string;
Expand Down
1 change: 1 addition & 0 deletions frontend/src/i18n/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ export const KEY = {
common_register: 'common_register',
common_users: 'common_users',
common_email: 'common_email',
common_email_subject: 'common_email_subject',
common_total: 'common_total',
common_roles: 'common_roles',
common_guests: 'common_guests',
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/i18n/translations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ export const nb = prepareTranslations({
[KEY.common_unknown]: 'Ukjent',
[KEY.common_lastname]: 'Etternavn',
[KEY.common_email]: 'Epost',
[KEY.common_email_subject]: 'Emne',
[KEY.common_phonenumber]: 'Telefonnummer',
[KEY.common_register]: 'Registrer',
[KEY.common_password]: 'passord',
Expand Down Expand Up @@ -547,6 +548,7 @@ export const en = prepareTranslations({
[KEY.common_unknown]: 'Unknown',
[KEY.common_register]: 'Register',
[KEY.common_email]: 'Email',
[KEY.common_email_subject]: 'Subject',
[KEY.common_phonenumber]: 'Phone number',
[KEY.common_lastname]: 'Last name',
[KEY.common_password]: 'password',
Expand Down
1 change: 1 addition & 0 deletions frontend/src/routes/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,7 @@ export const ROUTES_BACKEND = {
samfundet__recruitment_user_priority_update: '/recruitment-user-priority-update/:pk/',
samfundet__recruitment_withdraw_application_recruiter: '/recruitment-withdraw-application-recruiter/:pk/',
samfundet__active_recruitment_positions: '/active-recruitment-positions/',
samfundet__rejected_applicants: '/rejected-applicants/',
samfundet__applicants_without_interviews: '/recruitment-applicants-without-interviews/:pk/',
samfundet__applicants_without_three_interview_criteria:
'/recruitment-applicants-without-three-interview-criteria/:pk/',
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/routes/frontend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ export const ROUTES_FRONTEND = {
admin_recruitment_users_without_interview: '/control-panel/recruitment/:recruitmentId/users-without-applications/',
admin_recruitment_overview: '/control-panel/recruitment/:recruitmentId/recruitment-overview/',
admin_recruitment_gang_overview: '/control-panel/recruitment/:recruitmentId/gang-overview/',
admin_recruitment_gang_overview_rejection_email:
'/control-panel/recruitment/:recruitmentId/gang-overview/rejection-email/',
admin_recruitment_gang_position_overview: '/control-panel/recruitment/:recruitmentId/gang/:gangId',
admin_recruitment_gang_position_create: '/control-panel/recruitment/:recruitmentId/gang/:gangId/create/',
admin_recruitment_gang_position_edit: '/control-panel/recruitment/:recruitmentId/gang/:gangId/edit/:positionId',
Expand Down
Loading