-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Expand file tree
/
Copy pathNewAlertChannelPresenter.server.ts
More file actions
131 lines (112 loc) · 3.51 KB
/
Copy pathNewAlertChannelPresenter.server.ts
File metadata and controls
131 lines (112 loc) · 3.51 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
import {
type AuthenticatableIntegration,
OrgIntegrationRepository,
} from "~/models/orgIntegration.server";
import { BasePresenter } from "./basePresenter.server";
import { type WebClient } from "@slack/web-api";
import { tryCatch } from "@trigger.dev/core";
import { logger } from "~/services/logger.server";
export class NewAlertChannelPresenter extends BasePresenter {
public async call(projectId: string) {
const project = await this._prisma.project.findFirstOrThrow({
where: {
id: projectId,
},
});
// Find the latest Slack integration
const slackIntegration = await this._prisma.organizationIntegration.findFirst({
where: {
service: "SLACK",
organizationId: project.organizationId,
},
orderBy: {
createdAt: "desc",
},
include: {
tokenReference: true,
},
});
// If there is a slack integration, then we need to get a list of Slack Channels
if (slackIntegration) {
const [error, channels] = await tryCatch(getSlackChannelsForToken(slackIntegration));
if (error) {
if (isSlackError(error) && error.data.error === "token_revoked") {
return {
slack: {
status: "ACCESS_REVOKED" as const,
},
};
}
logger.error("Failed fetching Slack channels for creating alerts", {
error,
slackIntegrationId: slackIntegration.id,
});
return {
slack: {
status: "FAILED_FETCHING_CHANNELS" as const,
},
};
}
return {
slack: {
status: "READY" as const,
channels: channels ?? [],
integrationId: slackIntegration.id,
},
};
}
if (OrgIntegrationRepository.isSlackSupported) {
return {
slack: {
status: "NOT_CONFIGURED" as const,
},
};
}
return {
slack: {
status: "NOT_AVAILABLE" as const,
},
};
}
}
async function getSlackChannelsForToken(integration: AuthenticatableIntegration) {
const client = await OrgIntegrationRepository.getAuthenticatedClientForIntegration(integration);
const channels = await getAllSlackConversations(client);
return (channels ?? [])
.filter((channel) => !channel.is_archived)
.filter((channel) => channel.is_channel)
.filter((channel) => channel.num_members)
.sort((a, b) => a.name!.localeCompare(b.name!));
}
type Channels = Awaited<ReturnType<WebClient["conversations"]["list"]>>["channels"];
async function getSlackConversationsPage(client: WebClient, nextCursor?: string) {
return client.conversations.list({
types: "public_channel,private_channel",
exclude_archived: true,
cursor: nextCursor,
});
}
async function getAllSlackConversations(client: WebClient) {
let nextCursor: string | undefined = undefined;
let channels: Channels = [];
do {
const response = await getSlackConversationsPage(client, nextCursor);
if (!response.ok) {
throw new Error(`Failed to get channels: ${response.error}`);
}
channels = channels.concat(response.channels ?? []);
nextCursor = response.response_metadata?.next_cursor;
} while (nextCursor);
return channels;
}
function isSlackError(obj: unknown): obj is { data: { error: string } } {
return Boolean(
typeof obj === "object" &&
obj !== null &&
"data" in obj &&
typeof obj.data === "object" &&
obj.data !== null &&
"error" in obj.data &&
typeof obj.data.error === "string"
);
}