Passed
Push — 1.x-dev ( 3377ef...a9d859 )
by Brian
03:01
created

JiraProject::buildSubmittedIssue()   F

Complexity

Conditions 12
Paths 385

Size

Total Lines 59
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 14.7316

Importance

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