Failed Conditions
Push — master ( 117895...2cd9b5 )
by Alexander
03:10
created

IssueCloner   B

Complexity

Total Complexity 36

Size/Duplication

Total Lines 319
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 0%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 36
lcom 1
cbo 4
dl 0
loc 319
ccs 0
cts 158
cp 0
rs 8.8
c 1
b 0
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A getIssues() 0 21 4
A _buildCustomFieldsMap() 0 8 3
A _getQueryFields() 0 12 3
C _getLinkedIssue() 0 31 8
B createLinkedIssue() 0 63 7
A isAlreadyProcessed() 0 4 1
A isLinkAccepted() 0 4 1
B getChangelogEntryIssueTypeId() 0 19 5
A getIssueCustomField() 0 10 2
A getIssueStatusName() 0 6 1
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\Api;
15
use chobie\Jira\Issue;
16
use chobie\Jira\Issues\Walker;
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 Api
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 Api $jira_api Jira REST client.
59
	 */
60
	public function __construct(Api $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 string  $link_project_key Link project key.
74
	 *
75
	 * @return array
76
	 */
77
	public function getIssues($jql, $link_name, $link_direction, $link_project_key)
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
			$linked_issue = $this->_getLinkedIssue($issue, $link_name, $link_direction, $link_project_key);
88
89
			if ( is_object($linked_issue) && $this->isAlreadyProcessed($issue, $linked_issue) ) {
90
				continue;
91
			}
92
93
			$ret[] = array($issue, $linked_issue);
94
		}
95
96
		return $ret;
97
	}
98
99
	/**
100
	 * Builds custom field map.
101
	 *
102
	 * @return void
103
	 */
104
	private function _buildCustomFieldsMap()
105
	{
106
		foreach ( $this->jiraApi->getFields() as $field_key => $field_data ) {
107
			if ( substr($field_key, 0, 12) === 'customfield_' ) {
108
				$this->_customFieldsMap[$field_data['name']] = $field_key;
109
			}
110
		}
111
	}
112
113
	/**
114
	 * Returns query fields.
115
	 *
116
	 * @return array
117
	 */
118
	private function _getQueryFields()
119
	{
120
		$ret = $this->queryFields;
121
122
		foreach ( $this->_copyCustomFields as $custom_field ) {
123
			if ( isset($this->_customFieldsMap[$custom_field]) ) {
124
				$ret[] = $this->_customFieldsMap[$custom_field];
125
			}
126
		}
127
128
		return $ret;
129
	}
130
131
	/**
132
	 * Returns issue, which backports given issue.
133
	 *
134
	 * @param Issue   $issue            Issue.
135
	 * @param string  $link_name        Link name.
136
	 * @param integer $link_direction   Link direction.
137
	 * @param string  $link_project_key Link project key.
138
	 *
139
	 * @return Issue|null
140
	 * @throws \InvalidArgumentException When link direction isn't valid.
141
	 */
142
	private function _getLinkedIssue(Issue $issue, $link_name, $link_direction, $link_project_key)
143
	{
144
		foreach ( $issue->get('issuelinks') as $issue_link ) {
145
			if ( $issue_link['type']['name'] !== $link_name ) {
146
				continue;
147
			}
148
149
			if ( $link_direction === self::LINK_DIRECTION_INWARD ) {
150
				$check_key = 'inwardIssue';
151
			}
152
			elseif ( $link_direction === self::LINK_DIRECTION_OUTWARD ) {
153
				$check_key = 'outwardIssue';
154
			}
155
			else {
156
				throw new \InvalidArgumentException('The "' . $link_direction . '" link direction isn\'t valid.');
157
			}
158
159
			if ( array_key_exists($check_key, $issue_link) ) {
160
				$linked_issue = new Issue($issue_link[$check_key]);
161
				$linked_issue_data = $this->jiraApi->getIssue($linked_issue->getKey(), 'project')->getResult();
162
163
				if ( $this->isLinkAccepted($issue, $linked_issue)
164
					&& $linked_issue_data['fields']['project']['key'] === $link_project_key
165
				) {
166
					return $linked_issue;
167
				}
168
			}
169
		}
170
171
		return null;
172
	}
173
174
	/**
175
	 * Creates backports issues.
176
	 *
177
	 * @param Issue   $issue          Issue.
178
	 * @param string  $project_key    Project key.
179
	 * @param string  $link_name      Link name.
180
	 * @param integer $link_direction Link direction.
181
	 * @param array   $component_ids  Component IDs.
182
	 *
183
	 * @return string
184
	 * @throws \RuntimeException When failed to create an issue.
185
	 * @throws \InvalidArgumentException When link direction isn't valid.
186
	 */
187
	public function createLinkedIssue(Issue $issue, $project_key, $link_name, $link_direction, array $component_ids)
