509 lines
16 KiB
Python
Executable File
509 lines
16 KiB
Python
Executable File
"""
|
|
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 )
|