Skip to content

Commit b3f8406

Browse files
committed
Add improved code layout for interactions
1 parent 7d2d89a commit b3f8406

11 files changed

Lines changed: 168 additions & 130 deletions

File tree

src/infrastructure/discord/bot.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ import { Client, GatewayIntentBits, Events } from "discord.js";
22
import { config } from "dotenv";
33
import fs from "fs";
44
import path from "path";
5+
import { handleModalSubmit } from "./commands/createIssue";
56
import {
6-
handleModalSubmit,
7-
handleAssigneeSelect,
8-
} from "./commands/createIssue";
9-
import { handleButtonInteraction } from "./commands/unassignedIssues";
7+
assigneeSelectInteraction,
8+
issueButtonInteraction,
9+
} from "./interactions";
1010

1111
config();
1212

@@ -54,15 +54,14 @@ client.on(Events.InteractionCreate, async (interaction) => {
5454
await handleModalSubmit(interaction);
5555
}
5656

57-
if (
58-
interaction.isUserSelectMenu() &&
59-
interaction.customId === "create-issue:assigneeSelect"
60-
) {
61-
await handleAssigneeSelect(interaction);
57+
// TODO: need more specificity on this check as there could be other user select interactions
58+
if (interaction.isUserSelectMenu()) {
59+
await assigneeSelectInteraction(interaction);
6260
}
6361

62+
// TODO: need more specificity on this check as there could be other button interactions
6463
if (interaction.isButton()) {
65-
await handleButtonInteraction(interaction);
64+
await issueButtonInteraction(interaction);
6665
}
6766
});
6867

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { ActionRowBuilder, UserSelectMenuBuilder } from "discord.js";
2+
3+
export const buildAssigneeSelectionRow = () => {
4+
return new ActionRowBuilder<UserSelectMenuBuilder>().addComponents(
5+
new UserSelectMenuBuilder()
6+
.setCustomId("create-issue:assigneeSelect")
7+
.setPlaceholder("Choose a user")
8+
.setMinValues(1)
9+
.setMaxValues(1),
10+
);
11+
};
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { buildIssueButtonRow } from "./issueButtonRow";
2+
export { buildAssigneeSelectionRow } from "./assigneeSelectionRow";
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js";
2+
3+
type IssueButtonOperation = "edit" | "assign" | "unassign" | "delete" | "open";
4+
5+
export const buildIssueButtonRow = (
6+
issueId: string,
7+
link: string,
8+
operations: IssueButtonOperation[] = [
9+
"edit",
10+
"assign",
11+
"unassign",
12+
"delete",
13+
"open",
14+
],
15+
) => {
16+
const row = new ActionRowBuilder<ButtonBuilder>();
17+
18+
if (operations.includes("edit")) {
19+
row.addComponents(
20+
new ButtonBuilder()
21+
.setCustomId(`issue:edit:${issueId}`)
22+
.setLabel("Edit")
23+
.setStyle(ButtonStyle.Primary),
24+
);
25+
}
26+
27+
if (operations.includes("assign")) {
28+
row.addComponents(
29+
new ButtonBuilder()
30+
.setCustomId(`issue:assign:${issueId}`)
31+
.setLabel("Assign")
32+
.setStyle(ButtonStyle.Secondary),
33+
);
34+
}
35+
36+
if (operations.includes("unassign")) {
37+
row.addComponents(
38+
new ButtonBuilder()
39+
.setCustomId(`issue:unassign:${issueId}`)
40+
.setLabel("Unassign")
41+
.setStyle(ButtonStyle.Secondary),
42+
);
43+
}
44+
45+
if (operations.includes("delete")) {
46+
row.addComponents(
47+
new ButtonBuilder()
48+
.setCustomId(`issue:delete:${issueId}`)
49+
.setLabel("Delete")
50+
.setStyle(ButtonStyle.Danger),
51+
);
52+
}
53+
54+
if (operations.includes("open")) {
55+
row.addComponents(
56+
new ButtonBuilder()
57+
.setLabel("Open")
58+
.setStyle(ButtonStyle.Link)
59+
.setURL(link),
60+
);
61+
}
62+
63+
return row;
64+
};

