Jump to content

User:ChristieBot/GA web.py

From Wikipedia, the free encyclopedia
'''
Copyright (c) 2022 Mike Christie
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
'''

import urllib.parse
import re
import datetime
import pywikibot
import pymysql
import operator
import GA_config
import sys
from dateutil.parser import parse
from GA import GAN, Subtopic



class Editor:

    def __init__(self, conn, editor, config, wbgan, name_changes):
        self.editor = editor
        cursor = conn.cursor(pymysql.cursors.DictCursor)

        sql = "select article_title from historical_GA_reviews where type = 'GAN' and lower(outcome) in ('promoted','pass','passed','listed') "
        sql += "and (nominator = '" + self.editor.replace("'","''") + "' or nominator in (select old_name from name_changes where new_name = '" + self.editor.replace("'","''") + "')) "
        self.GAs = 0
        try:
            self.GAs = cursor.execute(sql)
        except pymysql.Error as e:
            pass

        sql = "select article_title from historical_GA_reviews where type = 'GAN' "
        sql += "and (nominator = '" + self.editor.replace("'","''") + "' or nominator in (select old_name from name_changes where new_name = '" + self.editor.replace("'","''") + "')) "
        self.nominations = 0
        try:
            self.nominations = cursor.execute(sql)
        except pymysql.Error as e:
            pass

        sql = "select article_title from historical_GA_reviews where reviewer = '" + self.editor.replace("'","''") + "' and type = 'GAN'"
        sql = "select article_title from historical_GA_reviews where type = 'GAN' "
        sql += "and (reviewer = '" + self.editor.replace("'","''") + "' or reviewer in (select old_name from name_changes where new_name = '" + self.editor.replace("'","''") + "')) "
        self.reviews = 0
        try:
            self.reviews = cursor.execute(sql)
        except pymysql.Error as e:
            pass

        self.ratio = 'Infinite'
        if self.GAs > 0:
            self.ratio = round(self.reviews/self.GAs,1)

        self.current_GAs = 0
        if self.editor.lower() in wbgan.keys():
            self.current_GAs = int(wbgan[self.editor.lower()])

        if self.editor in name_changes.keys():
            if name_changes[self.editor].lower() in wbgan.keys():
                self.current_GAs += int(wbgan[name_changes[self.editor].lower()])

        if self.editor in name_changes.values():
            old_names = [x for x in name_changes.keys() if name_changes[x] == self.editor]
            for old_name in old_names:
                if old_name.lower() in wbgan.keys():
                    self.current_GAs += int(wbgan[old_name.lower()])

    def display_summary(self):
        editor_text = ["<h2>Summary</h2>"]
        editor_text.append("<ul>")
        editor_text.append('<li>GA nominations: ' + str(self.nominations) + "</li>")
        editor_text.append('<li>Promoted GA nominations: ' + str(self.GAs) + "</li>")
        editor_text.append('<li>Promoted GA nominations that are still GAs: ' + str(self.current_GAs) + "</li>")
        editor_text.append('<li>GA reviews: ' + str(self.reviews) + "</li>")
        editor_text.append('<li>Ratio of reviews to successful nominations: ' + str(self.ratio) + "</li>")
        editor_text.append("</ul>")
        editor_text.append("<br />")
        note_text = 'Note that apart from the "Promoted GA nominations that are still GAs", which is taken from '
        note_text += '<a href = "https://sdzerobot.toolforge.org/gans?user=' + urllib.parse.quote_plus(self.editor) + '" target="_blank">SDZeroBot</a>'
        note_text += ', these statistics only include GAs performed using GA subpages of the article talk page.  They do not include GAs that were reviewed and promoted '
        note_text += 'solely via discussions on the article talk page, which was the method used for GA reviews before about mid-2006.\n'
        editor_text.append(note_text)
        return("\n".join(editor_text))
        
