1

我希望能够在 Rally 中查询现有缺陷,然后复制该缺陷,仅更改几个字段,同时保留所有附件。有没有一种简单的方法可以做到这一点?我尝试调用 rally.create 并传递现有的缺陷对象,但它未能将所有成员序列化为 JSON。最终,如果将 pyral 扩展为包含这种功能,那就太好了。

相反,我编写了一些代码来复制现有缺陷的每个 python-native 属性,然后将 .ref 用于其他所有内容。它似乎工作得很好。我已经利用 Mark W 的代码来复制附件,而且效果也很好。剩下的一个挫折是复制迭代不起作用。当我在 Iteration 属性上调用 .ref 时,我得到了这个:

>>> s
<pyral.entity.Defect object at 0x029A74F0>
>>> s.Iteration
<pyral.entity.Iteration object at 0x029A7710>
>>> s.Iteration.ref
No classFor item for |UserIterationCapacity|
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "c:\python27\lib\site-packages\pyral\entity.py", line 119, in __getattr__
    hydrateAnInstance(self._context, item, existingInstance=self)
  File "c:\python27\lib\site-packages\pyral\restapi.py", line 77, in hydrateAnInstance
    return hydrator.hydrateInstance(item, existingInstance=existingInstance)
  File "c:\python27\lib\site-packages\pyral\hydrate.py", line 62, in hydrateInstance
    self._setAppropriateAttrValueForType(instance, attrName, attrValue, 1)
  File "c:\python27\lib\site-packages\pyral\hydrate.py", line 128, in _setAppropriateAttrValueForType
    elements = [self._unravel(element) for element in attrValue]
  File "c:\python27\lib\site-packages\pyral\hydrate.py", line 162, in _unravel
    return self._basicInstance(thing)
  File "c:\python27\lib\site-packages\pyral\hydrate.py", line 110, in _basicInstance
    raise KeyError(itemType)
KeyError: u'UserIterationCapacity'
>>>

这看起来像是 Rally 的问题,还是我们的项目管理员可能导致的自定义字段的问题?我能够通过从 oid 构建 ref 来解决它:

newArtifact["Iteration"] = { "_ref": "iteration/" + currentArtifact.Iteration.oid }

不过,这对我来说感觉很笨拙。

4

2 回答 2

0

查看这个问题的 Python 答案(有 2 个答案,另一个是 Ruby):

Rally API:如何复制测试文件夹和成员测试用例

答案包含一个 python 脚本,它复制测试用例和附件。虽然脚本是针对测试用例的,但逻辑应该很容易适应缺陷,因为操作基本相同——只有字段属性不同。脚本复制了附件、标签等,几乎是整个工件。

于 2013-01-08T18:04:46.293 回答
0

最终解决方案,包括 Mark W 复制附件的代码

def getDataCopy( data ):

    """ Given a piece of data, figure out how to copy it.  If it's a native python type
        like a string or numeric, just return the value.  If it's a rally object, return
        the ref to it.  If it's a list, iterate and call ourself recursively for the
        list members. """

    if isinstance( data, types.ListType ):
        copyData = []
        for entry in data:
            copyData.append( getDataCopy(entry) )

    elif hasattr( data, "ref" ):
        copyData = { "_ref": data.ref }

    else:
        copyData = data

    return copyData

def getArtifactCopy( artifact ):

    """ Build a dictionary based on the values in the specified artifact.  This dictionary
        can then be passed to a rallyConn.put() call to actually create the new entry in
        Rally.  Attachments and Tasks must be copied seperately, since they require creation
        of additional artifacts """

    newArtifact = {}

    for attrName in artifact.attributes():

        # Skip the attributes that we can't or shouldn't handle directly
        if attrName.startswith("_") or attrName == "oid" or attrName == "Iteration" or attrName == "Attachments":
            continue

        attrValue = getattr( artifact, attrName )
        newArtifact[attrName] = getDataCopy( attrValue )

    if getattr( artifact, "Iteration", None ) != None:
        newArtifact["Iteration"] = { "_ref": "iteration/" + artifact.Iteration.oid }

    return newArtifact

def copyAttachments( rallyConn, oldArtifact, newArtifact ):

    """ For each attachment in the old artifact, create new attachments and attach them to the new artifact"""

    # Copy Attachments
    source_attachments = rallyConn.getAttachments(oldArtifact)

    for source_attachment in source_attachments:

        # First copy the content
        source_attachment_content = source_attachment.Content
        target_attachment_content_fields = { "Content": base64.encodestring(source_attachment_content) }

        try:
            target_attachment_content = rallyConn.put( 'AttachmentContent', target_attachment_content_fields )
            print "\t===> Copied AttachmentContent: %s" % target_attachment_content.ref
        except pyral.RallyRESTAPIError, details:
            sys.stderr.write('ERROR: %s \n' % details)
            sys.exit(2)

        # Next copy the attachment object
        target_attachment_fields = {
            "Name": source_attachment.Name,
            "Description": source_attachment.Description,
            "Content": target_attachment_content.ref,
            "ContentType": source_attachment.ContentType,
            "Size": source_attachment.Size,
            "User": source_attachment.User.ref
        }

        # Attach it to the new artifact
        target_attachment_fields["Artifact"] = newArtifact.ref

        try:
            target_attachment = rallyConn.put( source_attachment._type, target_attachment_fields)
            print "\t===> Copied Attachment: '%s'" % target_attachment.Name
        except pyral.RallyRESTAPIError, details:
            sys.stderr.write('ERROR: %s \n' % details)
            sys.exit(2)

def copyTasks( rallyConn, oldArtifact, newArtifact ):

    """ Iterate over the old artifacts tasks and create new ones, attaching them to the new artifact """

    for currentTask in oldArtifact.Tasks:

        newTask = getArtifactCopy( currentTask )

        # Assign the new task to the new artifact
        newTask["WorkProduct"] = newArtifact.ref

        # Push the new task into rally
        newTaskObj = rallyConn.put( currentTask._type, newTask )

        # Copy any attachments the task had
        copyAttachments( rallyConn, currentTask, newTaskObj )

def copyDefect( rallyConn, currentDefect, addlUpdates = {} ):

    """ Copy a defect including its attachments and tasks.  Add the new defect as a
        duplicate to the original """

    newArtifact = getArtifactCopy( currentDefect )

    # Add the current defect as a duplicate for the new one
    newArtifact["Duplicates"].append( { "_ref": currentDefect.ref } )

    # Copy in any updates that might be needed
    for (attrName, attrValue) in addlUpdates.items():
        newArtifact[attrName] = attrValue

    print "Copying %s: %s..." % (currentDefect.Project.Name, currentDefect.FormattedID),
    newDefect = rallyConn.create( currentDefect._type, newArtifact )

    print "done, new item", newDefect.FormattedID

    print "\tCopying attachments"
    copyAttachments( rallyConn, currentDefect, newDefect )

    print "\tCopying tasks"
    copyTasks( rallyConn, currentDefect, newDefect )
于 2013-01-15T18:20:11.970 回答