Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
40650fd
feat(reports): add optional Teams integration configuration
KristiDodaj Oct 19, 2026
eeaa261
feat(reports): persist Teams bot installations
KristiDodaj Oct 19, 2026
2232075
feat(reports): handle authenticated Teams installation events
KristiDodaj Oct 19, 2026
0b0e3b9
feat(reports): deliver alert notifications to Teams channels
KristiDodaj Oct 19, 2026
c71d86f
feat(reports): validate Teams recipients on create, update, and delivery
KristiDodaj Oct 19, 2026
88af203
feat(reports): add Teams destination selection to alert configuration
KristiDodaj Oct 19, 2026
cabde02
fix(reports): handle Teams configuration changes and delivery timeouts
KristiDodaj Oct 19, 2026
dd650b7
test(reports): expand Teams notification coverage and document setup
KristiDodaj Oct 20, 2026
fcf4462
refactor(reports): simplify Teams delivery timeout handling
KristiDodaj Oct 20, 2026
3a90b63
fix(reports): validate Teams destination IDs during configuration
KristiDodaj Oct 20, 2026
742b7ef
fix(reports): hide Teams options when optional SDK is missing
KristiDodaj Oct 20, 2026
5d13c4a
fix(reports): limit Teams installation callback request size
KristiDodaj Oct 20, 2026
e47cac0
refactor(reports): simplify Teams destination discovery and consolida…
KristiDodaj Oct 20, 2026
4c6c8e2
test(reports): consolidate Teams test fixtures and coverage
KristiDodaj Oct 20, 2026
c0d82bf
docs(reports): fix Teams configuration guide link
KristiDodaj Oct 22, 2026
b32f64c
fix(reports): handle Teams alert formats and stale installation events
KristiDodaj Oct 23, 2026
fb0d270
fix(reports): preserve Teams event precision and handle app upgrades
KristiDodaj Oct 24, 2026
2db3919
fix(reports): support older Flask and allow pausing unavailable Teams…
KristiDodaj Oct 24, 2026
79241e0
fix(reports): preserve recipient normalization for non-Teams methods
KristiDodaj Oct 24, 2026
4fcb591
fix(reports): allow unchanged Teams alert edits when unavailable
KristiDodaj Oct 25, 2026
70ee150
fix(reports): reject duplicate Teams destinations
KristiDodaj Oct 25, 2026
c1034e7
fix(reports): harden Teams delivery retries and callback tests
KristiDodaj Oct 26, 2026
7d9c27d
test(reports): cover Teams authorization failure on final attempt
KristiDodaj Oct 26, 2026
f4066eb
fix(ci): lock Teams SDK development dependencies
KristiDodaj Oct 26, 2026
47dc737
test(reports): isolate saved Teams format fixtures
KristiDodaj Oct 26, 2026
27c27f2
fix(reports): preserve Teams delivery success and clarify team IDs
KristiDodaj Oct 26, 2026
b652516
test(reports): assert exact Teams notification error types
KristiDodaj Oct 28, 2026
6834d36
fix(reports): localize Teams notification text
KristiDodaj Oct 30, 2026
056cfe9
fix(reports): preserve Teams review fixes after rebase
KristiDodaj Oct 30, 2026
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
2 changes: 2 additions & 0 deletions .github/workflows/superset-python-unittest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ jobs:
uses: $/.github/actions/setup-backend/
with:
python-version: ${{ matrix.python-version }}
- name: Verify Teams SDK is available for authentication and delivery tests
run: python -c "import microsoft_teams.apps"
- name: SDK unit tests
env:
PYTEST_DISABLE_PLUGIN_AUTOLOAD: "1"
Expand Down
211 changes: 211 additions & 0 deletions docs/admin_docs/configuration/alerts-reports.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -892,6 +892,217 @@ This information can then be retrieved from the endpoint `/api/v1/saved_query/`
schedule the queries that have `schedule_info` in their JSON metadata. For schedulers other than
Airflow, additional fields can be easily added to the configuration file above.

## Microsoft Teams configuration

Teams is an optional, disabled-by-default notification destination for
attachment-free alerts in standard channels. Each deployment supports one
Microsoft tenant and bot identity. Scheduled reports, private/shared channels,
direct messages, and file attachments are not supported.

### Install and configure

Install the extra in both the web-server and Celery worker images using the same
Superset release as the deployment:

```bash
pip install 'apache-superset[teams]==<your-superset-version>'
superset db upgrade
```

Configure both processes with the same server-side settings. Load the secret
value from a deployment secret store, rather than checking it into source control:

```python
import os

FEATURE_FLAGS = {
"ALERT_REPORTS": True,
"ALERT_REPORT_TEAMS": True,
}
ALERT_REPORTS_TEAMS = {
"tenant_id": os.environ["TEAMS_TENANT_ID"],
"application_id": os.environ["TEAMS_APPLICATION_ID"],
"credential": os.environ["TEAMS_CLIENT_SECRET"],
"destinations": [
{
"key": "ops",
"label": "Operations alerts",
"team_id": os.environ["TEAMS_OPS_TEAM_ID"],
"channel_id": os.environ["TEAMS_OPS_CHANNEL_ID"],
},
],
}
```

Destination keys must be unique, stable identifiers of 1–128 letters, digits,
underscores or hyphens, starting with a letter or digit. Keep keys stable when
changing routing. Each destination requires a label, team ID, and channel ID.
Comment thread
KristiDodaj marked this conversation as resolved.
Approve only standard channels in the configured tenant. Duplicate keys or
channels and unknown configuration fields are rejected. An empty mapping leaves
Teams unconfigured.

For `team_id`, use the Teams thread ID, for example `19:example@thread.tacv2`,
not the Microsoft 365 group GUID shown as the Team ID in Graph or the Teams
admin center. In Teams, open the team's **More options (…) > Get link to team**.
Copy the value between `/l/team/` and `/conversations` and URL-decode it
(`%3A` becomes `:` and `%40` becomes `@`). Do not use the link's `groupId` parameter.
For `channel_id`, use **Get link to channel** and URL-decode the value immediately
after `/l/channel/`. See Microsoft's [Teams link formats](https://learn.microsoft.com/en-us/microsoftteams/platform/concepts/build-and-test/deep-link-teams).
The installation callback matches `team_id` exactly. A group GUID will cause
installation events to return 403 and leave the destination without an active
installation.

Merge these feature flags into your existing flags. Restart the web server and
workers after changing configuration or rotating a secret. Apply the migration
before accepting installation events. The scheduler, broker, and worker setup
from the earlier Alerts and Reports instructions still applies; the Teams SDK
does not run another web server. Attachment-free Teams alerts do not need a
browser or screenshot worker.

### Register the Microsoft app

