Personalizando o Jira de acordo com suas necessidades. Fluxo perfeito e bilhete perfeito



Se você trabalha em uma empresa de TI, provavelmente seus processos são construídos em torno do conhecido produto Atlassian - Jira. Existem muitos rastreadores de tarefas no mercado para solucionar os mesmos problemas, incluindo soluções de código aberto (Trac, Redmine, Bugzilla), mas talvez o Jira seja o mais amplamente usado atualmente.

Meu nome é Dmitry Semenikhin, sou líder de equipe no Badoo. Em uma curta série de artigos, mostrarei exatamente como usamos o Jira, como o personalizamos para nossos processos, quais coisas boas foram "estragadas" e como, portanto, transformamos o rastreador de problemas em um único centro de comunicação para a tarefa e simplificamos nossa vida. Neste artigo, você verá nosso fluxo interno, aprenderá como pode "torcer" seu Jira e ler sobre os recursos adicionais da ferramenta que você talvez não conheça.

O artigo é voltado principalmente para aqueles que já usam o Jira, mas pode ter dificuldade em integrar seus recursos padrão aos processos existentes na empresa. Além disso, o artigo pode ser útil para empresas que usam outros rastreadores de tarefas, mas encontraram algumas limitações e estão considerando uma mudança de decisão. O artigo não se baseia no princípio de “problema - solução”; descrevo as ferramentas e os recursos existentes que construímos em torno do Jira, bem como as tecnologias que usamos para implementá-los.

Recursos adicionais do Jira


Para tornar o texto a seguir mais compreensível, vamos descobrir quais ferramentas o Jira nos fornece para a implementação da lista de desejos não padrão - aquelas que vão além da funcionalidade padrão do Jira.

API REST


Em geral, uma chamada de comando da API é uma solicitação HTTP para uma URL da API, indicando o método (GET, PUT, POST e DELETE), comando e corpo da solicitação. O corpo da solicitação, bem como a resposta da API, está no formato JSON. Um exemplo de solicitação que retorna uma representação JSON de um ticket:

GET /rest/api/latest/issue/{ticket_number} 

Usando a API, você pode, usando scripts em qualquer linguagem de programação:

  • criar tickets;
  • modificar quaisquer propriedades de tickets (embutidos e personalizados);
  • escreva comentários;
  • usando JQL (linguagem de consulta interna) para receber qualquer lista de tickets;
  • e muito mais

A documentação detalhada da API está disponível aqui .

Escrevemos nosso próprio cliente Jira API de alto nível em PHP, que implementa todos os comandos que precisamos. Aqui está um exemplo de comandos para trabalhar com comentários:

 public function addComment($issue_key, $comment) {  return $this->_post("issue/{$issue_key}/comment", ['body' => $comment]); } public function updateComment($issue_key, $comment_id, $new_text) {  return $this->_put("issue/{$issue_key}/comment/{$comment_id}", ['body' => $new_text]); } public function deleteComment($issue_key, $comment_id) {  return $this->_delete("issue/{$issue_key}/comment/{$comment_id}"); } 

Webhooks


Usando o webhook, você pode configurar a chamada de uma função de retorno de chamada externa em seu host para vários eventos no Jira. Ao mesmo tempo, você pode configurar quantas regras desejar, para que URLs diferentes sejam "contorcidos" para eventos diferentes e para tickets que correspondam ao filtro especificado no webhook. A interface de configuração dos webhooks está disponível para o administrador do Jira.

Como resultado, você pode criar regras como esta:

Nome : "SRV - Novo recurso criado / atualizado"
URL : www.myremoteapp.com/webhookreceiver
Escopo : Projeto = SRV E digite ('Novo Recurso')
Eventos : problema atualizado, problema criado

Neste exemplo, o URL especificado será chamado para criação de ticket e eventos de alteração correspondentes ao filtro Escopo . Ao mesmo tempo, o corpo da solicitação conterá todas as informações necessárias sobre o que exatamente mudou e qual evento ocorreu.

