Skip to content

Commit bc60c52

Browse files
authored
Merge pull request #142 from pneumaticapp/backend/templates/44593__noify_performers
44593 backend [ notifications ] Notify All' type notifications
2 parents f1b5832 + 2fd11b3 commit bc60c52

60 files changed

Lines changed: 2814 additions & 339 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

backend/docker-compose.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,20 @@ services:
9494
CIO_WEBHOOK_API_VERSION: ${CIO_WEBHOOK_API_VERSION:-}
9595
CIO_WEBHOOK_API_KEY: ${CIO_WEBHOOK_API_KEY:-}
9696
CIO_TRANSACTIONAL_API_KEY: ${CIO_TRANSACTIONAL_API_KEY:-}
97+
CIO_TEMPLATE__RESET_PASSWORD: ${CIO_TEMPLATE__RESET_PASSWORD:-}
98+
CIO_TEMPLATE__USER_DEACTIVATED: ${CIO_TEMPLATE__USER_DEACTIVATED:-}
99+
CIO_TEMPLATE__NEW_TASK: ${CIO_TEMPLATE__NEW_TASK:-}
100+
CIO_TEMPLATE__TASK_RETURNED: ${CIO_TEMPLATE__TASK_RETURNED:-}
101+
CIO_TEMPLATE__ACCOUNT_VERIFICATION: ${CIO_TEMPLATE__ACCOUNT_VERIFICATION:-}
102+
CIO_TEMPLATE__WORKFLOWS_DIGEST: ${CIO_TEMPLATE__WORKFLOWS_DIGEST:-}
103+
CIO_TEMPLATE__TASKS_DIGEST: ${CIO_TEMPLATE__TASKS_DIGEST:-}
104+
CIO_TEMPLATE__USER_TRANSFER: ${CIO_TEMPLATE__USER_TRANSFER:-}
105+
CIO_TEMPLATE__UNREAD_NOTIFICATIONS: ${CIO_TEMPLATE__UNREAD_NOTIFICATIONS:-}
106+
CIO_TEMPLATE__GUEST_NEW_TASK: ${CIO_TEMPLATE__GUEST_NEW_TASK:-}
107+
CIO_TEMPLATE__OVERDUE_TASK: ${CIO_TEMPLATE__OVERDUE_TASK:-}
108+
CIO_TEMPLATE__MENTION: ${CIO_TEMPLATE__MENTION:-}
109+
CIO_TEMPLATE__TASK_REMINDER: ${CIO_TEMPLATE__TASK_REMINDER:-}
110+
CIO_TEMPLATE__COMPLETE_WORKFLOW: ${CIO_TEMPLATE__COMPLETE_WORKFLOW:-}
97111
EMAIL_HOST: ${EMAIL_HOST:-}
98112
EMAIL_PORT: ${EMAIL_PORT:-}
99113
EMAIL_HOST_USER: ${EMAIL_HOST_USER:-}
@@ -175,6 +189,20 @@ services:
175189
CIO_WEBHOOK_API_VERSION: ${CIO_WEBHOOK_API_VERSION:-}
176190
CIO_WEBHOOK_API_KEY: ${CIO_WEBHOOK_API_KEY:-}
177191
CIO_TRANSACTIONAL_API_KEY: ${CIO_TRANSACTIONAL_API_KEY:-}
192+
CIO_TEMPLATE__RESET_PASSWORD: ${CIO_TEMPLATE__RESET_PASSWORD:-}
193+
CIO_TEMPLATE__USER_DEACTIVATED: ${CIO_TEMPLATE__USER_DEACTIVATED:-}
194+
CIO_TEMPLATE__NEW_TASK: ${CIO_TEMPLATE__NEW_TASK:-}
195+
CIO_TEMPLATE__TASK_RETURNED: ${CIO_TEMPLATE__TASK_RETURNED:-}
196+
CIO_TEMPLATE__ACCOUNT_VERIFICATION: ${CIO_TEMPLATE__ACCOUNT_VERIFICATION:-}
197+
CIO_TEMPLATE__WORKFLOWS_DIGEST: ${CIO_TEMPLATE__WORKFLOWS_DIGEST:-}
198+
CIO_TEMPLATE__TASKS_DIGEST: ${CIO_TEMPLATE__TASKS_DIGEST:-}
199+
CIO_TEMPLATE__USER_TRANSFER: ${CIO_TEMPLATE__USER_TRANSFER:-}
200+
CIO_TEMPLATE__UNREAD_NOTIFICATIONS: ${CIO_TEMPLATE__UNREAD_NOTIFICATIONS:-}
201+
CIO_TEMPLATE__GUEST_NEW_TASK: ${CIO_TEMPLATE__GUEST_NEW_TASK:-}
202+
CIO_TEMPLATE__OVERDUE_TASK: ${CIO_TEMPLATE__OVERDUE_TASK:-}
203+
CIO_TEMPLATE__MENTION: ${CIO_TEMPLATE__MENTION:-}
204+
CIO_TEMPLATE__TASK_REMINDER: ${CIO_TEMPLATE__TASK_REMINDER:-}
205+
CIO_TEMPLATE__COMPLETE_WORKFLOW: ${CIO_TEMPLATE__COMPLETE_WORKFLOW:-}
178206
EMAIL_HOST: ${EMAIL_HOST:-}
179207
EMAIL_PORT: ${EMAIL_PORT:-}
180208
EMAIL_HOST_USER: ${EMAIL_HOST_USER:-}

