Skip to content

New output Mattermost.#126

Open
mithunbharadwaj wants to merge 20 commits intomainfrom
Feature/add-mattermost-notifications
Open

New output Mattermost.#126
mithunbharadwaj wants to merge 20 commits intomainfrom
Feature/add-mattermost-notifications

Conversation

@mithunbharadwaj
Copy link
Copy Markdown
Contributor

@mithunbharadwaj mithunbharadwaj commented Mar 16, 2026

Summary by CodeRabbit

  • New Features
    • Mattermost support: send templated notifications to Mattermost channels or users with tenant-aware delivery, payload rendering, and queue/web endpoints.
  • Documentation
    • README and examples updated with Mattermost configuration, Kafka/message examples, and supported outputs.
  • Chores
    • Configuration file extended with a Mattermost section and related keys.
  • QA
    • Added Mattermost-focused test cases and a new Mattermost message template.

Mithun Shivashankar added 2 commits March 16, 2026 15:26
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 16, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds Mattermost as a new output channel: new orchestrator, output service, schema and tenant config support, HTTP and Kafka handlers, templates, docs, and sample config; wiring and orchestrator activation are conditional on presence of a [mattermost] config section.

Changes

Cohort / File(s) Summary
Application integration
asabiris/app.py
Conditionally initialize MattermostOutputService and SendMattermostOrchestrator; include "mattermost" in enabled_orchestrators when configured.
HTTP handler
asabiris/handlers/webhandler.py
Add PUT /send_mattermost route and send_mattermost handler with tenant context, mattermost_schema validation, error mapping, and tenant reset.
Kafka handler
asabiris/handlers/kafkahandler.py
Add ValidationSchemaMattermost, dispatch for "mattermost", handle_mattermost, and extend error/template handling to include Mattermost.
Orchestrator
asabiris/orchestration/sendmattermost.py
New SendMattermostOrchestrator: validate message, enforce /Templates/Mattermost/ template path, render body and nested props via Jinja, build payload, delegate to output service.
Output service
asabiris/output/mattermost/service.py, asabiris/output/__init__.py, asabiris/output/mattermost/__init__.py
New MattermostOutputService with tenant-aware config resolution, REST helpers (_post_json, get_user_ids, get_direct_channel), send logic (channel vs DM), error mapping, and exports updated.
Schemas & package exports
asabiris/schemas/__init__.py, asabiris/schemas/mattermostschema.py
Export/import mattermost_schema; provide Mattermost JSON schema for validation.
Tenant configuration
asabiris/tenantconfiguration/tenant_config.py
Add get_mattermost_config(tenant) to extract tenant-level Mattermost settings (url, token, bot_username, security_channel_id).
Docs, templates & QA
README.md, qa.md, library/Templates/Mattermost/message.md
Add README and QA cases for Mattermost; add new Mattermost message template file.
Config sample
etc/asab-iris.conf
Add [mattermost] sample section (url, token, bot_username, security_channel_id) and new SMTP proxy keys.

Sequence Diagram

sequenceDiagram
    participant Client as Client
    participant Web as WebHandler
    participant Validator as JSONValidator
    participant Orch as SendMattermostOrchestrator
    participant Jinja as JinjaService
    participant Output as MattermostOutputService
    Client->>Web: PUT /send_mattermost (JSON)
    Web->>Validator: Validate with mattermost_schema
    alt valid
        Validator-->>Web: Valid
        Web->>Orch: send_to_mattermost(msg, tenant)
        Orch->>Jinja: render body.template with params
        Orch->>Jinja: render nested props (recursively)
        Orch->>Output: send(payload, channel_id/username)
        Output->>Output: resolve tenant config / call Mattermost API
        Output-->>Orch: success or ASABIrisError
        Orch-->>Web: result / error
        Web-->>Client: 200 OK or mapped error
    else invalid
        Validator-->>Web: Validation error
        Web-->>Client: 400 Bad Request
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • ateska

Poem