class GAN_records:

    @classmethod
    def table_headings(cls, table_type=None):
        table_headings = "<tr>\n"
        columns = ["Article","Nominator","Nominated","Reviewed","Reviewer","Page","Subtopic","Outcome","Currently","Comments"]
        if table_type == "Nomination list":
            columns = ["No.","Article","Nominated","Reviewed","Reviewer","Page","Subtopic","Outcome", "Currently","Comments"]
        elif table_type == "Review list":
            columns = ["No.", "Article","Nominator","Nominated","Reviewed","Page","Subtopic","Outcome","Currently"]
        for c in columns:
            table_headings += "<th>" + c + "</th>\n"
        table_headings += "</tr>\n"
        return(table_headings)

    @classmethod
    def get_table_data(cls, conn, table_type, constraints):
        site = pywikibot.Site('en','wikipedia')
        gan_records = GAN_records(conn, constraints)
        ctr = 0
        table_text = []
        for g in gan_records.gans:
            ctr += 1
            new_row = g.get_table_row(conn, table_type, ctr)
            table_text.append(new_row)
        return(table_text)

    @classmethod
    #def display_table(cls, conn, table_type, constraints, header, ga_cat_list):
    def display_table(cls, conn, table_type, constraints, header):
        table_text = "<h2>" + header + "</h2>\n"
        table_text += "<table>\n"
        table_text += GAN_records.table_headings(table_type)
        site = pywikibot.Site('en','wikipedia')
        #ga_pages = []
        #ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Agriculture, food and drink'))
        #ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Art and architecture'))
        #ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Engineering and technology'))
        #ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Geography and places'))
        #ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/History'))
        #ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Language and literature'))
        #ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Mathematics'))
        #ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Media and drama'))
        #ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Music'))
        #ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Natural sciences'))
        #ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Philosophy and religion'))
        #ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Social sciences and society'))
        #ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Sports and recreation'))
        #ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Video games'))
        #ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Warfare'))
        #ga_text = ""
        #for gp in ga_pages:
        #    ga_text += gp.text
        #ga_text = GAN_records.get_ga_text()

        #ga_all = (pywikibot.Page(site, 'Wikipedia:Good articles/all'))
        #ga_text = ga_all.text
        #site = pywikibot.Site('en','wikipedia')
        #fa_page = pywikibot.Page(site, 'Wikipedia:Featured articles')
        #fa_text = fa_page.text
        
        gan_records = GAN_records(conn, constraints)
        #gan_records = GAN_records(conn, constraints, ga_text, fa_text)
        #gan_records = GAN_records(conn, constraints, ga_cat_list, fa_text)
        #gan_records = GAN_records(conn, constraints)
        ctr = 0
        for g in gan_records.gans:
            ctr += 1
            new_row = g.table_row(conn, table_type, ctr)
            table_text += new_row
        table_text += "</table>\n"
        return(table_text)

    @classmethod
    def get_ga_text(cls):
        site = pywikibot.Site('en','wikipedia')
        ga_pages = []
        ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Agriculture, food and drink'))
        ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Art and architecture'))
        ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Engineering and technology'))
        ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Geography and places'))
        ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/History'))
        ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Language and literature'))
        ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Mathematics'))
        ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Media and drama'))
        ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Music'))
        ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Natural sciences'))
        ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Philosophy and religion'))
        ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Social sciences and society'))
        ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Sports and recreation'))
        ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Video games'))
        ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Warfare'))
        ga_text = ""
        for gp in ga_pages:
            ga_text += gp.text
        return(ga_text)

    @classmethod
    def get_fa_text(cls):
        site = pywikibot.Site('en','wikipedia')
        fa_page = pywikibot.Page(site, 'Wikipedia:Featured articles')
        return(fa_page.text)

    def __init__(self, conn, constraints):
    #def __init__(self, conn, constraints, ga_text, fa_text):
    #def __init__(self, conn, constraints, ga_cat_list, fa_text):
    #def __init__(self, conn, constraints):
        self.constraints = constraints
        self.gans = []
        cursor = conn.cursor(pymysql.cursors.DictCursor)
        where_clause = ""
        order_by_clause = " order by nomination_ts"
        conjunction = "where "
        for column in constraints.keys():
            if column in ['page']:
                where_clause += conjunction + " " + column + " = " + constraints[column]
                conjunction = " and "
            if column in ['nominator','reviewer']:
                where_clause += conjunction + "(" + column + " = '" + constraints[column].replace("'","''") + "' or " + column + " in (select old_name from name_changes where new_name = '" + constraints[column].replace("'","''") +"'))"
                conjunction = " and "
                if column == 'reviewer':
                    order_by_clause = " order by review_ts"
            if column in ['type']:
                where_clause += conjunction + column + " = '" + constraints[column] + "'"
                conjunction = " and "
        sql = "select article_title, nominator, nomination_ts, review_ts, reviewer, page, subtopic, outcome, nvl(comments,'') as comments from historical_GA_reviews "
        sql += where_clause
        sql += order_by_clause
        #GAN.log(conn,"GAN_records.__init__", None, "sql is " + sql)
        ga_text = GAN_records.get_ga_text()
        fa_text = GAN_records.get_fa_text()
        try:
            cursor.execute(sql)
        except pymysql.Error as e:
            return(None)
        for row in cursor.fetchall():
            self.gans.append(GAN_record(row['article_title'], row['nominator'], row['nomination_ts'], row['review_ts'], row['reviewer'], row['page'], row['subtopic'], row['outcome'], row['comments'], ga_text, fa_text))
            #self.gans.append(GAN_record(row['article_title'], row['nominator'], row['nomination_ts'], row['review_ts'], row['reviewer'], row['page'], row['subtopic'], row['outcome'], row['comments'], ga_cat_list, fa_text))
            #self.gans.append(GAN_record(row['article_title'], row['nominator'], row['nomination_ts'], row['review_ts'], row['reviewer'], row['page'], row['subtopic'], row['outcome'], row['comments']))

