Passed
Push — master ( 789ab6...490312 )
by Brian
01:34 queued 11s
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 \ArrayObject|\JiraRestApi\Issue\Version[]
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 \ArrayObject|\JiraRestApi\Issue\Version[] 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
            if (!$this->issuesCache[$key] = $this->IssueService->get($key)) {
316
                $this->setError($key, 'MissingIssueException');
317
                throw new MissingIssueException($key);
318
            }
319
        }
320
321 2
        return $this->issuesCache[$key];
322
    }
323
324
    /**
325
     * Gets a list of issues that are considered bugs.
326
     * @return \JiraRestApi\Issue\IssueSearchResult|\JiraRestApi\Issue\IssueSearchResultV3 A list of issue objects.
327
     */
328 2
    public function getBugs()
329
    {
330 2
        return $this->getIssues('Bug');
331
    }
332
333
    /**
334
     * Gets a list of open issues that are considered bugs.
335
     * @return \JiraRestApi\Issue\IssueSearchResult|\JiraRestApi\Issue\IssueSearchResultV3 A list of issue objects.
336
     */
337 2
    public function getOpenBugs()
338
    {
339 2
        return $this->getOpenIssues('Bug');
340
    }
341
342
    /**
343
     * Methods used to submit an Issue to Jira.
344
     */
345
346
    /**
347
     * Returns the allowed types and their settings
348
     *
349
     * @param string|null $type The type of issue you want to get.
350
     * @throws \Fr3nch13\Jira\Exception\MissingAllowedTypeException If a type is given, and that type is not configured.
351
     * @return array the content of $this->allowedTypes.
352
     */
353 5
    public function getAllowedTypes($type = null)
354
    {
355 5
        if ($type) {
356 1
            if (!isset($this->allowedTypes[$type])) {
357
                $this->setError($type, 'MissingAllowedTypeException');
358
                throw new MissingAllowedTypeException($type);
359
            }
360
361 1
            return $this->allowedTypes[$type];
362
        }
363
364 4
        return $this->allowedTypes;
365
    }
366
367
    /**
368
     * Allows you to modify the form allowdTypes to fir your situation.
369
     *
370
     * @param string $type The type of issue you want to add/modify.
371
     * @param array $settings The settings for the type.
372
     * @throws \Fr3nch13\Jira\Exception\MissingIssueFieldException If we're adding a new issue type, and the summary field isn't defined.
373
     * @return void
374
     */
375 4
    public function modifyAllowedTypes($type, $settings = [])
376
    {
377 4
        if (!isset($this->allowedTypes[$type])) {
378 4
            $this->allowedTypes[$type] = [];
379 4
            if (!isset($settings['jiraType'])) {
380
                $this->setError('jiraType', 'MissingIssueFieldException');
381
                throw new MissingIssueFieldException('jiraType');
382
            }
383 4
            if (!isset($settings['formData'])) {
384
                $this->setError('formData', 'MissingIssueFieldException');
385
                throw new MissingIssueFieldException('formData');
386
            }
387 4
            if (!isset($settings['formData']['fields'])) {
388
                $this->setError('formData.fields', 'MissingIssueFieldException');
389
                throw new MissingIssueFieldException('formData.fields');
390
            }
391 4
            if (!isset($settings['formData']['fields']['summary'])) {
392
                $this->setError('formData.fields.summary', 'MissingIssueFieldException');
393
                throw new MissingIssueFieldException('formData.fields.summary');
394
            }
395
        }
396
397 4
        $this->allowedTypes[$type] += $settings;
398 4
    }
399
400
    /**
401
     * Checks to see if a type is allowed.
402
     *
403
     * @param string $type The type to check.
404
     * @return bool if it's allowed or not.
405
     */
406 5
    public function isAllowedType($type)
407
    {
408 5
        return (isset($this->allowedTypes[$type]) ? true : false);
409
    }
410
411
    /**
412
     * Gets the array for the forms when submitting an issue to Jira.
413
     *
414
     * @param string|null $type The type of issue we're submitting.
415
     * @throws \Fr3nch13\Jira\Exception\MissingAllowedTypeException If that type is not configured.
416
     * @throws \Fr3nch13\Jira\Exception\Exception If the form data for that type is missing.
417
     * @return array The array of data to fill in the form with.
418
     */
