feat(notifications): sync failed notification + ui tweaks

This commit is contained in:
x032205
2025-09-16 02:24:41 -04:00
parent 747248b7a4
commit 888ca40a58
5 changed files with 29 additions and 6 deletions
+2 -1
View File
@@ -1148,7 +1148,8 @@ export const registerRoutes = async (
appConnectionDAL, appConnectionDAL,
licenseService, licenseService,
gatewayService, gatewayService,
gatewayV2Service gatewayV2Service,
notificationService
}); });
const secretQueueService = secretQueueFactory({ const secretQueueService = secretQueueFactory({
@@ -14,7 +14,8 @@ export enum NotificationType {
IMPORT_FAILED = "import-failed", IMPORT_FAILED = "import-failed",
DIRECT_PROJECT_ACCESS_ISSUED_TO_ADMIN = "direct-project-access-issued-to-admin", DIRECT_PROJECT_ACCESS_ISSUED_TO_ADMIN = "direct-project-access-issued-to-admin",
PROJECT_ACCESS_REQUEST = "project-access-request", PROJECT_ACCESS_REQUEST = "project-access-request",
PROJECT_INVITATION = "project-invitation" PROJECT_INVITATION = "project-invitation",
SECRET_SYNC_FAILED = "secret-sync-failed"
} }
export interface TCreateUserNotificationDTO { export interface TCreateUserNotificationDTO {
@@ -61,6 +61,8 @@ 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 { TNotificationServiceFactory } from "../notification/notification-service";
import { NotificationType } from "../notification/notification-types";
export type TSecretSyncQueueFactory = ReturnType<typeof secretSyncQueueFactory>; export type TSecretSyncQueueFactory = ReturnType<typeof secretSyncQueueFactory>;
@@ -100,6 +102,7 @@ type TSecretSyncQueueFactoryDep = {
licenseService: Pick<TLicenseServiceFactory, "getPlan">; licenseService: Pick<TLicenseServiceFactory, "getPlan">;
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">; gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">;
gatewayV2Service: Pick<TGatewayV2ServiceFactory, "getPlatformConnectionDetailsByGatewayId">; gatewayV2Service: Pick<TGatewayV2ServiceFactory, "getPlatformConnectionDetailsByGatewayId">;
notificationService: Pick<TNotificationServiceFactory, "createUserNotifications">;
}; };
type SecretSyncActionJob = Job< type SecretSyncActionJob = Job<
@@ -142,7 +145,8 @@ export const secretSyncQueueFactory = ({
folderCommitService, folderCommitService,
licenseService, licenseService,
gatewayService, gatewayService,
gatewayV2Service gatewayV2Service,
notificationService
}: TSecretSyncQueueFactoryDep) => { }: TSecretSyncQueueFactoryDep) => {
const appCfg = getConfig(); const appCfg = getConfig();
@@ -898,6 +902,19 @@ export const secretSyncQueueFactory = ({
break; break;
} }
const syncPath = `/projects/secret-management/${projectId}/integrations/secret-syncs/${destination}/${secretSync.id}`;
await 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
}))
);
await smtpService.sendMail({ await smtpService.sendMail({
recipients: projectAdmins.map((member) => member.user.email!).filter(Boolean), recipients: projectAdmins.map((member) => member.user.email!).filter(Boolean),
template: SmtpTemplates.SecretSyncFailed, template: SmtpTemplates.SecretSyncFailed,
@@ -910,7 +927,7 @@ export const secretSyncQueueFactory = ({
secretPath: folder?.path, secretPath: folder?.path,
environment: environment?.name, environment: environment?.name,
projectName: project.name, projectName: project.name,
syncUrl: `${appCfg.SITE_URL}/projects/secret-management/${projectId}/integrations/secret-syncs/${destination}/${secretSync.id}` syncUrl: `${appCfg.SITE_URL}${syncPath}`
} }
}); });
}; };
@@ -26,7 +26,11 @@ export const Notification = ({ notification, onDelete }: Props) => {
{!notification.isRead && ( {!notification.isRead && (
<FontAwesomeIcon icon={faCircle} className="mt-1.5 size-2 text-yellow-400" /> <FontAwesomeIcon icon={faCircle} className="mt-1.5 size-2 text-yellow-400" />
)} )}
<Tooltip content={<Markdown>{notification.title}</Markdown>} delayDuration={300}> <Tooltip
content={<Markdown>{notification.title}</Markdown>}
delayDuration={300}
className="z-[1000]"
>
<span className="overflow-hidden text-ellipsis whitespace-nowrap text-sm font-medium leading-5 text-mineshaft-100"> <span className="overflow-hidden text-ellipsis whitespace-nowrap text-sm font-medium leading-5 text-mineshaft-100">
<Markdown components={{ p: "span" }}>{notification.title}</Markdown> <Markdown components={{ p: "span" }}>{notification.title}</Markdown>
</span> </span>
@@ -46,7 +46,7 @@ export const NotificationDropdown = () => {
<DropdownMenuContent <DropdownMenuContent
align="end" align="end"
side="bottom" side="bottom"
className="z-[9999] mt-3 flex h-[550px] w-[400px] overflow-hidden rounded-lg" className="z-[999] mt-3 flex h-[550px] w-[400px] overflow-hidden rounded-lg"
> >
<div className="flex w-full flex-col"> <div className="flex w-full flex-col">
<div className="flex items-center justify-between border-b border-mineshaft-500 px-3 py-2"> <div className="flex items-center justify-between border-b border-mineshaft-500 px-3 py-2">