class GAN_record:

    def __init__(self, article_title, nominator, nomination_ts, review_ts, reviewer, page, subtopic, outcome, comments, ga_text, fa_text):
    #def __init__(self, article_title, nominator, nomination_ts, review_ts, reviewer, page, subtopic, outcome, comments, ga_cat_list, fa_text):
    #def __init__(self, article_title, nominator, nomination_ts, review_ts, reviewer, page, subtopic, outcome, comments):
        self.article_title = article_title
        self.nominator = nominator
        self.nomination_ts = nomination_ts
        self.review_ts = review_ts
        self.reviewer = reviewer
        self.page = page
        self.subtopic = subtopic
        if subtopic in Subtopic.subtopic_var_dict.keys():
            self.subtopic = Subtopic.subtopic_var_dict[subtopic]
        if outcome is None:
            self.outcome = 'Nominated'
        elif outcome.lower() in ['listed','pass','passed','promoted']:
            self.outcome = 'Promoted'
        elif outcome.lower() in ['fail','failed','not listed','not promoted']:
            self.outcome = 'Not promoted'
        elif outcome.lower() in ['under review']:
            self.outcome = 'Under review'
        else:
            self.outcome = outcome
        self.comments = comments
        #site = pywikibot.Site('en','wikipedia')
        #p = pywikibot.Page(site, article_title)
        self.state = ''
        #if "Talk:" + self.article_title in ga_cat_list:
        if '[' + self.article_title.lower() + ']' in ga_text.lower() or ('[' + self.article_title.lower() + ']').replace('_',' ') in ga_text.lower() or '[' + self.article_title.lower() + '|' in ga_text.lower() or ('[' + self.article_title.lower() + '|').replace('_',' ') in ga_text.lower():
            self.state = 'Good'
        elif '[' + self.article_title.lower() + ']' in fa_text.lower() or ('[' + self.article_title.lower() + ']').replace('_',' ') in fa_text.lower() or '[' + self.article_title.lower() + '|' in fa_text.lower() or ('[' + self.article_title.lower() + '|').replace('_',' ') in fa_text.lower():
            self.state = 'Featured'
        elif self.outcome == 'Promoted':
            self.state = 'Delisted'
        #if '{{good article}}' in p.text.lower():
        #    self.state = 'Good'
        #elif '{{featured article}}' in p.text.lower():
        #    self.state = 'Featured'
        #elif self.outcome == 'Promoted':
        #    self.state = 'Delisted'

    def table_row(self, conn, table_type, ctr):
        row_text = ""
        #GAN.log(conn,"table_row", self.article_title, "Called with " + table_type)
        if table_type == "Nomination list":
            #GAN.log(conn,"table_row", self.article_title, "In Nomination list branch")
            row_text = "<tr>\n"
            row_text += '<td>' + str(ctr) + '</td>\n'
            row_text += '<td>' + GANstats.article_link(self.article_title) + '</td>\n'
            row_text += "<td>" + GANstats.display_date(self.nomination_ts)+ "</td>\n" 
            row_text += "<td>" + GANstats.display_date(self.review_ts)+ "</td>\n" 
            row_text += "<td>" + GANstats.editor_link(self.reviewer) + "</td>\n" 
            row_text += "<td>" + GANstats.review_link(self.article_title, self.page) + "</td>\n" 
            row_text += "<td>" + self.subtopic + "</td>\n"
            row_text += "<td>" + self.outcome + "</td>\n" 
            row_text += "<td>" + self.state + "</td>\n" 
            row_text += "<td>" + self.comments + "</td>\n" 
            row_text += "</tr>\n"
        elif table_type == "Review list":
            #GAN.log(conn,"table_row", self.article_title, "In Review list branch")
            row_text = "<tr>\n"
            row_text += '<td>' + str(ctr) + '</td>\n'
            row_text += '<td>' + GANstats.article_link(self.article_title) + '</td>\n'
            row_text += "<td>" + GANstats.editor_link(self.nominator) + "</td>\n" 
            row_text += "<td>" + GANstats.display_date(self.nomination_ts)+ "</td>\n" 
            row_text += "<td>" + GANstats.display_date(self.review_ts)+ "</td>\n" 
            row_text += "<td>" + GANstats.review_link(self.article_title, self.page) + "</td>\n" 
            row_text += "<td>" + self.subtopic + "</td>\n" 
            row_text += "<td>" + self.outcome + "</td>\n" 
            row_text += "<td>" + self.state + "</td>\n" 
            row_text += "</tr>\n"
        return(row_text)

    def get_table_row(self, conn, table_type, ctr):
        row = []
        #GAN.log(conn,"table_row", self.article_title, "Called with " + table_type)
        if table_type == "Nomination list":
            #GAN.log(conn,"table_row", self.article_title, "In Nomination list branch")
            row = {}
            row['ctr'] = str(ctr)
            row['article_link_title'] = self.article_title.replace(" ","_")
            row['article_title'] = self.article_title
            row['nomination_ts'] = (GANstats.display_date(self.nomination_ts))
            row['review_ts'] = (GANstats.display_date(self.review_ts))
            row['reviewer_link'] = self.reviewer.replace(" ","_")
            row['reviewer'] = self.reviewer
            row['page'] = self.page
            row['subtopic'] = self.subtopic
            row['outcome'] = self.outcome
            row['state'] = self.state
            row['comments'] = self.comments
        elif table_type == "Review list":
            row = {}
            row['ctr'] = str(ctr)
            row['article_link_title'] = self.article_title.replace(" ","_")
            row['article_title'] = self.article_title
            row['nomination_ts'] = (GANstats.display_date(self.nomination_ts))
            row['review_ts'] = (GANstats.display_date(self.review_ts))
            row['nominator_link'] = self.nominator.replace(" ","_")
            row['nominator'] = self.nominator
            row['page'] = self.page
            row['subtopic'] = self.subtopic
            row['outcome'] = self.outcome
            row['state'] = self.state
        return(row)

