Se você digitar “Jira Badoo” na string de pesquisa em Habré, os resultados levarão mais de uma página: nós a mencionamos em quase todos os lugares, porque ela desempenha um papel importante em nossos processos. E cada um de nós quer um pouco diferente dela.

O desenvolvedor, que recebeu a tarefa para a revisão, espera que uma ramificação seja indicada na tarefa, há links para diff e o log de alterações. O desenvolvedor que escreveu o código espera ver comentários em Jira após a revisão. O testador, que recebe a tarefa após eles, deseja ver os resultados do teste e poder executar os assemblies necessários sem precisar acessar outras interfaces. Os gerentes de produto geralmente desejam criar dez tarefas de desenvolvimento ao mesmo tempo pressionando um único botão.
E tudo isso está disponível hoje e acontece automaticamente. Implementamos a maior parte da mágica em PHP usando a API Jira em constante evolução e usando seu
webhook . E hoje queremos compartilhar com a comunidade nossa versão do cliente para esta API.
Inicialmente, queríamos apenas falar sobre as idéias e abordagens que usamos e, em seguida, decidimos que definitivamente não havia código suficiente para um artigo como esse para maior clareza. Portanto, havia uma versão de código aberto do
Badoo Jira PHP Client . Muito obrigado a
ShaggyRatte por ajudar com sua descrição. E bem-vindo ao Kat!
Mais detalhes e contexto. O que ele pode fazer?
De fato, o Badoo Jira PHP Client é um conjunto de classes de wrapper prontas para respostas da API Jira, a maioria das quais pode se comportar como ActiveRecord: eles sabem como obter dados sobre si mesmos e atualizá-los no servidor, suportam inicialização lenta e cache de dados no nível código. Todas as entidades do Jira com as quais você precisa trabalhar constantemente são agrupadas:
problema, status, prioridade, registro de alterações, usuário, versão, componente etc. (quase tudo que você vê na interface).
Além disso, o Badoo Jira PHP Client fornece uma hierarquia de classe única para todos os campos personalizados do Jira e é capaz de gerar definições de classe para cada campo personalizado necessário.
$Issue = new \Badoo\Jira\Issue('SMPL-1'); $Issue->addComment('Sample comment text'); $Issue->attachFile('kitten.jpeg', 'pretty!', 'image/jpeg'); if ($Issue->getStatus()->getName() === 'Open') { $Issue->step('In Progress'); }
$DeveloperField = new \Example\CustomFields\Developer($Issue); $DeveloperField->setValue('username')->save();
$User = \Badoo\Jira\User::get('username'); $User->watchIssue('SMPL-1'); $User->assign('SMPL-2');
Graças a isso, a interação com a API do PHP se torna mais simples e conveniente, e a documentação do seu Jira se move diretamente para o código, o que permite usar o preenchimento automático no IDE para muitas ações padrão.
Mas as primeiras coisas primeiro.
Recursos de API que encontramos
Quando começamos a usar ativamente a API do Jira, ela estava disponível apenas através do protocolo SOAP. Sua versão REST apareceu mais tarde e estávamos entre os primeiros usuários. Naquela época, havia muito poucos clientes REST disponíveis publicamente escritos em PHP. Foi ainda mais difícil encontrar algo que pudesse ser facilmente integrado à nossa base de código, passando gradualmente do SOAP para o REST. Portanto, não tivemos escolha: decidimos continuar desenvolvendo nosso próprio cliente.
Por isso, vivemos arrastando e soltando todos os tipos de hacks e muletas do cliente SOAP e adquirindo novos devido aos recursos REST. Como resultado, desenvolvemos algumas classes muito ousadas com um monte de código duplicado e é necessário limpar essa bagunça.
Os campos personalizados sempre foram o lugar mais doloroso para nós: temos mais de 300 deles (no momento em que escrevemos este artigo - 338), e esse número está crescendo lentamente.
Mensagens de erro estranhas
Ao longo da longa história de interação com a API, vimos muitas coisas diferentes. A maioria das mensagens de erro é adequada, mas há aquelas para as quais você precisa sobrecarregar bastante o cérebro.
Por exemplo, se Jira de repente reconhecer um robô no seu usuário, ela começará a mostrar um captcha. Nesse caso, ao acessar a API, ela descaradamente ignora o cabeçalho
Accept-Encoding: application / json e fornece HTML. Naturalmente, o cliente que está aguardando JSON pode não estar pronto para esse "olá".
E aqui está um exemplo de trabalho com um campo personalizado:

