Skip to content

Commit

Permalink
Implement tag selection process
Browse files Browse the repository at this point in the history
  • Loading branch information
No767 committed Dec 29, 2023
1 parent 5023b44 commit 06b4b26
Show file tree
Hide file tree
Showing 4 changed files with 197 additions and 12 deletions.
19 changes: 18 additions & 1 deletion bot/cogs/tickets.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import discord
from discord.ext import commands
from discord.utils import format_dt, utcnow
from libs.tickets.structs import ReservedTags, TicketThread
from libs.tickets.structs import ReservedTags, StatusChecklist, TicketThread
from libs.tickets.utils import get_cached_thread, get_partial_ticket
from libs.utils.embeds import LoggingEmbed

Expand Down Expand Up @@ -56,6 +56,7 @@ def __init__(self, bot: Rodhaj) -> None:
self.pool = self.bot.pool
self.logger = self.bot.logger
self.reserved_tags: dict[int, ReservedTags] = {}
self.in_progress_tickets: dict[int, StatusChecklist] = {}

### Tag selection utils

Expand All @@ -66,6 +67,15 @@ def is_tag_selected(self, user_id: int, type: str) -> Optional[bool]:
return
return conf[type]

def add_in_progress_tag(self, author_id: int, tags: ReservedTags) -> ReservedTags:
reserved = self.reserved_tags.setdefault(author_id, tags)
return reserved

def add_status_checklist(
self, author_id: int, status: StatusChecklist
) -> StatusChecklist:
return self.in_progress_tickets.setdefault(author_id, status)

#### Determining staff

@lru_cache(maxsize=64)
Expand Down Expand Up @@ -191,8 +201,15 @@ async def create_ticket(self, ticket: TicketThread) -> Optional[TicketOutput]:

# TODO: Add file attachment support later

all_tags = tc.available_tags
applied_tags = [
discord.utils.get(all_tags, name=tag.title()) for tag in ticket.tags
]
processed_tags = [tag for tag in applied_tags if tag is not None]

