Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ API_SPECS := \
components/renku_data_services/notebooks/apispec.py \
components/renku_data_services/platform/apispec.py \
components/renku_data_services/data_connectors/apispec.py \
components/renku_data_services/search/apispec.py
components/renku_data_services/search/apispec.py \
components/renku_data_services/notifications/apispec.py

schemas: ${API_SPECS} ## Generate pydantic classes from apispec yaml files
@echo "generated classes based on ApiSpec"
Expand Down
8 changes: 8 additions & 0 deletions bases/renku_data_services/data_api/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from renku_data_services.data_connectors.blueprints import DataConnectorsBP
from renku_data_services.namespace.blueprints import GroupsBP
from renku_data_services.notebooks.blueprints import NotebooksBP, NotebooksNewBP
from renku_data_services.notifications.blueprints import NotificationsBP
from renku_data_services.platform.blueprints import PlatformConfigBP, PlatformUrlRedirectBP
from renku_data_services.project.blueprints import ProjectsBP, ProjectSessionSecretBP
from renku_data_services.repositories.blueprints import RepositoriesBP
Expand Down Expand Up @@ -258,6 +259,12 @@ def register_all_handlers(app: Sanic, dm: DependencyManager) -> Sanic:
authenticator=dm.authenticator,
metrics=dm.metrics,
)
notifications = NotificationsBP(
name="notifications",
url_prefix=url_prefix,
notifications_repo=dm.notifications_repo,
authenticator=dm.authenticator,
)
app.blueprint(
[
resource_pools.blueprint(),
Expand Down Expand Up @@ -286,6 +293,7 @@ def register_all_handlers(app: Sanic, dm: DependencyManager) -> Sanic:
search.blueprint(),
data_connectors.blueprint(),
platform_redirects.blueprint(),
notifications.blueprint(),
]
)
if builds is not None:
Expand Down
8 changes: 8 additions & 0 deletions bases/renku_data_services/data_api/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import renku_data_services.connected_services
import renku_data_services.crc
import renku_data_services.data_connectors
import renku_data_services.notifications
import renku_data_services.platform
import renku_data_services.repositories
import renku_data_services.search
Expand Down Expand Up @@ -54,6 +55,7 @@
from renku_data_services.notebooks.api.classes.data_service import DummyGitProviderHelper, GitProviderHelper
from renku_data_services.notebooks.config import GitProviderHelperProto, get_clusters
from renku_data_services.notebooks.constants import AMALTHEA_SESSION_GVK, JUPYTER_SESSION_GVK
from renku_data_services.notifications.db import NotificationsRepository
from renku_data_services.platform.db import PlatformRepository, UrlRedirectRepository
from renku_data_services.project.db import (
ProjectMemberRepository,
Expand Down Expand Up @@ -143,6 +145,7 @@ class DependencyManager:
shipwright_client: ShipwrightClient | None
url_redirect_repo: UrlRedirectRepository
git_provider_helper: GitProviderHelperProto
notifications_repo: NotificationsRepository

spec: dict[str, Any] = field(init=False, repr=False, default_factory=dict)
app_name: str = "renku_data_services"
Expand Down Expand Up @@ -171,6 +174,7 @@ def load_apispec() -> dict[str, Any]:
renku_data_services.platform.__file__,
renku_data_services.data_connectors.__file__,
renku_data_services.search.__file__,
renku_data_services.notifications.__file__,
]

api_specs = []
Expand Down Expand Up @@ -381,6 +385,9 @@ def from_env(cls) -> DependencyManager:
project_repo=project_repo,
data_connector_repo=data_connector_repo,
)
notifications_repo = NotificationsRepository(
session_maker=config.db.async_session_maker,
)
return cls(
config,
authenticator=authenticator,
Expand Down Expand Up @@ -417,4 +424,5 @@ def from_env(cls) -> DependencyManager:
low_level_user_secrets_repo=low_level_user_secrets_repo,
url_redirect_repo=url_redirect_repo,
git_provider_helper=git_provider_helper,
notifications_repo=notifications_repo,
)
6 changes: 4 additions & 2 deletions components/renku_data_services/migrations/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from renku_data_services.metrics.orm import BaseORM as metrics
from renku_data_services.migrations.utils import run_migrations
from renku_data_services.namespace.orm import BaseORM as namespaces
from renku_data_services.notifications.orm import BaseORM as notifications
from renku_data_services.platform.orm import BaseORM as platform
from renku_data_services.project.orm import BaseORM as project
from renku_data_services.search.orm import BaseORM as search
Expand All @@ -19,20 +20,21 @@

all_metadata = [
authz.metadata,
crc.metadata,
connected_services.metadata,
crc.metadata,
data_connectors.metadata,
events.metadata,
k8s_cache.metadata,
metrics.metadata,
namespaces.metadata,
notifications.metadata,
platform.metadata,
project.metadata,
search.metadata,
secrets.metadata,
sessions.metadata,
storage.metadata,
users.metadata,
search.metadata,
]

run_migrations(all_metadata)
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""add alerts table

