From 036f40613c81480ec58ac6c94a4fa29d5dba243e Mon Sep 17 00:00:00 2001 From: Tal Date: Tue, 3 Dec 2024 13:07:57 +0200 Subject: [PATCH] feat: microsoft teams provider add support for adaptivecards (#2736) --- .../documentation/teams-provider.mdx | 170 ++++++++++++++++-- .../workflows/keep-teams-adaptive-cards.yaml | 23 +++ keep-ui/package-lock.json | 8 +- keep-ui/package.json | 2 +- .../teams_provider/teams_provider.py | 69 +++++-- pyproject.toml | 2 +- 6 files changed, 237 insertions(+), 37 deletions(-) create mode 100644 examples/workflows/keep-teams-adaptive-cards.yaml diff --git a/docs/providers/documentation/teams-provider.mdx b/docs/providers/documentation/teams-provider.mdx index 673b9dc07..345fc3a40 100644 --- a/docs/providers/documentation/teams-provider.mdx +++ b/docs/providers/documentation/teams-provider.mdx @@ -11,14 +11,19 @@ The `notify` function in the `TeamsProvider` class takes the following parameter ```python kwargs (dict): message (str): The message to send. *Required* - typeCard (str): The card type. (MessageCard is default) - themeColor (str): Hexadecimal color. - sections (array): Array of custom informations + typeCard (str): The card type. Can be "MessageCard" (legacy) or "message" (for Adaptive Cards). Default is "message" + themeColor (str): Hexadecimal color (only used with MessageCard type) + sections (array/str): For MessageCard: Array of custom information sections + For Adaptive Cards: Array of card elements following the Adaptive Card schema + Can be provided as a JSON string or array + attachments (array/str): Custom attachments array for Adaptive Cards (overrides default attachment structure) + Can be provided as a JSON string or array + schema (str): Schema URL for Adaptive Cards. Default is "http://adaptivecards.io/schemas/adaptive-card.json" ``` ## Outputs -_No information yet, feel free to contribute it using the "Edit this page" link the bottom of the page_ +The response as JSON, which is the response from the Microsoft Teams API. ## Authentication Parameters @@ -28,26 +33,159 @@ The TeamsProviderAuthConfig class takes the following parameters: ## Connecting with the Provider -1. Open the Microsoft Teams application or website and select the team or channel where you want to add the webhook. + + + 1. In the New Teams client, select Teams and navigate to the channel where + you want to add an Incoming Webhook. 2. Select More options ••• on the right + side of the channel name. 3. Select Manage Channel + + + + + For members who aren't admins of the channel, the Manage channel option is + available under the Open channel details option in the upper-right corner + of a channel. + + 4. Select Edit + + + + 5. Search for Incoming Webhook and select Add. + + + + 6. Select Add + + + + 7. Provide a name for the webhook and upload an image if necessary. 8. Select + Create. + + + + 9. Copy and save the unique webhook URL present in the dialog. The URL maps to + the channel and you can use it to send information to Teams. 10. Select Done. + The webhook is now available in the Teams channel. + + + + + + 1. In the Classic Teams client, select Teams and navigate to the channel + where you want to add an Incoming Webhook. 2. Select More options ••• from + the upper-right corner. 3. Select Connectors from the dropdown menu. + + + + 4. Search for Incoming Webhook and select Add. + + + + 5. Select Add. + + + + 6. Provide a name for the webhook and upload an image if necessary. 7. + Select Create. + + + + 8. Copy and save the unique webhook URL present in the dialog. The URL maps + to the channel and you can use it to send information to Teams. 9. Select + Done. + + + + + -2. Click on the three-dot icon next to the team or channel name and select "Connectors" from the dropdown menu. - -3. Search for "Incoming Webhook" and click on the "Add" button. - -4. Give your webhook a name and an optional icon, then click on the "Create" button. +## Notes -5. Copy the webhook URL that is generated and save it for later use. +When using Adaptive Cards (`typeCard="message"`): + +- The `sections` parameter should follow the [Adaptive Cards schema](https://adaptivecards.io/explorer/) +- `themeColor` is ignored for Adaptive Cards +- If no sections are provided, the message will be displayed as a simple text block +- Both `sections` and `attachments` can be provided as JSON strings or arrays + +### Workflow Example + +You can also find this example in our [examples](https://github.com/keephq/keep/tree/main/examples/workflows/keep-teams-adaptive-cards.yaml) folder in the Keep GitHub repository. + +```yaml +id: 6bc7c72e-ab3d-4913-84dd-08b9323195ae +description: Teams Adaptive Cards Example +disabled: false +triggers: + - type: manual + - filters: + - key: source + value: r".*" + type: alert +consts: {} +name: Keep Teams Adaptive Cards +owners: [] +services: [] +steps: [] +actions: + - name: teams-action + provider: + config: "{{ providers.teams }}" + type: teams + with: + message: "" + sections: '[{"type": "TextBlock", "text": "{{alert.name}}"}, {"type": "TextBlock", "text": "Tal from Keep"}]' + typeCard: message +``` -6. Select the options that you want to configure for your webhook, such as the default name and avatar that will be used when posting messages. + + The sections parameter is a JSON string that follows the Adaptive Cards schema, but can also be an object. + If it's a string, it will be parsed as a JSON string. + -7. Click on the "Save" button to save your webhook settings. +### Using Sections -You can now use the webhook URL to send messages to the selected channel or team in Microsoft Teams. +```python +provider.notify( + message="Fallback text", + typeCard="message", + sections=[ + { + "type": "TextBlock", + "text": "Hello from Adaptive Card!" + }, + { + "type": "Image", + "url": "https://example.com/image.jpg" + } + ] +) +``` -## Notes +### Using Custom Attachments -_No information yet, feel free to contribute it using the "Edit this page" link the bottom of the page_ +```python +provider.notify( + typeCard="message", + attachments=[{ + "contentType": "application/vnd.microsoft.card.adaptive", + "content": { + "type": "AdaptiveCard", + "version": "1.2", + "body": [ + { + "type": "TextBlock", + "text": "Custom Attachment Example" + } + ] + } + }] +) +``` ## Useful Links - https://learn.microsoft.com/pt-br/microsoftteams/platform/webhooks-and-connectors/how-to/add-incoming-webhook +- https://learn.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/connectors-using +- https://adaptivecards.io/explorer/ +- https://adaptivecards.io/schemas/adaptive-card.json diff --git a/examples/workflows/keep-teams-adaptive-cards.yaml b/examples/workflows/keep-teams-adaptive-cards.yaml new file mode 100644 index 000000000..ad2f64509 --- /dev/null +++ b/examples/workflows/keep-teams-adaptive-cards.yaml @@ -0,0 +1,23 @@ +id: 6bc7c72e-ab3d-4913-84dd-08b9323195ae +description: Teams Adaptive Cards Example +disabled: false +triggers: + - type: manual + - filters: + - key: source + value: r".*" + type: alert +consts: {} +name: Keep Teams Adaptive Cards +owners: [] +services: [] +steps: [] +actions: + - name: teams-action + provider: + config: "{{ providers.teams }}" + type: teams + with: + message: "" + sections: '[{"type": "TextBlock", "text": "{{alert.name}}"}, {"type": "TextBlock", "text": "Tal from Keep"}]' + typeCard: message diff --git a/keep-ui/package-lock.json b/keep-ui/package-lock.json index d72714d7d..0a3fd9436 100644 --- a/keep-ui/package-lock.json +++ b/keep-ui/package-lock.json @@ -63,7 +63,7 @@ "postcss-nested": "^6.0.1", "postcss-selector-parser": "^6.0.12", "postcss-value-parser": "^4.2.0", - "posthog-js": "^1.194.1", + "posthog-js": "^1.194.2", "posthog-node": "^3.1.1", "pusher-js": "^8.3.0", "react": "^18.3.1", @@ -16753,9 +16753,9 @@ } }, "node_modules/posthog-js": { - "version": "1.194.1", - "resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.194.1.tgz", - "integrity": "sha512-d68hmU9DY4iPe3WneBlnglERhimRhXuF7Lx0Au6OTmOL+IFdFUxB3Qf5LaLqJc1QLt3NUolMq1HiXOaIULe3kQ==", + "version": "1.194.2", + "resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.194.2.tgz", + "integrity": "sha512-UVFVvx6iJMEjHo+N/HmPDK4zjkVY8m+G13jTQmvHMtByfyn/fH6JhOz/ph+gtmvXPI03130y1qrwwgPIZ3ty8A==", "dependencies": { "core-js": "^3.38.1", "fflate": "^0.4.8", diff --git a/keep-ui/package.json b/keep-ui/package.json index 58420dd53..84ec9dad5 100644 --- a/keep-ui/package.json +++ b/keep-ui/package.json @@ -64,7 +64,7 @@ "postcss-nested": "^6.0.1", "postcss-selector-parser": "^6.0.12", "postcss-value-parser": "^4.2.0", - "posthog-js": "^1.194.1", + "posthog-js": "^1.194.2", "posthog-node": "^3.1.1", "pusher-js": "^8.3.0", "react": "^18.3.1", diff --git a/keep/providers/teams_provider/teams_provider.py b/keep/providers/teams_provider/teams_provider.py index d7898e9a5..3c709f71a 100644 --- a/keep/providers/teams_provider/teams_provider.py +++ b/keep/providers/teams_provider/teams_provider.py @@ -4,6 +4,7 @@ import dataclasses +import json5 as json import pydantic import requests @@ -51,37 +52,78 @@ def dispose(self): def _notify( self, message="", - typeCard="MessageCard", + typeCard="message", themeColor=None, sections=[], + schema="http://adaptivecards.io/schemas/adaptive-card.json", + attachments=[], **kwargs: dict, ): """ Notify alert message to Teams using the Teams Incoming Webhook API - https://learn.microsoft.com/pt-br/microsoftteams/platform/webhooks-and-connectors/how-to/connectors-using?tabs=cURL Args: - kwargs (dict): The providers with context + message (str): The message to send + typeCard (str): Type of card to send ("MessageCard" or "message" for Adaptive Cards) + themeColor (str): Color theme for MessageCard + sections (list): Sections for MessageCard or Adaptive Card content + attachments (list): Attachments for Adaptive Card + **kwargs (dict): Additional arguments """ self.logger.debug("Notifying alert message to Teams") - webhook_url = self.authentication_config.webhook_url - response = requests.post( - webhook_url, - json={ + if isinstance(sections, str): + try: + sections = json.loads(sections) + except json.JSONDecodeError as e: + self.logger.error(f"Failed to decode sections string to JSON: {e}") + + if attachments and isinstance(attachments, str): + try: + attachments = json.loads(attachments) + except json.JSONDecodeError as e: + self.logger.error(f"Failed to decode attachments string to JSON: {e}") + + if typeCard == "message": + # Adaptive Card format + payload = { + "type": "message", + "attachments": attachments + or [ + { + "contentType": "application/vnd.microsoft.card.adaptive", + "contentUrl": None, + "content": { + "$schema": schema, + "type": "AdaptiveCard", + "version": "1.2", + "body": ( + sections + if sections + else [{"type": "TextBlock", "text": message}] + ), + }, + } + ], + } + else: + # Standard MessageCard format + payload = { "@type": typeCard, "themeColor": themeColor, "text": message, "sections": sections, - }, - ) + } + + response = requests.post(webhook_url, json=payload) if not response.ok: raise ProviderException( f"{self.__class__.__name__} failed to notify alert message to Teams: {response.text}" ) self.logger.debug("Alert message notified to Teams") + return response.json() if __name__ == "__main__": @@ -106,12 +148,9 @@ def _notify( ) provider = TeamsProvider(context_manager, provider_id="teams", config=config) provider.notify( - typeCard="MessageCard", - themeColor="0076D7", - message="Microsoft Teams alert", + typeCard="message", sections=[ - {"name": "Assigned to", "value": "Danilo Vaz"}, - {"name": "Sum", "value": 10}, - {"name": "Count", "value": 100}, + {"type": "TextBlock", "text": "Danilo Vaz"}, + {"type": "TextBlock", "text": "Tal from Keep"}, ], ) diff --git a/pyproject.toml b/pyproject.toml index 29b099c2e..3d6a966d9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "keep" -version = "0.30.7" +version = "0.31.0" description = "Alerting. for developers, by developers." authors = ["Keep Alerting LTD"] packages = [{include = "keep"}]