Changeset 95238 in webkit


Ignore:
Timestamp:
Sep 15, 2011 3:10:50 PM (13 years ago)
Author:
eric@webkit.org
Message:

Reviewed by Adam Barth.

webkit-patch should be able to find users and add them to bugzilla groups
https://bugs.webkit.org/show_bug.cgi?id=63351

These are both very basic commands. But it's now possible to find
all users matching a regexp, as well as add all users matching a regexp
to a set of groups.

bugzilla.py already knew how to find users (for validate-committer-lists)
but now it has the ability to modify the user records.

I split some of the logic out into a new EditUsersParser class
to try and reduce the amount of code in Bugzilla/BugzillaQueries.

  • Scripts/webkitpy/common/net/bugzilla/bugzilla.py:
  • Scripts/webkitpy/common/net/bugzilla/bugzilla_unittest.py:
  • Scripts/webkitpy/tool/commands/init.py:
  • Scripts/webkitpy/tool/commands/adduserstogroups.py: Added.
  • Scripts/webkitpy/tool/commands/findusers.py: Added.
Location:
trunk/Tools
Files:
2 added
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/Tools/ChangeLog

    r95234 r95238  
     12011-09-15  Eric Seidel  <eric@webkit.org>
     2
     3        Reviewed by Adam Barth.
     4
     5        webkit-patch should be able to find users and add them to bugzilla groups
     6        https://bugs.webkit.org/show_bug.cgi?id=63351
     7
     8        These are both very basic commands.  But it's now possible to find
     9        all users matching a regexp, as well as add all users matching a regexp
     10        to a set of groups.
     11
     12        bugzilla.py already knew how to find users (for validate-committer-lists)
     13        but now it has the ability to modify the user records.
     14
     15        I split some of the logic out into a new EditUsersParser class
     16        to try and reduce the amount of code in Bugzilla/BugzillaQueries.
     17
     18        * Scripts/webkitpy/common/net/bugzilla/bugzilla.py:
     19        * Scripts/webkitpy/common/net/bugzilla/bugzilla_unittest.py:
     20        * Scripts/webkitpy/tool/commands/__init__.py:
     21        * Scripts/webkitpy/tool/commands/adduserstogroups.py: Added.
     22        * Scripts/webkitpy/tool/commands/findusers.py: Added.
     23
    1242011-09-15  Eric Seidel  <eric@webkit.org>
    225
  • trunk/Tools/Scripts/webkitpy/common/net/bugzilla/bugzilla.py

    r87413 r95238  
    1 # Copyright (c) 2009 Google Inc. All rights reserved.
     1# Copyright (c) 2011 Google Inc. All rights reserved.
    22# Copyright (c) 2009 Apple Inc. All rights reserved.
    33# Copyright (c) 2010 Research In Motion Limited. All rights reserved.
     
    5050
    5151
     52class EditUsersParser(object):
     53    def __init__(self):
     54        self._group_name_to_group_string_cache = {}
     55
     56    def _login_and_uid_from_row(self, row):
     57        first_cell = row.find("td")
     58        # The first row is just headers, we skip it.
     59        if not first_cell:
     60            return None
     61        # When there were no results, we have a fake "<none>" entry in the table.
     62        if first_cell.find(text="<none>"):
     63            return None
     64        # Otherwise the <td> contains a single <a> which contains the login name or a single <i> with the string "<none>".
     65        anchor_tag = first_cell.find("a")
     66        login = unicode(anchor_tag.string).strip()
     67        user_id = int(re.search(r"userid=(\d+)", str(anchor_tag['href'])).group(1))
     68        return (login, user_id)
     69
     70    def login_userid_pairs_from_edit_user_results(self, results_page):
     71        soup = BeautifulSoup(results_page, convertEntities=BeautifulStoneSoup.HTML_ENTITIES)
     72        results_table = soup.find(id="admin_table")
     73        login_userid_pairs = [self._login_and_uid_from_row(row) for row in results_table('tr')]
     74        # Filter out None from the logins.
     75        return filter(lambda pair: bool(pair), login_userid_pairs)
     76
     77    def _group_name_and_string_from_row(self, row):
     78        label_element = row.find('label')
     79        group_string = unicode(label_element['for'])
     80        group_name = unicode(label_element.find('strong').string).rstrip(':')
     81        return (group_name, group_string)
     82
     83    def user_dict_from_edit_user_page(self, page):
     84        soup = BeautifulSoup(page, convertEntities=BeautifulStoneSoup.HTML_ENTITIES)
     85        user_table = soup.find("table", {'class': 'main'})
     86        user_dict = {}
     87        for row in user_table('tr'):
     88            label_element = row.find('label')
     89            if not label_element:
     90                continue  # This must not be a row we know how to parse.
     91            if row.find('table'):
     92                continue  # Skip the <tr> holding the groups table.
     93
     94            key = label_element['for']
     95            if "group" in key:
     96                key = "groups"
     97                value = user_dict.get('groups', set())
     98                # We must be parsing a "tr" inside the inner group table.
     99                (group_name, _) = self._group_name_and_string_from_row(row)
     100                if row.find('input', {'type': 'checkbox', 'checked': 'checked'}):
     101                    value.add(group_name)
     102            else:
     103                value = unicode(row.find('td').string).strip()
     104            user_dict[key] = value
     105        return user_dict
     106
     107    def _group_rows_from_edit_user_page(self, edit_user_page):
     108        soup = BeautifulSoup(edit_user_page, convertEntities=BeautifulSoup.HTML_ENTITIES)
     109        return soup('td', {'class': 'groupname'})
     110
     111    def group_string_from_name(self, edit_user_page, group_name):
     112        # Bugzilla uses "group_NUMBER" strings, which may be different per install
     113        # so we just look them up once and cache them.
     114        if not self._group_name_to_group_string_cache:
     115            rows = self._group_rows_from_edit_user_page(edit_user_page)
     116            name_string_pairs = map(self._group_name_and_string_from_row, rows)
     117            self._group_name_to_group_string_cache = dict(name_string_pairs)
     118        return self._group_name_to_group_string_cache[group_name]
     119
     120
    52121def timestamp():
    53122    return datetime.now().strftime("%Y%m%d%H%M%S")
     
    175244        return self._fetch_attachment_ids_request_query(review_queue_url)
    176245
    177     def _login_from_row(self, row):
    178         first_cell = row.find("td")
    179         # The first row is just headers, we skip it.
    180         if not first_cell:
    181             return None
    182         # When there were no results, we have a fake "<none>" entry in the table.
    183         if first_cell.find(text="<none>"):
    184             return None
    185         # Otherwise the <td> contains a single <a> which contains the login name or a single <i> with the string "<none>".
    186         return str(first_cell.find("a").string).strip()
    187 
    188     def _parse_logins_from_editusers_results(self, results_page):
    189         soup = BeautifulSoup(results_page, convertEntities=BeautifulStoneSoup.HTML_ENTITIES)
    190         results_table = soup.find(id="admin_table")
    191         logins = [self._login_from_row(row) for row in results_table('tr')]
    192         # Filter out None from the logins.
    193         return filter(lambda login: bool(login), logins)
    194 
    195246    # This only works if your account has edituser privileges.
    196247    # We could easily parse https://bugs.webkit.org/userprefs.cgi?tab=permissions to
    197248    # check permissions, but bugzilla will just return an error if we don't have them.
    198     def fetch_logins_matching_substring(self, search_string):
     249    def fetch_login_userid_pairs_matching_substring(self, search_string):
    199250        review_queue_url = "editusers.cgi?action=list&matchvalue=login_name&matchstr=%s&matchtype=substr" % urllib.quote(search_string)
    200251        results_page = self._load_query(review_queue_url)
    201         return self._parse_logins_from_editusers_results(results_page)
     252        # We could pull the EditUsersParser off Bugzilla if needed.
     253        return EditUsersParser().login_userid_pairs_from_edit_user_results(results_page)
     254
     255    # FIXME: We should consider adding a BugzillaUser class.
     256    def fetch_logins_matching_substring(self, search_string):
     257        pairs = self.fetch_login_userid_pairs_matching_substring(search_string)
     258        return map(lambda pair: pair[0], pairs)
    202259
    203260
    204261class Bugzilla(object):
    205 
    206262    def __init__(self, dryrun=False, committers=committers.CommitterList()):
    207263        self.dryrun = dryrun
     
    210266        self.committers = committers
    211267        self.cached_quips = []
     268        self.edit_user_parser = EditUsersParser()
    212269
    213270        # FIXME: We should use some sort of Browser mock object when in dryrun
     
    215272        from webkitpy.thirdparty.autoinstalled.mechanize import Browser
    216273        self.browser = Browser()
    217         # Ignore bugs.webkit.org/robots.txt until we fix it to allow this
    218         # script.
     274        # Ignore bugs.webkit.org/robots.txt until we fix it to allow this script.
    219275        self.browser.set_handle_robots(False)
     276
     277    def fetch_user(self, user_id):
     278        self.authenticate()
     279        edit_user_page = self.browser.open(self.edit_user_url_for_id(user_id))
     280        return self.edit_user_parser.user_dict_from_edit_user_page(edit_user_page)
     281
     282    def add_user_to_groups(self, user_id, group_names):
     283        self.authenticate()
     284        user_edit_page = self.browser.open(self.edit_user_url_for_id(user_id))
     285        self.browser.select_form(nr=1)
     286        for group_name in group_names:
     287            group_string = self.edit_user_parser.group_string_from_name(user_edit_page, group_name)
     288            self.browser.find_control(group_string).items[0].selected = True
     289        self.browser.submit()
    220290
    221291    def quips(self):
     
    249319                                             attachment_id,
    250320                                             action_param)
     321
     322    def edit_user_url_for_id(self, user_id):
     323        return "%seditusers.cgi?action=edit&userid=%s" % (config_urls.bug_server_url, user_id)
    251324
    252325    def _parse_attachment_flag(self,
  • trunk/Tools/Scripts/webkitpy/common/net/bugzilla/bugzilla_unittest.py

    r86288 r95238  
    1 # Copyright (C) 2009 Google Inc. All rights reserved.
     1# Copyright (C) 2011 Google Inc. All rights reserved.
    22#
    33# Redistribution and use in source and binary forms, with or without
     
    3131import StringIO
    3232
    33 from .bugzilla import Bugzilla, BugzillaQueries
     33from .bugzilla import Bugzilla, BugzillaQueries, EditUsersParser
    3434
    3535from webkitpy.common.checkout.changelog import parse_bug_id
     
    393393        queries._load_query("request.cgi?action=queue&type=review&group=type")
    394394
     395
     396class EditUsersParserTest(unittest.TestCase):
    395397    _example_user_results = """
    396     <div id="bugzilla-body">
    397     <p>1 user found.</p>
    398     <table id="admin_table" border="1" cellpadding="4" cellspacing="0">
    399       <tr bgcolor="#6666FF">
    400           <th align="left">Edit user...
    401           </th>
    402           <th align="left">Real name
    403           </th>
    404           <th align="left">Account History
    405           </th>
    406       </tr>
    407       <tr>
    408           <td >
    409               <a href="editusers.cgi?action=edit&amp;userid=1234&amp;matchvalue=login_name&amp;groupid=&amp;grouprestrict=&amp;matchtype=substr&amp;matchstr=abarth%40webkit.org">
    410             abarth&#64;webkit.org
    411               </a>
    412           </td>
    413           <td >
    414             Adam Barth
    415           </td>
    416           <td >
    417               <a href="editusers.cgi?action=activity&amp;userid=1234&amp;matchvalue=login_name&amp;groupid=&amp;grouprestrict=&amp;matchtype=substr&amp;matchstr=abarth%40webkit.org">
    418             View
    419               </a>
    420           </td>
    421       </tr>
    422     </table>
    423 """
     398        <div id="bugzilla-body">
     399        <p>1 user found.</p>
     400        <table id="admin_table" border="1" cellpadding="4" cellspacing="0">
     401          <tr bgcolor="#6666FF">
     402              <th align="left">Edit user...
     403              </th>
     404              <th align="left">Real name
     405              </th>
     406              <th align="left">Account History
     407              </th>
     408          </tr>
     409          <tr>
     410              <td >
     411                  <a href="editusers.cgi?action=edit&amp;userid=1234&amp;matchvalue=login_name&amp;groupid=&amp;grouprestrict=&amp;matchtype=substr&amp;matchstr=abarth%40webkit.org">
     412                abarth&#64;webkit.org
     413                  </a>
     414              </td>
     415              <td >
     416                Adam Barth
     417              </td>
     418              <td >
     419                  <a href="editusers.cgi?action=activity&amp;userid=1234&amp;matchvalue=login_name&amp;groupid=&amp;grouprestrict=&amp;matchtype=substr&amp;matchstr=abarth%40webkit.org">
     420                View
     421                  </a>
     422              </td>
     423          </tr>
     424        </table>
     425    """
    424426
    425427    _example_empty_user_results = """
     
    439441    """
    440442
    441     def _assert_parsed_logins(self, results_page, expected_logins):
    442         queries = BugzillaQueries(None)
    443         logins = queries._parse_logins_from_editusers_results(results_page)
     443    def _assert_login_userid_pairs(self, results_page, expected_logins):
     444        parser = EditUsersParser()
     445        logins = parser.login_userid_pairs_from_edit_user_results(results_page)
    444446        self.assertEquals(logins, expected_logins)
    445447
    446     def test_parse_logins_from_editusers_results(self):
    447         self._assert_parsed_logins(self._example_user_results, ["abarth@webkit.org"])
    448         self._assert_parsed_logins(self._example_empty_user_results, [])
     448    def test_logins_from_editusers_results(self):
     449        self._assert_login_userid_pairs(self._example_user_results, [("abarth@webkit.org", 1234)])
     450        self._assert_login_userid_pairs(self._example_empty_user_results, [])
     451
     452    _example_user_page = """<table class="main"><tr>
     453  <th><label for="login">Login name:</label></th>
     454  <td>eric&#64;webkit.org
     455  </td>
     456</tr>
     457<tr>
     458  <th><label for="name">Real name:</label></th>
     459  <td>Eric Seidel
     460  </td>
     461</tr>
     462    <tr>
     463      <th>Group access:</th>
     464      <td>
     465        <table class="groups">
     466          <tr>
     467          </tr>
     468          <tr>
     469            <th colspan="2">User is a member of these groups</th>
     470          </tr>
     471            <tr class="direct">
     472              <td class="checkbox"><input type="checkbox"
     473                           id="group_7"
     474                           name="group_7"
     475                           value="1" checked="checked" /></td>
     476              <td class="groupname">
     477                <label for="group_7">
     478                  <strong>canconfirm:</strong>
     479                  Can confirm a bug.
     480                </label>
     481              </td>
     482            </tr>
     483            <tr class="direct">
     484              <td class="checkbox"><input type="checkbox"
     485                           id="group_6"
     486                           name="group_6"
     487                           value="1" /></td>
     488              <td class="groupname">
     489                <label for="group_6">
     490                  <strong>editbugs:</strong>
     491                  Can edit all aspects of any bug.
     492                /label>
     493              </td>
     494            </tr>
     495        </table>
     496      </td>
     497    </tr>
     498
     499  <tr>
     500    <th>Product responsibilities:</th>
     501    <td>
     502        <em>none</em>
     503    </td>
     504  </tr>
     505</table>"""
     506
     507    def test_user_dict_from_edit_user_page(self):
     508        parser = EditUsersParser()
     509        user_dict = parser.user_dict_from_edit_user_page(self._example_user_page)
     510        expected_user_dict = {u'login': u'eric@webkit.org', u'groups': set(['canconfirm']), u'name': u'Eric Seidel'}
     511        self.assertEqual(expected_user_dict, user_dict)
  • trunk/Tools/Scripts/webkitpy/tool/commands/__init__.py

    r92324 r95238  
    11# Required for Python to search this directory for module files
    22
     3from webkitpy.tool.commands.adduserstogroups import AddUsersToGroups
    34from webkitpy.tool.commands.bugfortest import BugForTest
    45from webkitpy.tool.commands.bugsearch import BugSearch
     
    67from webkitpy.tool.commands.earlywarningsystem import *
    78from webkitpy.tool.commands.expectations import OptimizeExpectations
     9from webkitpy.tool.commands.findusers import FindUsers
    810from webkitpy.tool.commands.gardenomatic import GardenOMatic
    911from webkitpy.tool.commands.openbugs import OpenBugs
Note: See TracChangeset for help on using the changeset viewer.