Jul 31 - Update to ensure customers don't get notification for internal comments

This commit is contained in:
2026-07-31 15:03:47 -04:00
parent 31ffeb0abf
commit f9c93ca3ed
2 changed files with 48 additions and 9 deletions
+2 -1
View File
@@ -8,7 +8,8 @@
"Bash(python docs/convert_manual.py)", "Bash(python docs/convert_manual.py)",
"Bash(grep -n -A3 dateOnlyFormatter __TRACKED_VAR__/Sync/SyncManager.swift)", "Bash(grep -n -A3 dateOnlyFormatter __TRACKED_VAR__/Sync/SyncManager.swift)",
"Bash(python -c \"import ast; ast.parse\\(open\\('app/utils/pdf_export.py'\\).read\\(\\)\\); print\\('OK'\\)\")", "Bash(python -c \"import ast; ast.parse\\(open\\('app/utils/pdf_export.py'\\).read\\(\\)\\); print\\('OK'\\)\")",
"Bash(python -c \"import ast,io; ast.parse\\(io.open\\('app/utils/pdf_export.py',encoding='utf-8'\\).read\\(\\)\\); print\\('OK'\\)\")" "Bash(python -c \"import ast,io; ast.parse\\(io.open\\('app/utils/pdf_export.py',encoding='utf-8'\\).read\\(\\)\\); print\\('OK'\\)\")",
"Bash(python -c \"import ast,io; ast.parse\\(io.open\\('app/routes/issues.py',encoding='utf-8'\\).read\\(\\)\\); print\\('OK'\\)\")"
] ]
} }
} }
+46 -8
View File
@@ -30,17 +30,31 @@ logger = logging.getLogger(__name__)
# ── Shared helper ───────────────────────────────────────────────────────────── # ── Shared helper ─────────────────────────────────────────────────────────────
def _notify_followers(issue, title, body, exclude_user_ids=None): def _notify_followers(issue, title, body, exclude_user_ids=None,
"""Dispatch a notification to every follower of the given issue.""" customer_body=None, skip_customers=False):
"""Dispatch a notification to every follower of the given issue.
Customer followers are handled separately so a staff-only (internal) comment
never leaks to them: pass ``skip_customers=True`` to omit customer followers
entirely, or ``customer_body`` to send them a customer-safe message in place
of ``body``. Non-customer followers always receive ``body``.
"""
exclude = set(exclude_user_ids or []) exclude = set(exclude_user_ids or [])
issue_link = url_for('issues.view', issue_id=issue.id) issue_link = url_for('issues.view', issue_id=issue.id)
for follower in issue.followers.all(): for follower in issue.followers.all():
if follower.user_id in exclude: if follower.user_id in exclude:
continue continue
is_customer = bool(follower.user) and follower.user.role == 'customer'
if is_customer:
if skip_customers:
continue
f_body = customer_body if customer_body is not None else body
else:
f_body = body
notify( notify(
recipient = follower.user, recipient = follower.user,
title = title, title = title,
body = body, body = f_body,
link = issue_link, link = issue_link,
issue_id = issue.id, issue_id = issue.id,
event_type = EVENT_ISSUE_FOLLOW, event_type = EVENT_ISSUE_FOLLOW,
@@ -585,34 +599,58 @@ def view(issue_id):
if old_assigned_to: if old_assigned_to:
exclude_ids.add(old_assigned_to) exclude_ids.add(old_assigned_to)
# Whether the comment just added is visible to customers. Internal
# (staff-only) comments must NEVER reach customer accounts — they only
# ever hear about comments explicitly shared with them. Only staff reach
# this branch (customers POST via the earlier customer-only path), so the
# checkbox governs. `changes` drives staff-facing notifications;
# `customer_changes` drives every customer-facing dispatch.
comment_customer_visible = bool(comment_body) and ('is_customer_visible' in request.form)
changes = [] changes = []
customer_changes = []
if old_status != issue.status: if old_status != issue.status:
changes.append( _c = (
f'status changed from "{old_status.replace("_"," ").title()}" ' f'status changed from "{old_status.replace("_"," ").title()}" '
f'to "{issue.status.replace("_"," ").title()}"' f'to "{issue.status.replace("_"," ").title()}"'
) )
changes.append(_c)
customer_changes.append(_c)
if old_assigned_to != new_assigned_to: if old_assigned_to != new_assigned_to:
_new_assignee_obj = db.session.get(User, new_assigned_to) if new_assigned_to else None _new_assignee_obj = db.session.get(User, new_assigned_to) if new_assigned_to else None
new_name = _new_assignee_obj.username if _new_assignee_obj else 'Unassigned' new_name = _new_assignee_obj.username if _new_assignee_obj else 'Unassigned'
changes.append(f'reassigned to {new_name}') _c = f'reassigned to {new_name}'
changes.append(_c)
customer_changes.append(_c)
if comment_body: if comment_body:
changes.append(f'new comment added by {current_user.username}') changes.append(f'new comment added by {current_user.username}')
if comment_customer_visible:
customer_changes.append(f'new comment added by {current_user.username}')
if changes: if changes:
_loc = issue.area.name if issue.area else issue.resolved_facility.name if issue.resolved_facility else ''
customer_relevant = bool(customer_changes)
_notify_followers( _notify_followers(
issue = issue, issue = issue,
title = f'Issue #{issue.id} Updated', title = f'Issue #{issue.id} Updated',
body = ( body = (
f'Issue #{issue.id} in {issue.area.name if issue.area else issue.resolved_facility.name if issue.resolved_facility else ''} was updated by ' f'Issue #{issue.id} in {_loc} was updated by '
f'{current_user.username}: {"; ".join(changes)}.' f'{current_user.username}: {"; ".join(changes)}.'
), ),
customer_body = (
f'Issue #{issue.id} in {_loc} was updated by '
f'{current_user.username}: {"; ".join(customer_changes)}.'
) if customer_relevant else None,
skip_customers = not customer_relevant,
exclude_user_ids = exclude_ids, exclude_user_ids = exclude_ids,
) )
# ── Notify via matrix (issue_updated_customer) ─────────────── # ── Notify via matrix (issue_updated_customer) ───────────────
# Gated on `customer_changes`: an update whose ONLY change is an internal
# comment leaves this empty, so no customer notification fires.
facility_id = issue.resolved_facility.id if issue.resolved_facility else None facility_id = issue.resolved_facility.id if issue.resolved_facility else None
if facility_id and changes: if facility_id and customer_changes:
changes_summary = '; '.join(changes) changes_summary = '; '.join(customer_changes)
notify_by_matrix( notify_by_matrix(
event_type = 'issue_updated_customer', event_type = 'issue_updated_customer',
title = f'Issue #{issue.id} Updated at {issue.resolved_facility.name if issue.resolved_facility else ''}', title = f'Issue #{issue.id} Updated at {issue.resolved_facility.name if issue.resolved_facility else ''}',