65.9K
CodeProject 正在变化。 阅读更多。
Home

Excel、Jira、Rest API 端到端示例

starIconstarIconstarIconstarIconstarIcon

5.00/5 (13投票s)

2016年4月12日

CPOL

19分钟阅读

viewsIcon

232477

downloadIcon

9973

关于如何将 Jira REST API 与 Excel 集成的端到端视图。

引言

网上有很多关于 Jira REST API 的信息,并且可以从 Excel 对 Jira 进行一些简单的 REST 调用,但是我一直未能找到一个好的端到端资源,可以将所有概念整合在一起,向人们展示如何登录、检索数据、解析返回数据并插入到 Excel 中。有一些第三方付费的 Excel 插件,但我想要一个通用的 Jira 与 Excel 集成插件,它能让我获取、更新、创建,并从 Jira 信息创建 Word 文档(即更改请求表单),而无需付费。

在本文中,我将分享我的经验、Excel VBA 代码,以及对其他代码的引用,这些代码提供了 Jira、Excel 和 Word 之间相当优雅(尽管仍在进行中)的半通用集成。

 

背景

使用 Jira 时,我想要解决的第一个问题是,我希望看到一个问题列表,以及它们的关联问题、关联问题的当前状态和已修复版本。我无法在 Jira 中找到实现此功能的方法,因此我决定编写一个 Excel 宏,该宏将从 Jira 获取问题,并解析关联的工单并列出状态和已修复版本,以便在一行/视图中查看工单及其关联工单的状态。在下图的 O、P 和 Q 列中显示了我正在寻找的结果。如果您查看 CLOUD-8050 行,您将看到当有多个问题链接到一个问题时,它是如何显示在一个单元格中的。

 

解决了这个问题之后,我不断扩展 Excel 宏的功能,以创建新工单、更新工单并使用 Jira 工单中的数据填充 Word 文档。创建和更新功能或多或少是一次性需求,但表单项目似乎是一个持续的通用问题。

在从事这个项目时,我最大的问题是,有很多文章讨论了在 Excel 中使用 Jira Rest API 的单点解决方案,但没有真正提供关于如何登录、调用 API、解析 Json 以及我所学到的细微差别的端到端视图。

使用 Excel Jira 集成宏

在本节中,我将记录如何以最终用户身份使用 Excel 工作簿。在兴趣点部分,我将记录学习内容和关键代码片段。

注意:我已使用来自 https://jira.atlassian.com 的数据作为示例。我注意到一件有趣的事情是,REST API 调用有时会失败,但随后的调用会正常工作。我将其归咎于 Jira 服务器的负载,但尚未引入重试逻辑,因为只需再次点击“获取问题”按钮,第二次一切都会正常工作,这已经足够简单了。这也是我必须在登录过程中引入“无身份验证”代码的地方,因为 Jira 网站似乎不需要身份验证,也不会在 Jira Rest API 上接受它。

 

本文附带的 Excel 工作簿包含自定义菜单添加项以及我开发或其他人开发的宏。

打开工作簿时,功能区上会有一个名为 JIRA 的新菜单。这是一个自定义菜单,它调用执行与检索和处理 Jira 数据相关的各种功能的宏。

                          

主要的菜单选项是“获取问题”,如果用户尚未通过 Excel 登录 Jira,此宏将调用登录。它将在 B1 列中执行 Jira JQL 命令,并从 A8 处的表中返回结果,该表在 Excel 中由定义的名称“StartRow”引用。

请参见背景部分图片中 Excel 工作簿的示例。

在 macroValues 工作表上有一个表,用于定义如何从 JSON 格式的 Jira 返回数据中提取数据,以便将其插入到 Excel 工作表中。

在 Excel 工作表“工单”的第 5 行(已隐藏)包含每个列的键。此键会在 macroValues 工作表中的此表中查找信息,以确定如何从 Jira Json 中提取数据,以及是否应将方法应用于数据以转换数据。

您可以通过简单地插入新列并指明要用于转换数据的键值,从而添加和删除“工单”工作表中的列。您可以通过遵循表中的格式,在 macroValues 中更新您的 Jira 实例特定自定义字段和您可能想要拉入 Excel 的其他字段。请注意,即使您的字段名称/标签相同,自定义字段也会有不同的值。这些值对于每个 Jira 实例都是唯一的。

