Passed
Push — master ( 69a302...297af8 )
by
unknown
01:35 queued 12s
created

JiraProject::buildSubmittedIssue()   F

Complexity

Conditions 12
Paths 385

Size

Total Lines 53
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 14.7316

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 12
eloc 30
c 2
b 0
f 0
nc 385
nop 2
dl 0
loc 53
ccs 22
cts 30
cp 0.7332
crap 14.7316
rs 3.8208

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
declare(strict_types=1);
3
4
/**
5
 * JiraProjectReader
6
 */
7
8
namespace Fr3nch13\Jira\Lib;
9
10
use Cake\Core\Configure;
11
use Fr3nch13\Jira\Exception\Exception;
12
use Fr3nch13\Jira\Exception\IssueSubmissionException;
13
use Fr3nch13\Jira\Exception\MissingAllowedTypeException;
14
use Fr3nch13\Jira\Exception\MissingConfigException;
15
use Fr3nch13\Jira\Exception\MissingIssueException;
16
use Fr3nch13\Jira\Exception\MissingIssueFieldException;
17
use Fr3nch13\Jira\Exception\MissingProjectException;
18
use JiraRestApi\Configuration\ArrayConfiguration;
19
use JiraRestApi\Issue\Issue;
20
use JiraRestApi\Issue\IssueField;
21
use JiraRestApi\Issue\IssueService;
22
use JiraRestApi\Issue\JqlQuery;
23
use JiraRestApi\JiraException;
24
use JiraRestApi\Project\ProjectService;
25
26
/**
27
 * Jira Project class
28
 */