🐰 I hopped through configs, templates, and code,
Mattermost doors now open on the road,
Tenants, templates, channels in view,
Props rendered snug, and payloads too,
🥕 A rabbit’s small deploy — hooray, we’re through!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 72.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'New output Mattermost' is directly related to the main change in the changeset, which adds comprehensive Mattermost notification support as a new output service across the application.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch Feature/add-mattermost-notifications

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@asabiris/app.py`:
- Around line 137-145: The code references non-existent Mattermost modules
(MattermostOutputService, SendMattermostOrchestrator, and mattermost_schema);
either implement those modules or remove all Mattermost usage—prefer removal for
now: delete any imports of asabiris.output.mattermost and
asabiris.orchestration.sendmattermost and any import of mattermost_schema,
remove the conditional block that instantiates
MattermostOutputService/SendMattermostOrchestrator (or replace it with setting
self.MattermostOutputService = None and self.SendMattermostOrchestrator = None
unconditionally), and search for and remove or guard any other references to
MattermostOutputService, SendMattermostOrchestrator, or mattermost_schema in the
codebase (including webhandler-related code) so no ImportError occurs at
runtime.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1b37def2-6e5b-456b-93ce-6a24cd1de6f9

📥 Commits

Reviewing files that changed from the base of the PR and between 9d62cd8 and fb02a16.

📒 Files selected for processing (2)
  • asabiris/app.py
  • asabiris/handlers/webhandler.py

Comment on lines +137 to +145
if 'mattermost' in asab.Config.sections():
self.MattermostOutputService = MattermostOutputService(self)
if not self.MattermostOutputService.IsConfigured:
self.SendMattermostOrchestrator = None
else:
self.SendMattermostOrchestrator = SendMattermostOrchestrator(self)
else:
self.MattermostOutputService = None
self.SendMattermostOrchestrator = None
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Verify MattermostOutputService has IsConfigured property

# Search for IsConfigured in mattermost output service
fd -t f "mattermost.py" --exec cat {} \; | grep -n "IsConfigured"

# Also check the class definition
ast-grep --pattern 'class MattermostOutputService($_) {
  $$$
}'

Repository: TeskaLabs/asab-iris

Length of output: 45


🏁 Script executed:

# Search for MattermostOutputService across all Python files
fd -t f -e py . | head -20

Repository: TeskaLabs/asab-iris

Length of output: 665


🏁 Script executed:

# Look for imports and MattermostOutputService references in app.py
rg "MattermostOutputService" -B 2 -A 2

Repository: TeskaLabs/asab-iris

Length of output: 931


🏁 Script executed:

# Search for the class definition of MattermostOutputService
ast-grep --pattern 'class MattermostOutputService'

Repository: TeskaLabs/asab-iris

Length of output: 45


🏁 Script executed:

# Search for IsConfigured property across the codebase
rg "IsConfigured" -A 2 -B 2

Repository: TeskaLabs/asab-iris

Length of output: 356


🏁 Script executed:

# Find the mattermost output service file
fd -t f -path "*/output/mattermost*"

Repository: TeskaLabs/asab-iris

Length of output: 231


🏁 Script executed:

# Check the output directory structure
fd -t d -path "*/output" | head -5

Repository: TeskaLabs/asab-iris

Length of output: 231


🏁 Script executed:

# List files in output directory
ls -la asabiris/output/ 2>/dev/null || find . -name "output" -type d

Repository: TeskaLabs/asab-iris

Length of output: 565


🏁 Script executed:

# Search entire repository for MattermostOutputService definition
rg "class MattermostOutputService" -l

Repository: TeskaLabs/asab-iris

Length of output: 45


🏁 Script executed:

# Search for any mattermost references
rg "mattermost" -i

Repository: TeskaLabs/asab-iris

Length of output: 1674


🏁 Script executed:

# Check if mattermost module exists anywhere
find . -name "*mattermost*" -type f

Repository: TeskaLabs/asab-iris

Length of output: 45


🏁 Script executed:

# Check for mattermost schema
fd -t f "mattermostschema*"

Repository: TeskaLabs/asab-iris

Length of output: 45


🏁 Script executed:

# Check orchestration directory
ls -la asabiris/orchestration/

Repository: TeskaLabs/asab-iris

Length of output: 615


🏁 Script executed:

# Check schemas directory
ls -la asabiris/schemas/ 2>/dev/null || find . -name "schemas" -type d

Repository: TeskaLabs/asab-iris

Length of output: 616


Remove Mattermost service references or implement missing modules.

The code references MattermostOutputService (line 138), SendMattermostOrchestrator (line 141), and mattermost_schema, but their implementations do not exist:

  • Missing: asabiris/output/mattermost/ directory and module
  • Missing: asabiris/orchestration/sendmattermost.py
  • Missing: asabiris/schemas/mattermostschema.py

These imports in app.py and webhandler.py will cause ImportError at runtime. Either implement these modules or remove Mattermost support entirely.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@asabiris/app.py` around lines 137 - 145, The code references non-existent
Mattermost modules (MattermostOutputService, SendMattermostOrchestrator, and
mattermost_schema); either implement those modules or remove all Mattermost
usage—prefer removal for now: delete any imports of asabiris.output.mattermost
and asabiris.orchestration.sendmattermost and any import of mattermost_schema,
remove the conditional block that instantiates
MattermostOutputService/SendMattermostOrchestrator (or replace it with setting
self.MattermostOutputService = None and self.SendMattermostOrchestrator = None
unconditionally), and search for and remove or guard any other references to
MattermostOutputService, SendMattermostOrchestrator, or mattermost_schema in the
codebase (including webhandler-related code) so no ImportError occurs at
runtime.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
asabiris/handlers/kafkahandler.py (1)

