Skip to content

Commit

Permalink
feat: release feed backlog fetching
Browse files Browse the repository at this point in the history
  • Loading branch information
seven7ty committed Jun 16, 2024
2 parents bd82292 + 9586ecc commit a4ffa61
Show file tree
Hide file tree
Showing 9 changed files with 195 additions and 14 deletions.
4 changes: 2 additions & 2 deletions cogs/backend/workers/release_feed.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ async def handle_feed_repo(self,
guild: GitBotGuild,
repo: ReleaseFeedRepo,
rfi: ReleaseFeedItem,
new_release: dict) -> None:
new_release: dict, no_mention: bool = False) -> None:
stage: str = 'prerelease' if new_release['release']['isPrerelease'] else 'release'
if new_release['release']['isDraft']:
stage += ' draft'
Expand All @@ -91,7 +91,7 @@ async def handle_feed_repo(self,
embed.add_field(name=':notepad_spiral: Body:', value=body, inline=False)
embed.add_field(name=':mag_right: Info:', value=info)
await self.send_to_rfi(guild, rfi, embed,
self.bot.mgr.release_feed_mention_to_actual(rfi['mention']) if rfi.get('mention') else None)
self.bot.mgr.release_feed_mention_to_actual(rfi['mention']) if rfi.get('mention') and not no_mention else None)