Ao escrever o código e "testar agora", você está testando, é muito fácil entender que customfield_12664 é
Desenvolvedores . E se esse erro aparecer em algum lugar da produção (por exemplo, porque alguém alterou a configuração e alterou a lista de valores aceitáveis para o campo Selecionar), geralmente a única maneira de identificar o campo é entrar no Jira e descobrir o nome a partir daí. Além disso, em sua interface, os IDs não são exibidos - apenas nomes.
Acontece que quando você deseja descobrir o nome do campo que levou ao erro e corrigir sua configuração, você pode fazer isso através de uma solicitação para a API ou usando outros métodos não óbvios: vasculhando o código fonte da página, abrindo as configurações para um campo arbitrário e corrigir o URL na barra de endereço, etc. Esse processo não pode ser chamado de conveniente e cada vez que leva muito tempo para uma tarefa tão simples.
Mas nomes de campos obscuros não se limitam a problemas. Veja como é a interação com a API se você cometer um erro na estrutura de dados ao atualizar o campo:


Muitos formatos de dados diferentes
E não esqueça que atualizar campos de tipos diferentes requer estruturas de dados diferentes.
$Jira->issue()->edit( 'SMPL-1', [ 'customfield_10200' => ['name' => 'denkoren'], 'customfield_10300' => ['value' => 'PHP'], 'customfield_10400' => [['value' => 'Android'], ['value' => 'iOS']], 'security' => ['id' => 100500], 'description' => 'Just text', ], );
As respostas da API para eles também são diferentes.
É possível ter isso em mente apenas se você estiver trabalhando constantemente com a API do Jira e não se distrair por um longo tempo resolvendo outros problemas. Caso contrário, esses recursos ficarão sem memória em algumas semanas. Além disso, você precisa se lembrar do tipo de campo necessário para "alimentá-lo" com a estrutura correta. Quando você possui centenas de campos personalizados, geralmente precisa procurar no código onde ele ainda foi usado ou subir no painel de administração do Jira.
Antes de escrevermos nosso cliente, Stack Overflow e Atlassian Community eram meus melhores amigos na atualização de campos personalizados. Agora, essas informações são pesquisadas no Google de maneira fácil e rápida, mas mudamos para a API REST quando ela ainda era relativamente nova e ativamente desenvolvida: demorou dez minutos para encontrar uma solicitação de cURL adequada no Google e então você teve que analisar esse monte de colchetes com os olhos e converter na estrutura correta do PHP, que geralmente não funcionava na primeira tentativa.
Em geral, a interação com campos personalizados é o processo cuja reorganização foi necessária em primeiro lugar.
Em que consiste o cliente?
Classes para trabalhar com campos personalizados
Antes de tudo, queríamos nos livrar da lembrança das estruturas de dados para interagir com a API e obter nomes de campos legíveis quando ocorreram erros.
Como resultado, criamos uma hierarquia de classe única para todos os campos personalizados. Descobriu-se três camadas:
- Um pai abstrato abstrato para todos: \ Badoo \ Jira \ CustomFields \ CustomField .
- Por uma classe abstrata para cada tipo de campo: SelectField, UserField, TextField , etc.
- Por classe para cada campo específico: por exemplo, Desenvolvedor ou Revisor .
Essas classes podem ser escritas independentemente ou podem ser criadas automaticamente usando um gerador de script especial (retornaremos a ela).

