""" RS.Utils.Bugstar Module for interfacing with Bugstar. """ import webbrowser import clr import re from functools import wraps import RS.Config import System #import System.Collections.Generic as Generic clr.AddReference("RSG.Base") from RSG.Base.Configuration import ConfigFactory from RSG.Base import Configuration clr.AddReference("RSG.Interop.Bugstar") from RSG.Interop import Bugstar as IBugstar from RSG.Interop.Bugstar.Organisation import Project from RSG.Interop.Bugstar import BugCategory from RSG.Interop.Bugstar import BugBuilder from RSG.Interop.Bugstar import BugPriority # Decorator def cache(func): """ caches the value of the decorated function as a variable of the same name but that has a leading underscore. This is done to avoid calling bugstar multiple times to retrieve the same data. Calls to bugstar are slow, so we want to minimize the number of times that we try to access it as much as possible. :param func: method object the method which you are decorating, automatically gets passed when using the decorator expression :return: func """ @wraps(func) def wrapper(self, *args, **kwargs): #Create the variable name from the method name func_name = func.func_name cached_attribute_name = "_{}".format(func_name) #Get the value of the method from the object passed in attribute_value = getattr(self, cached_attribute_name, None) #Set the value of the variable by running the method #If it hasn't been cached if not attribute_value: setattr(self, cached_attribute_name, func(self,*args, **kwargs)) #print cached_attribute_name, getattr(self, cached_attribute_name) return getattr(self, cached_attribute_name, None) return wrapper #Class class Bugstar(object): category = { "A": BugCategory.A, "B": BugCategory.B, "C": BugCategory.C, "D": BugCategory.D, "Task": BugCategory.Task, "Todo": BugCategory.Todo, "Track": BugCategory.Track} priority = { "High": BugPriority.High, "Low" : BugPriority.Low, 1: BugPriority.High, 2: BugPriority.P2, 3: BugPriority.P3, 4: BugPriority.P4, 5: BugPriority.Low} def __init__(self, username="gameasserts", password="gamea$$ert5", domain=""): """ logins into the server based on the credentials passed in. If no credentials are passed in then the generic login info from North will be used. The domain for rockstar is rockstar.t2.corp :param username: string your login name ei. dvega :param password: string your password :param domain: string the domain that you are trying to access ei. rockstar.t2.corp :param connection: Bugstar.BugstarConnection instance() the connection instance to bugstar :return: True """ self.loggedin = False self.login(username, password, domain) def _AssertProject(self, project): """ check that the project is a RS.Interop.Bugstar.Project :param project: :return: """ assert isinstance(project, Project) def login(self, username="gameasserts", password="gamea$$ert5", domain=""): """ logins into the server based on the credentials passed in. If no credentials are passed in then the generic login info from North will be used. The domain for rockstar is rockstar.t2.corp :param username: string your login name ei. dvega :param password: string your password :param domain: string the domain that you are trying to access ei. rockstar.t2.corp :return: True """ #Thanks for the login North ! #Rockstar Domain : rockstar.t2.corp try: self.connection.Login(username, password, domain) self.clear_cache() self.loggedin = True except: self.loggedin = False def connected(self): return self.loggedin @property @cache def connection(self): """ gets the bugstar connection instance Return: BugstarConnection instance """ #if "RDR3" in RS.Config.Project.Name: # print "rdr" # return self.rdr_connection return IBugstar.BugstarConnection(self.config.RESTService, self.config.AttachmentService) @property def rdr_connection(self): """ At the time of writing, RDR has yet to update their tools to use the new Bugstar C# files. This is a temporary work around to connect to bugstar in the meantime. To be removed at a later time :return: BugstarConnection instance """ print (ConfigFactory.CreateConfig(), self.config) print Configuration.IConfig(ConfigFactory.CreateConfig()), Configuration.Bugstar.IBugstarConfig(self.config) return IBugstar.BugstarConnection(ConfigFactory.CreateConfig(), self.config) @property @cache def config(self): """ Generates a Bugstar Config instance to use Return: BugstarConfig instance """ return ConfigFactory.CreateBugstarConfig() @property @cache def project(self): """ Query the assigned Bugstar project for the current project. Returns: RSG.Model.Bugstar.Organisation.Project object. """ self._project_id = self.config.ProjectId return Project.GetProjectById(self.connection, self.config.ProjectId) @property @cache def projects(self): """ Collect all of the users projects as a dictionary. Returns: Dictionary of RSG.Model.Bugstar.Organisation.Project objects. The key is the name of the project. """ projects = Project.GetProjects(self.connection) return {project.Name: project for project in projects} def get_project_by_name(self, project_name ): """ Get a Bugstar project. Arguments: projectName: The name of the project to get. Returns: RSG.Model.Bugstar.Organisation.Project object. """ return self.projects.get(project_name, None) @property @cache def user(self): """ Returns the name of the current user Return: string; The name of the current user """ return self.project.CurrentUser @property @cache def users(self): """ Gets a list of all the users on the project :param project: :return: dictionary """ #This regular expression is so we get names without the studio they work at #ei. David Vega (R*NYC) to david vega #Compiled the regular expression and assigned method to local variable to increase speed #In python , when you call a method via it's class , ei. obj.method, it has to find it first which takes time. #By assigning it to a variable we skip having to search for it and local scope variables are easier for python #to access than global variables user_name = re.compile("[a-z\- ]+", re.I) user_name_match = user_name.match lower = str.lower users = {lower(user_name_match(user.Name.encode('utf-8')).group()[:-1]): user for user in self.project.Users} return users def get_user_by_name( self, name): """ Get a Bugstar user. Arguments: name: The friendly name of the user to look up. For example: 'Jason Hayes' Returns: If the user exists in the project, will return a RSG.Model.Bugstar.Organisation.User object. None if the user doesn't exist in the project. """ self._AssertProject(self.project) return self.users.get(name.lower(), None) def get_user_by_email(self, email): """ Get a Bugstar user via their email address :param email: string the email address for the user that you want. ei. david.vega@rockstargames.com :param project: RSG.Interop.Bugstar.Organisation.Project() The project instance generated from RSG.Interop.Bugstar.Organisation.Project() :return: RSG.Interop.Bugstar.Organisation.User or None if not match """ lower = unicode.lower email_dictionary = {lower(user.Email): user for user in self.project.Users if user.Email} return email_dictionary.get(email.lower(), None) def get_user_id(self, user=None): """ retrieves the user id of the provided user :param user: string or RSG.Interop.Bugstar.Organisation.User the name, email or User instance of the person who's user Id you want to get :param project: RSG.Interop.Bugstar.Organisation.Project the current project :return: int their id """ # Owner if not user: return self.user.Id if isinstance(user, basestring): if "@" in user: user = self.get_user_by_email(user) else: user = self.get_user_by_name(user) elif isinstance(user, int): return user return user.Id @property @cache def teams(self): lower = str.lower return {lower(user.Name.encode('utf-8')): user for user in self.project.GetTeamsList()} def get_team_by_name(self, name): return self.team.get(name, None) @property @cache def members(self): members = dict(self.users) members.update(self.teams) return members def get_member_by_name(self, name): return self.members.get(name.lower(), None) def get_member_id(self, member): if not member: return self.user.Id if isinstance(member, basestring): if "@" in member: member = self.get_user_by_email(member) else: member = self.get_member_by_name(member) elif isinstance(member, int): return member if member: return member.Id def open_bug(self, bugId ): """ Opens the supplied bug. Arguments: bugId: The bug id to open. """ webbrowser.open( 'url:bugstar:{0}'.format( bugId ) ) def create_bug(self, summary, description, owner = None, qaOwner = None, reviewer = None, ccList = [], category = "Task", priority = 3, tags = [] ): """ Creates a bug. Arguments: project: The project to add the bug. Requires a RSG.Interop.Bugstar.Organisation.Project object. summary: The summary for the bug. description: The description for the bug. Keyword Arguments: owner: The owner of the bug. Provide the friendly name. If not provided, the current user will be used. For example: 'Jason Hayes' qaOwner: The QA owner of the bug. Provide the friendly name. If not provided, the current user will be used. reviewer: The reviewer of the bug. Provide the friendly name. If not provided, the current user will be used. category: The category type of the bug. Use RSG.Interop.Bugstar.BugCategory enums. For quick reference: BugCategory.A BugCategory.B BugCategory.C BugCategory.D BugCategory.Task BugCategory.Todo BugCategory.Track priority: The bug priority. Use RSG.Interop.Bugstar.BugPriority enums. For quick reference: BugPriority.High BugPriority.P2 BugPriority.P3 BugPriority.P4 BugPriority.Low tags: List of tags to add to the bug. Returns: The created bug, which is a RSG.Interop.Bugstar.Bug object. """ self._AssertProject( self.project ) bugBuilder = BugBuilder( self.project ) bugBuilder.Summary = summary bugBuilder.Description = description bugBuilder.Category = self.category.get(category, BugCategory.Task) bugBuilder.Priority = self.priority.get(priority, BugPriority.P3) if owner: # Owner bugBuilder.DeveloperId = self.get_member_id(owner) if qaOwner: # QA Owner bugBuilder.TesterId = self.get_member_id(qaOwner) #Reviewer shouldn't be set- the reviewer list for RDR2 is limited, if set and reviewer not on the list #It will throw a 505 Internal Error #if reviewer: # Reviewer # bugBuilder.ReviewerId = self.get_member_id(reviewer) # CC List cc_list = [self.get_member_id(each) for each in ccList] bugBuilder.CCList = System.Array[System.UInt32](cc_list) # Tags for tag in tags: bugBuilder.Tags.Add( tag ) # Create the bug. return bugBuilder.ToBug(self.connection) def get_bug_by_id( self, bugId ): """ Get a bug by the supplied id. Arguments: bugId: The bug id to query. Returns: RSG.Model.Bugstar.Bug object, None if the bug could not be found. """ #Storing project variable to avoid calling bugstar multiple times bug = IBugstar.Bug( self.project ) found = bug.GetBugById( self.project, bugId ) if found: return found else: current_projects = dict(self.projects) current_projects.pop(self.project.Name) for project_name, project in current_projects.iteritems(): bug = IBugstar.Bug( project ) found = bug.GetBugById( project, bugId ) if found: return found return None def clear_cache(self): """ Sets the value of the cached attributes to None so the cached attributes can be set again :return: None """ [setattr(self, each, None) for each in dir(self) if re.match("_(?=[a-z]+)", each) and each not in ("_config", "_connection", "_AssertProject")] def CreateBugNull( pControl, pEvent ): """ TODO (Hayes 7/17/2013): Need to evaluate if this is still needed. For now, migrating the function from the old bugstar module into this one. """ import pyfbsdk #from pyfbsdk_additions import * import RS.Core.Reference.Manager lBugNull = pyfbsdk.FBFindModelByLabelName( "REFERENCE: BugNull" ) lSceneNull = RS.Core.Reference.Manager.GetReferenceSceneNull() if not lBugNull: lBugNull = pyfbsdk.FBCreateObject( "Browsing/Templates/Elements", "Null", "REFERENCE: BugNull") lBugNull.PropertyCreate( 'Bug Numbers', pyfbsdk.FBPropertyType.kFBPT_charptr, 'String', False, True, None ) if lSceneNull: lBugNull.Parent = lSceneNull lBugNumber = pyfbsdk.FBMessageBoxGetUserValue( "Enter Bug Number", "BugNumber: ", '', pyfbsdk.FBPopupInputType.kFBPopupString, "Ok", "Cancel" ) lProp = lBugNull.PropertyList.Find( 'Bug Numbers' ) if lBugNumber != "": lProp.Data = lProp.Data + ", " + str(lBugNumber) del( lBugNull, lSceneNull, lBugNumber, lProp )