Revision ID: 5ec28ea89e0a
Revises: d437be68a4fb
Create Date: 2025-11-05 13:41:25.972261

"""

import sqlalchemy as sa
from alembic import op

from renku_data_services.utils.sqlalchemy import ULIDType

# revision identifiers, used by Alembic.
revision = "5ec28ea89e0a"
down_revision = "42049656cdb8"
branch_labels = None
depends_on = None


def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"alerts",
sa.Column("id", ULIDType(), nullable=False),
sa.Column("title", sa.String(), nullable=False),
sa.Column("message", sa.String(), nullable=False),
sa.Column("user_id", sa.String(), nullable=False),
sa.Column("session_name", sa.String(), nullable=True),
sa.Column("creation_date", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
sa.Column("resolved_at", sa.DateTime(timezone=True), nullable=True),
sa.PrimaryKeyConstraint("id"),
schema="notifications",
)
# ### end Alembic commands ###


def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table("alerts", schema="notifications")
# ### end Alembic commands ###
1 change: 1 addition & 0 deletions components/renku_data_services/notifications/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Blueprints for notifications."""
196 changes: 196 additions & 0 deletions components/renku_data_services/notifications/api.spec.yaml
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good start πŸ‘

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @leafty!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've made these changes now

Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
openapi: 3.0.2
info:
title: Renku Data Services API
description: |
A service that allows alerts to be sent to users.
version: v1
servers:
- url: /api/data
paths:
/alerts:
post:
summary: Send an alert to a user. Requires admin permissions.
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/AlertPost"
responses:
"201":
description: Alert successfully created
content:
"application/json":
schema:
$ref: "#/components/schemas/Alert"
default:
$ref: "#/components/responses/Error"
tags:
- alerts
get:
summary: Retrieve all active alerts for the authenticated user.
parameters:
- name: session_name
in: query
description: Optional filter to only retrieve alerts for a specific session.
required: false
schema:
$ref: "#/components/schemas/SessionName"
responses:
"200":
description: A list of active alerts for the authenticated user.
content:
"application/json":
schema:
$ref: "#/components/schemas/AlertList"
default:
$ref: "#/components/responses/Error"
tags:
- alerts
/alerts/{alert_id}:
patch:
summary: Resolve an alert. Requires admin permissions.
description: Mark an alert as resolved. The alert remains in the database but is no longer active.
parameters:
- in: path
name: alert_id
required: true
schema:
$ref: "#/components/schemas/Ulid"
description: The ID of the alert to resolve.
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/AlertPatch"
responses:
"200":
description: Alert successfully updated
content:
"application/json":
schema:
$ref: "#/components/schemas/Alert"
"404":
description: Alert not found
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
default:
$ref: "#/components/responses/Error"
tags:
- alerts
components:
schemas:
AlertPatch:
description: Data to update an alert.
type: object
additionalProperties: false
properties:
resolved:
type: boolean
description: Set to true to mark the alert as resolved. The resolved_at timestamp will be set automatically.
Ulid:
description: ULID identifier
type: string
minLength: 26
maxLength: 26
pattern: "^[0-7][0-9A-HJKMNP-TV-Z]{25}$" # This is case-insensitive
Alert:
description: An alert sent or displayed to a user.
type: object
properties:
id:
$ref: "#/components/schemas/Ulid"
title:
$ref: "#/components/schemas/AlertTitle"
message:
$ref: "#/components/schemas/AlertMessage"
user_id:
$ref: "#/components/schemas/UserId"
session_name:
$ref: "#/components/schemas/SessionName"
creation_date:
type: string
format: date-time
description: The date and time when the alert was created.
resolved_at:
type: string
format: date-time
nullable: true
description: The date and time when the alert was resolved, or null if it is still active.
required:
- id
- title
- message
- user_id
- creation_date
AlertPost:
description: Data required to create an alert.
type: object
properties:
title:
$ref: "#/components/schemas/AlertTitle"
message:
$ref: "#/components/schemas/AlertMessage"
user_id:
$ref: "#/components/schemas/UserId"
session_name:
$ref: "#/components/schemas/SessionName"
required:
- title
- message
- user_id
AlertTitle:
type: string
description: The title of the alert.
AlertMessage:
type: string
description: The message body of the alert.
SessionName:
description: Renku session name
type: string
minLength: 1
maxLength: 99
example: My Renku Session :)
UserId:
type: string
description: Keycloak user ID
example: f74a228b-1790-4276-af5f-25c2424e9b0c
pattern: "^[A-Za-z0-9]{1}[A-Za-z0-9-]+$"
AlertList:
type: array
description: A list of alerts.
items:
$ref: "#/components/schemas/Alert"
ErrorResponse:
type: object
properties:
error:
type: object
properties:
code:
type: integer
minimum: 0
exclusiveMinimum: true
example: 1404
detail:
type: string
example: A more detailed optional message showing what the problem was
message:
type: string
example: Something went wrong - please try again later
required:
- code
- message
required:
- error

responses:
Error:
description: The schema for all 4xx and 5xx responses
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
Loading
Loading