feature/notification-actions #5
3 changed files with 28 additions and 7 deletions
|
|
@ -127,12 +127,18 @@ class PushMessage(BaseModel):
|
||||||
def get_gotify_extras(self):
|
def get_gotify_extras(self):
|
||||||
"""Merge the resolved click url into extras using Gotify's click-action
|
"""Merge the resolved click url into extras using Gotify's click-action
|
||||||
convention, without disturbing any other extras keys the caller already
|
convention, without disturbing any other extras keys the caller already
|
||||||
set."""
|
set. Also passes action_code through as-is, when set — even if it
|
||||||
|
didn't resolve to a click_url yet (e.g. no NotificationLink row for
|
||||||
|
it) — so the frontend can see which action_code a notification
|
||||||
|
carries, not just the URL it happened to resolve to."""
|
||||||
extras = dict(self.extras or {})
|
extras = dict(self.extras or {})
|
||||||
click_url = self.resolve_click_url()
|
click_url = self.resolve_click_url()
|
||||||
if click_url:
|
if click_url or self.action_code:
|
||||||
notification_extra = dict(extras.get(self.GOTIFY_CLICK_EXTRA_KEY) or {})
|
notification_extra = dict(extras.get(self.GOTIFY_CLICK_EXTRA_KEY) or {})
|
||||||
notification_extra["click"] = {"url": click_url}
|
if click_url:
|
||||||
|
notification_extra["click"] = {"url": click_url}
|
||||||
|
if self.action_code:
|
||||||
|
notification_extra["action_code"] = self.action_code
|
||||||
extras[self.GOTIFY_CLICK_EXTRA_KEY] = notification_extra
|
extras[self.GOTIFY_CLICK_EXTRA_KEY] = notification_extra
|
||||||
return extras
|
return extras
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,8 @@ is clickable at all — is under a reserved key, per
|
||||||
"ads::ad::approve": true,
|
"ads::ad::approve": true,
|
||||||
"ad_uuid": "8fb638c2-e6a4-4baf-aefe-83dab78fb5bd",
|
"ad_uuid": "8fb638c2-e6a4-4baf-aefe-83dab78fb5bd",
|
||||||
"client::notification": {
|
"client::notification": {
|
||||||
"click": { "url": "https://app.gooyal.ir/ads/8fb638c2-e6a4-4baf-aefe-83dab78fb5bd" }
|
"click": { "url": "https://app.gooyal.ir/ads/8fb638c2-e6a4-4baf-aefe-83dab78fb5bd" },
|
||||||
|
"action_code": "billboard.approved"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -34,6 +35,13 @@ is clickable at all — is under a reserved key, per
|
||||||
Pull `extras["client::notification"]["click"]["url"]`. If it's present, tapping
|
Pull `extras["client::notification"]["click"]["url"]`. If it's present, tapping
|
||||||
navigates there. **If it's absent, do nothing on tap** — see next section.
|
navigates there. **If it's absent, do nothing on tap** — see next section.
|
||||||
|
|
||||||
|
`extras["client::notification"]["action_code"]` rides along too, whenever the
|
||||||
|
producer sent one — even if it hasn't resolved to a `click` URL yet (no
|
||||||
|
`NotificationLink` row for it, or it's inactive). You don't need it to
|
||||||
|
implement tap-to-navigate; it's there for client-side logic keyed off the
|
||||||
|
notification *type* itself (grouping, icons, analytics, a fallback in-app
|
||||||
|
handler for a code before its admin row exists), not just its destination.
|
||||||
|
|
||||||
## `click_url` is optional — most notifications are not clickable
|
## `click_url` is optional — most notifications are not clickable
|
||||||
|
|
||||||
Do not assume every notification carries a URL. The backend field this comes
|
Do not assume every notification carries a URL. The backend field this comes
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,10 @@ Two ways to set a destination on a `PushMessage`:
|
||||||
GOTIFY_CLICK_EXTRA_KEY = "client::notification" # Gotify's own reserved namespace
|
GOTIFY_CLICK_EXTRA_KEY = "client::notification" # Gotify's own reserved namespace
|
||||||
|
|
||||||
click_url = models.URLField(max_length=1000, null=True, blank=True)
|
click_url = models.URLField(max_length=1000, null=True, blank=True)
|
||||||
action_code = models.SlugField(max_length=100, null=True, blank=True, db_index=True)
|
# CharField, not SlugField -- the documented catalog is dotted
|
||||||
|
# (billboard.approved, escrow.timeout, ...) and SlugField rejects dots.
|
||||||
|
action_code = models.CharField(max_length=100, null=True, blank=True, db_index=True,
|
||||||
|
validators=[validate_action_code])
|
||||||
click_object_id_value = models.CharField(max_length=255, null=True, blank=True)
|
click_object_id_value = models.CharField(max_length=255, null=True, blank=True)
|
||||||
|
|
||||||
def resolve_click_url(self):
|
def resolve_click_url(self):
|
||||||
|
|
@ -103,15 +106,19 @@ def resolve_click_url(self):
|
||||||
def get_gotify_extras(self):
|
def get_gotify_extras(self):
|
||||||
extras = dict(self.extras or {})
|
extras = dict(self.extras or {})
|
||||||
click_url = self.resolve_click_url()
|
click_url = self.resolve_click_url()
|
||||||
if click_url:
|
if click_url or self.action_code:
|
||||||
notification_extra = dict(extras.get(self.GOTIFY_CLICK_EXTRA_KEY) or {})
|
notification_extra = dict(extras.get(self.GOTIFY_CLICK_EXTRA_KEY) or {})
|
||||||
notification_extra["click"] = {"url": click_url}
|
if click_url:
|
||||||
|
notification_extra["click"] = {"url": click_url}
|
||||||
|
if self.action_code:
|
||||||
|
notification_extra["action_code"] = self.action_code
|
||||||
extras[self.GOTIFY_CLICK_EXTRA_KEY] = notification_extra
|
extras[self.GOTIFY_CLICK_EXTRA_KEY] = notification_extra
|
||||||
return extras
|
return extras
|
||||||
```
|
```
|
||||||
|
|
||||||
Design decisions worth knowing if you touch this:
|
Design decisions worth knowing if you touch this:
|
||||||
- **An unresolvable action_code is not an error** — no matching `NotificationLink`, or one marked `is_active=False`, just means no click action, same as a message with nothing set at all. A producer can start sending a new action_code before anyone's configured it in the admin; nothing breaks, the notification just isn't clickable yet.
|
- **An unresolvable action_code is not an error** — no matching `NotificationLink`, or one marked `is_active=False`, just means no click action, same as a message with nothing set at all. A producer can start sending a new action_code before anyone's configured it in the admin; nothing breaks, the notification just isn't clickable yet.
|
||||||
|
- **`action_code` itself rides along in extras, not just the URL it resolves to** — added after testing against a live Gotify container surfaced that the frontend had no way to see which action_code produced a notification, only its resolved destination (or nothing, if unresolved). It's included even when there's no click_url yet, so the frontend can key client-side logic off the notification type itself.
|
||||||
- **The merge is additive**: any other `extras` keys a producer already sends (e.g. chat's `conversation_uuid`/`post_id`) pass through untouched — `get_gotify_extras()` only ever adds the `client::notification` key, never removes others.
|
- **The merge is additive**: any other `extras` keys a producer already sends (e.g. chat's `conversation_uuid`/`post_id`) pass through untouched — `get_gotify_extras()` only ever adds the `client::notification` key, never removes others.
|
||||||
- **`get_gotify_extras()` is the single place resolution happens** — `tasks.py` calls it instead of reading `push_message.extras`/`click_url` directly, so the single-push path, the bulk-push path, and both `click_url` and `action_code` all go through one function.
|
- **`get_gotify_extras()` is the single place resolution happens** — `tasks.py` calls it instead of reading `push_message.extras`/`click_url` directly, so the single-push path, the bulk-push path, and both `click_url` and `action_code` all go through one function.
|
||||||
- **The admin table is deliberately not seeded with rows by any migration.** Populating it is an ops/product decision (which is the entire point of moving it out of code) — see [frontend-notification-click-actions.md](frontend-notification-click-actions.md) for the current action_code catalog they need to fill in.
|
- **The admin table is deliberately not seeded with rows by any migration.** Populating it is an ops/product decision (which is the entire point of moving it out of code) — see [frontend-notification-click-actions.md](frontend-notification-click-actions.md) for the current action_code catalog they need to fill in.
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue