1
0
Fork 0
mirror of https://gitlab.com/bramw/baserow.git synced 2025-04-15 01:28:30 +00:00

Resolve "Email notifications should have links to redirect the user directly to the specific comment, field, etc."

This commit is contained in:
Bram Wiepjes 2024-04-08 12:30:06 +00:00
parent a704135b07
commit e643ac84ab
34 changed files with 321 additions and 163 deletions

View file

@ -45,6 +45,7 @@ class CollaboratorAddedToRowNotificationType(
EmailNotificationTypeMixin, NotificationType
):
type = "collaborator_added_to_row"
has_web_frontend_route = True
@classmethod
def get_notification_title_for_email(cls, notification, context):
@ -172,6 +173,7 @@ class UserMentionInRichTextFieldNotificationType(
EmailNotificationTypeMixin, NotificationType
):
type = "user_mention_in_rich_text_field"
has_web_frontend_route = True
@classmethod
def get_notification_title_for_email(cls, notification, context):

View file

@ -30,6 +30,7 @@ class FormSubmittedNotificationData:
class FormSubmittedNotificationType(EmailNotificationTypeMixin, NotificationType):
type = "form_submitted"
has_web_frontend_route = True
@classmethod
def create_form_submitted_notification(

View file

@ -160,10 +160,12 @@ class NotificationsSummaryEmail(BaseEmailMessage):
notification, context
)
)
email_url = notification_type.get_web_frontend_url(notification)
rendered_notifications.append(
{
"title": email_title,
"description": email_description,
"url": email_url,
}
)
unlisted_notifications_count = self.new_notifications_count - len(

View file

@ -78,7 +78,7 @@ class Command(BaseCommand):
timestamp = options["timestamp"]
if user_id is not None and not frequency:
result = NotificationHandler.send_new_notifications_to_users_matching_filters_by_email(
result = NotificationHandler.send_unread_notifications_by_email_to_users_matching_filters(
Q(id=user_id), max_emails=max_emails
)
logger.info(

View file

@ -752,7 +752,7 @@ class NotificationHandler:
@classmethod
@baserow_trace(tracer)
def send_new_notifications_to_users_matching_filters_by_email(
def send_unread_notifications_by_email_to_users_matching_filters(
cls, user_filters_q: Q, max_emails: Optional[int] = None
) -> UserWithScheduledEmailNotifications:
"""

View file

@ -55,6 +55,13 @@ class Notification(models.Model):
)
data = models.JSONField(default=dict, help_text="The data of the notification.")
@property
def web_frontend_url(self):
from .registries import notification_type_registry
notification_type = notification_type_registry.get(self.type)
return notification_type.get_web_frontend_url(self)
class Meta:
ordering = ["-created_on"]
indexes = [

View file

@ -1,5 +1,8 @@
from abc import ABCMeta, abstractmethod
from typing import Optional
from urllib.parse import urljoin
from django.conf import settings
from baserow.core.exceptions import (
InstanceTypeAlreadyRegistered,
@ -20,6 +23,18 @@ class NotificationType(MapAPIExceptionsInstanceMixin, Instance):
model_class = Notification
include_in_notifications_email = False
def get_web_frontend_url(self, notification: Notification) -> Optional[str]:
"""
Can optionally return a public URL related to the notification. This is
typically used when the user wants to get more information about the
notification.
:param notification: The notification for which we want to generate the URL.
:return: The generated URL as string, or None if not compatible.
"""
return None
class EmailNotificationTypeMixin(metaclass=ABCMeta):
"""
@ -29,6 +44,12 @@ class EmailNotificationTypeMixin(metaclass=ABCMeta):
include_in_notifications_email = True
has_web_frontend_route = False
"""
If `True`, then the notification will be clickable in the email. Note that this
will only work if a route is defined in the web-frontend.
"""
@classmethod
@abstractmethod
def get_notification_title_for_email(cls, notification, context) -> str:
@ -46,6 +67,16 @@ class EmailNotificationTypeMixin(metaclass=ABCMeta):
and context.
"""
def get_web_frontend_url(self, notification):
if not self.has_web_frontend_route:
return None
base_url = settings.BASEROW_EMBEDDED_SHARE_URL
# This path must match the one defined in web-frontend/modules/core/routes.js
path = f"/notification/{notification.workspace_id}/{notification.id}"
return urljoin(base_url, path)
class CliNotificationTypeMixin(metaclass=ABCMeta):
@classmethod

View file

@ -8,6 +8,7 @@ from baserow.core.notifications.handler import NotificationHandler
from baserow.core.notifications.models import NotificationRecipient
from baserow.core.registries import OperationType
from .exceptions import NotificationDoesNotExist
from .operations import (
ListNotificationsOperationType,
MarkNotificationAsReadOperationType,
@ -36,6 +37,35 @@ class NotificationService:
return NotificationHandler.list_notifications(user, workspace)
@classmethod
def get_notification(
cls,
user: AbstractUser,
workspace_id: int,
notification_id: int,
) -> NotificationRecipient:
"""
Get notification
:param user: The user on whose behalf the request is made.
:param workspace_id: The workspace id to get the notification for.
:param notification_id: The notification id to get.
"""
workspace = cls.get_workspace_if_has_permissions_or_raise(
user, workspace_id, ListNotificationsOperationType
)
try:
notification = NotificationHandler.all_notifications_for_user(
user, workspace
).get(notification_id=notification_id)
except NotificationRecipient.DoesNotExist:
raise NotificationDoesNotExist(
f"Notification {notification_id} is not found."
)
return notification
@classmethod
def mark_notification_as_read(
cls,

View file

@ -128,11 +128,9 @@ def send_instant_notifications_email_to_users():
)
max_emails = settings.EMAIL_NOTIFICATIONS_LIMIT_PER_TASK[notifications_frequency]
return (
NotificationHandler.send_new_notifications_to_users_matching_filters_by_email(
Q(profile__email_notification_frequency=notifications_frequency),
max_emails,
)
return NotificationHandler.send_unread_notifications_by_email_to_users_matching_filters(
Q(profile__email_notification_frequency=notifications_frequency),
max_emails,
)
@ -177,7 +175,7 @@ def send_daily_notifications_email_to_users(now: Optional[datetime] = None):
notifications_frequency = UserProfile.EmailNotificationFrequencyOptions.DAILY.value
max_emails = settings.EMAIL_NOTIFICATIONS_LIMIT_PER_TASK[notifications_frequency]
return handler.send_new_notifications_to_users_matching_filters_by_email(
return handler.send_unread_notifications_by_email_to_users_matching_filters(
Q(
profile__email_notification_frequency=notifications_frequency,
profile__timezone__in=timezones_to_send_notifications,
@ -209,7 +207,7 @@ def send_weekly_notifications_email_to_users(now: Optional[datetime] = None):
notifications_frequency = UserProfile.EmailNotificationFrequencyOptions.WEEKLY.value
max_emails = settings.EMAIL_NOTIFICATIONS_LIMIT_PER_TASK[notifications_frequency]
return handler.send_new_notifications_to_users_matching_filters_by_email(
return handler.send_unread_notifications_by_email_to_users_matching_filters(
Q(
profile__email_notification_frequency=notifications_frequency,
profile__timezone__in=timezones_to_send_notifications,

View file

@ -119,7 +119,7 @@ class SnapshotHandler:
mark_for_deletion=False,
)
.select_related("created_by")
.order_by("-created_at")
.order_by("-created_at", "-id")
)
def create(self, application_id: int, performed_by: User, name: str):

View file

@ -64,6 +64,13 @@
color="#9c9c9f"
font-family="Inter,sans-serif"
/>
<mj-class
name="notification-title"
font-size="14px"
color="#070810"
font-family="Inter,sans-serif"
line-height="170%"
/>
<mj-class
name="notification-description"
font-size="12px"

View file

@ -59,10 +59,8 @@
</style>
<![endif]-->
<!--[if !mso]><!-->
<link href="https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700" rel="stylesheet" type="text/css">
<link href="https://fonts.googleapis.com/css?family=Inter:400,600" rel="stylesheet" type="text/css">
<style type="text/css">
@import url(https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700);
@import url(https://fonts.googleapis.com/css?family=Inter:400,600);
</style>
<!--<![endif]-->
@ -156,11 +154,22 @@
</tr>
<!-- htmlmin:ignore -->{% for notification in notifications %}
<!-- htmlmin:ignore -->
<!-- htmlmin:ignore -->{% if notification.url %}
<!-- htmlmin:ignore -->
<tr>
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#000000;">{{ notification.title }}</div>
<div style="font-family:Inter,sans-serif;font-size:14px;line-height:170%;text-align:left;color:#070810;"><a style="font-family:Inter,sans-serif;font-size:14px;line-height:170%;text-align:left;color:#070810;" href="{{ notification.url }}">{{ notification.title }}</a></div>
</td>
</tr>
<!-- htmlmin:ignore -->{% else %}
<!-- htmlmin:ignore -->
<tr>
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<div style="font-family:Inter,sans-serif;font-size:14px;line-height:170%;text-align:left;color:#070810;">{{ notification.title }}</div>
</td>
</tr>
<!-- htmlmin:ignore -->{% endif %}
<!-- htmlmin:ignore -->
<!-- htmlmin:ignore -->{% if notification.description %}
<!-- htmlmin:ignore -->
<tr>

View file

@ -11,7 +11,12 @@
</mj-text>
<mj-divider border-width="1px" border-style="dashed" border-color="lightgrey" />
<mj-raw><!-- htmlmin:ignore -->{% for notification in notifications %}<!-- htmlmin:ignore --></mj-raw>
<mj-text mj-class="notification-title">{{ notification.title }}</mj-text>
{{ notification.url }}
<mj-raw><!-- htmlmin:ignore -->{% if notification.url %}<!-- htmlmin:ignore --></mj-raw>
<mj-text mj-class="notification-title"><a style="font-family:Inter,sans-serif;font-size:14px;line-height:170%;text-align:left;color:#070810;" href="{{ notification.url }}">{{ notification.title }}</a></mj-text>
<mj-raw><!-- htmlmin:ignore -->{% else %}<!-- htmlmin:ignore --></mj-raw>
<mj-text mj-class="notification-title">{{ notification.title }}</mj-text>
<mj-raw><!-- htmlmin:ignore -->{% endif %}<!-- htmlmin:ignore --></mj-raw>
<mj-raw><!-- htmlmin:ignore -->{% if notification.description %}<!-- htmlmin:ignore --></mj-raw>
<mj-text mj-class="notification-description">{{ notification.description|linebreaksbr }}</mj-text>
<mj-raw><!-- htmlmin:ignore -->{% endif %}<!-- htmlmin:ignore --></mj-raw>

View file

@ -464,7 +464,7 @@ def test_email_notifications_are_created_correctly_for_collaborators_added(
assert response.status_code == HTTP_200_OK
# Force to send the notifications
res = NotificationHandler.send_new_notifications_to_users_matching_filters_by_email(
res = NotificationHandler.send_unread_notifications_by_email_to_users_matching_filters(
Q(pk=user_2.pk)
)
assert res.users_with_notifications == [user_2]
@ -479,12 +479,16 @@ def test_email_notifications_are_created_correctly_for_collaborators_added(
assert user_2_summary_email.to == [user_2.email]
assert user_2_summary_email.get_subject() == "You have 1 new notification - Baserow"
notif = NotificationRecipient.objects.get(recipient=user_2)
notification_url = f"http://localhost:3000/notification/{notif.workspace_id}/{notif.notification_id}"
expected_context = {
"notifications": [
{
"title": f"User 1 assigned you to Collaborator 1 in row unnamed row"
f" {row.id} in Example.",
"description": None,
"url": notification_url,
}
],
"new_notifications_count": 1,
@ -1024,7 +1028,7 @@ def test_email_notifications_are_created_correctly_for_mentions_in_rich_text_fie
)
# Force to send the notifications
res = NotificationHandler.send_new_notifications_to_users_matching_filters_by_email(
res = NotificationHandler.send_unread_notifications_by_email_to_users_matching_filters(
Q(pk=user_2.pk)
)
assert res.users_with_notifications == [user_2]
@ -1039,6 +1043,9 @@ def test_email_notifications_are_created_correctly_for_mentions_in_rich_text_fie
assert user_2_summary_email.to == [user_2.email]
assert user_2_summary_email.get_subject() == "You have 1 new notification - Baserow"
notif = NotificationRecipient.objects.get(recipient=user_2)
notification_url = f"http://localhost:3000/notification/{notif.workspace_id}/{notif.notification_id}"
expected_context = {
"notifications": [
{
@ -1046,6 +1053,7 @@ def test_email_notifications_are_created_correctly_for_mentions_in_rich_text_fie
f"Lisa Smith mentioned you in RichTextField in row unnamed row {row.id} in Example."
),
"description": None,
"url": notification_url,
}
],
"new_notifications_count": 1,

View file

@ -0,0 +1,37 @@
import pytest
from baserow.core.models import Notification
@pytest.mark.django_db
def test_get_web_frontend_url(data_fixture):
user = data_fixture.create_user()
workspace = data_fixture.create_workspace(user=user)
notification = data_fixture.create_workspace_notification_for_users(
recipients=[user], workspace=workspace
)
assert notification.web_frontend_url is None
@pytest.mark.django_db
def test_get_web_frontend_url_with_notification_that_has_url(data_fixture):
workspace = data_fixture.create_workspace()
notification = Notification.objects.create(
type="form_submitted",
workspace_id=workspace.id,
data={
"row_id": 1,
"values": [["Name", "1"]],
"form_id": 1,
"table_id": 2,
"form_name": "Form",
"table_name": "Table",
"database_id": 3,
},
)
assert notification.web_frontend_url == (
f"http://localhost:3000/notification/{workspace.id}/{notification.id}"
)

View file

@ -373,19 +373,19 @@ def test_not_all_notification_types_are_included_in_the_email_notification_summa
notification_type=ExcludedFromEmailTestNotification.type,
)
res = NotificationHandler.send_new_notifications_to_users_matching_filters_by_email(
res = NotificationHandler.send_unread_notifications_by_email_to_users_matching_filters(
Q(profile__email_notification_frequency=options.DAILY)
)
assert res.users_with_notifications == []
assert res.remaining_users_to_notify_count == 0
res = NotificationHandler.send_new_notifications_to_users_matching_filters_by_email(
res = NotificationHandler.send_unread_notifications_by_email_to_users_matching_filters(
Q(profile__email_notification_frequency=options.WEEKLY)
)
assert res.users_with_notifications == []
assert res.remaining_users_to_notify_count == 0
res = NotificationHandler.send_new_notifications_to_users_matching_filters_by_email(
res = NotificationHandler.send_unread_notifications_by_email_to_users_matching_filters(
Q(profile__email_notification_frequency=options.INSTANT)
)
assert res.users_with_notifications == [user_1]
@ -407,6 +407,7 @@ def test_not_all_notification_types_are_included_in_the_email_notification_summa
{
"title": "Test notification",
"description": None,
"url": None,
}
],
"new_notifications_count": 1,
@ -434,19 +435,19 @@ def test_no_email_without_renderable_notifications(
notification_type=ExcludedFromEmailTestNotification.type,
)
res = NotificationHandler.send_new_notifications_to_users_matching_filters_by_email(
res = NotificationHandler.send_unread_notifications_by_email_to_users_matching_filters(
Q(profile__email_notification_frequency=options.DAILY)
)
assert res.users_with_notifications == []
assert res.remaining_users_to_notify_count == 0
res = NotificationHandler.send_new_notifications_to_users_matching_filters_by_email(
res = NotificationHandler.send_unread_notifications_by_email_to_users_matching_filters(
Q(profile__email_notification_frequency=options.WEEKLY)
)
assert res.users_with_notifications == []
assert res.remaining_users_to_notify_count == 0
res = NotificationHandler.send_new_notifications_to_users_matching_filters_by_email(
res = NotificationHandler.send_unread_notifications_by_email_to_users_matching_filters(
Q(profile__email_notification_frequency=options.INSTANT)
)
assert res.users_with_notifications == []
@ -487,7 +488,7 @@ def test_user_with_daily_email_notification_frequency_settings(
notification_type=ExcludedFromEmailTestNotification.type,
)
res = NotificationHandler.send_new_notifications_to_users_matching_filters_by_email(
res = NotificationHandler.send_unread_notifications_by_email_to_users_matching_filters(
Q(profile__email_notification_frequency=email_notification_frequency)
)
assert res.users_with_notifications == [user_1]
@ -509,6 +510,7 @@ def test_user_with_daily_email_notification_frequency_settings(
{
"title": "Test notification",
"description": None,
"url": None,
}
],
"new_notifications_count": 1,
@ -539,7 +541,7 @@ def test_email_notifications_are_sent_only_after_setting_is_activated(
user_1 = UserHandler().update_user(
user_1, email_notification_frequency=options.INSTANT
)
res = NotificationHandler.send_new_notifications_to_users_matching_filters_by_email(
res = NotificationHandler.send_unread_notifications_by_email_to_users_matching_filters(
Q(pk=user_1.pk)
)
assert res.users_with_notifications == []
@ -549,7 +551,7 @@ def test_email_notifications_are_sent_only_after_setting_is_activated(
recipients=[user_1], notification_type=TestNotification.type
)
res = NotificationHandler.send_new_notifications_to_users_matching_filters_by_email(
res = NotificationHandler.send_unread_notifications_by_email_to_users_matching_filters(
Q(pk=user_1.pk)
)
@ -572,6 +574,7 @@ def test_email_notifications_are_sent_only_after_setting_is_activated(
{
"title": "Test notification",
"description": None,
"url": None,
}
],
"new_notifications_count": 1,
@ -601,7 +604,7 @@ def test_email_notifications_are_included_up_to_email_limit(
recipients=[user_1], notification_type=TestNotification.type
)
res = NotificationHandler.send_new_notifications_to_users_matching_filters_by_email(
res = NotificationHandler.send_unread_notifications_by_email_to_users_matching_filters(
Q(pk=user_1.pk)
)
assert res.users_with_notifications == [user_1]
@ -624,6 +627,7 @@ def test_email_notifications_are_included_up_to_email_limit(
{
"title": "Test notification",
"description": None,
"url": None,
}
for _ in range(limit)
],
@ -656,7 +660,7 @@ def test_email_notifications_are_sent_just_once(
)
assert NotificationRecipient.objects.filter(email_scheduled=True).count() == 1
res = NotificationHandler.send_new_notifications_to_users_matching_filters_by_email(
res = NotificationHandler.send_unread_notifications_by_email_to_users_matching_filters(
Q(profile__email_notification_frequency=options.INSTANT)
)
assert res.users_with_notifications == [user_1]
@ -664,7 +668,7 @@ def test_email_notifications_are_sent_just_once(
assert res.remaining_users_to_notify_count == 0
assert NotificationRecipient.objects.filter(email_scheduled=True).count() == 0
res = NotificationHandler.send_new_notifications_to_users_matching_filters_by_email(
res = NotificationHandler.send_unread_notifications_by_email_to_users_matching_filters(
Q(profile__email_notification_frequency=options.INSTANT)
)
assert res.users_with_notifications == []
@ -683,7 +687,7 @@ def test_broadcast_notifications_are_not_sent_by_email(
user_1 = data_fixture.create_user()
res = NotificationHandler.send_new_notifications_to_users_matching_filters_by_email(
res = NotificationHandler.send_unread_notifications_by_email_to_users_matching_filters(
Q(pk=user_1.pk)
)
assert res.users_with_notifications == []
@ -708,7 +712,7 @@ def test_email_notifications_are_not_sent_if_global_setting_is_disabled(
assert NotificationRecipient.objects.filter(email_scheduled=True).count() == 1
res = NotificationHandler.send_new_notifications_to_users_matching_filters_by_email(
res = NotificationHandler.send_unread_notifications_by_email_to_users_matching_filters(
Q(pk=user_1.pk)
)
assert res.users_with_notifications == [user_1]
@ -745,7 +749,7 @@ def test_email_notifications_are_not_sent_if_already_read_by_user(
assert NotificationRecipient.objects.filter(email_scheduled=True).count() == 0
res = NotificationHandler.send_new_notifications_to_users_matching_filters_by_email(
res = NotificationHandler.send_unread_notifications_by_email_to_users_matching_filters(
Q(pk=user_1.pk)
)
assert res.users_with_notifications == []
@ -765,7 +769,7 @@ def test_email_notifications_are_not_sent_if_already_read_by_user(
assert NotificationRecipient.objects.filter(email_scheduled=True).count() == 0
res = NotificationHandler.send_new_notifications_to_users_matching_filters_by_email(
res = NotificationHandler.send_unread_notifications_by_email_to_users_matching_filters(
Q(pk=user_1.pk)
)
assert res.users_with_notifications == []
@ -798,7 +802,7 @@ def test_email_notifications_are_not_sent_if_already_cleared_by_user(
assert NotificationRecipient.objects.filter(email_scheduled=True).count() == 0
res = NotificationHandler.send_new_notifications_to_users_matching_filters_by_email(
res = NotificationHandler.send_unread_notifications_by_email_to_users_matching_filters(
Q(pk=user_1.pk)
)
assert res.users_with_notifications == []

View file

@ -83,6 +83,7 @@ def test_daily_report_is_sent_at_correct_time_according_to_user_timezone(
{
"title": "Test notification",
"description": None,
"url": None,
}
],
"new_notifications_count": 1,
@ -125,10 +126,12 @@ def test_daily_report_is_sent_at_correct_time_according_to_user_timezone(
{
"title": "Test notification",
"description": None,
"url": None,
},
{
"title": "Test notification",
"description": None,
"url": None,
},
],
"new_notifications_count": 2,
@ -207,6 +210,7 @@ def test_weekly_report_is_sent_at_correct_date_and_time_according_to_user_timezo
{
"title": "Test notification",
"description": None,
"url": None,
}
],
"new_notifications_count": 1,
@ -249,10 +253,12 @@ def test_weekly_report_is_sent_at_correct_date_and_time_according_to_user_timezo
{
"title": "Test notification",
"description": None,
"url": None,
},
{
"title": "Test notification",
"description": None,
"url": None,
},
],
"new_notifications_count": 2,

View file

@ -0,0 +1,7 @@
{
"type": "feature",
"message": "Made email notifications clickable and link to related row.",
"issue_number": 1881,
"bullet_points": [],
"created_at": "2024-03-28"
}

View file

@ -48,6 +48,7 @@ class RowCommentNotificationData:
class RowCommentMentionNotificationType(EmailNotificationTypeMixin, NotificationType):
type = "row_comment_mention"
has_web_frontend_route = True
@classmethod
def notify_mentioned_users(cls, row_comment, row, mentions):
@ -93,6 +94,7 @@ class RowCommentNotificationType(EmailNotificationTypeMixin, NotificationType):
"""
type = "row_comment"
has_web_frontend_route = True
@classmethod
def notify_subscribed_users(

View file

@ -232,7 +232,7 @@ def test_email_notifications_are_created_correctly(
assert response.status_code == HTTP_200_OK
# Force to send the notifications
res = NotificationHandler.send_new_notifications_to_users_matching_filters_by_email(
res = NotificationHandler.send_unread_notifications_by_email_to_users_matching_filters(
Q(pk=user_2.pk)
)
assert res.users_with_notifications == [user_2]
@ -247,11 +247,15 @@ def test_email_notifications_are_created_correctly(
assert user_2_summary_email.to == [user_2.email]
assert user_2_summary_email.get_subject() == "You have 1 new notification - Baserow"
notif = NotificationRecipient.objects.get(recipient=user_2)
notification_url = f"http://localhost:3000/notification/{notif.workspace_id}/{notif.notification_id}"
expected_context = {
"notifications": [
{
"title": f"User 1 mentioned you in row {str(row)} in {table.name}.",
"description": "@User 2",
"url": notification_url,
}
],
"new_notifications_count": 1,

View file

@ -1,7 +1,7 @@
<template>
<nuxt-link
class="notification-panel__notification-link"
:to="url"
:to="route"
@click.native="markAsReadAndHandleClick"
>
<div class="notification-panel__notification-content-title">
@ -42,33 +42,6 @@ export default {
RichTextEditor,
},
mixins: [notificationContent],
props: {
notification: {
type: Object,
required: true,
},
workspace: {
type: Object,
required: true,
},
},
computed: {
params() {
const data = this.notification.data
return {
databaseId: data.database_id,
tableId: data.table_id,
rowId: data.row_id,
}
},
url() {
return {
name: 'database-table-row',
params: this.params,
}
},
},
methods: {
handleClick(evt) {
this.$emit('close-panel')

View file

@ -1,7 +1,7 @@
<template>
<nuxt-link
class="notification-panel__notification-link"
:to="url"
:to="route"
@click.native="markAsReadAndHandleClick"
>
<div class="notification-panel__notification-content-title">
@ -40,40 +40,6 @@ export default {
RichTextEditor,
},
mixins: [notificationContent],
props: {
notification: {
type: Object,
required: true,
},
},
computed: {
params() {
const data = this.notification.data
let viewId = null
if (
['database-table-row', 'database-table'].includes(
this.$nuxt.$route.name
) &&
this.$nuxt.$route.params.tableId === this.notification.data.table_id
) {
viewId = this.$nuxt.$route.params.viewId
}
return {
databaseId: data.database_id,
tableId: data.table_id,
rowId: data.row_id,
viewId,
}
},
url() {
return {
name: 'database-table-row',
params: this.params,
}
},
},
methods: {
handleClick(evt) {
this.$emit('close-panel')

View file

@ -15,6 +15,17 @@ export class RowCommentMentionNotificationType extends NotificationType {
getContentComponent() {
return RowCommentMentionNotification
}
getRoute(notificationData) {
return {
name: 'database-table-row',
params: {
databaseId: notificationData.database_id,
tableId: notificationData.table_id,
rowId: notificationData.row_id,
},
}
}
}
export class RowCommentNotificationType extends NotificationType {
@ -29,4 +40,15 @@ export class RowCommentNotificationType extends NotificationType {
getContentComponent() {
return RowCommentNotification
}
getRoute(notificationData) {
return {
name: 'database-table-row',
params: {
databaseId: notificationData.database_id,
tableId: notificationData.table_id,
rowId: notificationData.row_id,
},
}
}
}

View file

@ -22,11 +22,5 @@ import notificationContent from '@baserow/modules/core/mixins/notificationConten
export default {
name: 'BaserowVersionUpgradeNotification',
mixins: [notificationContent],
props: {
notification: {
type: Object,
required: true,
},
},
}
</script>

View file

@ -28,11 +28,5 @@ import notificationContent from '@baserow/modules/core/mixins/notificationConten
export default {
name: 'WorkspaceInvitationAcceptedNotification',
mixins: [notificationContent],
props: {
notification: {
type: Object,
required: true,
},
},
}
</script>

View file

@ -28,12 +28,6 @@ import notificationContent from '@baserow/modules/core/mixins/notificationConten
export default {
name: 'WorkspaceInvitationCreatedNotification',
mixins: [notificationContent],
props: {
notification: {
type: Object,
required: true,
},
},
methods: {
handleClick() {
this.$emit('close-panel')

View file

@ -28,11 +28,5 @@ import notificationContent from '@baserow/modules/core/mixins/notificationConten
export default {
name: 'WorkspaceInvitationRejectedNotification',
mixins: [notificationContent],
props: {
notification: {
type: Object,
required: true,
},
},
}
</script>

View file

@ -1,7 +1,22 @@
import { notifyIf } from '@baserow/modules/core/utils/error'
export default {
props: {
workspace: {
type: Object,
required: true,
},
notification: {
type: Object,
required: true,
},
},
computed: {
route() {
return this.$registry
.get('notification', this.notification.type)
.getRoute(this.notification.data)
},
sender() {
return this.notification.sender?.first_name
},

View file

@ -19,6 +19,17 @@ export class NotificationType extends Registerable {
getIconComponentProps() {
return {}
}
/**
* Should return the nuxt route of the page where to redirect to if the user clicks
* on the notification. Note that the backend also uses this to create a link in
* external communication like the email, and if anything changes in this method,
* the `safe_route_data_parameters` then might need to be updated as well. If
* `null` is returned, it means that the notification is not clickable.
*/
getRoute(notificationData) {
return null
}
}
export class WorkspaceInvitationCreatedNotificationType extends NotificationType {

View file

@ -0,0 +1,37 @@
<script>
import notificationService from '@baserow/modules/core/services/notification'
/**
* This page functions as a never changing path in the web-frontend that will redirect
* the visitor to the correct page related to the provided notification type and ID.
* The reason we have this is so that the backend doesn't need to know about the paths
* available in the web-frontend, and won't break behavior if they change.
*/
export default {
async asyncData({ route, redirect, app, error, store }) {
let notification
try {
const { data } = await notificationService(app.$client).markAsRead(
route.params.workspaceId,
route.params.notificationId
)
notification = data
} catch {
return error({ statusCode: 404, message: 'Notification not found.' })
}
const notificationType = app.$registry.get(
'notification',
notification.type
)
const redirectParams = notificationType.getRoute(notification.data)
if (!redirectParams) {
return error({ statusCode: 404, message: 'Notification has no route.' })
}
return redirect(redirectParams)
},
}
</script>

View file

@ -83,6 +83,11 @@ export const routes = [
},
],
},
{
name: 'notification-redirect',
path: '/notification/:workspaceId/:notificationId',
component: path.resolve(__dirname, 'pages/notificationRedirect.vue'),
},
]
if (process.env.NODE_ENV !== 'production') {

View file

@ -1,7 +1,7 @@
<template>
<nuxt-link
class="notification-panel__notification-link"
:to="url"
:to="route"
@click.native="markAsReadAndHandleClick"
>
<div class="notification-panel__notification-content-title">
@ -36,27 +36,6 @@ import notificationContent from '@baserow/modules/core/mixins/notificationConten
export default {
name: 'CollaboratorAddedToRowNotification',
mixins: [notificationContent],
props: {
notification: {
type: Object,
required: true,
},
},
computed: {
params() {
return {
databaseId: this.notification.data.database_id,
tableId: this.notification.data.table_id,
rowId: this.notification.data.row_id,
}
},
url() {
return {
name: 'database-table-row',
params: this.params,
}
},
},
methods: {
handleClick() {
this.$emit('close-panel')

View file

@ -1,7 +1,7 @@
<template>
<nuxt-link
class="notification-panel__notification-link"
:to="url"
:to="route"
@click.native="markAsReadAndHandleClick"
>
<div class="notification-panel__notification-content-title">
@ -37,31 +37,12 @@ import notificationContent from '@baserow/modules/core/mixins/notificationConten
export default {
name: 'FormSubmittedNotification',
mixins: [notificationContent],
props: {
notification: {
type: Object,
required: true,
},
},
data() {
return {
limitValues: 3, // only the first 3 elements to keep it short
}
},
computed: {
params() {
return {
databaseId: this.notification.data.database_id,
tableId: this.notification.data.table_id,
rowId: this.notification.data.row_id,
}
},
url() {
return {
name: 'database-table-row',
params: this.params,
}
},
submittedValuesSummary() {
return this.notification.data.values
.slice(0, this.limitValues)

View file

@ -16,6 +16,17 @@ export class CollaboratorAddedToRowNotificationType extends NotificationType {
getContentComponent() {
return CollaboratorAddedToRowNotification
}
getRoute(notificationData) {
return {
name: 'database-table-row',
params: {
databaseId: notificationData.database_id,
tableId: notificationData.table_id,
rowId: notificationData.row_id,
},
}
}
}
export class FormSubmittedNotificationType extends NotificationType {
@ -30,6 +41,17 @@ export class FormSubmittedNotificationType extends NotificationType {
getContentComponent() {
return FormSubmittedNotification
}
getRoute(notificationData) {
return {
name: 'database-table-row',
params: {
databaseId: notificationData.database_id,
tableId: notificationData.table_id,
rowId: notificationData.row_id,
},
}
}
}
export class UserMentionInRichTextFieldNotificationType extends NotificationType {
@ -44,4 +66,15 @@ export class UserMentionInRichTextFieldNotificationType extends NotificationType
getContentComponent() {
return UserMentionInRichTextFieldNotification
}
getRoute(notificationData) {
return {
name: 'database-table-row',
params: {
databaseId: notificationData.database_id,
tableId: notificationData.table_id,
rowId: notificationData.row_id,
},
}
}
}