当您对表进行更改时,需要单击功能区 Jira 组中的“初始化”按钮。这将动态创建一个名为 getValue 的宏,并使用此表创建一个子例程,该子例程知道如何引用 Jira 返回数据中的所需数据。

 

字段 值参考 检查空引用 方法
("key") ("key")  
状态 ("fields")("status")("name") ("fields")("status")("name")  
项目 ("fields")("project")("name") ("fields")("project")("name")  
summary ("fields")("summary") ("fields")("summary")  
已创建 ("fields")("created") ("fields")("created") 从ISODateTimeNoZ
已更新 ("fields")("updated") ("fields")("updated") 从ISODateTimeNoZ
受让人 ("fields")("assignee")("name") ("fields")("assignee")  
报告人 ("fields")("reporter")("name") ("fields")("reporter")  
修复版本 ("fields")("fixversions") ("fields")("fixversions") 获取带计数字段
问题类型 ("fields")("issuetype")("name") ("fields")("issuetype")  
标签 ("fields")("labels") ("fields")("labels") 获取带计数字段
优先级 ("fields")("priority")("name") ("fields")("priority")  
严重性 ("fields")("customfield_12520")("value") ("fields")("customfield_12520")("value")
解决 ("fields")("resolution")("name") ("fields")("resolution")  
问题链接 ("fields")("issuelinks") ("fields")("issuelinks") 获取字段问题链接
业务驱动 ("fields")("customfield_15623") ("fields")("customfield_15623")  
业务重要性 ("fields")("customfield_12420") ("fields")("customfield_12420")  
description ("fields")("description") ("fields")("description")  
详细描述 ("fields")("customfield_15621") ("fields")("customfield_15621")  
可计费 ("fields")("customfield_15926")(1)("value") ("fields")("customfield_15926")  
工作量估算 ("fields")("customfield_15629") ("fields")("customfield_15629")  
需要审批 ("fields")("customfield_16320")("value") ("fields")("customfield_16320")  
经批准者 ("fields")("customfield_11622")("displayName") ("fields")("customfield_11622")  
 
 
          
 
 
登录菜单适用于那些可能有多个 Jira 实例的用户。例如,您使用的不同 Jira 服务器并希望从中检索数据。并且只有在同一会话中获取数据时才需要它。例如,如果您打开并关闭 Excel,然后切换服务器,“获取问题”命令按钮将提示您输入登录信息并让您切换服务器。
 
“更新链接状态”命令按钮仅用于您只想更新“工单”工作表中已存在的链接工单的状态。当您使用“获取工单”命令按钮时,它会获取 Jira 工单并一次性更新链接状态。
 
“填写 Word 模板”命令按钮会提示选择 Word 模板和 Jira 工单密钥。Word 模板的下拉框填充自 macroValues 工作表中的表格,只需添加您想要用作模板的任何 Word 文档的完整路径即可。
 
该命令会加载 Word 文档,遍历 Word 文档中的所有自定义表单字段,并将每个表单字段的标签与上面“键”列中描述的同一表格匹配。匹配成功后,相应的 Jira 数据将插入到表单字段中。
 
以下 Word 文档摘录显示了表单字段位于表格的第二列中。
 
要在 Word 文档中插入 Word 客户表单字段,您必须在 Word 中启用开发人员选项卡。下图显示了 Word 中已启用开发人员选项卡。要插入字段,请选择“设计模式”命令模式,它将突出显示为灰色,表示您处于 Word 的设计模式。
 
 
进入设计模式后,您可以插入一个文本框或复选框,宏可以将 Jira 数据插入其中。插入表单字段后,您可以右键单击它并选择属性菜单选项。
 
 
在刚插入的表单字段的属性对话框中,编辑标签字段并插入 macroValues 工作表上 Jira 表格中键的名称,以指明您希望将哪些数据插入此表单字段。同时,勾选“内容不可编辑”字段也是个好主意,这样用户就不会在修改 Word 文档时误以为可以在 Jira 中进行更改。
 
未来的版本或单独的项目可能会创建代码,允许更新 Word 文档中的 Jira 数据,然后将其更新到 Jira 中。虽然这在技术上是可行的,但需要仔细考虑工作流程和这样做的潜在风险。例如,如果自数据插入 Word 文档后数据已更改怎么办?最终用户是否可以访问 Jira?通常,此功能旨在获取 Jira 数据的静态副本,并从客户或利益相关者那里获取物理签名。
 
