Passed
Push — 1.x-dev ( 41151e...6e580d )
by Brian
03:15
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
 * JiraProject
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<\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');
0 ignored issues
show
Bug introduced by
It seems like $this->projectKey can also be of type null; however, parameter $msg of Fr3nch13\Jira\Lib\JiraProject::setError() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

172
            $this->setError(/** @scrutinizer ignore-type */ $this->projectKey, 'MissingProjectException');
Loading history...
173
            throw new MissingProjectException($this->projectKey);
174
        }
175
176 20
        $this->Versions = (array)$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<\JiraRestApi\Issue\Version> A list of version objects.
238
     */
239 2
    public function getVersions(): array
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 $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(int $id): \JiraRestApi\Issue\Issue
308
    {
309 2
        $key = $this->projectKey . '-' . $id;
310 2
        if (!isset($this->issuesCache[$key])) {
311 2
            $this->issuesCache[$key] = $this->IssueService->get($key);
312 2
            if (!$this->issuesCache[$key]) {
313
                $this->setError($key, 'MissingIssueException');
314
                throw new MissingIssueException($key);
315
            }
316
        }
317
318 2
        return $this->issuesCache[$key];
319
    }
320
321
    /**
322
     * Gets a list of issues that are considered bugs.
323
     * @return \JiraRestApi\Issue\IssueSearchResult A list of issue objects.
324
     */
325 2
    public function getBugs(): \JiraRestApi\Issue\IssueSearchResult
326
    {
327 2
        return $this->getIssues('Bug');
328
    }
329
330
    /**
331
     * Gets a list of open issues that are considered bugs.
332
     * @return \JiraRestApi\Issue\IssueSearchResult A list of issue objects.
333
     */
334 2
    public function getOpenBugs(): \JiraRestApi\Issue\IssueSearchResult
335
    {
336 2
        return $this->getOpenIssues('Bug');
337
    }
338
339
    /**
340
     * Methods used to submit an Issue to Jira.
341
     */
342
343
    /**
344
     * Returns the allowed types and their settings
345
     *
346
     * @param string|null $type The type of issue you want to get.
347
     * @throws \Fr3nch13\Jira\Exception\MissingAllowedTypeException If a type is given, and that type is not configured.
348
     * @return array the content of $this->allowedTypes.
349
     */
350 5
    public function getAllowedTypes(?string $type = null): array
351
    {
352 5
        if ($type) {
353 1
            if (!isset($this->allowedTypes[$type])) {
354
                $this->setError($type, 'MissingAllowedTypeException');
355
                throw new MissingAllowedTypeException($type);
356
            }
357
358 1
            return $this->allowedTypes[$type];
359
        }
360
361 4
        return $this->allowedTypes;
362
    }
363
364
    /**
365
     * Allows you to modify the form allowdTypes to fir your situation.
366
     *
367
     * @param string $type The type of issue you want to add/modify.
368
     * @param array $settings The settings for the type.
369
     * @throws \Fr3nch13\Jira\Exception\MissingIssueFieldException If we're adding a new issue type, and the summary field isn't defined.
370
     * @return void
371
     */
372 4
    public function modifyAllowedTypes(string $type, array $settings = []): void
373
    {
374 4
        if (!isset($this->allowedTypes[$type])) {
375 4
            $this->allowedTypes[$type] = [];
376 4
            if (!isset($settings['jiraType'])) {
377
                $this->setError('jiraType', 'MissingIssueFieldException');
378
                throw new MissingIssueFieldException('jiraType');
379
            }
380 4
            if (!isset($settings['formData'])) {
381
                $this->setError('formData', 'MissingIssueFieldException');
382
                throw new MissingIssueFieldException('formData');
383
            }
384 4
            if (!isset($settings['formData']['fields'])) {
385
                $this->setError('formData.fields', 'MissingIssueFieldException');
386
                throw new MissingIssueFieldException('formData.fields');
387
            }
388 4
            if (!isset($settings['formData']['fields']['summary'])) {
389
                $this->setError('formData.fields.summary', 'MissingIssueFieldException');
390
                throw new MissingIssueFieldException('formData.fields.summary');
391
            }
392
        }
393
394 4
        $this->allowedTypes[$type] += $settings;
395 4
    }
396
397
    /**
398
     * Checks to see if a type is allowed.
399
     *
400
     * @param string $type The type to check.
401
     * @return bool if it's allowed or not.
402
     */
403 5
    public function isAllowedType(string $type): bool
404
    {
405 5
        return (isset($this->allowedTypes[$type]) ? true : false);
406
    }
