Skip to content

Commit 1c3f003

Browse files
Merge pull request #7200 from hotosm/refactor/task-notifications
feat: Task validation and invalidation toggles for user to subscribe to respective notifications
2 parents c01b210 + be2b08b commit 1c3f003

File tree

10 files changed

+94
-13
lines changed

10 files changed

+94
-13
lines changed

backend/models/dtos/user_dto.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ class UserDTO(BaseModel):
5454
)
5555
projects_notifications: bool = Field(None, alias="projectsNotifications")
5656
tasks_notifications: bool = Field(None, alias="tasksNotifications")
57+
task_validation_notification: bool = Field(None, alias="taskValidationNotification")
58+
task_invalidation_notification: bool = Field(
59+
None, alias="taskInvalidationNotification"
60+
)
5761
tasks_comments_notifications: bool = Field(None, alias="taskCommentsNotifications")
5862
teams_announcement_notifications: bool = Field(
5963
None, alias="teamsAnnouncementNotifications"

backend/models/postgis/user.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ class User(Base):
7777
projects_comments_notifications = Column(Boolean, default=False, nullable=False)
7878
projects_notifications = Column(Boolean, default=True, nullable=False)
7979
tasks_notifications = Column(Boolean, default=True, nullable=False)
80+
task_validation_notification = Column(Boolean, default=True, nullable=False)
81+
task_invalidation_notification = Column(Boolean, default=True, nullable=False)
8082
tasks_comments_notifications = Column(Boolean, default=False, nullable=False)
8183
teams_announcement_notifications = Column(Boolean, default=True, nullable=False)
8284
date_registered = Column(DateTime, default=timestamp)
@@ -503,6 +505,8 @@ async def as_dto(self, logged_in_username: str, db: Database) -> UserDTO:
503505
user_dto.projects_notifications = self.projects_notifications
504506
user_dto.projects_comments_notifications = self.projects_comments_notifications
505507
user_dto.tasks_notifications = self.tasks_notifications
508+
user_dto.task_validation_notification = self.task_validation_notification
509+
user_dto.task_invalidation_notification = self.task_invalidation_notification
506510
user_dto.tasks_comments_notifications = self.tasks_comments_notifications
507511
user_dto.teams_announcement_notifications = (
508512
self.teams_announcement_notifications

backend/services/messaging/message_service.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -206,12 +206,16 @@ async def _push_messages(messages: list, db: Database):
206206
and obj.message_type == MessageType.TASK_COMMENT_NOTIFICATION.value
207207
):
208208
continue
209+
if (
210+
user.task_validation_notification is False
211+
and obj.message_type == MessageType.VALIDATION_NOTIFICATION.value
212+
):
213+
continue
209214

210-
if user.tasks_notifications is False and obj.message_type in (
211-
MessageType.VALIDATION_NOTIFICATION.value,
212-
MessageType.INVALIDATION_NOTIFICATION.value,
215+
if (
216+
user.task_invalidation_notification is False
217+
and obj.message_type == MessageType.INVALIDATION_NOTIFICATION.value
213218
):
214-
messages_objs.append(obj)
215219
continue
216220
# If the notification is enabled, send an email
217221
messages_objs.append(obj)

backend/services/users/user_service.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,8 @@ async def register_user(osm_id, username, changeset_count, picture_url, email, d
251251
"projects_comments_notifications": False,
252252
"projects_notifications": True,
253253
"tasks_notifications": True,
254+
"task_validation_notification": True,
255+
"task_invalidation_notification": True,
254256
"tasks_comments_notifications": False,
255257
"teams_announcement_notifications": True,
256258
"date_registered": datetime.datetime.utcnow(),

frontend/src/components/user/forms/notifications.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,15 @@ export function UserNotificationsForm(props) {
1919
default: false,
2020
},
2121
{
22-
labelId: 'taskUpdates',
23-
descriptionId: 'taskUpdatesDescription',
24-
fieldName: 'tasksNotifications',
22+
labelId: 'taskValidationUpdates',
23+
descriptionId: 'taskValidationUpdatesDescription',
24+
fieldName: 'taskValidationNotification',
25+
default: true,
26+
},
27+
{
28+
labelId: 'taskInvalidationUpdates',
29+
descriptionId: 'taskInvalidationUpdatesDescription',
30+
fieldName: 'taskInvalidationNotification',
2531
default: true,
2632
},
2733
{

frontend/src/components/user/messages.js

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -146,9 +146,13 @@ export default defineMessages({
146146
id: 'user.notifications.projects',
147147
defaultMessage: 'Project updates',
148148
},
149-
taskUpdates: {
150-
id: 'user.notifications.tasks',
151-
defaultMessage: 'Tasks validation emails',
149+
taskValidationUpdates: {
150+
id: 'user.notifications.tasks.validation',
151+
defaultMessage: 'Task validation emails',
152+
},
153+
taskInvalidationUpdates: {
154+
id: 'user.notifications.tasks.invalidation',
155+
defaultMessage: 'Task invalidation emails',
152156
},
153157
required: {
154158
id: 'user.settings.required',
@@ -158,10 +162,14 @@ export default defineMessages({
158162
id: 'user.notifications.projects.description',
159163
defaultMessage: 'You get a notification when a project you have contributed to makes progress.',
160164
},
161-
taskUpdatesDescription: {
162-
id: 'user.notifications.task.description',
165+
taskValidationUpdatesDescription: {
166+
id: 'user.notifications.task.validation.description',
163167
defaultMessage: 'Receive an email when a task you have contributed to is validated.',
164168
},
169+
taskInvalidationUpdatesDescription: {
170+
id: 'user.notifications.task.invalidation.description',
171+
defaultMessage: 'Receive an email when a task you have contributed to is invalidated.',
172+
},
165173
questionsAndComments: {
166174
id: 'user.notifications.questionsAndComments',
167175
defaultMessage: 'Questions and comments',

frontend/src/network/tests/mockData/userList.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ export const userQueryDetails = {
5656
mentionsNotifications: true,
5757
questionsAndCommentsNotifications: true,
5858
projectsNotifications: true,
59-
tasksNotifications: true,
59+
taskValidationNotification: true,
60+
taskInvalidationNotification: true,
6061
taskCommentsNotifications: true,
6162
teamsAnnouncementNotifications: false,
6263
gender: 'MALE',
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
"""Split tasks_notifications into task_validation_notification and task_invalidation_notification
2+
3+
Revision ID: a1b2c3d4e5f6
4+
Revises: b720f42ce3e8
5+
Create Date: 2026-03-17 10:25:00.000000
6+
7+
"""
8+
9+
import sqlalchemy as sa
10+
from alembic import op
11+
12+
# revision identifiers, used by Alembic.
13+
revision = "a1b2c3d4e5f6"
14+
down_revision = "b720f42ce3e8"
15+
branch_labels = None
16+
depends_on = None
17+
18+
19+
def upgrade():
20+
op.add_column(
21+
"users",
22+
sa.Column(
23+
"task_validation_notification",
24+
sa.Boolean(),
25+
nullable=False,
26+
server_default=sa.text("true"),
27+
),
28+
)
29+
op.add_column(
30+
"users",
31+
sa.Column(
32+
"task_invalidation_notification",
33+
sa.Boolean(),
34+
nullable=False,
35+
server_default=sa.text("true"),
36+
),
37+
)
38+
39+
40+
def downgrade():
41+
op.drop_column("users", "task_validation_notification")
42+
op.drop_column("users", "task_invalidation_notification")

tests/api/helpers/test_helpers.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,8 @@ async def return_canned_user(db, username=TEST_USERNAME, id=TEST_USER_ID) -> Use
254254
test_user.projects_comments_notifications = False
255255
test_user.projects_notifications = True
256256
test_user.tasks_notifications = True
257+
test_user.task_validation_notification = True
258+
test_user.task_invalidation_notification = True
257259
test_user.tasks_comments_notifications = False
258260
test_user.teams_announcement_notifications = True
259261
test_user.date_registered = datetime.utcnow()
@@ -281,13 +283,15 @@ async def create_canned_user(db, test_user=None):
281283
id, username, role, mapping_level, tasks_mapped, tasks_validated, tasks_invalidated,
282284
email_address, is_email_verified, is_expert, default_editor, mentions_notifications,
283285
projects_comments_notifications, projects_notifications, tasks_notifications,
286+
task_validation_notification, task_invalidation_notification,
284287
tasks_comments_notifications, teams_announcement_notifications, date_registered,
285288
last_validation_date
286289
)
287290
VALUES (
288291
:id, :username, :role, :mapping_level, :tasks_mapped, :tasks_validated, :tasks_invalidated,
289292
:email_address, :is_email_verified, :is_expert, :default_editor, :mentions_notifications,
290293
:projects_comments_notifications, :projects_notifications, :tasks_notifications,
294+
:task_validation_notification, :task_invalidation_notification,
291295
:tasks_comments_notifications, :teams_announcement_notifications, :date_registered,
292296
:last_validation_date
293297
)
@@ -308,6 +312,8 @@ async def create_canned_user(db, test_user=None):
308312
"projects_comments_notifications": test_user.projects_comments_notifications,
309313
"projects_notifications": test_user.projects_notifications,
310314
"tasks_notifications": test_user.tasks_notifications,
315+
"task_validation_notification": test_user.task_validation_notification,
316+
"task_invalidation_notification": test_user.task_invalidation_notification,
311317
"tasks_comments_notifications": test_user.tasks_comments_notifications,
312318
"teams_announcement_notifications": test_user.teams_announcement_notifications,
313319
"date_registered": test_user.date_registered,

tests/api/unit/models/postgis/test_user.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ async def setup_test_data(self, db_connection_fixture, request):
3131
"projects_comments_notifications": False,
3232
"projects_notifications": True,
3333
"tasks_notifications": True,
34+
"task_validation_notification": True,
35+
"task_invalidation_notification": True,
3436
"tasks_comments_notifications": False,
3537
"teams_announcement_notifications": True,
3638
}
@@ -42,6 +44,7 @@ async def setup_test_data(self, db_connection_fixture, request):
4244
is_email_verified, is_expert, default_editor,
4345
mentions_notifications, projects_comments_notifications,
4446
projects_notifications, tasks_notifications,
47+
task_validation_notification, task_invalidation_notification,
4548
tasks_comments_notifications, teams_announcement_notifications
4649
)
4750
VALUES (
@@ -50,6 +53,7 @@ async def setup_test_data(self, db_connection_fixture, request):
5053
:is_email_verified, :is_expert, :default_editor,
5154
:mentions_notifications, :projects_comments_notifications,
5255
:projects_notifications, :tasks_notifications,
56+
:task_validation_notification, :task_invalidation_notification,
5357
:tasks_comments_notifications, :teams_announcement_notifications
5458
)
5559
ON CONFLICT (id) DO NOTHING

0 commit comments

Comments
 (0)