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

Create an admin task when votes are 2 weeks old #2323

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
4f235c8
add thread_id to pydantic model
Nov 6, 2022
b1845e5
add thread mentions to review history
Nov 6, 2022
99d9662
add list of threads to a review's markdown file
Nov 6, 2022
18e7cbe
replace thread mention with jump url
Nov 7, 2022
71d6ebc
add style to thread not found message
Nov 7, 2022
293dac8
use get_or_fetch_channel to look from thread
Nov 7, 2022
f6f3e55
use jump_url in the review markdown file
Nov 7, 2022
33828c8
add emtpy task to track forgotten nominations
Nov 12, 2022
ecc29bd
fetch forgotten nominations from site
Nov 12, 2022
8bd6840
filter out untracked nominations in GitHub
Nov 12, 2022
c4be56d
Merge branch '2304-link-previous-nomination-threads' into 2226-track-…
Nov 12, 2022
346be1a
filter out untracked nominations in GitHub
Nov 12, 2022
ed728d4
return a nominationXMessage tuple
Nov 12, 2022
506fbb2
add dummy method that tracks vote in GitHub
Nov 12, 2022
0ba1ddf
use "🎫" as emoji
Nov 12, 2022
9773aca
add github_admin_repo section
Nov 13, 2022
0da9f16
add loader for the github_admin_repo section
Nov 13, 2022
fef37fe
log repo name when token hasn't been provided
Nov 13, 2022
9259f84
reset value of autoreview task
Nov 13, 2022
161df87
change repo name to 'admin-tasks'
shtlrs Nov 27, 2022
4b0d49b
add debug log upon creating Github issue
shtlrs Nov 27, 2022
154c51d
handle case where member cannot be found upon tracking vote
shtlrs Nov 27, 2022
802003b
handle case where thread cannot be found when filtering tracked nomin…
shtlrs Nov 27, 2022
5a916ac
handle case where thread starter message cannot be found when filteri…
shtlrs Nov 27, 2022
68c625c
replace usage of typing.List with native list
shtlrs Nov 27, 2022
3ca37a0
use log.warning instead of deprecetated log.warn
shtlrs Nov 27, 2022
1c8059a
update name of the method that identifies untracked nominations
shtlrs Nov 27, 2022
da3c904
add jump url to the issue's body
shtlrs Nov 27, 2022
59319b4
Merge branch 'main' into 2226-track-old-votes-in-github
shtlrs Jan 1, 2023
33415da
move Github token check to `cog_load`
Nov 6, 2022
babf906
use review start date instead of nomination inserted_at
shtlrs Jan 8, 2023
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
8 changes: 8 additions & 0 deletions bot/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,14 @@ class VideoPermission(metaclass=YAMLGetter):
default_permission_duration: int


class GithubAdminRepo(metaclass=YAMLGetter):
section = "github_admin_repo"

owner: str
name: str
token: str


class ThreadArchiveTimes(Enum):
HOUR = 60
DAY = 1440
Expand Down
101 changes: 99 additions & 2 deletions bot/exts/recruitment/talentpool/_cog.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import asyncio
import textwrap
from io import StringIO
from typing import Optional, Union
from typing import Optional, Tuple, Union

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, GithubAdminRepo, Guild, MODERATION_ROLES, Roles, STAFF_ROLES
)
from bot.converters import MemberOrUser, UnambiguousMemberOrUser
from bot.exts.recruitment.talentpool._review import Reviewer
from bot.log import get_logger
Expand All @@ -23,7 +27,9 @@
from ._api import Nomination, NominationAPI

AUTOREVIEW_ENABLED_KEY = "autoreview_enabled"
FLAG_EMOJI = "🎫"
REASON_MAX_CHARS = 1000
OLD_NOMINATIONS_THRESHOLD_IN_DAYS = 14

log = get_logger(__name__)

Expand All @@ -44,6 +50,11 @@ def __init__(self, bot: Bot) -> None:

async def cog_load(self) -> None:
"""Start autoreview loop if enabled."""
if not GithubAdminRepo.token:
log.warning(f"No token for the {GithubAdminRepo.name} repository was provided, skipping issue creation.")
else:
self.track_forgotten_nominations.start()

if await self.autoreview_enabled():
self.autoreview_loop.start()

Expand Down Expand Up @@ -112,6 +123,92 @@ async def autoreview_status(self, ctx: Context) -> None:
else:
await ctx.send("Autoreview is currently disabled.")

@tasks.loop(hours=72)
async def track_forgotten_nominations(self) -> None:
"""Track active nominations who are more than 2 weeks old."""
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:
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/{GithubAdminRepo.owner}/{GithubAdminRepo.name}/issues"
headers = {
"Accept": "application/vnd.github.v3+json",
"Authorization": f"Bearer {GithubAdminRepo.token}"
}
member = await get_or_fetch_member(self.bot.get_guild(Guild.id), nomination.user_id)
ChrisLovering marked this conversation as resolved.
Show resolved Hide resolved
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}"}
ChrisLovering marked this conversation as resolved.
Show resolved Hide resolved
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

@tasks.loop(hours=1)
async def autoreview_loop(self) -> None:
"""Send request to `reviewer` to send a nomination if ready."""
Expand Down
8 changes: 7 additions & 1 deletion config-default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -545,7 +545,7 @@ voice_gate:


branding:
cycle_frequency: 3 # How many days bot wait before refreshing server icon
cycle_frequency: 3 # How many days the bot waits before refreshing server icon


config:
Expand All @@ -554,3 +554,9 @@ config:

video_permission:
default_permission_duration: 5 # Default duration for stream command in minutes


github_admin_repo:
owner: 'python-discord'
name: 'admin-tasks'
token: !ENV ["ADMIN_GITHUB_REPO_TOKEN", ""]