É importante entender que Jira não garante que seu evento será entregue. Se o URL externo não respondeu ou respondeu com um erro, isso não estará visível em nenhum lugar (exceto nos registros, talvez). Portanto, o manipulador de eventos do webhook deve ser o mais confiável possível. Por exemplo, você pode enfileirar eventos e tentar processá-los até que seja bem-sucedido. Isso ajudará a resolver problemas com serviços temporariamente indisponíveis, por exemplo, qualquer banco de dados externo necessário para o processamento correto do evento.

A documentação detalhada sobre webhooks está disponível aqui .

Scriptrunner


Este é um plug-in para o Jira, uma ferramenta muito poderosa que permite personalizar grande parte do Jira (incluindo a capacidade de substituir webhooks). O uso deste plugin requer conhecimento do Groovy. A principal vantagem da ferramenta para nós é que você pode incorporar lógica personalizada no fluxo online. Seu código de script será executado imediatamente no ambiente Jira em resposta a uma ação específica. Por exemplo, você pode criar seu próprio botão na interface do ticket, clicando nele para criar tickets associados à tarefa atual ou executar testes de unidade para esta tarefa. E se de repente algo der errado, você como usuário saberá imediatamente.

Os interessados ​​podem ler a documentação .

Fluxo: o que está escondido sob o capô


E agora, sobre como aplicamos recursos adicionais do Jira em nossos projetos. Considere isso no contexto de analisar nosso ticket de fluxo típico, da criação ao fechamento. Ao mesmo tempo, vou falar sobre o fluxo em si.

Abrir / pendência


Portanto, primeiro o ticket entra no backlog de novos tickets com o status Open . Além disso, o líder do componente, depois de ver um novo ticket em seu painel, toma uma decisão: atribuir um ticket agora ao desenvolvedor ou enviá-lo para o backlog de tickets conhecidos (status do Backlog ) para atribuí-lo mais tarde, quando um desenvolvedor gratuito aparecer e os tickets com prioridade mais alta serão fechados. Isso pode parecer estranho, pois parece lógico fazer o oposto: crie tickets no status Backlog e depois traduza-os no status Open. Mas criamos raízes exatamente nesse esquema. Permite configurar filtros facilmente para reduzir o tempo de decisão para novos tickets. Um exemplo de um filtro JQL que mostra novas tarefas para um lead:

Project = SRV AND assignee is EMPTY AND status in (Open)

Em andamento


Nuances técnicas do trabalho com o Git
Note-se que trabalhamos em cada tarefa em um ramo Git separado. Por isso, temos um acordo de que o nome da agência no início deve conter o número do ticket. Por exemplo, SRV-123_new_super_feature . Além disso, os comentários para cada confirmação na ramificação devem conter o número do ticket no formato [SRV-123]: {comment}. Precisamos desse formato, por exemplo, para a remoção correta de uma tarefa "ruim" de uma compilação. Como isso é feito é descrito em detalhes no artigo .

Esses requisitos são controlados pelos ganchos Git. Por exemplo, aqui está o conteúdo de prepare-commit-msg, que prepara um comentário para o commit, obtendo o número do ticket do nome da ramificação atual:

 #!/bin/bash b=`git symbolic-ref HEAD| sed -e 's|^refs/heads/||' | sed -e 's|_.*||'` c=`cat $1` if [ -n "$b" ] && [[ "$c" != "[$b]:"* ]] then echo "[$b]: $c" > $1 fi 

Se você tentar enviar por push um commit com um comentário "incorreto", esse push será rejeitado. Uma tentativa de iniciar uma filial sem um número de ticket no início também será rejeitada.

Quando um ticket atinge o desenvolvedor, a primeira coisa que ele decompõe. O resultado da decomposição é a ideia do desenvolvedor de como resolver o problema e quanto tempo a solução levará. Após todos os detalhes principais terem sido esclarecidos, o ticket é transferido para o status Em andamento e o desenvolvedor começa a escrever o código.

