Changeset 290889 in webkit
- Timestamp:
- Mar 7, 2022, 7:56:17 AM (5 years ago)
- Location:
- trunk
- Files:
-
- 18 edited
-
ChangeLog (modified) (1 diff)
-
Tools/ChangeLog (modified) (1 diff)
-
Tools/Scripts/libraries/webkitbugspy/setup.py (modified) (1 diff)
-
Tools/Scripts/libraries/webkitbugspy/webkitbugspy/__init__.py (modified) (1 diff)
-
Tools/Scripts/libraries/webkitbugspy/webkitbugspy/bugzilla.py (modified) (1 diff)
-
Tools/Scripts/libraries/webkitbugspy/webkitbugspy/github.py (modified) (6 diffs)
-
Tools/Scripts/libraries/webkitbugspy/webkitbugspy/mocks/__init__.py (modified) (1 diff)
-
Tools/Scripts/libraries/webkitbugspy/webkitbugspy/mocks/base.py (modified) (1 diff)
-
Tools/Scripts/libraries/webkitbugspy/webkitbugspy/mocks/bugzilla.py (modified) (3 diffs)
-
Tools/Scripts/libraries/webkitbugspy/webkitbugspy/mocks/data.py (modified) (1 diff)
-
Tools/Scripts/libraries/webkitbugspy/webkitbugspy/mocks/github.py (modified) (5 diffs)
-
Tools/Scripts/libraries/webkitbugspy/webkitbugspy/mocks/radar.py (modified) (5 diffs)
-
Tools/Scripts/libraries/webkitbugspy/webkitbugspy/radar.py (modified) (4 diffs)
-
Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tests/bugzilla_unittest.py (modified) (2 diffs)
-
Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tests/github_unittest.py (modified) (1 diff)
-
Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tests/radar_unittest.py (modified) (3 diffs)
-
Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tracker.py (modified) (2 diffs)
-
metadata/trackers.json (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
trunk/ChangeLog
r290801 r290889 1 2022-03-07 Jonathan Bedard <jbedard@apple.com> 2 3 [webkitbugspy] Allow creation of new issues 4 https://bugs.webkit.org/show_bug.cgi?id=237123 5 <rdar://problem/89388181> 6 7 Reviewed by Dewei Zhu. 8 9 * metadata/trackers.json: Define project. 10 1 11 2022-03-03 Dan Glastonbury <djg@apple.com> 2 12 -
trunk/Tools/ChangeLog
r290876 r290889 1 2022-03-07 Jonathan Bedard <jbedard@apple.com> 2 3 [webkitbugspy] Allow creation of new issues 4 https://bugs.webkit.org/show_bug.cgi?id=237123 5 <rdar://problem/89388181> 6 7 Reviewed by Dewei Zhu. 8 9 * Scripts/libraries/webkitbugspy/setup.py: Bump version. 10 * Scripts/libraries/webkitbugspy/webkitbugspy/__init__.py: Ditto. 11 * Scripts/libraries/webkitbugspy/webkitbugspy/bugzilla.py: 12 (Tracker.projects): Return a dictionary of projects, components and versions. 13 (Tracker.create): Create a new bug, prompting user for product, component and version 14 if needed. 15 * Scripts/libraries/webkitbugspy/webkitbugspy/github.py: 16 (Tracker.labels): Return a dictionary of all labels associated with the project. 17 (Tracker.projects): Return an empty dictionary. 18 (Tracker.create): Create a new issue. 19 * Scripts/libraries/webkitbugspy/webkitbugspy/mocks/__init__.py: Export PROJECT data. 20 * Scripts/libraries/webkitbugspy/webkitbugspy/mocks/base.py: 21 (Base.__init__): Mock trackers should define a project dictionary. 22 * Scripts/libraries/webkitbugspy/webkitbugspy/mocks/bugzilla.py: 23 (Bugzilla.__init__): 24 (Bugzilla._product_details): Derive product details from project dictionary. 25 (Bugzilla._create): Create a new bug. 26 (Bugzilla.request): Handle requests for product details and bug creation. 27 * Scripts/libraries/webkitbugspy/webkitbugspy/mocks/data.py: 28 * Scripts/libraries/webkitbugspy/webkitbugspy/mocks/github.py: 29 (GitHub.__init__): 30 (GitHub._labels): Return a list of project labels. 31 (GitHub._create): Create a new issue. 32 (GitHub.request): Handle requests for labels and issue creation. 33 * Scripts/libraries/webkitbugspy/webkitbugspy/mocks/radar.py: 34 (RadarClient.find_components): Mock radarclient's component search API. 35 (RadarClient.create_radar): Mock radarclient's radar creation API. 36 (Radar.Component): Added. 37 (Radar.exceptions): Added. 38 * Scripts/libraries/webkitbugspy/webkitbugspy/radar.py: 39 (Tracker): 40 (Tracker.Encoder.default): Pass 'projects' to radar instances. 41 (Tracker.__init__): Callers should define projects to limit component scope. 42 (Tracker.projects): Search radar components to provide a subset relevant to the declared projects. 43 (Tracker.create): Create a radar, prompting user for component and version if needed. 44 * Scripts/libraries/webkitbugspy/webkitbugspy/tests/bugzilla_unittest.py: 45 (TestBugzilla.test_state_why): 46 (TestBugzilla): 47 (TestBugzilla.test_projects): 48 (TestBugzilla.test_create): 49 (TestBugzilla.test_create_prompt): 50 * Scripts/libraries/webkitbugspy/webkitbugspy/tests/github_unittest.py: 51 (TestGitHub.test_state_why): 52 (TestGitHub): 53 (TestGitHub.test_labels): 54 (TestGitHub.test_projects): 55 (TestGitHub.test_create): 56 * Scripts/libraries/webkitbugspy/webkitbugspy/tests/radar_unittest.py: 57 (TestRadar.test_state_why): 58 (TestRadar): 59 (TestRadar.test_projects): 60 (TestRadar.test_create): 61 (TestRadar.test_create_prompt): 62 * Scripts/libraries/webkitbugspy/webkitbugspy/tracker.py: 63 (Tracker.from_json): Pass 'projects' to radar instances. 64 (Tracker.projects): Add base-class implementation. 65 (Tracker.create): Ditto. 66 1 67 2022-03-06 Lauro Moura <lmoura@igalia.com> 2 68 -
trunk/Tools/Scripts/libraries/webkitbugspy/setup.py
r290793 r290889 31 31 setup( 32 32 name='webkitbugspy', 33 version='0. 3.4',33 version='0.4.0', 34 34 description='Library containing a shared API for various bug trackers.', 35 35 long_description=readme(), -
trunk/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/__init__.py
r290793 r290889 47 47 ) 48 48 49 version = Version(0, 3, 4)49 version = Version(0, 4, 0) 50 50 51 51 from .user import User -
trunk/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/bugzilla.py
r289835 r290889 285 285 286 286 return result 287 288 @property 289 @webkitcorepy.decorators.Memoize() 290 def projects(self): 291 response = requests.get('{}/rest/product_enterable{}'.format(self.url, self._login_arguments(required=False))) 292 if response.status_code // 100 != 2: 293 sys.stderr.write("Failed to retrieve project list'\n") 294 return dict() 295 296 result = dict() 297 for id in response.json().get('ids', []): 298 id_response = requests.get('{}/rest/product/{}{}'.format(self.url, id, self._login_arguments(required=False))) 299 if response.status_code // 100 != 2: 300 sys.stderr.write("Failed to query bugzilla about prod '{}'\n".format(id)) 301 continue 302 for product in id_response.json()['products']: 303 if not product['is_active']: 304 continue 305 result[product['name']] = dict( 306 description=product['description'], 307 versions=[version['name'] for version in product['versions']], 308 components=dict(), 309 ) 310 for component in product['components']: 311 if not component['is_active']: 312 continue 313 result[product['name']]['components'][component['name']] = dict(description=component['description']) 314 315 return result 316 317 def create( 318 self, title, description, 319 project=None, component=None, version=None, assign=True, 320 ): 321 if not title: 322 raise ValueError('Must define title to create bug') 323 if not description: 324 raise ValueError('Must define description to create bug') 325 326 if not project and len(self.projects.keys()) == 1: 327 project = list(self.projects.keys())[0] 328 elif not project: 329 project = webkitcorepy.Terminal.choose( 330 'What project should the bug be associated with?', 331 options=sorted(self.projects.keys()), numbered=True, 332 ) 333 if project not in self.projects: 334 raise ValueError("'{}' is not a recognized product on {}".format(project, self.url)) 335 336 if not component and len(self.projects[project]['components'].keys()) == 1: 337 component = list(self.projects[project]['components'].keys())[0] 338 elif not component: 339 component = webkitcorepy.Terminal.choose( 340 "What component in '{}' should the bug be associated with?".format(project), 341 options=sorted(self.projects[project]['components'].keys()), numbered=True, 342 ) 343 if component not in self.projects[project]['components']: 344 raise ValueError("'{}' is not a recognized component in '{}'".format(component, project)) 345 346 if not version and len(self.projects[project]['versions']) == 1: 347 version = self.projects[project]['versions'][0] 348 elif not version: 349 version = webkitcorepy.Terminal.choose( 350 "What version of '{}' should the bug be associated with?".format(project), 351 options=self.projects[project]['versions'], numbered=True, 352 ) 353 if version not in self.projects[project]['versions']: 354 raise ValueError("'{}' is not a recognized version for '{}'".format(version, project)) 355 356 params = dict( 357 summary=title, 358 description=description, 359 product=project, 360 component=component, 361 version=version, 362 ) 363 if assign: 364 params['assigned_to'] = self.me().username 365 366 response = requests.post('{}/rest/bug{}'.format(self.url, self._login_arguments(required=True)), json=params) 367 if response.status_code // 100 != 2: 368 sys.stderr.write("Failed to create bug: {}\n".format(response.json().get('message', '?'))) 369 return None 370 return self.issue(response.json()['id']) -
trunk/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/github.py
r290796 r290889 138 138 params['page'] = params.get('page', 1) 139 139 140 url = '{api_url}/repos/{owner}/{name}/ issues{path}'.format(140 url = '{api_url}/repos/{owner}/{name}/{path}'.format( 141 141 api_url=self.api_url, 142 142 owner=self.owner, 143 143 name=self.name, 144 path=' /{}'.format(path) if path else '',144 path='{}'.format(path) if path else '', 145 145 ) 146 146 response = requests.get(url, params=params, headers=headers, auth=auth) … … 200 200 201 201 if member in ('title', 'timestamp', 'creator', 'opened', 'assignee', 'description'): 202 response = self.request(path= issue.id)202 response = self.request(path='issues/{}'.format(issue.id)) 203 203 if response: 204 204 issue._title = response['title'] … … 214 214 issue._watchers = [] 215 215 refs = set() 216 response = self.request(path= issue.id)216 response = self.request(path='issues/{}'.format(issue.id)) 217 217 if response: 218 218 for assignee in response['assignees']: … … 236 236 237 237 if member == 'comments': 238 response = self.request(path=' {}/comments'.format(issue.id))238 response = self.request(path='issues/{}/comments'.format(issue.id)) 239 239 issue._comments = [] 240 240 for node in response or []: … … 271 271 refs.add(candidate.link) 272 272 273 response = self.request(path=' {}/timeline'.format(issue.id))273 response = self.request(path='issues/{}/timeline'.format(issue.id)) 274 274 if response: 275 275 for event in response: … … 358 358 issue._comments.append(result) 359 359 return result 360 361 @property 362 @webkitcorepy.decorators.Memoize() 363 def labels(self): 364 response = self.request('labels') 365 result = {} 366 for label in response: 367 if 'name' not in label: 368 continue 369 result[label['name']] = dict( 370 color=label.get('color'), 371 description=label.get('description'), 372 ) 373 return result 374 375 @property 376 def projects(self): 377 # FIXME: Should be implemented via tags in GitHub, come up with a standard technique for parsing 378 return dict() 379 380 def create(self, title, description, labels=None, assign=True): 381 if not title: 382 raise ValueError('Must define title to create issue') 383 if not description: 384 raise ValueError('Must define description to create issue') 385 386 for label in labels or []: 387 if label not in self.labels: 388 raise ValueError("'{}' is not a recognized label in '{}'".format(label, self.url)) 389 390 data = dict( 391 title=title, 392 body=description, 393 ) 394 if labels: 395 data['labels'] = labels 396 if assign: 397 data['assignee'] = self.me().username 398 399 response = requests.post( 400 '{api_url}/repos/{owner}/{name}/issues'.format( 401 api_url=self.api_url, 402 owner=self.owner, 403 name=self.name, 404 ), auth=HTTPBasicAuth(*self.credentials(required=True)), 405 headers=dict(Accept='application/vnd.github.v3+json'), 406 json=data, 407 ) 408 if response.status_code // 100 != 2: 409 sys.stderr.write("Failed to create issue: '{}'\n".format(response.json().get('message', '?'))) 410 return None 411 412 return self.issue(response.json()['number']) -
trunk/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/mocks/__init__.py
r288038 r290889 22 22 23 23 from .bugzilla import Bugzilla 24 from .data import ISSUES, USERS 24 from .data import ISSUES, USERS, PROJECTS 25 25 from .github import GitHub 26 26 from .radar import Radar, NoRadar -
trunk/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/mocks/base.py
r288312 r290889 34 34 ) 35 35 36 def __init__(self, users=None, issues=None ):36 def __init__(self, users=None, issues=None, projects=None): 37 37 self.users = User.Mapping() 38 38 for user in users or []: 39 39 self.users.add(type(self).transform_user(user)) 40 40 41 self.projects = projects or dict() 41 42 self.issues = {} 42 43 for issue in issues or []: -
trunk/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/mocks/bugzilla.py
r289243 r290889 48 48 ) 49 49 50 def __init__(self, hostname='bugs.example.com', users=None, issues=None, environment=None ):51 Base.__init__(self, users=users, issues=issues )50 def __init__(self, hostname='bugs.example.com', users=None, issues=None, environment=None, projects=None): 51 Base.__init__(self, users=users, issues=issues, projects=projects) 52 52 mocks.Requests.__init__(self, hostname) 53 53 self._comment_count = 1 … … 246 246 ) 247 247 248 def _product_details(self, url, id): 249 for name, product in self.projects.items(): 250 if product['id'] != id: 251 continue 252 return mocks.Response.fromJson( 253 dict(products=[dict( 254 name=name, 255 description=product['description'], 256 is_active=True, 257 components=[dict( 258 name=component, 259 description=details['description'], 260 is_active=True, 261 ) for component, details in product['components'].items()], 262 versions=[dict( 263 name=version, 264 is_active=True, 265 ) for version in product['versions']], 266 )]), url=url, 267 ) 268 269 return mocks.Response.fromJson( 270 dict(products=[]), 271 url=url, 272 ) 273 274 def _create(self, url, credentials, data): 275 user = self._user_for_credentials(credentials) 276 assignee = self.users.get(data['assigned_to']) if 'assigned_to' in data else None 277 if not user: 278 return mocks.Response(status_code=401, url=url) 279 280 if not all((data.get('summary'), data.get('description'), data.get('product'), data.get('component'), data.get('version'))): 281 return mocks.Response( 282 status_code=400, 283 text=json.dumps(dict(message='Failed to create bug')), 284 url=url, 285 ) 286 287 id = 1 288 while id in self.issues.keys(): 289 id += 1 290 291 self.issues[id] = dict( 292 id=id, 293 title=data['summary'], 294 timestamp=int(time.time()), 295 opened=True, 296 creator=user, 297 assignee=assignee, 298 description=data['description'], 299 comments=[], watchers=[user, assignee] if assignee else [user], 300 ) 301 302 return mocks.Response( 303 status_code=200, 304 text=json.dumps(dict(id=id)), 305 url=url, 306 ) 307 248 308 def request(self, method, url, data=None, params=None, auth=None, json=None, **kwargs): 249 309 if not url.startswith('http://') and not url.startswith('https://'): … … 272 332 return self._post_comment(url, int(match.group('id')), match.group('credentials'), json) 273 333 334 match = re.match(r'{}/rest/product_enterable(?P<credentials>\?login=\S+\&password=\S+)?$'.format(self.hosts[0]), stripped_url) 335 if match and method == 'GET': 336 return mocks.Response.fromJson(dict( 337 ids=[project['id'] for project in self.projects.values()], 338 ), url=url) 339 340 match = re.match(r'{}/rest/product/(?P<id>\d+)(?P<credentials>\?login=\S+\&password=\S+)?$'.format(self.hosts[0]), stripped_url) 341 if match and method == 'GET': 342 return self._product_details(url, int(match.group('id'))) 343 344 match = re.match(r'{}/rest/bug(?P<credentials>\?login=\S+\&password=\S+)?$'.format(self.hosts[0]), stripped_url) 345 if match and method == 'POST': 346 return self._create(url, match.group('credentials'), json) 274 347 return mocks.Response.create404(url) -
trunk/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/mocks/data.py
r287686 r290889 88 88 ), 89 89 ] 90 91 PROJECTS = dict( 92 WebKit=dict( 93 id=1, 94 description='The WebKit browser engine', 95 versions=['Other', 'Safari 15', 'Safari Technology Preview', 'WebKit Local Build'], 96 components=dict( 97 Scrolling=dict(description='Bugs related to main thread and off-main thread scrolling'), 98 SVG=dict(description='For bugs in the SVG implementation.'), 99 Tables=dict(description='For bugs specific to tables (both the DOM and rendering issues).'), 100 Text=dict(description='For bugs in text layout and rendering, including international text support.'), 101 ), 102 ), CFNetwork=dict( 103 id=2, 104 description='Darwin networking framework', 105 versions=['All'], 106 components=dict( 107 IPv4=dict(description='Bugs involving IPv4 networking'), 108 IPv6=dict(description='Bugs involving IPv6 networking'), 109 ), 110 ), 111 ) -
trunk/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/mocks/github.py
r289249 r290889 33 33 class GitHub(Base, mocks.Requests): 34 34 top = None 35 DEFAULT_LABELS = { 36 'bug': dict(color='d73a4a', description="Something isn't working"), 37 'documentation': dict(color='0075ca', description='Improvements or additions to documentation'), 38 'duplicate': dict(color='cfd3d7', description='This issue or pull request already exists'), 39 'enhancement': dict(color='a2eeef', description='New feature or request'), 40 'good first issue': dict(color='7057ff', description='Good for newcomers'), 41 'help wanted': dict(color='008672', description='Extra attention is needed'), 42 'invalid': dict(color='e4e669', description="This doesn't seem right"), 43 'question': dict(color='d876e3', description='Further information is requested'), 44 'wontfix': dict(color='ffffff', description='This will not be worked on'), 45 } 35 46 36 47 @classmethod … … 47 58 return datetime.utcfromtimestamp(timestamp - timedelta(hours=7).seconds).strftime('%Y-%m-%dT%H:%M:%SZ') 48 59 49 def __init__(self, hostname='github.example.com/WebKit/WebKit', users=None, issues=None, environment=None ):60 def __init__(self, hostname='github.example.com/WebKit/WebKit', users=None, issues=None, environment=None, labels=None): 50 61 hostname, repo = hostname.split('/', 1) 51 62 self.api_remote = 'api.{hostname}/repos/{repo}'.format(hostname=hostname, repo=repo) … … 61 72 '{}_TOKEN'.format(prefix): 'token', 62 73 }) 74 75 if not labels: 76 self.labels = self.DEFAULT_LABELS 63 77 64 78 def __enter__(self): … … 169 183 source=dict(issue=dict(number=reference)), 170 184 ) for reference in issue.get('references', []) 171 ]) 185 ], url=url) 186 187 def _labels(self, url): 188 return mocks.Response.fromJson([ 189 dict( 190 name=name, 191 color=details['color'], 192 description=details['description'], 193 ) for name, details in self.labels.items() 194 ], url=url) 195 196 def _create(self, url, credentials, data): 197 user = self.users.get(credentials.username) if credentials else None 198 assignee = self.users.get(data['assignee']) if 'assignee' in data else None 199 200 if not user: 201 return mocks.Response.create404(url=url) 202 if not all((data.get('title'), data.get('body'))): 203 return mocks.Response(status_code=400, url=url) 204 205 id = 1 206 while id in self.issues.keys(): 207 id += 1 208 209 issue = dict( 210 id=id, 211 title=data['title'], 212 timestamp=int(time.time()), 213 opened=True, 214 creator=user, 215 assignee=assignee, 216 description=data['body'], 217 comments=[], watchers=[user, assignee] if assignee else [user], 218 ) 219 self.issues[id] = issue 220 221 return mocks.Response.fromJson(dict( 222 number=id, 223 title=issue['title'], 224 body=issue['description'], 225 user=dict(login=self.users[issue['creator'].name].username), 226 created_at=self.time_string(issue['timestamp']), 227 state='opened' if issue['opened'] else 'closed', 228 assignee=dict(login=self.users[issue['assignee'].name].username) if issue['assignee'] else None, 229 assignees=[dict(login=self.users[user.name].username) for user in issue.get('watchers', [])], 230 ), url=url) 172 231 173 232 def request(self, method, url, data=None, params=None, auth=None, json=None, **kwargs): … … 195 254 return self._timelines(url, int(match.group('id'))) 196 255 256 match = re.match(r'{}/labels$'.format(self.api_remote), stripped_url) 257 if match and method == 'GET': 258 return self._labels(url) 259 260 match = re.match(r'{}/issues$'.format(self.api_remote), stripped_url) 261 if match and method == 'POST': 262 return self._create(url, auth, json) 263 197 264 return mocks.Response.create404(url) -
trunk/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/mocks/radar.py
r289243 r290889 23 23 import calendar 24 24 import os 25 import re 25 26 import time 26 27 27 28 from .base import Base 28 29 29 from webkitbugspy import User, Issue 30 from webkitbugspy import User, Issue, radar 30 31 from webkitcorepy import string_utils 31 32 from webkitcorepy.mocks import ContextStack … … 169 170 return RadarModel(self, found) 170 171 172 def find_components(self, request_data): 173 filters = [] 174 if 'name' in request_data: 175 name_data = request_data['name'] 176 action = list(name_data.keys())[0] if isinstance(name_data, dict) else 'eq' 177 name = name_data[action] if isinstance(name_data, dict) else name_data 178 filter = dict( 179 eq=lambda s: s == name, 180 like=lambda s: re.match(name.replace('%', '.*'), s), 181 ).get(action) 182 if not filter: 183 raise ValueError('{} is not a valid Radar filter'.format(action)) 184 filters.append(filter) 185 186 result = [] 187 for project, project_details in self.parent.projects.items(): 188 for component, component_details in project_details.get('components', {}).items(): 189 component_name = '{} {}'.format(project, component) 190 if any(not filter(component_name) for filter in filters): 191 continue 192 for version in project_details.get('versions', ['All']): 193 if 'version' in request_data and version != request_data['version']: 194 continue 195 result.append(Radar.Component(component_name, component_details['description'], version)) 196 return result 197 198 def create_radar(self, request_data): 199 if not all(( 200 request_data.get('title'), request_data.get('description'), request_data.get('component'), 201 request_data.get('classification'), request_data.get('reproducible'), 202 )): 203 raise Radar.exceptions.UnsuccessfulResponseException('Not enough parameters defined to create a radar') 204 205 if len(self.find_components(request_data['component'])) != 1: 206 raise Radar.exceptions.UnsuccessfulResponseException('Provided component is not valid') 207 if request_data['classification'] not in radar.Tracker.CLASSIFICATIONS: 208 raise Radar.exceptions.UnsuccessfulResponseException("'{}' is not a valid classification".format(request_data['classification'])) 209 if request_data['reproducible'] not in radar.Tracker.REPRODUCIBILITY: 210 raise Radar.exceptions.UnsuccessfulResponseException("'{}' is not a valid reproducibility value".format(request_data['reproducible'])) 211 212 id = 1 213 while id in self.parent.issues.keys(): 214 id += 1 215 216 user = self.parent.users['{}@APPLECONNECT.APPLE.COM'.format(self.authentication_strategy.username())] 217 issue = dict( 218 id=id, 219 title=request_data['title'], 220 timestamp=int(time.time()), 221 opened=True, 222 creator=user, 223 assignee=user, 224 description=request_data['description'], 225 comments=[], watchers=[user], 226 ) 227 self.parent.issues[id] = issue 228 229 return self.radar_for_id(id) 171 230 172 231 class Radar(Base, ContextStack): … … 189 248 def __init__(self, name, version): 190 249 pass 250 251 class Component(object): 252 def __init__(self, name, description, version): 253 self.name = name 254 self.description = description 255 self.version = version 191 256 192 257 class DiagnosisEntry(object): … … 195 260 self.addedAt = addedAt 196 261 self.addedBy = addedBy 262 263 class exceptions(object): 264 class UnsuccessfulResponseException(Exception): 265 pass 197 266 198 267 @classmethod … … 204 273 ) 205 274 206 def __init__(self, users=None, issues=None ):207 Base.__init__(self, users=users, issues=issues )275 def __init__(self, users=None, issues=None, projects=None): 276 Base.__init__(self, users=users, issues=issues, projects=projects) 208 277 ContextStack.__init__(self, Radar) 209 278 -
trunk/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/radar.py
r289243 r290889 25 25 import sys 26 26 import time 27 import webkitcorepy 27 28 28 29 from webkitcorepy import Environment, decorators … … 46 47 ] 47 48 49 OTHER_BUG = 'Other Bug' 50 SECURITY = 'Security' 51 CRASH_HANG_DATA_LOSS = 'Crash/Hang/Data Loss' 52 POWER = 'Power' 53 PERFORMANCE = 'Performance' 54 UI_USABILITY = 'UI/Usability' 55 SERIOUS_BUG = 'Serious Bug' 56 FEATURE = 'Feature (New)' 57 ENHANCEMENT = 'Enhancement' 58 TASK = 'Task' 59 60 CLASSIFICATIONS = [ 61 OTHER_BUG, 62 SECURITY, 63 CRASH_HANG_DATA_LOSS, 64 POWER, 65 PERFORMANCE, 66 UI_USABILITY, 67 SERIOUS_BUG, 68 FEATURE, 69 ENHANCEMENT, 70 TASK 71 ] 72 73 ALWAYS = 'Always' 74 SOMETIMES = 'Sometimes' 75 RARELY = 'Rarely' 76 UNABLE = 'Unable' 77 DIDNT_TRY = "I Didn't Try" 78 NOT_APPLICABLE = 'Not Applicable' 79 80 REPRODUCIBILITY = [NOT_APPLICABLE, ALWAYS, SOMETIMES, RARELY, UNABLE, DIDNT_TRY] 81 48 82 class Encoder(GenericTracker.Encoder): 49 83 @decorators.hybridmethod 50 84 def default(context, obj): 51 85 if isinstance(obj, Tracker): 52 return dict(type='radar' )86 return dict(type='radar', projects=obj._projects) 53 87 if isinstance(context, type): 54 88 raise TypeError('Cannot invoke parent class when classmethod') … … 64 98 return None 65 99 66 def __init__(self, users=None, authentication=None ):100 def __init__(self, users=None, authentication=None, project=None, projects=None): 67 101 super(Tracker, self).__init__(users=users) 102 self._projects = [project] if project else (projects or []) 68 103 69 104 self.library = self.radarclient() … … 266 301 267 302 return result 303 304 @property 305 @webkitcorepy.decorators.Memoize() 306 def projects(self): 307 result = dict() 308 for project in self._projects: 309 result[project] = dict( 310 components=dict(), 311 description=None, 312 versions=[], 313 ) 314 for component in self.client.find_components(dict(name=dict(like=project + '%'), isClosed=False)): 315 name = component.name[(len(project)):].lstrip() 316 if not name: 317 if 'All' not in result[project]['versions']: 318 result[project]['description'] = component.description 319 result[project]['versions'].append(component.version) 320 continue 321 322 if name not in result[project]['components']: 323 result[project]['components'][name] = dict(description=None, versions=[]) 324 if 'All' not in result[project]['versions']: 325 result[project]['components'][name]['description'] = component.description 326 result[project]['components'][name]['versions'].append(component.version) 327 return result 328 329 def create( 330 self, title, description, 331 project=None, component=None, version=None, 332 classification=None, reproducible=None, 333 assign=True, 334 ): 335 if not title: 336 raise ValueError('Must define title to create bug') 337 if not description: 338 raise ValueError('Must define description to create bug') 339 340 if not project and len(self.projects) == 1: 341 project = list(self.projects.keys())[0] 342 if not project: 343 project = webkitcorepy.Terminal.choose( 344 'What project should the bug be associated with?', 345 options=sorted(self.projects.keys()), numbered=True, 346 ) 347 348 components = sorted(self.projects.get(project, {}).get('components', {}).keys()) 349 if not component and len(components) == 1: 350 component = components[0] 351 elif not component and components: 352 if self.projects[project]['versions']: 353 components = ['*'] + components 354 component = webkitcorepy.Terminal.choose( 355 "What component in '{}' should the bug be associated with?".format(project), 356 options=components, numbered=True, default=('*' if components[0] == '*' else None), 357 ) 358 if not component or component == '*': 359 component = '' 360 361 if component: 362 versions = self.projects.get(project, {}).get('components', {}).get(component, {}).get('versions', []) 363 else: 364 versions = self.projects.get(project, {}).get('versions', []) 365 if not version and len(versions) == 1: 366 version = versions[0] 367 elif not version and versions: 368 version = webkitcorepy.Terminal.choose( 369 "What version of '{}{}' should the bug be associated with?".format(project, (' ' + component) if component else ''), 370 options=versions, numbered=True, default=('All' if 'All' in versions else None), 371 ) 372 if not version: 373 version = 'All' 374 375 # Don't perform any checks of project, component or version. Radar defines many more projects than this class 376 # is aware of, if the caller knows better, trust them. 377 378 classification = classification or self.CLASSIFICATIONS[0] 379 if classification not in self.CLASSIFICATIONS: 380 raise ValueError("'{}' is not a valid bug classification".format(classification)) 381 382 reproducible = reproducible or self.REPRODUCIBILITY[0] 383 if reproducible not in self.REPRODUCIBILITY: 384 raise ValueError("'{}' is not a valid reproducibility argument".format(classification)) 385 386 try: 387 response = self.client.create_radar(dict( 388 title=title, 389 description=description, 390 component=dict(name='{} {}'.format(project, component), version=version), 391 classification=classification, 392 reproducible=reproducible, 393 )) 394 except self.library.exceptions.UnsuccessfulResponseException as e: 395 sys.stderr.write('Failed to create radar:\n') 396 sys.stderr.write('{}\n'.format(e)) 397 return None 398 399 result = self.issue(response.id) 400 if assign: 401 result.assign(self.me()) 402 return result -
trunk/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tests/bugzilla_unittest.py
r289243 r290889 27 27 28 28 from webkitbugspy import Issue, Tracker, User, bugzilla, mocks 29 from webkitcorepy import mocks as wkmocks29 from webkitcorepy import OutputCapture, mocks as wkmocks 30 30 31 31 … … 263 263 self.assertTrue(issue.opened) 264 264 self.assertEqual(issue.comments[-1].content, 'Need to revert, fix broke the build') 265 266 def test_projects(self): 267 with mocks.Bugzilla(self.URL.split('://')[1], projects=mocks.PROJECTS): 268 self.assertDictEqual( 269 dict( 270 CFNetwork=dict( 271 description='Darwin networking framework', 272 versions=['All'], 273 components=dict( 274 IPv4=dict(description='Bugs involving IPv4 networking'), 275 IPv6=dict(description='Bugs involving IPv6 networking'), 276 ), 277 ), WebKit=dict( 278 description='The WebKit browser engine', 279 versions=['Other', 'Safari 15', 'Safari Technology Preview', 'WebKit Local Build'], 280 components=dict( 281 Scrolling=dict(description='Bugs related to main thread and off-main thread scrolling'), 282 SVG=dict(description='For bugs in the SVG implementation.'), 283 Tables=dict(description='For bugs specific to tables (both the DOM and rendering issues).'), 284 Text=dict(description='For bugs in text layout and rendering, including international text support.'), 285 ), 286 ), 287 ), bugzilla.Tracker(self.URL).projects, 288 ) 289 290 def test_create(self): 291 with mocks.Bugzilla(self.URL.split('://')[1], environment=wkmocks.Environment( 292 BUGS_EXAMPLE_COM_USERNAME='tcontributor@example.com', 293 BUGS_EXAMPLE_COM_PASSWORD='password', 294 ), projects=mocks.PROJECTS, issues=mocks.ISSUES): 295 created = bugzilla.Tracker(self.URL).create( 296 'New bug', 'Creating new bug', 297 project='WebKit', component='Tables', version='Other', 298 ) 299 self.assertEqual(created.id, 4) 300 self.assertEqual(created.title, 'New bug') 301 self.assertEqual(created.description, 'Creating new bug') 302 self.assertTrue(created.opened) 303 self.assertEqual( 304 User.Encoder().default(created.creator), 305 dict(name='Tim Contributor', username='tcontributor@example.com', emails=['tcontributor@example.com']), 306 ) 307 self.assertEqual( 308 User.Encoder().default(created.assignee), 309 dict(name='Tim Contributor', username='tcontributor@example.com', emails=['tcontributor@example.com']), 310 ) 311 312 def test_create_prompt(self): 313 with mocks.Bugzilla(self.URL.split('://')[1], environment=wkmocks.Environment( 314 BUGS_EXAMPLE_COM_USERNAME='tcontributor@example.com', 315 BUGS_EXAMPLE_COM_PASSWORD='password', 316 ), projects=mocks.PROJECTS, issues=mocks.ISSUES), wkmocks.Terminal.input('2', '1', '2'), OutputCapture() as captured: 317 created = bugzilla.Tracker(self.URL).create('New bug', 'Creating new bug') 318 self.assertEqual(created.id, 4) 319 self.assertEqual(created.title, 'New bug') 320 self.assertEqual(created.description, 'Creating new bug') 321 self.assertTrue(created.opened) 322 self.assertEqual( 323 User.Encoder().default(created.creator), 324 dict(name='Tim Contributor', username='tcontributor@example.com', emails=['tcontributor@example.com']), 325 ) 326 self.assertEqual( 327 User.Encoder().default(created.assignee), 328 dict(name='Tim Contributor', username='tcontributor@example.com', emails=['tcontributor@example.com']), 329 ) 330 331 self.assertEqual( 332 captured.stdout.getvalue(), 333 '''What project should the bug be associated with?: 334 1) CFNetwork 335 2) WebKit 336 : 337 What component in 'WebKit' should the bug be associated with?: 338 1) SVG 339 2) Scrolling 340 3) Tables 341 4) Text 342 : 343 What version of 'WebKit' should the bug be associated with?: 344 1) Other 345 2) Safari 15 346 3) Safari Technology Preview 347 4) WebKit Local Build 348 : 349 ''', 350 ) -
trunk/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tests/github_unittest.py
r289249 r290889 263 263 self.assertTrue(issue.opened) 264 264 self.assertEqual(issue.comments[-1].content, 'Need to revert, fix broke the build') 265 266 def test_labels(self): 267 with mocks.GitHub(self.URL.split('://')[1]) as mocked: 268 self.assertDictEqual(github.Tracker(self.URL).labels, mocked.DEFAULT_LABELS) 269 270 def test_projects(self): 271 with mocks.GitHub(self.URL.split('://')[1]): 272 self.assertDictEqual(github.Tracker(self.URL).projects, dict()) 273 274 def test_create(self): 275 with mocks.GitHub(self.URL.split('://')[1], issues=mocks.ISSUES, environment=wkmocks.Environment( 276 GITHUB_EXAMPLE_COM_USERNAME='tcontributor', 277 GITHUB_EXAMPLE_COM_TOKEN='token', 278 )): 279 created = github.Tracker(self.URL).create('New bug', 'Creating new bug') 280 self.assertEqual(created.id, 4) 281 self.assertEqual(created.title, 'New bug') 282 self.assertEqual(created.description, 'Creating new bug') 283 self.assertTrue(created.opened) 284 self.assertEqual( 285 User.Encoder().default(created.creator), 286 dict(name='Tim Contributor', username='tcontributor', emails=['tcontributor@example.com']), 287 ) 288 self.assertEqual( 289 User.Encoder().default(created.assignee), 290 dict(name='Tim Contributor', username='tcontributor', emails=['tcontributor@example.com']), 291 ) -
trunk/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tests/radar_unittest.py
r289243 r290889 25 25 26 26 from webkitbugspy import Issue, Tracker, User, radar, mocks 27 from webkitcorepy import mocks as wkmocks 27 from webkitcorepy import mocks as wkmocks, OutputCapture 28 28 29 29 … … 31 31 def test_encoding(self): 32 32 self.assertEqual( 33 radar.Tracker.Encoder().default(radar.Tracker( )),34 dict(type='radar' ),33 radar.Tracker.Encoder().default(radar.Tracker(project='WebKit')), 34 dict(type='radar', projects=['WebKit']), 35 35 ) 36 36 … … 225 225 self.assertTrue(issue.opened) 226 226 self.assertEqual(issue.comments[-1].content, 'Need to revert, fix broke the build') 227 228 def test_projects(self): 229 with mocks.Radar(projects=mocks.PROJECTS): 230 self.assertDictEqual( 231 dict( 232 WebKit=dict( 233 description=None, 234 versions=[], 235 components=dict( 236 Scrolling=dict( 237 versions=['Other', 'Safari 15', 'Safari Technology Preview', 'WebKit Local Build'], 238 description='Bugs related to main thread and off-main thread scrolling', 239 ), SVG=dict( 240 versions=['Other', 'Safari 15', 'Safari Technology Preview', 'WebKit Local Build'], 241 description='For bugs in the SVG implementation.', 242 ), Tables=dict( 243 versions=['Other', 'Safari 15', 'Safari Technology Preview', 'WebKit Local Build'], 244 description='For bugs specific to tables (both the DOM and rendering issues).', 245 ), Text=dict( 246 versions=['Other', 'Safari 15', 'Safari Technology Preview', 'WebKit Local Build'], 247 description='For bugs in text layout and rendering, including international text support.', 248 ), 249 ), 250 ), 251 ), radar.Tracker(project='WebKit').projects, 252 ) 253 254 def test_create(self): 255 with wkmocks.Environment(RADAR_USERNAME='tcontributor'), mocks.Radar(issues=mocks.ISSUES, projects=mocks.PROJECTS): 256 created = radar.Tracker(projects=['CFNetwork', 'WebKit']).create( 257 'New bug', 'Creating new bug', 258 project='WebKit', component='Tables', version='Other', 259 ) 260 self.assertEqual(created.id, 4) 261 self.assertEqual(created.title, 'New bug') 262 self.assertEqual(created.description, 'Creating new bug') 263 self.assertTrue(created.opened) 264 self.assertEqual( 265 User.Encoder().default(created.creator), 266 dict(name='Tim Contributor', username=504, emails=['tcontributor@example.com']), 267 ) 268 self.assertEqual( 269 User.Encoder().default(created.assignee), 270 dict(name='Tim Contributor', username=504, emails=['tcontributor@example.com']), 271 ) 272 273 def test_create_prompt(self): 274 with wkmocks.Environment(RADAR_USERNAME='tcontributor'), mocks.Radar(issues=mocks.ISSUES, projects=mocks.PROJECTS), \ 275 wkmocks.Terminal.input('2', '4', '4'), OutputCapture() as captured: 276 277 created = radar.Tracker(projects=['CFNetwork', 'WebKit']).create('New bug', 'Creating new bug') 278 self.assertEqual(created.id, 4) 279 self.assertEqual(created.title, 'New bug') 280 self.assertEqual(created.description, 'Creating new bug') 281 self.assertTrue(created.opened) 282 self.assertEqual( 283 User.Encoder().default(created.creator), 284 dict(name='Tim Contributor', username=504, emails=['tcontributor@example.com']), 285 ) 286 self.assertEqual( 287 User.Encoder().default(created.assignee), 288 dict(name='Tim Contributor', username=504, emails=['tcontributor@example.com']), 289 ) 290 291 self.assertEqual( 292 captured.stdout.getvalue(), 293 '''What project should the bug be associated with?: 294 1) CFNetwork 295 2) WebKit 296 : 297 What component in 'WebKit' should the bug be associated with?: 298 1) SVG 299 2) Scrolling 300 3) Tables 301 4) Text 302 : 303 What version of 'WebKit Text' should the bug be associated with?: 304 1) Other 305 2) Safari 15 306 3) Safari Technology Preview 307 4) WebKit Local Build 308 : 309 ''', 310 ) -
trunk/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tracker.py
r289411 r290889 60 60 ) 61 61 if data.get('type') == 'radar': 62 return radar.Tracker( )62 return radar.Tracker(project=data.get('project', None), projects=data.get('projects', [])) 63 63 raise TypeError("'{}' is not a recognized tracker type".format(data.get('type'))) 64 64 … … 113 113 def add_comment(self, issue, text): 114 114 raise NotImplementedError() 115 116 @property 117 def projects(self): 118 raise NotImplementedError() 119 120 def create(self, title, description, **kwargs): 121 raise NotImplementedError() -
trunk/metadata/trackers.json
r289411 r290889 8 8 ] 9 9 }, 10 { "type" : "radar" }10 { "type" : "radar", "projects" : [ "JavaScriptCore", "WebKit" ] } 11 11 ]
Note:
See TracChangeset
for help on using the changeset viewer.