138-163: ⚠️ Potential issue | 🟠 Major

handle_push() is still unreachable from this dispatcher.

dispatch() never branches on msg_type == "push", so Kafka push messages still fall into the unsupported-type warning even though handle_push() exists later in the same class. Either add the missing branch here or remove the dead handler if push is not meant to be Kafka-driven.

Proposed fix
 			elif msg_type == "sms":
 				if self.App.SendSMSOrchestrator is None:
 					L.warning("SMS service is not configured. Discarding notification.")
 					return
 				await self.handle_sms(msg)
+			elif msg_type == "push":
+				if getattr(self.App, "SendPushOrchestrator", None) is None:
+					L.warning("Push service is not configured. Discarding notification.")
+					return
+				await self.handle_push(msg)
 			else:
 				L.warning(
-					"Notification sending failed: Unsupported message type '{}'. Supported types are 'email', 'mattermost', 'slack', 'msteams', and 'sms'.".format(msg_type)
+					"Notification sending failed: Unsupported message type '{}'. Supported types are 'email', 'mattermost', 'slack', 'msteams', 'sms', and 'push'.".format(msg_type)
 				)

Also applies to: 293-310

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@asabiris/handlers/kafkahandler.py` around lines 138 - 163, The dispatcher in
dispatch() is missing a branch for msg_type == "push", so Kafka push messages
fall through to the unsupported-type warning although handle_push() exists;
either add an elif branch that mirrors the other services (check
self.App.SendPushOrchestrator is not None, warn+return if missing, otherwise
await self.handle_push(msg)) or remove the dead handle_push method and any
SendPushOrchestrator usage; apply the same fix to the other dispatcher
occurrence noted (the second dispatch block referenced in the review) so both
places consistently handle "push".
🧹 Nitpick comments (1)
asabiris/handlers/kafkahandler.py (1)

375-385: Reuse the original Mattermost envelope in the fallback path.

This branch hardcodes a second payload shape and always emits channel_id, username, and tenant, even when those keys are absent. Copying the original msg and only replacing body keeps the error path aligned with the normal Mattermost schema and avoids injecting None fields.

Refactor sketch
 				try:
 					L.log(asab.LOG_NOTICE, "Sending error notification via Mattermost Orchestrator.")
-					await self.App.SendMattermostOrchestrator.send_to_mattermost({
-						"body": {
-							"template": tpl_mattermost,
-							"params": params
-						},
-						"channel_id": msg.get("channel_id"),
-						"username": msg.get("username"),
-						"tenant": msg.get("tenant")
-					})
+					mattermost_msg = {
+						key: value
+						for key, value in msg.items()
+						if key != "body" and value is not None
+					}
+					mattermost_msg["body"] = {
+						"template": tpl_mattermost,
+						"params": params,
+					}
+					await self.App.SendMattermostOrchestrator.send_to_mattermost(mattermost_msg)
 				except ASABIrisError as e:
 					L.info("Error notification to Mattermost unsuccessful: Explanation: {}".format(e.TechMessage))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@asabiris/handlers/kafkahandler.py` around lines 375 - 385, The fallback