É habitual definir a data de vencimento para a tarefa no momento em que ela é transferida para o status Em andamento. Se o desenvolvedor não fez isso, ele receberá um lembrete no messenger corporativo do HipChat. Um script especial a cada duas horas:

  • o uso da API REST do Jira seleciona tickets em status em andamento com um campo de data de vencimento vazio ( projeto = SRV AND status = 'Em andamento' E a data de vencimento é VAZIA );
  • seleciona tickets incompletos com data de vencimento anterior à data atual ( projeto = SRV AND status = 'Em andamento' E o débito não é VAZIO E o débito <now () );
  • reconhece o desenvolvedor de cada ticket lendo o campo correspondente no ticket, bem como o lead do desenvolvedor;
  • agrupa tickets de desenvolvedores e leads e envia lembretes ao HipChat usando sua API.

Tendo feito todos os commits necessários, o desenvolvedor empurra o ramo para um nabo comum. Nesse caso, o gancho Git pós-recebimento é acionado, o que faz muitas coisas interessantes:

  • O nome da ramificação Git, bem como os comentários sobre confirmações, são verificados quanto à conformidade com nossas regras;
  • verifica-se que o ticket ao qual a filial está associada não está fechado (você não pode inserir novo código nos tickets fechados);
  • A sintaxe dos arquivos PHP modificados é verificada (PHP -l nome_do_arquivo.php );
  • a formatação é verificada;
  • se o ticket no qual a ramificação for enviada for no status Aberto , ele será automaticamente transferido para o status Em andamento ;
  • o ticket é anexado à ramificação, a entrada correspondente é feita no campo personalizado do ticket Commits usando a API do Jira. É assim:


( branchdiff é um link para o diff da ramificação com a cabeça da qual a ramificação atual se originou em nossa ferramenta de revisão de código Codeisok );

  • um comentário é criado no ticket com todas as confirmações neste envio.

    (Aida é o nome condicional do nosso complexo de automação para trabalhar com Jira, Git e não apenas. É a partir desse nome que comentários automáticos aparecem no ticket. Escrevemos mais sobre Aida no artigo ).
    Um clique no hash da confirmação é diferente da revisão anterior da ramificação (mostrarei abaixo como ela se parece);
  • verifica se há arquivos na ramificação que podem exigir tradução para os idiomas suportados (por exemplo, modelos de página da web) e, se houver, o novo valor \ Changed é definido como o campo personalizado do ticket Lexems. Isso garante que o ticket não seja produzido sem uma tradução completa;
  • o nome do funcionário que envia a filial é adicionado à lista de desenvolvedores (um campo personalizado do ticket Developers )

Em revisão


Depois de escrever o código e garantir que todos os requisitos para a tarefa sejam atendidos e os testes não sejam interrompidos, o desenvolvedor atribui um ticket ao revisor (status On Review ). Normalmente, o desenvolvedor decide quem analisará seu ticket. Provavelmente, será outro desenvolvedor que é bem versado na parte certa do código. A revisão ocorre usando a ferramenta Codeisok , que é aberta imediatamente com o diff desejado, clicando no link branchdiff no campo Ticket de Confirmações ou no comentário como um hash de confirmação nos comentários.

O revisor vê algo assim:


Após concluir a revisão, o revisor pressiona o botão Concluir e, entre outras coisas, neste momento acontece o seguinte:

  • usando a API JIra, um comentário é criado no ticket com comentários do revisor no contexto do código. Parece algo como isto:


  • se houver algum comentário sobre o código e o revisor decidir reabrir o ticket, o desenvolvedor receberá uma notificação sobre isso no HipChat (isso é feito usando a regra de webhook, que funciona na reabertura);
  • O campo do ticket Revisores é preenchido.

Resolvido


Além disso, se a revisão foi bem-sucedida, o ticket é enviado para a lista de pendências de engenheiros de controle de qualidade no status Resolvido . Mas, ao mesmo tempo, usando o webhook para o evento resolvido, testes automáticos no código da ramificação são iniciados em segundo plano. Após alguns minutos, um novo comentário aparecerá no ticket, informando os resultados do teste.



Além disso, a qualquer momento, você pode iniciar manualmente uma execução de teste repetida clicando no botão especial Executar testes de unidade no menu do ticket. Após uma execução bem-sucedida, um novo comentário aparecerá no ticket, semelhante ao anterior.