Use a Microsoft Entra application restricted to your organization and an Azure
Bot registration with the same application ID and tenant. Create a client secret
and put its **value** in `TEAMS_CLIENT_SECRET`. Enable the Microsoft Teams channel
on the bot registration. Set its messaging endpoint to
`https://superset.example/api/v1/teams/events/`, using your public HTTPS host and
any deployment URL prefix. See Microsoft's
[bot registration guidance](https://learn.microsoft.com/en-us/azure/bot-service/bot-service-quickstart-registration?view=azure-bot-service-4.0).

Package the notification-only app for your organization's Teams app catalog.
The bot must support team scope only and have `isNotificationOnly` enabled;
see the [manifest reference](https://learn.microsoft.com/en-us/microsoftteams/platform/resources/schema/manifest-schema#bots).
This example uses manifest version 1.22, which supports the required fields:

```json
{
"$schema": "https://developer.microsoft.com/json-schemas/teams/v1.22/MicrosoftTeams.schema.json",
"manifestVersion": "1.22",
"version": "1.0.0",
"id": "cccccccc-cccc-4ccc-8ccc-cccccccccccc",
"developer": {
"name": "Your organization",
"websiteUrl": "https://superset.example/",
"privacyUrl": "https://superset.example/privacy",
"termsOfUseUrl": "https://superset.example/terms"
},
"name": { "short": "Superset Alerts" },
"description": {
"short": "Superset alert notifications",
"full": "Receive configured Superset alerts in approved standard Teams channels."
},
"icons": { "outline": "outline.png", "color": "color.png" },
"accentColor": "#20A7C9",
"bots": [
{
"botId": "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
"scopes": ["team"],
"isNotificationOnly": true,
"supportsFiles": false
}
]
}
```

Replace the package `id` with a new UUID and `botId` with your configured application
ID. Replace the example URLs with your organization's published pages. Include
`manifest.json`, a 32×32 transparent outline icon, and a 192×192 color icon at the
root of the app ZIP. Have your Teams administrator approve the package and allow
installation in the selected teams. Install it in each approved team after the
callback is reachable. Configure each standard channel's actual team and channel
identifiers on the server; display labels and Microsoft 365 group names are not
routing IDs. Do not add personal or group-chat scopes, file support, or
private/shared channel capabilities.

Installing the app updates the installation record for its team, but does not
approve any destinations. Configure the allowlist separately. Uninstalling marks
the installation inactive; reinstalling refreshes its verified service URL and
reactivates the same record. If an initial installation happened before callback
routing was ready, remove and reinstall the app to deliver a fresh event.
Lifecycle events require a timezone-qualified source timestamp. Older events are
acknowledged without changing installation state, and removal wins timestamp ties.
Removal records are retained even when the installation event has not arrived.

### Route the callback

Your ingress must forward `POST /api/v1/teams/events/` (including the trailing
slash) to Flask, preserving the Authorization header and JSON body. The callback
accepts bodies up to 256 KiB, or a smaller deployment body limit, and returns 413
for oversized requests before JSON parsing, SDK authentication, or database access.
Do not send this path through a browser login redirect or an ingress SSO challenge. Keep TLS
termination and the configured public URL consistent. Other Superset routes keep
their normal authentication and CSRF requirements; do not exempt the whole API.
The callback validates Microsoft's connector token, application, tenant, approved
team, and signed service URL before changing installations.

Allow outbound connectivity from the web server for connector signing keys and
from workers for Entra token acquisition and the verified Bot Framework service
URL. Use the public Microsoft cloud endpoints supported by this adapter. Changing
the bot identity requires matching configuration on both web and worker processes
and a new verified installation for that identity.

### Create a Teams alert

1. Install the app in each approved team once the callback is reachable.
2. In **Alerts & reports**, create an alert and disable attachments.
3. Choose **Teams** as the notification method and select an approved destination
by its display label. Add another Teams notification method to notify another
channel.
4. Save the alert and check its execution logs after delivery.

Each alert creates a separate channel post containing its name, optional
description, and an **Open in Superset** link when link inclusion is enabled.
Execution errors show a generic failure message rather than SQL diagnostics.

You can save an alert before installing the bot, but delivery requires an active
installation. Saved destinations that are no longer configured remain visible
as unavailable. Restore the destination's stable key or select a replacement.
Credentials are managed in deployment configuration, not in the alert editor.

### Configure a recipient through the API

Use recipient type `Teams`, `recipient_config_json` containing only
`{"target": "ops"}`, and `report_format="NONE"`. Replace `ops` with a configured
destination key. Create, update, and delivery validation enforce the same
restrictions, including partial updates to saved alerts.

The protected destination API returns keys, labels, and installation availability,
without credentials or routing details.

### Troubleshooting

| Symptom | Check |
| --- | --- |
| Teams is absent from the editor | Install the extra, enable both feature flags, provide valid configuration, choose an alert, and disable attachments. |
| Destination says “Bot installation required” | Check the bot is installed in the configured team and the callback received its installation event. Saving is allowed before installation; delivery requires an active record. |
| Saved destination says “Unavailable” | Restore the original stable key or choose an approved replacement. Configuration changes do not rewrite saved recipients. |
| Callback returns 404 | Check `ALERT_REPORT_TEAMS`, ingress path/prefix, and that the request reached this deployment. |
| Callback returns 503 | Check the optional SDK is installed and the deployment configuration is nonempty and passes validation. |
| Callback returns 401 or 403 | Check connector token authentication, bot application/tenant IDs, approved team, signed service URL, and that ingress preserved Authorization. Browser cookies cannot authenticate this route. |
| Delivery fails authorization | Check installation status, secret value/expiry, bot registration, and channel access. A second 401 after one refresh or a 403 ends delivery. |
| Save fails after a configuration change | Restore the integration/key or explicitly remove/replace Teams recipients. Partial updates validate the merged schedule, including format and schedule type. |
| Delivery times out or exhausts retries | Check Entra and Bot Framework connectivity and worker execution context. The 60-second retry budget includes token acquisition and cleanup and is capped by the execution deadline. Use a supported prefork worker with finite `working_timeout` and `ALERT_REPORTS_WORKING_TIME_OUT_KILL=True` for the outer task limit. |
| A duplicate channel post appears | A response can be lost after Teams accepted a post. Bounded retries may create another root post; delivery is not exactly-once. |

Use the existing alert execution logs to inspect success and failure. Do not copy
access tokens, credentials, raw SDK responses, or SQL diagnostics into channel
messages or issue reports. Rotate credentials through deployment configuration;
there is no browser credential-management page.

### Delivery timeouts and retries

Delivery allows up to four attempts within a cooperative 60-second retry budget,
limited by the remaining report execution deadline. Requests have socket timeouts
of at most 10 seconds. Throttling honors Microsoft's retry delay, and expired
authentication can refresh once. A lost response can cause duplicate posts.

Socket timeouts do not bound DNS resolution or an entire streaming response. For
an outer time limit, use a Celery prefork worker with
[time-limit support](https://docs.celeryq.dev/en/stable/userguide/workers.html#time-limits),
keep `ALERT_REPORTS_WORKING_TIME_OUT_KILL=True`, and set a finite schedule
`working_timeout`. These limits cover the entire alert task, including query
execution and delivery. Hard task termination cannot guarantee resource cleanup.

:::resources

- [Tutorial: Automated Alerts and Reporting via Slack/Email in Superset](https://dev.to/ngtduc693/apache-superset-topic-5-automated-alerts-and-reporting-via-slackemail-in-superset-2gbe)
Expand Down
23 changes: 23 additions & 0 deletions docs/developer_docs/contributing/howtos.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,29 @@ npm run playwright:debug tests/auth/login.spec.ts
npm run playwright:report
```

### Testing Microsoft Teams notifications

For deployment setup, see the [Microsoft Teams configuration guide](/admin-docs/configuration/alerts-reports#microsoft-teams-configuration).

Backend tests under `tests/unit_tests/integrations/teams/` exercise the real SDK
with fake HTTP transports. Install the `teams` extra to run authentication and
transport tests rather than skipping them. Notification contract tests at
`tests/unit_tests/reports/notifications/teams_tests.py` run without the SDK.

The browser regression at `playwright/tests/reports/teams-alerts.spec.ts` requires a
running Superset instance, an admin test user, Reports and Teams enabled, at least
one configured destination, and a selectable test database. An unconfigured
integration skips this optional browser test. Use an isolated test deployment
with placeholder UUIDs and a dummy secret; no Microsoft app or installation is
needed for this test. It mocks destination availability, creates only inactive
alerts, blocks execution requests, verifies saved recipient keys, and deletes its
schedule afterward. It never clicks **Trigger now**. Run it from
`superset-frontend`:

```bash
npx playwright test playwright/tests/reports/teams-alerts.spec.ts --project=chromium
```

### Debugging Server App

For debugging the Flask backend:
Expand Down
6 changes: 6 additions & 0 deletions docs/static/feature-flags.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@
"lifecycle": "development",
"description": "Enables experimental tabs UI for Alerts and Reports"
},
{
"name": "ALERT_REPORT_TEAMS",
"default": false,
"lifecycle": "development",
"description": "Enable notification-only Microsoft Teams alerts to approved standard channels."
},
{
"name": "CHART_PLUGINS_EXPERIMENTAL",
"default": false,
Expand Down
56 changes: 55 additions & 1 deletion docs/static/resources/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -9448,7 +9448,8 @@
"Email",
"Slack",
"SlackV2",
"Webhook"
"Webhook",
"Teams"
],
"type": "string"
}
Expand Down Expand Up @@ -29206,6 +29207,59 @@
]
}
},
"/api/v1/report/teams_destinations/": {
"get": {
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"properties": {
"result": {
"items": {
"properties": {
"available": {
"type": "boolean"
},
"key": {
"type": "string"
},
"label": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": "Approved destinations and installation availability"
},
"401": {
"$ref": "#/components/responses/401"
},
"403": {
"$ref": "#/components/responses/403"
}
},
"security": [
{
"jwt": []
},
{
"api_key": []
}
],
"summary": "List approved Teams alert destinations",
"tags": [
"Report Schedules"
]
}
},
"/api/v1/report/{pk}": {
"delete": {
"parameters": [
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ pinot = ["pinotdb[sqlalchemy]>=9.2.2, <10.0.0"]
playwright = ["playwright>=1.64.0, <2"]
postgres = ["psycopg2-binary==2.9.13"]
presto = ["pyhive[presto]>=0.7.0"]
teams = ["microsoft-teams-apps==2.1.1"]
trino = ["trino>=0.340.0"]
prophet = [
"prophet>=1.5.0, <2",
Expand Down
2 changes: 1 addition & 1 deletion requirements/development.in
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
# specific language governing permissions and limitations
# under the License.
#
-e .[development,bigquery,clickhouse,cockroachdb,crate,databend,druid,duckdb,elasticsearch,fastmcp,firebird,gevent,gsheets,monetdb,mongodb,mssql,mysql,oracle,postgres,presto,prophet,risingwave,starrocks,trino,thumbnails,ydb]
-e .[development,bigquery,clickhouse,cockroachdb,crate,databend,druid,duckdb,elasticsearch,fastmcp,firebird,gevent,gsheets,monetdb,mongodb,mssql,mysql,oracle,postgres,presto,prophet,risingwave,starrocks,trino,teams,thumbnails,ydb]
-e ./superset-extensions-cli[test]
# testcontainers-backed db_engine_specs tests (tests/testcontainers/) --
# see .github/workflows/testcontainers.yml
Expand Down
Loading