Skip to content

Commit

Permalink
feat(notifications): u#3965 add a periodic task and a command to dele…
Browse files Browse the repository at this point in the history
…te read notifications
  • Loading branch information
bameda committed Nov 22, 2023
1 parent f276b45 commit 4236815
Show file tree
Hide file tree
Showing 8 changed files with 112 additions and 1 deletion.
4 changes: 3 additions & 1 deletion python/apps/taiga/src/taiga/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
from taiga.base.sampledata.commands import cli as sampledata_cli
from taiga.commons.storage.commands import cli as storage_cli
from taiga.emails.commands import cli as emails_cli
from taiga.notifications.commands import cli as notifications_cli
from taiga.tasksqueue.commands import cli as tasksqueue_cli
from taiga.tasksqueue.commands import init as init_tasksqueue
from taiga.tasksqueue.commands import run_worker, worker
Expand Down Expand Up @@ -69,9 +70,10 @@ def main(
cli.add_typer(db_cli, name="db")
cli.add_typer(emails_cli, name="emails")
cli.add_typer(i18n_cli, name="i18n")
cli.add_typer(notifications_cli, name="notifications")
cli.add_typer(sampledata_cli, name="sampledata")
cli.add_typer(tasksqueue_cli, name="tasksqueue")
cli.add_typer(storage_cli, name="storage")
cli.add_typer(tasksqueue_cli, name="tasksqueue")
cli.add_typer(tokens_cli, name="tokens")
cli.add_typer(users_cli, name="users")

Expand Down
2 changes: 2 additions & 0 deletions python/apps/taiga/src/taiga/conf/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from taiga.conf.events import EventsSettings
from taiga.conf.images import ImageSettings
from taiga.conf.logs import LOGGING_CONFIG
from taiga.conf.notifications import NotificationsSettings
from taiga.conf.storage import StorageSettings
from taiga.conf.tasksqueue import TaskQueueSettings
from taiga.conf.tokens import TokensSettings
Expand Down Expand Up @@ -102,6 +103,7 @@ class Settings(BaseSettings):
EMAIL: EmailSettings = EmailSettings()
EVENTS: EventsSettings = EventsSettings()
IMAGES: ImageSettings = ImageSettings()
NOTIFICATIONS: NotificationsSettings = NotificationsSettings()
STORAGE: StorageSettings = StorageSettings()
TASKQUEUE: TaskQueueSettings = TaskQueueSettings()
TOKENS: TokensSettings = TokensSettings()
Expand Down
13 changes: 13 additions & 0 deletions python/apps/taiga/src/taiga/conf/notifications.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# -*- coding: utf-8 -*-
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# Copyright (c) 2023-present Kaleidos INC

from pydantic import BaseSettings


class NotificationsSettings(BaseSettings):
CLEAN_READ_NOTIFICATIONS_CRON: str = "30 * * * *" # default: every hour at minute 30.
MINUTES_TO_STORE_READ_NOTIFICATIONS: int = 2 * 60 # 120 minutes
1 change: 1 addition & 0 deletions python/apps/taiga/src/taiga/conf/tasksqueue.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class TaskQueueSettings(BaseSettings):
TASKS_MODULES_PATHS: set[str] = {
"taiga.commons.storage.tasks",
"taiga.emails.tasks",
"taiga.notifications.tasks",
"taiga.projects.projects.tasks",
"taiga.tokens.tasks",
"taiga.users.tasks",
Expand Down
40 changes: 40 additions & 0 deletions python/apps/taiga/src/taiga/notifications/commands.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# Copyright (c) 2023-present Kaleidos INC

from datetime import timedelta

import typer
from taiga.base.utils import pprint
from taiga.base.utils.concurrency import run_async_as_sync
from taiga.base.utils.datetime import aware_utcnow
from taiga.conf import settings
from taiga.notifications import services as notifications_services

cli = typer.Typer(
name="Taiga Notifications commands",
help="Manage the notifications system of Taiga.",
add_completion=True,
)


@cli.command(help="Clean read notifications. Remove entries from DB.")
def clean_read_notifications(
minutes_to_store_read_notifications: int = typer.Option(
settings.NOTIFICATIONS.MINUTES_TO_STORE_READ_NOTIFICATIONS,
"--minutes",
"-m",
help="Number of minutes to store read notifications",
),
) -> None:
total_deleted = run_async_as_sync(
notifications_services.clean_read_notifications(
before=aware_utcnow() - timedelta(minutes=minutes_to_store_read_notifications)
)
)

color = "red" if total_deleted else "white"
pprint.print(f"Deleted [bold][{color}]{total_deleted}[/{color}][/bold] notifications.")
16 changes: 16 additions & 0 deletions python/apps/taiga/src/taiga/notifications/repositories.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
# Copyright (c) 2023-present Kaleidos INC

from collections.abc import Iterable
from datetime import datetime
from typing import Any, TypedDict
from uuid import UUID

Expand All @@ -25,6 +26,7 @@ class NotificationFilters(TypedDict, total=False):
id: UUID
owner: User
is_read: bool
read_before: datetime


async def _apply_filters_to_queryset(
Expand All @@ -36,6 +38,9 @@ async def _apply_filters_to_queryset(
if "is_read" in filter_data:
is_read = filter_data.pop("is_read")
filter_data["read_at__isnull"] = not is_read
if "read_before" in filter_data:
read_before = filter_data.pop("read_before")
filter_data["read_at__lt"] = read_before

return qs.filter(**filter_data)

Expand Down Expand Up @@ -111,6 +116,17 @@ async def mark_notifications_as_read(
return [a async for a in qs.all()]


##########################################################
# delete notifications
##########################################################


async def delete_notifications(filters: NotificationFilters = {}) -> int:
qs = await _apply_filters_to_queryset(qs=DEFAULT_QUERYSET, filters=filters)
count, _ = await qs.adelete()
return count


##########################################################
# misc
##########################################################
Expand Down
5 changes: 5 additions & 0 deletions python/apps/taiga/src/taiga/notifications/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
# Copyright (c) 2023-present Kaleidos INC

from collections.abc import Iterable
from datetime import datetime
from uuid import UUID

from taiga.base.serializers import BaseModel
Expand Down Expand Up @@ -57,3 +58,7 @@ async def count_user_notifications(user: User) -> dict[str, int]:
total = await notifications_repositories.count_notifications(filters={"owner": user})
read = await notifications_repositories.count_notifications(filters={"owner": user, "is_read": True})
return {"total": total, "read": read, "unread": total - read}


async def clean_read_notifications(before: datetime) -> int:
return await notifications_repositories.delete_notifications(filters={"is_read": True, "read_before": before})
32 changes: 32 additions & 0 deletions python/apps/taiga/src/taiga/notifications/tasks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# Copyright (c) 2023-present Kaleidos INC

import logging
from datetime import timedelta

from taiga.base.utils.datetime import aware_utcnow
from taiga.conf import settings
from taiga.notifications import services as notifications_services
from taiga.tasksqueue.manager import manager as tqmanager

logger = logging.getLogger(__name__)


@tqmanager.periodic(cron=settings.NOTIFICATIONS.CLEAN_READ_NOTIFICATIONS_CRON) # type: ignore
@tqmanager.task
async def clean_read_notifications(timestamp: int) -> int:
total_deleted = await notifications_services.clean_read_notifications(
before=aware_utcnow() - timedelta(minutes=settings.NOTIFICATIONS.MINUTES_TO_STORE_READ_NOTIFICATIONS)
)

logger.info(
"deleted notifications: %s",
total_deleted,
extra={"deleted": total_deleted},
)

return total_deleted

0 comments on commit 4236815

Please sign in to comment.