Graças a essa estrutura, para ensinar o código a atualizar o valor do seu campo personalizado do tipo
Lista de Seleção (múltipla escolha) , basta criar uma classe PHP herdada do
SelectField . De fato, todo campo Jira personalizado se transforma em um ActiveRecord regular no código PHP.
namespace \Example\CustomFields; class Developer extends \Badoo\Jira\CustomFields\SingleUserField { const ID = 'customfield_10200'; const NAME = 'Developer'; }
Na mesma classe, armazenamos informações sobre o campo: por padrão, esse é um ID, o nome do campo e uma lista de valores disponíveis, se forem limitados (por exemplo, para
Checkbox e
Select ).
Exemplos de campos na interface Jira e sua classe correspondente
class IssueFor extends \Badoo\Jira\CustomFields\SingleSelectField { const ID = 'customfield_10662'; const NAME = 'Issue for'; const VALUE_BI = 'BI'; const VALUE_C_C = 'C\C++'; const VALUE_HTML_CSS = 'HTML\CSS'; const VALUE_JS = 'JS'; const VALUE_OTHER = 'Other'; const VALUE_PHP = 'PHP'; const VALUE_TRANSLATION = 'Translation'; const VALUES = [ self::VALUE_BI, self::VALUE_C_C, self::VALUE_HTML_CSS, self::VALUE_JS, self::VALUE_OTHER, self::VALUE_PHP, self::VALUE_TRANSLATION, ]; public function getItemsList() : array { return static::VALUES; } }
Acontece que esse tipo de documentação é para o seu Jira, localizado diretamente no código PHP. Quando está tão perto, é muito conveniente e acelera significativamente o desenvolvimento, reduzindo o número de erros.
Além disso, as mensagens de erro ficam mais claras: em vez de dizer nada,
'customfield_12664' falha, por exemplo, algo como isto:
Uncaught Badoo\Jira\Exception\CustomField: User 'asdkljfh' not found in Jira. Can't change 'Developer' field value.
Classes para trabalhar com objetos do sistema
O Jira possui muitos dados com uma estrutura complexa: por exemplo, os campos do sistema
Status e
Segurança , links entre tarefas, usuários, versões, anexos (arquivos).
Também os envolvemos em aulas:
Esses wrappers oferecem ao seu IDE a capacidade de informar quais dados estão disponíveis e permitem formalizar estritamente as interfaces de função no seu código. Utilizamos ativamente declarações de tipo, quase sempre nos permite ver um erro, mesmo ao escrever código, graças ao destaque do IDE. E se você ainda perdeu o erro, ele sairá exatamente no local em que apareceu pela primeira vez, e não onde você finalmente soltou seu código.
Ainda existem métodos estáticos que permitem obter rapidamente um objeto por algum critério:
$users = \Badoo\Jira\User::search('<pattern>');
Esses métodos obedecem às regras gerais para que sejam fáceis de encontrar:
- :: search () , se você precisar encontrar objetos em vários campos: \ Badoo \ Jira \ Issue :: search () procura tarefas usando JQL, onde é possível especificar muitos critérios de pesquisa, e \ Badoo \ Jira \ User :: search () pesquisa o usuário ao mesmo tempo por 'nome' (login), 'email' e 'displayName' (o nome que é renderizado na web);
- :: by * () , se você precisar obter o objeto não por ID, mas por algum outro critério: \ Badoo \ Jira \ User :: byEmail () está procurando um usuário pelo endereço de email;
- :: for * () procura todos os objetos associados a algo: \ Badoo \ Jira \ Version :: forProject
fornece todas as versões de um projeto específico; - :: fromStdClass () criará um objeto a partir de dados brutos que possuem uma estrutura adequada, mas não recebida da API, mas, por exemplo, do webhook : no corpo da solicitação POST, Jira envia ao JSON informações diferentes sobre o evento, incluindo o corpo da tarefa incluindo todos os campos. Com base nesses dados, você pode criar o objeto \ Badoo \ Jira \ Issue e usá-lo normalmente.
Classe \ Badoo \ Jira \ Issue
Parece-me que a seguinte captura de tela do PhpStorm é bastante eloquente em si:

