blob: 6a4930656dcdba2a0206004a1455fd553382d9e9 [file] [log] [blame]
#!/usr/bin/python
# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import httplib
import logging
import common
try:
from apiclient.discovery import build
from apiclient.http import HttpError
except ImportError as e:
build = None
logging.info("API client for bug filing disabled. %s", e)
class ProjectHostingApiException(Exception):
"""
Raised when an api call fails, since the actual
HTTP error can be cryptic.
"""
class ProjectHostingApiClient():
"""
Client class for interaction with the project hosting api.
"""
def __init__(self, api_key, project_name):
if build is None:
logging.error('Cannot get apiclient library.')
return None
self._service = build('projecthosting', 'v2', developerKey=api_key)
self._project_name = project_name
def _execute_request(self, request):
"""
Executes an api request checking for a reason upon failure.
@param request: request to be executed.
@raises: ProjectHostingApiException if we
fail to execute the request.
@return: results from the executed request.
"""
try:
return request.execute()
except (HttpError, httplib.HTTPException) as e:
msg = 'Unable to execute your request. '
if hasattr(e, 'content') and 'keyInvalid' in e.content:
msg += 'Your credentials have been revoked.'
raise ProjectHostingApiException(msg)
def _get_field(self, field):
"""
Gets a field from the project.
This method directly queries the project hosting API using bugdroids1's,
api key.
@param field: A selector, which corresponds loosely to a field in the
new bug description of the crosbug frontend.
@return: A json formatted python dict of the specified field's options,
or None if we can't find the api library. This dictionary
represents the javascript literal used by the front end tracker
and can hold multiple filds.
The returned dictionary follows a template, but it's structure
is only loosely defined as it needs to match whatever the front
end describes via javascript.
For a new issue interface which looks like:
field 1: text box
drop down: predefined value 1 = description
predefined value 2 = description
field 2: text box
similar structure as field 1
you will get a dictionary like:
{
'field name 1': {
'project realted config': 'config value'
'property': [
{predefined value for property 1, description},
{predefined value for property 2, description}
]
},
'field name 2': {
similar structure
}
...
}
"""
project = self._service.projects()
request = project.get(projectId=self._project_name,
fields=field)
return self._execute_request(request)
def _get_property_values(self, prop_dict):
"""
Searches a dictionary as returned by _get_field for property lists,
then returns each value in the list. Effectively this gives us
all the accepted values for a property. For example, in crosbug
crosbug, 'properties' map to things like Status, Labels, Owner
etc, each of these will have a list within the issuesConfig dict. Each
list will contain a listing of all predefined values, this function
retrieves each of these lists.
@param prop_dict: dictionary which contains a list of properties.
@yield: each value in a property list. This can be a dict or any other
type of datastructure, the caller is responsible for handling
it correctly.
"""
for name, property in prop_dict.iteritems():
if isinstance(property, list):
for values in property:
yield values
def _get_cros_labels(self, prop_dict):
"""
Helper function to isolate labels from the labels dictionary. This
dictionary is of the form:
{
"label": "Cr-OS-foo",
"description": "description"
},
And maps to the frontend like so:
Labels: Cr-???
Cr-OS-foo = description
where Cr-OS-foo is a conveniently predefined value for Label Cr-OS-???.
@param prop_dict: a dictionary we expect the Cros label to be in.
@return: A lower case product area, eg: video, factory, ui.
"""
label = prop_dict.get('label')
if label and 'Cr-OS-' in label:
return label.split('Cr-OS-')[1]
def get_areas(self):
"""
Parse issue options and return a list of 'Cr-OS' labels.
@return: a list of Cr-OS labels from crosbug, eg: ['kernel', 'systems']
"""
if build is None:
logging.error('Missing Api-client import. Cannot get area-labels.')
return []
try:
issue_options_dict = self._get_field('issuesConfig')
except ProjectHostingApiException as e:
logging.error('Unable to determine area labels: %s', str(e))
return []
# Since we can request multiple fields at once we need to
# retrieve each one from the field options dictionary, even if we're
# really only asking for one field.
issue_options = issue_options_dict.get('issuesConfig')
if issue_options is None:
logging.error('The IssueConfig field does not contain issue '
'configuration as a member anymore; The project '
'hosting api might have changed.')
return []
return filter(None, [self._get_cros_labels(each)
for each in self._get_property_values(issue_options)
if isinstance(each, dict)])