Badoo Jira API客户端:PHP中的Jira魔术

如果您在Habré的搜索字符串中输入“ Jira Badoo”,结果将占用一页以上的内容:我们几乎在所有地方都提及该页面,因为它在我们的流程中起着重要作用。 我们每个人都希望与她有所不同。



收到了要进行审查的任务的开发人员期望任务中指示一个分支,其中有指向diff和更改日志的链接。 编写代码的开发人员希望在审查后能在Jira中看到注释。 在任务之后接收任务的测试人员希望查看测试结果并能够运行必要的程序集,而无需使用其他界面。 产品经理通常希望通过按一个按钮来同时创建十个开发任务。

所有这一切今天都可用,并且会自动发生。 我们使用不断发展的Jira API及其webhook在PHP中实现了大多数魔术。 今天,我们想与社区共享此API的客户端版本。

首先,我们只是想谈论我们使用的想法和方法,然后为了清晰起见,决定绝对没有足够的代码来编写此类文章。 因此,有了Badoo Jira PHP Client的开源版本。 非常感谢ShaggyRatte帮助她进行描述。 欢迎来到凯特!

更多细节和背景。
如果您需要有关Jira的详细信息,可以在其他文章中找到它们:

habr.com/cn/company/badoo/blog/169417
habr.com/cn/company/badoo/blog/433540
habr.com/en/company/badoo/blog/424655

他能做什么?


实际上,Badoo Jira PHP Client是一组用于Jira API响应的现成包装器类,其中大多数都可以像ActiveRecord一样运行:它们知道如何获取有关自身的数据以及如何在服务器上更新它们,在级别上支持延迟初始化和数据缓存。代码。 您必须不断使用的所有Jira实体都被包装: Issue,Status,Priority,Changelog,User,Version,Component等(几乎在界面中看到的所有内容)。

另外,Badoo Jira PHP Client为所有Jira自定义字段提供了一个单一的类层次结构,并且能够为您需要的每个自定义字段生成类定义。

$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'); 

因此,与PHP中的API的交互变得更加简单和方便,并且Jira的文档直接移至了代码,这使您可以在IDE中使用自动完成功能来执行许多标准操作。

但是首先是第一件事。

我们遇到的API功能


当我们开始积极使用Jira API时,它仅可通过SOAP协议使用。 其REST版本稍后出现,而我们是其第一批用户。 当时,很少有用PHP编写的公开可用的REST客户端。 要找到可以轻松集成到我们的代码库中的东西,甚至逐渐从SOAP到REST,变得更加困难。 因此,我们别无选择:我们决定继续开发自己的客户。

因此,由于REST的特殊性,我们通过从SOAP客户端拖放各种技巧和拐杖并获得新的技巧来生活。 结果,我们已经开发了一些带有大量重复代码的非常大胆的类,并且有必要清除这种混乱。

自定义字段一直是我们最痛苦的地方:我们有300多个字段(在撰写本文时为338个),并且这个数字正在缓慢增长。

奇怪的错误消息


在与API交互的悠久历史中,我们看到了许多不同的东西。 大多数错误消息是足够的,但是对于某些错误消息,您必须花费很多精力。

例如,如果Jira突然在您的用户中识别出一个机器人,则她将开始向他显示验证码。 在这种情况下,访问API时,它会毫不客气地忽略Accept-Encoding:application / json标头,并为您提供HTML。 自然,正在等待JSON的客户端可能尚未准备好迎接这个“ hello”。

这是使用自定义字段的示例:



在编写代码并“立即测试”时,您正在对其进行测试,这很容易理解customfield_12664是Developers 。 而且,如果这样的错误在生产环境中蔓延开来(例如,由于有人更改了配置并更改了Select字段的可接受值的列表),那么识别该字段的唯一方法通常是进入Jira并从那里找到名称。 此外,在其界面中,不显示ID-仅显示名称。

事实证明,当您想找出导致错误的字段名称并更正其配置时,可以通过向API请求或使用其他一些非显而易见的方法来做到这一点:浏览页面的源代码,打开任意字段的设置以及更正地址栏中的URL等。此过程不方便,每次执行此简单任务都花费太多时间。