407
408
    /**
409
     * Gets the array for the forms when submitting an issue to Jira.
410
     *
411
     * @param string|null $type The type of issue we're submitting.
412
     * @throws \Fr3nch13\Jira\Exception\MissingAllowedTypeException If that type is not configured.
413
     * @throws \Fr3nch13\Jira\Exception\Exception If the form data for that type is missing.
414
     * @return array The array of data to fill in the form with.
415
     */
416 3
    public function getFormData(?string $type = null): array
417
    {
418 3
        if (!$type) {
419
            $this->setError('[$type is not set]', 'MissingAllowedTypeException');
420
            throw new MissingAllowedTypeException('[$type is not set]');
421
        }
422
423 3
        if (!$this->isAllowedType($type)) {
424
            $this->setError($type, 'MissingAllowedTypeException');
425
            throw new MissingAllowedTypeException($type);
426
        }
427
428 3
        $allowedTypes = $this->getAllowedTypes();
429
430 3
        if (!isset($allowedTypes[$type]['formData'])) {
431
            $this->setError('No form data is set.', 'Exception');
432
            throw new Exception(__('No form data is set.'));
433
        }
434
435 3
        return $allowedTypes[$type]['formData'];
436
    }
437
438
    /**
439
     * Sets the formData variable if you want to modify the default/initial values.
440
     *
441
     * @param string $type The type you want to set the data for.
442
     *  - Needs to be in the allowedTypes already.
443
     * @param array $data The definition of the allowed types
444
     * @throws \Fr3nch13\Jira\Exception\MissingAllowedTypeException If that type is not configured.
445
     * @return void
446
     */
447 2
    public function setFormData(string $type, array $data = []): void
448
    {
449 2
        if (!$type) {
450
            $this->setError('[$type is not set]', 'MissingAllowedTypeException');
451
            throw new MissingAllowedTypeException('[$type is not set]');
452
        }
453
454 2
        if (!$this->isAllowedType($type)) {
455
            $this->setError($type, 'MissingAllowedTypeException');
456
            throw new MissingAllowedTypeException($type);
457
        }
458
459 2
        $this->allowedTypes[$type]['formData'] = $data;
460 2
    }
461
462
    /**
463
     * Submits the Issue
464
     *
465
     * @param string $type The type you want to set the data for.
466
     *  - Needs to be in the allowedTypes already.
467
     * @param array $data The array of details about the issue.
468
     * @throws \Fr3nch13\Jira\Exception\IssueSubmissionException If submitting the issue fails.
469
     * @throws \Fr3nch13\Jira\Exception\MissingAllowedTypeException If that issue type is not configured.
470
     * @throws \Fr3nch13\Jira\Exception\MissingIssueFieldException If we're adding a new issue, and required fields aren't defined.
471
     * @return bool If the request was successfully submitted.
472
     */
473 1
    public function submitIssue(string $type, array $data = []): bool
474
    {
475 1
        if (!$this->isAllowedType($type)) {
476
            $this->setError($type, 'MissingAllowedTypeException');
477
            throw new MissingAllowedTypeException($type);
478
        }
479
480 1
        if (!isset($data['summary'])) {
481
            $this->setError('summary', 'MissingIssueFieldException');
482
            throw new MissingIssueFieldException('summary');
483
        }
484
485 1
        $issueField = $this->buildSubmittedIssue($type, $data);
486
487 1
        $issueService = new IssueService($this->ConfigObj);
488
489
        try {
490 1
            $ret = $issueService->create($issueField);
491
        } catch (\JiraRestApi\JiraException $e) {
492
            //Sample return error with json in it.
493
            //Pasting here so I can mock this return message in the unit tests.
494
            //CURL HTTP Request Failed: Status Code : 400, URL:https://[hostname]/rest/api/2/issue
495
            //Error Message : {"errorMessages":[],"errors":{"user_type":"Field 'user_type' cannot be set. It is not on the appropriate screen, or unknown."}}             */
496
            $msg = $e->getMessage();
497
            if (strpos($msg, '{') !== false) {
498
                $msgArray = str_split($msg);
499
                // extract the json message.
500
                $json = '';
501
                $in = 0;
502
                foreach ($msgArray as $i => $char) {
503
                    if ($char == '{') {
504
                        $in++;
505
                    }
506
                    if ($in) {
507
                        $json .= $msg[$i];
508
                    }
509
                    if ($char == '}') {
510
                        $in--;
511
                    }
512
                }
513
                if ($json) {
514
                    $json = json_decode($json, true);
515
                }
516
                if ($json) {
517
                    $newMsg = [];
518
                    if (isset($json['errorMessages'])) {
519
                        foreach ($json['errorMessages'] as $jsonMsg) {
520
                            $newMsg[] = $jsonMsg;
521
                        }
522
                        foreach ($json['errors'] as $jsonMsg) {
523
                            $newMsg[] = $jsonMsg;
524
                        }
525
                        $msg = implode("\n", $newMsg);
526
                    }
527
                }
528
            }
529
            $this->setError($msg, 'IssueSubmissionException');
530
            throw new IssueSubmissionException($msg);
531
        }
532
533 1
        if ($ret instanceof \JiraRestApi\Issue\Issue && (int)$ret->id > 0) {
534 1
            return true;
535
        }
536
537
        return false;
538
    }
