-
Notifications
You must be signed in to change notification settings - Fork 15
29499 backend [ websockets ] Websockets improvements #81
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
Open
EBirkenfeld
wants to merge
6
commits into
master
Choose a base branch
from
backend/websockets/29499__websockets_improvements
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
29499 backend [ websockets ] Websockets improvements #81
EBirkenfeld
wants to merge
6
commits into
master
from
backend/websockets/29499__websockets_improvements
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
…update notification methods - Removed NewTaskConsumer, NotificationsConsumer, RemovedTaskConsumer, and WorkflowEventConsumer from the websocket and notifications services. - Added new notification methods for task and event actions in enums. - Updated WebSocketService to handle new message types and refactored notification sending logic. - Adjusted WorkflowActionService and related tasks to include is_completed flag for task notifications.
…pdate related tests
backend/src/notifications/tests/test_services/test_websockets.py
Outdated
Show resolved
Hide resolved
backend/src/notifications/tests/test_services/test_websockets.py
Outdated
Show resolved
Hide resolved
- Renamed `send_removed_task_deleted_notification` to `send_task_deleted_notification` across the codebase. - Updated all relevant service and test files to reflect the new method name for consistency. - Adjusted notification enums and websocket services to accommodate the changes.
…29499__websockets_improvements
pneumojoseph
approved these changes
Dec 1, 2025
…29499__websockets_improvements
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Unify websocket notifications under
EventsConsumer, standardize message envelope, and rename task events totask_created/task_deleted/task_completedacross services and routes/ws/eventsendpoint usingEventsConsumer{id, date_created_tsp, type, data}viaWebSocketService._sendcomplete_task→task_completed,removed_task→task_deleted, addtask_created,event_created,event_updated, andtask_completed_websocketevents_{user_id}groups📍Where to Start
Start with the message envelope and allowed methods in
WebSocketServicein websockets.py, then review task entrypoints in tasks.py.Changes since #81 opened
WebSocketService._sendto remove themessage_typeparameter and usemethod_namedirectly as the messagetypefield [6345ee9]send_removed_task_deleted_notificationtosend_task_deleted_notificationandsend_removed_task_completed_notificationtosend_task_completed_websocketthroughout the codebase [6345ee9]NotificationMethodenum andWebSocketService.ALLOWED_METHODSto addtask_completed_websocketand removenotification_created[6345ee9]WebSocketService.send_task_completed_websocketmethod and updatedsend_new_task_websocketto usetask_createdas method_name [6345ee9]test_consumer_send_notification__receivedtest to expect websocket payload typeNotificationMethod.commentinstead ofNotificationMethod.notification_created[6345ee9]📊 Macroscope summarized 0f65d7b. 12 files reviewed, 28 issues evaluated, 26 issues filtered, 0 comments posted
🗂️ Filtered Issues
backend/src/accounts/services/group.py — 0 comments posted, 2 evaluated, 2 filtered
send_notification_task.delay(...)will raiseAttributeErrorat runtime becausesrc.notifications.tasks.send_task_deleted_notificationhas been redefined later in the module without the@shared_taskdecorator, shadowing the original Celery task object. As a result, the importedsend_task_deleted_notificationis a plain function lacking.delay. This affects the call at this site in_send_users_notificationwhensend_notification_task is send_task_deleted_notification. [ Already posted ]send_notification_task.delay(...)will raiseAttributeErrorat runtime here becausesend_task_deleted_notificationimported fromsrc.notifications.tasksis a plain function (shadowing the Celery task due to a later redefinition without@shared_task)._send_removed_users_notificationspasses this object into_send_users_notification, which then calls.delayon it. [ Already posted ]backend/src/notifications/services/push.py — 0 comments posted, 2 evaluated, 2 filtered
UserNotifications.objects.get(user_id=user_id)in_send_to_appscan raiseDoesNotExistif a counter row is missing for the user, aborting push delivery for all app tokens and causing the task to fail. Add a safe fetch (get_or_createor graceful fallback) before incrementingcount_unread_push_in_ios_app. [ Low confidence ]counter.count_unread_push_in_ios_appis incremented once per app device token inside the loop, causing multiple increments for a single notification when a user has multiple devices. The badge should reflect unread notifications, not number of devices. Move the counter increment outside the token loop and reuse the same value for all device messages. [ Low confidence ]backend/src/notifications/services/websockets.py — 0 comments posted, 9 evaluated, 8 filtered
_sync_sendand_async_sendassumeget_channel_layer()returns a valid layer. If it returnsNone(misconfiguration or disabled channels), accessinggroup_sendwill raise at runtime. Add a guard to fail gracefully or skip sending. [ Low confidence ]_async_sendcreates a localbackground_tasks = set()and registerstask.add_done_callback(background_tasks.discard), but never adds thetaskto the set. The callback is a no-op and the local set is discarded when the function returns. This is dead code and provides no cleanup or tracking, potentially hiding intent to retain tasks to avoid premature GC or to manage lifecycle. [ Low confidence ]_async_sendusesget_event_loop()andloop.create_task(...)without ensuring a running loop in the current thread. In typical Django sync contexts,get_event_loop()can raiseRuntimeError(no loop set) or create tasks that never run. Additionally, exceptions in the task are not handled, causing "Task exception was never retrieved" warnings; andbackground_tasksis a local set that is never populated (only used indiscardcallback), providing no retention and no cleanup/visibility. [ Low confidence ]_get_serialized_notificationis called withnotificationthat can beNone(e.g., insend_overdue_task,send_task_completed,send_resume_workflow,send_delay_workflow,send_due_date_changed).NotificationsSerializer(instance=None).datawill attempt to serialize aNoneinstance and raise at runtime in DRF. Guard againstNoneor make the parameter non-optional. [ Low confidence ]notification=Noneto_get_serialized_notification, which constructsNotificationsSerializer(instance=None)and then accesses.data. In DRF, calling.datawithinstance=Nonecan raise at runtime (e.g.,AssertionErroror attribute errors during representation). There are no guards ensuringnotificationis non-null. [ Low confidence ]send_task_completed_websocketsends the wrong notification type: it usesNotificationMethod.task_completedinstead of the more specificNotificationMethod.task_completed_websocket. This breaks the externally visible contract by emitting an event type that does not match the method name and likely downstream expectations for websocket-specific completion events. [ Already posted ]send_task_completed_websocketsends withmethod_name=NotificationMethod.task_completedinstead of the more specificNotificationMethod.task_completed_websocket. This breaks contract parity: clients expecting the websocket-specific event type will not receive it, andALLOWED_METHODSincludes both, making the mismatch detectable. [ Already posted ]notification=Nonerisk exists in other methods that defaultnotificationtoNoneand immediately serialize it:send_resume_workflow,send_delay_workflow, andsend_due_date_changed. Each can raise at runtime whennotificationis omitted. [ Low confidence ]backend/src/notifications/tasks.py — 0 comments posted, 3 evaluated, 3 filtered
send_task_deleted_notificationis defined twice: first as a Celery task (@shared_task(base=NotificationTask)) and then redefined later as a plain function. The second definition overwrites the first module symbol, so callers importingsend_task_deleted_notificationwill see a regular function without the.delay/.apply_asyncattributes, causingAttributeErrorat runtime (e.g., when callingsend_task_deleted_notification.delay(...)). Celery will have registered the original task object, but the exported symbol no longer references it, breaking the module-level API expected by callers and tests. [ Already posted ]send_event_updatedis defined twice: first as a Celery task (@shared_task(base=NotificationTask)) and then redefined later as a plain function. The latter overwrites the task object in the module namespace, sosend_event_updated.delay(...)will fail withAttributeError, and code expecting a Celery task will break. [ Already posted ]if new_actions_ids:attempts to evaluate a Django QuerySet (ValuesListQuerySetfromWorkflowEventAction.objects.watched().only_ids()) in a boolean context, which raisesValueError: The truth value of a QuerySet is ambiguous. Use an explicit emptiness check, e.g.,if new_actions_ids.exists():or materialize to a list first:ids = list(new_actions_ids); if ids:and then useidsfor subsequent operations. [ Out of scope ]backend/src/processes/services/events.py — 0 comments posted, 3 evaluated, 3 filtered
_send_event_updatedcallssend_event_updated.delay(...). In this same commit,notifications.tasksredefinessend_event_updatedwithout the@shared_taskdecorator, overwriting the decorated task and removing the.delayattribute. Any call here will raiseAttributeError: 'function' object has no attribute 'delay'at runtime. [ Already posted ]update, ifforce_saveis left as defaultFalse, the call toself.partial_update(force_save=force_save, **kwargs)only mutates the in-memory instance and defers saving. This method does not callself.save()later, so changes may never persist to the database while notifications and analytics are sent based on unsaved state. [ Low confidence ]attachmentsis an empty list inupdate,with_attachmentsis set toFalse, but_update_attachmentsis not called because the code only runsself._update_attachments(attachments)underif attachments:. Existing attachments remain linked, leaving the database state inconsistent withwith_attachments=Falseand the intended semantics of clearing attachments by passing an empty list. [ Out of scope ]backend/src/processes/services/tasks/groups.py — 0 comments posted, 1 evaluated, 1 filtered
_delete_group_actions, when no individual (non-group) completed performer exists, the code falls back to a completed group and then doesgroup.users.first().user.group.users.first()returns aUserModel(not an object with a.userattribute). Accessing.userwill raiseAttributeError. The same pattern appears earlier with.first().useron aTaskPerformerthat could beNoneif no matching performer exists, but here the branch is only taken when completed performers exist; still, the.useraftergroup.users.first()is incorrect. [ Out of scope ]backend/src/processes/services/tasks/performers.py — 0 comments posted, 1 evaluated, 1 filtered
_delete_actions(TaskPerformersService), the same fallback branch usesgroup.users.first().user.group.users.first()already returns aUserModel, so accessing.userraisesAttributeErrorat runtime. [ Out of scope ]backend/src/processes/services/tasks/task_version.py — 0 comments posted, 2 evaluated, 2 filtered
default_performeris chosen asaccount.get_owner()if the workflow is external, otherwiseworkflow.workflow_starter. Ifworkflow.workflow_starterisNone(which is not guarded against), the code will callself.instance.add_raw_performer(default_performer)andself.instance.performers.add(default_performer)withNone, and also attempt to accessdefault_performer.id/email/is_new_tasks_subscriber, causing a runtime error (ValueErrororAttributeError). Add a guard to ensure a non-null fallback performer or handle the absence explicitly before using it. [ Low confidence ].delay(...), but insrc.notifications.tasksit is defined twice: first as a Celery@shared_task, then later (per the provided diff) redefined without the decorator. The later plain function overrides the task object in the module namespace, sosend_task_deleted_notification.delay(...)will raiseAttributeErrorat runtime because the overwritten function doesn't have adelayattribute. Ensure there is a single decorated task definition or import the decorated task under a different name to avoid shadowing. [ Already posted ]backend/src/processes/services/workflow_action.py — 0 comments posted, 4 evaluated, 3 filtered
force_delay_workflowdoes not validate thedateargument. Ifdate <= timezone.now(), computedduration = date - nowbecomes zero or negative. The code then setsstart_date=nowand uses thisdurationforDelay.durationandestimated_end_date = start_date + duration. This yields an immediate or already-expired delay, undermining the intended "force delay until future date" behavior and can cause confusing notifications and state transitions. [ Low confidence ]force_delay_workflowemits external side effects (Celery tasks via.delayand websocket/event dispatch throughWorkflowEventService._after_create_actions) inside atransaction.atomic()block. If the transaction later rolls back, notifications and events may already be dispatched, causing inconsistent state for recipients. Usetransaction.on_commit(...)to schedule these dispatches after a successful commit. [ Low confidence ]WorkflowEventService.force_delay_workflow_eventis called withdelaythat may beNoneifself.workflow.tasks.active()yields no tasks. In that case, insideWorkflowEventService.force_delay_workflow_event,DelayEventJsonSerializer(delay).datawill be constructed with aNoneinstance, causing a runtime error. Theforce_delay_workflowmethod has no guard ensuring at least one active task or validatingdelaybefore passing it. [ Low confidence ]backend/src/processes/services/workflows/workflow_version.py — 0 comments posted, 1 evaluated, 1 filtered
send_task_deleted_notification.delay(...)will raiseAttributeErrorat runtime because the importedsend_task_deleted_notificationsymbol is a plain function (it shadows the earlier@shared_taskdefinition due to a later redefinition insrc.notifications.tasks). This site invokes.delaywhen notifying about tasks deleted by version update. [ Already posted ]