根据需要,重复此过程,插入任意数量的 Jira 字段到您的 Word 模板中。
 
 
 
 
一旦您的 Word 模板准备就绪,只需从 JIRA 自定义菜单中选择命令按钮并填写对话框即可。Word 的新实例将打开,加载模板,从 Jira 获取数据,然后插入到 Word 文档中。
 
创建和更新菜单选项对我来说是一次性交易。它们硬编码为用于创建和更新工单的值,但可以作为使用 Jira API 的一个不错的参考,不过并没有什么花哨的,也没有什么其他文章没有涉及到的。

工具和依赖项

在进入兴趣点并讨论一些代码之前。已经使用了许多工具和其他代码。因此,在此向应得的致敬...

  • Chrome Postman
    • 我发现使用 Chrome 插件 Postman 非常有价值,因为我可以确保 REST URI 的基本格式和结构是正确的,并在 Postman 中对其进行测试。查看返回的 Json 也非常有用,这样我就可以识别层次结构,知道如何访问转换后的 Json 对象结构。
    • 通过 Postman 将 REST API 字符串发送到 Jira,我可以看到返回的原始 JSON。这是确定自定义字段标签的最佳方法,以便您可以准确地解引用返回的 JSON。您可能需要使用一些唯一的标识值更新现有工单,以便在返回的 JSON 中挑出该字段。

  • 保存用于登录对话框的用户名和服务器 URL,它使用了我从以下位置获取的 WriteProp 和 ReadProp 方法。

http://word.mvps.org/faqs/macrosvba/MixedDocProps.htm

  • Microsoft Office 自定义 UI 编辑器
    • 我在 http://openxmldeveloper.org/blog/b/openxmldeveloper/archive/2009/08/07/7293.aspx 找到了这个工具,并利用它轻松自定义 Excel 功能区菜单。
    • 为了将我自己的菜单添加到 Excel 文档中,我使用 UI 编辑器工具编辑和更新 Excel 文件,以便在打开 Excel 文件时可以使用 JIRA 功能区菜单。
  • 有用的东西

VBA 项目中有几个方法以“有用的东西”开头。有关“有用的东西”的详细信息,请查看以下网站。我复制了 fromISODateTime 函数并创建了 fromISODateTimeNoZ,它只是从正则表达式中删除了“Z”,以便解析从 Jira 返回的日期,这些日期是 ISO 格式,但末尾没有 Z。我曾一度尝试使用 mcpher 代码中提供的 cJobject,但最终使用的是下面提到的 VBA-JSON 项目。ramblings.mcpher.com 上似乎有很多很酷的东西,值得一读。我导入了很多他的代码,但最终没有使用太多。无论如何,我认为那里有一些非常酷的概念和东西值得进一步研究。


有用信息材料的许可和版权信息->

'gistThat@mcpher.com:请勿修改此行 - 详情请参阅ramblings.mcpher.com:于 2016 年 2 月 16 日上午 10:02:49 更新:来自 manifest:3414394 gist https://gist.github.com/brucemcpherson/3414346/raw
选项明确
' v2.23 3414346
 
' 感谢 Charles Wheeler - http://www.decisionmodels.com/ 提供的微型计时器程序。
这个工具相对容易使用,但我没有找到太多关于其实际用法的文档,并且不得不通过反复试验学习很多东西。我遇到的最大问题是确定是否存在类似 IsNULL 的值。
' ---

 

  • 这个工具相对容易使用,但我没有找到太多关于其实际用法的文档,并且不得不通过反复试验学习很多东西。我遇到的最大问题是确定是否存在类似 IsNULL 的值。

 

  • VBA-JSON 工具相对容易使用。
 Set JsonObject = JsonConverter.ParseJson(JsonIssues)

这里,从 Jira 返回的 Json 字符串被解析成一个名为 JsonObject 的对象,解析成对象后,您可以将 Json 引用为数组类型对象的数组。

从上面 Chrome Postman 中返回的 Json 图像中,可以进行以下引用。

dim s as string
s = JsonObject("maxResults") 
s = JsonObject("issues")(1)("key")
s = JsonObject("issues")(1)("fields")("customfield_16730")("value")
s = JsonObject("issues")(1)("fields")("priority")("name")

这里的诀窍是,当一个字段有子数组时,您必须检查父区域是否为空。您不能使用 isnull(JsonObject("issues")(1)("fields")("priority")("name")),您必须检查 JsonObject("issues")(1)("fields")("priority") 是否为空。


有用信息材料的许可和版权信息->

' VBA-JSON v2.0.1