419 3
    public function getFormData($type = null)
420
    {
421 3
        if (!$type) {
422
            $this->setError('[$type is not set]', 'MissingAllowedTypeException');
423
            throw new MissingAllowedTypeException('[$type is not set]');
424
        }
425
426 3
        if (!$this->isAllowedType($type)) {
427
            $this->setError($type, 'MissingAllowedTypeException');
428
            throw new MissingAllowedTypeException($type);
429
        }
430
431 3
        $allowedTypes = $this->getAllowedTypes();
432
433 3
        if (!isset($allowedTypes[$type]['formData'])) {
434
            $this->setError('No form data is set.', 'Exception');
435
            throw new Exception(__('No form data is set.'));
436
        }
437
438 3
        return $allowedTypes[$type]['formData'];
439
    }
440
441
    /**
442
     * Sets the formData variable if you want to modify the default/initial values.
443
     *
444
     * @param string $type The type you want to set the data for.
445
     *  - Needs to be in the allowedTypes already.
446
     * @param array $data The definition of the allowed types
447
     * @throws \Fr3nch13\Jira\Exception\MissingAllowedTypeException If that type is not configured.
448
     * @return void
449
     */
450 2
    public function setFormData($type, $data = [])
451
    {
452 2
        if (!$type) {
453
            $this->setError('[$type is not set]', 'MissingAllowedTypeException');
454
            throw new MissingAllowedTypeException('[$type is not set]');
455
        }
456
457 2
        if (!$this->isAllowedType($type)) {
458
            $this->setError($type, 'MissingAllowedTypeException');
459
            throw new MissingAllowedTypeException($type);
460
        }
461
462 2
        $this->allowedTypes[$type]['formData'] = $data;
463 2
    }
464
465
    /**
466
     * Submits the Issue
467
     *
468
     * @param string $type The type you want to set the data for.
469
     *  - Needs to be in the allowedTypes already.
470
     * @param array $data The array of details about the issue.
471
     * @throws \Fr3nch13\Jira\Exception\IssueSubmissionException If submitting the issue fails.
472
     * @throws \Fr3nch13\Jira\Exception\MissingAllowedTypeException If that issue type is not configured.
473
     * @throws \Fr3nch13\Jira\Exception\MissingIssueFieldException If we're adding a new issue, and required fields aren't defined.
474
     * @return int|bool If the request was successfully submitted.
475
     */
476 1
    public function submitIssue($type, array $data = [])
477
    {
478 1
        if (!$type) {
479
            $this->setError('[$type is not set]', 'MissingAllowedTypeException');
480
            throw new MissingAllowedTypeException('[$type is not set]');
481
        }
482
483 1
        if (!$this->isAllowedType($type)) {
484
            $this->setError($type, 'MissingAllowedTypeException');
485
            throw new MissingAllowedTypeException($type);
486
        }
487
488 1
        if (!isset($data['summary'])) {
489
            $this->setError('summary', 'MissingIssueFieldException');
490
            throw new MissingIssueFieldException('summary');
491
        }
492
493 1
        $issueField = $this->buildSubmittedIssue($type, $data);
494
495 1
        $issueService = new IssueService($this->ConfigObj);
496
497
        try {
498 1
            $ret = $issueService->create($issueField);
499
        } catch (\JiraRestApi\JiraException $e) {
500
            //Sample return error with json in it.
501
            //Pasting here so I can mock this return message in the unit tests.
502
            //CURL HTTP Request Failed: Status Code : 400, URL:https://[hostname]/rest/api/2/issue
503
            //Error Message : {"errorMessages":[],"errors":{"user_type":"Field 'user_type' cannot be set. It is not on the appropriate screen, or unknown."}}             */
504
            $msg = $e->getMessage();
505
            if (strpos($msg, '{') !== false) {
506
                $msgArray = str_split($msg);
507
                // extract the json message.
508
                $json = '';
509
                $in = 0;
510
                foreach ($msgArray as $i => $char) {
511
                    if ($char == '{') {
512
                        $in++;
513
                    }
514
                    if ($in) {
515
                        $json .= $msg[$i];
516
                    }
517
                    if ($char == '}') {
518
                        $in--;
519
                    }
520
                }
521
                if ($json) {
522
                    $json = json_decode($json, true);
523
                }
524
                if ($json) {
525
                    $newMsg = [];
526
                    if (isset($json['errorMessages'])) {
527
                        foreach ($json['errorMessages'] as $jsonMsg) {
528
                            $newMsg[] = $jsonMsg;
529
                        }
530
                        foreach ($json['errors'] as $jsonMsg) {
531
                            $newMsg[] = $jsonMsg;
532
                        }
533
                        $msg = implode("\n", $newMsg);
534
                    }
535
                }
536
            }
537
            $this->setError($msg, 'IssueSubmissionException');
538
            throw new IssueSubmissionException($msg);
539
        }
540
541 1
        if ($ret instanceof \JiraRestApi\Issue\Issue && $ret->id) {
542 1
            return (int)$ret->id;
543
        }
544
545
        return true;
546
    }
