async def channels(self, ctx): # TODO: allow guild admins to use this, restricted to single guild embed = Embed( timestamp=ctx.message.created_at, color=0x50f3d7, title='Bug Reporting Channels') guild_row = Guild.get_or_none(serverid=ctx.guild.id) guild_channels = [] non_guild_channels = dict() for row in BugReportingPlatform.select(): for channel_row in row.bug_channels: channel = self.bot.get_channel(channel_row.channelid) if not channel: channel_row.delete_instance() continue description = f"{row.platform}/{row.branch}: {channel.mention}" if channel_row.guild == guild_row: guild_channels.append(description) else: # TODO: get guild names and add to description if channel_row.guild.serverid not in non_guild_channels: non_guild_channels[channel_row.guild.serverid] = [] non_guild_channels[channel_row.guild.serverid].append(description) if guild_channels: embed.add_field(name=f'`{ctx.guild.name}` server', value="\n".join(guild_channels)) for guild_id, channel_list in non_guild_channels.items(): guild = self.bot.get_guild(guild_id) server_name = self.bot.get_guild(guild_id).name or f"[{guild_id}][MISSING GUILD]" embed.add_field(name=f'`{server_name}` server', value="\n".join(channel_list)) if not guild_channels and not non_guild_channels: await ctx.send("There are no configured bug reporting channels") else: await ctx.send(embed=embed)
async def send_report(): # save report in the database br = BugReport.create(reporter=user.id, platform=platform, deviceinfo=deviceinfo, platform_version=platform_version, branch=branch, app_version=app_version, app_build=app_build, title=title, steps=steps, expected=expected, actual=actual, additional=additional_text) for url in attachment_links: Attachments.create(report=br, url=url) # send report channel_name = f"{platform}_{branch}".lower() report_id_saved = False attachment_id_saved = False user_reported_channels = list() all_reported_channels = list() selected_platform = BugReportingPlatform.get(platform=platform, branch=branch) for row in BugReportingChannel.select().where(BugReportingChannel.platform == selected_platform): report_channel = self.bot.get_channel(row.channelid) message = await report_channel.send( content=Lang.get_locale_string("bugs/report_header", ctx, id=br.id, user=user.mention), embed=report) attachment = None if len(attachment_links) != 0: key = "attachment_info" if len(attachment_links) == 1 else "attachment_info_plural" attachment = await report_channel.send( Lang.get_locale_string(f"bugs/{key}", ctx, id=br.id, links="\n".join(attachment_links))) if report_channel.guild.id == Configuration.get_var('guild_id'): # Only save report and attachment IDs for posts in the official server if not report_id_saved and not attachment_id_saved: if attachment is not None: br.attachment_message_id = attachment.id attachment_id_saved = True br.message_id = message.id report_id_saved = True br.save() user_reported_channels.append(report_channel.mention) else: # guild is not the official server. if author is member, include user_reported_channels this_guild = self.bot.get_guild(report_channel.guild.id) if this_guild.get_member(user.id) is not None: user_reported_channels.append(report_channel.mention) all_reported_channels.append(report_channel) channels_mentions = [] channels_ids = set() if not all_reported_channels: await Logging.bot_log(f"no report channels for bug report #{br.id}") for report_channel in all_reported_channels: channels_mentions.append(report_channel.mention) channels_ids.add(report_channel.id) await channel.send( Lang.get_locale_string("bugs/report_confirmation", ctx, channel_info=', '.join(channels_mentions))) await self.send_bug_info(*channels_ids)
def get_platform(pl): platforms = dict() for row in BugReportingPlatform.select(): platforms[row.platform.lower()] = row.platform pl = pl.lower() if pl in platforms: return [platforms[pl]] return ["Android", "iOS", "Switch"]
def get_branch(br): platforms = dict() branches = set() for row in BugReportingPlatform.select(): branches.add(row.branch) if row.branch not in platforms: platforms[row.branch] = set() platforms[row.branch].add(row.platform) br = br.lower().capitalize() if br in branches: return [br] return ["Beta", "Stable"]
async def add_channel(self, ctx, channel: TextChannel, platform, branch): try: guild_row = Guild.get(serverid=ctx.guild.id) except DoesNotExist: await ctx.send(f"I couldn't find a record for guild id {ctx.guild.id}... call a plumber!") return try: platform_row = BugReportingPlatform.get(platform=platform, branch=branch) except DoesNotExist: await ctx.send(f"I couldn't find a record for platform/branch `{platform}`/`{branch}`") return try: record, created = BugReportingChannel.get_or_create(guild=guild_row, platform=platform_row, channelid=channel.id) except IntegrityError: await ctx.send(f"channel{channel.mention} is already in used for bug reporting") return if created: await ctx.send(f"{channel.mention} will now be used to record `{platform}/{branch}` bug reports") else: await ctx.send(f"{channel.mention} was already configured for `{platform}/{branch}` bug reports")
async def platforms(self, ctx): platforms = dict() for row in BugReportingPlatform.select(): if row.branch in platforms: if row.platform in platforms[row.branch]: await self.bot.guild_log(ctx.guild.id, f"duplicate platform in db: {row.platform}/{row.branch}") if row.branch not in platforms: platforms[row.branch] = list() platforms[row.branch].append(row.platform) embed = Embed( timestamp=ctx.message.created_at, color=0x50f3d7, title='Bug Reporting Platforms') for branch, platforms in platforms.items(): embed.add_field(name=branch, value='\n'.join(platforms), inline=False) if not platforms: await ctx.send("There are no bug reporting platforms in my database") else: await ctx.send(embed=embed)
async def actual_bug_reporter(self, user, trigger_channel): # wrap everything so users can't get stuck in limbo m = self.bot.metrics active_question = None restarting = False try: channel = await user.create_dm() last_message = await trigger_channel.history(limit=1).flatten() last_message = last_message[0] ctx = await self.bot.get_context(last_message) # vars to store everything asking = True platform = "" branch = "" app_build = None additional = False additional_text = "" attachments = False attachment_links = [] report = None # define all the parts we need as inner functions for easier sinfulness async def abort(): nonlocal asking await user.send(Lang.get_locale_string("bugs/abort_report", ctx)) asking = False m.reports_abort_count.inc() m.reports_exit_question.observe(active_question) await self.delete_progress(user.id) def set_platform(p): nonlocal platform platform = p def set_branch(b): nonlocal branch branch = b def add_additional(): nonlocal additional additional = True def add_attachments(): nonlocal attachments attachments = True def verify_version(v): if "latest" in v: return Lang.get_locale_string("bugs/latest_not_allowed", ctx) # TODO: double check if we actually want to enforce this if len(Utils.NUMBER_MATCHER.findall(v)) == 0: return Lang.get_locale_string("bugs/no_numbers", ctx) if len(v) > 20: return Lang.get_locale_string("bugs/love_letter", ctx) return True def max_length(length): def real_check(text): if len(text) > length: return Lang.get_locale_string("bugs/text_too_long", ctx, max=length) return True return real_check async def send_report(): # save report in the database br = BugReport.create(reporter=user.id, platform=platform, deviceinfo=deviceinfo, platform_version=platform_version, branch=branch, app_version=app_version, app_build=app_build, title=title, steps=steps, expected=expected, actual=actual, additional=additional_text) for url in attachment_links: Attachments.create(report=br, url=url) # send report channel_name = f"{platform}_{branch}".lower() report_id_saved = False attachment_id_saved = False user_reported_channels = list() all_reported_channels = list() selected_platform = BugReportingPlatform.get(platform=platform, branch=branch) for row in BugReportingChannel.select().where(BugReportingChannel.platform == selected_platform): report_channel = self.bot.get_channel(row.channelid) message = await report_channel.send( content=Lang.get_locale_string("bugs/report_header", ctx, id=br.id, user=user.mention), embed=report) attachment = None if len(attachment_links) != 0: key = "attachment_info" if len(attachment_links) == 1 else "attachment_info_plural" attachment = await report_channel.send( Lang.get_locale_string(f"bugs/{key}", ctx, id=br.id, links="\n".join(attachment_links))) if report_channel.guild.id == Configuration.get_var('guild_id'): # Only save report and attachment IDs for posts in the official server if not report_id_saved and not attachment_id_saved: if attachment is not None: br.attachment_message_id = attachment.id attachment_id_saved = True br.message_id = message.id report_id_saved = True br.save() user_reported_channels.append(report_channel.mention) else: # guild is not the official server. if author is member, include user_reported_channels this_guild = self.bot.get_guild(report_channel.guild.id) if this_guild.get_member(user.id) is not None: user_reported_channels.append(report_channel.mention) all_reported_channels.append(report_channel) channels_mentions = [] channels_ids = set() if not all_reported_channels: await Logging.bot_log(f"no report channels for bug report #{br.id}") for report_channel in all_reported_channels: channels_mentions.append(report_channel.mention) channels_ids.add(report_channel.id) await channel.send( Lang.get_locale_string("bugs/report_confirmation", ctx, channel_info=', '.join(channels_mentions))) await self.send_bug_info(*channels_ids) async def restart(): nonlocal restarting restarting = True m.reports_restarted.inc() await self.delete_progress(user.id) self.bot.loop.create_task(self.report_bug(user, trigger_channel)) # start global report timer and question timer report_start_time = question_start_time = time.time() m.reports_started.inc() def update_metrics(): nonlocal active_question nonlocal question_start_time now = time.time() question_duration = now - question_start_time question_start_time = now # Record the time taken to answer the previous question gauge = getattr(m, f"reports_question_{active_question}_duration") gauge.set(question_duration) active_question = active_question + 1 active_question = 0 await Questions.ask(self.bot, channel, user, Lang.get_locale_string("bugs/question_ready", ctx), [ Questions.Option("YES", "Press this reaction to answer YES and begin a report"), Questions.Option("NO", "Press this reaction to answer NO", handler=abort), ], show_embed=True, locale=ctx) update_metrics() if asking: # question 1: android or ios? platforms = set() options = [] for platform_row in BugReportingPlatform.select(): platforms.add(platform_row.platform) for platform_name in platforms: options.append( Questions.Option( platform_name.upper(), platform_name, set_platform, [platform_name])) await Questions.ask(self.bot, channel, user, Lang.get_locale_string("bugs/question_platform", ctx), options, show_embed=True, locale=ctx) update_metrics() # question 2: android/ios version platform_version = await Questions.ask_text(self.bot, channel, user, Lang.get_locale_string("bugs/question_platform_version", ctx, platform=platform), validator=verify_version, locale=ctx) update_metrics() # question 3: hardware info device_info_platform = Lang.get_locale_string(f"bugs/device_info_{platform.lower()}", ctx) deviceinfo = await Questions.ask_text(self.bot, channel, user, Lang.get_locale_string("bugs/question_device_info", ctx, platform=platform, device_info_help=device_info_platform, max=200), validator=max_length(200), locale=ctx) update_metrics() # question 4: stable or beta? branches = set() for platform_row in BugReportingPlatform.select(): if platform_row.platform == platform: branches.add(platform_row.branch) if len(branches) == 0: branch = "NONE" elif len(branches) == 1: branch = branches.pop() else: options = [] for branch_name in branches: branch_display_name = "Live" if branch_name.lower() == 'stable' else branch_name options.append( Questions.Option( branch_name.upper(), branch_display_name, set_branch, [branch_name])) await Questions.ask(self.bot, channel, user, Lang.get_locale_string("bugs/question_app_branch", ctx), options, show_embed=True, locale=ctx) update_metrics() # question 5: sky app version app_version = await Questions.ask_text( self.bot, channel, user, Lang.get_locale_string( "bugs/question_app_version", ctx, version_help=Lang.get_locale_string("bugs/version_" + platform.lower())), validator=verify_version, locale=ctx) update_metrics() # question 6: sky app build number app_build = await Questions.ask_text( self.bot, channel, user, Lang.get_locale_string("bugs/question_app_build", ctx), validator=verify_version, locale=ctx) update_metrics() # question 7: Title title = await Questions.ask_text( self.bot, channel, user, Lang.get_locale_string("bugs/question_title", ctx, max=300), validator=max_length(300), locale=ctx) update_metrics() # question 8: "actual" - defect behavior actual = await Questions.ask_text( self.bot, channel, user, Lang.get_locale_string("bugs/question_actual", ctx, max=800), validator=max_length(800), locale=ctx) update_metrics() # question 9: steps to reproduce steps = await Questions.ask_text( self.bot, channel, user, Lang.get_locale_string("bugs/question_steps", ctx, max=800), validator=max_length(800), locale=ctx) update_metrics() # question 10: expected behavior expected = await Questions.ask_text( self.bot, channel, user, Lang.get_locale_string("bugs/question_expected", ctx, max=800), validator=max_length(800), locale=ctx) update_metrics() # question 11: attachments y/n await Questions.ask( self.bot, channel, user, Lang.get_locale_string("bugs/question_attachments", ctx), [ Questions.Option("YES", Lang.get_locale_string("bugs/attachments_yes", ctx), handler=add_attachments), Questions.Option("NO", Lang.get_locale_string("bugs/skip_step", ctx)) ], show_embed=True, locale=ctx) update_metrics() if attachments: # question 12: attachments attachment_links = await Questions.ask_attachements(self.bot, channel, user, locale=ctx) # update metrics outside condition to keep count up-to-date and reflect skipped question as zero time update_metrics() # question 13: additional info y/n await Questions.ask( self.bot, channel, user, Lang.get_locale_string("bugs/question_additional", ctx), [ Questions.Option("YES", Lang.get_locale_string("bugs/additional_info_yes", ctx), handler=add_additional), Questions.Option("NO", Lang.get_locale_string("bugs/skip_step", ctx)) ], show_embed=True, locale=ctx) update_metrics() if additional: # question 14: additional info additional_text = await Questions.ask_text( self.bot, channel, user, Lang.get_locale_string("bugs/question_additional_info", ctx), validator=max_length(500), locale=ctx) # update metrics outside condition to keep count up-to-date and reflect skipped question as zero time update_metrics() # assemble the report and show to user for review report = Embed(timestamp=datetime.utcfromtimestamp(time.time())) report.set_author(name=f"{user} ({user.id})", icon_url=user.avatar_url_as(size=32)) report.add_field( name=Lang.get_locale_string("bugs/platform", ctx), value=f"{platform} {platform_version}") report.add_field( name=Lang.get_locale_string("bugs/app_version", ctx), value=app_version) report.add_field( name=Lang.get_locale_string("bugs/app_build", ctx), value=app_build) report.add_field( name=Lang.get_locale_string("bugs/device_info", ctx), value=deviceinfo, inline=False) report.add_field( name=Lang.get_locale_string("bugs/title", ctx), value=title, inline=False) report.add_field( name=Lang.get_locale_string("bugs/description", ctx), value=actual, inline=False) report.add_field( name=Lang.get_locale_string("bugs/steps_to_reproduce", ctx), value=steps, inline=False) report.add_field( name=Lang.get_locale_string("bugs/expected", ctx), value=expected) if additional: report.add_field( name=Lang.get_locale_string("bugs/additional_info", ctx), value=additional_text, inline=False) await channel.send( content=Lang.get_locale_string("bugs/report_header", ctx, id="##", user=user.mention), embed=report) if attachment_links: attachment_message = '' for a in attachment_links: attachment_message += f"{a}\n" await channel.send(attachment_message) review_time = 300 await asyncio.sleep(1) # Question 15 - final review await Questions.ask( self.bot, channel, user, Lang.get_locale_string("bugs/question_ok", ctx, timeout=Questions.timeout_format(review_time)), [ Questions.Option("YES", Lang.get_locale_string("bugs/send_report", ctx), send_report), Questions.Option("NO", Lang.get_locale_string("bugs/mistake", ctx), restart) ], show_embed=True, timeout=review_time, locale=ctx) update_metrics() report_duration = time.time() - report_start_time m.reports_duration.set(report_duration) else: return except Forbidden as ex: m.bot_cannot_dm_member.inc() await trigger_channel.send( Lang.get_locale_string("bugs/dm_unable", ctx, user=user.mention), delete_after=30) except asyncio.TimeoutError as ex: m.report_incomplete_count.inc() await channel.send(Lang.get_locale_string("bugs/report_timeout", ctx)) if active_question is not None: m.reports_exit_question.observe(active_question) except CancelledError as ex: m.report_incomplete_count.inc() if active_question is not None: m.reports_exit_question.observe(active_question) if not restarting: raise ex except Exception as ex: await Utils.handle_exception("bug reporting", self.bot, ex) raise ex finally: self.bot.loop.create_task(self.delete_progress(user.id))
async def add_platform(self, ctx, platform, branch): row, create = BugReportingPlatform.get_or_create(platform=platform, branch=branch) if create: await ctx.send(f"Ok, I added `{platform}/{branch}` to my database") else: await ctx.send(f"That platform/branch combination is already in my database")