Failed Conditions
Push — master ( 2cd9b5...479acf )
by Alexander
01:46
created

IssueCloner::getIssues()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 23
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 23
ccs 0
cts 17
cp 0
rs 8.5906
cc 5
eloc 12
nc 4
nop 4
crap 30
1
<?php
2
/**
3
 * This file is part of the Jira-CLI library.
4
 * For the full copyright and license information, please view
5
 * the LICENSE file that was distributed with this source code.
6
 *
7
 * @copyright Alexander Obuhovich <[email protected]>
8
 * @link      https://github.com/console-helpers/jira-cli
9
 */
10
11
namespace ConsoleHelpers\JiraCLI\Issue;
12
13
14
use chobie\Jira\Issue;
15
use chobie\Jira\Issues\Walker;
16
use ConsoleHelpers\JiraCLI\JiraApi;
17
18
class IssueCloner
19
{
20
21
	const LINK_DIRECTION_INWARD = 1;
22
23
	const LINK_DIRECTION_OUTWARD = 2;
24
25
	/**
26
	 * Jira REST client.
27
	 *
28
	 * @var JiraApi
29
	 */
30
	protected $jiraApi;
31
32
	/**
33
	 * Specifies custom fields to copy during backporting.
34
	 *
35
	 * @var array
36
	 */
37
	private $_copyCustomFields = array(
38
		'Change Log Group', 'Change Log Message',
39
	);
40
41
	/**
42
	 * Custom fields map.
43
	 *
44
	 * @var array
45
	 */
46
	private $_customFieldsMap = array();
47
48
	/**
49
	 * Fields to query during issue search.
50
	 *
51
	 * @var array
52
	 */
53
	protected $queryFields = array('summary', 'issuelinks');
54
55
	/**
56
	 * IssueCloner constructor.
57
	 *
58
	 * @param JiraApi $jira_api Jira REST client.
59
	 */
60
	public function __construct(JiraApi $jira_api)
61
	{
62
		$this->jiraApi = $jira_api;
63
64
		$this->jiraApi->setOptions(0); // Don't expand fields.
65
	}
66
67
	/**
68
	 * Returns issues.
69
	 *
70
	 * @param string  $jql               JQL.
71
	 * @param string  $link_name         Link name.
72
	 * @param integer $link_direction    Link direction.
73
	 * @param array   $link_project_keys Link project keys.
74
	 *
75
	 * @return array
76
	 */
77
	public function getIssues($jql, $link_name, $link_direction, array $link_project_keys)
78
	{
79
		$this->_buildCustomFieldsMap();
80
81
		$walker = new Walker($this->jiraApi);
82
		$walker->push($jql, implode(',', $this->_getQueryFields()));
83
84
		$ret = array();
85
86
		foreach ( $walker as $issue ) {
87
			foreach ( $link_project_keys as $link_project_key ) {
88
				$linked_issue = $this->_getLinkedIssue($issue, $link_name, $link_direction, $link_project_key);
89
90
				if ( is_object($linked_issue) && $this->isAlreadyProcessed($issue, $linked_issue) ) {
91
					continue;
92
				}
93
94
				$ret[] = array($issue, $linked_issue, $link_project_key);
95
			}
96
		}
97
98
		return $ret;
99
	}
100
101
	/**
102
	 * Builds custom field map.
103
	 *
104
	 * @return void
105
	 */
106
	private function _buildCustomFieldsMap()
107
	{
108
		foreach ( $this->jiraApi->getFields() as $field_key => $field_data ) {
109
			if ( substr($field_key, 0, 12) === 'customfield_' ) {
110
				$this->_customFieldsMap[$field_data['name']] = $field_key;
111
			}
112
		}
113
	}
114
115
	/**
116
	 * Returns query fields.
117
	 *
118
	 * @return array
119
	 */
120
	private function _getQueryFields()
121
	{
122
		$ret = $this->queryFields;
123
124
		foreach ( $this->_copyCustomFields as $custom_field ) {
125
			if ( isset($this->_customFieldsMap[$custom_field]) ) {
126
				$ret[] = $this->_customFieldsMap[$custom_field];
127
			}
128
		}
129
130
		return $ret;
131
	}
132
133
	/**
134
	 * Returns issue, which backports given issue.
135
	 *
136
	 * @param Issue   $issue            Issue.
137
	 * @param string  $link_name        Link name.
138
	 * @param integer $link_direction   Link direction.
139
	 * @param string  $link_project_key Link project key.
140
	 *
141
	 * @return Issue|null
142
	 * @throws \InvalidArgumentException When link direction isn't valid.
143
	 */
144
	private function _getLinkedIssue(Issue $issue, $link_name, $link_direction, $link_project_key)
145
	{
146
		foreach ( $issue->get('issuelinks') as $issue_link ) {
147
			if ( $issue_link['type']['name'] !== $link_name ) {
148
				continue;
149
			}
150
151
			if ( $link_direction === self::LINK_DIRECTION_INWARD ) {
152
				$check_key = 'inwardIssue';
153
			}
154
			elseif ( $link_direction === self::LINK_DIRECTION_OUTWARD ) {
155
				$check_key = 'outwardIssue';
156
			}
157
			else {
158
				throw new \InvalidArgumentException('The "' . $link_direction . '" link direction isn\'t valid.');
159
			}
160
161
			if ( array_key_exists($check_key, $issue_link) ) {
162
				$linked_issue = new Issue($issue_link[$check_key]);
163
				$linked_issue_data = $this->jiraApi->getIssue($linked_issue->getKey(), 'project')->getResult();
164
165
				if ( $this->isLinkAccepted($issue, $linked_issue)
166
					&& $linked_issue_data['fields']['project']['key'] === $link_project_key
167
				) {
168
					return $linked_issue;
169
				}
170
			}
171
		}
172
173
		return null;
174
	}
175
176
	/**
177
	 * Creates backports issues.
178
	 *
179
	 * @param Issue   $issue          Issue.
180
	 * @param string  $project_key    Project key.
181
	 * @param string  $link_name      Link name.
182
	 * @param integer $link_direction Link direction.
183
	 * @param array   $component_ids  Component IDs.
184
	 *
185
	 * @return string
186
	 * @throws \RuntimeException When failed to create an issue.
187
	 * @throws \InvalidArgumentException When link direction isn't valid.
188
	 */
189
	public function createLinkedIssue(Issue $issue, $project_key, $link_name, $link_direction, array $component_ids)
190
	{
191
		$create_fields = array(
192
			'description' => 'See ' . $issue->getKey() . '.',
193
			'components' => array(),
194
		);
195
196
		foreach ( $this->_copyCustomFields as $custom_field ) {
197
			if ( isset($this->_customFieldsMap[$custom_field]) ) {
198
				$custom_field_id = $this->_customFieldsMap[$custom_field];
199
				$create_fields[$custom_field_id] = $this->getIssueCustomField($issue, $custom_field_id);
200
			}
201
		}
202
203
		foreach ( $component_ids as $component_id ) {
204
			$create_fields['components'][] = array('id' => (string)$component_id);
205
		}
206
207
		$create_issue_result = $this->jiraApi->createIssue(
208
			$project_key,
209
			$issue->get('summary'),
210
			$this->getChangelogEntryIssueTypeId(),
211
			$create_fields
212
		);
213
214
		$raw_create_issue_result = $create_issue_result->getResult();
215
216
		if ( array_key_exists('errors', $raw_create_issue_result) ) {
217
			throw new \RuntimeException(sprintf(
218
				'Failed to create linked issue for "%s" issue. Errors: ' . PHP_EOL . '%s',
219
				$issue->getKey(),
220
				print_r($raw_create_issue_result['errors'], true)
221
			));
222
		}
223
224
		if ( $link_direction === self::LINK_DIRECTION_INWARD ) {
225
			$issue_link_result = $this->jiraApi->api(
226
				JiraApi::REQUEST_POST,
227
				'/rest/api/2/issueLink',
228
				array(
229
					'type' => array('name' => $link_name),
230
					'inwardIssue' => array('key' => $raw_create_issue_result['key']),
231
					'outwardIssue' => array('key' => $issue->getKey()),
232
				)
233
			);
234
		}
235
		elseif ( $link_direction === self::LINK_DIRECTION_OUTWARD ) {
236
			$issue_link_result = $this->jiraApi->api(
237
				JiraApi::REQUEST_POST,
238
				'/rest/api/2/issueLink',
239
				array(
240
					'type' => array('name' => $link_name),
241
					'inwardIssue' => array('key' => $issue->getKey()),
242
					'outwardIssue' => array('key' => $raw_create_issue_result['key']),
243
				)
244
			);
245
		}
246
		else {
247
			throw new \InvalidArgumentException('The "' . $link_direction . '" link direction isn\'t valid.');
248
		}
249
250
		return $raw_create_issue_result['key'];
251
	}
252
253
	/**
254
	 * Determines if link was already processed.
255
	 *
256
	 * @param Issue $issue        Issue.
257
	 * @param Issue $linked_issue Linked issue.
258
	 *
259
	 * @return boolean
260
	 */
261
	protected function isAlreadyProcessed(Issue $issue, Issue $linked_issue)
262
	{
263
		return false;
264
	}
265
266
	/**
267
	 * Determines if link is accepted.
268
	 *
269
	 * @param Issue $issue        Issue.
270
	 * @param Issue $linked_issue Linked issue.
271
	 *
272
	 * @return boolean
273
	 */
274
	protected function isLinkAccepted(Issue $issue, Issue $linked_issue)
275
	{
276
		return true;
277
	}
278
279
	/**
280
	 * Returns ID of "Changelog Entry" issue type.
281
	 *
282
	 * @return integer
283
	 * @throws \LogicException When "Changelog Entry" issue type wasn't found.
284
	 */
285
	protected function getChangelogEntryIssueTypeId()
286
	{
287
		static $issue_type_id;
288
289
		if ( !isset($issue_type_id) ) {
290
			foreach ( $this->jiraApi->getIssueTypes() as $issue_type ) {
291
				if ( $issue_type->getName() === 'Changelog Entry' ) {
292
					$issue_type_id = $issue_type->getId();
293
					break;
294
				}
295
			}
296
297
			if ( !isset($issue_type_id) ) {
298
				throw new \LogicException('The "Changelog Entry" issue type not found.');
299
			}
300
		}
301
302
		return $issue_type_id;
303
	}
304
305
	/**
306
	 * Returns custom field value.
307
	 *
308
	 * @param Issue  $issue           Issue.
309
	 * @param string $custom_field_id Custom field ID.
310
	 *
311
	 * @return mixed
312
	 */
313
	protected function getIssueCustomField(Issue $issue, $custom_field_id)
314
	{
315
		$custom_field_data = $issue->get($custom_field_id);
316
317
		if ( is_array($custom_field_data) ) {
318
			return array('value' => $custom_field_data['value']);
319
		}
320
321
		return $custom_field_data;
322
	}
323
324
	/**
325
	 * Returns issue status name.
326
	 *
327
	 * @param Issue $issue Issue.
328
	 *
329
	 * @return string
330
	 */
331
	public function getIssueStatusName(Issue $issue)
332
	{
333
		$status = $issue->get('status');
334
335
		return $status['name'];
336
	}
337
338
}
339