Em essência, o objeto
\ Badoo \ Jira \ Issue vincula tudo descrito acima em um único sistema. Ele armazena todas as informações sobre a tarefa, possui métodos para acesso rápido aos dados usados com mais frequência, transferência de tarefas entre status etc.
Para criar um objeto no caso mais simples, basta conhecer apenas a chave da tarefa.
Crie um objeto com apenas uma chave de tarefa no bolso $Issue = new \Badoo\Jira\Issue('SMPL-1');
Você também pode usar qualquer conjunto de dados fragmentado. Por exemplo, as informações do link entre as tarefas que chegam da API contêm apenas alguns campos: ID, resumo, status, prioridade e tipo de emissão.
\ Badoo \ Jira \ Issue permite coletar um objeto desses dados para que ele possa ser retornado imediatamente e, para o restante, acessar a API.
Crie um objeto, armazenando em cache valores para alguns campos $IssueFromLink = \Badoo\Jira\Issue::fromStdClass( $LinkInfo, [ 'id', 'key', 'summary', 'status', 'priority', 'issuetype', ] );
Isso é obtido através da inicialização lenta e do armazenamento em cache de dados no código. Essa abordagem é especialmente conveniente, pois você só pode trocar objetos
\ Badoo \ Jira \ Issue no seu código, independentemente de qual conjunto de campos eles foram criados.
Obter os dados da tarefa ausentes $IssueFromLink->getSummary();
Como vamos para a APINa API do Jira, é possível obter nem todos os campos de uma tarefa, mas apenas os campos necessários no momento: por exemplo, apenas chave e resumo. No entanto, intencionalmente não vamos a Jira para apenas um campo no getter. No exemplo acima, getDescription () atualizará informações sobre todos os campos de uma só vez. Como o
\ Badoo \ Jira \ Issue não tem a menor idéia do que mais você precisa a seguir, é mais lucrativo obter tudo da API imediatamente, já que fomos para lá de qualquer maneira. Sim, a consulta "obter apenas descrição" e a consulta "obter todos os campos por padrão" para algumas centenas de tickets levam tempos diferentes, mas para um, essa diferença não é tão perceptível.
//Time for single field: 0.40271635055542 (second) //Time for all default fields: 0.84159119129181 (second)
A partir das figuras, fica claro que, ao receber apenas três campos (um na solicitação), é mais rentável obter tudo de uma só vez, em vez de acessar a API de cada um. O resultado dessa medição, de fato, depende da configuração do Jira e do servidor no qual ele é executado. De tarefa para tarefa e de medição em medição, os números mudam e o
Tempo para todos os campos padrão acaba sendo estavelmente menor que três.
Tempo para campo único e, geralmente, menor que dois.
No entanto, ao trabalhar com um grande número de tarefas, a diferença pode ser medida em segundos. Portanto, quando você sabe que precisa apenas de chave e descrição para 500 tickets, a capacidade de obtê-los com uma consulta efetiva permanece nos
métodos \ Badoo \ Jira \ Issue :: search () e
\ Badoo \ Jira \ Issue :: byKeys () .
\ Badoo \ Jira \ Issue - geralmente sobre tarefas em algum Jira abstrato. Mas o seu (como o nosso) Jira não é abstrato - ele tem um conjunto muito específico de campos personalizados e seu próprio fluxo de trabalho. Você usa alguns dos campos e transições com frequência, por isso não é muito conveniente ir atrás deles o tempo todo. Portanto,
\ Badoo \ Jira \ Issue pode ser facilmente estendido com seus próprios métodos específicos para uma configuração específica do Jira.
Um exemplo de extensão de classe por um método para obter rapidamente um campo personalizado namespace \Deploy; class Issue extends \Badoo\Jira\Issue {
Solicitação de criação de problema
Criar uma tarefa no Jira é um procedimento bastante complicado. Quando você faz isso através da interface da web, é exibida uma tela especial (Criar tela) com um conjunto de campos. Alguns deles podem ser preenchidos simplesmente porque você deseja, e alguns são marcados como obrigatórios. Ao mesmo tempo, a tela Criar pode ser exclusiva para cada projeto e até para diferentes tipos de tarefas em um projeto. Portanto, existem todos os tipos de restrições nos valores do campo e na própria possibilidade de definir o valor do campo no processo de criação da tarefa.
A coisa mais desagradável para os desenvolvedores nessa situação é que essas restrições se aplicam à API. O último possui uma solicitação especial (
create-meta está disponível na API REST desde a versão 5.0), com a qual você pode obter uma lista das configurações de campo disponíveis ao criar uma tarefa. No entanto, um desenvolvedor que precisa "simplesmente fazer uma coisa simples agora" provavelmente não se incomodará com isso.
Como resultado, aconteceu da seguinte maneira: como a solicitação para criar uma tarefa pode ser bastante grande, geralmente adicionamos dados gradualmente a ela e recebemos um erro ao tentar enviar tudo para Jira. Depois disso, tive que procurar no código todos os lugares onde algo mudou na solicitação e uma tentativa longa e entediante de entender o que exatamente deu errado.
Portanto, fizemos
\ Badoo \ Jira \ Issue \ CreateRequest . Ele permite que você veja o erro mais cedo, exatamente no local em que está tentando fazer algo errado: forneça ao campo algum tipo de valor curvo ou altere o campo que não está disponível. Por exemplo, se você tentar especificar um componente que não existe no projeto, a exceção será interrompida no local em que você o fez e não no local em que você finalmente enviou a solicitação à API.
O fluxo de trabalho com CreateRequest se parece com isso $Request = new \Badoo\Jira\Issue\CreateRequest('DD', 'Task', $Client); $Request ->setSummary('summary') ->setDescription('description') ->setFieldValue('For QA', 'custom field with some comments for QA who will check the issue'); $Request->send();
Trabalhar diretamente com a API
O conjunto de classes descrito acima cobre a maioria das necessidades. No entanto, estamos bem cientes de que a maioria está longe de tudo. Portanto, também temos um cliente pequeno para trabalhar diretamente com a API -
\ Badoo \ Jira \ REST \ Client .
Caso de Uso do Cliente $Jira = \Badoo\Jira\REST\Client::instance(); $Jira->setJiraUrl('https://jira.example.com/'); $Jira->setAuth('user', 'password') $IssueInfo = $Jira->issue()->get('SMPL-1');
Gerador de classe para campos personalizados
Para facilitar o trabalho com campos personalizados, cada campo deve ter sua própria classe no código. Nós os criamos manualmente, conforme necessário, mas antes de publicar o cliente, decidimos que essa abordagem pode não ser muito conveniente para novos usuários. Portanto, criamos um gerador especial que pode acessar a API do Jira para obter uma lista de campos personalizados e criar classes de modelos para os tipos de campos conhecidos.
Acreditamos que, para a maioria das tarefas, é suficiente usar o script CLI
bin / generate do nosso repositório. Você pode pedir que ele conte sobre si mesmo através da opção
--help / -h :
./bin/generate --help
No caso mais simples, para geração, basta especificar a URL do seu Jira, o usuário, sua senha, espaço para nome das classes e o diretório onde colocar o código:
./bin/generate -u user -p password --jira-url https://jira.mlan --target-dir custom-fields --namespace CustomFields
Também implementamos a capacidade de adicionar nossos próprios modelos e gerar classes para campos individuais. Isso pode ser encontrado na
documentação .
Conclusão
Nós gostamos do que conseguimos. Com esse conceito - nossas próprias classes para campos personalizados, wrappers para status, versões, usuários etc. - vivemos há mais de um ano e nos sentimos bem. Antes de publicar o código, até expandimos a funcionalidade e adicionamos coisas maravilhosas que não chegavam às suas mãos por muito tempo para usar o cliente era ainda mais conveniente: por exemplo, adicionamos a capacidade de atualizar vários campos no problema em uma solicitação e escrevemos um gerador de classe para campos personalizados.
Em nossa opinião, acabou sendo uma coisa boa, que definitivamente deve ser sentida para entender se ela se adequa às suas tarefas e requisitos. Sob o nosso - apenas ajuste.
Link novamente:
github.com/badoo/jira-client .
Obrigado por ler até o fim. Esperamos que este código agora seja beneficiado e economize tempo, não apenas para nós.