De fato, esse botão é um dos status de tarefas adicionais no fluxo de trabalho do Jira, uma tradução na qual aciona um script Groovy para o plug-in ScriptRunner. O script chama uma URL externa, que inicia a execução do teste e, se a URL responder com êxito, o ticket retornará ao seu status anterior (no nosso caso, Resolvido ).

Em Tiro / Em Tiro - OK


A tarefa é testada primeiro em um ambiente de desenvolvimento. Se tudo estiver bem, uma captura será criada (por exemplo, clicando no link Criar captura no campo Commits ) - o diretório no servidor dedicado para o qual são copiadas as alterações do ticket adjacentes ao mestre atual. O servidor trabalha com dados de produção: os bancos de dados e serviços são os mesmos que atendem usuários reais. Assim, o testador pode abrir um site ou conectar-se à foto usando um cliente móvel e "isolar" o recurso no ambiente de produção. "Isolado" significa que nenhum outro código / funcionalidade, exceto o novo da ramificação e o mestre atual, é executado. Portanto, esse estágio de teste é talvez o principal, pois permite que o engenheiro de controle de qualidade encontre o problema com mais confiabilidade diretamente no problema de teste.

Os recursos de captura são acessados ​​usando URLs especiais que são gerados no script de criação de captura e são colocados no cabeçalho do ticket usando a API Jira. Como resultado, vemos links para o site, painel de administração, logs e outras ferramentas executadas em um ambiente de captura:



Além disso, no momento da geração da captura, é lançado um script que analisa o conteúdo dos arquivos alterados e cria solicitações para a tradução dos novos tokens encontrados. Após a conclusão da tradução, o valor do campo Lexems é alterado para Concluído e o ticket pode ser adicionado à compilação.

Se o teste no tiro foi bem-sucedido, o ticket é transferido para o status No Tiro - OK.

Na Construção / Na Construção - OK


Carregamos o código duas vezes por dia - de manhã e à noite. Para fazer isso, é criado um ramo de construção especial, que acabará por ser mesclado com o mestre e apresentado "em batalha".

No momento da construção da ramificação de construção, um script especial usando uma consulta JQL recebe uma lista de tickets no status In Shot - OK e tenta congelá-los na ramificação de construção quando todas as seguintes condições forem atendidas:

  • a tradução do ticket está concluída ou nada precisa ser traduzido ( Lexems em ('Não', 'Concluído') );
  • o desenvolvedor está presente no local de trabalho (o sistema de fusão automática verifica na base interna se o desenvolvedor está de férias ou em licença médica e, nesse caso, o ticket pode ser congelado manualmente apenas por engenheiros de liberação ou outro desenvolvedor responsável, indicado no campo especial Vice Developer ; o líder do desenvolvedor ausente, nesse caso, recebe uma notificação de que o ticket não pode ser adicionado automaticamente à compilação);
  • o ticket não possui o sinalizador Up in Build configurado como Developer (este é um campo personalizado especial do ticket que permite ao desenvolvedor determinar quando o ticket irá para a build);
  • a ramificação do ticket não depende de outra ramificação que ainda não atingiu o mestre ou a compilação atual. Fazemos o possível para evitar essa situação, mas às vezes isso acontece quando o desenvolvedor cria sua própria ramificação, não a partir do mestre, mas de uma ramificação de outro ticket ou quando ele congela outra ramificação para si mesmo. Isso também pode ser feito por acaso, por isso decidimos que a proteção adicional não faria mal.

Vale ressaltar que a mesclagem automática pode não ocorrer devido a um conflito de mesclagem. Nesse caso, o ticket é automaticamente transferido para o status Reabrir e atribuído ao desenvolvedor, sobre o qual ele recebe imediatamente uma notificação no HipChat, e uma mensagem correspondente é adicionada ao comentário do ticket. Depois de resolver o conflito, o ticket retorna para a compilação.

