Skip to content

Commit

Permalink
Merge pull request #16 from grnhse/add_paging_links
Browse files Browse the repository at this point in the history
There is no official method to access paging links in these tools.
  • Loading branch information
tdphillipsjr authored May 31, 2018
2 parents 6f3c9b3 + 21d5337 commit 5744b07
Show file tree
Hide file tree
Showing 7 changed files with 216 additions and 8 deletions.
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ This prevents issues that arise for systems that do not understand the array-ind
Use this service to interact with the Harvest API in Greenhouse. Documentation for the Harvest API [can be found here.](https://developers.greenhouse.io/harvest.html/) The purpose of this service is to make interactions with the Harvest API easier. To create a Harvest Service object, you must supply an active Harvest API key. Note that these are different than Job Board API keys.
```
<?php
$harvestService = $greenhouseService->getHarvestService();
$harvestService = $greenhouseService->getHarvestService();
?>
```

Expand Down Expand Up @@ -186,6 +186,19 @@ If the ID key is supplied in any way, that will take precedence.

Ex: [Adding a candidate to Greenhouse](https://developers.greenhouse.io/harvest.html#post-add-candidate)

**A note on paging**: As mentioned in the Harvest documentation, Greenhouse supports two methods of paging depending on the endpoint. The next page is returned in a Link header. The next page link is accessible in the Harvest Service at:

```
$harvestService->nextLink();
```

The link returned by this method will give you the next page of objects on this endpoint. Depending on which paging method the endpoint supports, the link returned will look like one of the following:

- `https://harvest.greenhouse.io/v1/<object>?page=2&per_page=100`
- `https://harvest.greenhouse.io/v1/<object>/?since_id=161963`

If the nextLink() method returns nothing, you have reached the last page.

Greenhouse includes several methods in Harvest to POST new objects. It should be noted that the creation of candidates and applications in Harvest differs from the Application service above. Documents via Harvest can only be received via binary content or by including a URL which contains the document. As such, the Harvest service uses the `body` parameter in Guzzle instead of including POST parameters.
```
$candidate = array(
Expand Down
15 changes: 15 additions & 0 deletions src/Clients/ApiClientInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,19 @@ public function formatPostParameters(Array $postParameters);
* future client.
*/
public function send($method, $url, Array $options);

/**
* These methods are to return the paging links as described in the Harvest docs. We return a Link header
* with paging information in next/previous/last format. For Harvest's two paging systems, only the
* getNextLink() method is relevant for both.
*/
public function getNextLink();
public function getPrevLink();
public function getLastLink();

/**
* Return the raw response from the client. In case users want information that is otherwise unavailable
* through this package.
*/
public function getResponse();
}
56 changes: 50 additions & 6 deletions src/Clients/GuzzleClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Greenhouse\GreenhouseToolsPhp\Clients\ApiClientInterface;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Psr7;
use Greenhouse\GreenhouseToolsPhp\Clients\Exceptions\GreenhouseAPIClientException;
use Greenhouse\GreenhouseToolsPhp\Clients\Exceptions\GreenhouseAPIResponseException;

Expand All @@ -13,7 +14,11 @@
*/
class GuzzleClient implements ApiClientInterface
{
public $guzzleResponse;
private $_client;
private $_nextLink;
private $_prevLink;
private $_lastLink;

/**
* Constructor should receive an array that would be understood by the Guzzle
Expand All @@ -39,7 +44,8 @@ public function __construct($options)
public function get($url="")
{
try {
$guzzleResponse = $this->_client->request('GET', $url);
$this->guzzleResponse = $this->_client->request('GET', $url);
$this->_setLinks();
} catch (RequestException $e) {
throw new GreenhouseAPIResponseException($e->getMessage(), 0, $e);
}
Expand All @@ -48,7 +54,7 @@ public function get($url="")
* Just return the response cast as a string. The rest of the universe need
* not be aware of Guzzle's details.
*/
return (string) $guzzleResponse->getBody();
return (string) $this->guzzleResponse->getBody();
}

/**
Expand All @@ -63,7 +69,7 @@ public function get($url="")
public function post(Array $postVars, Array $headers, $url=null)
{
try {
$guzzleResponse = $this->_client->request(
$this->guzzleResponse = $this->_client->request(
'POST',
$url,
array('multipart' => $postVars, 'headers' => $headers)
Expand All @@ -72,18 +78,19 @@ public function post(Array $postVars, Array $headers, $url=null)
throw new GreenhouseAPIResponseException($e->getMessage(), 0, $e);
}

return (string) $guzzleResponse->getBody();
return (string) $this->guzzleResponse->getBody();
}

public function send($method, $url, Array $options=array())
{
try {
$guzzleResponse = $this->_client->request($method, $url, $options);
$this->guzzleResponse = $this->_client->request($method, $url, $options);
$this->_setLinks();
} catch (RequestException $e) {
throw new GreenhouseAPIResponseException($e->getMessage(), 0, $e);
}

return (string) $guzzleResponse->getBody();
return (string) $this->guzzleResponse->getBody();
}

/**
Expand Down Expand Up @@ -122,4 +129,41 @@ public function getClient()
{
return $this->_client;
}

/**
* Set the next/prev/last links using the current response object.
*/
private function _setLinks()
{
$links = Psr7\parse_header($this->guzzleResponse->getHeader('Link'));
foreach ($links as $link) {
if ($link['rel'] == 'last') {
$this->_lastLink = str_replace(['<', '>'], '', $link[0]);
} elseif ($link['rel'] == 'next') {
$this->_nextLink = str_replace(['<', '>'], '', $link[0]);
} elseif ($link['rel'] == 'prev') {
$this->_prevLink = str_replace(['<', '>'], '', $link[0]);
}
}
}

public function getNextLink()
{
return $this->_nextLink;
}

public function getPrevLink()
{
return $this->_prevLink;
}

public function getLastLink()
{
return $this->_lastLink;
}

public function getResponse()
{
return $this->guzzleResponse;
}
}
19 changes: 19 additions & 0 deletions src/Services/HarvestService.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,25 @@ public function sendRequest()
return $this->_apiClient->send($this->_harvest['method'], $requestUrl, $options);
}

/**
* The following 3 methods are for Harvest Paging. They return paging info from the Link header (if it
* exists). Paging is complete if 'nextLink' returns nothing.
*/
public function nextLink()
{
return $this->_apiClient->getNextLink();
}

public function prevLink()
{
return $this->_apiClient->getPrevLink();
}

public function lastLink()
{
return $this->_apiClient->getLastLink();
}

/**
* In order to keep up to date with changes to the Harvest api and not trigger a re-release of this
* package each time a new method is created, the magic Call method is used to construct URLs to the
Expand Down
80 changes: 80 additions & 0 deletions tests/Clients/GuzzleClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Greenhouse\GreenhouseToolsPhp\Tests\Clients;

use Greenhouse\GreenhouseToolsPhp\Clients\GuzzleClient;
use Greenhouse\GreenhouseToolsPhp\Tests\Clients\Mocks\MockGuzzleResponse;

class GuzzleClientTest extends \PHPUnit_Framework_TestCase
{
Expand Down Expand Up @@ -72,4 +73,83 @@ public function testFormatPostParametersWithFiles()
$this->assertEquals($response[6]['name'], 'resume');
$this->assertEquals($response[6]['filename'], 'resume');
}

public function testLinksAllIncluded()
{
$linksResponse = array(
'<https://harvest.greenhouse.io/v1/candidates?page=3&per_page=100>; rel="next",' .
'<https://harvest.greenhouse.io/v1/candidates?page=1&per_page=100>; rel="prev",' .
'<https://harvest.greenhouse.io/v1/candidates?page=8273&per_page=100>; rel="last"'
);

$mockResponse = $this->createMock('Greenhouse\GreenhouseToolsPhp\Tests\Clients\Mocks\MockGuzzleResponse');
$mockResponse->method('getHeader')
->willReturn($linksResponse);
$this->client->guzzleResponse = $mockResponse;

$reflector = new \ReflectionClass('Greenhouse\GreenhouseToolsPhp\Clients\GuzzleClient');
$method = $reflector->getMethod('_setLinks');
$method->setAccessible(true);

$this->assertEquals($this->client->getNextLink(), '');
$this->assertEquals($this->client->getPrevLink(), '');
$this->assertEquals($this->client->getLastLink(), '');

$method->invokeArgs($this->client, array());

$this->assertEquals($this->client->getNextLink(), 'https://harvest.greenhouse.io/v1/candidates?page=3&per_page=100');
$this->assertEquals($this->client->getPrevLink(), 'https://harvest.greenhouse.io/v1/candidates?page=1&per_page=100');
$this->assertEquals($this->client->getLastLink(), 'https://harvest.greenhouse.io/v1/candidates?page=8273&per_page=100');
}

public function testLinksNoneIncluded()
{
$linksResponse = array('');

$mockResponse = $this->createMock('Greenhouse\GreenhouseToolsPhp\Tests\Clients\Mocks\MockGuzzleResponse');
$mockResponse->method('getHeader')
->willReturn($linksResponse);
$this->client->guzzleResponse = $mockResponse;

$reflector = new \ReflectionClass('Greenhouse\GreenhouseToolsPhp\Clients\GuzzleClient');
$method = $reflector->getMethod('_setLinks');
$method->setAccessible(true);

$this->assertEquals($this->client->getNextLink(), '');
$this->assertEquals($this->client->getPrevLink(), '');
$this->assertEquals($this->client->getLastLink(), '');

$method->invokeArgs($this->client, array());

$this->assertEquals($this->client->getNextLink(), '');
$this->assertEquals($this->client->getPrevLink(), '');
$this->assertEquals($this->client->getLastLink(), '');
}

public function testLinksSomeIncluded()
{
$linksResponse = array(
'<https://harvest.greenhouse.io/v1/candidates?page=1&per_page=100>; rel="prev",' .
'<https://harvest.greenhouse.io/v1/candidates?page=8273&per_page=100>; rel="last"'
);

$mockResponse = $this->createMock('Greenhouse\GreenhouseToolsPhp\Tests\Clients\Mocks\MockGuzzleResponse');
$mockResponse->method('getHeader')
->willReturn($linksResponse);
$this->client->guzzleResponse = $mockResponse;

$reflector = new \ReflectionClass('Greenhouse\GreenhouseToolsPhp\Clients\GuzzleClient');
$method = $reflector->getMethod('_setLinks');
$method->setAccessible(true);

$this->assertEquals($this->client->getNextLink(), '');
$this->assertEquals($this->client->getPrevLink(), '');
$this->assertEquals($this->client->getLastLink(), '');

$method->invokeArgs($this->client, array());

$this->assertEquals($this->client->getNextLink(), '');
$this->assertEquals($this->client->getPrevLink(), 'https://harvest.greenhouse.io/v1/candidates?page=1&per_page=100');
$this->assertEquals($this->client->getLastLink(), 'https://harvest.greenhouse.io/v1/candidates?page=8273&per_page=100');
}
}
17 changes: 17 additions & 0 deletions tests/Clients/Mocks/MockGuzzleResponse.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace Greenhouse\GreenhouseToolsPhp\Tests\Clients\Mocks;

/**
* This mocks a Guzzle response object. That's all. And currently only the headers in order to test the
* link setters in GuzzleClient.
*/
class MockGuzzleResponse
{
public $headers;

public function getHeader($type)
{
return $this->headers;
}
}
22 changes: 21 additions & 1 deletion tests/Services/HarvestServiceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Greenhouse\GreenhouseToolsPhp\Tests\Services;

use Greenhouse\GreenhouseToolsPhp\Services\HarvestService;
use Greenhouse\GreenhouseToolsPhp\GreenhouseService;

/**
* This test only tests that the service requests generate the expected links and arrays. This does not
Expand All @@ -15,12 +16,31 @@ public function setUp()
{
$this->harvestService = new HarvestService('greenhouse');
$apiStub = $this->getMockBuilder('\Greenhouse\GreenhouseToolsPhp\Client\GuzzleClient')
->setMethods(array('send'))
->setMethods(array('send', 'getNextLink', 'getPrevLink', 'getLastLink'))
->getMock();
$apiStub->method('getNextLink')->willReturn('http://example.com/next');
$apiStub->method('getPrevLink')->willReturn('http://example.com/prev');
$apiStub->method('getLastLink')->willReturn('http://example.com/last');

$this->harvestService->setClient($apiStub);
$this->expectedAuth = 'Basic Z3JlZW5ob3VzZTo=';
}

public function testGetNextLink()
{
$this->assertEquals($this->harvestService->nextLink(), 'http://example.com/next');
}

public function testGetPrevLink()
{
$this->assertEquals($this->harvestService->prevLink(), 'http://example.com/prev');
}

public function testGetLastLink()
{
$this->assertEquals($this->harvestService->lastLink(), 'http://example.com/last');
}

public function testGetActivityFeed()
{
$expected = array(
Expand Down

0 comments on commit 5744b07

Please sign in to comment.