Mattermost notification currently constructs a new payload that always injects
channel_id, username, and tenant and can insert None values; instead, reuse the
original msg envelope by copying msg (e.g., new_msg = dict(msg) or msg.copy()),
set new_msg["body"] = {"template": tpl_mattermost, "params": params}, and pass
that to self.App.SendMattermostOrchestrator.send_to_mattermost; keep the L.log
call and await semantics and do not add channel_id/username/tenant keys when
they were absent in the original msg.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@asabiris/handlers/kafkahandler.py`:
- Around line 138-163: The dispatcher in dispatch() is missing a branch for
msg_type == "push", so Kafka push messages fall through to the unsupported-type
warning although handle_push() exists; either add an elif branch that mirrors
the other services (check self.App.SendPushOrchestrator is not None, warn+return
if missing, otherwise await self.handle_push(msg)) or remove the dead
handle_push method and any SendPushOrchestrator usage; apply the same fix to the
other dispatcher occurrence noted (the second dispatch block referenced in the
review) so both places consistently handle "push".

---

Nitpick comments:
In `@asabiris/handlers/kafkahandler.py`:
- Around line 375-385: The fallback Mattermost notification currently constructs
a new payload that always injects channel_id, username, and tenant and can
insert None values; instead, reuse the original msg envelope by copying msg
(e.g., new_msg = dict(msg) or msg.copy()), set new_msg["body"] = {"template":
tpl_mattermost, "params": params}, and pass that to
self.App.SendMattermostOrchestrator.send_to_mattermost; keep the L.log call and
await semantics and do not add channel_id/username/tenant keys when they were
absent in the original msg.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 72bda40a-c56e-49b0-a83b-06c1e2419a37

📥 Commits

Reviewing files that changed from the base of the PR and between fb02a16 and 0c1ff62.

📒 Files selected for processing (1)
  • asabiris/handlers/kafkahandler.py

@mithunbharadwaj mithunbharadwaj requested a review from ateska March 19, 2026 08:14
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@asabiris/orchestration/sendmattermost.py`:
- Around line 107-109: The string rendering in sendmattermost.py uses
self.JinjaService.Environment.from_string(...).render(context) directly which
bypasses the template error handling; replace this direct render with the
existing safe path by calling self.JinjaService.format(value, context) (or, if
you must keep from_string, wrap the render call in try/except Exception as e and
re-raise an ASABIrisError carrying the original exception) so that any broken
props expressions are translated into the standard ASABIrisError flow; refer to
JinjaService.Environment.from_string, self.JinjaService.format, and
ASABIrisError when making the change.

In `@asabiris/output/mattermost/service.py`:
- Around line 156-163: The log call in MattermostOutputService.send is
persisting full message content via L.log (asab.LOG_NOTICE) with struct_data
including post_payload; remove or redact post_payload to avoid logging rendered
notification bodies/PII—replace it with safe metadata (e.g., only channel_id,
username, payload_size, or a whitelist of non-sensitive keys) or omit payload
entirely in the struct_data passed to L.log so the rendered message and props
are not retained.
- Around line 79-83: The tenant-specific lookup assumes the ConfigService has a
functional ZooKeeper backend and calls
self.ConfigService.get_mattermost_config(tenant), which raises AttributeError
when ZK is disabled; to fix, only attempt tenant-scoped resolution when
ConfigService exists and its ZK backend is present (e.g., check
getattr(self.ConfigService, "ZK", None) is not None) before calling
get_mattermost_config, otherwise skip to the global config path (or
alternatively expand the except to catch AttributeError alongside KeyError) so
tenant overrides are not attempted when ZooKeeper is disabled; refer to the
tenant variable and get_mattermost_config / self.ConfigService symbols to locate
the change.
- Around line 260-272: The except block handling aiohttp.ClientError around the
POST request does not catch aiohttp timeouts (asyncio.TimeoutError), so wrap
timeout exceptions in the same ASABIrisError: import asyncio if missing and
change the except to catch both aiohttp.ClientError and asyncio.TimeoutError
(e.g., except (aiohttp.ClientError, asyncio.TimeoutError) as e), preserving the
existing ASABIrisError construction and the "from e" chaining so timeouts are
reported with ErrorCode.SERVER_ERROR and the same tech_message/error_dict.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d1c5822a-ea88-4f19-a9e2-c5dbda7045b6

