Passed
Push — 1.x-dev ( 2550bc...9e01db )
by Brian
04:33
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
/**
3
 * JiraProjectReader
4
 */
5
6
namespace Fr3nch13\Jira\Lib;
7
8
use Cake\Core\Configure;
9
use Fr3nch13\Jira\Exception\Exception;
10
use Fr3nch13\Jira\Exception\IssueSubmissionException;
11
use Fr3nch13\Jira\Exception\MissingAllowedTypeException;
12
use Fr3nch13\Jira\Exception\MissingConfigException;
13
use Fr3nch13\Jira\Exception\MissingIssueException;
14
use Fr3nch13\Jira\Exception\MissingIssueFieldException;
15
use Fr3nch13\Jira\Exception\MissingProjectException;
16
use JiraRestApi\Configuration\ArrayConfiguration;
17
use JiraRestApi\Issue\IssueField;
18
use JiraRestApi\Issue\IssueService;
19
use JiraRestApi\Issue\JqlQuery;
20
use JiraRestApi\Project\ProjectService;
21
22
/**
23
 * Jira Project class
24
 */
25
class JiraProject
26
{
27
    /**
28
     * Config Object.
29
     * @var \JiraRestApi\Configuration\ArrayConfiguration
30
     */
31
    public $ConfigObj;
32
33
    /**
34
     * The key for the project.
35
     * @var string|null
36
     */
37
    public $projectKey = null;
38
39
    /**
40
     * The project service object.
41
     * @var \JiraRestApi\Project\ProjectService
42
     */
43
    public $ProjectService;
44
45
    /**
46
     * The project object.
47
     * @var \JiraRestApi\Project\Project
48
     */
49
    protected $Project;
50
51
    /**
52
     * The list of a Project's Versions.
53
     * @var array
54
     */
55
    protected $Versions;
56
57
    /**
58
     * The project service object.
59
     * @var \JiraRestApi\Issue\IssueService
60
     */
61
    public $IssueService;
62
63
    /**
64
     * The Cached list of issues.
65
     * @var array
66
     */
67
    protected $Issues = [];
68
69
    /**
70
     * The cached list of returned issue info from the below getIssue() method.
71
     * @var array
72
     */
73
    protected $issuesCache = [];
74
75
    /**
76
     * Valid Types.
77
     * Used to ensure we're getting a valid type when filtering.
78
     * Currently only support Jira Core and Software.
79
     * @see https://confluence.atlassian.com/adminjiracloud/issue-types-844500742.html
80
     * @var array
81
     */
82
    protected $validTypes = [
83
        'Bug',
84
        'Epic',
85
        'Story',
86
        'Subtask',
87
        'Task',
88
    ];
89
90
    /**
91
     * Types of issues allowed to be submitted.
92
     * @var array
93
     */
94
    protected $allowedTypes = [
95
        'Task' => [
96
            'jiraType' => 'Task', // Must be one of the types in the $this->validTypes.
97
            'jiraLabels' => 'task-submitted', // The label used to tag user submitted bugs.
98
            // The form's field information.
99
            'formData' => [
100
                'fields' => [
101
                    'summary' => [
102
                        'type' => 'text',
103
                        'required' => true,
104
                    ],
105
                    'details' => [
106
                        'type' => 'textarea',
107
                        'required' => true,
108
                    ],
109
                ],
110
            ],
111
        ],
112
        'Bug' => [
113
            'jiraType' => 'Bug', // Must be one of the types in the $this->validTypes.
114
            'jiraLabels' => 'bug-submitted', // The label used to tag user submitted bugs.
115
            // The form's field information.
116
            'formData' => [
117
                'fields' => [
118
                    'summary' => [
119
                        'type' => 'text',
120
                        'required' => true,
121
                    ],
122
                    'details' => [
123
                        'type' => 'textarea',
124
                        'required' => true,
125
                    ],
126
                ],
127
            ],
128
        ],
129
        'FeatureRequest' => [
130
            'jiraType' => 'Story', // Must be one of the types in the $this->validTypes.
131
            'jiraLabels' => 'feature-request', // The label used to tag feature requests.
132
            // The form's field information.
133
            'formData' => [
134
                'fields' => [
135
                    'summary' => [
136
                        'type' => 'text',
137
                        'required' => true,
138
                    ],
139
                    'details' => [
140
                        'type' => 'textarea',
141
                        'required' => true,
142
                    ],
143
                ],
144
            ],
145
        ],
146
    ];
147
148
    /**
149
     * This is here for the Form object (or any other object) to use.
150
     * It tacks all errors, even if an exception is thrown.
151
     * @var array
152
     */
153
    protected $errors = [];
154
155
    /**
156
     * Constructor
157
     *
158
     * Reads the configuration, and crdate a config object to be passed to the other objects.
159
     *
160
     * @throws \Fr3nch13\Jira\Exception\MissingProjectException When the project can't be found.
161
     * @return void
162
     */
163 20
    public function __construct()
164
    {
165 20
        $this->configure();
166
167
        // setup the objects
168 20
        $this->ProjectService = new ProjectService($this->ConfigObj);
169
        try {
170 20
            $this->Project = $this->ProjectService->get($this->projectKey);
171
        } catch (\JiraRestApi\JiraException $e) {
172
            $this->setError($this->projectKey, 'MissingProjectException');
173
            throw new MissingProjectException($this->projectKey);
174
        }
175
176 20
        $this->Versions = $this->ProjectService->getVersions($this->projectKey);
177 20
        $this->IssueService = new IssueService($this->ConfigObj);
178 20
    }
179
180
    /**
181
     * Configures the object.
182
     * Broken out of construct.
183
     *
184
     * @throws \Fr3nch13\Jira\Exception\MissingConfigException When a config setting isn't set.
185
     * @return void
186
     */
187 20
    public function configure()
188
    {
189 20
        $schema = Configure::read('Jira.schema');
190 20
        if (!$schema) {
191
            $this->setError('schema', 'MissingConfigException');
192
            throw new MissingConfigException('schema');
193
        }
194 20
        $host = Configure::read('Jira.host');
195 20
        if (!$host) {
196
            $this->setError('host', 'MissingConfigException');
197
            throw new MissingConfigException('host');
198
        }
199 20
        $username = Configure::read('Jira.username');
200 20
        if (!$username) {
201
            $this->setError('username', 'MissingConfigException');
202
            throw new MissingConfigException('username');
203
        }
204 20
        $apiKey = Configure::read('Jira.apiKey');
205 20
        if (!$apiKey) {
206
            $this->setError('apiKey', 'MissingConfigException');
207
            throw new MissingConfigException('apiKey');
208
        }
209 20
        $projectKey = Configure::read('Jira.projectKey');
210 20
        if (!$projectKey) {
211
            $this->setError('projectKey', 'MissingConfigException');
212
            throw new MissingConfigException('projectKey');
213
        }
214 20
        $this->ConfigObj = new ArrayConfiguration([
215 20
            'jiraHost' => $schema . '://' . $host,
216 20
            'jiraUser' => $username,
217 20
            'jiraPassword' => $apiKey,
218
        ]);
219
220 20
        $this->projectKey = $projectKey;
221 20
    }
222
223
    /**
224
     * Get the Project's Info.
225
     *
226
     * @return \JiraRestApi\Project\Project The information about the project.
227
     * @throws \Fr3nch13\Jira\Exception\MissingProjectException If the project can't be found.
228
     */
229 3
    public function getInfo()
230
    {
231 3
        return $this->Project;
232
    }
233
234
    /**
235
     * Get the Project's Versions.
236
     *
237
     * @return array A list of version objects.
238
     */
239 2
    public function getVersions()
240
    {
241 2
        return $this->Versions;
242
    }
243
244
    /**
245
     * Get the Project's Issues.
246
     *
247
     * @param string|null $type Filter the Issues by type.
248
     * @return \JiraRestApi\Issue\IssueSearchResult|\JiraRestApi\Issue\IssueSearchResultV3 A list of issue objects.
249
     */
250 4
    public function getIssues($type = null)
251
    {
252 4
        $cacheKey = 'all';
253 4
        if ($type) {
254 2
            $cacheKey .= '-' . $type;
255
        }
256 4
        if (!isset($this->Issues[$cacheKey])) {
257 4
            $jql = new JqlQuery();
258
259 4
            $jql->setProject($this->projectKey);
260 4
            if ($type && in_array($type, $this->validTypes)) {
261 2
                $jql->setType($type);
262
            }
263 4
            $jql->addAnyExpression('ORDER BY key DESC');
264
265 4
            $this->Issues[$cacheKey] = $this->IssueService->search($jql->getQuery(), 0, 1000);
266
        }
267
268 4
        return $this->Issues[$cacheKey];
269
    }
270
271
    /**
272
     * Get the Project's Open Issues.
273
     *
274
     * @param string|null $type Filter the Issues by type.
275
     * @return \JiraRestApi\Issue\IssueSearchResult|\JiraRestApi\Issue\IssueSearchResultV3 A list of issue objects.
276
     */
277 4
    public function getOpenIssues($type = null)
278
    {
279 4
        $cacheKey = 'open';
280 4
        if ($type) {
281 2
            $cacheKey .= '-' . $type;
282
        }
283 4
        if (!isset($this->Issues[$cacheKey])) {
284 4
            $jql = new JqlQuery();
285
286 4
            $jql->setProject($this->projectKey);
287 4
            if ($type && in_array($type, $this->validTypes)) {
288 2
                $jql->setType($type);
289
            }
290 4
            $jql->addAnyExpression('AND resolution is EMPTY');
291 4
            $jql->addAnyExpression('ORDER BY key DESC');
292
293 4
            $this->Issues[$cacheKey] = $this->IssueService->search($jql->getQuery(), 0, 1000);
294
        }
295
296 4
        return $this->Issues[$cacheKey];
297
    }
298
299
    /**
300
     * Gets info on a particular issue within your project.
301
     *
302
     * @param int|null $id The issue id. The integer part without the project key.
303
     * @return \JiraRestApi\Issue\Issue|\JiraRestApi\Issue\IssueV3 the object that has the info of that issue.
304
     * @throws \Fr3nch13\Jira\Exception\Exception If the issue's id isn't given.
305
     * @throws \Fr3nch13\Jira\Exception\MissingIssueException If the project's issue can't be found.
306
     */
307 2
    public function getIssue($id = null)
308
    {
309 2
        if (!is_int($id)) {
310
            $this->setError(__('Missing the Issue\'s ID.'), 'Exception');
311
            throw new Exception(__('Missing the Issue\'s ID.'));
312
        }
313 2
        $key = $this->projectKey . '-' . $id;
314 2
        if (!isset($this->issuesCache[$key])) {
315 2
            $this->issuesCache[$key] = $this->IssueService->get($key);
316 2
            if (!$this->issuesCache[$key]) {
317
                $this->setError($key, 'MissingIssueException');
318
                throw new MissingIssueException($key);
319
            }
320
        }
321
322 2
        return $this->issuesCache[$key];
323
    }
324
325
    /**
326
     * Gets a list of issues that are considered bugs.
327
     * @return \JiraRestApi\Issue\IssueSearchResult|\JiraRestApi\Issue\IssueSearchResultV3 A list of issue objects.
328
     */
329 2
    public function getBugs()
330
    {
331 2
        return $this->getIssues('Bug');
332
    }
333
334
    /**
335
     * Gets a list of open issues that are considered bugs.
336
     * @return \JiraRestApi\Issue\IssueSearchResult|\JiraRestApi\Issue\IssueSearchResultV3 A list of issue objects.
337
     */
338 2
    public function getOpenBugs()
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 5
    public function getAllowedTypes($type = null)
355
    {
356 5
        if ($type) {
357 1
            if (!isset($this->allowedTypes[$type])) {
358
                $this->setError($type, 'MissingAllowedTypeException');
359
                throw new MissingAllowedTypeException($type);
360
            }
361
362 1
            return $this->allowedTypes[$type];
363
        }
364
365 4
        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 4
    public function modifyAllowedTypes($type, $settings = [])
377
    {
378 4
        if (!isset($this->allowedTypes[$type])) {
379 4
            $this->allowedTypes[$type] = [];
380 4
            if (!isset($settings['jiraType'])) {
381
                $this->setError('jiraType', 'MissingIssueFieldException');
382
                throw new MissingIssueFieldException('jiraType');
383
            }
384 4
            if (!isset($settings['formData'])) {
385
                $this->setError('formData', 'MissingIssueFieldException');
386
                throw new MissingIssueFieldException('formData');
387
            }
388 4
            if (!isset($settings['formData']['fields'])) {
389
                $this->setError('formData.fields', 'MissingIssueFieldException');
390
                throw new MissingIssueFieldException('formData.fields');
391
            }
392 4
            if (!isset($settings['formData']['fields']['summary'])) {
393
                $this->setError('formData.fields.summary', 'MissingIssueFieldException');
394
                throw new MissingIssueFieldException('formData.fields.summary');
395
            }
396
        }
397
398 4
        $this->allowedTypes[$type] += $settings;
399 4
    }
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 5
    public function isAllowedType($type)
408
    {
409 5
        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 3
    public function getFormData($type = null)
421
    {
422 3
        if (!$type) {
423
            $this->setError('[$type is not set]', 'MissingAllowedTypeException');
424
            throw new MissingAllowedTypeException('[$type is not set]');
425
        }
426
427 3
        if (!$this->isAllowedType($type)) {
428
            $this->setError($type, 'MissingAllowedTypeException');
429
            throw new MissingAllowedTypeException($type);
430
        }
431
432 3
        $allowedTypes = $this->getAllowedTypes();
433
434 3
        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 3
        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 2
    public function setFormData($type, $data = [])
452
    {
453 2
        if (!$type) {
454
            $this->setError('[$type is not set]', 'MissingAllowedTypeException');
455
            throw new MissingAllowedTypeException('[$type is not set]');
456
        }
457
458 2
        if (!$this->isAllowedType($type)) {
459
            $this->setError($type, 'MissingAllowedTypeException');
460
            throw new MissingAllowedTypeException($type);
461
        }
462
463 2
        $this->allowedTypes[$type]['formData'] = $data;
464 2
    }
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|bool If the request was successfully submitted.
476
     */
477 1
    public function submitIssue($type, array $data = [])
478
    {
479 1
        if (!$type) {
480
            $this->setError('[$type is not set]', 'MissingAllowedTypeException');
481
            throw new MissingAllowedTypeException('[$type is not set]');
482
        }
483
484 1
        if (!$this->isAllowedType($type)) {
485
            $this->setError($type, 'MissingAllowedTypeException');
486
            throw new MissingAllowedTypeException($type);
487
        }
488
489 1
        if (!isset($data['summary'])) {
490
            $this->setError('summary', 'MissingIssueFieldException');
491
            throw new MissingIssueFieldException('summary');
492
        }
493
494 1
        $issueField = $this->buildSubmittedIssue($type, $data);
495
496 1
        $issueService = new IssueService($this->ConfigObj);
497
498
        try {
499 1
            $ret = $issueService->create($issueField);
500
        } catch (\JiraRestApi\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 1
        if ($ret instanceof \JiraRestApi\Issue\Issue && $ret->id) {
543 1
            return (int)$ret->id;
544
        }
545
546
        return true;
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 1
    public function buildSubmittedIssue($type, $data = [])
558
    {
559 1
        $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 1
            $project = $this->getInfo();
566
        } catch (MissingProjectException $e) {
567
            $this->setError($this->projectKey, 'MissingProjectException');
568
            throw $e;
569
        }
570
571 1
        $issueField = new IssueField();
572 1
        $issueField->setProjectKey($this->projectKey)
573 1
            ->setIssueType($typeInfo['jiraType']);
574
575 1
        if (isset($data['summary'])) {
576 1
            $issueField->setSummary($data['summary']);
577
        }
578 1
        if (isset($data['description'])) {
579 1
            $issueField->setDescription($data['description']);
580
        }
581 1
        if (isset($data['priority'])) {
582
            $issueField->setPriorityName($data['priority']);
583
        }
584 1
        if (isset($data['assignee'])) {
585
            $issueField->setPriorityName($data['assignee']);
586
        }
587 1
        if (isset($data['version'])) {
588
            $issueField->addVersion($data['version']);
589
        }
590 1
        if (isset($data['components'])) {
591
            $issueField->addComponents($data['components']);
592
        }
593 1
        if (isset($data['duedate'])) {
594
            $issueField->setDueDate($data['duedate']);
595
        }
596
597
        // labels should be space seperated
598 1
        if (isset($typeInfo['jiraLabels'])) {
599 1
            if (is_string($typeInfo['jiraLabels'])) {
600 1
                $typeInfo['jiraLabels'] = preg_split('/\s+/', $typeInfo['jiraLabels']);
601
            }
602
            // track the type with a label
603 1
            $typeInfo['jiraLabels'][] = 'user-submitted-type-' . $type;
604 1
            foreach ($typeInfo['jiraLabels'] as $jiralabel) {
605 1
                $issueField->addLabel($jiralabel);
606
            }
607
        }
608
609 1
        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
    public function setError($msg = '', $key = '')
620
    {
621
        if (!trim($msg)) {
622
            return false;
623
        }
624
        if ($key) {
625
            $this->errors[$key] = $msg;
626
        } else {
627
            $this->errors[] = $msg;
628
        }
629
630
        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($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