mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 08:42:13 +02:00
Compare commits
4 Commits
master
...
mult-slack
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
268d13786f | ||
|
|
79ab00bc29 | ||
|
|
95a0ddc49f | ||
|
|
9759d839d9 |
@@ -321,6 +321,43 @@ class WorkspaceNotificationRule extends BaseModel {
|
||||
})
|
||||
public workspaceType?: WorkspaceType = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateWorkspaceNotificationRule,
|
||||
],
|
||||
read: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadWorkspaceNotificationRule,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.EditWorkspaceNotificationRule,
|
||||
],
|
||||
})
|
||||
@Index()
|
||||
@TableColumn({
|
||||
type: TableColumnType.ObjectID,
|
||||
required: true,
|
||||
canReadOnRelationQuery: true,
|
||||
title: "Workspace Project Auth Token ID",
|
||||
description:
|
||||
"Workspace project auth token ID for this rule (used when multiple workspaces are connected)",
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.ObjectID,
|
||||
nullable: false,
|
||||
transformer: ObjectID.getDatabaseTransformer(),
|
||||
})
|
||||
public workspaceProjectAuthTokenId?: ObjectID = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [
|
||||
Permission.ProjectAdmin,
|
||||
|
||||
@@ -24,6 +24,7 @@ export interface MiscData {
|
||||
|
||||
export interface SlackMiscData extends MiscData {
|
||||
userId: string;
|
||||
teamId?: string;
|
||||
}
|
||||
|
||||
@TenantColumn("projectId")
|
||||
@@ -153,6 +154,29 @@ class WorkspaceUserAuthToken extends BaseModel {
|
||||
})
|
||||
public workspaceType?: WorkspaceType = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [Permission.CurrentUser],
|
||||
read: [Permission.CurrentUser],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
title: "Workspace Project ID",
|
||||
description:
|
||||
"Project ID in the Workspace (e.g., Slack team ID, Microsoft Teams team ID)",
|
||||
required: false,
|
||||
unique: false,
|
||||
type: TableColumnType.LongText,
|
||||
canReadOnRelationQuery: true,
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.LongText,
|
||||
length: ColumnLength.LongText,
|
||||
unique: false,
|
||||
nullable: true,
|
||||
})
|
||||
@Index()
|
||||
public workspaceProjectId?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [Permission.CurrentUser],
|
||||
read: [Permission.CurrentUser],
|
||||
|
||||
@@ -422,7 +422,25 @@ export default class MicrosoftTeamsAPI {
|
||||
logger.debug("User Profile: ");
|
||||
logger.debug(userProfile);
|
||||
|
||||
await WorkspaceUserAuthTokenService.refreshAuthToken({
|
||||
const existingProjectAuth: WorkspaceProjectAuthToken | null =
|
||||
await WorkspaceProjectAuthTokenService.getProjectAuth({
|
||||
projectId: new ObjectID(projectId),
|
||||
workspaceType: WorkspaceType.MicrosoftTeams,
|
||||
});
|
||||
|
||||
const userAuthData: {
|
||||
projectId: ObjectID;
|
||||
userId: ObjectID;
|
||||
workspaceType: WorkspaceType;
|
||||
authToken: string;
|
||||
workspaceUserId: string;
|
||||
miscData: {
|
||||
userId: string;
|
||||
displayName?: string;
|
||||
email?: string;
|
||||
};
|
||||
workspaceProjectId?: string;
|
||||
} = {
|
||||
projectId: new ObjectID(projectId),
|
||||
userId: new ObjectID(userId),
|
||||
workspaceType: WorkspaceType.MicrosoftTeams,
|
||||
@@ -435,15 +453,16 @@ export default class MicrosoftTeamsAPI {
|
||||
(userProfile["mail"] as string) ||
|
||||
(userProfile["userPrincipalName"] as string),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
if (existingProjectAuth?.workspaceProjectId) {
|
||||
userAuthData.workspaceProjectId =
|
||||
existingProjectAuth.workspaceProjectId;
|
||||
}
|
||||
|
||||
await WorkspaceUserAuthTokenService.refreshAuthToken(userAuthData);
|
||||
|
||||
// Check if admin consent is already granted
|
||||
const existingProjectAuth: WorkspaceProjectAuthToken | null =
|
||||
await WorkspaceProjectAuthTokenService.getProjectAuth({
|
||||
projectId: new ObjectID(projectId),
|
||||
workspaceType: WorkspaceType.MicrosoftTeams,
|
||||
});
|
||||
|
||||
if (
|
||||
existingProjectAuth &&
|
||||
(existingProjectAuth.miscData as any)?.adminConsentGranted
|
||||
@@ -776,6 +795,22 @@ export default class MicrosoftTeamsAPI {
|
||||
miscData: mergedMiscData,
|
||||
});
|
||||
|
||||
await WorkspaceUserAuthTokenService.updateBy({
|
||||
query: {
|
||||
projectId: new ObjectID(projectId),
|
||||
userId: new ObjectID(userId),
|
||||
workspaceType: WorkspaceType.MicrosoftTeams,
|
||||
},
|
||||
data: {
|
||||
workspaceProjectId: tenantId,
|
||||
},
|
||||
skip: 0,
|
||||
limit: 1,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
return Response.redirect(
|
||||
req,
|
||||
res,
|
||||
|
||||
@@ -275,16 +275,39 @@ export default class SlackAPI {
|
||||
},
|
||||
});
|
||||
|
||||
await WorkspaceUserAuthTokenService.refreshAuthToken({
|
||||
const userMiscData: {
|
||||
userId: string;
|
||||
teamId?: string;
|
||||
} = {
|
||||
userId: slackUserId || "",
|
||||
};
|
||||
|
||||
if (slackTeamId) {
|
||||
userMiscData.teamId = slackTeamId;
|
||||
}
|
||||
|
||||
const userAuthData: {
|
||||
projectId: ObjectID;
|
||||
userId: ObjectID;
|
||||
workspaceType: WorkspaceType;
|
||||
authToken: string;
|
||||
workspaceUserId: string;
|
||||
miscData: typeof userMiscData;
|
||||
workspaceProjectId?: string;
|
||||
} = {
|
||||
projectId: new ObjectID(projectId),
|
||||
userId: new ObjectID(userId),
|
||||
workspaceType: WorkspaceType.Slack,
|
||||
authToken: slackUserAccessToken || "",
|
||||
workspaceUserId: slackUserId || "",
|
||||
miscData: {
|
||||
userId: slackUserId || "",
|
||||
},
|
||||
});
|
||||
miscData: userMiscData,
|
||||
};
|
||||
|
||||
if (slackTeamId) {
|
||||
userAuthData.workspaceProjectId = slackTeamId;
|
||||
}
|
||||
|
||||
await WorkspaceUserAuthTokenService.refreshAuthToken(userAuthData);
|
||||
|
||||
// return back to dashboard after successful auth.
|
||||
Response.redirect(req, res, slackIntegrationPageUrl);
|
||||
@@ -441,15 +464,36 @@ export default class SlackAPI {
|
||||
*/
|
||||
|
||||
/*
|
||||
* check if the team id matches the project id.
|
||||
* get project auth.
|
||||
* check if the team id matches the project workspace.
|
||||
* get project auth based on team id.
|
||||
*/
|
||||
|
||||
const teamIdFromSlack: string | undefined =
|
||||
idToken["https://slack.com/team_id"]?.toString();
|
||||
|
||||
// If state is provided, enforce workspace selection.
|
||||
const expectedTeamId: string | undefined =
|
||||
req.query["state"]?.toString();
|
||||
|
||||
if (expectedTeamId && teamIdFromSlack) {
|
||||
if (expectedTeamId !== teamIdFromSlack) {
|
||||
return Response.redirect(
|
||||
req,
|
||||
res,
|
||||
slackIntegrationPageUrl.addQueryParam(
|
||||
"error",
|
||||
"Looks like you are trying to sign in to a different slack workspace. Please try again and sign in to the selected workspace.",
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const projectAuth: WorkspaceProjectAuthToken | null =
|
||||
await WorkspaceProjectAuthTokenService.findOneBy({
|
||||
query: {
|
||||
projectId: new ObjectID(projectId),
|
||||
workspaceType: WorkspaceType.Slack,
|
||||
workspaceProjectId: teamIdFromSlack,
|
||||
},
|
||||
select: {
|
||||
workspaceProjectId: true,
|
||||
@@ -460,19 +504,16 @@ export default class SlackAPI {
|
||||
},
|
||||
});
|
||||
|
||||
// cehck if the workspace project id is same as the team id.
|
||||
// check if the workspace project id is same as the team id.
|
||||
if (projectAuth) {
|
||||
logger.debug("Project Auth: ");
|
||||
logger.debug(projectAuth.workspaceProjectId);
|
||||
logger.debug("Response Team ID: ");
|
||||
logger.debug(idToken["https://slack.com/team_id"]);
|
||||
logger.debug(teamIdFromSlack);
|
||||
logger.debug("Response User ID: ");
|
||||
logger.debug(idToken["https://slack.com/user_id"]);
|
||||
|
||||
if (
|
||||
projectAuth.workspaceProjectId?.toString() !==
|
||||
idToken["https://slack.com/team_id"]?.toString()
|
||||
) {
|
||||
if (projectAuth.workspaceProjectId?.toString() !== teamIdFromSlack) {
|
||||
const teamName: string | undefined = (
|
||||
projectAuth.miscData as SlackMiscData
|
||||
)?.teamName;
|
||||
@@ -495,7 +536,7 @@ export default class SlackAPI {
|
||||
res,
|
||||
slackIntegrationPageUrl.addQueryParam(
|
||||
"error",
|
||||
"Looks like this OneUptime project is not connected to any slack workspace. Please try again and sign in to the workspace",
|
||||
"Looks like this OneUptime project is not connected to the selected slack workspace. Please connect the workspace first.",
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -524,7 +565,9 @@ export default class SlackAPI {
|
||||
workspaceUserId: slackUserId || "",
|
||||
miscData: {
|
||||
userId: slackUserId || "",
|
||||
teamId: teamIdFromSlack || "",
|
||||
},
|
||||
workspaceProjectId: teamIdFromSlack || "",
|
||||
});
|
||||
|
||||
// return back to dashboard after successful auth.
|
||||
@@ -714,12 +757,29 @@ export default class SlackAPI {
|
||||
);
|
||||
}
|
||||
|
||||
const workspaceProjectAuthTokenId: string | undefined =
|
||||
req.query["workspaceProjectAuthTokenId"]?.toString();
|
||||
|
||||
// Get Slack project auth
|
||||
const projectAuthQuery: {
|
||||
projectId: ObjectID;
|
||||
workspaceType: WorkspaceType;
|
||||
workspaceProjectAuthTokenId?: ObjectID;
|
||||
} = {
|
||||
projectId: props.tenantId,
|
||||
workspaceType: WorkspaceType.Slack,
|
||||
};
|
||||
|
||||
if (workspaceProjectAuthTokenId) {
|
||||
projectAuthQuery.workspaceProjectAuthTokenId = new ObjectID(
|
||||
workspaceProjectAuthTokenId,
|
||||
);
|
||||
}
|
||||
|
||||
const projectAuth: WorkspaceProjectAuthToken | null =
|
||||
await WorkspaceProjectAuthTokenService.getProjectAuth({
|
||||
projectId: props.tenantId,
|
||||
workspaceType: WorkspaceType.Slack,
|
||||
});
|
||||
await WorkspaceProjectAuthTokenService.getProjectAuth(
|
||||
projectAuthQuery,
|
||||
);
|
||||
|
||||
if (!projectAuth || !projectAuth.authToken) {
|
||||
return Response.sendErrorResponse(
|
||||
@@ -736,17 +796,28 @@ export default class SlackAPI {
|
||||
let updatedProjectAuth: WorkspaceProjectAuthToken | null = projectAuth;
|
||||
|
||||
if (!(projectAuth.miscData as SlackMiscData)?.channelCache) {
|
||||
await SlackUtil.getAllWorkspaceChannels({
|
||||
const getChannelsData: {
|
||||
authToken: string;
|
||||
projectId: ObjectID;
|
||||
workspaceProjectAuthTokenId?: ObjectID;
|
||||
} = {
|
||||
authToken: projectAuth.authToken,
|
||||
projectId: props.tenantId,
|
||||
});
|
||||
};
|
||||
|
||||
if (workspaceProjectAuthTokenId) {
|
||||
getChannelsData.workspaceProjectAuthTokenId = new ObjectID(
|
||||
workspaceProjectAuthTokenId,
|
||||
);
|
||||
}
|
||||
|
||||
await SlackUtil.getAllWorkspaceChannels(getChannelsData);
|
||||
|
||||
// Re-fetch to return the latest cached object
|
||||
updatedProjectAuth =
|
||||
await WorkspaceProjectAuthTokenService.getProjectAuth({
|
||||
projectId: props.tenantId,
|
||||
workspaceType: WorkspaceType.Slack,
|
||||
});
|
||||
await WorkspaceProjectAuthTokenService.getProjectAuth(
|
||||
projectAuthQuery,
|
||||
);
|
||||
}
|
||||
|
||||
const channelCache: {
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class MigrationName1770919024300 implements MigrationInterface {
|
||||
public name = "MigrationName1770919024300";
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "WorkspaceNotificationRule" ADD "workspaceProjectAuthTokenId" uuid`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "WorkspaceUserAuthToken" ADD "workspaceProjectId" character varying(500)`,
|
||||
);
|
||||
|
||||
await queryRunner.query(
|
||||
`WITH latest_auth AS (
|
||||
SELECT DISTINCT ON ("projectId", "workspaceType")
|
||||
"_id",
|
||||
"projectId",
|
||||
"workspaceType",
|
||||
"workspaceProjectId"
|
||||
FROM "WorkspaceProjectAuthToken"
|
||||
ORDER BY "projectId", "workspaceType", "createdAt" DESC
|
||||
)
|
||||
UPDATE "WorkspaceNotificationRule" AS r
|
||||
SET "workspaceProjectAuthTokenId" = latest_auth."_id"
|
||||
FROM latest_auth
|
||||
WHERE r."projectId" = latest_auth."projectId"
|
||||
AND r."workspaceType" = latest_auth."workspaceType"`,
|
||||
);
|
||||
|
||||
await queryRunner.query(
|
||||
`WITH latest_auth AS (
|
||||
SELECT DISTINCT ON ("projectId", "workspaceType")
|
||||
"projectId",
|
||||
"workspaceType",
|
||||
"workspaceProjectId"
|
||||
FROM "WorkspaceProjectAuthToken"
|
||||
ORDER BY "projectId", "workspaceType", "createdAt" DESC
|
||||
)
|
||||
UPDATE "WorkspaceUserAuthToken" AS u
|
||||
SET "workspaceProjectId" = latest_auth."workspaceProjectId"
|
||||
FROM latest_auth
|
||||
WHERE u."projectId" = latest_auth."projectId"
|
||||
AND u."workspaceType" = latest_auth."workspaceType"`,
|
||||
);
|
||||
|
||||
await queryRunner.query(
|
||||
`DELETE FROM "WorkspaceNotificationRule" WHERE "workspaceProjectAuthTokenId" IS NULL`,
|
||||
);
|
||||
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "WorkspaceNotificationRule" ALTER COLUMN "workspaceProjectAuthTokenId" SET NOT NULL`,
|
||||
);
|
||||
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_workspace_notification_rule_workspace_project_auth_token_id" ON "WorkspaceNotificationRule" ("workspaceProjectAuthTokenId")`,
|
||||
);
|
||||
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_workspace_user_auth_token_workspace_project_id" ON "WorkspaceUserAuthToken" ("workspaceProjectId")`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`DROP INDEX "IDX_workspace_user_auth_token_workspace_project_id"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`DROP INDEX "IDX_workspace_notification_rule_workspace_project_auth_token_id"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "WorkspaceNotificationRule" DROP COLUMN "workspaceProjectAuthTokenId"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "WorkspaceUserAuthToken" DROP COLUMN "workspaceProjectId"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class MigrationName1770922660423 implements MigrationInterface {
|
||||
public name = "MigrationName1770922660423";
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`DROP INDEX "public"."IDX_workspace_user_auth_token_workspace_project_id"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`DROP INDEX "public"."IDX_workspace_notification_rule_workspace_project_auth_token_id"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type":"Recurring","value":{"intervalType":"Day","intervalCount":{"_type":"PositiveNumber","value":1}}}'`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type":"RestrictionTimes","value":{"restictionType":"None","dayRestrictionTimes":null,"weeklyRestrictionTimes":[]}}'`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_50f3ab2c779757f0f72733b9f5" ON "WorkspaceUserAuthToken" ("workspaceProjectId") `,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_5691297e1b384944dea798b07a" ON "WorkspaceNotificationRule" ("workspaceProjectAuthTokenId") `,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`DROP INDEX "public"."IDX_5691297e1b384944dea798b07a"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`DROP INDEX "public"."IDX_50f3ab2c779757f0f72733b9f5"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type": "RestrictionTimes", "value": {"restictionType": "None", "dayRestrictionTimes": null, "weeklyRestrictionTimes": []}}'`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type": "Recurring", "value": {"intervalType": "Day", "intervalCount": {"_type": "PositiveNumber", "value": 1}}}'`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_workspace_notification_rule_workspace_project_auth_token_id" ON "WorkspaceNotificationRule" ("workspaceProjectAuthTokenId") `,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_workspace_user_auth_token_workspace_project_id" ON "WorkspaceUserAuthToken" ("workspaceProjectId") `,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -258,6 +258,8 @@ import { MigrationName1770728946893 } from "./1770728946893-MigrationName";
|
||||
import { MigrationName1770732721195 } from "./1770732721195-MigrationName";
|
||||
import { MigrationName1770833704656 } from "./1770833704656-MigrationName";
|
||||
import { MigrationName1770834237090 } from "./1770834237090-MigrationName";
|
||||
import { MigrationName1770919024300 } from "./1770919024300-MigrationName";
|
||||
import { MigrationName1770922660423 } from "./1770922660423-MigrationName";
|
||||
|
||||
export default [
|
||||
InitialMigration,
|
||||
@@ -520,4 +522,6 @@ export default [
|
||||
MigrationName1770732721195,
|
||||
MigrationName1770833704656,
|
||||
MigrationName1770834237090,
|
||||
MigrationName1770919024300,
|
||||
MigrationName1770922660423,
|
||||
];
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -17,6 +17,8 @@ export class Service extends DatabaseService<Model> {
|
||||
public async getProjectAuth(data: {
|
||||
projectId: ObjectID;
|
||||
workspaceType: WorkspaceType;
|
||||
workspaceProjectAuthTokenId?: ObjectID;
|
||||
workspaceProjectId?: string;
|
||||
}): Promise<Model | null> {
|
||||
if (!data.projectId) {
|
||||
throw new BadDataException("projectId is required");
|
||||
@@ -26,12 +28,30 @@ export class Service extends DatabaseService<Model> {
|
||||
throw new BadDataException("workspaceType is required");
|
||||
}
|
||||
|
||||
const query: {
|
||||
projectId: ObjectID;
|
||||
workspaceType: WorkspaceType;
|
||||
_id?: ObjectID;
|
||||
workspaceProjectId?: string;
|
||||
} = {
|
||||
projectId: data.projectId,
|
||||
workspaceType: data.workspaceType,
|
||||
};
|
||||
|
||||
if (data.workspaceProjectAuthTokenId) {
|
||||
query._id = data.workspaceProjectAuthTokenId;
|
||||
}
|
||||
|
||||
if (data.workspaceProjectId) {
|
||||
query.workspaceProjectId = data.workspaceProjectId;
|
||||
}
|
||||
|
||||
return await this.findOneBy({
|
||||
query: {
|
||||
projectId: data.projectId,
|
||||
workspaceType: data.workspaceType,
|
||||
...query,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
authToken: true,
|
||||
workspaceProjectId: true,
|
||||
miscData: true,
|
||||
@@ -46,16 +66,29 @@ export class Service extends DatabaseService<Model> {
|
||||
@CaptureSpan()
|
||||
public async getProjectAuths(data: {
|
||||
projectId: ObjectID;
|
||||
workspaceType?: WorkspaceType;
|
||||
}): Promise<Array<Model>> {
|
||||
if (!data.projectId) {
|
||||
throw new BadDataException("projectId is required");
|
||||
}
|
||||
|
||||
const query: {
|
||||
projectId: ObjectID;
|
||||
workspaceType?: WorkspaceType;
|
||||
} = {
|
||||
projectId: data.projectId,
|
||||
};
|
||||
|
||||
if (data.workspaceType) {
|
||||
query.workspaceType = data.workspaceType;
|
||||
}
|
||||
|
||||
return await this.findBy({
|
||||
query: {
|
||||
projectId: data.projectId,
|
||||
...query,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
authToken: true,
|
||||
workspaceProjectId: true,
|
||||
miscData: true,
|
||||
@@ -84,6 +117,7 @@ export class Service extends DatabaseService<Model> {
|
||||
authToken: string;
|
||||
workspaceProjectId: string;
|
||||
miscData: WorkspaceMiscData;
|
||||
workspaceProjectAuthTokenId?: ObjectID;
|
||||
}): Promise<void> {
|
||||
if (!data.projectId) {
|
||||
throw new BadDataException("projectId is required");
|
||||
@@ -105,11 +139,26 @@ export class Service extends DatabaseService<Model> {
|
||||
throw new BadDataException("miscData is required");
|
||||
}
|
||||
|
||||
const query: {
|
||||
projectId: ObjectID;
|
||||
workspaceType: WorkspaceType;
|
||||
workspaceProjectId?: string;
|
||||
_id?: ObjectID;
|
||||
} = {
|
||||
projectId: data.projectId,
|
||||
workspaceType: data.workspaceType,
|
||||
};
|
||||
|
||||
if (data.workspaceProjectId) {
|
||||
query.workspaceProjectId = data.workspaceProjectId;
|
||||
}
|
||||
|
||||
if (data.workspaceProjectAuthTokenId) {
|
||||
query._id = data.workspaceProjectAuthTokenId;
|
||||
}
|
||||
|
||||
let projectAuth: Model | null = await this.findOneBy({
|
||||
query: {
|
||||
projectId: data.projectId,
|
||||
workspaceType: data.workspaceType,
|
||||
},
|
||||
query: query,
|
||||
select: {
|
||||
_id: true,
|
||||
},
|
||||
|
||||
@@ -16,18 +16,31 @@ export class Service extends DatabaseService<Model> {
|
||||
projectId: ObjectID;
|
||||
userId: ObjectID;
|
||||
workspaceType: WorkspaceType;
|
||||
workspaceProjectId?: string;
|
||||
}): Promise<Model | null> {
|
||||
const query: {
|
||||
userId: ObjectID;
|
||||
projectId: ObjectID;
|
||||
workspaceType: WorkspaceType;
|
||||
workspaceProjectId?: string;
|
||||
} = {
|
||||
userId: data.userId,
|
||||
projectId: data.projectId,
|
||||
workspaceType: data.workspaceType,
|
||||
};
|
||||
|
||||
if (data.workspaceProjectId) {
|
||||
query.workspaceProjectId = data.workspaceProjectId;
|
||||
}
|
||||
|
||||
return await this.findOneBy({
|
||||
query: {
|
||||
userId: data.userId,
|
||||
projectId: data.projectId,
|
||||
workspaceType: data.workspaceType,
|
||||
},
|
||||
query: query,
|
||||
select: {
|
||||
authToken: true,
|
||||
workspaceUserId: true,
|
||||
miscData: true,
|
||||
workspaceType: true,
|
||||
workspaceProjectId: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
@@ -40,15 +53,27 @@ export class Service extends DatabaseService<Model> {
|
||||
projectId: ObjectID;
|
||||
userId: ObjectID;
|
||||
workspaceType: WorkspaceType;
|
||||
workspaceProjectId?: string;
|
||||
}): Promise<boolean> {
|
||||
const query: {
|
||||
projectId: ObjectID;
|
||||
userId: ObjectID;
|
||||
workspaceType: WorkspaceType;
|
||||
workspaceProjectId?: string;
|
||||
} = {
|
||||
projectId: data.projectId,
|
||||
userId: data.userId,
|
||||
workspaceType: data.workspaceType,
|
||||
};
|
||||
|
||||
if (data.workspaceProjectId) {
|
||||
query.workspaceProjectId = data.workspaceProjectId;
|
||||
}
|
||||
|
||||
return (
|
||||
(
|
||||
await this.countBy({
|
||||
query: {
|
||||
projectId: data.projectId,
|
||||
userId: data.userId,
|
||||
workspaceType: data.workspaceType,
|
||||
},
|
||||
query: query,
|
||||
skip: 0,
|
||||
limit: 1,
|
||||
props: {
|
||||
@@ -67,13 +92,25 @@ export class Service extends DatabaseService<Model> {
|
||||
authToken: string;
|
||||
workspaceUserId: string;
|
||||
miscData: SlackMiscData;
|
||||
workspaceProjectId?: string;
|
||||
}): Promise<void> {
|
||||
const query: {
|
||||
projectId: ObjectID;
|
||||
userId: ObjectID;
|
||||
workspaceType: WorkspaceType;
|
||||
workspaceProjectId?: string;
|
||||
} = {
|
||||
projectId: data.projectId,
|
||||
userId: data.userId,
|
||||
workspaceType: data.workspaceType,
|
||||
};
|
||||
|
||||
if (data.workspaceProjectId) {
|
||||
query.workspaceProjectId = data.workspaceProjectId;
|
||||
}
|
||||
|
||||
let userAuth: Model | null = await this.findOneBy({
|
||||
query: {
|
||||
projectId: data.projectId,
|
||||
userId: data.userId,
|
||||
workspaceType: data.workspaceType,
|
||||
},
|
||||
query: query,
|
||||
select: {
|
||||
_id: true,
|
||||
},
|
||||
@@ -92,6 +129,10 @@ export class Service extends DatabaseService<Model> {
|
||||
userAuth.workspaceUserId = data.workspaceUserId;
|
||||
userAuth.miscData = data.miscData;
|
||||
|
||||
if (data.workspaceProjectId !== undefined) {
|
||||
userAuth.workspaceProjectId = data.workspaceProjectId;
|
||||
}
|
||||
|
||||
await this.create({
|
||||
data: userAuth,
|
||||
props: {
|
||||
@@ -105,6 +146,9 @@ export class Service extends DatabaseService<Model> {
|
||||
authToken: data.authToken,
|
||||
workspaceUserId: data.workspaceUserId,
|
||||
miscData: data.miscData,
|
||||
...(data.workspaceProjectId !== undefined && {
|
||||
workspaceProjectId: data.workspaceProjectId,
|
||||
}),
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
|
||||
@@ -892,6 +892,7 @@ export default class MicrosoftTeamsUtil extends WorkspaceBase {
|
||||
channelName: string;
|
||||
projectId: ObjectID;
|
||||
teamId: string;
|
||||
workspaceProjectAuthTokenId?: ObjectID;
|
||||
}): Promise<WorkspaceChannel> {
|
||||
const channel: WorkspaceChannel | null =
|
||||
await this.getWorkspaceChannelByName({
|
||||
@@ -1547,6 +1548,7 @@ export default class MicrosoftTeamsUtil extends WorkspaceBase {
|
||||
authToken: string;
|
||||
projectId: ObjectID;
|
||||
teamId: string;
|
||||
workspaceProjectAuthTokenId?: ObjectID;
|
||||
}): Promise<Dictionary<WorkspaceChannel>> {
|
||||
logger.debug("Getting all workspace channels for team ID: " + data.teamId);
|
||||
|
||||
@@ -1600,6 +1602,7 @@ export default class MicrosoftTeamsUtil extends WorkspaceBase {
|
||||
channelName: string;
|
||||
projectId: ObjectID;
|
||||
teamId?: string;
|
||||
workspaceProjectAuthTokenId?: ObjectID;
|
||||
}): Promise<boolean> {
|
||||
if (!data.teamId) {
|
||||
throw new BadDataException(
|
||||
|
||||
@@ -435,6 +435,7 @@ export default class SlackUtil extends WorkspaceBase {
|
||||
authToken: string;
|
||||
channelNames: Array<string>;
|
||||
projectId: ObjectID;
|
||||
workspaceProjectAuthTokenId?: ObjectID;
|
||||
}): Promise<Array<WorkspaceChannel>> {
|
||||
logger.debug("Creating channels if they do not exist with data:");
|
||||
logger.debug(data);
|
||||
@@ -450,12 +451,24 @@ export default class SlackUtil extends WorkspaceBase {
|
||||
channelName = channelName.replace(/\s+/g, "-");
|
||||
|
||||
// Check if channel exists using optimized method
|
||||
const getChannelData: {
|
||||
authToken: string;
|
||||
channelName: string;
|
||||
projectId: ObjectID;
|
||||
workspaceProjectAuthTokenId?: ObjectID;
|
||||
} = {
|
||||
authToken: data.authToken,
|
||||
channelName: channelName,
|
||||
projectId: data.projectId,
|
||||
};
|
||||
|
||||
if (data.workspaceProjectAuthTokenId) {
|
||||
getChannelData.workspaceProjectAuthTokenId =
|
||||
data.workspaceProjectAuthTokenId;
|
||||
}
|
||||
|
||||
const existingChannel: WorkspaceChannel | null =
|
||||
await this.getWorkspaceChannelByName({
|
||||
authToken: data.authToken,
|
||||
channelName: channelName,
|
||||
projectId: data.projectId,
|
||||
});
|
||||
await this.getWorkspaceChannelByName(getChannelData);
|
||||
|
||||
if (existingChannel) {
|
||||
logger.debug(`Channel ${channelName} already exists.`);
|
||||
@@ -486,16 +499,29 @@ export default class SlackUtil extends WorkspaceBase {
|
||||
authToken: string;
|
||||
channelName: string;
|
||||
projectId: ObjectID;
|
||||
workspaceProjectAuthTokenId?: ObjectID;
|
||||
}): Promise<WorkspaceChannel> {
|
||||
logger.debug("Getting workspace channel ID from channel name with data:");
|
||||
logger.debug(data);
|
||||
|
||||
const channelLookupData: {
|
||||
authToken: string;
|
||||
channelName: string;
|
||||
projectId: ObjectID;
|
||||
workspaceProjectAuthTokenId?: ObjectID;
|
||||
} = {
|
||||
authToken: data.authToken,
|
||||
channelName: data.channelName,
|
||||
projectId: data.projectId,
|
||||
};
|
||||
|
||||
if (data.workspaceProjectAuthTokenId) {
|
||||
channelLookupData.workspaceProjectAuthTokenId =
|
||||
data.workspaceProjectAuthTokenId;
|
||||
}
|
||||
|
||||
const channel: WorkspaceChannel | null =
|
||||
await this.getWorkspaceChannelByName({
|
||||
authToken: data.authToken,
|
||||
channelName: data.channelName,
|
||||
projectId: data.projectId,
|
||||
});
|
||||
await this.getWorkspaceChannelByName(channelLookupData);
|
||||
|
||||
if (!channel) {
|
||||
logger.error("Channel not found.");
|
||||
@@ -576,6 +602,7 @@ export default class SlackUtil extends WorkspaceBase {
|
||||
public static override async getAllWorkspaceChannels(data: {
|
||||
authToken: string;
|
||||
projectId: ObjectID;
|
||||
workspaceProjectAuthTokenId?: ObjectID;
|
||||
}): Promise<Dictionary<WorkspaceChannel>> {
|
||||
logger.debug("Getting all workspace channels with data:");
|
||||
logger.debug(data);
|
||||
@@ -660,10 +687,21 @@ export default class SlackUtil extends WorkspaceBase {
|
||||
|
||||
// Update cache in bulk
|
||||
try {
|
||||
await this.updateChannelsInCache({
|
||||
const updateCacheData: {
|
||||
projectId: ObjectID;
|
||||
channelCache: Dictionary<WorkspaceChannel>;
|
||||
workspaceProjectAuthTokenId?: ObjectID;
|
||||
} = {
|
||||
projectId: data.projectId,
|
||||
channelCache: localChannelCache,
|
||||
});
|
||||
};
|
||||
|
||||
if (data.workspaceProjectAuthTokenId) {
|
||||
updateCacheData.workspaceProjectAuthTokenId =
|
||||
data.workspaceProjectAuthTokenId;
|
||||
}
|
||||
|
||||
await this.updateChannelsInCache(updateCacheData);
|
||||
} catch (error) {
|
||||
logger.error("Error bulk updating channel cache:");
|
||||
logger.error(error);
|
||||
@@ -677,12 +715,24 @@ export default class SlackUtil extends WorkspaceBase {
|
||||
private static async updateChannelsInCache(data: {
|
||||
projectId: ObjectID;
|
||||
channelCache: Dictionary<WorkspaceChannel>;
|
||||
workspaceProjectAuthTokenId?: ObjectID;
|
||||
}): Promise<void> {
|
||||
const projectAuthQuery: {
|
||||
projectId: ObjectID;
|
||||
workspaceType: WorkspaceType;
|
||||
workspaceProjectAuthTokenId?: ObjectID;
|
||||
} = {
|
||||
projectId: data.projectId,
|
||||
workspaceType: WorkspaceType.Slack,
|
||||
};
|
||||
|
||||
if (data.workspaceProjectAuthTokenId) {
|
||||
projectAuthQuery.workspaceProjectAuthTokenId =
|
||||
data.workspaceProjectAuthTokenId;
|
||||
}
|
||||
|
||||
const projectAuth: any =
|
||||
await WorkspaceProjectAuthTokenService.getProjectAuth({
|
||||
projectId: data.projectId,
|
||||
workspaceType: WorkspaceType.Slack,
|
||||
});
|
||||
await WorkspaceProjectAuthTokenService.getProjectAuth(projectAuthQuery);
|
||||
|
||||
if (!projectAuth) {
|
||||
logger.debug("No project auth found, cannot update cache");
|
||||
@@ -705,6 +755,9 @@ export default class SlackUtil extends WorkspaceBase {
|
||||
authToken: projectAuth.authToken,
|
||||
workspaceProjectId: projectAuth.workspaceProjectId,
|
||||
miscData: miscData,
|
||||
...(data.workspaceProjectAuthTokenId && {
|
||||
workspaceProjectAuthTokenId: data.workspaceProjectAuthTokenId,
|
||||
}),
|
||||
});
|
||||
|
||||
logger.debug("Channel cache updated successfully");
|
||||
@@ -714,15 +767,27 @@ export default class SlackUtil extends WorkspaceBase {
|
||||
public static async getChannelFromCache(data: {
|
||||
projectId: ObjectID;
|
||||
channelName: string;
|
||||
workspaceProjectAuthTokenId?: ObjectID;
|
||||
}): Promise<WorkspaceChannel | null> {
|
||||
logger.debug("Getting channel from cache with data:");
|
||||
logger.debug(data);
|
||||
|
||||
const cacheAuthQuery: {
|
||||
projectId: ObjectID;
|
||||
workspaceType: WorkspaceType;
|
||||
workspaceProjectAuthTokenId?: ObjectID;
|
||||
} = {
|
||||
projectId: data.projectId,
|
||||
workspaceType: WorkspaceType.Slack,
|
||||
};
|
||||
|
||||
if (data.workspaceProjectAuthTokenId) {
|
||||
cacheAuthQuery.workspaceProjectAuthTokenId =
|
||||
data.workspaceProjectAuthTokenId;
|
||||
}
|
||||
|
||||
const projectAuth: any =
|
||||
await WorkspaceProjectAuthTokenService.getProjectAuth({
|
||||
projectId: data.projectId,
|
||||
workspaceType: WorkspaceType.Slack,
|
||||
});
|
||||
await WorkspaceProjectAuthTokenService.getProjectAuth(cacheAuthQuery);
|
||||
|
||||
if (!projectAuth || !projectAuth.miscData) {
|
||||
logger.debug("No project auth found or no misc data");
|
||||
@@ -755,15 +820,29 @@ export default class SlackUtil extends WorkspaceBase {
|
||||
projectId: ObjectID;
|
||||
channelName: string;
|
||||
channel: WorkspaceChannel;
|
||||
workspaceProjectAuthTokenId?: ObjectID;
|
||||
}): Promise<void> {
|
||||
logger.debug("Updating channel cache with data:");
|
||||
logger.debug(data);
|
||||
|
||||
const updateCacheAuthQuery: {
|
||||
projectId: ObjectID;
|
||||
workspaceType: WorkspaceType;
|
||||
workspaceProjectAuthTokenId?: ObjectID;
|
||||
} = {
|
||||
projectId: data.projectId,
|
||||
workspaceType: WorkspaceType.Slack,
|
||||
};
|
||||
|
||||
if (data.workspaceProjectAuthTokenId) {
|
||||
updateCacheAuthQuery.workspaceProjectAuthTokenId =
|
||||
data.workspaceProjectAuthTokenId;
|
||||
}
|
||||
|
||||
const projectAuth: any =
|
||||
await WorkspaceProjectAuthTokenService.getProjectAuth({
|
||||
projectId: data.projectId,
|
||||
workspaceType: WorkspaceType.Slack,
|
||||
});
|
||||
await WorkspaceProjectAuthTokenService.getProjectAuth(
|
||||
updateCacheAuthQuery,
|
||||
);
|
||||
|
||||
if (!projectAuth) {
|
||||
logger.debug("No project auth found, cannot update cache");
|
||||
@@ -790,6 +869,9 @@ export default class SlackUtil extends WorkspaceBase {
|
||||
authToken: projectAuth.authToken,
|
||||
workspaceProjectId: projectAuth.workspaceProjectId,
|
||||
miscData: miscData,
|
||||
...(data.workspaceProjectAuthTokenId && {
|
||||
workspaceProjectAuthTokenId: data.workspaceProjectAuthTokenId,
|
||||
}),
|
||||
});
|
||||
|
||||
logger.debug("Channel cache updated successfully");
|
||||
@@ -800,6 +882,7 @@ export default class SlackUtil extends WorkspaceBase {
|
||||
authToken: string;
|
||||
channelName: string;
|
||||
projectId: ObjectID;
|
||||
workspaceProjectAuthTokenId?: ObjectID;
|
||||
}): Promise<WorkspaceChannel | null> {
|
||||
logger.debug("Getting workspace channel by name with data:");
|
||||
logger.debug(data);
|
||||
@@ -813,11 +896,22 @@ export default class SlackUtil extends WorkspaceBase {
|
||||
|
||||
// Try to get from cache first
|
||||
try {
|
||||
const cachedChannelLookup: {
|
||||
projectId: ObjectID;
|
||||
channelName: string;
|
||||
workspaceProjectAuthTokenId?: ObjectID;
|
||||
} = {
|
||||
projectId: data.projectId,
|
||||
channelName: normalizedChannelName,
|
||||
};
|
||||
|
||||
if (data.workspaceProjectAuthTokenId) {
|
||||
cachedChannelLookup.workspaceProjectAuthTokenId =
|
||||
data.workspaceProjectAuthTokenId;
|
||||
}
|
||||
|
||||
const cachedChannel: WorkspaceChannel | null =
|
||||
await this.getChannelFromCache({
|
||||
projectId: data.projectId,
|
||||
channelName: normalizedChannelName,
|
||||
});
|
||||
await this.getChannelFromCache(cachedChannelLookup);
|
||||
if (cachedChannel) {
|
||||
logger.debug("Channel found in cache:");
|
||||
logger.debug(cachedChannel);
|
||||
@@ -909,10 +1003,21 @@ export default class SlackUtil extends WorkspaceBase {
|
||||
|
||||
// Update cache before returning
|
||||
try {
|
||||
await this.updateChannelsInCache({
|
||||
const updateCacheData: {
|
||||
projectId: ObjectID;
|
||||
channelCache: Dictionary<WorkspaceChannel>;
|
||||
workspaceProjectAuthTokenId?: ObjectID;
|
||||
} = {
|
||||
projectId: data.projectId,
|
||||
channelCache: localChannelCache,
|
||||
});
|
||||
};
|
||||
|
||||
if (data.workspaceProjectAuthTokenId) {
|
||||
updateCacheData.workspaceProjectAuthTokenId =
|
||||
data.workspaceProjectAuthTokenId;
|
||||
}
|
||||
|
||||
await this.updateChannelsInCache(updateCacheData);
|
||||
} catch (error) {
|
||||
logger.error("Error bulk updating channel cache:");
|
||||
logger.error(error);
|
||||
@@ -931,10 +1036,21 @@ export default class SlackUtil extends WorkspaceBase {
|
||||
|
||||
// Update cache even if channel not found
|
||||
try {
|
||||
await this.updateChannelsInCache({
|
||||
const updateCacheData: {
|
||||
projectId: ObjectID;
|
||||
channelCache: Dictionary<WorkspaceChannel>;
|
||||
workspaceProjectAuthTokenId?: ObjectID;
|
||||
} = {
|
||||
projectId: data.projectId,
|
||||
channelCache: localChannelCache,
|
||||
});
|
||||
};
|
||||
|
||||
if (data.workspaceProjectAuthTokenId) {
|
||||
updateCacheData.workspaceProjectAuthTokenId =
|
||||
data.workspaceProjectAuthTokenId;
|
||||
}
|
||||
|
||||
await this.updateChannelsInCache(updateCacheData);
|
||||
} catch (error) {
|
||||
logger.error("Error bulk updating channel cache:");
|
||||
logger.error(error);
|
||||
@@ -1013,6 +1129,7 @@ export default class SlackUtil extends WorkspaceBase {
|
||||
authToken: string;
|
||||
channelName: string;
|
||||
projectId: ObjectID;
|
||||
workspaceProjectAuthTokenId?: ObjectID;
|
||||
}): Promise<boolean> {
|
||||
// if channel name starts with #, remove it
|
||||
if (data.channelName && data.channelName.startsWith("#")) {
|
||||
@@ -1023,12 +1140,24 @@ export default class SlackUtil extends WorkspaceBase {
|
||||
data.channelName = data.channelName.toLowerCase();
|
||||
|
||||
// Check if channel exists using optimized method
|
||||
const channelLookup: {
|
||||
authToken: string;
|
||||
channelName: string;
|
||||
projectId: ObjectID;
|
||||
workspaceProjectAuthTokenId?: ObjectID;
|
||||
} = {
|
||||
authToken: data.authToken,
|
||||
channelName: data.channelName,
|
||||
projectId: data.projectId,
|
||||
};
|
||||
|
||||
if (data.workspaceProjectAuthTokenId) {
|
||||
channelLookup.workspaceProjectAuthTokenId =
|
||||
data.workspaceProjectAuthTokenId;
|
||||
}
|
||||
|
||||
const channel: WorkspaceChannel | null =
|
||||
await this.getWorkspaceChannelByName({
|
||||
authToken: data.authToken,
|
||||
channelName: data.channelName,
|
||||
projectId: data.projectId,
|
||||
});
|
||||
await this.getWorkspaceChannelByName(channelLookup);
|
||||
|
||||
return channel !== null;
|
||||
}
|
||||
@@ -1059,12 +1188,25 @@ export default class SlackUtil extends WorkspaceBase {
|
||||
channelName = channelName.substring(1);
|
||||
}
|
||||
|
||||
const channelLookup: {
|
||||
authToken: string;
|
||||
channelName: string;
|
||||
projectId: ObjectID;
|
||||
workspaceProjectAuthTokenId?: ObjectID;
|
||||
} = {
|
||||
authToken: data.authToken,
|
||||
channelName: channelName,
|
||||
projectId: data.projectId,
|
||||
};
|
||||
|
||||
if (data.workspaceMessagePayload.workspaceProjectAuthTokenId) {
|
||||
channelLookup.workspaceProjectAuthTokenId = new ObjectID(
|
||||
data.workspaceMessagePayload.workspaceProjectAuthTokenId,
|
||||
);
|
||||
}
|
||||
|
||||
const channel: WorkspaceChannel | null =
|
||||
await this.getWorkspaceChannelByName({
|
||||
authToken: data.authToken,
|
||||
channelName: channelName,
|
||||
projectId: data.projectId,
|
||||
});
|
||||
await this.getWorkspaceChannelByName(channelLookup);
|
||||
|
||||
if (channel) {
|
||||
workspaceChannelsToPostTo.push(channel);
|
||||
|
||||
@@ -163,11 +163,27 @@ export default class WorkspaceUtil {
|
||||
const responses: Array<WorkspaceSendMessageResponse> = [];
|
||||
|
||||
for (const messagePayloadByWorkspace of data.messagePayloadsByWorkspace) {
|
||||
const workspaceProjectAuthTokenId: ObjectID | undefined =
|
||||
messagePayloadByWorkspace.workspaceProjectAuthTokenId
|
||||
? new ObjectID(messagePayloadByWorkspace.workspaceProjectAuthTokenId)
|
||||
: undefined;
|
||||
|
||||
const projectAuthQuery: {
|
||||
projectId: ObjectID;
|
||||
workspaceType: WorkspaceType;
|
||||
workspaceProjectAuthTokenId?: ObjectID;
|
||||
} = {
|
||||
projectId: data.projectId,
|
||||
workspaceType: messagePayloadByWorkspace.workspaceType,
|
||||
};
|
||||
|
||||
if (workspaceProjectAuthTokenId) {
|
||||
projectAuthQuery.workspaceProjectAuthTokenId =
|
||||
workspaceProjectAuthTokenId;
|
||||
}
|
||||
|
||||
const projectAuthToken: WorkspaceProjectAuthToken | null =
|
||||
await WorkspaceProjectAuthTokenService.getProjectAuth({
|
||||
projectId: data.projectId,
|
||||
workspaceType: messagePayloadByWorkspace.workspaceType,
|
||||
});
|
||||
await WorkspaceProjectAuthTokenService.getProjectAuth(projectAuthQuery);
|
||||
|
||||
if (!projectAuthToken) {
|
||||
responses.push({
|
||||
|
||||
@@ -43,6 +43,7 @@ export interface WorkspaceChannel {
|
||||
name: string;
|
||||
workspaceType: WorkspaceType;
|
||||
teamId?: string; // Required for Microsoft Teams
|
||||
workspaceProjectAuthTokenId?: string; // Optional: link to project auth token for multi-workspace support
|
||||
}
|
||||
|
||||
export default class WorkspaceBase {
|
||||
@@ -61,6 +62,7 @@ export default class WorkspaceBase {
|
||||
channelName: string;
|
||||
projectId: ObjectID;
|
||||
teamId?: string;
|
||||
workspaceProjectAuthTokenId?: ObjectID;
|
||||
}): Promise<boolean> {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
@@ -219,6 +221,7 @@ export default class WorkspaceBase {
|
||||
public static async getAllWorkspaceChannels(_data: {
|
||||
authToken: string;
|
||||
projectId: ObjectID;
|
||||
workspaceProjectAuthTokenId?: ObjectID;
|
||||
}): Promise<Dictionary<WorkspaceChannel>> {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
@@ -228,6 +231,7 @@ export default class WorkspaceBase {
|
||||
authToken: string;
|
||||
channelName: string;
|
||||
projectId: ObjectID;
|
||||
workspaceProjectAuthTokenId?: ObjectID;
|
||||
}): Promise<WorkspaceChannel> {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@@ -3,4 +3,5 @@ import { WorkspaceChannel } from "../../../Server/Utils/Workspace/WorkspaceBase"
|
||||
export default interface NotificationRuleWorkspaceChannel
|
||||
extends WorkspaceChannel {
|
||||
notificationRuleId: string;
|
||||
workspaceProjectAuthTokenId?: string;
|
||||
}
|
||||
|
||||
@@ -106,4 +106,5 @@ export default interface WorkspaceMessagePayload {
|
||||
messageBlocks: Array<WorkspaceMessageBlock>; // Message to add to blocks.
|
||||
workspaceType: WorkspaceType;
|
||||
teamId?: string | undefined; // Team ID for Microsoft Teams
|
||||
workspaceProjectAuthTokenId?: string | undefined; // Workspace project auth token to use (for multi-workspace support)
|
||||
}
|
||||
|
||||
@@ -40,6 +40,9 @@ const SlackChannelCacheModal: FunctionComponent<ComponentProps> = (
|
||||
await API.get({
|
||||
url: URL.fromString(
|
||||
`${HOME_URL.toString()}/api/slack/get-all-channels`,
|
||||
).addQueryParam(
|
||||
"workspaceProjectAuthTokenId",
|
||||
props.projectAuthTokenId.toString(),
|
||||
),
|
||||
headers: ModelAPI.getCommonHeaders(),
|
||||
});
|
||||
|
||||
@@ -30,6 +30,7 @@ import ListResult from "Common/Types/BaseDatabase/ListResult";
|
||||
import WorkspaceUserAuthToken from "Common/Models/DatabaseModels/WorkspaceUserAuthToken";
|
||||
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
|
||||
import WorkspaceType from "Common/Types/Workspace/WorkspaceType";
|
||||
import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax";
|
||||
import SlackIntegrationDocumentation from "./SlackIntegrationDocumentation";
|
||||
import Link from "Common/UI/Components/Link/Link";
|
||||
import SlackChannelCacheModal from "./SlackChannelCacheModal";
|
||||
@@ -47,18 +48,19 @@ const SlackIntegration: FunctionComponent<ComponentProps> = (
|
||||
const [isLoading, setIsLoading] = React.useState<boolean>(true);
|
||||
|
||||
const [manifest, setManifest] = React.useState<JSONObject | null>(null);
|
||||
const [isUserAccountConnected, setIsUserAccountConnected] =
|
||||
React.useState<boolean>(false);
|
||||
const [userAuthTokenId, setWorkspaceUserAuthTokenId] =
|
||||
React.useState<ObjectID | null>(null);
|
||||
const [projectAuthTokenId, setWorkspaceProjectAuthTokenId] =
|
||||
React.useState<ObjectID | null>(null);
|
||||
const [isProjectAccountConnected, setIsProjectAccountConnected] =
|
||||
React.useState<boolean>(false);
|
||||
const [projectAuthTokens, setProjectAuthTokens] = React.useState<
|
||||
Array<WorkspaceProjectAuthToken>
|
||||
>([]);
|
||||
const [userAuthTokens, setUserAuthTokens] = React.useState<
|
||||
Array<WorkspaceUserAuthToken>
|
||||
>([]);
|
||||
const [isButtonLoading, setIsButtonLoading] = React.useState<boolean>(false);
|
||||
const [slackTeamName, setSlackTeamName] = React.useState<string | null>(null);
|
||||
const [showChannelsModal, setShowChannelsModal] =
|
||||
React.useState<boolean>(false);
|
||||
const [selectedProjectAuthTokenId, setSelectedProjectAuthTokenId] =
|
||||
React.useState<ObjectID | null>(null);
|
||||
|
||||
const isProjectAccountConnected: boolean = projectAuthTokens.length > 0;
|
||||
|
||||
useEffect(() => {
|
||||
if (isProjectAccountConnected) {
|
||||
@@ -73,75 +75,81 @@ const SlackIntegration: FunctionComponent<ComponentProps> = (
|
||||
setError(null);
|
||||
setIsLoading(true);
|
||||
|
||||
// check if the project is already connected with slack.
|
||||
const projectId: ObjectID | null = ProjectUtil.getCurrentProjectId();
|
||||
const userId: ObjectID | null = UserUtil.getUserId();
|
||||
|
||||
if (!projectId) {
|
||||
setError(
|
||||
<div>
|
||||
Looks like you have not selected any project. Please select a
|
||||
project to continue.
|
||||
</div>,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!userId) {
|
||||
setError(
|
||||
<div>
|
||||
Looks like you are not logged in. Please login to continue.
|
||||
</div>,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const projectAuth: ListResult<WorkspaceProjectAuthToken> =
|
||||
await ModelAPI.getList<WorkspaceProjectAuthToken>({
|
||||
modelType: WorkspaceProjectAuthToken,
|
||||
query: {
|
||||
projectId: ProjectUtil.getCurrentProjectId()!,
|
||||
projectId: projectId,
|
||||
workspaceType: WorkspaceType.Slack,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
miscData: true,
|
||||
workspaceProjectId: true,
|
||||
},
|
||||
limit: 1,
|
||||
limit: LIMIT_PER_PROJECT,
|
||||
skip: 0,
|
||||
sort: {
|
||||
createdAt: SortOrder.Descending,
|
||||
createdAt: SortOrder.Ascending,
|
||||
},
|
||||
});
|
||||
|
||||
if (projectAuth.data.length > 0) {
|
||||
setIsProjectAccountConnected(true);
|
||||
const slackTeamName: string | undefined = (
|
||||
projectAuth.data[0]!.miscData! as SlackMiscData
|
||||
).teamName;
|
||||
setWorkspaceProjectAuthTokenId(projectAuth.data[0]!.id);
|
||||
setSlackTeamName(slackTeamName);
|
||||
}
|
||||
|
||||
// fetch user auth token.
|
||||
setProjectAuthTokens(projectAuth.data);
|
||||
|
||||
const userAuth: ListResult<WorkspaceUserAuthToken> =
|
||||
await ModelAPI.getList<WorkspaceUserAuthToken>({
|
||||
modelType: WorkspaceUserAuthToken,
|
||||
query: {
|
||||
userId: UserUtil.getUserId()!,
|
||||
userId: userId,
|
||||
projectId: projectId,
|
||||
workspaceType: WorkspaceType.Slack,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
workspaceProjectId: true,
|
||||
miscData: true,
|
||||
},
|
||||
limit: 1,
|
||||
limit: LIMIT_PER_PROJECT,
|
||||
skip: 0,
|
||||
sort: {
|
||||
createdAt: SortOrder.Descending,
|
||||
},
|
||||
});
|
||||
|
||||
if (userAuth.data.length > 0) {
|
||||
setIsUserAccountConnected(true);
|
||||
setWorkspaceUserAuthTokenId(userAuth.data[0]!.id);
|
||||
setUserAuthTokens(userAuth.data);
|
||||
|
||||
const response: HTTPErrorResponse | HTTPResponse<JSONObject> =
|
||||
await API.get<JSONObject>({
|
||||
url: URL.fromString(`${HOME_URL.toString()}/api/slack/app-manifest`),
|
||||
});
|
||||
|
||||
if (response instanceof HTTPErrorResponse) {
|
||||
throw response;
|
||||
}
|
||||
|
||||
if (!isUserAccountConnected || !isProjectAccountConnected) {
|
||||
// if any of this is not connected then fetch the app manifest, so we can connect with slack.
|
||||
|
||||
// fetch app manifest.
|
||||
const response: HTTPErrorResponse | HTTPResponse<JSONObject> =
|
||||
await API.get<JSONObject>({
|
||||
url: URL.fromString(
|
||||
`${HOME_URL.toString()}/api/slack/app-manifest`,
|
||||
),
|
||||
});
|
||||
|
||||
if (response instanceof HTTPErrorResponse) {
|
||||
throw response;
|
||||
}
|
||||
|
||||
setManifest(response.data);
|
||||
}
|
||||
setManifest(response.data);
|
||||
} catch (error) {
|
||||
setError(<div>{API.getFriendlyErrorMessage(error as Error)}</div>);
|
||||
} finally {
|
||||
@@ -175,91 +183,57 @@ const SlackIntegration: FunctionComponent<ComponentProps> = (
|
||||
return <ErrorMessage message={error} />;
|
||||
}
|
||||
|
||||
let cardTitle: string = "";
|
||||
let cardDescription: string = "";
|
||||
let cardButtons: Array<CardButtonSchema> = [];
|
||||
|
||||
// if user and project both connected with slack, then.
|
||||
if (isUserAccountConnected && isProjectAccountConnected) {
|
||||
cardTitle = `You are connected with ${slackTeamName} team on Slack`;
|
||||
cardDescription = `Your account is already connected with Slack.`;
|
||||
cardButtons = [
|
||||
{
|
||||
title: `View Channels`,
|
||||
isLoading: isButtonLoading,
|
||||
buttonStyle: ButtonStyleType.NORMAL,
|
||||
onClick: async () => {
|
||||
try {
|
||||
setError(null);
|
||||
setShowChannelsModal(true);
|
||||
} catch (error) {
|
||||
setError(
|
||||
<div>{API.getFriendlyErrorMessage(error as Exception)}</div>,
|
||||
);
|
||||
}
|
||||
},
|
||||
icon: IconProp.Slack,
|
||||
},
|
||||
{
|
||||
title: `Disconnect`,
|
||||
isLoading: isButtonLoading,
|
||||
buttonStyle: ButtonStyleType.DANGER,
|
||||
onClick: async () => {
|
||||
try {
|
||||
setIsButtonLoading(true);
|
||||
setError(null);
|
||||
if (userAuthTokenId) {
|
||||
await ModelAPI.deleteItem({
|
||||
modelType: WorkspaceUserAuthToken,
|
||||
id: userAuthTokenId!,
|
||||
});
|
||||
|
||||
setIsUserAccountConnected(false);
|
||||
setWorkspaceUserAuthTokenId(null);
|
||||
} else {
|
||||
setError(
|
||||
<div>
|
||||
Looks like the user auth token id is not set properly. Please
|
||||
try again.
|
||||
</div>,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
setError(
|
||||
<div>{API.getFriendlyErrorMessage(error as Exception)}</div>,
|
||||
);
|
||||
}
|
||||
setIsButtonLoading(false);
|
||||
},
|
||||
icon: IconProp.Close,
|
||||
},
|
||||
];
|
||||
interface ConnectWithSlackData {
|
||||
mode: "workspace" | "user";
|
||||
expectedWorkspaceProjectId?: string;
|
||||
}
|
||||
|
||||
const connectWithSlack: VoidFunction = (): void => {
|
||||
if (SlackAppClientId) {
|
||||
const projectId: ObjectID | null = ProjectUtil.getCurrentProjectId();
|
||||
const userId: ObjectID | null = UserUtil.getUserId();
|
||||
type ConnectWithSlack = (data: ConnectWithSlackData) => void;
|
||||
|
||||
if (!projectId) {
|
||||
setError(
|
||||
<div>
|
||||
Looks like you have not selected any project. Please select a
|
||||
project to continue.
|
||||
</div>,
|
||||
);
|
||||
return;
|
||||
}
|
||||
const connectWithSlack: ConnectWithSlack = (
|
||||
data: ConnectWithSlackData,
|
||||
): void => {
|
||||
if (!SlackAppClientId) {
|
||||
setError(
|
||||
<div>
|
||||
Looks like the Slack App Client ID is not set in the environment
|
||||
variables when you installed OneUptime. For more information, please
|
||||
check this guide to set up Slack App properly:{" "}
|
||||
<Link
|
||||
to={new Route("/docs/self-hosted/slack-integration")}
|
||||
openInNewTab={true}
|
||||
>
|
||||
Slack Integration
|
||||
</Link>
|
||||
</div>,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!userId) {
|
||||
setError(
|
||||
<div>
|
||||
Looks like you are not logged in. Please login to continue.
|
||||
</div>,
|
||||
);
|
||||
return;
|
||||
}
|
||||
const projectId: ObjectID | null = ProjectUtil.getCurrentProjectId();
|
||||
const userId: ObjectID | null = UserUtil.getUserId();
|
||||
|
||||
if (!projectId) {
|
||||
setError(
|
||||
<div>
|
||||
Looks like you have not selected any project. Please select a project
|
||||
to continue.
|
||||
</div>,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!userId) {
|
||||
setError(
|
||||
<div>Looks like you are not logged in. Please login to continue.</div>,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const projectInstallRedirectUri: string = `${APP_API_URL}/slack/auth/${projectId.toString()}/${userId.toString()}`;
|
||||
const userSigninRedirectUri: string = `${APP_API_URL}/slack/auth/${projectId.toString()}/${userId.toString()}/user`;
|
||||
|
||||
if (data.mode === "workspace") {
|
||||
const userScopes: Array<string> = [];
|
||||
|
||||
if (
|
||||
@@ -304,7 +278,6 @@ const SlackIntegration: FunctionComponent<ComponentProps> = (
|
||||
);
|
||||
}
|
||||
|
||||
// if any of the user or bot scopes length = = then error.
|
||||
if (userScopes.length === 0 || botScopes.length === 0) {
|
||||
setError(
|
||||
<div>
|
||||
@@ -321,75 +294,126 @@ const SlackIntegration: FunctionComponent<ComponentProps> = (
|
||||
return;
|
||||
}
|
||||
|
||||
const project_install_redirect_uri: string = `${APP_API_URL}/slack/auth/${projectId.toString()}/${userId.toString()}`;
|
||||
const user_signin_redirect_uri: string = `${APP_API_URL}/slack/auth/${projectId.toString()}/${userId.toString()}/user`;
|
||||
|
||||
if (!isProjectAccountConnected) {
|
||||
Navigation.navigate(
|
||||
URL.fromString(
|
||||
`https://slack.com/oauth/v2/authorize?scope=${botScopes.join(
|
||||
",",
|
||||
)}&user_scope=${userScopes.join(
|
||||
",",
|
||||
)}&client_id=${SlackAppClientId}&redirect_uri=${project_install_redirect_uri}`,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
// if project account is not connected then we just need to sign in with slack and not install the app.
|
||||
Navigation.navigate(
|
||||
URL.fromString(
|
||||
`https://slack.com/openid/connect/authorize?response_type=code&scope=openid%20profile%20email&client_id=${SlackAppClientId}&redirect_uri=${user_signin_redirect_uri}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
setError(
|
||||
<div>
|
||||
Looks like the Slack App Client ID is not set in the environment
|
||||
variables when you installed OneUptime. For more information, please
|
||||
check this guide to set up Slack App properly:{" "}
|
||||
<Link
|
||||
to={new Route("/docs/self-hosted/slack-integration")}
|
||||
openInNewTab={true}
|
||||
>
|
||||
Slack Integration
|
||||
</Link>
|
||||
</div>,
|
||||
Navigation.navigate(
|
||||
URL.fromString(
|
||||
`https://slack.com/oauth/v2/authorize?scope=${botScopes.join(
|
||||
",",
|
||||
)}&user_scope=${userScopes.join(
|
||||
",",
|
||||
)}&client_id=${SlackAppClientId}&redirect_uri=${projectInstallRedirectUri}`,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const stateParam: string | undefined = data.expectedWorkspaceProjectId;
|
||||
const stateQuery: string = stateParam
|
||||
? `&state=${encodeURIComponent(stateParam)}`
|
||||
: "";
|
||||
|
||||
Navigation.navigate(
|
||||
URL.fromString(
|
||||
`https://slack.com/openid/connect/authorize?response_type=code&scope=openid%20profile%20email&client_id=${SlackAppClientId}&redirect_uri=${userSigninRedirectUri}${stateQuery}`,
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
type GetConnectWithSlackButtonFunction = (title: string) => CardButtonSchema;
|
||||
type GetConnectWithSlackButtonFunction = (
|
||||
title: string,
|
||||
onClick: VoidFunction,
|
||||
) => CardButtonSchema;
|
||||
|
||||
const getConnectWithSlackButton: GetConnectWithSlackButtonFunction = (
|
||||
title: string,
|
||||
onClick: VoidFunction,
|
||||
): CardButtonSchema => {
|
||||
return {
|
||||
title: title || `Connect with Slack`,
|
||||
buttonStyle: ButtonStyleType.PRIMARY,
|
||||
onClick: () => {
|
||||
return connectWithSlack();
|
||||
return onClick();
|
||||
},
|
||||
|
||||
icon: IconProp.Slack,
|
||||
};
|
||||
};
|
||||
|
||||
// if user is not connected and the project is connected with slack.
|
||||
if (!isUserAccountConnected && isProjectAccountConnected) {
|
||||
cardTitle = `You are disconnected from Slack (but OneUptime is already installed in ${slackTeamName} team)`;
|
||||
cardDescription = `Connect your account with Slack to make the most out of OneUptime.`;
|
||||
cardButtons = [
|
||||
// connect with slack button.
|
||||
getConnectWithSlackButton(`Connect my account with Slack`),
|
||||
{
|
||||
const userAuthByWorkspaceProjectId: Map<string, WorkspaceUserAuthToken> =
|
||||
new Map();
|
||||
|
||||
userAuthTokens.forEach((token: WorkspaceUserAuthToken) => {
|
||||
if (token.workspaceProjectId) {
|
||||
userAuthByWorkspaceProjectId.set(token.workspaceProjectId, token);
|
||||
}
|
||||
});
|
||||
|
||||
const workspaceCards: Array<ReactElement> = projectAuthTokens.map(
|
||||
(workspace: WorkspaceProjectAuthToken) => {
|
||||
const teamName: string | undefined = (workspace.miscData as SlackMiscData)
|
||||
?.teamName;
|
||||
|
||||
const workspaceProjectId: string | undefined =
|
||||
workspace.workspaceProjectId;
|
||||
|
||||
const userAuth: WorkspaceUserAuthToken | undefined = workspaceProjectId
|
||||
? userAuthByWorkspaceProjectId.get(workspaceProjectId)
|
||||
: undefined;
|
||||
|
||||
const buttons: Array<CardButtonSchema> = [];
|
||||
|
||||
if (userAuth) {
|
||||
buttons.push({
|
||||
title: `Disconnect My Account`,
|
||||
isLoading: isButtonLoading,
|
||||
buttonStyle: ButtonStyleType.DANGER,
|
||||
onClick: async () => {
|
||||
try {
|
||||
setIsButtonLoading(true);
|
||||
setError(null);
|
||||
await ModelAPI.deleteItem({
|
||||
modelType: WorkspaceUserAuthToken,
|
||||
id: userAuth.id!,
|
||||
});
|
||||
await loadItems();
|
||||
} catch (error) {
|
||||
setError(
|
||||
<div>{API.getFriendlyErrorMessage(error as Exception)}</div>,
|
||||
);
|
||||
}
|
||||
setIsButtonLoading(false);
|
||||
},
|
||||
icon: IconProp.Close,
|
||||
});
|
||||
} else {
|
||||
buttons.push(
|
||||
getConnectWithSlackButton(`Connect My Account`, () => {
|
||||
const connectData: {
|
||||
mode: "user" | "workspace";
|
||||
expectedWorkspaceProjectId?: string;
|
||||
} = {
|
||||
mode: "user",
|
||||
};
|
||||
|
||||
if (workspaceProjectId) {
|
||||
connectData.expectedWorkspaceProjectId = workspaceProjectId;
|
||||
}
|
||||
|
||||
return connectWithSlack(connectData);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
buttons.push({
|
||||
title: `View Channels`,
|
||||
isLoading: isButtonLoading,
|
||||
buttonStyle: ButtonStyleType.NORMAL,
|
||||
onClick: async () => {
|
||||
try {
|
||||
setError(null);
|
||||
setShowChannelsModal(true);
|
||||
if (workspace.id) {
|
||||
setSelectedProjectAuthTokenId(workspace.id);
|
||||
setShowChannelsModal(true);
|
||||
}
|
||||
} catch (error) {
|
||||
setError(
|
||||
<div>{API.getFriendlyErrorMessage(error as Exception)}</div>,
|
||||
@@ -397,8 +421,9 @@ const SlackIntegration: FunctionComponent<ComponentProps> = (
|
||||
}
|
||||
},
|
||||
icon: IconProp.Slack,
|
||||
},
|
||||
{
|
||||
});
|
||||
|
||||
buttons.push({
|
||||
title: `Uninstall OneUptime from Slack`,
|
||||
isLoading: isButtonLoading,
|
||||
buttonStyle: ButtonStyleType.DANGER,
|
||||
@@ -406,21 +431,12 @@ const SlackIntegration: FunctionComponent<ComponentProps> = (
|
||||
try {
|
||||
setIsButtonLoading(true);
|
||||
setError(null);
|
||||
if (projectAuthTokenId) {
|
||||
if (workspace.id) {
|
||||
await ModelAPI.deleteItem({
|
||||
modelType: WorkspaceProjectAuthToken,
|
||||
id: projectAuthTokenId!,
|
||||
id: workspace.id,
|
||||
});
|
||||
|
||||
setIsProjectAccountConnected(false);
|
||||
setWorkspaceProjectAuthTokenId(null);
|
||||
} else {
|
||||
setError(
|
||||
<div>
|
||||
Looks like the user auth token id is not set properly. Please
|
||||
try again.
|
||||
</div>,
|
||||
);
|
||||
await loadItems();
|
||||
}
|
||||
} catch (error) {
|
||||
setError(
|
||||
@@ -430,15 +446,47 @@ const SlackIntegration: FunctionComponent<ComponentProps> = (
|
||||
setIsButtonLoading(false);
|
||||
},
|
||||
icon: IconProp.Trash,
|
||||
},
|
||||
];
|
||||
}
|
||||
});
|
||||
|
||||
if (!isProjectAccountConnected) {
|
||||
cardTitle = `Connect with Slack`;
|
||||
cardDescription = `Connect your account with Slack to make the most out of OneUptime.`;
|
||||
cardButtons = [getConnectWithSlackButton(`Connect with Slack`)];
|
||||
}
|
||||
return (
|
||||
<Card
|
||||
key={workspace.id?.toString()}
|
||||
title={`Slack Workspace: ${teamName || workspaceProjectId || "Slack"}`}
|
||||
description={
|
||||
userAuth
|
||||
? "Your account is connected to this Slack workspace."
|
||||
: "Connect your account to enable personalized Slack actions."
|
||||
}
|
||||
buttons={buttons}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
const connectWorkspaceCard: ReactElement = (
|
||||
<Card
|
||||
title={
|
||||
isProjectAccountConnected
|
||||
? "Connect Another Slack Workspace"
|
||||
: "Connect with Slack"
|
||||
}
|
||||
description={
|
||||
isProjectAccountConnected
|
||||
? "Install OneUptime in another Slack workspace."
|
||||
: "Install OneUptime in your Slack workspace."
|
||||
}
|
||||
buttons={[
|
||||
getConnectWithSlackButton(
|
||||
isProjectAccountConnected
|
||||
? "Connect Another Workspace"
|
||||
: "Connect with Slack",
|
||||
() => {
|
||||
return connectWithSlack({ mode: "workspace" });
|
||||
},
|
||||
),
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
if (!SlackAppClientId) {
|
||||
return <SlackIntegrationDocumentation manifest={manifest as JSONObject} />;
|
||||
@@ -446,18 +494,16 @@ const SlackIntegration: FunctionComponent<ComponentProps> = (
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div>
|
||||
<Card
|
||||
title={cardTitle}
|
||||
description={cardDescription}
|
||||
buttons={cardButtons}
|
||||
/>
|
||||
<div className="space-y-4">
|
||||
{connectWorkspaceCard}
|
||||
{workspaceCards}
|
||||
</div>
|
||||
{showChannelsModal && projectAuthTokenId ? (
|
||||
{showChannelsModal && selectedProjectAuthTokenId ? (
|
||||
<SlackChannelCacheModal
|
||||
projectAuthTokenId={projectAuthTokenId}
|
||||
projectAuthTokenId={selectedProjectAuthTokenId}
|
||||
onClose={() => {
|
||||
return setShowChannelsModal(false);
|
||||
setShowChannelsModal(false);
|
||||
setSelectedProjectAuthTokenId(null);
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
@@ -54,7 +54,11 @@ import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import { APP_API_URL } from "Common/UI/Config";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
import { MicrosoftTeamsTeam } from "Common/Models/DatabaseModels/WorkspaceProjectAuthToken";
|
||||
import WorkspaceProjectAuthToken, {
|
||||
MicrosoftTeamsMiscData,
|
||||
MicrosoftTeamsTeam,
|
||||
SlackMiscData,
|
||||
} from "Common/Models/DatabaseModels/WorkspaceProjectAuthToken";
|
||||
export interface ComponentProps {
|
||||
workspaceType: WorkspaceType;
|
||||
eventType: NotificationRuleEventType;
|
||||
@@ -87,6 +91,8 @@ const WorkspaceNotificationRuleTable: FunctionComponent<ComponentProps> = (
|
||||
const [microsoftTeamsTeams, setMicrosoftTeams] = React.useState<
|
||||
Array<MicrosoftTeamsTeam>
|
||||
>([]);
|
||||
const [workspaceProjectAuthTokens, setWorkspaceProjectAuthTokens] =
|
||||
React.useState<Array<WorkspaceProjectAuthToken>>([]);
|
||||
const [users, setUsers] = React.useState<Array<User>>([]);
|
||||
|
||||
const [showTestModal, setShowTestModal] = React.useState<boolean>(false);
|
||||
@@ -372,6 +378,27 @@ const WorkspaceNotificationRuleTable: FunctionComponent<ComponentProps> = (
|
||||
setMicrosoftTeams(teamsData);
|
||||
}
|
||||
}
|
||||
|
||||
const workspaceAuths: ListResult<WorkspaceProjectAuthToken> =
|
||||
await ModelAPI.getList<WorkspaceProjectAuthToken>({
|
||||
modelType: WorkspaceProjectAuthToken,
|
||||
query: {
|
||||
projectId: ProjectUtil.getCurrentProjectId()!,
|
||||
workspaceType: props.workspaceType,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
miscData: true,
|
||||
workspaceProjectId: true,
|
||||
},
|
||||
limit: LIMIT_PER_PROJECT,
|
||||
skip: 0,
|
||||
sort: {
|
||||
createdAt: SortOrder.Ascending,
|
||||
},
|
||||
});
|
||||
|
||||
setWorkspaceProjectAuthTokens(workspaceAuths.data);
|
||||
} catch (err) {
|
||||
setError(API.getFriendlyErrorMessage(err as Exception));
|
||||
}
|
||||
@@ -392,6 +419,70 @@ const WorkspaceNotificationRuleTable: FunctionComponent<ComponentProps> = (
|
||||
return <ErrorMessage message={error} />;
|
||||
}
|
||||
|
||||
type GetWorkspaceDisplayName = (
|
||||
workspace: WorkspaceProjectAuthToken,
|
||||
) => string;
|
||||
|
||||
const getWorkspaceDisplayName: GetWorkspaceDisplayName = (
|
||||
workspace: WorkspaceProjectAuthToken,
|
||||
): string => {
|
||||
if (props.workspaceType === WorkspaceType.Slack) {
|
||||
const teamName: string | undefined = (workspace.miscData as SlackMiscData)
|
||||
?.teamName;
|
||||
return teamName || workspace.workspaceProjectId || "Slack Workspace";
|
||||
}
|
||||
|
||||
if (props.workspaceType === WorkspaceType.MicrosoftTeams) {
|
||||
const teamName: string | undefined = (
|
||||
workspace.miscData as MicrosoftTeamsMiscData
|
||||
)?.teamName;
|
||||
return (
|
||||
teamName ||
|
||||
workspace.workspaceProjectId ||
|
||||
`${getWorkspaceTypeDisplayName(props.workspaceType)} Workspace`
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
workspace.workspaceProjectId ||
|
||||
`${getWorkspaceTypeDisplayName(props.workspaceType)} Workspace`
|
||||
);
|
||||
};
|
||||
|
||||
const workspaceOptions: Array<{ label: string; value: string }> =
|
||||
workspaceProjectAuthTokens.map((workspace: WorkspaceProjectAuthToken) => {
|
||||
return {
|
||||
label: getWorkspaceDisplayName(workspace),
|
||||
value: workspace.id?.toString() || "",
|
||||
};
|
||||
});
|
||||
|
||||
const defaultWorkspaceAuthTokenId: string | undefined =
|
||||
workspaceProjectAuthTokens.length === 1
|
||||
? workspaceProjectAuthTokens[0]?.id?.toString()
|
||||
: undefined;
|
||||
|
||||
type GetWorkspaceNameById = (id?: string) => string;
|
||||
|
||||
const getWorkspaceNameById: GetWorkspaceNameById = (id?: string): string => {
|
||||
if (!id) {
|
||||
return "-";
|
||||
}
|
||||
|
||||
const match: WorkspaceProjectAuthToken | undefined =
|
||||
workspaceProjectAuthTokens.find(
|
||||
(workspace: WorkspaceProjectAuthToken) => {
|
||||
return workspace.id?.toString() === id.toString();
|
||||
},
|
||||
);
|
||||
|
||||
if (!match) {
|
||||
return "-";
|
||||
}
|
||||
|
||||
return getWorkspaceDisplayName(match);
|
||||
};
|
||||
|
||||
type RemoveFilterWithNoValues = (
|
||||
notificationRule: IncidentNotificationRule,
|
||||
) => IncidentNotificationRule;
|
||||
@@ -473,18 +564,48 @@ const WorkspaceNotificationRuleTable: FunctionComponent<ComponentProps> = (
|
||||
values.eventType = props.eventType;
|
||||
values.projectId = ProjectUtil.getCurrentProjectId()!;
|
||||
values.workspaceType = props.workspaceType;
|
||||
if (
|
||||
!values.workspaceProjectAuthTokenId &&
|
||||
defaultWorkspaceAuthTokenId
|
||||
) {
|
||||
values.workspaceProjectAuthTokenId = new ObjectID(
|
||||
defaultWorkspaceAuthTokenId,
|
||||
);
|
||||
}
|
||||
values.notificationRule = removeFiltersWithNoValues(
|
||||
values.notificationRule as IncidentNotificationRule,
|
||||
);
|
||||
return Promise.resolve(values);
|
||||
}}
|
||||
onBeforeEdit={(values: WorkspaceNotificationRule) => {
|
||||
if (
|
||||
!values.workspaceProjectAuthTokenId &&
|
||||
defaultWorkspaceAuthTokenId
|
||||
) {
|
||||
values.workspaceProjectAuthTokenId = new ObjectID(
|
||||
defaultWorkspaceAuthTokenId,
|
||||
);
|
||||
}
|
||||
values.notificationRule = removeFiltersWithNoValues(
|
||||
values.notificationRule as IncidentNotificationRule,
|
||||
);
|
||||
return Promise.resolve(values);
|
||||
}}
|
||||
formFields={[
|
||||
{
|
||||
field: {
|
||||
workspaceProjectAuthTokenId: true,
|
||||
},
|
||||
title: `${getWorkspaceTypeDisplayName(props.workspaceType)} Workspace`,
|
||||
description: `Select the ${getWorkspaceTypeDisplayName(props.workspaceType)} workspace where this rule should post notifications.`,
|
||||
fieldType: FormFieldSchemaType.Dropdown,
|
||||
required: true,
|
||||
stepId: "basic",
|
||||
showIf: () => {
|
||||
return workspaceProjectAuthTokens.length > 1;
|
||||
},
|
||||
dropdownOptions: workspaceOptions,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
@@ -599,6 +720,28 @@ const WorkspaceNotificationRuleTable: FunctionComponent<ComponentProps> = (
|
||||
title: "Rule Description",
|
||||
type: FieldType.Text,
|
||||
},
|
||||
...(workspaceProjectAuthTokens.length > 1
|
||||
? [
|
||||
{
|
||||
field: {
|
||||
workspaceProjectAuthTokenId: true,
|
||||
},
|
||||
title: `${getWorkspaceTypeDisplayName(props.workspaceType)} Workspace`,
|
||||
type: FieldType.Element,
|
||||
getElement: (
|
||||
value: WorkspaceNotificationRule,
|
||||
): ReactElement => {
|
||||
return (
|
||||
<Fragment>
|
||||
{getWorkspaceNameById(
|
||||
value.workspaceProjectAuthTokenId?.toString(),
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
field: {
|
||||
notificationRule: true,
|
||||
|
||||
Reference in New Issue
Block a user