class GANstats: # Holds static methods that don't relate to the data

    @classmethod
    def get_wbgan(cls, config):
        wbgan = {}
        database = "s54328__goodarticles_p" 
        wbg_conn = pymysql.connections.Connection(user=config['client']['user'], password=config['client']['password'], database="s54328__goodarticles_p", host='tools.db.svc.eqiad.wmflabs')
        with wbg_conn.cursor() as cursor:
            sql = "select lower(nominator) as nominator, count(*) as GA_count from nominators group by nominator order by count(*) desc"
            try:
                cursor.execute(sql)
            except pymysql.Error as e:
                #GAN.log(gan_conn,"editor_query:wbgan",editor_name,str(e))
                pass
            result = cursor.fetchall()
            for row in result: 
                wbgan[row[0]] = row[1]
        return(wbgan)

    @classmethod
    def get_name_changes(cls, config):
        name_changes = {}
        database = "s55175__ganfilter"
        conn = pymysql.connections.Connection(user=config['client']['user'], password=config['client']['password'], database="s55175__ganfilter", host='tools.db.svc.eqiad.wmflabs')
        cursor = conn.cursor(pymysql.cursors.DictCursor)
        sql = "select n.old_name, n.new_name from " + GA_config.strings['name changes table name'] + " n "
        try:
            cursor.execute(sql)
            for row in cursor.fetchall():
                name_changes[row['old_name']] = row['new_name']
        except pymysql.Error as e:
            print(sql + " : " + str(e))
        return(name_changes)

    @classmethod
    def get_style(cls):
        style = "<style>\n"
        style += "table, th, td {\n"
        style += "  border: 1px solid black;\n"
        style += "  border-collapse: collapse;\n"
        style += "}\n"
        style += "\n"
        style += ".collapsible {\n"
        style += "  cursor: pointer;\n"
        style += "  padding: 18px;\n"
        style += "  width: 100%;\n"
        style += "  border: none;\n"
        style += "  text-align: left;\n"
        style += "  outline: none;\n"
        style += "  font-size: 15px;\n"
        style += "}\n"
        style += "\n"
        style += ".active, .collapsible:hover {\n"
        style += "  background-color: #555;\n"
        style += "}\n"
        style += ".content {\n"
        style += "  padding: 0 18px;\n"
        style += "  display: none;\n"
        style += "  overflow: hidden;\n"
        style += "  background-color: #f1f1f1;\n"
        style += "}\n"
        style += "\n"
        style += "</style>\n"
        return(style)

    @classmethod
    def get_top_bar_style(cls):
        top_bar_css  = '<style>\n'
        top_bar_css += 'body {\n'
        top_bar_css += '  margin: 0;\n'
        top_bar_css += '  font-family: Arial, Helvetica, sans-serif;\n'
        top_bar_css += '}\n'
        top_bar_css += '\n'
        top_bar_css += '.topnav {\n'
        top_bar_css += '  overflow: hidden;\n'
        top_bar_css += '  background-color: #333;\n'
        top_bar_css += '}\n'
        top_bar_css += '\n'
        top_bar_css += '.topnav a {\n'
        top_bar_css += '  float: left;\n'
        top_bar_css += '  color: #f2f2f2;\n'
        top_bar_css += '  text-align: center;\n'
        top_bar_css += '  padding: 14px 16px;\n'
        top_bar_css += '  text-decoration: none;\n'
        top_bar_css += '  font-size: 17px;}\n'
        top_bar_css += '\n'
        top_bar_css += '.topnav a:hover {\n'
        top_bar_css += '  background-color: #ddd;\n'
        top_bar_css += '  color: black;\n'
        top_bar_css += '}\n'
        top_bar_css += '\n'
        top_bar_css += '.topnav a.active {\n'
        top_bar_css += '  background-color: #04AA6D;\n'
        top_bar_css += '  color: white;\n'
        top_bar_css += '}\n'
        top_bar_css += '</style>\n'
        return(top_bar_css)

    @classmethod
    def get_top_bar_div(cls):
        top_bar_div = '<div class="topnav">\n'
        top_bar_div += '<a class="active" href="fac_editor_query">FAC stats</a>\n'
        top_bar_div += '<a href="gan_editor_query">GAN stats</a>\n'
        top_bar_div += '</div>'
        return(top_bar_div)

    @classmethod
    def get_html_top(cls):
        return("<html><head></head><body>\n")

    @classmethod
    def get_html_bottom(cls):
        return("</body></html>\n")

    @classmethod
    def article_link(cls, article_title):
        if article_title is None or article_title == '':
            return("")
        else:
            htext = '<a href="https://wiki.riteme.site/wiki/' + article_title.replace(" ","_") + '" target="_blank">' + article_title + '</a>'
            return(htext)

    @classmethod
    def editor_link(cls, editor):
        if editor is None or editor == '':
            return("")
        else:
            htext = '<a href="https://wiki.riteme.site/wiki/User:' + editor.replace(" ","_") + '" target="_blank">' + editor + '</a>'
            return(htext)

    @classmethod
    def review_link(cls, article_title, page):
        if article_title is None or article_title == '':
            return("")
        else:
            htext = '<a href="https://wiki.riteme.site/wiki/Talk:' + article_title.replace(" ","_") + '/GA' + str(page) + '" target="_blank">' + '/GA' + str(page) + '</a>'
            return(htext)

    @classmethod
    def display_date(cls, date_value):
        if date_value is None:
            return("")
        else:
            return (date_value.strftime('%Y-%m-%d'))