547
548
    /**
549
     * Creates the issue to send to the server.
550
     *
551
     * @param string $type The type of isse we're creating.
552
     * @param array $data The data from the submitted form.
553
     * @throws \Fr3nch13\Jira\Exception\MissingProjectException If submitting the issue fails.
554
     * @return \JiraRestApi\Issue\IssueField
555
     */
556 1
    public function buildSubmittedIssue($type, $data = [])
557
    {
558 1
        $typeInfo = $this->getAllowedTypes($type);
559
560
        // make sure we can get the project info first.
561
        // getInfo will throw an exception if it can't find the project.
562
        // putting a try/catch around it so scrutinizer stops complaining.
563
        try {
564 1
            $project = $this->getInfo();
565
        } catch (MissingProjectException $e) {
566
            $this->setError($this->projectKey, 'MissingProjectException');
567
            throw $e;
568
        }
569
570 1
        $issueField = new IssueField();
571 1
        $issueField->setProjectKey($this->projectKey)
572 1
            ->setIssueType($typeInfo['jiraType']);
573
574 1
        if (isset($data['summary'])) {
575 1
            $issueField->setSummary($data['summary']);
576
        }
577 1
        if (isset($data['description'])) {
578 1
            $issueField->setDescription($data['description']);
579
        }
580 1
        if (isset($data['priority'])) {
581
            $issueField->setPriorityName($data['priority']);
582
        }
583 1
        if (isset($data['assignee'])) {
584
            $issueField->setPriorityName($data['assignee']);
585
        }
586 1
        if (isset($data['version'])) {
587
            $issueField->addVersion($data['version']);
588
        }
589 1
        if (isset($data['components'])) {
590
            $issueField->addComponents($data['components']);
591
        }
592 1
        if (isset($data['duedate'])) {
593
            $issueField->setDueDate($data['duedate']);
594
        }
595
596
        // labels should be space seperated
597 1
        if (isset($typeInfo['jiraLabels'])) {
598 1
            if (is_string($typeInfo['jiraLabels'])) {
599 1
                $typeInfo['jiraLabels'] = preg_split('/\s+/', $typeInfo['jiraLabels']);
600
            }
601
            // track the type with a label
602 1
            $typeInfo['jiraLabels'][] = 'user-submitted-type-' . $type;
603 1
            foreach ($typeInfo['jiraLabels'] as $jiralabel) {
604 1
                $issueField->addLabel($jiralabel);
605
            }
606
        }
607
608 1
        return $issueField;
609
    }
610
611
    /**
612
     * Sets an error
613
     *
614
     * @param string $msg The error message.
615
     * @param string $key The key to use in the this->errors array.
616
     * @return bool If saved or not.
617
     */
618
    public function setError($msg = '', $key = '')
619
    {
620
        if (!trim($msg)) {
621
            return false;
622
        }
623
        if ($key) {
624
            $this->errors[$key] = $msg;
625
        } else {
626
            $this->errors[] = $msg;
627
        }
628
629
        return true;
630
    }
631
632
    /**
633
     * Gets the accumulated error messages.
634
     * If a key is given, return that specific message. If that key doesn't exist, return false.
635
     *
636
     * @param string|null $key The key to the specific message to get.
637
     * @return array|string|false
638
     */
639
    public function getErrors($key = null)
640
    {
641
        if ($key) {
642
            if (isset($this->errors[$key])) {
643
                return $this->errors[$key];
644
            } else {
645
                return false;
646
            }
647
        }
648
649
        return $this->errors;
650
    }
651
}
652