fix(microsoft-teams-integration): bug fixes

This commit is contained in:
Daniel Hougaard
2025-04-25 11:03:31 +04:00
parent 8563eb850b
commit 8987938642
9 changed files with 332 additions and 113 deletions
@@ -51,6 +51,9 @@ export async function up(knex: Knex): Promise<void> {
table.binary("encryptedAccessToken").nullable(); table.binary("encryptedAccessToken").nullable();
table.binary("encryptedBotAccessToken").nullable(); table.binary("encryptedBotAccessToken").nullable();
table.timestamp("accessTokenExpiresAt").nullable();
table.timestamp("botAccessTokenExpiresAt").nullable();
table.timestamps(true, true, true); table.timestamps(true, true, true);
}); });
@@ -15,6 +15,8 @@ export const MicrosoftTeamsIntegrationsSchema = z.object({
tenantId: z.string(), tenantId: z.string(),
encryptedAccessToken: zodBuffer.nullable().optional(), encryptedAccessToken: zodBuffer.nullable().optional(),
encryptedBotAccessToken: zodBuffer.nullable().optional(), encryptedBotAccessToken: zodBuffer.nullable().optional(),
accessTokenExpiresAt: z.date().nullable().optional(),
botAccessTokenExpiresAt: z.date().nullable().optional(),
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date() updatedAt: z.date()
}); });
@@ -61,7 +61,9 @@ export const triggerWorkflowIntegrationNotification = async (dto: TTriggerWorkfl
.sendNotification({ .sendNotification({
notification, notification,
target: data, target: data,
tenantId: microsoftTeamsConfig.tenantId tenantId: microsoftTeamsConfig.tenantId,
microsoftTeamsIntegrationId: microsoftTeamsConfig.id,
orgId: project.orgId
}) })
.catch((error) => { .catch((error) => {
logger.error(error, "Error sending Microsoft Teams notification"); logger.error(error, "Error sending Microsoft Teams notification");
@@ -79,7 +81,9 @@ export const triggerWorkflowIntegrationNotification = async (dto: TTriggerWorkfl
.sendNotification({ .sendNotification({
notification, notification,
target: data, target: data,
tenantId: microsoftTeamsConfig.tenantId tenantId: microsoftTeamsConfig.tenantId,
microsoftTeamsIntegrationId: microsoftTeamsConfig.id,
orgId: project.orgId
}) })
.catch((error) => { .catch((error) => {
logger.error(error, "Error sending Microsoft Teams notification"); logger.error(error, "Error sending Microsoft Teams notification");
@@ -1,6 +1,7 @@
/* eslint-disable class-methods-use-this */ /* eslint-disable class-methods-use-this */
import axios from "axios"; import axios from "axios";
import { TeamsActivityHandler, TurnContext } from "botbuilder"; import { TeamsActivityHandler, TurnContext } from "botbuilder";
import { Knex } from "knex";
import { z } from "zod"; import { z } from "zod";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
@@ -8,21 +9,35 @@ import { BadRequestError } from "@app/lib/errors";
import { logger } from "@app/lib/logger"; import { logger } from "@app/lib/logger";
import { TNotification, TriggerFeature } from "@app/lib/workflow-integrations/types"; import { TNotification, TriggerFeature } from "@app/lib/workflow-integrations/types";
import { TKmsServiceFactory } from "../kms/kms-service";
import { KmsDataKey } from "../kms/kms-types";
import { TWorkflowIntegrationDALFactory } from "../workflow-integration/workflow-integration-dal"; import { TWorkflowIntegrationDALFactory } from "../workflow-integration/workflow-integration-dal";
import { WorkflowIntegrationStatus } from "../workflow-integration/workflow-integration-types"; import { WorkflowIntegrationStatus } from "../workflow-integration/workflow-integration-types";
import { TMicrosoftTeamsIntegrationDALFactory } from "./microsoft-teams-integration-dal"; import { TMicrosoftTeamsIntegrationDALFactory } from "./microsoft-teams-integration-dal";
export const getMicrosoftTeamsAccessToken = async ({ export const getMicrosoftTeamsAccessToken = async (
{
orgId,
microsoftTeamsIntegrationId,
tenantId, tenantId,
clientId, clientId,
clientSecret, clientSecret,
getBotFrameworkToken = false kmsService,
microsoftTeamsIntegrationDAL,
getBotFrameworkToken
}: { }: {
microsoftTeamsIntegrationId: string;
orgId: string;
tenantId: string; tenantId: string;
clientId: string; clientId: string;
clientSecret: string; clientSecret: string;
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
microsoftTeamsIntegrationDAL: Pick<TMicrosoftTeamsIntegrationDALFactory, "findOne" | "update">;
getBotFrameworkToken?: boolean; getBotFrameworkToken?: boolean;
}) => { },
tx?: Knex
) => {
try {
const details = getBotFrameworkToken const details = getBotFrameworkToken
? { ? {
uri: "https://login.microsoftonline.com/botframework.com/oauth2/v2.0/token", uri: "https://login.microsoftonline.com/botframework.com/oauth2/v2.0/token",
@@ -33,7 +48,58 @@ export const getMicrosoftTeamsAccessToken = async ({
scope: "https://graph.microsoft.com/.default" scope: "https://graph.microsoft.com/.default"
}; };
const tokenResponse = await axios.post<{ access_token: string }>( const integration = await microsoftTeamsIntegrationDAL.findOne(
{
id: microsoftTeamsIntegrationId
},
tx
);
if (!integration) {
throw new BadRequestError({ message: "Microsoft Teams integration not found" });
}
if (getBotFrameworkToken) {
const currentTime = new Date(new Date().getTime() + 5 * 60 * 1000);
if (
integration.encryptedBotAccessToken &&
integration.botAccessTokenExpiresAt &&
integration.botAccessTokenExpiresAt > currentTime
) {
const { decryptor } = await kmsService.createCipherPairWithDataKey({
orgId,
type: KmsDataKey.Organization
});
const botAccessToken = decryptor({
cipherTextBlob: integration.encryptedBotAccessToken
});
return botAccessToken.toString();
}
} else {
const currentTime = new Date(new Date().getTime() + 5 * 60 * 1000);
if (
integration.encryptedAccessToken &&
integration.accessTokenExpiresAt &&
integration.accessTokenExpiresAt > currentTime
) {
const { decryptor } = await kmsService.createCipherPairWithDataKey({
orgId,
type: KmsDataKey.Organization
});
const accessToken = decryptor({
cipherTextBlob: integration.encryptedAccessToken
});
return accessToken.toString();
}
}
const tokenResponse = await axios.post<{ access_token: string; expires_in: number }>(
details.uri, details.uri,
new URLSearchParams({ new URLSearchParams({
client_id: clientId, client_id: clientId,
@@ -43,33 +109,118 @@ export const getMicrosoftTeamsAccessToken = async ({
}) })
); );
if (getBotFrameworkToken) {
const { encryptor } = await kmsService.createCipherPairWithDataKey({
orgId,
type: KmsDataKey.Organization
});
const { cipherTextBlob: encryptedBotAccessToken } = encryptor({
plainText: Buffer.from(tokenResponse.data.access_token)
});
const expiresAt = new Date(new Date().getTime() + tokenResponse.data.expires_in * 1000);
await microsoftTeamsIntegrationDAL.update(
{
id: microsoftTeamsIntegrationId
},
{
botAccessTokenExpiresAt: expiresAt,
encryptedBotAccessToken
},
tx
);
} else {
const { encryptor } = await kmsService.createCipherPairWithDataKey({
orgId,
type: KmsDataKey.Organization
});
const { cipherTextBlob: encryptedAccessToken } = encryptor({
plainText: Buffer.from(tokenResponse.data.access_token)
});
const expiresAt = new Date(new Date().getTime() + tokenResponse.data.expires_in * 1000);
await microsoftTeamsIntegrationDAL.update(
{
id: microsoftTeamsIntegrationId
},
{
accessTokenExpiresAt: expiresAt,
encryptedAccessToken
},
tx
);
}
return tokenResponse.data.access_token; return tokenResponse.data.access_token;
} catch (error) {
if (axios.isAxiosError(error)) {
logger.error(
error.response?.data,
`getMicrosoftTeamsAccessToken: Error fetching Microsoft Teams access token [status-code=${error.response?.status}]`
);
} else {
logger.error(error, "getMicrosoftTeamsAccessToken: Error fetching Microsoft Teams access token");
}
throw error;
}
}; };
export const isBotInstalledInTenant = async ({ export const isBotInstalledInTenant = async (
{
tenantId, tenantId,
botAppId, botAppId,
botAppPassword, botAppPassword,
botId botId,
orgId,
kmsService,
microsoftTeamsIntegrationDAL,
microsoftTeamsIntegrationId
}: { }: {
tenantId: string; tenantId: string;
botAppId: string; botAppId: string;
botAppPassword: string; botAppPassword: string;
botId: string; botId: string;
}) => { orgId: string;
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
microsoftTeamsIntegrationDAL: Pick<TMicrosoftTeamsIntegrationDALFactory, "findOne" | "update">;
microsoftTeamsIntegrationId: string;
},
tx?: Knex
) => {
try { try {
const botAccessToken = await getMicrosoftTeamsAccessToken({ const botAccessToken = await getMicrosoftTeamsAccessToken(
{
tenantId, tenantId,
clientId: botAppId.toString(), clientId: botAppId.toString(),
clientSecret: botAppPassword.toString(), clientSecret: botAppPassword.toString(),
getBotFrameworkToken: true getBotFrameworkToken: true,
}).catch(() => null); orgId,
kmsService,
microsoftTeamsIntegrationDAL,
microsoftTeamsIntegrationId
},
tx
).catch(() => null);
const accessToken = await getMicrosoftTeamsAccessToken({ const accessToken = await getMicrosoftTeamsAccessToken(
{
orgId,
tenantId, tenantId,
clientId: botAppId.toString(), clientId: botAppId.toString(),
clientSecret: botAppPassword.toString() clientSecret: botAppPassword.toString(),
}).catch(() => null); kmsService,
microsoftTeamsIntegrationDAL,
microsoftTeamsIntegrationId
},
tx
).catch(() => null);
console.log("botAccessToken", botAccessToken);
console.log("accessToken", accessToken);
if (!botAccessToken || !accessToken) { if (!botAccessToken || !accessToken) {
return { return {
@@ -105,6 +256,12 @@ export const isBotInstalledInTenant = async ({
const botInstalledInTenant = appsResponse.data.value.find((a) => a.externalId === botId); const botInstalledInTenant = appsResponse.data.value.find((a) => a.externalId === botId);
for (const app of appsResponse.data.value) {
if (app.displayName.toLowerCase().includes("infisical")) {
console.log(`${app.displayName} - ${app.externalId}`);
}
}
if (!botInstalledInTenant) { if (!botInstalledInTenant) {
return { return {
installed: false, installed: false,
@@ -311,17 +468,16 @@ export class TeamsBot extends TeamsActivityHandler {
await super.run(context); await super.run(context);
} }
async sendMessageToChannel(tenantId: string, channelId: string, teamId: string, notification: TNotification) { async sendMessageToChannel(
botAccessToken: string,
tenantId: string,
channelId: string,
teamId: string,
notification: TNotification
) {
try { try {
const { adaptiveCard } = buildTeamsPayload(notification); const { adaptiveCard } = buildTeamsPayload(notification);
const botToken = await getMicrosoftTeamsAccessToken({
tenantId,
clientId: this.botAppId,
clientSecret: this.botAppPassword,
getBotFrameworkToken: true
});
const adaptiveCardActivity = { const adaptiveCardActivity = {
type: "message", type: "message",
attachments: [ attachments: [
@@ -349,7 +505,7 @@ export class TeamsBot extends TeamsActivityHandler {
adaptiveCardActivity, adaptiveCardActivity,
{ {
headers: { headers: {
Authorization: `Bearer ${botToken}`, Authorization: `Bearer ${botAccessToken}`,
"Content-Type": "application/json" "Content-Type": "application/json"
} }
} }
@@ -364,18 +520,12 @@ export class TeamsBot extends TeamsActivityHandler {
} }
// todo: filter out teams that the bot is not a member of // todo: filter out teams that the bot is not a member of
async getTeamsAndChannels(tenantId: string, internalAppId: string) { async getTeamsAndChannels(accessToken: string, tenantId: string, internalAppId: string) {
try { try {
const token = await getMicrosoftTeamsAccessToken({
tenantId,
clientId: this.botAppId,
clientSecret: this.botAppPassword
});
const teamsResponse = await axios const teamsResponse = await axios
.get<{ value: { displayName: string; id: string }[] }>(`https://graph.microsoft.com/v1.0/teams`, { .get<{ value: { displayName: string; id: string }[] }>(`https://graph.microsoft.com/v1.0/teams`, {
headers: { headers: {
Authorization: `Bearer ${token}` Authorization: `Bearer ${accessToken}`
} }
}) })
.catch((error) => { .catch((error) => {
@@ -393,7 +543,7 @@ export class TeamsBot extends TeamsActivityHandler {
`https://graph.microsoft.com/v1.0/teams/${team.id}/installedApps?$expand=teamsAppDefinition`, `https://graph.microsoft.com/v1.0/teams/${team.id}/installedApps?$expand=teamsAppDefinition`,
{ {
headers: { headers: {
Authorization: `Bearer ${token}` Authorization: `Bearer ${accessToken}`
} }
} }
); );
@@ -412,7 +562,7 @@ export class TeamsBot extends TeamsActivityHandler {
`https://graph.microsoft.com/v1.0/teams/${team.id}/channels`, `https://graph.microsoft.com/v1.0/teams/${team.id}/channels`,
{ {
headers: { headers: {
Authorization: `Bearer ${token}` Authorization: `Bearer ${accessToken}`
} }
} }
) )
@@ -18,7 +18,7 @@ import { KmsDataKey } from "../kms/kms-types";
import { TSuperAdminDALFactory } from "../super-admin/super-admin-dal"; import { TSuperAdminDALFactory } from "../super-admin/super-admin-dal";
import { TWorkflowIntegrationDALFactory } from "../workflow-integration/workflow-integration-dal"; import { TWorkflowIntegrationDALFactory } from "../workflow-integration/workflow-integration-dal";
import { WorkflowIntegration, WorkflowIntegrationStatus } from "../workflow-integration/workflow-integration-types"; import { WorkflowIntegration, WorkflowIntegrationStatus } from "../workflow-integration/workflow-integration-types";
import { isBotInstalledInTenant, TeamsBot } from "./microsoft-teams-fns"; import { getMicrosoftTeamsAccessToken, isBotInstalledInTenant, TeamsBot } from "./microsoft-teams-fns";
import { TMicrosoftTeamsIntegrationDALFactory } from "./microsoft-teams-integration-dal"; import { TMicrosoftTeamsIntegrationDALFactory } from "./microsoft-teams-integration-dal";
import { import {
TCheckInstallationStatusDTO, TCheckInstallationStatusDTO,
@@ -56,6 +56,7 @@ type TMicrosoftTeamsServiceFactoryDep = {
| "findById" | "findById"
| "findByIdWithWorkflowIntegrationDetails" | "findByIdWithWorkflowIntegrationDetails"
| "findWithWorkflowIntegrationDetails" | "findWithWorkflowIntegrationDetails"
| "update"
>; >;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getOrgPermission">; permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getOrgPermission">;
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey" | "encryptWithRootKey" | "decryptWithRootKey">; kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey" | "encryptWithRootKey" | "decryptWithRootKey">;
@@ -131,16 +132,6 @@ export const microsoftTeamsServiceFactory = ({
actorAuthMethod, actorAuthMethod,
workflowIntegrationId workflowIntegrationId
}: TCheckInstallationStatusDTO) => { }: TCheckInstallationStatusDTO) => {
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
actorOrgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
const microsoftTeamsIntegration = const microsoftTeamsIntegration =
await microsoftTeamsIntegrationDAL.findByIdWithWorkflowIntegrationDetails(workflowIntegrationId); await microsoftTeamsIntegrationDAL.findByIdWithWorkflowIntegrationDetails(workflowIntegrationId);
@@ -150,6 +141,16 @@ export const microsoftTeamsServiceFactory = ({
}); });
} }
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
microsoftTeamsIntegration.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
const serverCfg = await serverCfgDAL.findById(ADMIN_CONFIG_DB_UUID); const serverCfg = await serverCfgDAL.findById(ADMIN_CONFIG_DB_UUID);
if (!serverCfg) { if (!serverCfg) {
throw new BadRequestError({ throw new BadRequestError({
@@ -175,7 +176,11 @@ export const microsoftTeamsServiceFactory = ({
tenantId: microsoftTeamsIntegration.tenantId, tenantId: microsoftTeamsIntegration.tenantId,
botAppId: decryptedAppId.toString(), botAppId: decryptedAppId.toString(),
botAppPassword: decryptedAppPassword.toString(), botAppPassword: decryptedAppPassword.toString(),
botId: decryptedBotId.toString() botId: decryptedBotId.toString(),
orgId: microsoftTeamsIntegration.orgId,
kmsService,
microsoftTeamsIntegrationDAL,
microsoftTeamsIntegrationId: microsoftTeamsIntegration.id
}); });
if (!teamsBotInfo.installed) { if (!teamsBotInfo.installed) {
@@ -258,12 +263,20 @@ export const microsoftTeamsServiceFactory = ({
const botAppPassword = decryptWithRoot(encryptedMicrosoftTeamsClientSecret); const botAppPassword = decryptWithRoot(encryptedMicrosoftTeamsClientSecret);
const botId = decryptWithRoot(encryptedMicrosoftTeamsBotId); const botId = decryptWithRoot(encryptedMicrosoftTeamsBotId);
const teamsBotInfo = await isBotInstalledInTenant({ const teamsBotInfo = await isBotInstalledInTenant(
{
tenantId: microsoftTeamsIntegration.tenantId, tenantId: microsoftTeamsIntegration.tenantId,
botAppId: botAppId.toString(), botAppId: botAppId.toString(),
botAppPassword: botAppPassword.toString(), botAppPassword: botAppPassword.toString(),
botId: botId.toString() botId: botId.toString(),
}); orgId: workflowIntegration.orgId,
kmsService,
microsoftTeamsIntegrationDAL,
microsoftTeamsIntegrationId: microsoftTeamsIntegration.id
},
tx
);
if (teamsBotInfo.installed) { if (teamsBotInfo.installed) {
const { encryptor: orgDataKeyEncryptor } = await kmsService.createCipherPairWithDataKey({ const { encryptor: orgDataKeyEncryptor } = await kmsService.createCipherPairWithDataKey({
orgId: workflowIntegration.orgId, orgId: workflowIntegration.orgId,
@@ -451,16 +464,6 @@ export const microsoftTeamsServiceFactory = ({
}; };
const getTeams = async ({ actorId, actor, actorOrgId, actorAuthMethod, workflowIntegrationId }: TGetTeamsDTO) => { const getTeams = async ({ actorId, actor, actorOrgId, actorAuthMethod, workflowIntegrationId }: TGetTeamsDTO) => {
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
actorOrgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Settings);
const microsoftTeamsIntegration = const microsoftTeamsIntegration =
await microsoftTeamsIntegrationDAL.findByIdWithWorkflowIntegrationDetails(workflowIntegrationId); await microsoftTeamsIntegrationDAL.findByIdWithWorkflowIntegrationDetails(workflowIntegrationId);
@@ -470,6 +473,16 @@ export const microsoftTeamsServiceFactory = ({
}); });
} }
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
microsoftTeamsIntegration.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Settings);
if (!teamsBot || !adapter) { if (!teamsBot || !adapter) {
throw new BadRequestError({ throw new BadRequestError({
message: "Unable to get teams and channels because the Microsoft Teams bot is uninitialized" message: "Unable to get teams and channels because the Microsoft Teams bot is uninitialized"
@@ -498,11 +511,15 @@ export const microsoftTeamsServiceFactory = ({
const decryptedAppPassword = decryptWithRoot(serverCfg.encryptedMicrosoftTeamsClientSecret); const decryptedAppPassword = decryptWithRoot(serverCfg.encryptedMicrosoftTeamsClientSecret);
const decryptedBotId = decryptWithRoot(serverCfg.encryptedMicrosoftTeamsBotId); const decryptedBotId = decryptWithRoot(serverCfg.encryptedMicrosoftTeamsBotId);
const { installed, internalId } = await isBotInstalledInTenant({ const { installed, internalId, accessToken } = await isBotInstalledInTenant({
tenantId: microsoftTeamsIntegration.tenantId, tenantId: microsoftTeamsIntegration.tenantId,
botAppId: decryptedAppId.toString(), botAppId: decryptedAppId.toString(),
botAppPassword: decryptedAppPassword.toString(), botAppPassword: decryptedAppPassword.toString(),
botId: decryptedBotId.toString() botId: decryptedBotId.toString(),
orgId: actorOrgId,
kmsService,
microsoftTeamsIntegrationDAL,
microsoftTeamsIntegrationId: microsoftTeamsIntegration.id
}); });
if (!installed) { if (!installed) {
@@ -511,7 +528,7 @@ export const microsoftTeamsServiceFactory = ({
}); });
} }
const teams = await teamsBot.getTeamsAndChannels(microsoftTeamsIntegration.tenantId, internalId); const teams = await teamsBot.getTeamsAndChannels(accessToken, microsoftTeamsIntegration.tenantId, internalId);
return teams; return teams;
}; };
@@ -580,15 +597,53 @@ export const microsoftTeamsServiceFactory = ({
}); });
}; };
const sendNotification = async ({ tenantId, target, notification }: TSendNotificationDTO) => { const sendNotification = async ({
tenantId,
target,
notification,
orgId,
microsoftTeamsIntegrationId
}: TSendNotificationDTO) => {
if (!teamsBot || !adapter) { if (!teamsBot || !adapter) {
throw new BadRequestError({ throw new BadRequestError({
message: "Unable to send notification because the Microsoft Teams bot is uninitialized" message: "Unable to send notification because the Microsoft Teams bot is uninitialized"
}); });
} }
const serverCfg = await serverCfgDAL.findById(ADMIN_CONFIG_DB_UUID);
if (!serverCfg) {
throw new BadRequestError({
message: "Failed to get server configuration."
});
}
if (
!serverCfg.encryptedMicrosoftTeamsAppId ||
!serverCfg.encryptedMicrosoftTeamsClientSecret ||
!serverCfg.encryptedMicrosoftTeamsBotId
) {
throw new BadRequestError({
message: "Microsoft Teams app ID, client secret, or bot ID is not set"
});
}
const decryptWithRoot = kmsService.decryptWithRootKey();
const botAppId = decryptWithRoot(serverCfg.encryptedMicrosoftTeamsAppId);
const botAppPassword = decryptWithRoot(serverCfg.encryptedMicrosoftTeamsClientSecret);
const botAccessToken = await getMicrosoftTeamsAccessToken({
tenantId,
clientId: botAppId.toString(),
clientSecret: botAppPassword.toString(),
getBotFrameworkToken: true,
orgId,
kmsService,
microsoftTeamsIntegrationDAL,
microsoftTeamsIntegrationId
});
for await (const channelId of target.channelIds) { for await (const channelId of target.channelIds) {
await teamsBot.sendMessageToChannel(tenantId, channelId, target.teamId, notification); await teamsBot.sendMessageToChannel(botAccessToken, tenantId, channelId, target.teamId, notification);
} }
}; };
@@ -28,6 +28,8 @@ export type TDeleteMicrosoftTeamsIntegrationDTO = {
export type TSendNotificationDTO = { export type TSendNotificationDTO = {
tenantId: string; tenantId: string;
microsoftTeamsIntegrationId: string;
orgId: string;
target: { target: {
teamId: string; teamId: string;
channelIds: string[]; channelIds: string[];
@@ -86,7 +86,6 @@ export const AddWorkflowIntegrationModal = ({ isOpen, onToggle }: Props) => {
return ( return (
<div className="relative" key={platform}> <div className="relative" key={platform}>
<div <div
key={platform}
className={twMerge( className={twMerge(
"flex h-32 w-36 cursor-pointer flex-col items-center space-y-4 rounded border border-mineshaft-500 bg-bunker-600 p-6 transition-all hover:border-primary/70 hover:bg-primary/10 hover:text-white", "flex h-32 w-36 cursor-pointer flex-col items-center space-y-4 rounded border border-mineshaft-500 bg-bunker-600 p-6 transition-all hover:border-primary/70 hover:bg-primary/10 hover:text-white",
isConfigured && "border-primary/50 opacity-60" isConfigured && "border-primary/50 opacity-60"
@@ -110,7 +109,7 @@ export const AddWorkflowIntegrationModal = ({ isOpen, onToggle }: Props) => {
{isConfigured && ( {isConfigured && (
<div className="absolute bottom-0 left-0 right-0 z-30 h-full"> <div className="absolute bottom-0 left-0 right-0 z-30 h-full">
<div className="relative h-full"> <div className="relative h-full">
<div className="absolute bottom-0 right-0 w-full flex-row items-center overflow-hidden whitespace-nowrap rounded-b-md bg-primary px-2 py-0.5 text-center text-xs text-black transition-all duration-300 group-hover:w-0 group-hover:p-0"> <div className="absolute bottom-0 right-0 w-full flex-row items-center overflow-hidden whitespace-nowrap rounded-b-md bg-primary px-2 py-0.5 text-center text-xs text-black">
Already Configured Already Configured
</div> </div>
</div> </div>
@@ -126,7 +125,7 @@ export const AddWorkflowIntegrationModal = ({ isOpen, onToggle }: Props) => {
{wizardStep === WizardSteps.PlatformInputs && {wizardStep === WizardSteps.PlatformInputs &&
selectedPlatform === WorkflowIntegrationPlatform.SLACK && ( selectedPlatform === WorkflowIntegrationPlatform.SLACK && (
<motion.div <motion.div
key="platform-inputs-step" key="platform-inputs-step-slack"
transition={{ duration: 0.1 }} transition={{ duration: 0.1 }}
initial={{ opacity: 0, translateX: 30 }} initial={{ opacity: 0, translateX: 30 }}
animate={{ opacity: 1, translateX: 0 }} animate={{ opacity: 1, translateX: 0 }}
@@ -138,7 +137,7 @@ export const AddWorkflowIntegrationModal = ({ isOpen, onToggle }: Props) => {
{wizardStep === WizardSteps.PlatformInputs && {wizardStep === WizardSteps.PlatformInputs &&
selectedPlatform === WorkflowIntegrationPlatform.MICROSOFT_TEAMS && ( selectedPlatform === WorkflowIntegrationPlatform.MICROSOFT_TEAMS && (
<motion.div <motion.div
key="platform-inputs-step" key="platform-inputs-step-ms-teams"
transition={{ duration: 0.1 }} transition={{ duration: 0.1 }}
initial={{ opacity: 0, translateX: 30 }} initial={{ opacity: 0, translateX: 30 }}
animate={{ opacity: 1, translateX: 0 }} animate={{ opacity: 1, translateX: 0 }}
@@ -2,6 +2,7 @@ import { Modal, ModalContent } from "@app/components/v2";
import { WorkflowIntegrationPlatform } from "@app/hooks/api/workflowIntegrations/types"; import { WorkflowIntegrationPlatform } from "@app/hooks/api/workflowIntegrations/types";
import { SlackIntegrationForm } from "./SlackIntegrationForm"; import { SlackIntegrationForm } from "./SlackIntegrationForm";
import { MicrosoftTeamsIntegrationForm } from "./MicrosoftTeamsIntegrationForm";
type Props = { type Props = {
isOpen?: boolean; isOpen?: boolean;
@@ -23,6 +24,9 @@ export const EditWorkflowIntegrationModal = ({ isOpen, onClose, integration }: P
{integration === WorkflowIntegrationPlatform.SLACK && ( {integration === WorkflowIntegrationPlatform.SLACK && (
<SlackIntegrationForm onClose={handleFormReset} /> <SlackIntegrationForm onClose={handleFormReset} />
)} )}
{integration === WorkflowIntegrationPlatform.MICROSOFT_TEAMS && (
<MicrosoftTeamsIntegrationForm onClose={handleFormReset} />
)}
</ModalContent> </ModalContent>
</Modal> </Modal>
); );
@@ -84,7 +84,7 @@ const formSchema = z
} }
}); });
type TSlackConfigForm = z.infer<typeof formSchema>; type TMicrosoftTeamsConfigForm = z.infer<typeof formSchema>;
type Props = { type Props = {
onClose: () => void; onClose: () => void;
@@ -109,7 +109,7 @@ export const MicrosoftTeamsIntegrationForm = ({ onClose }: Props) => {
handleSubmit, handleSubmit,
setValue, setValue,
formState: { isDirty, isSubmitting } formState: { isDirty, isSubmitting }
} = useForm<TSlackConfigForm>({ } = useForm<TMicrosoftTeamsConfigForm>({
resolver: zodResolver(formSchema), resolver: zodResolver(formSchema),
defaultValues: { defaultValues: {
isAccessRequestNotificationEnabled: false, isAccessRequestNotificationEnabled: false,
@@ -125,7 +125,7 @@ export const MicrosoftTeamsIntegrationForm = ({ onClose }: Props) => {
} }
}); });
const handleIntegrationSave = async (data: TSlackConfigForm) => { const handleIntegrationSave = async (data: TMicrosoftTeamsConfigForm) => {
try { try {
if (!currentWorkspace) { if (!currentWorkspace) {
return; return;
@@ -374,7 +374,7 @@ export const MicrosoftTeamsIntegrationForm = ({ onClose }: Props) => {
: [...(value || []), channel.channelId] : [...(value || []), channel.channelId]
); );
}} }}
key={`secret-requests-slack-channel-${channel.channelId}`} key={`secret-requests-microsoft-teams-channel-${channel.channelId}`}
iconPos="right" iconPos="right"
icon={isChecked && <FontAwesomeIcon icon={faCheckCircle} />} icon={isChecked && <FontAwesomeIcon icon={faCheckCircle} />}
> >