diff --git a/extensions/task_webhook_slack/CANVAS_MANIFEST.json b/extensions/task_webhook_slack/CANVAS_MANIFEST.json new file mode 100644 index 00000000..a9b0ab47 --- /dev/null +++ b/extensions/task_webhook_slack/CANVAS_MANIFEST.json @@ -0,0 +1,29 @@ +{ + "sdk_version": "0.1.4", + "plugin_version": "0.0.1", + "name": "task_webhook_notification", + "description": "Listens to a task created/updated and sends a payload to a webhook", + "components": { + "protocols": [ + { + "class": "task_webhook_notification.protocols.task_webhook_notification:TaskWebhookNotificationProtocol", + "description": "Listens to a task created/updated and sends a payload to a webhook", + "data_access": { + "event": "", + "read": [], + "write": [] + } + } + ], + "commands": [], + "content": [], + "effects": [], + "views": [] + }, + "secrets": ["WEBHOOK_NOTIFICATION_URL", "AUTH_TOKEN"], + "tags": {}, + "references": [], + "license": "", + "diagram": false, + "readme": "./README.md" +} diff --git a/extensions/task_webhook_slack/README.md b/extensions/task_webhook_slack/README.md new file mode 100644 index 00000000..2f801e5a --- /dev/null +++ b/extensions/task_webhook_slack/README.md @@ -0,0 +1,97 @@ +# Task Webhook Slack Integration + +image + + +## Description +Make integrations more efficient with webhooks. Webhooks are used to notify an application when an event occurs in another system, acting as a real-time communication channel and "push" information as soon as an event happens. + +When a task in Canvas is created or updated, send a webhook playload to an endpoint of your choice. + +The Canvas payload includes the following: + +- Task: ID, event (updated, created), title, due date +- Patient: ID, first name, last name, date of birth, sex at birth +- Assignee: ID, first name, last name, team (if applicable) +- Task creator, ID, first name, last name +- Customize the event trigger and payload to fit your needs. + +## Example +An example implemenation integrates Canvas task management with Slack notifications. When a task in Canvas is created or updated, it automatically sends a formatted message to a Slack channel via webhook, keeping your team informed in real-time. + + +### How It Works + +When a task event occurs in Canvas, this extension: +1. Captures the task details and associated patient/assignee information +2. Formats the data into a Slack-friendly payload +3. Sends the notification to your configured Slack webhook URL + +### Slack Setup + +#### 1. Create a Slack App and Webhook +Reference: https://docs.slack.dev/messaging/sending-messages-using-incoming-webhooks/ + +1. Go to [api.slack.com/apps](https://api.slack.com/apps) and create a new app +2. Choose "From scratch" and give it a name like "Canvas Task Notifications" +3. Select your workspace +4. Go to "Incoming Webhooks" and activate them +5. Click "Add New Webhook to Workspace" +6. Choose the channel where you want task notifications +7. Copy the webhook URL (it will look like: `https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX`) + +#### 2. Configure Canvas Extension + +Set these plugin secrets in your Canvas environment: + +- `WEBHOOK_NOTIFICATION_URL`: Your Slack webhook URL +- `AUTH_TOKEN`: Leave empty (Slack webhooks don't require bearer tokens) + +### Payload Structure + +The webhook sends the following data to Slack: + +```json +{ + "task_id": "12345", + "event": "created", // or "updated" + "title": "Follow up with patient", + "due_date": "2024-01-15T10:00:00Z", + "patient": { + "id": "67890", + "first_name": "John", + "last_name": "Doe", + "birth_date": "1980-05-15", + "sex_at_birth": "Male" + }, + "creator": { + "id": "11111", + "first_name": "Dr. Jane", + "last_name": "Smith" + }, + "assignee": { + "staff": { + "id": "22222", + "first_name": "Nurse", + "last_name": "Johnson" + }, + "team": null + } +} +``` + +### Example Slack Message + +When a task is created, you'll see a message like: + +**📋 New Task Created** + +- **Title:** Follow up with patient +- **Due:** January 15, 2024 at 10:00 AM +- **Patient:** John Doe (DOB: 05/15/1980, Male) +- **Created by:** Dr. Jane Smith +- **Assigned to:** Nurse Johnson + +## Customization + +You can customize the webhook payload format by modifying the `task_webhook_notification.py` file to match your specific Slack integration needs. diff --git a/extensions/task_webhook_slack/protocols/__init__.py b/extensions/task_webhook_slack/protocols/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/extensions/task_webhook_slack/protocols/task_webhook_notification.py b/extensions/task_webhook_slack/protocols/task_webhook_notification.py new file mode 100644 index 00000000..0f8081da --- /dev/null +++ b/extensions/task_webhook_slack/protocols/task_webhook_notification.py @@ -0,0 +1,77 @@ +from canvas_sdk.events import EventType +from canvas_sdk.protocols import BaseProtocol +from canvas_sdk.utils import Http +from canvas_sdk.v1.data.task import Task + +from logger import log + + +class TaskWebhookNotificationProtocol(BaseProtocol): + """ + When a task is created or updated, hit a webhook + """ + + RESPONDS_TO = [ + EventType.Name(EventType.TASK_CREATED), + EventType.Name(EventType.TASK_UPDATED), + ] + + def compute(self): + """ + Notify our server of tasks as they are created. + """ + url = self.secrets["WEBHOOK_NOTIFICATION_URL"] + auth_token = self.secrets["AUTH_TOKEN"] + headers = {"Authorization": f"Bearer {auth_token}"} + + verb = 'created' if self.event.type == EventType.TASK_CREATED else 'updated' + + task = Task.objects.select_related("patient").select_related("creator").get(id=self.target) + + patient_details = None + if task.patient: + patient_details = { + "id": task.patient.id, + "first_name": task.patient.first_name, + "last_name": task.patient.last_name, + "birth_date": task.patient.birth_date.isoformat(), + "sex_at_birth": task.patient.get_sex_at_birth_display(), + } + + staff_assignee_details = None + if task.assignee: + staff_assignee_details = { + "id": task.assignee.id, + "first_name": task.assignee.first_name, + "last_name": task.assignee.last_name, + } + + payload = { + "task_id": self.target, + "event": verb, + "title": task.title, + "due_date": task.due.isoformat(), + "patient": patient_details, + "creator": { + "id": task.creator.id, + "first_name": task.creator.first_name, + "last_name": task.creator.last_name, + }, + "assignee": { + "staff": staff_assignee_details, + # populate when team is available through the data module; + "team": None + } + } + + http = Http() + response = http.post(url, json=payload, headers=headers) + + event_name = EventType.Name(self.event.type) + + if response.ok: + log.info(f"Successfully notified API of {event_name} for task ID {task.id}") + else: + log.info(f"Notification of {event_name} for task ID {task.id} unsuccessful. =[") + + return []