From b43c0dbd079d38df74fe32c3a89e18378899ea05 Mon Sep 17 00:00:00 2001 From: Lukas Brabec Date: Mar 08 2021 13:39:55 +0000 Subject: Show discussion vote counts in web UI and IRC format Fixes: https://pagure.io/fedora-qa/blockerbugs/issue/150 Merges: https://pagure.io/fedora-qa/blockerbugs/pull-request/172 --- diff --git a/alembic/versions/72031671860e_added_votes_to_bug.py b/alembic/versions/72031671860e_added_votes_to_bug.py new file mode 100644 index 0000000..2f5b238 --- /dev/null +++ b/alembic/versions/72031671860e_added_votes_to_bug.py @@ -0,0 +1,27 @@ +"""Added votes to bug + +Revision ID: 72031671860e +Revises: 4eac64cdecd3 +Create Date: 2021-02-04 11:14:11.208780 + +""" + +# revision identifiers, used by Alembic. +revision = '72031671860e' +down_revision = '4eac64cdecd3' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('bug', sa.Column('votes', sa.Text(), + server_default=sa.sql.text('"{}"'), nullable=False)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('bug', 'votes') + # ### end Alembic commands ### diff --git a/blockerbugs/controllers/main.py b/blockerbugs/controllers/main.py index 7270ced..e168bb5 100644 --- a/blockerbugs/controllers/main.py +++ b/blockerbugs/controllers/main.py @@ -25,9 +25,12 @@ import datetime from sqlalchemy import func, desc, or_, and_ import bugzilla from flask_fas_openid import fas_login_required +import json +import itertools from blockerbugs import app, db, __version__ from blockerbugs.util.bz_interface import BlockerProposal, BZInterfaceError +from blockerbugs.util import pagure_bot from blockerbugs.models.bug import Bug from blockerbugs.models.milestone import Milestone from blockerbugs.models.update import Update @@ -178,6 +181,88 @@ def get_milestone_info(milestone): } +def bugz_to_votes(bugz): + """Returns votes for each bug in bugz in format: + { + bugid: { + tracker_name: { + "-1": ["nick1", "nick2"], + "0": [], + "+1": ["nick3"], + } + } + } + """ + # ignore milestone key and flatten + all_bugz = itertools.chain(*bugz.values()) + votes = {} + for bug in all_bugz: + votes[bug.bugid] = json.loads(bug.votes) + return votes + + +def irc_voting_info(bugz): + """Returns voting summary in IRC format for each bug in bugz: + { + bugid: "#info Ticket vote: ...\n#info Ticket vote: ..." + } + """ + voting_info = {} + all_votes = bugz_to_votes(bugz) + for bugid, votes in all_votes.items(): + info = "" + for tracker, vote in votes.items(): + # if no one voted +1 or -1, don't show the info at all + if not (vote['-1'] or vote['+1']): + continue + summary = f"(+{len(vote['+1'])},{len(vote['0'])},-{len(vote['-1'])})" + pros = [f"+{person_vote}" for person_vote in vote['+1']] + neutrals = [f"{person_vote}" for person_vote in vote['0']] + cons = [f"-{person_vote}" for person_vote in vote['-1']] + people = ", ".join(pros + neutrals + cons) + info += f"#info Ticket vote: {pagure_bot.NICER[tracker]} {summary} ({people})\n" + voting_info[bugid] = info.strip() + + return voting_info + + +def vote_count_tuple(votes, tracker_name): + """Returns voting tuple for given tracker_name in format: + (6, 0, 3) or None + """ + vote = votes.get(tracker_name) + if not vote: + return None + + return (len(vote['+1']), len(vote['0']), len(vote['-1'])) + + +def web_voting_info(bugz, milestone): + """Returns voting tuple for each bug and each section in web UI, + a dict in dict structure in format: + { + bugid: { + 'Prioritized Bugs': None, + 'Proposed Blockers': (6, 0, 3), + ... + } + } + """ + voting_info = {} + all_votes = bugz_to_votes(bugz) + for bugid, votes in all_votes.items(): + voting_info[bugid] = { + 'Proposed Blockers': vote_count_tuple(votes, f'{milestone}blocker'), + 'Accepted Blockers': vote_count_tuple(votes, f'{milestone}blocker'), + 'Proposed Freeze Exceptions': vote_count_tuple(votes, f'{milestone}freezeexception'), + 'Accepted Freeze Exceptions': vote_count_tuple(votes, f'{milestone}freezeexception'), + 'Accepted 0-day Blockers': vote_count_tuple(votes, '0day'), + 'Accepted Previous Release Blockers': vote_count_tuple(votes, 'previousrelease'), + 'Prioritized Bugs': None # no discussion for prioritized bugs + } + return voting_info + + @main.route('/') def index(): if app.debug: @@ -205,13 +290,15 @@ def display_buglist(num, release_name): bugz = get_milestone_bugs(milestone) recent_bugs = get_recent_modifications(milestone.id) whiteboard_change = get_recent_whiteboard_change(milestone.id) + vote_info = web_voting_info(bugz, milestone.version) return render_template('blocker_list.html', buglists=bugz, recent=recent_bugs, wb_change=whiteboard_change, info=release_info, title="Fedora %s %s Blocker Bugs" % ( - release_info['number'], release_info['phase'])) + release_info['number'], release_info['phase']), + vote_info=vote_info) @main.route('/bug//updates') @@ -236,8 +323,9 @@ def display_irc_blockers(num, release_name): abort(404) bugz = get_milestone_bugs(milestone) milestone_info = get_milestone_info(milestone) - - response = make_response(render_template('irc_format.txt', buglists=bugz, info=milestone_info)) + vote_info = irc_voting_info(bugz) + response = make_response(render_template('irc_format.txt', buglists=bugz, info=milestone_info, + vote_info=vote_info)) response.mimetype = 'text/plain' return response @@ -258,6 +346,7 @@ def display_release_updates(num, release_name): updates=updates, title="Fedora %s %s Blocker Bug Updates" % (milestone_info['number'], milestone_info['phase'])) + @main.route('/milestone///requests') def display_release_requests(num, release_name): release = Release.query.filter_by(number=num).first() @@ -274,6 +363,7 @@ def display_release_requests(num, release_name): response.mimetype = 'text/plain' return response + @main.route('/milestone///need_testing') def display_updates_need_testing(num, milestone_name): release = Release.query.filter_by(number=num).first() diff --git a/blockerbugs/models/bug.py b/blockerbugs/models/bug.py index 9f4622a..5ae01d9 100644 --- a/blockerbugs/models/bug.py +++ b/blockerbugs/models/bug.py @@ -54,6 +54,7 @@ class Bug(db.Model): backref=db.backref('bugs', lazy='dynamic')) #backref='bugs') discussion_link = db.Column(db.String, unique=False) + votes = db.Column(db.Text) def __init__(self, bugid, url, summary, status, component, milestone, active, needinfo, needinfo_requestee, @@ -79,6 +80,17 @@ class Bug(db.Model): self.prioritized = False self.last_whiteboard_change = last_whiteboard_change self.last_bug_sync = last_bug_sync + # self.votes is JSON representation of dict of all BugVoteTrackers + # and corresponding output of BugVoteTracker.enumerate_votes() + # with comment ids removed: + # { + # tracker_name: { + # "-1": ["nick1", "nick2"], + # "0": [], + # "+1": ["nick3"], + # } + # } + self.votes = "{}" def __repr__(self): return '' % (self.bugid, self.summary) diff --git a/blockerbugs/templates/blocker_list.html b/blockerbugs/templates/blocker_list.html index 7e4b9b7..fd53645 100644 --- a/blockerbugs/templates/blocker_list.html +++ b/blockerbugs/templates/blocker_list.html @@ -77,7 +77,15 @@ text: '{{ 'Discuss' if buglist.startswith('Accepted') else 'Vote!' }} + + {% if vote_info[bug.bugid][buglist] %} + +{{ vote_info[bug.bugid][buglist][0] }}, + {{ vote_info[bug.bugid][buglist][1] }}, + -{{ vote_info[bug.bugid][buglist][2] }} +
+ {% endif %} + {{ 'Discuss' if buglist.startswith('Accepted') else 'Vote!' }} +