backend/src/accounts/enums.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,21 @@
33

44
class NotificationType:
55

6+
# TODO Union with the NotificationMethod
7+
68
SYSTEM = 'system'
79
COMMENT = 'comment'
810
MENTION = 'mention'
911
URGENT = 'urgent'
1012
NOT_URGENT = 'not_urgent'
1113
OVERDUE_TASK = 'overdue_task'
14+
REMINDER_TASK = 'reminder_task'
1215
DELAY_WORKFLOW = 'snooze_workflow'
1316
RESUME_WORKFLOW = 'resume_workflow'
1417
DUE_DATE_CHANGED = 'due_date_changed'
1518
REACTION = 'reaction'
1619
COMPLETE_TASK = 'complete_task'
20+
COMPLETE_WORKFLOW = 'complete_workflow'
1721

1822
URGENT_TYPES = (
1923
URGENT,
@@ -27,11 +31,13 @@ class NotificationType:
2731
(URGENT, 'urgent'),
2832
(NOT_URGENT, 'not urgent'),
2933
(OVERDUE_TASK, 'overdue task'),
34+
(REMINDER_TASK, 'reminder task'),
3035
(DELAY_WORKFLOW, 'snooze workflow'),
3136
(RESUME_WORKFLOW, 'resume workflow'),
3237
(DUE_DATE_CHANGED, 'due date changed'),
3338
(REACTION, 'reaction'),
3439
(COMPLETE_TASK, 'complete task'),
40+
(COMPLETE_WORKFLOW, 'complete workflow'),
3541
)
3642

3743

backend/src/accounts/models.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -555,6 +555,8 @@ class Notification(
555555
AccountBaseMixin,
556556
):
557557