async def handle_missing_feed_repo(self, guild: GitBotGuild, rfi: ReleaseFeedItem, repo: ReleaseFeedRepo) -> None:
embed: discord.Embed = discord.Embed(
Expand Down
12 changes: 6 additions & 6 deletions cogs/ecosystem/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
ReleaseFeedRepo, AutomaticConversionSettings,
GitBotUser)
from typing import Optional, Literal, Any
from lib.structs import GitBotEmbed, GitBotCommandState, GitBot
from lib.structs import GitBotEmbed, GitBotCommandState, GitBot, ReleaseFeedBacklogView
from lib.utils.regex import DISCORD_CHANNEL_MENTION_RE
from lib.utils.decorators import normalize_repository
from lib.structs.discord.context import GitBotContext
Expand Down Expand Up @@ -233,13 +233,12 @@ async def _try_convert() -> Optional[dict]:
mention: str = f'<#{selected_rfi["cid"]}>'
if len(selected_rfi['repos']) < 10:
if (repo_ := repo_.lower()) not in map(lambda r: r['name'], selected_rfi['repos']):
await self.bot.db.guilds.update_one(guild,
{'$push':
{f'feed.{guild["feed"].index(selected_rfi)}.repos':
ReleaseFeedRepo(name=repo_.lower(), tag=tag)}})
rfr: ReleaseFeedRepo = ReleaseFeedRepo(name=repo_, tag=tag)
await self.bot.db.guilds.update_one(guild, {'$push':
{f'feed.{guild["feed"].index(selected_rfi)}.repos': rfr}})
await ctx.success(ctx.fmt('success',
f'`{repo_}`',
mention))
mention), view=ReleaseFeedBacklogView(ctx, selected_rfi, rfr))
return GitBotCommandState.SUCCESS
else:
await ctx.error(ctx.fmt('already_logged', f'`{repo_}`', mention))
Expand Down Expand Up @@ -548,6 +547,7 @@ async def delete_feed_repo_command(self, ctx: GitBotContext, repo: GitHubReposit
if not guild or not feed:
return await ctx.error(ctx.l.generic.nonexistent.release_feed)
present_in: list[ReleaseFeedItem] = []
repo: str = repo.lower()
for rfi in feed:
if repo in [rfr['name'].lower() for rfr in rfi['repos']]:
present_in.append(rfi)
Expand Down
35 changes: 35 additions & 0 deletions lib/api/github/github.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,29 @@ async def wrapper(*args: tuple, **kwargs: dict) -> Any:

return wrapper

def _flatten_nodes(func: Callable) -> Callable[..., DictProxy | SnakeCaseDictProxy | None]:
"""
Flattens the nodes field in the response dict by copying the value up one level and removing the old key
:param func: The function to wrap (must return a dict-like interface)
:return: The wrapped functions output, with the nodes field flattened and removed
"""

@functools.wraps(func)
async def wrapper(*args: tuple, **kwargs: dict) -> Any:
result: dict | None = await func(*args, **kwargs)
if result is not None and isinstance(result, (DictProxy, SnakeCaseDictProxy, dict)):
result = SnakeCaseDictProxy(result)
paths: list[tuple[str, ...]] = get_all_dict_paths(result)
for path in paths:
if path[-1] == 'nodes' and len(get_nested_key(result, path[:-1])) == 1: # noqa
# this dict path contains a nodes field in the last position -
# we flatten it by copying the dict item up one level and effectively replacing the nodes field with it
set_nested_key(result, path[:-2] + (f'{path[-2]}',), get_nested_key(result, path))
return result

return wrapper


class GitHubQueryDebugInfo:
__ignorable_error_substrings__: tuple[str, ...] = (
Expand Down Expand Up @@ -437,3 +460,15 @@ async def get_user(self, user: GitHubUser) -> Optional[_ReturnDict]:
return await self.query(self.queries.user, Login=user, FromTime=YEAR_START,
ToTime=datetime.utcnow().strftime('%Y-%m-%dT%XZ'),
on_fail_return=None, transformer=transform_user)

@_wrap_proxy
@normalize_repository
async def get_latest_n_releases(self, repo: GitHubRepository, n: int = 5) -> list[_ReturnDict]:
return await self.query(self.queries.latest_releases, _Repo=repo, N=n,
on_fail_return=[], transformer=('repository', 'releases', 'nodes'))

@_wrap_proxy
@_flatten_nodes
@normalize_repository
async def get_latest_n_releases_with_repo(self, repo: GitHubRepository, n: int = 5) -> list[_ReturnDict]:
return await self.query(self.queries.latest_releases, _Repo=repo, N=n, on_fail_return=None, transformer=transform_latest_release)
9 changes: 6 additions & 3 deletions lib/api/github/transformations.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,14 @@ def transform_repo(repo_dict: _Transformable) -> _Transformable:

def transform_latest_release(release_dict: _Transformable) -> _Transformable:
release_dict = release_dict['repository']
release_dict['release'] = release_dict['latestRelease'] if release_dict['latestRelease'] else None
release_dict['release'] = release_dict['latestRelease'] if release_dict.get('latestRelease') else None # .get for parity with backlog fetching
release_dict['color'] = int(release_dict['primaryLanguage']['color'][1:], 16) if release_dict[
'primaryLanguage'] else 0x2f3136
del release_dict['primaryLanguage']
del release_dict['latestRelease']
try: # parity with backlog fetching
del release_dict['primaryLanguage']
del release_dict['latestRelease']
except KeyError:
pass
return release_dict


Expand Down
11 changes: 11 additions & 0 deletions lib/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,11 @@
from motor.motor_asyncio import AsyncIOMotorClient, AsyncIOMotorCollection # noqa
from typing import Optional, Callable, Any, Reversible, Iterable, Type, TYPE_CHECKING, Generator
if TYPE_CHECKING:
from cogs.backend.workers.release_feed import ReleaseFeedWorker
from lib.structs.discord.context import GitBotContext
from lib.api.github import GitHubAPI
from lib.structs.discord.bot import GitBot
from lib.typehints import ReleaseFeedItem, ReleaseFeedRepo
from lib.utils.dict_utils import *
from lib.typehints import AnyDict, ReleaseFeedItemMention, GitbotRepoConfig

Expand Down Expand Up @@ -1056,3 +1058,12 @@ def set_prefix(self, prefix: str, absolute: bool = True) -> None:
'\'%s\'', self.prefix.strip(), self_.get_last_call_from_callstack())

return _Formatter(ctx)

async def handle_backlog_request(self, ctx: 'GitBotContext', rfi: 'ReleaseFeedItem', rfr: 'ReleaseFeedRepo', n: int) -> int:
backlog = await self.bot.github.get_latest_n_releases_with_repo(rfr['name'], n)
rf_worker: 'ReleaseFeedWorker' = self.bot.get_cog('ReleaseFeedWorker') # noqa
for release in reversed(backlog['releases']):
# the release is weirdly wrapped for parity (laziness)
backlog['release'] = release # since we iterate sequentially, we can just overwrite the key each time
await rf_worker.handle_feed_repo({'_id': ctx.guild.id}, rfr, rfi, backlog, no_mention=True)
return len(backlog['releases'])
2 changes: 1 addition & 1 deletion lib/structs/discord/components/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
from .view_file import ViewFileButton
from .github_lines_view import GitHubLinesView
from .confirmation import ConfirmationView

from .feed_backlog_view import ReleaseFeedBacklogView
84 changes: 84 additions & 0 deletions lib/structs/discord/components/feed_backlog_view.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import discord
from typing import TYPE_CHECKING, Callable, Any, Coroutine

if TYPE_CHECKING:
from lib.typehints.db.guild.release_feed import ReleaseFeedRepo, ReleaseFeedItem
from lib.structs import GitBotContext

__all__: tuple = ('ReleaseFeedBacklogView',)


class ReleaseFeedBacklogView(discord.ui.View):
def __init__(self, ctx: 'GitBotContext', rfi: 'ReleaseFeedItem', rfr: 'ReleaseFeedRepo', timeout: int = 180):
super().__init__(timeout=timeout)
self.ctx = ctx
self.rfi, self.rfr = rfi, rfr
self.add_item(_FetchReleaseFeedBacklogButton(ctx=ctx))
self.fetch_btn = self.children[0]


class _FetchReleaseFeedBacklogButton(discord.ui.Button):
view: 'ReleaseFeedBacklogView'

def __init__(self, ctx: 'GitBotContext'):
self.ctx = ctx
super().__init__(label=self.ctx.l.config.feed.repo.backlog.button.label, row=0)

async def callback(self, interaction: discord.Interaction):
if interaction.user.id != self.ctx.author.id or self.disabled:
return

async def _closing_hook(): # close everything up so that we don't get double interactions
self.disabled = True
await interaction.message.edit(view=None)
await interaction.delete_original_response()

await interaction.response.send_message(self.ctx.l.config.feed.repo.backlog.how_many, ephemeral=True,
view=_ReleaseFeedBacklogWantedView(self.ctx, self.view.rfi,
self.view.rfr, self.view,
_closing_hook))


class _ReleaseFeedBacklogWantedView(discord.ui.View):
def __init__(self, ctx: 'GitBotContext', rfi: 'ReleaseFeedItem', rfr: 'ReleaseFeedRepo',
orig_view: 'ReleaseFeedBacklogView', after_hook: Callable[..., Coroutine[Any, Any, None]]):
super().__init__(timeout=orig_view.timeout)
self.ctx = ctx
self.rfi, self.rfr = rfi, rfr
self.orig_view = orig_view
self.after_hook = after_hook
self.add_item(_FetchReleaseFeedBacklogSelectMenu(ctx=ctx))


class _FetchReleaseFeedBacklogSelectMenu(discord.ui.Select):
view: '_ReleaseFeedBacklogWantedView'

def __init__(self, ctx: 'GitBotContext'):
self.ctx = ctx
options: list[discord.SelectOption] = [
discord.SelectOption(description=self.ctx.l.config.feed.repo.backlog.select.options.one,
emoji=self.ctx.bot.mgr.e.digits.pixel.one, label='1', value='1'),
discord.SelectOption(description=self.ctx.l.config.feed.repo.backlog.select.options.five,
emoji=self.ctx.bot.mgr.e.digits.pixel.five, label='5', value='5'),
discord.SelectOption(description=self.ctx.l.config.feed.repo.backlog.select.options.ten,
emoji=self.ctx.bot.mgr.e.digits.pixel.ten, label='10', value='10')
]

super().__init__(placeholder=self.ctx.l.config.feed.repo.backlog.select.placeholder, options=options)

async def callback(self, interaction: discord.Interaction):
await interaction.response.send_message(self.ctx.bot.mgr.e.dot_sep + ' ' +
self.ctx.l.config.feed.repo.backlog.fetching, ephemeral=True)
n: int = await self.view.ctx.bot.mgr.handle_backlog_request(self.ctx, self.view.rfi, self.view.rfr,
int(self.values[0]))
self.disabled = True
await self.view.after_hook()
if n:
await interaction.edit_original_response(
content=(self.ctx.bot.mgr.e.checkmark + ' ' +
self.ctx.l.config.feed.repo.backlog.fetched.format(n, '<#' + str(self.view.rfi['cid']) + '>')),
view=None)
else:
await interaction.edit_original_response(
content=f'{self.ctx.bot.mgr.e.circle_yellow} {self.ctx.l.config.feed.repo.backlog.no_backlog}',
view=None)
21 changes: 19 additions & 2 deletions resources/locale/en.locale.json
Original file line number Diff line number Diff line change
Expand Up @@ -533,7 +533,7 @@
"title": "Your GitBot Config",
"github": {
"logged_in_as": "You're logged into GitHub as {0}",
"not_logged_in": "You're not logged into GitHub"
"not_logged_in": "You're not logged into GitHub"
},
"qa": {
"heading": "**Quick Access:**",
Expand Down Expand Up @@ -651,7 +651,24 @@
"invalid_channel": "{0} is not a valid **channel number/mention** from the list! Send another one, or type `cancel` to exit.",
"success": "New {0} releases will now be logged in {1}!",
"cancelled": "Adding a repo to the release feed **cancelled.**",
"already_logged": "{0} releases are **already being logged** in {1}!"
"already_logged": "{0} releases are **already being logged** in {1}!",
"backlog": {
"button": {
"label": "Fetch some previous releases?"
},
"select": {
"placeholder": "Select a number of releases to fetch",
"options": {
"one": "Just one",
"five": "A few...",
"ten": "A whole lot!"
}
},
"how_many": "How many releases would you like to fetch?",
"fetching": "Fetching a backlog of available releases...",
"fetched": "**Done!** I managed to fetch a backlog of `{0}` releases; they're waiting in {1} right now.",
"no_backlog": "The repo doesn't have any releases to fetch, so your feed will be empty for now."
}
},
"mention": {
"embed": {
Expand Down
31 changes: 31 additions & 0 deletions resources/queries/latest_releases.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
query($Name: String!, $Owner: String!, $N: Int!) {
repository(name: $Name, owner: $Owner) {
url
usesCustomOpenGraphImage
openGraphImageUrl
primaryLanguage {
color
}
releases(first: $N, orderBy: {field: CREATED_AT, direction: DESC}) {
nodes {
isDraft
releaseAssets {
totalCount
}
descriptionHTML
publishedAt
tagName
url
createdAt
isPrerelease
isLatest
publishedAt
name
author {
login
url
}
}
}
}
}

0 comments on commit a4ffa61

Please sign in to comment.