但是,晦涩的字段名称不仅限于问题。 如果您在更新字段的数据结构中出错,则与API的交互如下所示:




许多不同的数据格式


并且不要忘记,更新不同类型的字段需要不同的数据结构。

 $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', ], ); 

它们的API答案当然也不同。

只有当您不断使用Jira API并且长时间不因解决其他问题而分心时,才可以记住这一点。 否则,这些功能将在几周内飞出内存。 此外,您需要记住所需的字段类型,以便以正确的结构“填充”该字段。 当您有数百个自定义字段时,您通常不得不在代码中查找仍在使用的字段,或者爬入Jira管理面板。

在编写客户之前,关于更新自定义字段,Stack Overflow和Atlassian Community是我最好的朋友。 现在,可以轻松,快速地在Google上搜索此信息,但是当它仍是相当新的并且积极开发时,我们便切换到REST API:花了十分钟的时间在Google中找到合适的cURL请求,然后您不得不用眼睛解析这堆括号并进行转换转换为适用于PHP的正确结构,但通常在第一次尝试时就无法使用。

通常,与自定义字段的交互是首先需要对其进行重组的过程。

客户由什么组成?


用于处理自定义字段的类


首先,我们想摆脱记住与API交互的数据结构,并在发生错误时获取可读的字段名称。

结果,我们为所有自定义字段创建了一个单一的类层次结构。 结果是三层:

  1. 每个人都可以使用的通用抽象父级: \ Badoo \ Jira \ CustomFields \ CustomField
  2. 通过每种字段类型的抽象类: SelectField,UserField,TextField等。
  3. 每个特定字段的类别:例如DeveloperReviewer

这些类可以独立编写,也可以使用特殊的脚本生成器自动创建(我们将返回到它)。


由于这种结构,为了教代码更新选择列表(多选)类型的自定义字段的值,创建从SelectField继承的PHP类就足够了。 实际上,每个自定义的Jira字段都将变成PHP代码中的常规ActiveRecord。

 namespace \Example\CustomFields; class Developer extends \Badoo\Jira\CustomFields\SingleUserField { const ID = 'customfield_10200'; const NAME = 'Developer'; } // ,  ,  ! 


在同一个类中,我们存储有关字段的信息:默认情况下,这是ID,字段名称以及可用值列表(如果有限制的话)(例如, CheckboxSelect )。

Jira界面及其相应类中的字段示例


 class IssueFor extends \Badoo\Jira\CustomFields\SingleSelectField { const ID = 'customfield_10662'; const NAME = 'Issue for'; /* Available field values. */ 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; } } 


事实证明,这种文档适合您的Jira,直接位于PHP代码中。 当它如此接近时,它非常方便并且可以显着加快开发速度,同时减少错误数量。

此外,错误消息变得更加清晰:例如, “ customfield_12664”崩溃而不是什么也不说,例如:

 Uncaught Badoo\Jira\Exception\CustomField: User 'asdkljfh' not found in Jira. Can't change 'Developer' field value. 

用于处理系统对象的类


Jira具有结构复杂的大量数据:例如, 状态安全性系统字段,任务,用户,版本,附件(文件)之间的链接。

我们还将它们包装在类中:

 //   $Status = $Issue->getStatus(); $Status->getName(); $Status->getId(); 

 // changelog  $History = \Badoo\Jira\Issue\History::forIssue('SMPL-1'); $seconds_in_status = $History->getTimeInStatus('Open'); 

 //  Jira $User = new \Badoo\Jira\User('sampleuser'); $User->assign('SMPL-1'); 

这样的包装使您的IDE能够分辨出哪些数据可用,并允许您严格地规范代码中的函数接口。 我们积极使用类型声明,由于IDE的突出显示,几乎总是使我们即使在编写代码时也能看到错误。 而且,如果您仍然错过该错误,它将在最初出现的位置准确显示出来,而不是在最终删除代码的地方出现。

仍然有静态方法可以让您通过某些条件快速获取对象:

 $users = \Badoo\Jira\User::search('<pattern>'); //    login, email  display name $Version = \Badoo\Jira\Version::byName('<project>', '<version name>'); //        $components = \Badoo\Jira\Component::forProject('<project>'); //      