📥 Commits

Reviewing files that changed from the base of the PR and between 0c1ff62 and e9ed4b0.

📒 Files selected for processing (8)
  • README.md
  • asabiris/orchestration/sendmattermost.py
  • asabiris/output/__init__.py
  • asabiris/output/mattermost/__init__.py
  • asabiris/output/mattermost/service.py
  • asabiris/schemas/__init__.py
  • asabiris/tenantconfiguration/tenant_config.py
  • etc/asab-iris.conf
✅ Files skipped from review due to trivial changes (4)
  • asabiris/schemas/init.py
  • asabiris/output/init.py
  • asabiris/output/mattermost/init.py
  • etc/asab-iris.conf

Comment on lines +107 to +109
if isinstance(value, str):
template = self.JinjaService.Environment.from_string(value)
return template.render(context)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Route body.props rendering failures through ASABIrisError too.

The main message on Line 69 goes through JinjaService.format(), but string leaves in props are rendered directly here. A broken props expression will therefore skip the existing template-error translation and surface as a generic failure instead of the normal IRIS error response.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@asabiris/orchestration/sendmattermost.py` around lines 107 - 109, The string
rendering in sendmattermost.py uses
self.JinjaService.Environment.from_string(...).render(context) directly which
bypasses the template error handling; replace this direct render with the
existing safe path by calling self.JinjaService.format(value, context) (or, if
you must keep from_string, wrap the render call in try/except Exception as e and
re-raise an ASABIrisError carrying the original exception) so that any broken
props expressions are translated into the standard ASABIrisError flow; refer to
JinjaService.Environment.from_string, self.JinjaService.format, and
ASABIrisError when making the change.

Comment on lines +79 to +83
if tenant and self.ConfigService is not None:
try:
tenant_config = self.ConfigService.get_mattermost_config(tenant)
except KeyError:
L.warning("Tenant-specific Mattermost configuration not found for '%s'. Using global config.", tenant)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Skip tenant overrides when ZooKeeper is disabled.

asabiris/tenantconfiguration/tenant_config.py explicitly allows a no-ZooKeeper mode on Lines 15-31, but Line 81 assumes the backend is usable as soon as the service exists. In that mode, get_mattermost_config() eventually hits self.ZK.exists(...) and raises AttributeError, so tenant-scoped sends fail instead of falling back to the global [mattermost] settings.

🛡️ Suggested guard
-		if tenant and self.ConfigService is not None:
+		if (
+			tenant
+			and self.ConfigService is not None
+			and getattr(self.ConfigService, "ZK", None) is not None
+			and getattr(self.ConfigService, "TenantConfigPath", None)
+		):
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@asabiris/output/mattermost/service.py` around lines 79 - 83, The
tenant-specific lookup assumes the ConfigService has a functional ZooKeeper
backend and calls self.ConfigService.get_mattermost_config(tenant), which raises
AttributeError when ZK is disabled; to fix, only attempt tenant-scoped
resolution when ConfigService exists and its ZK backend is present (e.g., check
getattr(self.ConfigService, "ZK", None) is not None) before calling
get_mattermost_config, otherwise skip to the global config path (or
alternatively expand the except to catch AttributeError alongside KeyError) so
tenant overrides are not attempted when ZooKeeper is disabled; refer to the
tenant variable and get_mattermost_config / self.ConfigService symbols to locate
the change.