539
540
    /**
541
     * Creates the issue to send to the server.
542
     *
543
     * @param string $type The type of isse we're creating.
544
     * @param array $data The data from the submitted form.
545
     * @throws \Fr3nch13\Jira\Exception\MissingProjectException If submitting the issue fails.
546
     * @return \JiraRestApi\Issue\IssueField
547
     */
548 1
    public function buildSubmittedIssue(string $type, array $data = []): \JiraRestApi\Issue\IssueField
549
    {
550 1
        $typeInfo = $this->getAllowedTypes($type);
551
552
        // make sure we can get the project info first.
553
        // getInfo will throw an exception if it can't find the project.
554
        // putting a try/catch around it so scrutinizer stops complaining.
555
        try {
556 1
            $project = $this->getInfo();
557
        } catch (MissingProjectException $e) {
558
            $this->setError($this->projectKey, 'MissingProjectException');
0 ignored issues
show
Bug introduced by
It seems like $this->projectKey can also be of type null; however, parameter $msg of Fr3nch13\Jira\Lib\JiraProject::setError() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

558
            $this->setError(/** @scrutinizer ignore-type */ $this->projectKey, 'MissingProjectException');
Loading history...
559
            throw $e;
560
        }
561
562 1
        $issueField = new IssueField();
563 1
        $issueField->setProjectKey($this->projectKey)
564 1
            ->setIssueType($typeInfo['jiraType']);
565
566 1
        if (isset($data['summary'])) {
567 1
            $issueField->setSummary($data['summary']);
568
        }
569 1
        if (isset($data['description'])) {
570 1
            $issueField->setDescription($data['description']);
571
        }
572 1
        if (isset($data['priority'])) {
573
            $issueField->setPriorityName($data['priority']);
574
        }
575 1
        if (isset($data['assignee'])) {
576
            $issueField->setPriorityName($data['assignee']);
577
        }
578 1
        if (isset($data['version'])) {
579
            $issueField->addVersion($data['version']);
580
        }
581 1
        if (isset($data['components'])) {
582
            $issueField->addComponents($data['components']);
583
        }
584 1
        if (isset($data['duedate'])) {
585
            $issueField->setDueDate($data['duedate']);
586
        }
587
588
        // labels should be space seperated
589 1
        if (isset($typeInfo['jiraLabels'])) {
590 1
            if (is_string($typeInfo['jiraLabels'])) {
591 1
                $typeInfo['jiraLabels'] = preg_split('/\s+/', $typeInfo['jiraLabels']);
592
            }
593
            // track the type with a label
594 1
            $typeInfo['jiraLabels'][] = 'user-submitted-type-' . $type;
595 1
            foreach ($typeInfo['jiraLabels'] as $jiralabel) {
596 1
                $issueField->addLabel($jiralabel);
597
            }
598
        }
599
600 1
        return $issueField;
601
    }
602
603
    /**
604
     * Sets an error
605
     *
606
     * @param string $msg The error message.
607
     * @param string $key The key to use in the this->errors array.
608
     * @return bool If saved or not.
609
     */
610
    public function setError(string $msg = '', string $key = ''): bool
611
    {
612
        if (!trim($msg)) {
613
            return false;
614
        }
615
        if ($key) {
616
            $this->errors[$key] = $msg;
617
        } else {
618
            $this->errors[] = $msg;
619
        }
620
621
        return true;
622
    }
623
624
    /**
625
     * Gets the accumulated error messages.
626
     * If a key is given, return that specific message. If that key doesn't exist, return false.
627
     *
628
     * @param string|null $key The key to the specific message to get.
629
     * @return array|string|false
630
     */
631
    public function getErrors($key = null)
632
    {
633
        if ($key) {
634
            if (isset($this->errors[$key])) {
635
                return $this->errors[$key];
636
            } else {
637
                return false;
638
            }
639
        }
640
641
        return $this->errors;
642
    }
643
}
644