这些方法遵循一般规则,因此很容易找到:
  • :: search() ,如果需要按以下几个字段查找对象:\ Badoo \ Jira \ Issue :: search()使用JQL搜索任务,您可以在其中指定许多搜索条件,以及\ Badoo \ Jira \ User :: search ()通过“名称” (登录), “电子邮件”“ displayName” (在网络上呈现的名称)同时搜索用户;
  • :: by *() ,如果您不是通过ID而是通过其他标准来获取对象:\ Badoo \ Jira \ User :: byEmail()正在通过其电子邮件地址查找用户;
  • :: for *()查找与某物关联的所有对象:\ Badoo \ Jira \版本:: forProject
    提供特定项目的所有版本;
  • :: fromStdClass()将根据原始数据创建一个对象,该原始数据具有合适的结构,但不会从API接收,但是例如从webhook接收 :在POST请求的主体中,Jira发送带有有关事件的不同信息的JSON,包括任务主体包括所有领域。 基于此数据,您可以创建\ Badoo \ Jira \ Issue对象,并照常使用它。

类\ Badoo \ Jira \ Issue


在我看来,以下PhpStorm屏幕截图本身非常雄辩:



本质上, \ Badoo \ Jira \ Issue对象将上述所有内容绑定到单个系统中。 它存储有关任务的所有信息,具有快速访问最常用数据,在状态之间传输任务等方法。

要在最简单的情况下创建对象,仅知道任务的关键就足够了。

创建一个只带任务键的对象
 $Issue = new \Badoo\Jira\Issue('SMPL-1'); 


您还可以使用任何零散的数据集。 例如,从API到达的任务之间的链接信息仅包含几个字段:id,摘要,状态,优先级和问题类型。 \ Badoo \ Jira \ Issue允许您从此数据中收集对象,以便可以立即返回该对象,其余的访问API。

创建一个对象,缓存某些字段的值
  $IssueFromLink = \Badoo\Jira\Issue::fromStdClass( $LinkInfo, [ 'id', 'key', 'summary', 'status', 'priority', 'issuetype', ] ); 


这是通过延迟初始化和缓存代码中的数据来实现的。 这种方法特别方便,因为您只能交换代码中的\ Badoo \ Jira \ Issue对象,而不管它们是使用哪个字段集创建的。

获取丢失的任务数据
 $IssueFromLink->getSummary(); //    API,    $IssueFromLink->getDescription(); //  API    description 


我们如何使用API
在Jira API中,可能无法获取任务的所有字段,而仅获取当前需要的字段:例如,仅获取键和摘要。 但是,我们故意不要仅在吸气剂领域中去吉拉。 在上面的示例中,getDescription()将立即更新有关所有字段的信息。 由于\ Badoo \ Jira \ Issue并没有丝毫的下一步需求,因此立即从API中获取所有内容会更加有利可图,因为我们还是到了那里。 是的,数百张票证的查询“仅获取描述”和“默认情况下获取所有字段”查询花费的时间不同,但是对于一次来说,这种区别并不那么明显。

 //Time for single field: 0.40271635055542 (second) //Time for all default fields: 0.84159119129181 (second) 

从图中可以清楚地看出,当仅接收三个字段(请求中的一个)时,一次获取所有内容而不是针对每个字段使用API​​会更有利可图。 实际上,此测量的结果取决于Jira的配置以及运行Jira的服务器。 从一个任务到另一个任务以及从一个度量到另一个度量,数字变化,并且所有默认字段的时间稳定地少于三个,而单个字段的时间却常常少于两个。

但是,当处理大量任务时,差异可以以秒为单位。 因此,当您知道只需要500张票证的密钥和说明时,通过\ Badoo \ Jira \ Issue :: search()\ Badoo \ Jira \ Issue :: byKeys()方法获得一个有效请求的能力仍然存在。