' (c) Tim Hall - https://github.com/VBA-tools/VBA-JSON
'
' VBA 的 JSON 转换器
'
' 错误
' 10001 - JSON 解析错误
'
' @class JsonConverter
' @author tim.hall.engr@gmail.com
' @license MIT (https://open-source.org.cn/licenses/mit-license.php)
'' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ '
'
' 最初基于 vba-json(经过大量修改)
' 包含以下 BSD 许可证
'
' JSONLib, http://code.google.com/p/vba-json/
'
' 版权所有 (c) 2013, Ryo Yokoyama
' 保留所有权利。


关注点

基本代码流程

  • 登录 Jira
  • 调用 Jira REST API 返回 Json
  • 将 Json 转换为对象
  • 使用 Excel 中定义的表解引用对象
  • 将结果插入工作表或 Word 文档

登录

互联网上有很多登录 Jira 的例子。为了完善这些例子,我增加了一些功能。

  • 登录对话框
  • 能够在基本身份验证和无身份验证之间选择
  • 保存用户名和 Jira URL 以备将来登录
  • 在同一会话中保存凭据,以便不需要后续登录

凭据存储在一个名为 usernamep 的全局变量中,如果该值不为空“”,则在进行登录调用时,该值将与先前提供的凭据一起返回。否则,将显示一个对话框。如果选中“无身份验证”复选框,则 usernamep 将设置为“NoAuth”。

提供 GetIssues 函数以显示如何使用登录信息。当您将其组合在一起时,它非常基本。如您所见,如果它是 NoAuth,则标头设置为“Authorization”、“No Auth”,否则您将标头设置为 basic 并使用编码的用户名密码。

将 usernamep 用于两个目的是一种糟糕的编程实践,理想情况下,授权值应该是一个完全独立的变量。未来的代码版本可能会支持这一点,以便可以支持额外的授权方法。

             If usernamep = "NoAuth" Then
                .SetRequestHeader "Authorization", "No Auth"
             Else
                .SetRequestHeader "Authorization", "Basic " & usernamep
             End If

Public usernamep As String
Dim url As String
Dim noauth As Boolean

Public Function UserPassBase64() As String
    Dim objXML       As MSXML2.DOMDocument60
    Dim objNode      As MSXML2.IXMLDOMElement
    Dim arrData()    As Byte

    If usernamep = "" Then
        fLogin.Show
        If (fLogin.Cancel = False) Then
            usernamep = fLogin.txtUsername.Value + ":" + fLogin.txtPassword.Value
            url = fLogin.txtServer.Value
            noauth = fLogin.cbNoAuth.Value
            If noauth = True Then
                UserPassBase64 = "NoAuth"
                usernamep = "NoAuth"
            Else
                arrData = StrConv(usernamep, vbFromUnicode)
                Set objXML = New MSXML2.DOMDocument60
                Set objNode = objXML.createElement("b64")
                objNode.DataType = "bin.base64"
                objNode.nodeTypedValue = arrData
                UserPassBase64 = objNode.Text
            End If
        Else
            UserPassBase64 = "cancel"
        End If
    Else
        UserPassBase64 = usernamep
    End If

End Function
 
 
 
 

Rest 调用以获取问题

此方法唯一真正独特的地方是 s = GetUrl 行。我们可以进一步抽象此方法,以便传入实际的 Rest API,从而可以在同一方法中进行返回多个问题或单个问题的查询,并重用所有相关逻辑。
 
注意:当您开始撰写代码时,您会发现自己的代码中存在惊人的重构和改进之处。
 
Public Function GetIssues(query As String) As String

    Dim JiraService As New MSXML2.XMLHTTP60
    Dim json As Object
    Dim s As String

    usernamep = UserPassBase64
 

    If usernamep = "cancel" Then
        fStatus.AppendStatus ("User canceled login.")
    Else
        fStatus.AppendStatus ("Getting Jira Issues you might see Not Responding in title bar. The time this takes is dependant on the complexity of your query, network, and number of issues being returned.")

        With JiraService
             s = GetUrl + "/rest/api/2/search?jql=" + query + "&startAt=" + _
                        CStr(Range("StartAt").Value) + "&maxResults=" + _
                        CStr(Range("MaxResults").Value)
      .Open "GET", s         
             .SetRequestHeader "Content-Type", "application/json"
             .SetRequestHeader "Accept", "application/json"

             If usernamep = "NoAuth" Then
                .SetRequestHeader "Authorization", "No Auth"
             Else
                .SetRequestHeader "Authorization", "Basic " & usernamep
             End If
             .Send ""

             If .status = "401" Then
                 fStatus.AppendStatus ("Something wrong with query in GetIssues, check your network connection, :  " + .ResponseText)
                 GetIssues = ""
             Else
                 GetIssues = JiraService.ResponseText
             End If
        End With
    End If