Comment on lines +156 to +163
L.log(
asab.LOG_NOTICE,
"MattermostOutputService.send -> channel_id=%s, username=%s, payload=%r",
struct_data={
"channel_id": channel_id,
"username": username,
"payload": post_payload,
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Do not persist rendered notification bodies in logs.

post_payload contains the full Mattermost message and optional props; the examples in README.md already include usernames and IP addresses. Emitting that object at NOTICE level turns every notification into log-retained message content/PII.

🧹 Suggested redaction
 		L.log(
 			asab.LOG_NOTICE,
-			"MattermostOutputService.send -> channel_id=%s, username=%s, payload=%r",
-			struct_data={
-				"channel_id": channel_id,
-				"username": username,
-				"payload": post_payload,
-			}
+			"MattermostOutputService.send -> channel_id=%s, username=%s",
+			channel_id,
+			username,
+			struct_data={
+				"channel_id": channel_id,
+				"username": username,
+			},
 		)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
L.log(
asab.LOG_NOTICE,
"MattermostOutputService.send -> channel_id=%s, username=%s, payload=%r",
struct_data={
"channel_id": channel_id,
"username": username,
"payload": post_payload,
}
L.log(
asab.LOG_NOTICE,
"MattermostOutputService.send -> channel_id=%s, username=%s",
channel_id,
username,
struct_data={
"channel_id": channel_id,
"username": username,
},
)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@asabiris/output/mattermost/service.py` around lines 156 - 163, The log call
in MattermostOutputService.send is persisting full message content via L.log
(asab.LOG_NOTICE) with struct_data including post_payload; remove or redact
post_payload to avoid logging rendered notification bodies/PII—replace it with
safe metadata (e.g., only channel_id, username, payload_size, or a whitelist of
non-sensitive keys) or omit payload entirely in the struct_data passed to L.log
so the rendered message and props are not retained.

Comment on lines +260 to +272
timeout = aiohttp.ClientTimeout(total=self.Timeout)

try:
async with aiohttp.ClientSession(timeout=timeout) as session:
async with session.post(url, headers=headers, json=payload) as resp:
body = await resp.text()
except aiohttp.ClientError as e:
raise ASABIrisError(
ErrorCode.SERVER_ERROR,
tech_message="Mattermost request failed: {}".format(e),
error_i18n_key="Error occurred while calling Mattermost. Reason: '{{error_message}}'.",
error_dict={"error_message": str(e)},
) from e
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In aiohttp, does exceeding ClientTimeout(total=...)raiseasyncio.TimeoutErrororaiohttp.ClientError?

💡 Result:

Exceeding ClientTimeout(total=...) in aiohttp raises asyncio.TimeoutError.

Citations:


🏁 Script executed:

# Find and read the service.py file around the mentioned lines
find . -type f -name "service.py" -path "*/mattermost/*" | head -5

Repository: TeskaLabs/asab-iris

Length of output: 102


🏁 Script executed:

# Search for the mattermost service.py file
fd -t f "service\.py" -p "*mattermost*"

Repository: TeskaLabs/asab-iris

Length of output: 162


🏁 Script executed:

# Check the structure first
git ls-files | grep -i mattermost | head -20

Repository: TeskaLabs/asab-iris

Length of output: 180


🏁 Script executed:

# Read the service.py file with line numbers, focusing on the relevant section and imports
head -30 ./asabiris/output/mattermost/service.py

Repository: TeskaLabs/asab-iris

Length of output: 883


🏁 Script executed:

# Read lines around 260-272 with context
sed -n '250,280p' ./asabiris/output/mattermost/service.py

Repository: TeskaLabs/asab-iris

Length of output: 1172


🏁 Script executed:

# Check if asyncio is already imported
grep -n "^import asyncio\|^from asyncio" ./asabiris/output/mattermost/service.py

Repository: TeskaLabs/asab-iris

Length of output: 45


🏁 Script executed:

# Search for all exception handlers in the file to understand full context
grep -n "except " ./asabiris/output/mattermost/service.py

Repository: TeskaLabs/asab-iris

Length of output: 265


🏁 Script executed:

# Read more context after line 272 to check if there's a broader exception handler
sed -n '272,310p' ./asabiris/output/mattermost/service.py

Repository: TeskaLabs/asab-iris

Length of output: 1190


Add asyncio.TimeoutError to the exception handler to catch timeout failures.

When ClientTimeout(total=...) expires, aiohttp raises asyncio.TimeoutError, not aiohttp.ClientError. The current handler at line 266 only catches aiohttp.ClientError, so timeout exceptions escape the ASABIrisError wrapper and propagate uncaught.

Fix
+import asyncio
 import json
 import logging
 import configparser
@@
-		except aiohttp.ClientError as e:
+		except (aiohttp.ClientError, asyncio.TimeoutError) as e:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@asabiris/output/mattermost/service.py` around lines 260 - 272, The except
block handling aiohttp.ClientError around the POST request does not catch
aiohttp timeouts (asyncio.TimeoutError), so wrap timeout exceptions in the same
ASABIrisError: import asyncio if missing and change the except to catch both
aiohttp.ClientError and asyncio.TimeoutError (e.g., except (aiohttp.ClientError,
asyncio.TimeoutError) as e), preserving the existing ASABIrisError construction
and the "from e" chaining so timeouts are reported with ErrorCode.SERVER_ERROR
and the same tech_message/error_dict.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@qa.md`:
- Line 1740: Update the test label string "Access non existant template" to
correct the typo; replace it with "Access non-existent template" (or "Access
nonexistent template") wherever that exact label appears so test names are
consistent and readable.
- Line 1421: Add language identifiers ("json") to the new fenced code blocks in
qa.md to satisfy markdownlint MD040; replace plain fences ``` with ```json for
the request/response JSON examples (e.g., the blocks containing
{"channel_id":"security_channel_id", ...} and {"type":"mattermost",
"channel_id":"security_channel_id", ...}) and apply the same change for the
other occurrences noted (lines referenced in the review). Ensure every JSON
example fence begins with ```json and ends with ``` so the content is properly
highlighted.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3e7fb066-cbd5-442f-8736-ff8cb5667d97

📥 Commits

Reviewing files that changed from the base of the PR and between e9ed4b0 and 8b4957a.

📒 Files selected for processing (1)
  • qa.md


`PUT /send_mattermost`

```
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add language identifiers to new fenced code blocks.

New fences use plain ``` and trigger markdownlint MD040. Please mark request/response examples as `json`.

Suggested fix
- ```
+ ```json
 {
   "channel_id":"security_channel_id",
   ...
 }
- ```
+ ```


- ```
+ ```json
 {"type":"mattermost", "channel_id":"security_channel_id", ...}
- ```
+ ```

Also applies to: 1443-1443, 1464-1464, 1479-1479, 1525-1525, 1542-1542, 1562-1562, 1585-1585, 1603-1603, 1732-1732

🧰 Tools
🪛 markdownlint-cli2 (0.21.0)

[warning] 1421-1421: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@qa.md` at line 1421, Add language identifiers ("json") to the new fenced code
blocks in qa.md to satisfy markdownlint MD040; replace plain fences ``` with
```json for the request/response JSON examples (e.g., the blocks containing
{"channel_id":"security_channel_id", ...} and {"type":"mattermost",
"channel_id":"security_channel_id", ...}) and apply the same change for the
other occurrences noted (lines referenced in the review). Ensure every JSON
example fence begins with ```json and ends with ``` so the content is properly
highlighted.

'Bad template path'
{"type":"mattermost", "channel_id":"security_channel_id", "body":{"template":"/Templates/Mattermosts/message.md", "params":{"user.name":"alice" }}}

'Access non existant template'
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix typo in test label.

“non existant” should be “non existent” (or “nonexistent”) for consistency/readability.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@qa.md` at line 1740, Update the test label string "Access non existant
template" to correct the typo; replace it with "Access non-existent template"
(or "Access nonexistent template") wherever that exact label appears so test
names are consistent and readable.

@mithunbharadwaj mithunbharadwaj changed the title New output mattermos. New output Mattermost. Mar 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant