Skip to content

Commit

Permalink
feat: allow handler to mark last application message as unread (#3120)…
Browse files Browse the repository at this point in the history
… (HL-1117)
  • Loading branch information
EmiliaMakelaVincit committed Jun 27, 2024
1 parent 7423486 commit 79efe24
Show file tree
Hide file tree
Showing 16 changed files with 274 additions and 61 deletions.
97 changes: 74 additions & 23 deletions backend/benefit/messages/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -559,19 +559,10 @@ def test_applications_list_with_message_count(
assert response.data["unread_messages_count"] == 0


@pytest.mark.parametrize(
"view_name",
[
"applicant-message-list",
"handler-message-list",
],
)
def test_list_messages_read_receipt(
def test_list_messages_read_receipt_applicant(
api_client,
handler_api_client,
handling_application,
mock_get_organisation_roles_and_create_company,
view_name,
):
handling_application.company = mock_get_organisation_roles_and_create_company
handling_application.save()
Expand All @@ -586,21 +577,81 @@ def test_list_messages_read_receipt(
assert Message.objects.filter(seen_by_applicant=True).count() == 0
assert Message.objects.filter(seen_by_handler=True).count() == 0

if view_name == "applicant-message-list":
result = api_client.get(
reverse(view_name, kwargs={"application_pk": handling_application.pk})
result = api_client.get(
reverse(
"applicant-message-list", kwargs={"application_pk": handling_application.pk}
)
assert result.status_code == 200
assert len(result.data) == 2
assert Message.objects.filter(seen_by_applicant=True).count() == 2
assert Message.objects.filter(seen_by_handler=True).count() == 0
else:
result = handler_api_client.get(
reverse(view_name, kwargs={"application_pk": handling_application.pk})
)
assert result.status_code == 200
assert len(result.data) == 2
assert Message.objects.filter(seen_by_applicant=True).count() == 2
assert Message.objects.filter(seen_by_handler=True).count() == 0


@pytest.mark.parametrize(
"first_message_type,second_message_type,expected_count_after_mark_unread",
[
(MessageType.APPLICANT_MESSAGE, MessageType.HANDLER_MESSAGE, 2),
(MessageType.HANDLER_MESSAGE, MessageType.APPLICANT_MESSAGE, 1),
],
)
def test_list_messages_read_receipt_handler(
handler_api_client,
handling_application,
mock_get_organisation_roles_and_create_company,
first_message_type,
second_message_type,
expected_count_after_mark_unread,
):
handling_application.company = mock_get_organisation_roles_and_create_company
handling_application.save()

first_message = MessageFactory(
application=handling_application, message_type=first_message_type
)
first_message.created_at = "2021-06-04 00:00:00+00:00"
first_message.save()
second_message = MessageFactory(
application=handling_application, message_type=second_message_type
)
second_message.created_at = "2021-06-04 01:00:00+00:00"
second_message.save()

assert Message.objects.count() == 2
assert Message.objects.filter(seen_by_applicant=True).count() == 0
assert Message.objects.filter(seen_by_handler=True).count() == 0

result = handler_api_client.get(
reverse(
"handler-message-list", kwargs={"application_pk": handling_application.pk}
)
assert result.status_code == 200
assert Message.objects.filter(seen_by_applicant=True).count() == 0
assert Message.objects.filter(seen_by_handler=True).count() == 2
)
assert result.status_code == 200
assert Message.objects.filter(seen_by_applicant=True).count() == 0
assert Message.objects.filter(seen_by_handler=True).count() == 0

result = handler_api_client.post(
reverse(
"handler-message-mark-read",
kwargs={"application_pk": handling_application.pk},
)
)
assert result.status_code == 204
assert Message.objects.filter(seen_by_applicant=True).count() == 0
assert Message.objects.filter(seen_by_handler=True).count() == 2

result = handler_api_client.post(
reverse(
"handler-message-mark-unread",
kwargs={"application_pk": handling_application.pk},
)
)
assert result.status_code == 204
assert Message.objects.filter(seen_by_applicant=True).count() == 0
assert (
Message.objects.filter(seen_by_handler=True).count()
== expected_count_after_mark_unread
)


def test_applications_list_with_message_count_multiple_messages(
Expand Down
25 changes: 22 additions & 3 deletions backend/benefit/messages/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
from django.db import transaction
from django.utils import translation
from django.utils.translation import gettext_lazy as _
from rest_framework import viewsets
from rest_framework import status, viewsets
from rest_framework.decorators import action
from rest_framework.exceptions import NotFound
from rest_framework.response import Response

from applications.models import Application
from common.permissions import BFIsApplicant, BFIsHandler, TermsOfServiceAccepted
Expand Down Expand Up @@ -79,11 +81,28 @@ def get_queryset(self):
return Message.objects.none()
return application.messages.get_messages_qs()

@transaction.atomic
def list(self, request, *args, **kwargs):
self.get_queryset().update(seen_by_handler=True)
return super(viewsets.ModelViewSet, self).list(request, *args, **kwargs)

@transaction.atomic
@action(detail=False, methods=["post"])
def mark_read(self, *args, **kwargs):
self.get_queryset().update(seen_by_handler=True)
return Response(status=status.HTTP_204_NO_CONTENT)

@transaction.atomic
@action(detail=False, methods=["post"])
def mark_unread(self, *args, **kwargs):
last_message = self.get_queryset().order_by("-created_at").first()
if (
last_message is not None
and last_message.message_type == MessageType.APPLICANT_MESSAGE
):
last_message.seen_by_handler = False
last_message.save()

return Response(status=status.HTTP_204_NO_CONTENT)


class HandlerNoteViewSet(HandlerMessageViewSet):
serializer_class = NoteSerializer
Expand Down
8 changes: 4 additions & 4 deletions frontend/benefit/applicant/src/components/header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,12 @@ const Header: React.FC = () => {
isOpen={isMessagesDrawerVisible}
onClose={() => setMessagesDrawerVisiblity(false)}
canWriteNewMessages={canWriteNewMessages}
customItemsMessages={
<$CustomMessagesActions>
customItemsMessages={[
<$CustomMessagesActions key="isSecure">
<IconLock />
<p>{t('common:messenger.isSecure')}</p>
</$CustomMessagesActions>
}
</$CustomMessagesActions>,
]}
/>
)}
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { useMessenger } from './useMessenger';
interface ComponentProps {
isOpen: boolean;
onClose?: () => void;
customItemsMessages?: React.ReactNode;
customItemsMessages?: Array<React.ReactNode>;
canWriteNewMessages?: boolean;
}

Expand Down
7 changes: 6 additions & 1 deletion frontend/benefit/handler/public/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@
"fetch": {
"label": "Tapahtui virhe",
"text": " Virhe tapahtui viestejä haettaessa. Yritä myöhemmin uudelleen."
},
"readMarkerOperation": {
"label": "Tapahtui virhe",
"text": "Viestien lukumerkintöjen päivitys ei onnistunut. Yritä myöhemmin uudelleen."
}
}
},
Expand All @@ -47,7 +51,8 @@
"save": "Tallenna",
"close": "Sulje",
"noMessages": "Ei viestejä",
"seenByUser": "Viesti luettu"
"seenByUser": "Viesti luettu",
"markAsUnread": "Merkitse lukemattomaksi"
},
"notifications": {
"accepted": {
Expand Down
7 changes: 6 additions & 1 deletion frontend/benefit/handler/public/locales/fi/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@
"fetch": {
"label": "Tapahtui virhe",
"text": " Virhe tapahtui viestejä haettaessa. Yritä myöhemmin uudelleen."
},
"readMarkerOperation": {
"label": "Tapahtui virhe",
"text": "Viestien lukumerkintöjen päivitys ei onnistunut. Yritä myöhemmin uudelleen."
}
}
},
Expand All @@ -47,7 +51,8 @@
"save": "Tallenna",
"close": "Sulje",
"noMessages": "Ei viestejä",
"seenByUser": "Viesti luettu"
"seenByUser": "Viesti luettu",
"markAsUnread": "Merkitse lukemattomaksi"
},
"notifications": {
"accepted": {
Expand Down
7 changes: 6 additions & 1 deletion frontend/benefit/handler/public/locales/sv/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@
"fetch": {
"label": "Tapahtui virhe",
"text": " Virhe tapahtui viestejä haettaessa. Yritä myöhemmin uudelleen."
},
"readMarkerOperation": {
"label": "Tapahtui virhe",
"text": "Viestien lukumerkintöjen päivitys ei onnistunut. Yritä myöhemmin uudelleen."
}
}
},
Expand All @@ -47,7 +51,8 @@
"save": "Tallenna",
"close": "Sulje",
"noMessages": "Ei viestejä",
"seenByUser": "Viesti luettu"
"seenByUser": "Viesti luettu",
"markAsUnread": "Merkitse lukemattomaksi"
},
"notifications": {
"accepted": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -301,12 +301,12 @@ const AlterationHandlingForm = ({
messagesReadOnly
application={application}
onClose={() => toggleMessagesDrawerVisibility(false)}
customItemsNotes={
<$CustomNotesActions>
customItemsNotes={[
<$CustomNotesActions key="showToHandlerOnly">
<IconLock />
<p>{t('common:messenger.showToHandlerOnly')}</p>
</$CustomNotesActions>
}
</$CustomNotesActions>,
]}
/>
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,13 +160,15 @@ const HandlingApplicationActions: React.FC<Props> = ({
}
application={application}
onClose={toggleMessagesDrawerVisibility}
customItemsMessages={<EditAction application={application} />}
customItemsNotes={
<$CustomNotesActions>
customItemsMessages={[
<EditAction application={application} key="edit" />,
]}
customItemsNotes={[
<$CustomNotesActions key="showToHandlerOnly">
<IconLock />
<p>{t('common:messenger.showToHandlerOnly')}</p>
</$CustomNotesActions>
}
</$CustomNotesActions>,
]}
/>
</$Wrapper>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -381,13 +381,15 @@ const HandlingApplicationActions: React.FC<Props> = ({
(application.status && HANDLED_STATUSES.includes(application.status))
}
onClose={toggleMessagesDrawerVisibility}
customItemsMessages={<EditAction application={application} />}
customItemsNotes={
<$CustomNotesActions>
customItemsMessages={[
<EditAction application={application} key="edit" />,
]}
customItemsNotes={[
<$CustomNotesActions key="showToHandlerOnly">
<IconLock />
<p>{t('common:messenger.showToHandlerOnly')}</p>
</$CustomNotesActions>
}
</$CustomNotesActions>,
]}
/>
</$Wrapper>
);
Expand Down
49 changes: 43 additions & 6 deletions frontend/benefit/handler/src/components/sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Application } from 'benefit/handler/types/application';
import { Tab, TabPanel, Tabs } from 'hds-react';
import { MESSAGE_TYPES } from 'benefit-shared/constants';
import { Button, Tab, TabPanel, Tabs } from 'hds-react';
import * as React from 'react';
import { useEffect } from 'react';
import { $TabList } from 'shared/components/benefit/tabs/Tabs.sc';
import Drawer from 'shared/components/drawer/Drawer';
import Actions from 'shared/components/messaging/Actions';
Expand All @@ -14,8 +16,8 @@ interface ComponentProps {
messagesReadOnly?: boolean;
notesReadOnly?: boolean;
onClose?: () => void;
customItemsMessages?: React.ReactNode;
customItemsNotes?: React.ReactNode;
customItemsMessages?: Array<React.ReactNode>;
customItemsNotes?: Array<React.ReactNode>;
application: Application;
}

Expand All @@ -28,8 +30,31 @@ const Sidebar: React.FC<ComponentProps> = ({
onClose,
application,
}) => {
const { t, messages, notes, handleSendMessage, handleCreateNote } =
useSidebar(application?.id);
const {
t,
messages,
notes,
handleSendMessage,
handleCreateNote,
handleMarkMessagesRead,
handleMarkLastMessageUnread,
} = useSidebar(application?.id);

useEffect(() => {
if (isOpen) {
handleMarkMessagesRead();
}
}, [handleMarkMessagesRead, isOpen, messages.length]);

const closeAndMarkAsUnread = (): void => {
handleMarkLastMessageUnread(null, {
onSuccess: onClose,
});
};

const isLastMessageFromApplicant =
messages.length > 0 &&
messages.at(-1).messageType === MESSAGE_TYPES.APPLICANT_MESSAGE;

return (
<Drawer
Expand All @@ -54,7 +79,19 @@ const Sidebar: React.FC<ComponentProps> = ({
<Messages data={messages} variant="message" withScroll />
{!messagesReadOnly && (
<Actions
customItems={customItemsMessages}
customItems={[
...(customItemsMessages || []),
<Button
onClick={closeAndMarkAsUnread}
variant="secondary"
theme="black"
size="small"
key="markAsUnread"
disabled={!isLastMessageFromApplicant}
>
{t('common:messenger.markAsUnread')}
</Button>,
]}
sendText={t('common:messenger.send')}
errorText={t('common:form.validation.string.max', { max: 1024 })}
placeholder={t('common:messenger.compose')}
Expand Down
Loading

0 comments on commit 79efe24

Please sign in to comment.