End Function
 
 
 
 

将 Json 转换为对象并插入工作表

这里更有趣的方法是,一旦 Json 从 Jira 返回,代码就会遍历返回 Json 中的行和工作表中的列。使用列标题提取要显示的数据。

 

此方法的基本流程是

  • 删除表中的现有数据
  • 将 Json 字符串转换为对象:设置 JsonObject = JsonConverter.ParseJson(JsonIssues)
  • 循环行,遍历 Json 中返回的问题数量
  • 循环列并将数据插入工作表

 

Sub ProcessIssues(JsonIssues As String)
    Dim t As ListObject
    Dim z As Double
    Dim JsonObject As Object
    Dim r As Range
    Dim fpath As Range
    Dim s As String

    Set t = ActiveWorkbook.Sheets("Tickets").ListObjects("Table2")

    ' ignoring error if table is already empty
    On Error Resume Next
    t.DataBodyRange.Delete
    On Error GoTo 0
  
  

    Set r = Range("StartRow") ' row to start putting data in
    Set fpath = Range("StartFieldKey") ' Column headings to loop through

    'Convert Json to an object array that makes it easy to access the Json Hierarchy
    fStatus.AppendStatus ("Parsing Returned Json")
    Set JsonObject = JsonConverter.ParseJson(JsonIssues)

    Range("TotalReturned").Value = JsonObject("total")
    fStatus.AppendStatus ("Retrieved " & CStr(JsonObject("total")) & " issue(s) matching query from Jira. Max issues to be retrieved is set at " & CStr(Range("MaxResults").Value) & vbCrLf & " Update maxResults on Trickets sheet to retrieve more.")
    Set issues = JsonObject("issues")
    On Error GoTo gerr

    For z = 1 To issues.count ' row loop: through and process each issue
        fStatus.AppendStatusBar ("Inserting jira issue " & z & " of " & issues.count & " into worksheet.")
        fStatus.progress ((z * 100 / (issues.count)) / 2)
        While (fpath.Value <> "") ' column loop: for each column in excel get the value from the Json Object and put in the cell

            ' Select statement first handles fields that have to have special handling these are fields that have multiple values usually
            ' the case else : calls the default field handling where there is a simple translaction from the Json
            On Error Resume Next
            r.Offset(0, fpath.column - 1).Value = getValue(issues(z), fpath.Value)
            On Error GoTo gerr

            Set fpath = fpath.Offset(0, 1)

        Wend
        Set fpath = Range("StartFieldKey")
        Set r = r.Offset(1, 0)
    Next z
    GoTo finish
gerr:
    fStatus.AppendStatus ("Oops something went wrong: " & vbCrLf & err.description & vbCrLf & " source: " & err.Source & " at row: " & r.row & " column: " & fpath.Value)

finish:

End Sub

 

获取值

getValue 函数是根据 macroValues 工作表上 D 到 G 列中的表格动态创建的。这允许能够根据每个 Jira 实例的自定义字段值从 Json 中提取数据,因为自定义字段值对于 Jira 实例是唯一的。

 

CreateCaseSub 函数创建一个新函数,并使用 VBProject 对象引用将其动态添加到 Excel VBA 项目中。

创建函数的函数具有创建长字符串然后调用适当方法将字符串作为代码插入的基本形式。

  • 创建函数头代码
  • 遍历表格并创建 Select Case 语句
  • 创建函数脚注代码
  • 插入到 VBA 项目中