content = f"({ticket.user.display_name}, {discord.utils.format_dt(ticket.created_at)})\n\n{ticket.content}"
created_ticket = await tc.create_thread(
applied_tags=processed_tags,
name=ticket.title,
content=content,
reason=f"Ticket submitted by {ticket.user.global_name} (ID: {ticket.user.id})",
Expand Down
7 changes: 7 additions & 0 deletions bot/libs/tickets/structs.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import asyncio
import datetime
from typing import NamedTuple, Optional, TypedDict, Union

Expand All @@ -8,6 +9,11 @@
import msgspec


class StatusChecklist(msgspec.Struct):
title: asyncio.Event = asyncio.Event()
tags: asyncio.Event = asyncio.Event()


class ReservedTags(TypedDict):
question: bool
serious: bool
Expand All @@ -24,6 +30,7 @@ class TicketThread(msgspec.Struct):
user: Union[discord.User, discord.Member]
location_id: int
content: str
tags: list[str]
created_at: datetime.datetime


Expand Down
170 changes: 163 additions & 7 deletions bot/libs/tickets/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
from typing import TYPE_CHECKING, Optional

import discord
from libs.tickets.structs import TicketThread
from libs.utils import ErrorEmbed, RoboModal, RoboView
from libs.tickets.structs import ReservedTags, TicketThread
from libs.utils import Embed, ErrorEmbed, RoboModal, RoboView

from .utils import register_user, safe_content

Expand All @@ -18,7 +18,7 @@


class TicketTitleModal(RoboModal, title="Ticket Title"):
def __init__(self, ctx: RoboContext, *args, **kwargs):
def __init__(self, ctx: RoboContext, ticket_cog: Tickets, *args, **kwargs):
super().__init__(ctx=ctx, *args, **kwargs)

self.title_input = discord.ui.TextInput(
Expand All @@ -29,19 +29,100 @@ def __init__(self, ctx: RoboContext, *args, **kwargs):
max_length=100,
)
self.input: Optional[str] = None
self.ticket_cog = ticket_cog
self.add_item(self.title_input)

async def on_submit(
self, interaction: discord.Interaction[Rodhaj]
) -> Optional[str]:
self.input = self.title_input.value
self.ticket_cog.in_progress_tickets[interaction.user.id].title.set()
await interaction.response.send_message(
f"The title of the ticket is set to: `{self.title_input.value}`",
f"The title of the ticket is set to:\n`{self.title_input.value}`",
ephemeral=True,
)
return self.input


class TicketTagsSelect(discord.ui.Select):
def __init__(self, tickets_cog: Tickets):
options = [
discord.SelectOption(
label="Question",
value="question",
description="Represents one or more question",
emoji=discord.PartialEmoji(name="\U00002753"),
),
discord.SelectOption(
label="Serious",
value="serious",
description="Represents a serious concern or question(s)",
emoji=discord.PartialEmoji(name="\U0001f610"),
),
discord.SelectOption(
label="Private",
value="private",
description="Represents a private concern or matter",
emoji=discord.PartialEmoji(name="\U0001f512"),
),
]
super().__init__(
placeholder="Select a tag...",
min_values=1,
max_values=3,
options=options,
row=0,
)
self.tickets_cog = tickets_cog
self.prev_selected: Optional[set] = None

def tick(self, status) -> str:
if status is True:
return "\U00002705"
return "\U0000274c"

async def callback(self, interaction: discord.Interaction[Rodhaj]) -> None:
values = self.values
in_progress_tag = self.tickets_cog.reserved_tags.get(interaction.user.id)
if in_progress_tag is None:
await interaction.response.send_message(
"Are there really any tags cached?", ephemeral=True
)
return

output_tag = ReservedTags(question=False, serious=False, private=False)

if interaction.user.id in self.tickets_cog.reserved_tags:
output_tag = in_progress_tag

current_selected = set(self.values)

if self.prev_selected is not None:
missing = self.prev_selected - current_selected
added = current_selected - self.prev_selected

combined = missing.union(added)

for tag in combined:
output_tag[tag] = not in_progress_tag[tag]
else:
for tag in values:
output_tag[tag] = not in_progress_tag[tag]

self.tickets_cog.reserved_tags[interaction.user.id] = output_tag
self.tickets_cog.in_progress_tickets[interaction.user.id].tags.set()
self.prev_selected = set(self.values)
formatted_str = "\n".join(
f"{self.tick(v)} - {k.title()}" for k, v in output_tag.items()
)
result = f"The following have been modified:\n\n{formatted_str}"

embed = Embed(title="Modified Tags")
embed.description = result
embed.set_footer(text="\U00002705 = Selected | \U0000274c = Unselected")
await interaction.response.send_message(embed=embed, ephemeral=True)


class TicketConfirmView(RoboView):
def __init__(
self,
Expand All @@ -62,6 +143,12 @@ def __init__(
self.triggered = asyncio.Event()
self.pool = self.bot.pool
self._modal = None
self.add_item(TicketTagsSelect(cog))

def tick(self, status) -> str:
if status is True:
return "\U00002705"
return "\U0000274c"

async def delete_response(self, interaction: discord.Interaction):
await interaction.response.defer()
Expand All @@ -70,11 +157,44 @@ async def delete_response(self, interaction: discord.Interaction):

self.stop()

@discord.ui.button(label="Set Title", style=discord.ButtonStyle.blurple, row=1)
@discord.ui.button(
label="Checklist",
style=discord.ButtonStyle.primary,
emoji=discord.PartialEmoji(name="\U00002753"),
row=1,
)
async def see_checklist(
self, interaction: discord.Interaction, button: discord.ui.Button
) -> None:
status = self.cog.in_progress_tickets.get(interaction.user.id)

if status is None:
await interaction.response.send_message(
"Unable to view checklist", ephemeral=True
)
return

dict_status = {"title": status.title, "tags": status.tags}
formatted_status = "\n".join(
f"{self.tick(v.is_set())} - {k.title()}" for k, v in dict_status.items()
)

embed = Embed()
embed.title = "\U00002753 Status Checklist"
embed.description = f"The current status is shown below:\n\n{formatted_status}"
embed.set_footer(text="\U00002705 = Completed | \U0000274c = Incomplete")
await interaction.response.send_message(embed=embed, ephemeral=True)

@discord.ui.button(
label="Set Title",
style=discord.ButtonStyle.primary,
emoji=discord.PartialEmoji(name="\U0001f58b"),
row=1,
)
async def set_title(
self, interaction: discord.Interaction, button: discord.ui.Button
) -> None:
self._modal = TicketTitleModal(self.ctx)
self._modal = TicketTitleModal(self.ctx, self.cog)
await interaction.response.send_modal(self._modal)

@discord.ui.button(
Expand All @@ -89,15 +209,48 @@ async def confirm(
await register_user(self.ctx.author.id, self.pool)
author = self.ctx.author

# TODO: Probably add user config defaults instead
thread_display_id = uuid.uuid4()
thread_name = f"{author.display_name} | {thread_display_id}"
title = self._modal.input if self._modal and self._modal.input else thread_name

tags = self.cog.reserved_tags.get(interaction.user.id)
status = self.cog.in_progress_tickets.get(interaction.user.id)
if tags is None or status is None:
await interaction.response.send_message(
"Unable to obtain reserved tags and in progress tags", ephemeral=True
)
return

applied_tags = [k for k, v in tags.items() if v is True]

if not status.title.is_set() or not status.tags.is_set():
dict_status = {"title": status.title, "tags": status.tags}
formatted_status = "\n".join(
f"{self.tick(v.is_set())} - {k.title()}" for k, v in dict_status.items()
)

embed = ErrorEmbed()
embed.title = "\U00002757 Unfinished Ticket"
embed.description = (
"Hold up! It seems like you have an unfinished ticket! "
"It's important to have a finished ticket "
"as it allows staff to work more efficiently. "
"Please refer to the checklist given below for which parts "
"that are incomplete.\n\n"
f"{formatted_status}"
"\n\nNote: In order to know, refer to the status checklist. "
'This can be found by clicking the "See Checklist" button. '
)
embed.set_footer(text="\U00002705 = Complete | \U0000274c = Incomplete")
await interaction.response.send_message(embed=embed, ephemeral=True)
return

ticket = TicketThread(
title=title,
user=author,
location_id=self.guild.id,
content=self.content,
tags=applied_tags,
created_at=discord.utils.utcnow(),
)
created_ticket = await self.cog.create_ticket(ticket)
Expand All @@ -117,6 +270,9 @@ async def confirm(
safe_content(self.content),
)

self.cog.reserved_tags.pop(self.ctx.author.id, None)
self.cog.in_progress_tickets.pop(self.ctx.author.id, None)

if self.message:
self.triggered.set()

Expand Down
13 changes: 9 additions & 4 deletions bot/rodhaj.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from cogs import EXTENSIONS, VERSION
from cogs.config import GuildWebhookDispatcher
from discord.ext import commands
from libs.tickets.structs import ReservedTags
from libs.tickets.structs import ReservedTags, StatusChecklist
from libs.tickets.utils import get_cached_thread, get_partial_ticket
from libs.tickets.views import TicketConfirmView
from libs.utils import RoboContext, RodhajCommandTree, send_error_embed
Expand Down Expand Up @@ -118,9 +118,12 @@ async def on_message(self, message: discord.Message) -> None:
if potential_ticket is None:
# This is for the tag selection system but Noelle is still working on that
tickets_cog: Tickets = self.get_cog("Tickets") # type: ignore
tickets_cog.reserved_tags[author.id] = ReservedTags(
question=True, serious=False, private=False
default_tags = ReservedTags(
question=False, serious=False, private=False
)
status_checklist = StatusChecklist()
tickets_cog.add_in_progress_tag(author.id, default_tags)
tickets_cog.add_status_checklist(author.id, status_checklist)
guild = self.get_guild(TRANSPROGRAMMER_SERVER_ID) or (
await self.fetch_guild(TRANSPROGRAMMER_SERVER_ID)
)
Expand All @@ -132,7 +135,9 @@ async def on_message(self, message: discord.Message) -> None:
embed.description = (
"Are you ready to create a ticket? "
"Before you click the `Confirm` button, please select the tags found in the dropdown menu. "
"Doing this step is crucial as these tags are used in order to help better sort tickets for the staff team."
"Doing this step is crucial as these tags are used in order to help better sort tickets for the staff team. "
"In addition, please set the title of your ticket using the `Set Title` button. "
"This will also help identify your ticket and streamline the process."
"\n\nNote: Once you have created your ticket, this prompt will not show up again"
)

Expand Down

0 comments on commit 06b4b26

Please sign in to comment.