Se você curte o Jira, não deixe de conferir meu artigo sobre gestão de backlog usando Jira!
No último artigo eu ensinei como criar diversos tipos de automações em Jira. Venho trabalhando com esta ferramenta desde 2018 e ela é bem poderosa, mais até do que a que usei por mais de 10 anos, o famoso TFS da Microsoft.
Entre as inúmeras funcionalidades, o Jira permite criar diversos campos customizados de vários tipos, mas, além disso, campos que carregam o seu conteúdo dinamicamente a partir de scripts Groovy, graças ao plugin Adaptavist ScriptRunner. Vou mostrar aqui como criar um campo dinâmico.
Este tutorial foi feito usando o Jira na versão server/on-premise (v7.10), mas possivelmente deve funcionar em versões posteriores e talvez até na cloud, desde que tenha o plugin/add-on do Script Runner. Você precisa de permissões de administrador do Jira para conseguir fazer o que vou mostrar.
Atenção: Este artigo não tem a pretensão de ensinar a administrar Jira, mas apenas a dar um upgrade nas suas skills se já for um administrador de Jira Server. Para quem quer aprender a administrar Jira, recomendo meu curso online de Jira.
Custom Fields
Por padrão o Jira permite que você crie Custom Fields ou Campos Personalizados. Nada mais são do que inputs comuns, usando os tipos pré-existentes do Jira, mas como rótulos/labels personalizados e que adicionam mais informações às screens já existentes ou a novas screens.
Caso isso seja novidade para você, o passo-a-passo abaixo mostra como criar um custom field simples. Me perdoe as imagens em português, foi uma imagem que tirou os prints e ela usa o Jira em pt-br (eu prefiro usar em inglês pois facilita a procura por informações na Internet).
- Acesse o painel de administração;
- Seção Issues;
- Sub-seção Fields;
- Opção Custom Fields/Add Custom Field
Na tela que se abrirá, preencha de acordo com o campo que precisa (campo de texto, de seleção, etc). Cada tipo de campo possui as suas particuliaridades, como a Select List, que exige que tu preencha manualmente todas as opções do drop-down.
A seguir, depois de clicar em Create, você vai ser solicitado que selecione em quais telas este campo vai aparecer.
E pronto. Caso ele ainda não apareça nas telas que você selecionou, dê uma olhada na Screen para ver se não tem de torná-lo visível pois às vezes ele está na tela, mas oculto/hidden.
Mas e para criar custom fields baseados em scripts, como faz?
Script Fields
Você pode criar campos dinâmicos que carregam o seu conteúdo a partir de scripts, o que chamamos de Script Field. Esse recurso é muito poderoso para, por exemplo:
- exibir informações de linked issues dentro das issues principais (e vice-versa);
- exibir informações resultantes de cálculos ou transformações de outros dados da issue;
Uma vez com o campo criado, é possível adicioná-lo em qualquer screen ou até mesmo usá-lo em queries e reports.
Para criar um Script Field você deve:
- Entre em Administration;
- Escolha a opção Issues;
- Clique em Custom Fields, abaixo da seção Fields;
- Clique em Add Custom Field;
- Em Select Field Type, encontre Scripted Field dentro de Advanced (o ícone é o mesmo do Script Runner);
- Next;
- Dê um nome e uma descrição ao campo (este último é opcional) e salve;
- Selecione todas as telas em que este campo deve aparecer e clique em Update;
- Retorna a Administration, Add-ons, Script Runner e Fields;
- Encontre o seu Scripted Field criado e depois clique em Edit;
Preencha o formulário como segue:
- Field Name: não editável;
- Field Description: não editável;
- Note: uma nota opcional;
- Template: como esse campo será renderizado na screen;
- Script: o código Groovy que vai retornar o conteúdo desse campo. É aqui que a magia acontece e esse código sempre recebe um objeto issue como argumento, com as propriedades da issue atualmente visível na screen onde esse script field está.
- Preview Issue Key: coloque o id de uma issue aqui e clique no botão preview para ver como seu Script Field vais e parecer na screen.
Clique em Update para finalizar.
Caso seu script field não aparece na screen que deveria, reveja as configurações de screen do custom field e/ou verifique na screen se o campo está aparecendo no editor visual dela.
Um exemplo de código Groovy para script field é mostrado abaixo. Nele, nós ascendemos na hierarquia da issue atual até encontrar qual o Projeto (um issue type custom) ela pertence, imprimindo essa informação então no custom script field:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
import com.atlassian.jira.component.ComponentAccessor; import com.atlassian.jira.issue.Issue; import com.atlassian.jira.issue.link.IssueLink; /* determine if this is a projeto, if so, return the name */ if (issue.issueTypeObject.name == "Projeto"){ return issue.summary } def issueReference = issue def issueManager = ComponentAccessor.getIssueManager() if(issue.issueTypeObject.name == "Sub-tarefa" || issue.issueTypeObject.name == "Bug (sub)"){ issueReference = issue.getParentObject() } if (issueReference.issueTypeObject.name != "Epic"){ def customFieldManager = ComponentAccessor.getCustomFieldManager() def epiclinkfield = customFieldManager.getCustomFieldObjectByName("Epic Link") def epicValue = issueReference.getCustomFieldValue(epiclinkfield) //se não tem epic link, deixa quieto if(epicValue == null) return "nenhum" issueReference = issueManager.getIssueObject(epicValue.toString()) } Collection<Issue> allOutIssueLink = ComponentAccessor.getIssueLinkManager().getLinkCollectionOverrideSecurity(issueReference).getAllIssues() for (Issue linkedIssue : allOutIssueLink) { if(linkedIssue.issueTypeObject.name == "Projeto") return linkedIssue.summary; } return "nenhum" |
No exemplo abaixo, de outro script field, dado um issue do tipo Mudança, a gente busca o item originador desta mudança (outra issue) e se tiver mais de um, pega apenas o primeiro para exibir no custom script field:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import com.atlassian.jira.component.ComponentAccessor; import com.atlassian.jira.issue.Issue; import com.atlassian.jira.issue.link.IssueLink; if (issue.issueTypeObject.name != "Mudança"){ return ""; } def issueManager = ComponentAccessor.getIssueManager() def customFieldManager = ComponentAccessor.getCustomFieldManager() def itemOriginadorField = customFieldManager.getCustomFieldObjectByName("Item(s) originador(es) da mudança") def itemOriginadorValue = issue.getCustomFieldValue(itemOriginadorField) if(itemOriginadorValue == null) return "Item originador não preenchido"; def key = itemOriginadorValue.toString().split(",")[0].replace("[","").replace("]",""); def originalIssue = issueManager.getIssueByCurrentKey(key) if(originalIssue == null) return "Item originador não existente"; return originalIssue.issueTypeObject.name; |
Neste aqui, bem simples, tenho um Custom Field do tipo Issue Picker e quero apresentar o summary da issue escolhida em um Script Field, para fins de relatório (por padrão, em relatórios, aparece o id da issue).
O primeiro if considera que o custom field aparece em apenas alguns issue types, esse teste evita erros. Se for aparecer em todos, retire o if.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import com.atlassian.jira.component.ComponentAccessor; import com.atlassian.jira.issue.Issue; import com.atlassian.jira.issue.link.IssueLink; def issueReference = issue def issueManager = ComponentAccessor.getIssueManager() if (issueReference.issueTypeObject.name != ""){//aqui você coloca o tipo de issue que vai ter esse issue picker novo def customFieldManager = ComponentAccessor.getCustomFieldManager() def issuePicked = customFieldManager.getCustomFieldObjectByName("")//aqui vai o nome do custom field/issue picker def issueId = issueReference.getCustomFieldValue(issuePicked) //se não tem issue, deixa quieto if(issueId == null) return "nenhum" issueReference = issueManager.getIssueObject(issueId.toString()) return issueReference.summary; } return "nenhum" |
E nesse abaixo, mais simples de todos, temos um custom script field que apenas exibe o nome do épico associado a esta issue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
import com.atlassian.jira.component.ComponentAccessor; import com.atlassian.jira.issue.CustomFieldManager; import com.atlassian.jira.issue.fields.CustomField; import com.atlassian.jira.issue.Issue; CustomFieldManager customFieldManager = ComponentAccessor.getCustomFieldManager(); CustomField epicLink = customFieldManager.getCustomFieldObjectByName('Epic Link'); CustomField epicName = customFieldManager.getCustomFieldObjectByName('Epic Name'); //This is where we store the issue that is linked to the epic. Issue issue_linked_to_epic /* determine if this is an epic, if so, return the epic link name */ if (issue.issueTypeObject.name == "Epic"){ return issue.getCustomFieldValue(epicName) } /*determine if this is a subTaskIssueTypes(), if so, set the parent to the issue linked to the epic */ Issue issueParent = issue.getParentObject() if(issueParent != null && issueParent.issueTypeObject.name == "Epic"){ return issueParent.getCustomFieldValue(epicName) } else if (issueParent == null){ issue_linked_to_epic = issue } else { issue_linked_to_epic = issueParent } /* get epic link */ Issue epic = (Issue)issue_linked_to_epic.getCustomFieldValue(epicLink) if (epic == null){ return null } String epicNameString = epic.getCustomFieldValue(epicName) return epicNameString |
E por fim, adicionando mais um exemplo, abaixo exibo um script que criei que realiza uma consulta JQL para trazer os filhos de um Épico e em cima de cada um deles, faz o somatório de um custom field de Pontos de Função.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
import com.atlassian.jira.bc.issue.search.SearchService import com.atlassian.jira.component.ComponentAccessor import com.atlassian.jira.issue.search.SearchException import com.atlassian.jira.web.bean.PagerFilter // The JQL query you want to search with final jqlSearch = "'Epic Link' = " + issue.key // Some components def user = ComponentAccessor.jiraAuthenticationContext.loggedInUser def searchService = ComponentAccessor.getComponentOfType(SearchService) def issueManager = ComponentAccessor.getIssueManager() def customFieldManager = ComponentAccessor.getCustomFieldManager() // Parse the query def parseResult = searchService.parseQuery(user, jqlSearch) if (!parseResult.valid) { log.error('Invalid query') return null } try { // Perform the query to get the issues def results = searchService.search(user, parseResult.query, PagerFilter.unlimitedFilter) def issues = results.results def total = 0; issues.each { def childIssue = issueManager.getIssueObject(it.key) def cField = customFieldManager.getCustomFieldObjectByName("Pontos de Função Detalhado") if(cField != null) { def valor = (Double)childIssue.getCustomFieldValue(cField) total = total + valor } } return total } catch (SearchException e) { e.printStackTrace() null } |
Espero ter ajudado!
Caso se interesse por métodos ágeis em geral, dê uma conferida no meu curso online de Scrum e Métodos Ágeis clicando no banner abaixo.
Olá, tudo bem?
O que você achou deste conteúdo? Conte nos comentários.