diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e55634..fa8cf8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Minimal supported PHP version changed from 5.2 to 5.3 by [@chobie]. - The `Api::getPriorties` renamed into `Api::getPriorities` (former method kept for BC reasons) by [@josevh]. - Remove trailing slash from endpoint url by [@Procta]. +- Added local cache to getResolutions [@jpastoor]. ### Removed ... @@ -34,6 +35,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Fixed PHP deprecation notice, when creating issue attachments via `CurlClient` on PHP 5.5+ by [@DerMika]. - The `Api::getRoles` call was always retuning an error by [@aik099]. - Attempt to make a `DELETE` API call using `CurlClient` wasn't working by [@aik099]. +- Clearing local caches (statuses, priorities, fields and resolutions) on endpoint change [@jpastoor]. ## [1.0.0] - 2014-07-27 ### Added diff --git a/src/Jira/Api.php b/src/Jira/Api.php index 16e321a..f45b9a4 100644 --- a/src/Jira/Api.php +++ b/src/Jira/Api.php @@ -68,26 +68,33 @@ class Api protected $options = self::AUTOMAP_FIELDS; /** - * Fields. + * Client-side cache of fields. List of fields when loaded, null when nothing is fetched yet. * - * @var array + * @var array|null */ protected $fields; /** - * Priorities. + * Client-side cache of priorities. List of priorities when loaded, null when nothing is fetched yet. * - * @var array + * @var array|null */ protected $priorities; /** - * Statuses. + * Client-side cache of statuses. List of statuses when loaded, null when nothing is fetched yet. * - * @var array + * @var array|null */ protected $statuses; + /** + * Client-side cache of resolutions. List of resolutions when loaded, null when nothing is fetched yet. + * + * @var array|null + */ + protected $resolutions; + /** * Create a JIRA API client. * @@ -141,12 +148,26 @@ public function getEndpoint() */ public function setEndpoint($url) { - $this->fields = array(); - // Remove trailing slash in the url. $url = rtrim($url, '/'); - $this->endpoint = $url; + if ( $url !== $this->endpoint ) { + $this->endpoint = $url; + $this->clearLocalCaches(); + } + } + + /** + * Helper method to clear the local caches. Is called when switching endpoints + * + * @return void + */ + protected function clearLocalCaches() + { + $this->fields = null; + $this->priorities = null; + $this->statuses = null; + $this->resolutions = null; } /** @@ -156,13 +177,14 @@ public function setEndpoint($url) */ public function getFields() { - if ( !count($this->fields) ) { + // Fetch fields when the method is called for the first time. + if ( $this->fields === null ) { $fields = array(); - $_fields = $this->api(self::REQUEST_GET, '/rest/api/2/field', array()); + $result = $this->api(self::REQUEST_GET, '/rest/api/2/field', array(), true); /* set hash key as custom field id */ - foreach ( $_fields->getResult() as $k => $v ) { - $fields[$v['id']] = $v; + foreach ( $result as $field ) { + $fields[$field['id']] = $field; } $this->fields = $fields; @@ -456,13 +478,14 @@ public function findVersionByName($project_key, $name) */ public function getPriorities() { - if ( !count($this->priorities) ) { + // Fetch priorities when the method is called for the first time. + if ( $this->priorities === null ) { $priorities = array(); - $result = $this->api(self::REQUEST_GET, '/rest/api/2/priority', array()); + $result = $this->api(self::REQUEST_GET, '/rest/api/2/priority', array(), true); /* set hash key as custom field id */ - foreach ( $result->getResult() as $k => $v ) { - $priorities[$v['id']] = $v; + foreach ( $result as $priority ) { + $priorities[$priority['id']] = $priority; } $this->priorities = $priorities; @@ -491,13 +514,14 @@ public function getPriorties() */ public function getStatuses() { - if ( !count($this->statuses) ) { + // Fetch statuses when the method is called for the first time. + if ( $this->statuses === null ) { $statuses = array(); - $result = $this->api(self::REQUEST_GET, '/rest/api/2/status', array()); + $result = $this->api(self::REQUEST_GET, '/rest/api/2/status', array(), true); /* set hash key as custom field id */ - foreach ( $result->getResult() as $k => $v ) { - $statuses[$v['id']] = $v; + foreach ( $result as $status ) { + $statuses[$status['id']] = $status; } $this->statuses = $statuses; @@ -875,14 +899,24 @@ public function getProjectIssueTypes($project_key) /** * Returns a list of all resolutions. * - * @param string $project_key Project key. - * - * @return array|false + * @return array * @since 2.0.0 */ public function getResolutions() { - return $this->api(self::REQUEST_GET, '/rest/api/2/resolution', array(), true); + // Fetch resolutions when the method is called for the first time. + if ( $this->resolutions === null ) { + $resolutions = array(); + $result = $this->api(self::REQUEST_GET, '/rest/api/2/resolution', array(), true); + + foreach ( $result as $resolution ) { + $resolutions[$resolution['id']] = $resolution; + } + + $this->resolutions = $resolutions; + } + + return $this->resolutions; } } diff --git a/tests/Jira/ApiTest.php b/tests/Jira/ApiTest.php index 7824f5a..a8eabd4 100644 --- a/tests/Jira/ApiTest.php +++ b/tests/Jira/ApiTest.php @@ -149,6 +149,114 @@ public function testFindVersionByName() ); } + public function testGetResolutions() + { + $response = file_get_contents(__DIR__ . '/resources/api_resolution.json'); + + $this->expectClientCall( + Api::REQUEST_GET, + '/rest/api/2/resolution', + array(), + $response + ); + + $actual = $this->api->getResolutions(); + + $response_decoded = json_decode($response, true); + + $expected = array( + '1' => $response_decoded[0], + '10000' => $response_decoded[1], + ); + $this->assertEquals($expected, $actual); + + // Second time we call the method the results should be cached and not trigger an API Request. + $this->client->sendRequest(Api::REQUEST_GET, '/rest/api/2/resolution', array(), self::ENDPOINT, $this->credential) + ->shouldNotBeCalled(); + $this->assertEquals($expected, $this->api->getResolutions(), 'Calling twice did not yield the same results'); + } + + public function testGetFields() + { + $response = file_get_contents(__DIR__ . '/resources/api_field.json'); + + $this->expectClientCall( + Api::REQUEST_GET, + '/rest/api/2/field', + array(), + $response + ); + + $actual = $this->api->getFields(); + + $response_decoded = json_decode($response, true); + + $expected = array( + 'issuetype' => $response_decoded[0], + 'timespent' => $response_decoded[1], + ); + $this->assertEquals($expected, $actual); + + // Second time we call the method the results should be cached and not trigger an API Request. + $this->client->sendRequest(Api::REQUEST_GET, '/rest/api/2/field', array(), self::ENDPOINT, $this->credential) + ->shouldNotBeCalled(); + $this->assertEquals($expected, $this->api->getFields(), 'Calling twice did not yield the same results'); + } + + public function testGetStatuses() + { + $response = file_get_contents(__DIR__ . '/resources/api_status.json'); + + $this->expectClientCall( + Api::REQUEST_GET, + '/rest/api/2/status', + array(), + $response + ); + + $actual = $this->api->getStatuses(); + + $response_decoded = json_decode($response, true); + + $expected = array( + '1' => $response_decoded[0], + '3' => $response_decoded[1], + ); + $this->assertEquals($expected, $actual); + + // Second time we call the method the results should be cached and not trigger an API Request. + $this->client->sendRequest(Api::REQUEST_GET, '/rest/api/2/status', array(), self::ENDPOINT, $this->credential) + ->shouldNotBeCalled(); + $this->assertEquals($expected, $this->api->getStatuses(), 'Calling twice did not yield the same results'); + } + + public function testGetPriorities() + { + $response = file_get_contents(__DIR__ . '/resources/api_priority.json'); + + $this->expectClientCall( + Api::REQUEST_GET, + '/rest/api/2/priority', + array(), + $response + ); + + $actual = $this->api->getPriorities(); + + $response_decoded = json_decode($response, true); + + $expected = array( + '1' => $response_decoded[0], + '5' => $response_decoded[1], + ); + $this->assertEquals($expected, $actual); + + // Second time we call the method the results should be cached and not trigger an API Request. + $this->client->sendRequest(Api::REQUEST_GET, '/rest/api/2/priority', array(), self::ENDPOINT, $this->credential) + ->shouldNotBeCalled(); + $this->assertEquals($expected, $this->api->getPriorities(), 'Calling twice did not yield the same results'); + } + /** * Expects a particular client call. * diff --git a/tests/Jira/resources/api_field.json b/tests/Jira/resources/api_field.json new file mode 100644 index 0000000..32d19ec --- /dev/null +++ b/tests/Jira/resources/api_field.json @@ -0,0 +1,35 @@ +[ + { + "id": "issuetype", + "key": "issuetype", + "name": "Issue Type", + "custom": false, + "orderable": true, + "navigable": true, + "searchable": true, + "clauseNames": [ + "issuetype", + "type" + ], + "schema": { + "type": "issuetype", + "system": "issuetype" + } + }, + { + "id": "timespent", + "key": "timespent", + "name": "Time Spent", + "custom": false, + "orderable": false, + "navigable": true, + "searchable": false, + "clauseNames": [ + "timespent" + ], + "schema": { + "type": "number", + "system": "timespent" + } + } +] diff --git a/tests/Jira/resources/api_priority.json b/tests/Jira/resources/api_priority.json new file mode 100644 index 0000000..3be3a91 --- /dev/null +++ b/tests/Jira/resources/api_priority.json @@ -0,0 +1,18 @@ +[ + { + "self": "https://test.atlassian.net/rest/api/2/priority/1", + "statusColor": "#cc0000", + "description": "Blocks development and/or testing work, production could not run.", + "iconUrl": "https://test.atlassian.net/images/icons/priorities/blocker.svg", + "name": "Blocker", + "id": "1" + }, + { + "self": "https://test.atlassian.net/rest/api/2/priority/5", + "statusColor": "#003300", + "description": "Cosmetic problem like misspelt words or misaligned text.", + "iconUrl": "https://test.atlassian.net/images/icons/priorities/trivial.svg", + "name": "Trivial", + "id": "5" + } +] diff --git a/tests/Jira/resources/api_resolution.json b/tests/Jira/resources/api_resolution.json new file mode 100644 index 0000000..5faaeed --- /dev/null +++ b/tests/Jira/resources/api_resolution.json @@ -0,0 +1,14 @@ +[ + { + "self": "https://test.atlassian.net/rest/api/2/resolution/1", + "id": "1", + "description": "A fix for this issue is checked into the tree and tested.", + "name": "Fixed" + }, + { + "self": "https://test.atlassian.net/rest/api/2/resolution/10000", + "id": "10000", + "description": "This issue won't be actioned.", + "name": "Won't Do" + } +] diff --git a/tests/Jira/resources/api_status.json b/tests/Jira/resources/api_status.json new file mode 100644 index 0000000..aa6b107 --- /dev/null +++ b/tests/Jira/resources/api_status.json @@ -0,0 +1,30 @@ +[ + { + "self": "https://test.atlassian.net/rest/api/2/status/1", + "description": "The issue is open and ready for the assignee to start work on it.", + "iconUrl": "https://test.atlassian.net/images/icons/statuses/open.png", + "name": "Open", + "id": "1", + "statusCategory": { + "self": "https://test.atlassian.net/rest/api/2/statuscategory/2", + "id": 2, + "key": "new", + "colorName": "blue-gray", + "name": "To Do" + } + }, + { + "self": "https://test.atlassian.net/rest/api/2/status/3", + "description": "This issue is being actively worked on at the moment by the assignee.", + "iconUrl": "https://test.atlassian.net/images/icons/statuses/inprogress.png", + "name": "In Progress", + "id": "3", + "statusCategory": { + "self": "https://test.atlassian.net/rest/api/2/statuscategory/4", + "id": 4, + "key": "indeterminate", + "colorName": "yellow", + "name": "In Progress" + } + } +]