feat(slack-integration): add support for secret sync error notifications and enhance notification handling

This commit is contained in:
Victor Santos
2025-10-24 18:04:51 -03:00
parent abd4cb6217
commit ecca51b1d6
10 changed files with 367 additions and 121 deletions
@@ -0,0 +1,29 @@
import { Knex } from "knex";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasColumn("project_slack_configs", "isSecretSyncErrorNotificationEnabled"))) {
await knex.schema.alterTable("project_slack_configs", (table) => {
table.boolean("isSecretSyncErrorNotificationEnabled").notNullable().defaultTo(false);
});
}
if (!(await knex.schema.hasColumn("project_slack_configs", "secretSyncErrorChannels"))) {
await knex.schema.alterTable("project_slack_configs", (table) => {
table.text("secretSyncErrorChannels").notNullable().defaultTo("");
});
}
}
export async function down(knex: Knex): Promise<void> {
if (await knex.schema.hasColumn("project_slack_configs", "isSecretSyncErrorNotificationEnabled")) {
await knex.schema.alterTable("project_slack_configs", (table) => {
table.dropColumn("isSecretSyncErrorNotificationEnabled");
});
}
if (await knex.schema.hasColumn("project_slack_configs", "secretSyncErrorChannels")) {
await knex.schema.alterTable("project_slack_configs", (table) => {
table.dropColumn("secretSyncErrorChannels");
});
}
}
@@ -16,7 +16,9 @@ export const ProjectSlackConfigsSchema = z.object({
isSecretRequestNotificationEnabled: z.boolean().default(false), isSecretRequestNotificationEnabled: z.boolean().default(false),
secretRequestChannels: z.string().default(""), secretRequestChannels: z.string().default(""),
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date() updatedAt: z.date(),
isSecretSyncErrorNotificationEnabled: z.boolean().default(false),
secretSyncErrorChannels: z.string().default("")
}); });
export type TProjectSlackConfigs = z.infer<typeof ProjectSlackConfigsSchema>; export type TProjectSlackConfigs = z.infer<typeof ProjectSlackConfigsSchema>;
@@ -0,0 +1,92 @@
import { validateMicrosoftTeamsChannelsSchema } from "@app/services/microsoft-teams/microsoft-teams-fns";
import { TMicrosoftTeamsServiceFactory } from "@app/services/microsoft-teams/microsoft-teams-service";
import {
TProjectMicrosoftTeamsConfigDALFactory,
TProjectMicrosoftTeamsConfigWithIntegrations
} from "@app/services/microsoft-teams/project-microsoft-teams-config-dal";
import { logger } from "../../logger";
import { TNotification, TriggerFeature } from "../types";
const handleMicrosoftTeamsNotification = async ({
microsoftTeamsConfig,
notification,
orgId,
microsoftTeamsService
}: {
microsoftTeamsConfig: TProjectMicrosoftTeamsConfigWithIntegrations;
notification: TNotification;
orgId: string;
microsoftTeamsService: Pick<TMicrosoftTeamsServiceFactory, "sendNotification">;
}): Promise<void> => {
let targetChannels: unknown;
let isEnabled = false;
switch (notification.type) {
case TriggerFeature.ACCESS_REQUEST:
case TriggerFeature.ACCESS_REQUEST_UPDATED:
targetChannels = microsoftTeamsConfig.accessRequestChannels;
isEnabled = microsoftTeamsConfig.isAccessRequestNotificationEnabled;
break;
case TriggerFeature.SECRET_APPROVAL:
targetChannels = microsoftTeamsConfig.secretRequestChannels;
isEnabled = microsoftTeamsConfig.isSecretRequestNotificationEnabled;
break;
default:
return;
}
if (isEnabled && targetChannels) {
const { success, data, error: validationError } = validateMicrosoftTeamsChannelsSchema.safeParse(targetChannels);
if (!success) {
logger.error(validationError, "Invalid Microsoft Teams channel configuration");
return;
}
if (data) {
await microsoftTeamsService
.sendNotification({
notification,
target: data,
tenantId: microsoftTeamsConfig.tenantId,
microsoftTeamsIntegrationId: microsoftTeamsConfig.id,
orgId
})
.catch((error) => {
logger.error(
error,
`Error sending Microsoft Teams notification. Notification type: ${notification.type}, Tenant ID: ${microsoftTeamsConfig.tenantId}, Project ID: ${microsoftTeamsConfig.projectId}`
);
});
}
}
};
export const triggerMicrosoftTeamsNotification = async ({
projectId,
notification,
orgId,
projectMicrosoftTeamsConfigDAL,
microsoftTeamsService
}: {
projectId: string;
notification: TNotification;
orgId: string;
projectMicrosoftTeamsConfigDAL: Pick<TProjectMicrosoftTeamsConfigDALFactory, "getIntegrationDetailsByProject">;
microsoftTeamsService: Pick<TMicrosoftTeamsServiceFactory, "sendNotification">;
}): Promise<void> => {
try {
const config = await projectMicrosoftTeamsConfigDAL.getIntegrationDetailsByProject(projectId);
if (config) {
await handleMicrosoftTeamsNotification({
microsoftTeamsConfig: config,
notification,
orgId,
microsoftTeamsService
});
}
} catch (error) {
logger.error(error, `Error handling Microsoft Teams notification. Project ID: ${projectId}`);
}
};
@@ -0,0 +1,80 @@
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import {
TProjectSlackConfigDALFactory,
TProjectSlackConfigWithIntegrations
} from "@app/services/slack/project-slack-config-dal";
import { sendSlackNotification } from "@app/services/slack/slack-fns";
import { logger } from "../../logger";
import { TNotification, TriggerFeature } from "../types";
const handleSlackNotification = async ({
slackConfig,
notification,
orgId,
kmsService
}: {
slackConfig: TProjectSlackConfigWithIntegrations;
notification: TNotification;
orgId: string;
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
}): Promise<void> => {
let targetChannelIds: string[] = [];
let isEnabled = false;
switch (notification.type) {
case TriggerFeature.ACCESS_REQUEST:
case TriggerFeature.ACCESS_REQUEST_UPDATED:
targetChannelIds = slackConfig.accessRequestChannels?.split(", ") || [];
isEnabled = slackConfig.isAccessRequestNotificationEnabled;
break;
case TriggerFeature.SECRET_APPROVAL:
targetChannelIds = slackConfig.secretRequestChannels?.split(", ") || [];
isEnabled = slackConfig.isSecretRequestNotificationEnabled;
break;
case TriggerFeature.SECRET_SYNC_ERROR:
targetChannelIds = slackConfig.secretSyncErrorChannels?.split(", ") || [];
isEnabled = slackConfig.isSecretSyncErrorNotificationEnabled;
break;
default:
return;
}
if (targetChannelIds.length && isEnabled) {
await sendSlackNotification({
orgId,
notification,
kmsService,
targetChannelIds,
slackIntegration: slackConfig
}).catch((error) => {
logger.error(
error,
`Error sending Slack notification. Notification type: ${notification.type}, Target channel IDs: ${targetChannelIds.join(", ")}, Project ID: ${slackConfig.projectId}`
);
});
}
};
export const triggerSlackNotification = async ({
projectId,
notification,
orgId,
projectSlackConfigDAL,
kmsService
}: {
projectId: string;
notification: TNotification;
orgId: string;
projectSlackConfigDAL: Pick<TProjectSlackConfigDALFactory, "getIntegrationDetailsByProject">;
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
}): Promise<void> => {
try {
const config = await projectSlackConfigDAL.getIntegrationDetailsByProject(projectId);
if (config) {
await handleSlackNotification({ slackConfig: config, notification, orgId, kmsService });
}
} catch (error) {
logger.error(error, `Error handling Slack notification. Project ID: ${projectId}`);
}
};
@@ -1,8 +1,7 @@
import { validateMicrosoftTeamsChannelsSchema } from "@app/services/microsoft-teams/microsoft-teams-fns";
import { sendSlackNotification } from "@app/services/slack/slack-fns";
import { logger } from "../logger"; import { logger } from "../logger";
import { TriggerFeature, TTriggerWorkflowNotificationDTO } from "./types"; import { triggerMicrosoftTeamsNotification } from "./notification-handlers/microsoft-teams";
import { triggerSlackNotification } from "./notification-handlers/slack";
import { TTriggerWorkflowNotificationDTO } from "./types";
export const triggerWorkflowIntegrationNotification = async (dto: TTriggerWorkflowNotificationDTO) => { export const triggerWorkflowIntegrationNotification = async (dto: TTriggerWorkflowNotificationDTO) => {
try { try {
@@ -16,88 +15,25 @@ export const triggerWorkflowIntegrationNotification = async (dto: TTriggerWorkfl
return; return;
} }
const microsoftTeamsConfig = await projectMicrosoftTeamsConfigDAL.getIntegrationDetailsByProject(projectId); const handlerPromises = [
const slackConfig = await projectSlackConfigDAL.getIntegrationDetailsByProject(projectId); triggerSlackNotification({
projectId,
notification,
orgId: project.orgId,
projectSlackConfigDAL,
kmsService
}),
if (slackConfig) { triggerMicrosoftTeamsNotification({
if ( projectId,
notification.type === TriggerFeature.ACCESS_REQUEST || notification,
notification.type === TriggerFeature.ACCESS_REQUEST_UPDATED orgId: project.orgId,
) { projectMicrosoftTeamsConfigDAL,
const targetChannelIds = slackConfig.accessRequestChannels?.split(", ") || []; microsoftTeamsService
if (targetChannelIds.length && slackConfig.isAccessRequestNotificationEnabled) { })
await sendSlackNotification({ ];
orgId: project.orgId,
notification,
kmsService,
targetChannelIds,
slackIntegration: slackConfig
}).catch((error) => {
logger.error(error, "Error sending Slack notification");
});
}
} else if (notification.type === TriggerFeature.SECRET_APPROVAL) {
const targetChannelIds = slackConfig.secretRequestChannels?.split(", ") || [];
if (targetChannelIds.length && slackConfig.isSecretRequestNotificationEnabled) {
await sendSlackNotification({
orgId: project.orgId,
notification,
kmsService,
targetChannelIds,
slackIntegration: slackConfig
}).catch((error) => {
logger.error(error, "Error sending Slack notification");
});
}
}
}
if (microsoftTeamsConfig) { await Promise.allSettled(handlerPromises);
if (
notification.type === TriggerFeature.ACCESS_REQUEST ||
notification.type === TriggerFeature.ACCESS_REQUEST_UPDATED
) {
if (microsoftTeamsConfig.isAccessRequestNotificationEnabled && microsoftTeamsConfig.accessRequestChannels) {
const { success, data } = validateMicrosoftTeamsChannelsSchema.safeParse(
microsoftTeamsConfig.accessRequestChannels
);
if (success && data) {
await microsoftTeamsService
.sendNotification({
notification,
target: data,
tenantId: microsoftTeamsConfig.tenantId,
microsoftTeamsIntegrationId: microsoftTeamsConfig.id,
orgId: project.orgId
})
.catch((error) => {
logger.error(error, "Error sending Microsoft Teams notification");
});
}
}
} else if (notification.type === TriggerFeature.SECRET_APPROVAL) {
if (microsoftTeamsConfig.isSecretRequestNotificationEnabled && microsoftTeamsConfig.secretRequestChannels) {
const { success, data } = validateMicrosoftTeamsChannelsSchema.safeParse(
microsoftTeamsConfig.secretRequestChannels
);
if (success && data) {
await microsoftTeamsService
.sendNotification({
notification,
target: data,
tenantId: microsoftTeamsConfig.tenantId,
microsoftTeamsIntegrationId: microsoftTeamsConfig.id,
orgId: project.orgId
})
.catch((error) => {
logger.error(error, "Error sending Microsoft Teams notification");
});
}
}
}
}
} catch (error) { } catch (error) {
logger.error(error, "Error triggering workflow integration notification"); logger.error(error, "Error triggering workflow integration notification");
} }
+12 -1
View File
@@ -7,7 +7,8 @@ import { TProjectSlackConfigDALFactory } from "@app/services/slack/project-slack
export enum TriggerFeature { export enum TriggerFeature {
SECRET_APPROVAL = "secret-approval", SECRET_APPROVAL = "secret-approval",
ACCESS_REQUEST = "access-request", ACCESS_REQUEST = "access-request",
ACCESS_REQUEST_UPDATED = "access-request-updated" ACCESS_REQUEST_UPDATED = "access-request-updated",
SECRET_SYNC_ERROR = "secret-sync-error"
} }
export type TNotification = export type TNotification =
@@ -51,6 +52,16 @@ export type TNotification =
editorFullName?: string; editorFullName?: string;
editorEmail?: string; editorEmail?: string;
}; };
}
| {
type: TriggerFeature.SECRET_SYNC_ERROR;
payload: {
syncName: string;
syncActionLabel: string;
syncDestination: string;
failureMessage: string;
syncUrl: string;
};
}; };
export type TTriggerWorkflowNotificationDTO = { export type TTriggerWorkflowNotificationDTO = {
@@ -1,16 +1,20 @@
import { Knex } from "knex"; import { Knex } from "knex";
import { TDbClient } from "@app/db"; import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas"; import { TableName, TMicrosoftTeamsIntegrations } from "@app/db/schemas";
import { TProjectMicrosoftTeamsConfigs } from "@app/db/schemas/project-microsoft-teams-configs";
import { ormify, selectAllTableCols } from "@app/lib/knex"; import { ormify, selectAllTableCols } from "@app/lib/knex";
export type TProjectMicrosoftTeamsConfigDALFactory = ReturnType<typeof projectMicrosoftTeamsConfigDALFactory>; export type TProjectMicrosoftTeamsConfigDALFactory = ReturnType<typeof projectMicrosoftTeamsConfigDALFactory>;
export type TProjectMicrosoftTeamsConfigWithIntegrations = TProjectMicrosoftTeamsConfigs & TMicrosoftTeamsIntegrations;
export const projectMicrosoftTeamsConfigDALFactory = (db: TDbClient) => { export const projectMicrosoftTeamsConfigDALFactory = (db: TDbClient) => {
const projectMicrosoftTeamsConfigOrm = ormify(db, TableName.ProjectMicrosoftTeamsConfigs); const projectMicrosoftTeamsConfigOrm = ormify(db, TableName.ProjectMicrosoftTeamsConfigs);
const getIntegrationDetailsByProject = (projectId: string, tx?: Knex) => { const getIntegrationDetailsByProject = (projectId: string, tx?: Knex) => {
return (tx || db.replicaNode())(TableName.ProjectMicrosoftTeamsConfigs) return (tx || db.replicaNode())<TProjectMicrosoftTeamsConfigWithIntegrations>(
TableName.ProjectMicrosoftTeamsConfigs
)
.join( .join(
TableName.MicrosoftTeamsIntegrations, TableName.MicrosoftTeamsIntegrations,
`${TableName.ProjectMicrosoftTeamsConfigs}.microsoftTeamsIntegrationId`, `${TableName.ProjectMicrosoftTeamsConfigs}.microsoftTeamsIntegrationId`,
@@ -10,6 +10,8 @@ import { TLicenseServiceFactory } from "@app/ee/services/license/license-service
import { KeyStorePrefixes, TKeyStoreFactory } from "@app/keystore/keystore"; import { KeyStorePrefixes, TKeyStoreFactory } from "@app/keystore/keystore";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { logger } from "@app/lib/logger"; import { logger } from "@app/lib/logger";
import { triggerWorkflowIntegrationNotification } from "@app/lib/workflow-integrations/trigger-notification";
import { TriggerFeature } from "@app/lib/workflow-integrations/types";
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue"; import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
import { SecretNameSchema } from "@app/server/lib/schemas"; import { SecretNameSchema } from "@app/server/lib/schemas";
import { decryptAppConnectionCredentials } from "@app/services/app-connection/app-connection-fns"; import { decryptAppConnectionCredentials } from "@app/services/app-connection/app-connection-fns";
@@ -62,8 +64,11 @@ import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
import { TAppConnectionDALFactory } from "../app-connection/app-connection-dal"; import { TAppConnectionDALFactory } from "../app-connection/app-connection-dal";
import { TFolderCommitServiceFactory } from "../folder-commit/folder-commit-service"; import { TFolderCommitServiceFactory } from "../folder-commit/folder-commit-service";
import { TMicrosoftTeamsServiceFactory } from "../microsoft-teams/microsoft-teams-service";
import { TProjectMicrosoftTeamsConfigDALFactory } from "../microsoft-teams/project-microsoft-teams-config-dal";
import { TNotificationServiceFactory } from "../notification/notification-service"; import { TNotificationServiceFactory } from "../notification/notification-service";
import { NotificationType } from "../notification/notification-types"; import { NotificationType } from "../notification/notification-types";
import { TProjectSlackConfigDALFactory } from "../slack/project-slack-config-dal";
export type TSecretSyncQueueFactory = ReturnType<typeof secretSyncQueueFactory>; export type TSecretSyncQueueFactory = ReturnType<typeof secretSyncQueueFactory>;
@@ -104,6 +109,9 @@ type TSecretSyncQueueFactoryDep = {
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">; gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">;
gatewayV2Service: Pick<TGatewayV2ServiceFactory, "getPlatformConnectionDetailsByGatewayId">; gatewayV2Service: Pick<TGatewayV2ServiceFactory, "getPlatformConnectionDetailsByGatewayId">;
notificationService: Pick<TNotificationServiceFactory, "createUserNotifications">; notificationService: Pick<TNotificationServiceFactory, "createUserNotifications">;
projectSlackConfigDAL: Pick<TProjectSlackConfigDALFactory, "getIntegrationDetailsByProject">;
projectMicrosoftTeamsConfigDAL: Pick<TProjectMicrosoftTeamsConfigDALFactory, "getIntegrationDetailsByProject">;
microsoftTeamsService: Pick<TMicrosoftTeamsServiceFactory, "sendNotification">;
}; };
type SecretSyncActionJob = Job< type SecretSyncActionJob = Job<
@@ -147,7 +155,10 @@ export const secretSyncQueueFactory = ({
licenseService, licenseService,
gatewayService, gatewayService,
gatewayV2Service, gatewayV2Service,
notificationService notificationService,
projectSlackConfigDAL,
projectMicrosoftTeamsConfigDAL,
microsoftTeamsService
}: TSecretSyncQueueFactoryDep) => { }: TSecretSyncQueueFactoryDep) => {
const appCfg = getConfig(); const appCfg = getConfig();
@@ -923,32 +934,57 @@ export const secretSyncQueueFactory = ({
const syncPath = `/projects/secret-management/${projectId}/integrations/secret-syncs/${destination}/${secretSync.id}`; const syncPath = `/projects/secret-management/${projectId}/integrations/secret-syncs/${destination}/${secretSync.id}`;
await notificationService.createUserNotifications( const notifications = [
projectAdmins.map((admin) => ({ triggerWorkflowIntegrationNotification({
userId: admin.userId, input: {
orgId: project.orgId, notification: {
type: NotificationType.SECRET_SYNC_FAILED, type: TriggerFeature.SECRET_SYNC_ERROR,
title: `Secret Sync Failed to ${actionLabel} Secrets`, payload: {
body: `Your **${syncDestination}** sync **${name}** failed to complete${failureMessage ? `: \`${failureMessage}\`` : ""}`, syncName: name,
link: syncPath syncDestination,
})) failureMessage: failureMessage || "An unknown error occurred",
); syncUrl: `${appCfg.SITE_URL}${syncPath}`,
syncActionLabel: actionLabel
}
},
projectId: project.id
},
dependencies: {
projectDAL,
projectSlackConfigDAL,
kmsService,
microsoftTeamsService,
projectMicrosoftTeamsConfigDAL
}
}),
notificationService.createUserNotifications(
projectAdmins.map((admin) => ({
userId: admin.userId,
orgId: project.orgId,
type: NotificationType.SECRET_SYNC_FAILED,
title: `Secret Sync Failed to ${actionLabel} Secrets`,
body: `Your **${syncDestination}** sync **${name}** failed to complete${failureMessage ? `: \`${failureMessage}\`` : ""}`,
link: syncPath
}))
),
smtpService.sendMail({
recipients: projectAdmins.map((member) => member.user.email!).filter(Boolean),
template: SmtpTemplates.SecretSyncFailed,
subjectLine: `Secret Sync Failed to ${actionLabel} Secrets`,
substitutions: {
syncName: name,
syncDestination,
content: `Your ${syncDestination} Sync named "${name}" failed while attempting to ${action.toLowerCase()} secrets.`,
failureMessage,
secretPath: folder?.path,
environment: environment?.name,
projectName: project.name,
syncUrl: `${appCfg.SITE_URL}${syncPath}`
}
})
];
await smtpService.sendMail({ await Promise.allSettled(notifications);
recipients: projectAdmins.map((member) => member.user.email!).filter(Boolean),
template: SmtpTemplates.SecretSyncFailed,
subjectLine: `Secret Sync Failed to ${actionLabel} Secrets`,
substitutions: {
syncName: name,
syncDestination,
content: `Your ${syncDestination} Sync named "${name}" failed while attempting to ${action.toLowerCase()} secrets.`,
failureMessage,
secretPath: folder?.path,
environment: environment?.name,
projectName: project.name,
syncUrl: `${appCfg.SITE_URL}${syncPath}`
}
});
}; };
const queueSecretSyncsSyncSecretsByPath = async ({ const queueSecretSyncsSyncSecretsByPath = async ({
@@ -1,16 +1,17 @@
import { Knex } from "knex"; import { Knex } from "knex";
import { TDbClient } from "@app/db"; import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas"; import { TableName, TProjectSlackConfigs, TSlackIntegrations } from "@app/db/schemas";
import { ormify, selectAllTableCols } from "@app/lib/knex"; import { ormify, selectAllTableCols } from "@app/lib/knex";
export type TProjectSlackConfigDALFactory = ReturnType<typeof projectSlackConfigDALFactory>; export type TProjectSlackConfigDALFactory = ReturnType<typeof projectSlackConfigDALFactory>;
export type TProjectSlackConfigWithIntegrations = TProjectSlackConfigs & TSlackIntegrations;
export const projectSlackConfigDALFactory = (db: TDbClient) => { export const projectSlackConfigDALFactory = (db: TDbClient) => {
const projectSlackConfigOrm = ormify(db, TableName.ProjectSlackConfigs); const projectSlackConfigOrm = ormify(db, TableName.ProjectSlackConfigs);
const getIntegrationDetailsByProject = (projectId: string, tx?: Knex) => { const getIntegrationDetailsByProject = (projectId: string, tx?: Knex) => {
return (tx || db.replicaNode())(TableName.ProjectSlackConfigs) return (tx || db.replicaNode())<TProjectSlackConfigWithIntegrations>(TableName.ProjectSlackConfigs)
.join( .join(
TableName.SlackIntegrations, TableName.SlackIntegrations,
`${TableName.ProjectSlackConfigs}.slackIntegrationId`, `${TableName.ProjectSlackConfigs}.slackIntegrationId`,
+60 -5
View File
@@ -8,6 +8,9 @@ import { TNotification, TriggerFeature } from "@app/lib/workflow-integrations/ty
import { KmsDataKey } from "../kms/kms-types"; import { KmsDataKey } from "../kms/kms-types";
import { TSendSlackNotificationDTO } from "./slack-types"; import { TSendSlackNotificationDTO } from "./slack-types";
const COMPANY_BRAND_COLOR = "#e0ed34";
const ERROR_COLOR = "#e74c3c";
export const fetchSlackChannels = async (botKey: string) => { export const fetchSlackChannels = async (botKey: string) => {
const slackChannels: { const slackChannels: {
name: string; name: string;
@@ -74,7 +77,8 @@ View the complete details <${appCfg.SITE_URL}/projects/secret-management/${paylo
return { return {
payloadMessage: messageBody, payloadMessage: messageBody,
payloadBlocks payloadBlocks,
color: COMPANY_BRAND_COLOR
}; };
} }
case TriggerFeature.ACCESS_REQUEST: { case TriggerFeature.ACCESS_REQUEST: {
@@ -112,7 +116,8 @@ User Note: ${payload.note}`
return { return {
payloadMessage: messageBody, payloadMessage: messageBody,
payloadBlocks payloadBlocks,
color: COMPANY_BRAND_COLOR
}; };
} }
case TriggerFeature.ACCESS_REQUEST_UPDATED: { case TriggerFeature.ACCESS_REQUEST_UPDATED: {
@@ -150,7 +155,52 @@ Editor Note: ${payload.editNote}`
return { return {
payloadMessage: messageBody, payloadMessage: messageBody,
payloadBlocks payloadBlocks,
color: COMPANY_BRAND_COLOR
};
}
case TriggerFeature.SECRET_SYNC_ERROR: {
const { payload } = notification;
const messageBody = `${payload.syncName} for ${payload.syncDestination} failed on ${payload.syncActionLabel}
Sync Error: ${payload.failureMessage}`;
const payloadBlocks = [
{
type: "header",
text: {
type: "plain_text",
text: `${payload.syncName} for ${payload.syncDestination} failed on ${payload.syncActionLabel}`,
emoji: true
}
},
{
type: "section",
text: {
type: "mrkdwn",
text: `*Sync Error:* ${payload.failureMessage}`
}
},
{
type: "actions",
elements: [
{
type: "button",
text: {
type: "plain_text",
text: `Open ${payload.syncName}`,
emoji: true
},
url: payload.syncUrl
}
]
}
];
return {
payloadMessage: messageBody,
payloadBlocks,
color: ERROR_COLOR
}; };
} }
default: { default: {
@@ -177,7 +227,7 @@ export const sendSlackNotification = async ({
}).toString("utf8"); }).toString("utf8");
const slackWebClient = new WebClient(botKey); const slackWebClient = new WebClient(botKey);
const { payloadMessage, payloadBlocks } = buildSlackPayload(notification); const { payloadMessage, payloadBlocks, color } = buildSlackPayload(notification);
for await (const conversationId of targetChannelIds) { for await (const conversationId of targetChannelIds) {
// we send both text and blocks for compatibility with barebone clients // we send both text and blocks for compatibility with barebone clients
@@ -185,7 +235,12 @@ export const sendSlackNotification = async ({
.postMessage({ .postMessage({
channel: conversationId, channel: conversationId,
text: payloadMessage, text: payloadMessage,
blocks: payloadBlocks attachments: [
{
color,
blocks: payloadBlocks
}
]
}) })
.catch((err) => logger.error(err)); .catch((err) => logger.error(err));
} }