558+
# TODO Move to the "notifications" module
559+
558560
author = models.ForeignKey(
559561
User,
560562
on_delete=models.CASCADE,

backend/src/accounts/tokens.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ class UnsubscribeEmailToken(BaseToken):
112112
lifetime = timedelta(days=settings.UNSUBSCRIBE_TOKEN_IN_DAYS)
113113

114114
@classmethod
115-
def create_token(cls, user_id: int, email_type: MailoutType):
115+
def create_token(cls, user_id: int, email_type: MailoutType.LITERALS):
116116
token = cls.for_user_id(user_id)
117117
token.payload.update({'email_type': MailoutType.MAP[email_type]})
118118
return token

backend/src/analysis/enums.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from typing_extensions import Literal
12

23

34
class MailoutType:
@@ -35,3 +36,13 @@ class MailoutType:
3536
CUSTOMERIO_TYPES = {
3637
NEWSLETTER,
3738
}
39+
40+
LITERALS = Literal[
41+
NEWSLETTER,
42+
OFFER,
43+
COMMENTS,
44+
NEW_TASK,
45+
WF_DIGEST,
46+
TASKS_DIGEST,
47+
COMPLETE_TASK,
48+
]

backend/src/generics/mixins/queries.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,26 @@ def dereferenced_performers():
9494
AND ptp.user_id = %(user_id)s
9595
)
9696
"""
97+
98+
@staticmethod
99+
def all_dereferenced_performers():
100+
101+
""" Convert group performers to users and return it (for any user) """
102+
103+
return f"""
104+
SELECT DISTINCT ON (user_id, ptp.task_id)
105+
(
106+
CASE
107+
WHEN ptp.type = '{PerformerType.GROUP}' THEN g.user_id
108+
ELSE ptp.user_id
109+
END
110+
) AS user_id,
111+
ptp.task_id,
112+
ptp.is_completed
113+
FROM processes_taskperformer ptp
114+
LEFT JOIN accounts_usergroup_users g
115+
ON g.usergroup_id = ptp.group_id
116+
WHERE
117+
ptp.is_deleted IS FALSE
118+
AND ptp.directly_status != '{DirectlyStatus.DELETED}'
119+
"""

backend/src/notifications/clients/smtp.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
class SMTPEmailClient(EmailClient):
1414

15+
DEFAULT_TEMPLATE_WORKFLOWS = 'workflows.html'
1516
DEFAULT_TEMPLATE_TASKS = 'tasks.html'
1617
DEFAULT_TEMPLATE_AUTH = 'auth.html'
1718
DEFAULT_TEMPLATE_DIGESTS = 'digests.html'
@@ -30,6 +31,8 @@ class SMTPEmailClient(EmailClient):
3031
EmailType.WORKFLOWS_DIGEST: DEFAULT_TEMPLATE_DIGESTS,
3132
EmailType.TASKS_DIGEST: DEFAULT_TEMPLATE_DIGESTS,
3233
EmailType.INVITE: DEFAULT_TEMPLATE_AUTH,
34+
EmailType.COMPLETE_WORKFLOW: DEFAULT_TEMPLATE_WORKFLOWS,
35+
EmailType.TASK_REMINDER: DEFAULT_TEMPLATE_AUTH,
3336
}
3437

3538
def __init__(self, account_id: int):
@@ -127,6 +130,7 @@ def _get_default_subject(
127130

128131
subjects = {
129132
EmailType.NEW_TASK: 'New Task Assigned',
133+
EmailType.INVITE: 'Join your team',
130134
EmailType.TASK_RETURNED: 'Task Returned',
131135
EmailType.OVERDUE_TASK: 'Task Overdue',
132136
EmailType.GUEST_NEW_TASK: 'New Task Assigned',
@@ -140,15 +144,18 @@ def _get_default_subject(
140144
EmailType.UNREAD_NOTIFICATIONS: (
141145
'You have unread notifications on Pneumatic'
142146
),
143-
EmailType.INVITE: 'Join your team 👥',
147+
EmailType.COMPLETE_WORKFLOW: 'Workflow completed',
148+
EmailType.TASK_REMINDER: (
149+
'Reminder: you have unfinished tasks in Pneumatic'
150+
),
144151
}
145152

146153
return subjects.get(template_code, f'Pneumatic - {template_code}')
147154

148155
def _get_fallback_template(
149-
self,
150-
template_code: EmailType.LITERALS,
151-
message_data: Dict[str, Any],
156+
self,
157+
template_code: EmailType.LITERALS,
158+
message_data: Dict[str, Any],
152159
) -> Tuple[str, str]:
153160
"""Get simple fallback template when no file template exists."""
154161
subject = self._get_default_subject(template_code, message_data)

backend/src/notifications/enums.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@ class NotificationMethod:
99
new_task_websocket = 'new_task_websocket'
1010
returned_task = 'returned_task'
1111
removed_task = 'removed_task'
12+
task_reminder = 'task_reminder'
1213
overdue_task = 'overdue_task'
1314
complete_task = 'complete_task'
1415
mention = 'mention'
1516
comment = 'comment'
1617
delay_workflow = 'delay_workflow'
1718
guest_new_task = 'guest_new_task'
1819
resume_workflow = 'resume_workflow'
20+
complete_workflow = 'complete_workflow'
1921
unread_notifications = 'unread_notifications'
2022
due_date_changed = 'due_date_changed'
2123
system = 'system'
@@ -42,13 +44,15 @@ class NotificationMethod:
4244
new_task_websocket,
4345
returned_task,
4446
removed_task,
47+
task_reminder,
4548
overdue_task,
4649
complete_task,
4750
mention,
4851
comment,
4952
delay_workflow,
5053
guest_new_task,
5154
resume_workflow,
55+
complete_workflow,
5256
unread_notifications,
5357
due_date_changed,
5458
system,
@@ -89,6 +93,7 @@ class EmailType:
8993
USER_DEACTIVATED = 'user_deactivated'
9094
NEW_TASK = 'new_task'
9195
TASK_RETURNED = 'task_returned'
96+
TASK_REMINDER = 'task_reminder'
9297
ACCOUNT_VERIFICATION = 'account_verification'
9398
WORKFLOWS_DIGEST = 'digest'
9499
TASKS_DIGEST = 'tasks_digest'
@@ -98,12 +103,14 @@ class EmailType:
98103
OVERDUE_TASK = 'overdue_task'
99104
MENTION = 'mention'
100105
INVITE = 'invite'
106+
COMPLETE_WORKFLOW = 'complete_workflow'
101107

102108
LITERALS = Literal[
103109
RESET_PASSWORD,
104110
USER_DEACTIVATED,
105111
NEW_TASK,
106112
TASK_RETURNED,
113+
TASK_REMINDER,
107114
ACCOUNT_VERIFICATION,
108115
WORKFLOWS_DIGEST,
109116
TASKS_DIGEST,
@@ -113,13 +120,15 @@ class EmailType:
113120
OVERDUE_TASK,
114121
MENTION,
115122
INVITE,
123+
COMPLETE_WORKFLOW,
116124
]
117125

118126
CHOICES = [
119127
(RESET_PASSWORD, 'Reset Password'),
120128
(USER_DEACTIVATED, 'User Deactivated'),
121129
(NEW_TASK, 'New Task'),
122130
(TASK_RETURNED, 'Task Returned'),
131+
(TASK_REMINDER, 'Task Reminder'),
123132
(ACCOUNT_VERIFICATION, 'Account Verification'),
124133
(WORKFLOWS_DIGEST, 'Workflows Digest'),
125134
(TASKS_DIGEST, 'Tasks Digest'),
@@ -129,6 +138,7 @@ class EmailType:
129138
(OVERDUE_TASK, 'Overdue Task'),
130139
(MENTION, 'Mention'),
131140
(INVITE, 'Invite'),
141+
(COMPLETE_WORKFLOW, 'Complete Workflow'),
132142
]
133143

134144

@@ -137,6 +147,7 @@ class EmailType:
137147
EmailType.USER_DEACTIVATED: env.get('CIO_TEMPLATE__USER_DEACTIVATED'),
138148
EmailType.NEW_TASK: env.get('CIO_TEMPLATE__NEW_TASK'),
139149
EmailType.TASK_RETURNED: env.get('CIO_TEMPLATE__TASK_RETURNED'),
150+
EmailType.TASK_REMINDER: env.get('CIO_TEMPLATE__TASK_REMINDER'),
140151
EmailType.ACCOUNT_VERIFICATION: env.get(
141152
'CIO_TEMPLATE__ACCOUNT_VERIFICATION',
142153
),
@@ -149,6 +160,7 @@ class EmailType:
149160
EmailType.GUEST_NEW_TASK: env.get('CIO_TEMPLATE__GUEST_NEW_TASK'),
150161
EmailType.OVERDUE_TASK: env.get('CIO_TEMPLATE__OVERDUE_TASK'),
151162
EmailType.MENTION: env.get('CIO_TEMPLATE__MENTION'),
163+
EmailType.COMPLETE_WORKFLOW: env.get('CIO_TEMPLATE__COMPLETE_WORKFLOW'),
152164
}
153165

154166
email_titles = {
@@ -169,4 +181,8 @@ class EmailType:
169181
NotificationMethod.invite: (
170182
"✅ You've been invited to join your team in Pneumatic"
171183
),
184+
NotificationMethod.complete_workflow: 'Workflow completed',
185+
NotificationMethod.task_reminder: (
186+
'Reminder: you have unfinished tasks in Pneumatic'
187+
),
172188
}

backend/src/notifications/messages.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,10 @@
2626
MSG_NF_0018 = _('Send workflows digest')
2727
MSG_NF_0019 = _('Send tasks digest')
2828
MSG_NF_0020 = _('Join your team')
29+
MSG_NF_0021 = _('Workflow was completed')
30+
MSG_NF_0022 = _('Reminder')
31+
MSG_NF_0023 = _('You have unfinished task in Pneumatic')
32+
MSG_NF_0024 = lambda count: format_lazy(
33+
_('You have {count} unfinished tasks in Pneumatic'),
34+
count=count,
35+
)

backend/src/notifications/queries.py

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
from django.contrib.auth import get_user_model
44

5-
from src.accounts.enums import NotificationType
5+
from src.accounts.enums import NotificationType, UserStatus
6+
from src.generics.mixins.queries import DereferencedPerformersMixin
7+
from src.notifications.enums import NotificationMethod
68
from src.processes.enums import (
79
DirectlyStatus,
810
TaskStatus,
@@ -17,13 +19,6 @@
1719

1820
class UsersWithOverdueTaskQuery(SqlQueryObject):
1921

20-
def __init__(self):
21-
self.params = {
22-
'workflow_status': WorkflowStatus.RUNNING,
23-
'directly_status': DirectlyStatus.DELETED,
24-
'notification_type': NotificationType.OVERDUE_TASK,
25-
}
26-
2722
def get_sql(self) -> Tuple[str, dict]:
2823
return f"""
2924
SELECT DISTINCT ON (user_id, task_id)
@@ -78,17 +73,60 @@ def get_sql(self) -> Tuple[str, dict]:
7873
LEFT JOIN accounts_notification an
7974
ON an.task_id = pt.id
8075
AND an.user_id = ptp.user_id
81-
AND an.type = %(notification_type)s
76+
AND an.type = '{NotificationType.OVERDUE_TASK}'
8277
WHERE pt.is_deleted is FALSE
83-
AND pw.status = %(workflow_status)s
78+
AND pw.status = '{WorkflowStatus.RUNNING}'
8479
AND pt.due_date IS NOT NULL
8580
AND pt.due_date <= NOW()
8681
AND ptp.is_completed IS FALSE
87-
AND ptp.directly_status != %(directly_status)s
82+
AND ptp.directly_status != '{DirectlyStatus.DELETED}'
83+
AND au.status = '{UserStatus.ACTIVE}'
8884
) result INNER JOIN accounts_user au
8985
ON au.id = result.workflow_starter_id
9086
INNER JOIN accounts_account aa
9187
ON aa.id = au.account_id
9288
WHERE notification is NULL
9389
ORDER BY user_id, task_id, account_id
94-
""", self.params
90+
""", {}
91+
92+
93+
class UsersWithRemainderTaskQuery(SqlQueryObject, DereferencedPerformersMixin):
94+
95+
def get_sql(self) -> Tuple[str, dict]:
96+
result = f"""
97+
SELECT
98+
au.id AS user_id,
99+
au.first_name AS user_first_name,
100+
au.email AS user_email,
101+
au.type AS user_type,
102+
MIN(pt.id) AS task_id,
103+
aa.id AS account_id,
104+
aa.logo_lg,
105+
aa.log_api_requests AS logging,
106+
'{NotificationMethod.task_reminder}' AS method_name,
107+
TRUE as sync,
108+
COUNT(pt.id) AS count
109+
FROM processes_workflow pw
110+
INNER JOIN processes_task pt
111+
ON pt.workflow_id = pw.id
112+
INNER JOIN (
113+
{self.all_dereferenced_performers()}
114+
) dereferenced_performers
115+
ON pt.id = dereferenced_performers.task_id
116+
INNER JOIN accounts_user au
117+
ON au.id = dereferenced_performers.user_id
118+
INNER JOIN accounts_account aa
119+
ON au.account_id = aa.id
120+
WHERE
121+
pw.is_deleted = FALSE
122+
AND pw.status = '{WorkflowStatus.RUNNING}'
123+
AND pw.reminder_notification = TRUE
124+
AND pw.is_deleted = FALSE
125+
AND pt.is_deleted = FALSE
126+
AND pt.status = '{TaskStatus.ACTIVE}'
127+
AND dereferenced_performers.is_completed IS FALSE
128+
AND au.status = '{UserStatus.ACTIVE}'
129+
GROUP BY au.id, aa.id
130+
ORDER BY au.id
131+
"""
132+
return result, {}

0 commit comments

Comments
 (0)