src/infrastructure/discord/commands/createIssue.ts

Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,11 @@ import {
55
TextInputBuilder,
66
TextInputStyle,
77
ActionRowBuilder,
8-
UserSelectMenuBuilder,
9-
UserSelectMenuInteraction,
108
} from "discord.js";
119
import { ModalSubmitInteraction } from "discord.js";
1210
import { ItemService } from "@src/items/services";
1311
import { can } from "../authz";
12+
import { promptAssigneeSelection } from "../interactions";
1413

1514
export const data = new SlashCommandBuilder()
1615
.setName("create-issue")
@@ -89,28 +88,5 @@ export async function handleModalSubmit(
8988
return;
9089
}
9190

92-
await interaction.reply({
93-
content: "Select an assignee for this issue:",
94-
components: [
95-
new ActionRowBuilder<UserSelectMenuBuilder>().addComponents(
96-
new UserSelectMenuBuilder()
97-
.setCustomId("create-issue:assigneeSelect")
98-
.setPlaceholder("Choose a user")
99-
.setMinValues(1)
100-
.setMaxValues(1),
101-
),
102-
],
103-
ephemeral: true,
104-
});
105-
}
106-
107-
export async function handleAssigneeSelect(
108-
interaction: UserSelectMenuInteraction,
109-
): Promise<void> {
110-
const selectedUserId = interaction.values[0];
111-
112-
await interaction.update({
113-
content: `**Assigned**: <@${selectedUserId}>`,
114-
components: [],
115-
});
91+
await promptAssigneeSelection(interaction);
11692
}

src/infrastructure/discord/commands/myIssues.ts

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
import {
22
SlashCommandBuilder,
33
CommandInteraction,
4-
ActionRowBuilder,
5-
ButtonBuilder,
6-
ButtonStyle,
74
} from "discord.js";
85
import { GithubAPI } from "@infrastructure/github";
96
import logger from "@config/logger";
107
import githubDiscordMapJson from "../../../../data/githubDiscordMap.json";
118
import { can } from "../authz";
9+
import { buildIssueButtonRow } from "../builders";
1210

