Skip to content

Commit 1d4bf65

Browse files
feat: Add Notification Client
Notification Client - Apply Dynamic Strategy
1 parent 26a2e86 commit 1d4bf65

File tree

14 files changed

+616
-0
lines changed

14 files changed

+616
-0
lines changed

docs/api_reference.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ API Reference
2222
api_reference/work_item
2323
api_reference/test_plan
2424
api_reference/artifact
25+
api_reference/notification
2526

2627
Indices and tables
2728
------------------
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
.. _api_notification_page:
2+
3+
nisystemlink.clients.notification
4+
==========================
5+
6+
.. autoclass:: nisystemlink.clients.notification.NotificationClient
7+
:exclude-members: __init__
8+
9+
.. automethod:: __init__
10+
.. automethod:: apply_notification_strategy
11+
12+
.. automodule:: nisystemlink.clients.notification.models
13+
:members:
14+
:imported-members:

docs/getting_started.rst

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,3 +506,29 @@ Create, query and delete test plan templates.
506506
.. literalinclude:: ../examples/test_plan/test_plan_templates.py
507507
:language: python
508508
:linenos:
509+
510+
Notification API
511+
----------------
512+
513+
Overview
514+
~~~~~~~~
515+
516+
The :class:`.NotificationClient` class is the primary entry point of the Notification API.
517+
518+
When constructing a :class:`.NotificationClient`, you can pass an
519+
:class:`.HttpConfiguration` (like one retrieved from the
520+
:class:`.HttpConfigurationManager`), or let :class:`.NotificationClient` use the
521+
default connection. The default connection depends on your environment.
522+
523+
With a :class:`.NotificationClient` object, you can:
524+
525+
* Apply dynamic notification strategy using :meth:`~.NotificationClient.apply_notification_strategy`
526+
527+
Examples
528+
~~~~~~~~
529+
530+
Apply a notification strategy
531+
532+
.. literalinclude:: ../examples/notification/notification.py
533+
:language: python
534+
:linenos:
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import uuid
2+
from datetime import datetime
3+
4+
from nisystemlink.clients.alarm import AlarmClient
5+
from nisystemlink.clients.alarm.models._alarm import Alarm, AlarmSeverityLevel
6+
from nisystemlink.clients.alarm.models._create_or_update_alarm_request import (
7+
CreateOrUpdateAlarmRequest,
8+
SetAlarmTransition,
9+
)
10+
from nisystemlink.clients.core import HttpConfiguration
11+
from nisystemlink.clients.notification import NotificationClient
12+
from nisystemlink.clients.notification.models import (
13+
AddressFields,
14+
AddressGroup,
15+
DynamicNotificationConfiguration,
16+
DynamicNotificationStrategy,
17+
DynamicStrategyRequest,
18+
MessageTemplate,
19+
MessageTemplateFields,
20+
)
21+
22+
# Server configuration is not required when used with SystemLink Client or run through Jupyter on SystemLink
23+
server_configuration: HttpConfiguration | None = None
24+
25+
# To set up the server configuration to point to your instance of SystemLink Enterprise, uncomment
26+
# the following lines and provide your server URI and API key.
27+
# server_configuration = HttpConfiguration(
28+
# server_uri="https://yourserver.yourcompany.com",
29+
# api_key="",
30+
# )
31+
32+
33+
# Create request for applying strategy
34+
def create_notification_request_for_alarm(
35+
alarm: Alarm,
36+
address_group: AddressGroup,
37+
message_template: MessageTemplate,
38+
) -> DynamicStrategyRequest:
39+
"""Creates and returns a dynamic strategy request."""
40+
occurred_at = alarm.most_recent_transition_occurred_at
41+
42+
return DynamicStrategyRequest(
43+
message_template_substitution_fields={
44+
"alarm_id": alarm.alarm_id,
45+
"alarm_condition": alarm.condition,
46+
"alarm_description": alarm.description,
47+
"alarm_severity": str(alarm.current_severity_level),
48+
"alarm_occurred_at": occurred_at.isoformat() if occurred_at else "",
49+
},
50+
notification_strategy=DynamicNotificationStrategy(
51+
notification_configurations=[
52+
DynamicNotificationConfiguration(
53+
address_group=address_group,
54+
message_template=message_template,
55+
)
56+
]
57+
),
58+
)
59+
60+
61+
# Create clients for Notification and Alarm services
62+
notification_client = NotificationClient(configuration=server_configuration)
63+
alarm_client = AlarmClient(configuration=server_configuration)
64+
65+
# Create a unique alarm ID for this example
66+
alarm_id = f"example_alarm_{uuid.uuid1().hex}"
67+
68+
# Create an alarm with a SET transition
69+
create_alarm_request = CreateOrUpdateAlarmRequest(
70+
alarm_id=alarm_id,
71+
transition=SetAlarmTransition(
72+
occurred_at=datetime.now(),
73+
severity_level=AlarmSeverityLevel.HIGH,
74+
value="85",
75+
condition="Greater than 80",
76+
short_text="Temperature is high",
77+
detail_text="Temperature sensor reading is 85°C (higher than the configured threshold of 80°C)",
78+
),
79+
description="Example alarm for notification",
80+
)
81+
id = alarm_client.create_or_update_alarm(create_alarm_request)
82+
print("Alarm created successfully")
83+
84+
# Get the alarm by its instance ID (the unique occurrence identifier)
85+
retrieved_alarm = alarm_client.get_alarm(instance_id=id)
86+
87+
# Define recipients to notify
88+
recipients = AddressFields(toAddresses=["sample1@example.com"])
89+
90+
# Create address group
91+
address_group = AddressGroup(
92+
interpreting_service_name="smtp",
93+
display_name="Alarm Notification Recipients",
94+
properties={"address group": "Alarm"},
95+
fields=recipients,
96+
)
97+
98+
# Create mail template for alarm creation notification
99+
alarm_creation_template = MessageTemplate(
100+
interpreting_service_name="smtp",
101+
display_name="Alarm Creation Template",
102+
fields=MessageTemplateFields(
103+
subject_template="Alarm Created: <alarm_id>",
104+
body_template="An alarm with ID <alarm_id> has been created.\n"
105+
"Condition: <alarm_condition>\n"
106+
"Description: <alarm_description>\n"
107+
"Current severity: <alarm_severity>\n"
108+
"Occurred At: <alarm_occurred_at>",
109+
),
110+
)
111+
112+
# Send notification for alarm creation
113+
notification_for_alarm_creation = create_notification_request_for_alarm(
114+
alarm=retrieved_alarm,
115+
address_group=address_group,
116+
message_template=alarm_creation_template,
117+
)
118+
notification_client.apply_notification_strategy(request=notification_for_alarm_creation)
119+
print("Notification sent for alarm creation")
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from ._notification_client import NotificationClient
2+
3+
# flake8: noqa
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
"""Implementation of Notification Client"""
2+
3+
from nisystemlink.clients import core
4+
from nisystemlink.clients.core._uplink._base_client import BaseClient
5+
from nisystemlink.clients.core._uplink._methods import post
6+
from uplink import retry
7+
8+
from . import models
9+
10+
11+
@retry(
12+
when=retry.when.status(408, 429, 502, 503, 504),
13+
stop=retry.stop.after_attempt(1),
14+
on_exception=retry.CONNECTION_ERROR,
15+
)
16+
class NotificationClient(BaseClient):
17+
def __init__(self, configuration: core.HttpConfiguration | None = None):
18+
"""Initialize an instance.
19+
20+
Args:
21+
configuration: Defines the web server to connect to and information about
22+
how to connect. If not provided, the
23+
:class:`HttpConfigurationManager <nisystemlink.clients.core.HttpConfigurationManager>`
24+
is used to obtain the configuration.
25+
26+
Raises:
27+
ApiException: if unable to communicate with the `/ninotification` service.
28+
"""
29+
if configuration is None:
30+
configuration = core.HttpConfigurationManager.get_configuration()
31+
32+
super().__init__(configuration, base_path="/ninotification/v1/")
33+
34+
@post("apply-dynamic-strategy")
35+
def apply_notification_strategy(
36+
self, request: models.DynamicStrategyRequest
37+
) -> None:
38+
"""Applies the notification strategy from the given request.
39+
40+
Args:
41+
request: Request with message template substitution fields and notification strategies.
42+
43+
Returns:
44+
None.
45+
46+
Raises:
47+
ApiException: if unable to communicate with the `/ninotification` service or provided invalid arguments.
48+
"""
49+
...
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from ._address_group import AddressGroup, AddressFields
2+
from ._dynamic_strategy_request import DynamicStrategyRequest
3+
from ._message_template import MessageTemplate, MessageTemplateFields
4+
from ._dynamic_notification_configuration import DynamicNotificationConfiguration
5+
from ._dynamic_notification_strategy import DynamicNotificationStrategy
6+
7+
# flake8: noqa
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
from typing import List
2+
3+
from nisystemlink.clients.core._uplink._json_model import JsonModel
4+
5+
from ._common_meta_data import BaseNotificationMetadata
6+
7+
8+
class AddressFields(JsonModel):
9+
"""Fields representing the subject and body templates of a message."""
10+
11+
toAddresses: List[str] | None = None
12+
"""List of primary recipient addresses."""
13+
14+
ccAddresses: List[str] | None = None
15+
"""List of carbon copy recipient addresses."""
16+
17+
bccAddresses: List[str] | None = None
18+
"""List of blind carbon copy recipient addresses."""
19+
20+
21+
class AddressGroup(BaseNotificationMetadata):
22+
"""Model defining notification recipients."""
23+
24+
interpreting_service_name: str
25+
"""Gets or sets the name of the interpreting service.
26+
27+
Example: "smtp"
28+
"""
29+
30+
fields: AddressFields
31+
"""Gets or sets the address group's fields. Requires at least one valid recipient.
32+
33+
Valid fields:
34+
- toAddresses
35+
- ccAddresses
36+
- bccAddresses
37+
38+
Example:
39+
{
40+
toAddresses: [ "address1@example.com" ],
41+
ccAddresses: [ "address2@example.com" ],
42+
bccAddresses: [ "address3@example.com" ]
43+
}
44+
"""
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from typing import Dict, List
2+
3+
from nisystemlink.clients.core._uplink._json_model import JsonModel
4+
5+
6+
class BaseNotificationMetadata(JsonModel):
7+
id: str | None = None
8+
"""Gets or sets the ID"""
9+
10+
interpreting_service_name: str | None = None
11+
"""Gets or sets the name of the interpreting service.
12+
13+
Example: "smtp"
14+
"""
15+
16+
display_name: str | None = None
17+
"""Gets or sets the display name.
18+
19+
Example: "name"
20+
"""
21+
22+
properties: Dict[str, str] | None = None
23+
"""Gets or sets the properties.
24+
25+
Example: { "property": "value" }
26+
"""
27+
28+
referencing_notification_strategies: List[str] | None = None
29+
"""Gets or sets the referencing notification strategies."""
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from nisystemlink.clients.core._uplink._json_model import JsonModel
2+
3+
from ._address_group import AddressGroup
4+
from ._message_template import MessageTemplate
5+
6+
7+
class DynamicNotificationConfiguration(JsonModel):
8+
"""Model for notification configuration defining address groups and message template for the notification.
9+
10+
Requires at least one of addressGroupId or addressGroup, and one of messageTemplateId or messageTemplate.
11+
"""
12+
13+
address_group_id: str | None = None
14+
"""Gets the address group ID"""
15+
16+
message_template_id: str | None = None
17+
"""Gets the message template ID"""
18+
19+
address_group: AddressGroup | None = None
20+
"""Gets the address group defining notification recipients."""
21+
22+
message_template: MessageTemplate | None = None
23+
"""Gets the message template defining notification content structure"""

0 commit comments

Comments
 (0)