feat(notifications): add Mattermost integration for notifications#3751
feat(notifications): add Mattermost integration for notifications#3751hosseinmoghaddam wants to merge 1 commit intoDokploy:canaryfrom
Conversation
- Introduced support for Mattermost notifications, including the ability to create, update, and test connections for Mattermost notifications. - Updated the notification schema to include Mattermost as a notification type. - Added Mattermost icon and UI components for handling Mattermost notifications in the dashboard. - Implemented backend logic for creating and updating Mattermost notifications, along with necessary database schema changes. - Enhanced existing notification functionalities to support Mattermost notifications across various events (e.g., build success, failure, database backups).
|
|
||
| export const apiUpdateMattermost = apiCreateMattermost.partial().extend({ | ||
| notificationId: z.string().min(1), | ||
| mattermostId: z.string(), |
There was a problem hiding this comment.
inconsistent validation - teamsId uses .min(1) but mattermostId doesn't
| mattermostId: z.string(), | |
| mattermostId: z.string().min(1), |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
There was a problem hiding this comment.
Pull request overview
This pull request adds comprehensive Mattermost integration to the Dokploy notification system, enabling users to receive notifications about build events, backups, server alerts, and system events through Mattermost webhooks.
Changes:
- Added Mattermost as a new notification type with database schema, backend services, and API endpoints
- Integrated Mattermost notifications across all notification event types (builds, backups, server thresholds, system events)
- Updated the dashboard UI to include Mattermost configuration forms and icon display
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 13 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/server/src/db/schema/notification.ts | Added mattermost table schema, notification type enum value, foreign key relationship, and Zod validation schemas for create/update/test operations |
| packages/server/src/services/notification.ts | Implemented createMattermostNotification and updateMattermostNotification service functions with transaction handling |
| packages/server/src/utils/notifications/utils.ts | Added sendMattermostNotification utility function for sending webhook-based notifications with attachment formatting |
| packages/server/src/utils/notifications/volume-backup.ts | Integrated Mattermost notifications for volume backup success/failure events |
| packages/server/src/utils/notifications/server-threshold.ts | Added Mattermost notifications for server resource threshold alerts |
| packages/server/src/utils/notifications/dokploy-restart.ts | Integrated Mattermost notifications for Dokploy server restart events |
| packages/server/src/utils/notifications/docker-cleanup.ts | Added Mattermost notifications for Docker cleanup operations |
| packages/server/src/utils/notifications/database-backup.ts | Integrated Mattermost notifications for database backup success/failure events |
| packages/server/src/utils/notifications/build-success.ts | Added Mattermost notifications for successful build deployments with action buttons |
| packages/server/src/utils/notifications/build-error.ts | Integrated Mattermost notifications for build failures with truncated error messages |
| apps/dokploy/server/api/routers/notification.ts | Added three new tRPC endpoints: createMattermost, updateMattermost, and testMattermostConnection |
| apps/dokploy/components/icons/notification-icons.tsx | Added MattermostIcon SVG component for UI display |
| apps/dokploy/components/dashboard/settings/notifications/show-notifications.tsx | Updated notification list display to show Mattermost icon for Mattermost notification types |
| apps/dokploy/components/dashboard/settings/notifications/handle-notifications.tsx | Added Mattermost form fields, validation schema, mutation handlers, and test connection logic |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const { channel } = mattermost; | ||
| await sendMattermostNotification(mattermost, { | ||
| channel: channel, |
There was a problem hiding this comment.
The channel field extraction and inclusion in the payload is redundant. The sendMattermostNotification function already handles the channel field from the connection object if it exists (line 160-162 in utils.ts). Passing channel separately in the message object and then spreading the connection will result in the channel being included twice in the payload, which is unnecessary.
| const { channel } = mattermost; | |
| await sendMattermostNotification(mattermost, { | |
| channel: channel, | |
| await sendMattermostNotification(mattermost, { |
| const { channel } = mattermost; | ||
| await sendMattermostNotification(mattermost, { | ||
| channel: channel, |
There was a problem hiding this comment.
The channel field extraction and inclusion in the payload is redundant. The sendMattermostNotification function already handles the channel field from the connection object if it exists (line 160-162 in utils.ts). Passing channel separately in the message object and then spreading the connection will result in the channel being included twice in the payload, which is unnecessary.
| const { channel } = mattermost; | |
| await sendMattermostNotification(mattermost, { | |
| channel: channel, | |
| await sendMattermostNotification(mattermost, { |
| const { channel } = mattermost; | ||
| await sendMattermostNotification(mattermost, { | ||
| channel: channel, |
There was a problem hiding this comment.
The channel field extraction and inclusion in the payload is redundant. The sendMattermostNotification function already handles the channel field from the connection object if it exists (line 160-162 in utils.ts). Passing channel separately in the message object and then spreading the connection will result in the channel being included twice in the payload, which is unnecessary.
| const { channel } = mattermost; | |
| await sendMattermostNotification(mattermost, { | |
| channel: channel, | |
| await sendMattermostNotification(mattermost, { |
| const { channel } = mattermost; | ||
| await sendMattermostNotification(mattermost, { | ||
| channel: channel, |
There was a problem hiding this comment.
The channel field extraction and inclusion in the payload is redundant. The sendMattermostNotification function already handles the channel field from the connection object if it exists (line 160-162 in utils.ts). Passing channel separately in the message object and then spreading the connection will result in the channel being included twice in the payload, which is unnecessary.
| const { channel } = mattermost; | |
| await sendMattermostNotification(mattermost, { | |
| channel: channel, | |
| await sendMattermostNotification(mattermost, { |
| ], | ||
| actions: [ | ||
| { | ||
| type: "button", | ||
| name: "build_details", | ||
| text: "View Build Details", | ||
| integration: { | ||
| url: buildLink, | ||
| }, |
There was a problem hiding this comment.
The action button structure may not work correctly with Mattermost's incoming webhooks. According to Mattermost's documentation, the actions field with 'integration' property is used for interactive message buttons that require a Mattermost app with proper OAuth configuration. For simple incoming webhooks, either remove the actions array or use a simpler approach by including the link in the message text or pretext. The current implementation may fail silently or be ignored by Mattermost.
| ], | |
| actions: [ | |
| { | |
| type: "button", | |
| name: "build_details", | |
| text: "View Build Details", | |
| integration: { | |
| url: buildLink, | |
| }, | |
| { | |
| title: "Build Details", | |
| value: buildLink, | |
| short: false, |
| const { channel } = mattermost; | ||
| await sendMattermostNotification(mattermost, { | ||
| channel: channel, |
There was a problem hiding this comment.
The channel field extraction and inclusion in the payload is redundant. The sendMattermostNotification function already handles the channel field from the connection object if it exists (line 160-162 in utils.ts). Passing channel separately in the message object and then spreading the connection will result in the channel being included twice in the payload, which is unnecessary.
| const { channel } = mattermost; | |
| await sendMattermostNotification(mattermost, { | |
| channel: channel, | |
| await sendMattermostNotification(mattermost, { |
| <svg | ||
| viewBox="0 0 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid" | ||
| className={cn("size-8", className)} |
There was a problem hiding this comment.
The SVG element has inconsistent formatting with extra whitespace. The opening tag spans two lines (37-38) with improper indentation. This should be reformatted to follow standard JSX/TSX formatting conventions for better readability and consistency with the rest of the codebase.
| <svg | |
| viewBox="0 0 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid" | |
| className={cn("size-8", className)} | |
| <svg | |
| viewBox="0 0 256 256" | |
| version="1.1" | |
| xmlns="http://www.w3.org/2000/svg" | |
| preserveAspectRatio="xMidYMid" | |
| className={cn("size-8", className)} |
| </div> | ||
| )} | ||
| {notification.notificationType === "mattermost" && ( | ||
| <div className="flex items-center justify-center rounded-lg"> |
There was a problem hiding this comment.
There are two consecutive spaces in the className attribute. This should be a single space for consistency and to avoid potential styling issues.
| export const createMattermostNotification = async ( | ||
| input: typeof apiCreateMattermost._type, | ||
| organizationId: string, | ||
| ) => { | ||
| await db.transaction(async (tx) => { | ||
| const newMattermost = await tx | ||
| .insert(mattermost) | ||
| .values({ | ||
| webhookUrl: input.webhookUrl, | ||
| channel: input.channel, | ||
| }) | ||
| .returning() | ||
| .then((value) => value[0]); | ||
|
|
||
| if (!newMattermost) { | ||
| throw new TRPCError({ | ||
| code: "BAD_REQUEST", | ||
| message: "Error input: Inserting mattermost", | ||
| }); | ||
| } | ||
|
|
||
| const newDestination = await tx | ||
| .insert(notifications) | ||
| .values({ | ||
| mattermostId: newMattermost.mattermostId, | ||
| name: input.name, | ||
| appDeploy: input.appDeploy, | ||
| appBuildError: input.appBuildError, | ||
| databaseBackup: input.databaseBackup, | ||
| volumeBackup: input.volumeBackup, | ||
| dokployRestart: input.dokployRestart, | ||
| dockerCleanup: input.dockerCleanup, | ||
| notificationType: "mattermost", | ||
| organizationId: organizationId, | ||
| serverThreshold: input.serverThreshold, | ||
| }) | ||
| .returning() | ||
| .then((value) => value[0]); | ||
|
|
||
| if (!newDestination) { | ||
| throw new TRPCError({ | ||
| code: "BAD_REQUEST", | ||
| message: "Error input: Inserting notification", | ||
| }); | ||
| } | ||
|
|
||
| return newDestination; | ||
| }); | ||
| }; |
There was a problem hiding this comment.
The createMattermostNotification function doesn't return the result from the transaction. The transaction's return value is not being returned from the outer function, which will cause the function to return undefined. Add a return statement before db.transaction to match the pattern used by other notification creation functions.
| export const updateMattermostNotification = async ( | ||
| input: typeof apiUpdateMattermost._type, | ||
| ) => { | ||
| await db.transaction(async (tx) => { | ||
| const newDestination = await tx | ||
| .update(notifications) | ||
| .set({ | ||
| name: input.name, | ||
| appDeploy: input.appDeploy, | ||
| appBuildError: input.appBuildError, | ||
| databaseBackup: input.databaseBackup, | ||
| volumeBackup: input.volumeBackup, | ||
| dokployRestart: input.dokployRestart, | ||
| dockerCleanup: input.dockerCleanup, | ||
| organizationId: input.organizationId, | ||
| serverThreshold: input.serverThreshold, | ||
| }) | ||
| .where(eq(notifications.notificationId, input.notificationId)) | ||
| .returning() | ||
| .then((value) => value[0]); | ||
|
|
||
| if (!newDestination) { | ||
| throw new TRPCError({ | ||
| code: "BAD_REQUEST", | ||
| message: "Error Updating notification", | ||
| }); | ||
| } | ||
|
|
||
| await tx | ||
| .update(mattermost) | ||
| .set({ | ||
| webhookUrl: input.webhookUrl, | ||
| channel: input.channel, | ||
| }) | ||
| .where(eq(mattermost.mattermostId, input.mattermostId)) | ||
| .returning() | ||
| .then((value) => value[0]); | ||
|
|
||
| return newDestination; | ||
| }); | ||
| }; |
There was a problem hiding this comment.
The updateMattermostNotification function doesn't return the result from the transaction. The transaction's return value is not being returned from the outer function, which will cause the function to return undefined. Add a return statement before db.transaction to match the pattern used by other notification update functions.
What is this PR about?
Add Mattermost integration for notifications
Checklist
Before submitting this PR, please make sure that:
canarybranch.Screenshots (if applicable)
Greptile Summary
Adds Mattermost as a new notification provider with comprehensive integration across all notification types. Implementation follows existing patterns for other notification providers (Slack, Teams, Discord, etc.) and includes database schema, API endpoints, UI components, and notification handlers for all event types (build success/error, database backup, volume backup, Docker cleanup, Dokploy restart, server threshold).
mattermosttable withwebhookUrlandchannelfieldscreateMattermost,updateMattermost,testMattermostConnection)Confidence Score: 4/5
packages/server/src/db/schema/notification.tsis a style issue rather than a functional problemLast reviewed commit: 4c61819
(2/5) Greptile learns from your feedback when you react with thumbs up/down!