1311
const githubDiscordMap: { [githubUsername: string]: string } =
1412
githubDiscordMapJson;
@@ -77,24 +75,7 @@ export async function execute(interaction: CommandInteraction) {
7775
for (const item of assignedItems.slice(0, 5)) {
7876
const link = item.url ?? "https://github.com/";
7977

80-
const buttons = new ActionRowBuilder<ButtonBuilder>().addComponents(
81-
new ButtonBuilder()
82-
.setCustomId(`issue:edit:${item.githubId}`)
83-
.setLabel("Edit")
84-
.setStyle(ButtonStyle.Primary),
85-
new ButtonBuilder()
86-
.setCustomId(`issue:unassign:${item.githubId}`)
87-
.setLabel("Unassign")
88-
.setStyle(ButtonStyle.Secondary),
89-
new ButtonBuilder()
90-
.setCustomId(`issue:delete:${item.githubId}`)
91-
.setLabel("Delete")
92-
.setStyle(ButtonStyle.Danger),
93-
new ButtonBuilder()
94-
.setLabel("Open")
95-
.setStyle(ButtonStyle.Link)
96-
.setURL(link),
97-
);
78+
const buttons = buildIssueButtonRow(item.githubId, link, ["unassign", "open"]);
9879

9980
await interaction.followUp({
10081
content: `## ${item.title}`,
Lines changed: 6 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,9 @@
1-
import {
2-
SlashCommandBuilder,
3-
CommandInteraction,
4-
ActionRowBuilder,
5-
ButtonBuilder,
6-
ButtonStyle,
7-
Interaction,
8-
CacheType,
9-
} from "discord.js";
1+
import { SlashCommandBuilder, CommandInteraction } from "discord.js";
102
import { GithubAPI } from "@infrastructure/github";
113
import { filterForUnassigned } from "@src/items";
124
import logger from "@config/logger";
135
import { can } from "../authz";
6+
import { buildIssueButtonRow } from "../builders";
147

158
export const data = new SlashCommandBuilder()
169
.setName("unassigned-issues")
@@ -83,24 +76,10 @@ export async function execute(interaction: CommandInteraction) {
8376
for (const item of limitedItems) {
8477
const link = item.url ?? "https://github.com/";
8578

86-
const buttons = new ActionRowBuilder<ButtonBuilder>().addComponents(
87-
new ButtonBuilder()
88-
.setCustomId(`issue:edit:${item.githubId}`)
89-
.setLabel("Edit")
90-
.setStyle(ButtonStyle.Primary),
91-
new ButtonBuilder()
92-
.setCustomId(`issue:assign:${item.githubId}`)
93-
.setLabel("Assign")
94-
.setStyle(ButtonStyle.Secondary),
95-
new ButtonBuilder()
96-
.setCustomId(`issue:delete:${item.githubId}`)
97-
.setLabel("Delete")
98-
.setStyle(ButtonStyle.Danger),
99-
new ButtonBuilder()
100-
.setLabel("Open")
101-
.setStyle(ButtonStyle.Link)
102-
.setURL(link),
103-
);
79+
const buttons = buildIssueButtonRow(item.githubId, link, [
80+
"assign",
81+
"open",
82+
]);
10483

10584
await interaction.followUp({
10685
content: `## ${item.title}`,
@@ -114,49 +93,3 @@ export async function execute(interaction: CommandInteraction) {
11493
body: `${unassignedItems.length} unassigned issues returned.`,
11594
});
11695
}
117-
118-
export async function handleButtonInteraction(
119-
interaction: Interaction<CacheType>,
120-
) {
121-
if (!interaction.isButton()) return;
122-
123-
const [_issue, action, githubId] = interaction.customId.split(":");
124-
125-
if (!githubId) {
126-
await interaction.reply({
127-
content: "⚠️ Invalid button ID.",
128-
ephemeral: true,
129-
});
130-
return;
131-
}
132-
133-
switch (action) {
134-
case "edit":
135-
await interaction.reply({
136-
content: `✏️ Editing issue with ID \`${githubId}\` (not yet implemented).`,
137-
ephemeral: true,
138-
});
139-
break;
140-
141-
case "assign":
142-
await interaction.reply({
143-
content: `👤 Assigning you to issue \`${githubId}\` (not yet implemented).`,
144-
ephemeral: true,
145-
});
146-
break;
147-
148-
case "delete":
149-
await interaction.reply({
150-
content: `🗑️ Deleting issue \`${githubId}\` (not yet implemented).`,
151-
ephemeral: true,
152-
});
153-
break;
154-
155-
default:
156-
// logger.warn({ event: "button.unknownAction", action });
157-
await interaction.reply({
158-
content: `❌ Unknown action: \`${action}\``,
159-
ephemeral: true,
160-
});
161-
}
162-
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { UserSelectMenuInteraction } from "discord.js";
2+
3+
export async function assigneeSelectInteraction(
4+
interaction: UserSelectMenuInteraction,
5+
): Promise<void> {
6+
const selectedUserId = interaction.values[0];
7+
8+
await interaction.update({
9+
content: `**Assigned**: <@${selectedUserId}>`,
10+
components: [],
11+
});
12+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export { assigneeSelectInteraction } from "./assigneeSelectInteraction";
2+
export { issueButtonInteraction } from "./issueButtonInteraction";
3+
export { promptAssigneeSelection } from "./promptAssigneeSelection";
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { CacheType, Interaction } from "discord.js";
2+
import { promptAssigneeSelection } from "./promptAssigneeSelection";
3+
4+
export async function issueButtonInteraction(
5+
interaction: Interaction<CacheType>,
6+
) {
7+
if (!interaction.isButton()) return;
8+
9+
const [_issue, action, githubId] = interaction.customId.split(":");
10+
11+
if (!githubId) {
12+
await interaction.reply({
13+
content: "⚠️ Invalid button ID.",
14+
ephemeral: true,
15+
});
16+
return;
17+
}
18+
19+
switch (action) {
20+
case "edit":
21+
await interaction.reply({
22+
content: `✏️ Editing issue with ID \`${githubId}\` (not yet implemented).`,
23+
ephemeral: true,
24+
});
25+
break;
26+
27+
case "assign":
28+
await promptAssigneeSelection(interaction);
29+
break;
30+
31+
case "delete":
32+
await interaction.reply({
33+
content: `🗑️ Deleting issue \`${githubId}\` (not yet implemented).`,
34+
ephemeral: true,
35+
});
36+
break;
37+
38+
default:
39+
// logger.warn({ event: "button.unknownAction", action });
40+
await interaction.reply({
41+
content: `❌ Unknown action: \`${action}\``,
42+
ephemeral: true,
43+
});
44+
}
45+
}

0 commit comments

Comments
 (0)