29
class JiraProject
30
{
31
    /**
32
     * @var \JiraRestApi\Configuration\ArrayConfiguration Config Object.
33
     */
34
    public $ConfigObj;
35
36
    /**
37
     * @var string|null The key for the project.
38
     */
39
    public $projectKey = null;
40
41
    /**
42
     * @var \JiraRestApi\Project\ProjectService The project service object.
43
     */
44
    public $ProjectService;
45
46
    /**
47
     * @var \JiraRestApi\Project\Project The project object.
48
     */
49
    protected $Project;
50
51
    /**
52
     * @var array<\JiraRestApi\Issue\Version> The list of a Project's Versions.
53
     */
54
    protected $Versions;
55
56
    /**
57
     * @var \JiraRestApi\Issue\IssueService The project service object.
58
     */
59
    public $IssueService;
60
61
    /**
62
     * @var array The Cached list of issues.
63
     */
64
    protected $Issues = [];
65
66
    /**
67
     * @var array The cached list of returned issue info from the below getIssue() method.
68
     */
69
    protected $issuesCache = [];
70
71
    /**
72
     * Valid Types.
73
     * Used to ensure we're getting a valid type when filtering.
74
     * Currently only support Jira Core and Software.
75
     *
76
     * @see https://confluence.atlassian.com/adminjiracloud/issue-types-844500742.html
77
     * @var array
78
     */
79
    protected $validTypes = [
80
        'Bug',
81
        'Epic',
82
        'Story',
83
        'Subtask',
84
        'Task',
85
    ];
86
87
    /**
88
     * @var array Types of issues allowed to be submitted.
89
     */
90
    protected $allowedTypes = [
91
        'Task' => [
92
            'jiraType' => 'Task', // Must be one of the types in the $this->validTypes.
93
            'jiraLabels' => 'task-submitted', // The label used to tag user submitted bugs.
94
            // The form's field information.
95
            'formData' => [
96
                'fields' => [
97
                    'summary' => [
98
                        'type' => 'text',
99
                        'required' => true,
100
                    ],
101
                    'details' => [
102
                        'type' => 'textarea',
103
                        'required' => true,
104
                    ],
105
                ],
106
            ],
107
        ],
108
        'Bug' => [
109
            'jiraType' => 'Bug', // Must be one of the types in the $this->validTypes.
110
            'jiraLabels' => 'bug-submitted', // The label used to tag user submitted bugs.
111
            // The form's field information.
112
            'formData' => [
113
                'fields' => [
114
                    'summary' => [
115
                        'type' => 'text',
116
                        'required' => true,
117
                    ],
118
                    'details' => [
119
                        'type' => 'textarea',
120
                        'required' => true,
121
                    ],
122
                ],
123
            ],
124
        ],
125
        'FeatureRequest' => [
126
            'jiraType' => 'Story', // Must be one of the types in the $this->validTypes.
127
            'jiraLabels' => 'feature-request', // The label used to tag feature requests.
128
            // The form's field information.
129
            'formData' => [
130
                'fields' => [
131
                    'summary' => [
132
                        'type' => 'text',
133
                        'required' => true,
134
                    ],
135
                    'details' => [
136
                        'type' => 'textarea',
137
                        'required' => true,
138
                    ],
139
                ],
140
            ],
141
        ],
142
    ];
143
144
    /**
145
     * This is here for the Form object (or any other object) to use.
146
     * It tacks all errors, even if an exception is thrown.
147
     *
148
     * @var array
149
     */
150
    protected $errors = [];
151
152
    /**
153
     * Constructor
154
     *
155
     * Reads the configuration, and crdate a config object to be passed to the other objects.
156
     *
157
     * @throws \Fr3nch13\Jira\Exception\MissingProjectException When the project can't be found.
158
     * @return void
159
     */
160 28
    public function __construct()
161
    {
162 28
        $this->configure();
163
164
        // setup the objects
165 28
        $this->ProjectService = new ProjectService($this->ConfigObj);
166
        try {
167 28
            $this->Project = $this->ProjectService->get($this->projectKey);
168
        } catch (JiraException $e) {
169
            $this->setError($this->projectKey, 'MissingProjectException');
170
            throw new MissingProjectException($this->projectKey);
171
        }
172
173 28
        $this->Versions = (array)$this->ProjectService->getVersions($this->projectKey);
174 28
        $this->IssueService = new IssueService($this->ConfigObj);
175 28
    }
176
177
    /**
178
     * Configures the object.
179
     * Broken out of construct.
180
     *
181
     * @throws \Fr3nch13\Jira\Exception\MissingConfigException When a config setting isn't set.
182
     * @return void
183
     */
184 28
    public function configure(): void
185
    {
186 28
        $schema = Configure::read('Jira.schema');
187 28
        if (!$schema) {
188
            $this->setError('schema', 'MissingConfigException');
189
            throw new MissingConfigException('schema');
190
        }
191 28
        $host = Configure::read('Jira.host');
192 28
        if (!$host) {
193
            $this->setError('host', 'MissingConfigException');
194
            throw new MissingConfigException('host');
195
        }
196 28
        $username = Configure::read('Jira.username');
197 28
        if (!$username) {
198
            $this->setError('username', 'MissingConfigException');
199
            throw new MissingConfigException('username');
200
        }
201 28
        $apiKey = Configure::read('Jira.apiKey');
202 28
        if (!$apiKey) {
203
            $this->setError('apiKey', 'MissingConfigException');
204
            throw new MissingConfigException('apiKey');
205
        }
206 28
        $projectKey = Configure::read('Jira.projectKey');
207 28
        if (!$projectKey) {
208
            $this->setError('projectKey', 'MissingConfigException');
209
            throw new MissingConfigException('projectKey');
210
        }
211 28
        $this->ConfigObj = new ArrayConfiguration([
212 28
            'jiraHost' => $schema . '://' . $host,
213 28
            'jiraUser' => $username,
214 28
            'jiraPassword' => $apiKey,
215
        ]);
216
217 28
        $this->projectKey = $projectKey;
218 28
    }
219
220
    /**
221
     * Get the Project's Info.
222
     *
223
     * @return \JiraRestApi\Project\Project The information about the project.
224
     * @throws \Fr3nch13\Jira\Exception\MissingProjectException If the project can't be found.
225
     */
226 4
    public function getInfo(): \JiraRestApi\Project\Project
227
    {
228 4
        return $this->Project;
229
    }
230
231
    /**
232
     * Get the Project's Versions.
233
     *
234
     * @return array<\JiraRestApi\Issue\Version> A list of version objects.
235
     */
236 2
    public function getVersions(): array
237
    {
238 2
        return $this->Versions;
239
    }
240
241
    /**
242
     * Get the Project's Issues.
243
     *
244
     * @param string|null $type Filter the Issues by type.
245
     * @return \JiraRestApi\Issue\IssueSearchResult|\JiraRestApi\Issue\IssueSearchResultV3 A list of issue objects.
246
     */
247 4
    public function getIssues(?string $type = null): \JiraRestApi\Issue\IssueSearchResult
248
    {
249 4
        $cacheKey = 'all';
250 4
        if ($type) {
251 2
            $cacheKey .= '-' . $type;
252
        }
253 4
        if (!isset($this->Issues[$cacheKey])) {
254 4
            $jql = new JqlQuery();
255
256 4
            $jql->setProject($this->projectKey);
257 4
            if ($type && in_array($type, $this->validTypes)) {
258 2
                $jql->setType($type);
259
            }
260 4
            $jql->addAnyExpression('ORDER BY key DESC');
261
262 4
            $this->Issues[$cacheKey] = $this->IssueService->search($jql->getQuery(), 0, 1000);
263
        }
264
265 4
        return $this->Issues[$cacheKey];
266
    }
267
268
    /**
269
     * Get the Project's Open Issues.
270
     *
271
     * @param string|null $type Filter the Issues by type.
272
     * @return \JiraRestApi\Issue\IssueSearchResult|\JiraRestApi\Issue\IssueSearchResultV3 A list of issue objects.
273
     */
274 4
    public function getOpenIssues(?string $type = null): \JiraRestApi\Issue\IssueSearchResult
275
    {
276 4
        $cacheKey = 'open';
277 4
        if ($type) {
278 2
            $cacheKey .= '-' . $type;
279
        }
280 4
        if (!isset($this->Issues[$cacheKey])) {
281 4
            $jql = new JqlQuery();
282
283 4
            $jql->setProject($this->projectKey);
284 4
            if ($type && in_array($type, $this->validTypes)) {
285 2
                $jql->setType($type);
286
            }
287 4
            $jql->addAnyExpression('AND resolution is EMPTY');
288 4
            $jql->addAnyExpression('ORDER BY key DESC');
289
290 4
            $this->Issues[$cacheKey] = $this->IssueService->search($jql->getQuery(), 0, 1000);
291
        }
292
293 4
        return $this->Issues[$cacheKey];
294
    }
295
296
    /**
297
     * Gets info on a particular issue within your project.
298
     *
299
     * @param int|null $id The issue id. The integer part without the project key.
300
     * @return \JiraRestApi\Issue\Issue|\JiraRestApi\Issue\IssueV3 the object that has the info of that issue.
301
     * @throws \Fr3nch13\Jira\Exception\Exception If the issue's id isn't given.
302
     * @throws \Fr3nch13\Jira\Exception\MissingIssueException If the project's issue can't be found.
303
     */
304 3
    public function getIssue(?int $id = null): \JiraRestApi\Issue\Issue
305
    {
306 3
        if (!is_int($id)) {
307
            $this->setError(__('Missing the Issue\'s ID.'), 'Exception');
308
            throw new Exception(__('Missing the Issue\'s ID.'));
309
        }
310 3
        $key = $this->projectKey . '-' . $id;
311 3
        if (!isset($this->issuesCache[$key])) {
312
            try {
313 3
                $this->issuesCache[$key] = $this->IssueService->get($key);
314 1
            } catch (JiraException $e) {
315 1
                $this->setError($this->projectKey, 'MissingIssueException');
316 1
                throw new MissingIssueException($key);
317
            }
318
        }
319
320 2
        return $this->issuesCache[$key];
321
    }
322
323
    /**
324
     * Gets a list of issues that are considered bugs.
325
     *
326
     * @return \JiraRestApi\Issue\IssueSearchResult|\JiraRestApi\Issue\IssueSearchResultV3 A list of issue objects.
327
     */
328 2
    public function getBugs(): \JiraRestApi\Issue\IssueSearchResult
329
    {
330 2
        return $this->getIssues('Bug');
331
    }
332
333
    /**
334
     * Gets a list of open issues that are considered bugs.
335
     *
336
     * @return \JiraRestApi\Issue\IssueSearchResult|\JiraRestApi\Issue\IssueSearchResultV3 A list of issue objects.
337
     */
338 2
    public function getOpenBugs(): \JiraRestApi\Issue\IssueSearchResult
339
    {
340 2
        return $this->getOpenIssues('Bug');
341
    }
342
343
    /**
344
     * Methods used to submit an Issue to Jira.
345
     */
346
347
    /**
348
     * Returns the allowed types and their settings
349
     *
350
     * @param string|null $type The type of issue you want to get.
351
     * @throws \Fr3nch13\Jira\Exception\MissingAllowedTypeException If a type is given, and that type is not configured.
352
     * @return array the content of $this->allowedTypes.
353
     */
354 12
    public function getAllowedTypes(?string $type = null): array
355
    {
356 12
        if ($type) {
357 2
            if (!isset($this->allowedTypes[$type])) {
358
                $this->setError($type, 'MissingAllowedTypeException');
359
                throw new MissingAllowedTypeException($type);
360
            }
361
362 2
            return $this->allowedTypes[$type];
363
        }
364
365 11
        return $this->allowedTypes;
366
    }
367
368
    /**
369
     * Allows you to modify the form allowdTypes to fir your situation.
370
     *
371
     * @param string $type The type of issue you want to add/modify.
372
     * @param array $settings The settings for the type.
373
     * @throws \Fr3nch13\Jira\Exception\MissingIssueFieldException If we're adding a new issue type, and the summary field isn't defined.
374
     * @return void
375
     */
376 5
    public function modifyAllowedTypes(string $type, array $settings = []): void
377
    {
378 5
        if (!isset($this->allowedTypes[$type])) {
379 5
            $this->allowedTypes[$type] = [];
380 5
            if (!isset($settings['jiraType'])) {
381
                $this->setError('jiraType', 'MissingIssueFieldException');
382
                throw new MissingIssueFieldException('jiraType');
383
            }
384 5
            if (!isset($settings['formData'])) {
385
                $this->setError('formData', 'MissingIssueFieldException');
386
                throw new MissingIssueFieldException('formData');
387
            }
388 5
            if (!isset($settings['formData']['fields'])) {
389
                $this->setError('formData.fields', 'MissingIssueFieldException');
390
                throw new MissingIssueFieldException('formData.fields');
391
            }
392 5
            if (!isset($settings['formData']['fields']['summary'])) {
393
                $this->setError('formData.fields.summary', 'MissingIssueFieldException');
394
                throw new MissingIssueFieldException('formData.fields.summary');
395
            }
396
        }
397
398 5
        $this->allowedTypes[$type] += $settings;
399 5
    }
400
401
    /**
402
     * Checks to see if a type is allowed.
403
     *
404
     * @param string $type The type to check.
405
     * @return bool if it's allowed or not.
406
     */
407 12
    public function isAllowedType(string $type): bool
408
    {
409 12
        return isset($this->allowedTypes[$type]) ? true : false;
410
    }
411
412
    /**
413
     * Gets the array for the forms when submitting an issue to Jira.
414
     *
415
     * @param string|null $type The type of issue we're submitting.
416
     * @throws \Fr3nch13\Jira\Exception\MissingAllowedTypeException If that type is not configured.
417
     * @throws \Fr3nch13\Jira\Exception\Exception If the form data for that type is missing.
418
     * @return array The array of data to fill in the form with.
419
     */
420 10
    public function getFormData(?string $type = null): array
421
    {
422 10
        if (!$type) {
423
            $this->setError('[$type is not set]', 'MissingAllowedTypeException');
424
            throw new MissingAllowedTypeException('[$type is not set]');
425
        }
426
427 10
        if (!$this->isAllowedType($type)) {
428
            $this->setError($type, 'MissingAllowedTypeException');
429
            throw new MissingAllowedTypeException($type);
430
        }
431
432 10
        $allowedTypes = $this->getAllowedTypes();
433
434 10
        if (!isset($allowedTypes[$type]['formData'])) {
435
            $this->setError('No form data is set.', 'Exception');
436
            throw new Exception(__('No form data is set.'));
437
        }
438
439 10
        return $allowedTypes[$type]['formData'];
440
    }
441
442
    /**
443
     * Sets the formData variable if you want to modify the default/initial values.
444
     *
445
     * @param string $type The type you want to set the data for.
446
     *  - Needs to be in the allowedTypes already.
447
     * @param array $data The definition of the allowed types
448
     * @throws \Fr3nch13\Jira\Exception\MissingAllowedTypeException If that type is not configured.
449
     * @return void
450
     */
451 9
    public function setFormData(string $type, array $data = []): void
452
    {
453 9
        if (!$type) {
454
            $this->setError('[$type is not set]', 'MissingAllowedTypeException');
455
            throw new MissingAllowedTypeException('[$type is not set]');
456
        }
457
458 9
        if (!$this->isAllowedType($type)) {
459
            $this->setError($type, 'MissingAllowedTypeException');
460
            throw new MissingAllowedTypeException($type);
461
        }
462
463 9
        $this->allowedTypes[$type]['formData'] = $data;
464 9
    }
465
466
    /**
467
     * Submits the Issue
468
     *
469
     * @param string $type The type you want to set the data for.
470
     *  - Needs to be in the allowedTypes already.
471
     * @param array $data The array of details about the issue.
472
     * @throws \Fr3nch13\Jira\Exception\IssueSubmissionException If submitting the issue fails.
473
     * @throws \Fr3nch13\Jira\Exception\MissingAllowedTypeException If that issue type is not configured.
474
     * @throws \Fr3nch13\Jira\Exception\MissingIssueFieldException If we're adding a new issue, and required fields aren't defined.
475
     * @return int > 0 If the request was successfully submitted.
476
     */
477 2
    public function submitIssue(string $type, array $data = []): int
478
    {
479 2
        if (!$type) {
480
            $this->setError('[$type is not set]', 'MissingAllowedTypeException');
481
            throw new MissingAllowedTypeException('[$type is not set]');
482
        }
483
484 2
        if (!$this->isAllowedType($type)) {
485
            $this->setError($type, 'MissingAllowedTypeException');
486
            throw new MissingAllowedTypeException($type);
487
        }
488
489 2
        if (!isset($data['summary'])) {
490
            $this->setError('summary', 'MissingIssueFieldException');
491
            throw new MissingIssueFieldException('summary');
492
        }
493
494 2
        $issueField = $this->buildSubmittedIssue($type, $data);
495
496 2
        $issueService = new IssueService($this->ConfigObj);
497
498
        try {
499 2
            $ret = $issueService->create($issueField);
500
        } catch (JiraException $e) {
501
            //Sample return error with json in it.
502
            //Pasting here so I can mock this return message in the unit tests.
503
            //CURL HTTP Request Failed: Status Code : 400, URL:https://[hostname]/rest/api/2/issue
504
            //Error Message : {"errorMessages":[],"errors":{"user_type":"Field 'user_type' cannot be set. It is not on the appropriate screen, or unknown."}}             */
505
            $msg = $e->getMessage();
506
            if (strpos($msg, '{') !== false) {
507
                $msgArray = str_split($msg);
508
                // extract the json message.
509
                $json = '';
510
                $in = 0;
511
                foreach ($msgArray as $i => $char) {
512
                    if ($char == '{') {
513
                        $in++;
514
                    }
515
                    if ($in) {
516
                        $json .= $msg[$i];
517
                    }
518
                    if ($char == '}') {
519
                        $in--;
520
                    }
521
                }
522
                if ($json) {
523
                    $json = json_decode($json, true);
524
                }
525
                if ($json) {
526
                    $newMsg = [];
527
                    if (isset($json['errorMessages'])) {
528
                        foreach ($json['errorMessages'] as $jsonMsg) {
529
                            $newMsg[] = $jsonMsg;
530
                        }
531
                        foreach ($json['errors'] as $jsonMsg) {
532
                            $newMsg[] = $jsonMsg;
533
                        }
534
                        $msg = implode("\n", $newMsg);
535
                    }
536
                }
537
            }
538
            $this->setError($msg, 'IssueSubmissionException');
539
            throw new IssueSubmissionException($msg);
540
        }
541
542 2
        if ($ret instanceof Issue && $ret->id) {
543 2
            return (int)$ret->id;
544
        }
545
546
        return 0;
547
    }
548
549
    /**
550
     * Creates the issue to send to the server.
551
     *
552
     * @param string $type The type of isse we're creating.
553
     * @param array $data The data from the submitted form.
554
     * @throws \Fr3nch13\Jira\Exception\MissingProjectException If submitting the issue fails.
555
     * @return \JiraRestApi\Issue\IssueField
556
     */
557 2
    public function buildSubmittedIssue(string $type, array $data = []): \JiraRestApi\Issue\IssueField
558
    {
559 2
        $typeInfo = $this->getAllowedTypes($type);
560
561
        // make sure we can get the project info first.
562
        // getInfo will throw an exception if it can't find the project.
563
        // putting a try/catch around it so scrutinizer stops complaining.
564
        try {
565 2
            $project = $this->getInfo();
566
        } catch (MissingProjectException $e) {
567
            $this->setError($this->projectKey, 'MissingProjectException');
568
            throw $e;
569
        }
570
571 2
        $issueField = new IssueField();
572 2
        $issueField->setProjectKey($this->projectKey)
573 2
            ->setIssueType($typeInfo['jiraType']);
574
575 2
        if (isset($data['summary'])) {
576 2
            $issueField->setSummary($data['summary']);
577
        }
578 2
        if (isset($data['description'])) {
579 1
            $issueField->setDescription($data['description']);
580
        }
581 2
        if (isset($data['priority'])) {
582
            $issueField->setPriorityName($data['priority']);
583
        }
584 2
        if (isset($data['assignee'])) {
585
            $issueField->setPriorityName($data['assignee']);
586
        }
587 2
        if (isset($data['version'])) {
588
            $issueField->addVersion($data['version']);
589
        }
590 2
        if (isset($data['components'])) {
591
            $issueField->addComponents($data['components']);
592
        }
593 2
        if (isset($data['duedate'])) {
594
            $issueField->setDueDate($data['duedate']);
595
        }
596
597
        // labels should be space seperated
598 2
        if (isset($typeInfo['jiraLabels'])) {
599 2
            if (is_string($typeInfo['jiraLabels'])) {
600 2
                $typeInfo['jiraLabels'] = preg_split('/\s+/', $typeInfo['jiraLabels']);
601
            }
602
            // track the type with a label
603 2
            $typeInfo['jiraLabels'][] = 'user-submitted-type-' . $type;
604 2
            foreach ($typeInfo['jiraLabels'] as $jiralabel) {
605 2
                $issueField->addLabel($jiralabel);
606
            }
607
        }
608
609 2
        return $issueField;
610
    }
611
612
    /**
613
     * Sets an error
614
     *
615
     * @param string $msg The error message.
616
     * @param string $key The key to use in the this->errors array.
617
     * @return bool If saved or not.
618
     */
619 1
    public function setError(string $msg = '', string $key = ''): bool
620
    {
621 1
        if (!trim($msg)) {
622
            return false;
623
        }
624 1
        if ($key) {
625 1
            $this->errors[$key] = $msg;
626
        } else {
627
            $this->errors[] = $msg;
628
        }
629
630 1
        return true;
631
    }
632
633
    /**
634
     * Gets the accumulated error messages.
635
     * If a key is given, return that specific message. If that key doesn't exist, return false.
636
     *
637
     * @param string|null $key The key to the specific message to get.
638
     * @return array|string|false
639
     */
640
    public function getErrors(?string $key = null)
641
    {
642
        if ($key) {
643
            if (isset($this->errors[$key])) {
644
                return $this->errors[$key];
645
            } else {
646
                return false;
647
            }
648
        }
649
650
        return $this->errors;
651
    }
652
}
653