Failed Conditions
Push — master ( d4f278...5433f0 )
by Alexander
03:10
created

BackportCommand::configure()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 17
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 17
ccs 0
cts 17
cp 0
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 13
nc 1
nop 0
crap 2
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\Command;
12
13
14
use chobie\Jira\Api;
15
use chobie\Jira\Issue;
16
use chobie\Jira\Issues\Walker;
17
use ConsoleHelpers\ConsoleKit\Exception\CommandException;
18
use Symfony\Component\Console\Helper\Table;
19
use Symfony\Component\Console\Input\InputArgument;
20
use Symfony\Component\Console\Input\InputInterface;
21
use Symfony\Component\Console\Input\InputOption;
22
use Symfony\Component\Console\Output\OutputInterface;
23
24
class BackportCommand extends AbstractCommand
25
{
26
27
	const ISSUE_LINK_NAME = 'Backports';
28
29
	/**
30
	 * Specifies custom fields to copy during backporting.
31
	 *
32
	 * @var array
33
	 */
34
	protected $copyCustomFields = array(
35
		'Change Log Group', 'Change Log Message',
36
	);
37
38
	/**
39
	 * Custom fields map.
40
	 *
41
	 * @var array
42
	 */
43
	protected $customFieldsMap = array();
44
45
	/**
46
	 * {@inheritdoc}
47
	 */
48
	protected function configure()
49
	{
50
		$this
51
			->setName('backport')
52
			->setDescription('Shows/creates backport issues')
53
			->addArgument(
54
				'project_key',
55
				InputArgument::REQUIRED,
56
				'Project key, e.g. <comment>JRA</comment>'
57
			)
58
			->addOption(
59
				'create',
60
				null,
61
				InputOption::VALUE_NONE,
62
				'Creates missing backported issues'
63
			);
64
	}
65
66
	/**
67
	 * {@inheritdoc}
68
	 *
69
	 * @throws CommandException When no backportable issues were found.
70
	 */
71
	protected function execute(InputInterface $input, OutputInterface $output)
72
	{
73
		$this->buildCustomFieldsMap();
74
75
		$this->jiraApi->setOptions(0); // Don't expand fields.
76
		$project_key = $this->io->getArgument('project_key');
77
78
		$backportable_issues = $this->getBackportableIssues($project_key);
79
		$issue_count = count($backportable_issues);
80
81
		if ( !$issue_count ) {
82
			throw new CommandException('No backportable issues found.');
83
		}
84
85
		$this->io->writeln(
86
			'Found <info>' . $issue_count . '</info> backportable issues in <info>' . $project_key . '</info> project.'
87
		);
88
89
		if ( $this->io->getOption('create') ) {
90
			$this->createBackportsIssues($project_key, $backportable_issues);
91
		}
92
		else {
93
			$this->showBackportableIssues($backportable_issues);
94
		}
95
	}
96
97
	/**
98
	 * Builds custom field map.
99
	 *
100
	 * @return void
101
	 */
102
	protected function buildCustomFieldsMap()
103
	{
104
		foreach ( $this->jiraApi->getFields() as $field_key => $field_data ) {
105
			if ( substr($field_key, 0, 12) === 'customfield_' ) {
106
				$this->customFieldsMap[$field_data['name']] = $field_key;
107
			}
108
		}
109
	}
110
111
	/**
112
	 * Shows backportable issues.
113
	 *
114
	 * @param array $backportable_issues Backportable Issues.
115
	 *
116
	 * @return void
117
	 */
118
	protected function showBackportableIssues(array $backportable_issues)
119
	{
120
		$table = new Table($this->io->getOutput());
121
122
		foreach ( $backportable_issues as $issue_pair ) {
123
			/** @var Issue $issue */
124
			$issue = $issue_pair[0];
125
126
			/** @var Issue $backported_by_issue */
127
			$backported_by_issue = $issue_pair[1];
128
129
			$issue_status = $this->getIssueStatusName($issue);
130
			$row_data = array(
131
				$issue->getKey(),
132
				$issue->get('summary'),
133
				$issue_status,
134
			);
135
136
			if ( is_object($backported_by_issue) ) {
137
				$backported_by_issue_status = $this->getIssueStatusName($backported_by_issue);
138
				$row_data[] = $backported_by_issue->getKey();
139
				$row_data[] = $backported_by_issue->get('summary');
140
				$row_data[] = $backported_by_issue_status;
141
			}
142
			else {
143
				$row_data[] = '';
144
				$row_data[] = '';
145
				$row_data[] = '';
146
			}
147
148
			$table->addRow($row_data);
149
		}
150
151
		$table->setHeaders(array(
152
			'From Key',
153
			'From Summary',
154
			'From Status',
155
			'To Key',
156
			'To Summary',
157
			'To Status',
158
		));
159
160
		$table->render();
161
	}
162
163
	/**
164
	 * Creates backports issues.
165
	 *
166
	 * @param string $project_key         Project key.
167
	 * @param array  $backportable_issues Backportable Issues.
168
	 *
169
	 * @return void
170
	 * @throws \LogicException When "Changelog Entry" issue type isn't found.
171
	 * @throws CommandException When failed to create an issue.
172
	 */
173
	protected function createBackportsIssues($project_key, array $backportable_issues)
174
	{
175
		$issue_type_id = $this->getChangelogEntryIssueTypeId();
176
177
		if ( !is_numeric($issue_type_id) ) {
178
			throw new \LogicException('The "Changelog Entry" issue type not found.');
179
		}
180
181
		foreach ( $backportable_issues as $issue_pair ) {
182
			/** @var Issue $issue */
183
			$issue = $issue_pair[0];
184
185
			/** @var Issue $backported_by_issue */
186
			$backported_by_issue = $issue_pair[1];
187
188
			$this->io->write('Processing "' . $issue->getKey() . '" issue ... ');
189
190
			if ( is_object($backported_by_issue) ) {
191
				$this->io->writeln('skipping [already has linked issue].');
192
				continue;
193
			}
194
195
			$create_fields = array(
196
				'description' => 'See ' . $issue->getKey() . '.',
197
				'components' => array(),
198
			);
199
200
			foreach ( $this->copyCustomFields as $custom_field ) {
201
				if ( isset($this->customFieldsMap[$custom_field]) ) {
202
					$custom_field_id = $this->customFieldsMap[$custom_field];
203
					$create_fields[$custom_field_id] = $this->getIssueCustomField($issue, $custom_field_id);
204
				}
205
			}
206
207
			foreach ( $issue->get('components') as $component ) {
208
				$create_fields['components'][] = array('id' => $component['id']);
209
			}
210
211
			$create_issue_result = $this->jiraApi->createIssue(
212
				$project_key,
213
				$issue->get('summary'),
0 ignored issues
show
Documentation introduced by
$issue->get('summary') is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
214
				$issue_type_id,
215
				$create_fields
216
			);
217
218
			$raw_create_issue_result = $create_issue_result->getResult();
219
220
			if ( array_key_exists('errors', $raw_create_issue_result) ) {
221
				throw new CommandException(sprintf(
222
					'Failed to create backported issue for "%s" issue. Errors: ' . PHP_EOL . '%s',
223
					$issue->getKey(),
224
					print_r($raw_create_issue_result['errors'], true)
225
				));
226
			}
227
228
			$issue_link_result = $this->jiraApi->api(
0 ignored issues
show
Unused Code introduced by
$issue_link_result is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
229
				Api::REQUEST_POST,
230
				'/rest/api/2/issueLink',
231
				array(
232
					'type' => array('name' => self::ISSUE_LINK_NAME),
233
					'inwardIssue' => array('key' => $raw_create_issue_result['key']),
234
					'outwardIssue' => array('key' => $issue->getKey()),
235
				)
236
			);
237
238
			$this->io->writeln('linked issue created.');
239
		}
240
	}
241
242
	/**
243
	 * Returns ID of "Changelog Entry" issue type.
244
	 *
245
	 * @return integer|null
246
	 */
247
	protected function getChangelogEntryIssueTypeId()
248
	{
249
		foreach ( $this->jiraApi->getIssueTypes() as $issue_type ) {
250
			if ( $issue_type->getName() === 'Changelog Entry' ) {
251
				return $issue_type->getId();
252
			}
253
		}
254
255
		return null;
256
	}
257
258
	/**
259
	 * Returns custom field value.
260
	 *
261
	 * @param Issue  $issue           Issue.
262
	 * @param string $custom_field_id Custom field ID.
263
	 *
264
	 * @return mixed
265
	 */
266
	protected function getIssueCustomField(Issue $issue, $custom_field_id)
267
	{
268
		$custom_field_data = $issue->get($custom_field_id);
269
270
		if ( is_array($custom_field_data) ) {
271
			return array('value' => $custom_field_data['value']);
272
		}
273
274
		return $custom_field_data;
275
	}
276
277
	/**
278
	 * Returns backportable issues.
279
	 *
280
	 * @param string $project_key Project key.
281
	 *
282
	 * @return array
283
	 */
284
	protected function getBackportableIssues($project_key)
285
	{
286
		$needed_fields = array('summary', 'status', 'components', 'issuelinks');
287
288
		foreach ( $this->copyCustomFields as $custom_field ) {
289
			if ( isset($this->customFieldsMap[$custom_field]) ) {
290
				$needed_fields[] = $this->customFieldsMap[$custom_field];
291
			}
292
		}
293
294
		$walker = new Walker($this->jiraApi);
295
		$walker->push(
296
			'project = ' . $project_key . ' AND labels = backportable',
297
			implode(',', $needed_fields)
298
		);
299
300
		$ret = array();
301
302
		foreach ( $walker as $issue ) {
303
			$backported_by_issue = $this->getBackportedBy($issue);
304
305
			$issue_status = $this->getIssueStatusName($issue);
306
307
			if ( is_object($backported_by_issue) ) {
308
				$backported_by_issue_status = $this->getIssueStatusName($backported_by_issue);
309
310
				// Exclude already processed issues.
311
				if ( $issue_status === 'Resolved' && $backported_by_issue_status === 'Resolved' ) {
312
					continue;
313
				}
314
			}
315
316
			$ret[] = array($issue, $backported_by_issue);
317
		}
318
319
		return $ret;
320
	}
321
322
	/**
323
	 * Returns issue, which backports given issue.
324
	 *
325
	 * @param Issue $issue Issue.
326
	 *
327
	 * @return Issue|null
328
	 */
329
	protected function getBackportedBy(Issue $issue)
330
	{
331
		foreach ( $issue->get('issuelinks') as $issue_link ) {
332
			if ( $issue_link['type']['name'] !== self::ISSUE_LINK_NAME ) {
333
				continue;
334
			}
335
336
			if ( array_key_exists('inwardIssue', $issue_link) ) {
337
				return new Issue($issue_link['inwardIssue']);
338
			}
339
		}
340
341
		return null;
342
	}
343
344
	/**
345
	 * Returns issue status name.
346
	 *
347
	 * @param Issue $issue Issue.
348
	 *
349
	 * @return string
350
	 */
351
	protected function getIssueStatusName(Issue $issue)
352
	{
353
		$status = $issue->get('status');
354
355
		return $status['name'];
356
	}
357
358
}
359