Skip to content
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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions bot/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,17 @@ class _Icons(EnvConfig):
Icons = _Icons()


class _GithubModsRepository(EnvConfig):
section = "github_mods_"

owner: str = "python-discord"
name: str = "mods"
Copy link
Member

@vivekashok1221 vivekashok1221 Jun 14, 2023

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 or python-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 the python-discord/meta repo.

Copy link
Member

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.

Copy link
Member

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.

token: str
Copy link
Member

Choose a reason for hiding this comment

The 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 .env file as defined in constants). Maybe we can try making this optional. We also need to update the .env guide on the site https://www.pythondiscord.com/pages/guides/pydis-guides/contributing/bot/#appendix-full-env-file-options

Suggested change
token: str
token: str = ""

Copy link
Member Author

Choose a reason for hiding this comment

The 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!

Copy link
Member

@RohanJnr RohanJnr May 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we already have API_KEYS_GITHUB (its under _Keys as github) as optional, is the new token constant any different or does it use a different auth approach ?

Copy link
Member Author

Choose a reason for hiding this comment

The 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

Copy link
Member

Choose a reason for hiding this comment

The 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_"
Expand Down
99 changes: 98 additions & 1 deletion bot/exts/recruitment/talentpool/_cog.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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."""
Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Member

Choose a reason for hiding this comment

The 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:
Copy link
Member

@vivekashok1221 vivekashok1221 Jun 14, 2023

Choose a reason for hiding this comment

The 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
Expand Down