Function CreateCaseSub() As VBComponent
    Dim code As String
    Dim r As Range
   
    Set r = Range("getValueStart")
    code = "Public Function getValue(issue as object, key as String) as String" & vbNewLine & _
                vbTab & "dim rvalue as String" & vbNewLine & _
                vbTab & "on error goto gerr" & vbNewLine & _
                vbTab & "Select Case key" & vbNewLine
   
    While (r.Value <> "")
        code = code & vbTab & vbTab & "Case " & """" & r.Offset(0, 0).Value & """" & vbNewLine & _
        vbTab & vbTab & vbTab & "if IsNull(issue" & r.Offset(0, 2).Value & ") then" & vbNewLine & _
        vbTab & vbTab & vbTab & vbTab & "rvalue = """"" & vbNewLine & _
        vbTab & vbTab & vbTab & "else" & vbNewLine


        If r.Offset(0, 3).Value = "" Then
            code = code & vbTab & vbTab & vbTab & vbTab & "rvalue = issue" & r.Offset(0, 1).Value & vbNewLine
        Else
            code = code & vbTab & vbTab & vbTab & vbTab & "rvalue = " & r.Offset(0, 3).Value & "(issue" & r.Offset(0, 1).Value & ")" & vbNewLine
           
        End If
        code = code & vbTab & vbTab & vbTab & "End if" & vbNewLine
        Set r = r.Offset(1, 0)
    Wend
    code = code & vbTab & "End Select" & vbNewLine & _
            vbTab & "getValue = rvalue" & vbNewLine & _
            vbTab & "goto finish:" & vbNewLine & _
            "gerr:" & vbNewLine & _
            vbTab & "AppendStatus (""error getValue issue key: "" & key & "" "" & err.description )" & vbNewLine & _
            "finish:" & vbNewLine & _
            "End Function"
   
   
   
    Dim tempModule As VBComponent
    Set tempModule = ThisWorkbook.VBProject.VBComponents.add(VBIDE.vbext_ComponentType.vbext_ct_StdModule)
    Call tempModule.codeModule.DeleteLines(1, tempModule.codeModule.CountOfLines)
    Call tempModule.codeModule.AddFromString(code)
    tempModule.name = "Module1"
    Set CreateCaseSub = tempModule
End Function

以下代码是 getValue 的一个示例。对于表中的每个键,都会检查对象是否存在空引用,并返回“”(表示空)或实际的对象数据。

最初我使用了 iif 函数,但它和许多三元运算符一样,实际上会解析 false 表达式并在无法引用对象时抛出错误,因此必须使用更冗长的 if then else 子句。

Public Function getValue(issue As Object, key As String) As String
    Dim rvalue As String
    On Error GoTo gerr
    Select Case key
        Case "key"
            If IsNull(issue("key")) Then
                rvalue = ""
            Else
                rvalue = issue("key")
            End If
        Case "status"
            If IsNull(issue("fields")("status")("name")) Then
                rvalue = ""
            Else
                rvalue = issue("fields")("status")("name")
            End If
        Case "project"
            If IsNull(issue("fields")("project")("name")) Then
                rvalue = ""
            Else
                rvalue = issue("fields")("project")("name")
            End If
        Case "summary"
            If IsNull(issue("fields")("summary")) Then
                rvalue = ""
            Else
                rvalue = issue("fields")("summary")
            End If
        Case "created"
            If IsNull(issue("fields")("created")) Then
                rvalue = ""
            Else
                rvalue = fromISODateTimeNoZ(issue("fields")("created"))
            End If
        Case "updated"
            If IsNull(issue("fields")("updated")) Then
                rvalue = ""
            Else
                rvalue = fromISODateTimeNoZ(issue("fields")("updated"))
            End If
        Case "assignee"
            If IsNull(issue("fields")("assignee")) Then
                rvalue = ""
            Else
                rvalue = issue("fields")("assignee")("name")
            End If
        Case "reporter"
            If IsNull(issue("fields")("reporter")) Then
                rvalue = ""
            Else
                rvalue = issue("fields")("reporter")("name")
            End If
        Case "fixversions"
            If IsNull(issue("fields")("fixversions")) Then
                rvalue = ""
            Else
                rvalue = GetFieldsWithCount(issue("fields")("fixversions"))
            End If
        Case "issuetype"
            If IsNull(issue("fields")("issuetype")) Then
                rvalue = ""
            Else
                rvalue = issue("fields")("issuetype")("name")
            End If
        Case "labels"
            If IsNull(issue("fields")("labels")) Then
                rvalue = ""
            Else
                rvalue = GetFieldsWithCount(issue("fields")("labels"))
            End If
        Case "prioity"
            If IsNull(issue("fields")("priority")) Then
                rvalue = ""
            Else
                rvalue = issue("fields")("priority")("name")
            End If
        Case "severity"
            If IsNull(issue("fields")("customfield_12520")("value")) Then
                rvalue = ""
            Else
                rvalue = issue("fields")("customfield_12520")("value")
            End If
        Case "resolution"
            If IsNull(issue("fields")("resolution")) Then
                rvalue = ""
            Else
                rvalue = issue("fields")("resolution")("name")
            End If
        Case "issuelinks"
            If IsNull(issue("fields")("issuelinks")) Then
                rvalue = ""
            Else
                rvalue = GetFieldsIssueLink(issue("fields")("issuelinks"))
            End If
        Case "businessDriver"
            If IsNull(issue("fields")("customfield_15623")) Then
                rvalue = ""
            Else
                rvalue = issue("fields")("customfield_15623")
            End If
        Case "businessSignificance"
            If IsNull(issue("fields")("customfield_12420")) Then
                rvalue = ""
            Else
                rvalue = issue("fields")("customfield_12420")
            End If
        Case "description"
            If IsNull(issue("fields")("description")) Then
                rvalue = ""
            Else
                rvalue = issue("fields")("description")
            End If
        Case "detailedDescription"
            If IsNull(issue("fields")("customfield_15621")) Then
                rvalue = ""
            Else
                rvalue = issue("fields")("customfield_15621")
            End If
        Case "billable"
            If IsNull(issue("fields")("customfield_15926")) Then
                rvalue = ""
            Else
                rvalue = issue("fields")("customfield_15926")(1)("value")
            End If
        Case "LOE"
            If IsNull(issue("fields")("customfield_15629")) Then
                rvalue = ""
            Else
                rvalue = issue("fields")("customfield_15629")
            End If
        Case "approvalRequired"
            If IsNull(issue("fields")("customfield_16320")) Then
                rvalue = ""
            Else
                rvalue = issue("fields")("customfield_16320")("value")
            End If
        Case "approvedBy"
            If IsNull(issue("fields")("customfield_11622")) Then
                rvalue = ""
            Else
                rvalue = issue("fields")("customfield_11622")("displayName")
            End If
    End Select
    getValue = rvalue
    GoTo finish:
gerr:
    AppendStatus ("error getValue issue key: " & key & " " & err.description)
finish:
End FunctionPublic Function 

 

将数据插入 Word

将代码插入 Word 的方式类似。它也使用 getValue 将存储在 Word 自定义表单字段标记属性中的键值转换为用于插入 Word 模板的数据。创建 Word 模板的基本流程是

  • 选择 Word 模板并提供 Jira 票证密钥
  • 打开 Word 模板
  • 检索该票证的 Jira 数据
  • 遍历自定义表单字段
  • 将标签与键值匹配并插入数据

以下代码执行解析。在提示输入模板和键,检索 Json 并将其转换为对象后,此代码会遍历 Word 表单字段。

 

Sub FilloutWordTemplateWithJiraData(ByRef o As Object, template As String, s As fEnhancement)


    Dim w As Word.Application
    Dim wd As Word.Document
    Dim jiraKey As String   
    Dim oCC As ContentControl
    Dim i As Integer
    Dim lc As Boolean
   
     
    s.status.Caption = "Opening Microsoft Word"
    Set w = CreateObject("Word.Application")
    w.Visible = True
       
    s.status.Caption = "Opening template: " & template
    Set wd = w.Documents.Open(template)
    'wd.SaveAs MyDocuments() & "\" & jiraKey


    i = 1
    For Each oCC In wd.ContentControls
        strText = ""
        s.status.Caption = "Update form field " & i & " of " & wd.ContentControls.count
        i = i + 1
        Debug.Print oCC.tag
       
        strText = getValue(o, oCC.tag)
        lc = oCC.LockContents
     
        oCC.LockContents = False
        Select Case oCC.Type
            Case wdContentControlCheckBox
               
                If strText = "Yes" Then
                   oCC.Checked = True
                End If
               
            'Case wdContentControlDate ' todo: determine if special date handling needs to be added
            Case default
               
                If strText <> "" Then
                    oCC.Range.InsertAfter strText
                    oCC.SetPlaceholderText , , strText ' if placeholder text exist this removes it so that a clean document without unwanted texted is provided.
                End If
           
        End Select
        oCC.LockContents = lc
    Next oCC
    s.status.Caption = "Finished updating " & i & " fields in template."
  Exit Sub
End Sub

解析 Jira Json 的经验

注意“billable”字段及其在 (“value”) 之前的 (1) 是如何使用的
 
来自 macroValues 工作表的行
可计费 ("fields")("customfield_15926")(1)("value") ("fields")("customfield_15926")
 
以下是 Jira 返回的 Json 代码片段,请注意字段名称后如何有一个方括号“[”。这表示 Json 中的一个数组。这里我决定只抓取第一个数组值。可以编写一个特殊方法来从该字段中提取所有值,类似于 GetFieldsWithCount。但是,在这种情况下,我知道只会有一个值。这很可能是 Jira 中自定义字段的创建方式导致的,在这种情况下可能是一个错误。但它作为一个很好的参考和关于如何提取 Json 的提示。
...

   "customfield_13624": null,
    "customfield_15926": [
      {
        "self": "https://jira.mycompany.com/rest/api/2/customFieldOption/16883",
        "value": "Yes",
        "id": "16883"
      }
    ],
    "cus....
     

创建 Excel 菜单

为每个 Excel 文件创建自定义菜单非常酷。请注意,这不是永久性的加载项。菜单只会在您打开关联的 Excel 文件并使其获得焦点时显示。

要使用 Microsoft Office 的自定义 UI 编辑器,只需在工具中打开 Excel 文件(注意:Excel 文件不能在 Excel 中打开),创建 XML 并保存文件。如果 XML 中存在错误,菜单将不会显示,也不会显示错误消息。因此,建议一次添加一个元素。

我没有尝试为我的自定义菜单创建自定义图标,而是从 Office 的标准图标集中选择。我利用以下链接中的参考资料浏览了可用的图标。

http://soltechs.net/CustomUI/imageMso01.asp

我发现的一个更令人恼火的学习是,UI 编辑器似乎除了菜单外,还将 Excel 宏加载到其内存中。因此,如果您打开 UI 编辑器,打开 Excel 文件并更新 XML,然后保存它,但让 Excel 文件在 UI 编辑器中保持打开状态,如果您回到 UI 编辑器并进行更改并再次保存,您对 Excel 宏所做的任何更改都将被覆盖。因此,学习是:在 UI 编辑器中打开 Excel 文件,进行更改,然后关闭文件,再在 Excel 中打开。

 

<customUI xmlns="http://schemas.microsoft.com/office/2009/07/customui">
 <ribbon>
  <tabs>
   <tab id="customTab" label="JIRA" insertAfterMso="TabView">
    <group idMso="GroupClipboard" />
    <group idMso="GroupFont" />
    <group id="customGroup" label="Jira Tools">
     <button id="customButton5" label="Login" size="large" onAction="Login" imageMso="TableIndexes" /> 
     <button id="customButton4" label="Get Issues" size="large" onAction="GetTickets" imageMso="CacheListData" />    
     <button id="customButton1" label="Get Linked Status" size="large" onAction="UpdateStatus" imageMso="AccessRelinkLists" />
     <button id="customButton3" label="Create Tickets" size="large" onAction="CreateTickets" imageMso="QueryShowTable" />
     <button id="customButton2" label="Update Dashboard" size="large" onAction="UpdateDashboard" imageMso="CreateForm"/>    
     <button id="customButton7" label="Create Enhancement" size="large" onAction="CreateEForm" imageMso="ColumnsDialog"/>
    </group>
   
   </tab>
    <group idMso="GroupEnterDataAlignment" />
    <group idMso="GroupEnterDataNumber" />
    <group idMso="GroupQuickFormatting" />
  </tabs>
 </ribbon>
</customUI>

 

 

随机思考

起初,我使用 Excel 左下角的 Excel 状态栏并将 Excel 光标更新为 xlWait,但后来意识到状态栏有点偏僻,不那么引人注目。所以我弹出一个带有状态标签的对话框,向用户提供进度反馈。这似乎更人性化,信息量也更大。我还决定不更改光标,因为对话框会阻止用户执行任何操作,直到宏完成并提供所需的视觉反馈。我发现,当您尝试手动更新光标时,几乎总会出现不可预见的情况,导致光标处于不准确的状态。

在字符串前后与 vbCrLf 和逗号进行了大量的尝试,才使“链接状态”和“链接已修复版本”在单元格内与关联的链接工单对齐。可能有一种更好的处理方法,但目前这种字符串操作的暴力方法奏效了。

 

历史

2016年4月13日:编辑文章,额外致谢 http://word.mvps.org/faqs/macrosvba/MixedDocProps.htm

更新了附件 Excel 工作簿,包含了 GetIssues 中的修复,将 .send "" 仅改为 .send,如 Dirk_B 所报告

 

© . All rights reserved.