-
-
Notifications
You must be signed in to change notification settings - Fork 675
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Track forgotten nominations #2553
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -618,6 +618,17 @@ class _Icons(EnvConfig): | |||||
Icons = _Icons() | ||||||
|
||||||
|
||||||
class _GithubModsRepository(EnvConfig): | ||||||
section = "github_mods_" | ||||||
|
||||||
owner: str = "python-discord" | ||||||
name: str = "mods" | ||||||
token: str | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This PR throws an error when the github token is missing (when it tries it get it from
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ha ! Yes. It didn't before because we weren't using pydantic at that time. Good catch, i will take care of it! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we already have There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Github introduced tokens per repo. So this one will be granular in regards to the repo where tasks will be created There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ah okay, cool |
||||||
|
||||||
|
||||||
GithubModsRepository = _GithubModsRepository() | ||||||
|
||||||
|
||||||
class _Keys(EnvConfig): | ||||||
|
||||||
EnvConfig.Config.env_prefix = "api_keys_" | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,15 +3,19 @@ | |
from datetime import UTC, datetime | ||
from io import StringIO | ||
|
||
import arrow | ||
import discord | ||
from async_rediscache import RedisCache | ||
from discord import Color, Embed, Member, PartialMessage, RawReactionActionEvent, User | ||
from discord.ext import commands, tasks | ||
from discord.ext.commands import BadArgument, Cog, Context, group, has_any_role | ||
from discord.utils import snowflake_time | ||
from pydis_core.site_api import ResponseCodeError | ||
|
||
from bot.bot import Bot | ||
from bot.constants import Bot as BotConfig, Channels, Emojis, Guild, MODERATION_ROLES, Roles, STAFF_ROLES | ||
from bot.constants import ( | ||
Bot as BotConfig, Channels, Emojis, GithubModsRepository, Guild, MODERATION_ROLES, Roles, STAFF_ROLES | ||
) | ||
from bot.converters import MemberOrUser, UnambiguousMemberOrUser | ||
from bot.exts.recruitment.talentpool._api import Nomination, NominationAPI | ||
from bot.exts.recruitment.talentpool._review import Reviewer | ||
|
@@ -22,7 +26,9 @@ | |
from bot.utils.members import get_or_fetch_member | ||
|
||
AUTOREVIEW_ENABLED_KEY = "autoreview_enabled" | ||
FLAG_EMOJI = "🎫" | ||
REASON_MAX_CHARS = 1000 | ||
OLD_NOMINATIONS_THRESHOLD_IN_DAYS = 14 | ||
|
||
# The number of days that a user can have no activity (no messages sent) | ||
# until they should be removed from the talentpool. | ||
|
@@ -50,6 +56,11 @@ async def cog_load(self) -> None: | |
if await self.autoreview_enabled(): | ||
self.autoreview_loop.start() | ||
|
||
if not GithubModsRepository.token: | ||
log.warning(f"No token for the {GithubModsRepository.name} repository was provided.") | ||
else: | ||
self.track_forgotten_nominations.start() | ||
|
||
self.prune_talentpool.start() | ||
|
||
async def autoreview_enabled(self) -> bool: | ||
|
@@ -652,6 +663,92 @@ async def _nomination_to_string(self, nomination: Nomination) -> str: | |
|
||
return lines.strip() | ||
|
||
@tasks.loop(hours=72) | ||
async def track_forgotten_nominations(self) -> None: | ||
"""Track active nominations who are more than 2 weeks old.""" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I feel like it would be more accurate to use the term active reviews instead of active nominations because active nomination refers to candidates in the talentpool who may or may not be currently undrgoing review in #nominations-voting. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or at least that's how I heard the phrase "active nominations" being used. |
||
old_nominations = await self._get_forgotten_nominations() | ||
untracked_nominations = await self._find_untracked_nominations(old_nominations) | ||
for nomination, thread in untracked_nominations: | ||
issue_created = await self._track_vote_in_github(nomination, thread.jump_url) | ||
if issue_created: | ||
await thread.starter_message.add_reaction(FLAG_EMOJI) | ||
|
||
async def _get_forgotten_nominations(self) -> list[Nomination]: | ||
"""Get active nominations that are more than 2 weeks old.""" | ||
now = arrow.utcnow() | ||
nominations = [ | ||
nomination | ||
for nomination in await self.api.get_nominations(active=True) | ||
if ( | ||
nomination.thread_id and | ||
(now - snowflake_time(nomination.thread_id)).days >= OLD_NOMINATIONS_THRESHOLD_IN_DAYS | ||
) | ||
] | ||
return nominations | ||
|
||
async def _find_untracked_nominations( | ||
self, | ||
nominations: list[Nomination] | ||
) -> list[tuple[Nomination, discord.Thread]]: | ||
""" | ||
Returns a list of tuples representing a nomination and its vote message. | ||
|
||
All active nominations are iterated over to identify whether they're tracked or not | ||
by checking whether the nomination message has the "🎫" emoji or not. | ||
""" | ||
untracked_nominations = [] | ||
|
||
for nomination in nominations: | ||
# We avoid the scenario of this task run & nomination created at the same time | ||
if not nomination.thread_id: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am not sure I understand the purpose of this statement because it looks like it's taken care of here . Is it here in case this method is used somewhere else in a different scenario? |
||
continue | ||
|
||
try: | ||
thread = await get_or_fetch_channel(nomination.thread_id) | ||
except discord.NotFound: | ||
log.debug(f"Couldn't find thread {nomination.thread_id}") | ||
continue | ||
|
||
starter_message = thread.starter_message | ||
if not starter_message: | ||
# Starter message will be null if it's not cached | ||
try: | ||
starter_message = await self.bot.get_channel(Channels.nomination_voting).fetch_message(thread.id) | ||
except discord.NotFound: | ||
log.debug(f"Couldn't find message {thread.id} in channel: {Channels.nomination_voting}") | ||
continue | ||
|
||
if FLAG_EMOJI in [reaction.emoji for reaction in starter_message.reactions]: | ||
# Nomination has been already tracked in GitHub | ||
continue | ||
|
||
untracked_nominations.append((nomination, thread)) | ||
return untracked_nominations | ||
|
||
async def _track_vote_in_github(self, nomination: Nomination, vote_jump_url: str | None = None) -> bool: | ||
""" | ||
Adds an issue in GitHub to track dormant vote. | ||
|
||
Returns True when the issue has been created, False otherwise. | ||
""" | ||
url = f"https://api.github.com/repos/{GithubModsRepository.owner}/{GithubModsRepository.name}/issues" | ||
headers = { | ||
"Accept": "application/vnd.github.v3+json", | ||
"Authorization": f"Bearer {GithubModsRepository.token}" | ||
} | ||
member = await get_or_fetch_member(self.bot.get_guild(Guild.id), nomination.user_id) | ||
if not member: | ||
log.debug(f"Couldn't find member: {nomination.user_id}") | ||
return False | ||
|
||
data = {"title": f"Nomination review needed. Id: {nomination.id}. User: {member.name}"} | ||
if vote_jump_url: | ||
data["body"] = f"Jump to the [vote message]({vote_jump_url})" | ||
|
||
async with self.bot.http_session.post(url=url, raise_for_status=True, headers=headers, json=data) as response: | ||
log.debug(f"Creating a review reminder issue for user {member.name}") | ||
return response.status == 201 | ||
|
||
async def cog_unload(self) -> None: | ||
"""Cancels the autoreview loop on cog unload.""" | ||
# Only cancel the loop task when the autoreview code is not running | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I very vaguely remember the discussion reagrding this feature so I would like to double check whether the issue needs to be raised on the
python-discord/mods
repo orpython-discord/admins
(?) repo.Whenever a vote has passed the upvote threshold, the admins add a ticket to the admin tasks tracker** and not
python-discord/mods
repo (AFAICT) - which is why I'm asking for clarification on which repo stale reviews should be posted.**I'm not actually sure what the "admin tasks tracker" is, I always assumed it's a bunch of issues raised on
python-discord/admins
or something, similar to thepython-discord/meta
repo.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, admin tasks tracker is the
admin-tasks
repo.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, the issue is to be raised on
python-discord/admin-tasks
, Chris just confirmed.