Se tudo estiver bem e a ramificação do ticket estiver congelada na compilação, o ticket será automaticamente transferido para o status In Build e o nome da compilação será gravado no campo personalizado do ticket Build_Name .


Além disso, usando esse valor, é fácil obter uma lista de tickets que foram postados com cada build. Por exemplo, procurar alguém para culpar se algo der errado.

No próximo estágio, os engenheiros de controle de qualidade também verificam se o código da tarefa funciona corretamente em conjunto com outras tarefas na compilação. Se tudo estiver bem, o ticket será definido manualmente como In Build - OK.

Em produção / Em produção - OK / Fechado


Além disso, na compilação, todo o nosso conjunto de testes é executado (unidade, integração, selênio, etc.). Se tudo estiver bem, a construção é congelada no mestre e o código é definido para produção. O ticket é transferido para o status Em produção.

Além disso, o desenvolvedor (ou o cliente) garante que o recurso funcione corretamente na produção e define o status do ticket Na produção - OK.

Após duas semanas, os tickets no status Em produção - OK são transferidos automaticamente para o status Fechado , se alguém não tiver feito isso manualmente antes.

Também vale mencionar status adicionais em que o ticket pode estar localizado:

  • Requisitos - quando não é possível obter rapidamente do cliente os esclarecimentos necessários sobre a tarefa, e sem eles, o trabalho adicional no ticket é impossível, o ticket é transferido para esse status e atribuído àquele que precisa dar explicações;
  • Suspenso - se o trabalho do ticket for suspenso, por exemplo, se o desenvolvedor for bloqueado por tarefas de uma equipe adjacente ou forçado a mudar para uma tarefa mais urgente;
  • Reaberto - uma tarefa pode ser redescoberta para o desenvolvedor após uma revisão, após o teste, após uma tentativa malsucedida de mesclar uma ramificação com o mestre.

Como resultado, um diagrama simplificado do nosso fluxo de trabalho se parece com o seguinte:


Ticket - centro de comunicação para a tarefa


Como resultado da passagem do ticket pelo fluxo, seu cabeçalho adquire aproximadamente o seguinte formato:



O que mais é interessante aqui que personalizamos para nós mesmos e que ainda não mencionei?

  • Componente - usado para agrupar um ticket em um departamento grande. Diferentes subgrupos são responsáveis ​​por diferentes componentes e, consequentemente, em seus painéis, eles veem apenas tarefas para seus componentes. Por exemplo, posso listar todos os erros abertos para os componentes da minha equipe com esta consulta:

     Project = SRV AND type = Bug AND status = Open AND component in componentsLeadByUser(d.semenihin) 

  • Revisão - se a revisão de código é necessária. O padrão é necessário. Se o valor do campo estiver definido como Não, o ticket receberá imediatamente o status Resolvido.

    QA - O testador precisa de verificação. O padrão é necessário. Se o valor do campo estiver definido como Não, o ticket passará imediatamente para o status In Shot - OK.

    Sprint - no nosso caso, é relevante apenas para tarefas com o tipo de novo recurso, um plano para o qual elaboramos antecipadamente por uma semana.
  • Data de vencimento - o desenvolvedor determina a data em que o ticket estará em produção. Exibido antes de iniciar o trabalho na tarefa.
  • Situação - de fato, um log curto com uma breve descrição do status atual da tarefa. Por exemplo, "20/08 estou aguardando traduções" , "21/08 é necessário esclarecer o cliente sobre o problema X" . Isso ajuda a ver um breve resumo da tarefa na lista de outras tarefas.
  • Msg4QA - informações para engenheiros de controle de qualidade, compartilhadas pelo desenvolvedor para simplificar o processo de teste

Tentamos conduzir uma discussão de questões controversas com o diretor da tarefa nos comentários do ticket, e não "borrar" esclarecimentos importantes por correio e mensagens instantâneas. Se, no entanto, a discussão ocorreu "ao lado", é altamente desejável copiar o que você concordou no ticket.

Além dos textos "humanos", como mencionei acima, muitas coisas são escritas automaticamente em um comentário usando a API:

  • compromete
  • revisar resultados;
  • resultados da execução de teste.