\ Badoo \ Jira \ Issue-通常涉及一些抽象Jira中的任务。 但是您(像我们一样)的Jira并不是抽象的-它具有一组非常特定的自定义字段和自己的工作流程。 您经常使用某些令人讨厌的字段和转换,因此很难一路走下去。 因此,可以使用特定于特定Jira配置的自身方法轻松扩展\ Badoo \ Jira \ Issue

通过快速获取自定义字段的方法进行类扩展的示例
 namespace \Deploy; class Issue extends \Badoo\Jira\Issue { // … /** * Get 'Developer' custom field as object */ function getDeveloperField() : \Deploy\CustomFields\Developer { return $this->getCustomField(\Deploy\CustomFields\Developer::class); } // ... } 



发布创建请求


在Jira中创建任务是一个相当复杂的过程。 通过Web界面执行此操作时,将显示一个带有一组字段的特殊屏幕(创建屏幕)。 您可以仅根据需要填写其中的一些,而有些则标记为必填。 同时,对于每个项目,甚至对于一个项目中的不同类型的任务,创建屏幕都可以是唯一的。 因此,对字段值以及在创建任务的过程中设置字段值的可能性都有各种限制。

在这种情况下,对开发人员而言最不愉快的事情是这些限制适用于API。 后者有一个特殊的请求(从5.0版开始,REST API中提供了create-meta ),通过它可以获取创建任务时可用的字段设置列表。 但是,需要“现在就做一件简单的事情”的开发人员很可能不会为此而烦恼。

结果就是这样:因为创建任务的请求可能很大,所以我们经常向其中逐步添加数据,并且在尝试将所有内容发送给Jira时收到错误。 之后,我必须在代码中查找请求中发生任何更改的所有地方,并且进行了冗长而乏味的尝试,以了解到底出了什么问题。

因此,我们做了\ Badoo \ Jira \ Issue \ CreateRequest 。 它使您可以在尝试做错事的地方更早地看到错误:为字段提供某种弯曲值或更改不可用的字段。 例如,如果您尝试指定项目中不存在的组件,则异常将在您执行该操作的位置崩溃,而不是最终向API发送请求的位置。

CreateRequest的工作流程如下所示
 $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(); 


直接使用API


上面描述的类集涵盖了大多数需求。 但是,我们深知,大多数还远远没有。 因此,我们还有一个直接用于API的小型客户端- \ Badoo \ Jira \ REST \ Client

客户用例
 $Jira = \Badoo\Jira\REST\Client::instance(); $Jira->setJiraUrl('https://jira.example.com/'); $Jira->setAuth('user', 'password') $IssueInfo = $Jira->issue()->get('SMPL-1'); 


自定义字段的类生成器


为了方便使用自定义字段,每个字段在代码中必须有自己的类。 我们根据需要手动创建了它们,但是在发布客户端之前,我们决定这种方法对于新用户可能不太方便。 因此,我们制作了一个特殊的生成器,可以使用Jira API获取自定义字段的列表,并为已知字段类型创建模板类。

我们认为,对于大多数任务而言,从我们的存储库中使用bin / generate CLI脚本就足够了。 您可以要求他通过--help / -h选项介绍自己:

 ./bin/generate --help 

在最简单的情况下,对于生成而言,只需指定您的Jira的URL,用户,他的密码,类的名称空间以及放置代码的目录:

 ./bin/generate -u user -p password --jira-url https://jira.mlan --target-dir custom-fields --namespace CustomFields 

我们还实现了添加自己的模板并为各个字段生成类的功能。 可以在文档中找到。

结论


我们喜欢我们得到的。 有了这个概念-我们自己的自定义字段类,状态,版本,用户等的包装器-我们已经生活了一年多,感觉很棒。 在发布代码之前,我们甚至扩展了功能并添加了很多长时间无法使用客户端才能使用的奇妙功能,这更加方便:例如,我们添加了在一个请求中更新Issue中多个字段的功能,并为自定义字段编写了一个类生成器。

我们认为,这是一件好事,一定要理解它是否适​​合您的任务和要求。 在我们的下-恰到好处。

再次链接: github.com/badoo/jira-client

感谢您阅读到最后。 我们希望这段代码现在不仅可以为我们带来好处,而且可以节省时间。

Source: https://habr.com/ru/post/zh-CN475840/


All Articles