188
	{
189
		$create_fields = array(
190
			'description' => 'See ' . $issue->getKey() . '.',
191
			'components' => array(),
192
		);
193
194
		foreach ( $this->_copyCustomFields as $custom_field ) {
195
			if ( isset($this->_customFieldsMap[$custom_field]) ) {
196
				$custom_field_id = $this->_customFieldsMap[$custom_field];
197
				$create_fields[$custom_field_id] = $this->getIssueCustomField($issue, $custom_field_id);
198
			}
199
		}
200
201
		foreach ( $component_ids as $component_id ) {
202
			$create_fields['components'][] = array('id' => (string)$component_id);
203
		}
204
205
		$create_issue_result = $this->jiraApi->createIssue(
206
			$project_key,
207
			$issue->get('summary'),
208
			$this->getChangelogEntryIssueTypeId(),
209
			$create_fields
210
		);
211
212
		$raw_create_issue_result = $create_issue_result->getResult();
213
214
		if ( array_key_exists('errors', $raw_create_issue_result) ) {
215
			throw new \RuntimeException(sprintf(
216
				'Failed to create linked issue for "%s" issue. Errors: ' . PHP_EOL . '%s',
217
				$issue->getKey(),
218
				print_r($raw_create_issue_result['errors'], true)
219
			));
220
		}
221
222
		if ( $link_direction === self::LINK_DIRECTION_INWARD ) {
223
			$issue_link_result = $this->jiraApi->api(
224
				Api::REQUEST_POST,
225
				'/rest/api/2/issueLink',
226
				array(
227
					'type' => array('name' => $link_name),
228
					'inwardIssue' => array('key' => $raw_create_issue_result['key']),
229
					'outwardIssue' => array('key' => $issue->getKey()),
230
				)
231
			);
232
		}
233
		elseif ( $link_direction === self::LINK_DIRECTION_OUTWARD ) {
234
			$issue_link_result = $this->jiraApi->api(
235
				Api::REQUEST_POST,
236
				'/rest/api/2/issueLink',
237
				array(
238
					'type' => array('name' => $link_name),
239
					'inwardIssue' => array('key' => $issue->getKey()),
240
					'outwardIssue' => array('key' => $raw_create_issue_result['key']),
241
				)
242
			);
243
		}
244
		else {
245
			throw new \InvalidArgumentException('The "' . $link_direction . '" link direction isn\'t valid.');
246
		}
247
248
		return $raw_create_issue_result['key'];
249
	}
250
251
	/**
252
	 * Determines if link was already processed.
253
	 *
254
	 * @param Issue $issue        Issue.
255
	 * @param Issue $linked_issue Linked issue.
256
	 *
257
	 * @return boolean
258
	 */
259
	protected function isAlreadyProcessed(Issue $issue, Issue $linked_issue)
260
	{
261
		return false;
262
	}
263
264
	/**
265
	 * Determines if link is accepted.
266
	 *
267
	 * @param Issue $issue        Issue.
268
	 * @param Issue $linked_issue Linked issue.
269
	 *
270
	 * @return boolean
271
	 */
272
	protected function isLinkAccepted(Issue $issue, Issue $linked_issue)
273
	{
274
		return true;
275
	}
276
277
	/**
278
	 * Returns ID of "Changelog Entry" issue type.
279
	 *
280
	 * @return integer
281
	 * @throws \LogicException When "Changelog Entry" issue type wasn't found.
282
	 */
283
	protected function getChangelogEntryIssueTypeId()
284
	{
285
		static $issue_type_id;
286
287
		if ( !isset($issue_type_id) ) {
288
			foreach ( $this->jiraApi->getIssueTypes() as $issue_type ) {
289
				if ( $issue_type->getName() === 'Changelog Entry' ) {
290
					$issue_type_id = $issue_type->getId();
291
					break;
292
				}
293
			}
294
295
			if ( !isset($issue_type_id) ) {
296
				throw new \LogicException('The "Changelog Entry" issue type not found.');
297
			}
298
		}
299
300
		return $issue_type_id;
301
	}
302
303
	/**
304
	 * Returns custom field value.
305
	 *
306
	 * @param Issue  $issue           Issue.
307
	 * @param string $custom_field_id Custom field ID.
308
	 *
309
	 * @return mixed
310
	 */
311
	protected function getIssueCustomField(Issue $issue, $custom_field_id)
312
	{
313
		$custom_field_data = $issue->get($custom_field_id);
314
315
		if ( is_array($custom_field_data) ) {
316
			return array('value' => $custom_field_data['value']);
317
		}
318
319
		return $custom_field_data;
320
	}
321
322
	/**
323
	 * Returns issue status name.
324
	 *
325
	 * @param Issue $issue Issue.
326
	 *
327
	 * @return string
328
	 */
329
	public function getIssueStatusName(Issue $issue)
330
	{
331
		$status = $issue->get('status');
332
333
		return $status['name'];
334
	}
335
336
}
337