Às vezes, comentários automáticos podem interferir, por exemplo, nos gerentes de produto. Portanto, criamos um script JS simples que adiciona um botão à interface do Jira e permite minimizar todos os comentários automáticos, deixando apenas os humanos. Como resultado, comentários automáticos minimizados parecem compactos.



Código JS do script que incorporamos no modelo de ticket
 window.addEventListener('load', () => {   const $ = window.jQuery;   const botsAttrMatch = [       'aida',       'itops.api'   ].map(bot => `[rel="${bot}"]`).join(',');   if (!$) {       return;   }   const AIDA_COLLAPSE_KEY = 'aida-collapsed';   const COMMENT_SELECTOR = '.issue-data-block.activity-comment.twixi-block';   const JiraImprovements = {       init() {           this.addButtons();           this.handleAidaCollapsing();           this.handleCommentExpansion();           // Handle toggle button and aida collapsing and put it on a loop           // to handle unexpected JIRA behaviour           const self = this;           setInterval(function () {               self.addButtons();               self.handleAidaCollapsing();           }, 2000);           addCss(`               #badoo-toggle-bots {                   background: #fff2c9;                   color: #594300;                   border-radius: 0 3px 0 0;                   margin-top: 3px;                   display: inline-block;               }           `);       },       addButtons() {           // Do we already have the button?           if ($('#badoo-toggle-bots').length > 0) {               return;           }           // const headerOps = $('ul#opsbar-opsbar-operations');           const jiraHeader = $('#issue-tabs');           // Only add it in ticket state           if (jiraHeader.length > 0) {               const li = $('<a id="badoo-toggle-bots" class="aui-button aui-button-primary aui-style" href="/">Collapse Bots</a>');               li.on('click', this.toggleAidaCollapsing.bind(this));               jiraHeader.append(li);           }       },       toggleAidaCollapsing(e) {           e.preventDefault();           const isCollapsed = localStorage.getItem(AIDA_COLLAPSE_KEY) === 'true';           localStorage.setItem(AIDA_COLLAPSE_KEY, !isCollapsed);           this.handleAidaCollapsing();       },       handleAidaCollapsing() {           const isCollapsed = localStorage.getItem(AIDA_COLLAPSE_KEY) === 'true';           const aidaComments = $(COMMENT_SELECTOR).has(botsAttrMatch).not('.manual-toggle');           if (isCollapsed) {               aidaComments.removeClass('expanded').addClass('collapsed');               $('#badoo-toggle-bots').text('Show Bots');           }           else {               aidaComments.removeClass('collapsed').addClass('expanded');               $('#badoo-toggle-bots').text('Collapse Bots');           }       },       handleCommentExpansion() {           $(document.body).delegate('a.collapsed-comments', 'click', function () {               const self = this; // eslint-disable-line no-invalid-this               let triesLeft = 100;               const interval = setInterval(() => {                   if (--triesLeft < 0 || self.offsetHeight === 0) {                       clearInterval(interval);                   }                   // Element has been removed from DOM. ie new jira comments have been added                   if (self.offsetHeight === 0) {                       JiraImprovements.handleAidaCollapsing();                   }               }, 100);           });           $(document.body).delegate(COMMENT_SELECTOR, 'click', function () {               $(this).addClass('manual-toggle');// eslint-disable-line no-invalid-this           });       }   };   JiraImprovements.init();   function addCss(cssText) {       const style = document.createElement('style');       style.type = 'text/css';       if (style.styleSheet) {           style.styleSheet.cssText = cssText;       }       else {           style.appendChild(document.createTextNode(cssText));       }       document.head.appendChild(style);   } }); 


O que mais?


API webhooks Jira :

  • HipChat, - ( );
  • HipChat ( , );
  • ( ) ( ; );
  • ; , ;
  • In progress ;
  • , «» (, On Review), ;
  • , Jira (, «d.semenihin (Day off)»). .

Sumário


Jira — , , . , , . Jira , .

— . Jira , Jira. , - .

Obrigado pela atenção!

Source: https://habr.com/ru/post/pt424655/


All Articles