Add a command to assign release versions to Jira tickets [MAILPOET-1783]

This commit is contained in:
wxa
2019-02-12 09:48:33 +03:00
committed by M. Shull
parent 8a23a9def8
commit 84f0816dca
3 changed files with 171 additions and 12 deletions

View File

@@ -588,6 +588,16 @@ class RoboFile extends \Robo\Tasks {
); );
} }
protected function getReleaseVersionController($project) {
require_once './tasks/release/ReleaseVersionController.php';
$this->loadEnv();
return \MailPoetTasks\Release\ReleaseVersionController::createWithJiraCredentials(
getenv('WP_JIRA_TOKEN'),
getenv('WP_JIRA_USER'),
$project
);
}
public function testAcceptanceGroupTests() { public function testAcceptanceGroupTests() {
return $this->taskSplitTestFilesByGroups(4) return $this->taskSplitTestFilesByGroups(4)
->projectRoot('.') ->projectRoot('.')
@@ -598,10 +608,7 @@ class RoboFile extends \Robo\Tasks {
public function writeReleaseVersion($version) { public function writeReleaseVersion($version) {
$version = trim($version); $version = trim($version);
if (!preg_match('/\d+\.\d+\.\d+/', $version)) { $this->validateVersion($version);
$this->yell('Incorrect version format', 40, 'red');
exit(1);
}
$this->taskReplaceInFile(__DIR__ . '/readme.txt') $this->taskReplaceInFile(__DIR__ . '/readme.txt')
->regex('/Stable tag:\s*\d+\.\d+\.\d+/i') ->regex('/Stable tag:\s*\d+\.\d+\.\d+/i')
@@ -618,4 +625,34 @@ class RoboFile extends \Robo\Tasks {
->to(sprintf("'version' => '%s',", $version)) ->to(sprintf("'version' => '%s',", $version))
->run(); ->run();
} }
public function jiraReleaseVersion($opts = ['free' => null, 'premium' => null]) {
require_once './tasks/release/Jira.php';
if (empty($opts['free']) && empty($opts['premium'])) {
$this->yell('No Free or Premium version specified', 40, 'red');
exit(1);
}
$output = [];
if (!empty($opts['free'])) {
$this->validateVersion($opts['free']);
$output[] = $this->getReleaseVersionController(\MailPoetTasks\Release\Jira::PROJECT_MAILPOET)
->assignVersionToCompletedTickets($opts['free']);
}
if (!empty($opts['premium'])) {
$this->validateVersion($opts['premium']);
$output[] = $this->getReleaseVersionController(\MailPoetTasks\Release\Jira::PROJECT_PREMIUM)
->assignVersionToCompletedTickets($opts['premium']);
}
if($opts['quiet']) {
return;
}
$this->say(join("\n", $output));
}
private function validateVersion($version) {
if (!preg_match('/\d+\.\d+\.\d+/', $version)) {
$this->yell('Incorrect version format', 40, 'red');
exit(1);
}
}
} }

View File

@@ -11,6 +11,7 @@ class Jira {
const PROJECT_PREMIUM = 'PREMIUM'; const PROJECT_PREMIUM = 'PREMIUM';
const JIRA_DOMAIN = 'mailpoet.atlassian.net'; const JIRA_DOMAIN = 'mailpoet.atlassian.net';
const JIRA_API_VERSION = '3';
/** @var string */ /** @var string */
private $token; private $token;
@@ -31,7 +32,7 @@ class Jira {
* @see https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-project-projectIdOrKey-versions-get * @see https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-project-projectIdOrKey-versions-get
*/ */
function getVersion($version_name = null) { function getVersion($version_name = null) {
$versions = $this->fetchFromJira("project/$this->project/versions"); $versions = $this->makeJiraRequest("project/$this->project/versions");
if ($version_name === null) { if ($version_name === null) {
return end($versions); return end($versions);
} }
@@ -44,12 +45,22 @@ class Jira {
} }
/** /**
* @see https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-search-get * @see https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-version-post
*/ */
function createVersion($version_name) {
$data = [
'name' => $version_name,
'archived' => false,
'released' => false,
'project' => $this->project,
];
return $this->makeJiraRequest('/version', 'POST', $data);
}
function getIssuesDataForVersion($version) { function getIssuesDataForVersion($version) {
$changelog_id = self::CHANGELOG_FIELD_ID; $changelog_id = self::CHANGELOG_FIELD_ID;
$release_note_id = self::RELEASENOTE_FIELD_ID; $release_note_id = self::RELEASENOTE_FIELD_ID;
$issues_data = $this->fetchFromJira("/search?fields=key,$changelog_id,$release_note_id,status&jql=fixVersion={$version['id']}"); $issues_data = $this->search("fixVersion={$version['id']}", ['key', $changelog_id, $release_note_id, 'status']);
// Sort issues by importance of change (Added -> Updated -> Improved -> Changed -> Fixed -> Others) // Sort issues by importance of change (Added -> Updated -> Improved -> Changed -> Fixed -> Others)
usort($issues_data['issues'], function($a, $b) use ($changelog_id) { usort($issues_data['issues'], function($a, $b) use ($changelog_id) {
$order = array_flip(['added', 'updat', 'impro', 'chang', 'fixed']); $order = array_flip(['added', 'updat', 'impro', 'chang', 'fixed']);
@@ -62,16 +73,46 @@ class Jira {
return $issues_data['issues']; return $issues_data['issues'];
} }
private function fetchFromJira($path) { /**
* @see https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-search-get
*/
function search($jql, array $fields = null) {
$params = ['jql' => $jql];
if ($fields) {
$params['fields'] = join(',', $fields);
}
return $this->makeJiraRequest("/search?" . http_build_query($params));
}
/**
* @see https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-issue-issueIdOrKey-put
*/
function updateIssue($key, $data) {
return $this->makeJiraRequest("/issue/$key", 'PUT', $data);
}
private function makeJiraRequest($path, $method = 'GET', array $data = null) {
$url_user = urlencode($this->user); $url_user = urlencode($this->user);
$url_token = urlencode($this->token); $url_token = urlencode($this->token);
$jira_domain = self::JIRA_DOMAIN; $jira_domain = self::JIRA_DOMAIN;
$jira_url = "https://$url_user:$url_token@$jira_domain/rest/api/3/$path"; $jira_api_version = self::JIRA_API_VERSION;
$data = file_get_contents($jira_url); $jira_url = "https://$url_user:$url_token@$jira_domain/rest/api/$jira_api_version/$path";
if ($data === false) { $options = [];
if ($method === 'POST' || $method === 'PUT') {
$options = [
'http' => [
'method' => $method,
'header' => "Content-type: application/json\r\n",
'content' => json_encode($data),
]
];
}
$context = stream_context_create($options);
$result = file_get_contents($jira_url, false, $context);
if ($result === false) {
$error = error_get_last(); $error = error_get_last();
throw new \Exception('JIRA request error: ' . $error['message']); throw new \Exception('JIRA request error: ' . $error['message']);
} }
return json_decode($data, true); return json_decode($result, true);
} }
} }

View File

@@ -0,0 +1,81 @@
<?php
namespace MailPoetTasks\Release;
require_once __DIR__ . '/Jira.php';
class ReleaseVersionController {
/** @var Jira */
private $jira;
/** @var string */
private $project;
function __construct(Jira $jira, $project) {
$this->jira = $jira;
$this->project = $project;
}
static function createWithJiraCredentials($token, $user, $project) {
return new self(new Jira($token, $user, $project), $project);
}
function assignVersionToCompletedTickets($version) {
$output = [];
$output[] = "Checking version $version in $this->project";
if (!$this->checkVersion($version)) {
$output[] = "The version is invalid or already released";
return join("\n", $output);
}
$output[] = "Setting version $version to completed tickets in $this->project...";
$issues = $this->getDoneIssuesWithoutVersion();
$result = array_map(function ($issue) use ($version) {
return $this->setIssueFixVersion($issue['key'], $version);
}, $issues);
$output[] = "Done, issues processed: " . count($result);
return join("\n", $output);
}
function getDoneIssuesWithoutVersion() {
$jql = "project = $this->project AND status = Done AND (fixVersion = EMPTY OR fixVersion IN unreleasedVersions()) AND updated >= -52w";
$result = $this->jira->search($jql, ['key']);
return array_map(function ($issue) {
return [
'id' => $issue['id'],
'key' => $issue['key'],
];
}, $result['issues']);
}
function checkVersion($version) {
try {
$version_data = $this->jira->getVersion($version);
} catch (\Exception $e) {
$version_data = false;
}
if (!empty($version_data['released'])) {
// version is already released
return false;
} else if (empty($version_data)) {
// version does not exist
$this->jira->createVersion($version);
}
// version exists
return true;
}
function setIssueFixVersion($issue_key, $version) {
$data = [
'update' => [
'fixVersions' => [
['set' => [['name' => $version]]]
]
]
];
return $this->jira->updateIssue($issue_key, $data);
}
}