Skip to content

Commit

Permalink
Prepare to publish panoptes.js (#1082)
Browse files Browse the repository at this point in the history
* Reorganize READMEs. Support query env for POST and PUT

* Remove tutorial include req
  • Loading branch information
srallen authored and rogerhutchings committed Sep 18, 2019
1 parent 78fb367 commit 5e7a1f2
Show file tree
Hide file tree
Showing 12 changed files with 117 additions and 155 deletions.
2 changes: 1 addition & 1 deletion packages/lib-panoptes-js/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import { panoptes } from '@zooniverse/panoptes-js';

## Documentation

Full API documentation is avialable at []().
The repository contains readme files under each sub-folder.

## Tests

Expand Down
2 changes: 1 addition & 1 deletion packages/lib-panoptes-js/docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
### Added
- Base helpers for HTTP requests GET, PUT, POST, and DELETE
- Client configuration file for which API host to use depending on environment
- Project resource request helpers
- Project, Subject, Tutorial, and Collection resource request helpers
- Media resource test mocks
- User resource test mocks
- Tests
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

All of the following examples will be using ES6.

## Configuring your environment
The client has a base set of request functions built on top of superagent for HTTP GET, POST, PUT, and DELETE. Each function returns a promise that returns the request response. The client does not do error handling and that is up to the consuming script or app either by using a catch on the promise or wrapping async/await in a try/catch statement. The function will error, though, if an argument is an incorrect type or if a parameter is missing that is necessary to the request.

There are currently options for running the client against the staging and production environments, which determine which endpoints are used for requests handled by the module. There are a few different ways to set which you want to use - **staging is the default environment**.
## Request Environment and Host

Note: There is a test environment specified in the config file, but it is set to use the same API hosts as staging. The test environment is specifically for automated test running.
The environment used cascades from the environment set via query param, then node process environment variable, then to a default of `'staging'`. Depending on the environment, an appropriate Panoptes host is used. For the environments of `'test'`, `'development'`, `'staging'`, the host `'https://panoptes-staging.zooniverse.org/api'` is used. For `'production'`, the host `'https://www.zooniverse.org/api'` is used.

Each of the request functions will take a query param for `env` which then will return the correctly matched host and use that as the destination to make the HTTP request. The `env` param then gets deleted from the param object before it is then passed along as params to the request itself.

The currently set environment is available from the config file exported as `env`.

Expand All @@ -28,6 +30,17 @@ If you're running an app using hash history, you'll need to add `?env=` before t
http://localhost:3000?env=production#/classify
```

Then in your app, you'll have to retrieve the environment set in the window.location.search string and pass the environment into the request in the query params argument. A package like `query-string` is helpful for parsing the search string.


``` javascript
import panoptes from '@zoonivere/panoptes-js';
import queryString from 'query-string';

const { env } = queryString.parse(window.location.search);
const response = panoptes.get('/projects', { env })
```

### Setting the environment via `PANOPTES_ENV`

This lets you choose a Panoptes environment in isolation from your Node environment, so you can use the production Panoptes API in development, for example.
Expand Down Expand Up @@ -56,6 +69,10 @@ The available host configurations are:
host: 'https://panoptes-staging.zooniverse.org/api',
oauth: 'https://panoptes-staging.zooniverse.org'
},
development: {
host: 'https://panoptes-staging.zooniverse.org/api',
oauth: 'https://panoptes-staging.zooniverse.org'
},
staging: {
host: 'https://panoptes-staging.zooniverse.org/api',
oauth: 'https://panoptes-staging.zooniverse.org'
Expand Down Expand Up @@ -106,7 +123,7 @@ panoptes.get(endpoint, query, authorization, host)

**Example**

Get many projects:
Get next page of projects:

``` javascript
panoptes.get('/projects', { page: 2 }).then((response) => {
Expand All @@ -127,14 +144,15 @@ panoptes.get('/projects/1104', { include: 'avatar,background,owners' }).then((re
**Function**

``` javascript
panoptes.post(endpoint, data, authorization, host)
panoptes.post(endpoint, data, authorization, query, host)
```

**Arguments**

- endpoint _(string)_ - the API endpoint for the request. Required.
- data _(object)_ - an object of data to send with the request. Optional.
- authorization _(string)_ - a string of the authorization type and token, i.e. `'Bearer 12345abcde'`. Optional.
- query _(object)_ - an object of query parameters to send with the request. Optional.
- host _(string)_ - available to specify a different API host. Defaults to the hosts defined in the `config.js` file. Optional.

**Returns**
Expand All @@ -156,14 +174,15 @@ panoptes.get('/projects', { private: true }).then((response) => {
**Function**

``` javascript
panoptes.post(endpoint, data, authorization, host)
panoptes.put(endpoint, data, authorization, query, host)
```

**Arguments**

- endpoint _(string)_ - the API endpoint for the request. Required.
- data _(object)_ - an object of data to send with the request. Optional.
- data _(object)_ - an object of data to send with the request. Required.
- authorization _(string)_ - a string of the authorization type and token, i.e. `'Bearer 12345abcde'`. Optional.
- query _(object)_ - an object of query parameters to send with the request. Optional.
- host _(string)_ - available to specify a different API host. Defaults to the hosts defined in the `config.js` file. Optional.

**Returns**
Expand All @@ -175,7 +194,7 @@ panoptes.post(endpoint, data, authorization, host)
Update a project:

``` javascript
panoptes.put('/projects/1104', { display_name: 'Super Zoo' }).then((response) => {
panoptes.put('/projects/1104', { projects: { display_name: 'Super Zoo' }}, { authorization: 'Bearer 12345' }).then((response) => {
// Do something with the response
});
```
Expand Down Expand Up @@ -212,9 +231,12 @@ panoptes.del('/projects/1104').then((response) => {

Using helper functions for a defined Panoptes resource in a React component. These resources have functions defined:

- [Projects](projects.md)
- [Subjects](subjects.md)
- [Tutorials](tutorials.md)
- Collections
- Projects
- Subjects
- Tutorials

A readme on specific use is available in the folder for each resource type.

The API for resource helpers will include:

Expand Down Expand Up @@ -263,4 +285,4 @@ class MyComponent extends React.Component {
)
}
}
```
```
56 changes: 30 additions & 26 deletions packages/lib-panoptes-js/src/panoptes.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,79 +24,83 @@ function determineHost (query, host) {
return config.host
}

function getQueryParams (query) {
const defaultParams = { admin: checkForAdminFlag(), http_cache: true }

if (query && Object.keys(query).length > 0) {
if (query && query.env) delete query.env
const fullQuery = Object.assign({}, query, defaultParams)
return fullQuery
} else {
return defaultParams
}
}

// TODO: Consider how to integrate a GraphQL option
function get (endpoint, query = {}, headers = {}, host) {
const defaultParams = { admin: checkForAdminFlag(), http_cache: true }
if (!endpoint) return handleMissingParameter('Request needs a defined resource endpoint')
if (typeof query !== 'object') return Promise.reject(new TypeError('Query must be an object'))

const apiHost = determineHost(query, host)
const request = superagent.get(`${apiHost}${endpoint}`)
.set('Content-Type', 'application/json')
.set('Accept', 'application/vnd.api+json; version=1')

if (headers && headers.authorization) request.set('Authorization', headers.authorization)
const queryParams = getQueryParams(query)

if (query && Object.keys(query).length > 0) {
if (typeof query !== 'object') return Promise.reject(new TypeError('Query must be an object'))
if (query && query.env) delete query.env
const fullQuery = Object.assign({}, query, defaultParams)
request.query(fullQuery)
} else {
request.query(defaultParams)
}

return request.then(response => response)
return request.query(queryParams).then(response => response)
}

// TODO support env query
function post (endpoint, data, headers = {}, host) {
const defaultParams = { admin: checkForAdminFlag(), http_cache: true }

function post(endpoint, data, headers = {}, query = {}, host) {
if (!endpoint) return handleMissingParameter('Request needs a defined resource endpoint')
const apiHost = host || config.host
if (typeof query !== 'object') return Promise.reject(new TypeError('Query must be an object'))

const apiHost = determineHost(query, host)
const request = superagent.post(`${apiHost}${endpoint}`)
.set('Content-Type', 'application/json')
.set('Accept', 'application/vnd.api+json; version=1')

if (headers && headers.authorization) request.set('Authorization', headers.authorization)
const queryParams = getQueryParams(query)

return request.query(defaultParams)
return request.query(queryParams)
.send(data)
.then(response => response)
}

// TODO: support env query
function put (endpoint, data, headers = {}, host) {
const defaultParams = { admin: checkForAdminFlag(), http_cache: true }
function put(endpoint, data, headers = {}, query = {}, host) {
if (!endpoint) return handleMissingParameter('Request needs a defined resource endpoint')
if (!data) return handleMissingParameter('Request needs a defined data for update')
const apiHost = host || config.host
if (typeof query !== 'object') return Promise.reject(new TypeError('Query must be an object'))

const apiHost = determineHost(query, host)
const request = superagent.put(`${apiHost}${endpoint}`)
.set('Content-Type', 'application/json')
.set('Accept', 'application/vnd.api+json; version=1')

if (headers && headers.authorization) request.set('Authorization', headers.authorization)
if (headers && headers.etag) request.set('If-Match', headers.etag)
const queryParams = getQueryParams(query)

return request.query(defaultParams)
return request.query(queryParams)
.send(data)
.then(response => response)
}

function del (endpoint, query = {}, headers = {}, host) {
const defaultParams = { admin: checkForAdminFlag(), http_cache: true }

if (!endpoint) return handleMissingParameter('Request needs a defined resource endpoint')
const apiHost = determineHost(query, host)
if (typeof query !== 'object') return Promise.reject(new TypeError('Query must be an object'))

const apiHost = determineHost(query, host)
const request = superagent.delete(`${apiHost}${endpoint}`)
.set('Content-Type', 'application/json')
.set('Accept', 'application/vnd.api+json; version=1')

if (headers && headers.authorization) request.set('Authorization', headers.authorization)
const queryParams = getQueryParams(query)

return request.query(defaultParams)
return request.query(queryParams)
.then(response => response)
}

Expand Down
56 changes: 50 additions & 6 deletions packages/lib-panoptes-js/src/panoptes.spec.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const { expect } = require('chai')
const nock = require('nock')

const { config } = require('./config')
const { baseConfig, config } = require('./config')
const panoptes = require('./panoptes')

describe('panoptes.js', function () {
Expand All @@ -28,6 +28,7 @@ describe('panoptes.js', function () {
testAcceptHeader('get', endpoint)
testAuthHeader('get', endpoint)
testHttpCache('get', endpoint)
testEnvParam('get', endpoint, expectedResponse)
testAdminParam('get', endpoint)
testNoEndpoint('get')

Expand Down Expand Up @@ -69,13 +70,14 @@ describe('panoptes.js', function () {
testAcceptHeader('post', endpoint)
testAuthHeader('post', endpoint)
testHttpCache('post', endpoint)
testEnvParam('post', endpoint, expectedResponse)
testAdminParam('post', endpoint)
testNoEndpoint('post')

it('should send any data params if defined', async function () {
const params = { display_name: 'My project' }
const response = await panoptes.post(endpoint, params)
expect(response.request._data).to.deep.equal(params)
const data = { display_name: 'My project' }
const response = await panoptes.post(endpoint, data)
expect(response.request._data).to.deep.equal(data)
})
})

Expand Down Expand Up @@ -103,6 +105,7 @@ describe('panoptes.js', function () {
testAcceptHeader('put', endpoint, update)
testAuthHeader('put', endpoint, update)
testHttpCache('put', endpoint, update)
testEnvParam('put', endpoint, expectedResponse, update)
testAdminParam('put', endpoint, update)
testNoEndpoint('put')

Expand Down Expand Up @@ -141,8 +144,22 @@ describe('panoptes.js', function () {
testAcceptHeader('del', endpoint)
testAuthHeader('del', endpoint)
testHttpCache('del', endpoint)
testEnvParam('del', endpoint, expectedResponse)
testAdminParam('del', endpoint)
testNoEndpoint('del')

it('should use the env query param to set the host only', async function () {
const queryParams = { env: 'production' }
const { host } = baseConfig[queryParams.env]

nock(host).delete(uri => uri.includes(endpoint))
.query(true)
.reply(200, expectedResponse)

const response = await panoptes.del(endpoint, queryParams)
expect(response.request.url.includes(host)).to.be.true()
expect(response.req.path.includes('env=production')).to.be.false()
})
})

function testExpectedResponse (method, endpoint, expectedResponse, update = null) {
Expand All @@ -152,14 +169,18 @@ describe('panoptes.js', function () {
})
}

function testHostArg (method, endpoint, expectedResponse, update = null) {
function testHostArg (method, endpoint, expectedResponse, update = null, query = null) {
it('should use the host from the function call if defined', async function () {
const mockAPIHost = 'https://my-api.com'

const isDel = method === 'del'
const isPost = method === 'post'
const isPut = method === 'put'
// Nock calls it 'delete', panoptes-js calls it 'del'
const nockMethod = isDel ? 'delete' : method
const methodArgs = [endpoint, update, null, mockAPIHost]
const methodArgs = (isPost || isPut) ?
[endpoint, update, null, query, mockAPIHost] :
[endpoint, update, null, mockAPIHost]

nock(mockAPIHost)[nockMethod](uri => uri.includes(endpoint))
.query(true)
Expand Down Expand Up @@ -208,6 +229,29 @@ describe('panoptes.js', function () {
})
}

function testEnvParam (method, endpoint, expectedResponse) {
it('should use the env query param to set the host only', async function () {
const envParams = { env: 'production' }
const { host } = baseConfig[envParams.env]
const isDel = method === 'del'
const isPost = method === 'post'
const isPut = method === 'put'
// Nock calls it 'delete', panoptes-js calls it 'del'
const nockMethod = isDel ? 'delete' : method
const methodArgs = (isPost || isPut) ?
[endpoint, {}, '', envParams] :
[endpoint, envParams]

nock(host)[nockMethod](uri => uri.includes(endpoint))
.query(true)
.reply(200, expectedResponse)

const response = await panoptes[method].apply(this, methodArgs)
expect(response.request.url.includes(host)).to.be.true()
expect(response.req.path.includes('env=production')).to.be.false()
})
}

function testAdminParam (method, endpoint, update = null) {
it('should add the admin default query param if flag is found in local storage', async function () {
localStorage.setItem('adminFlag', true)
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loading

0 comments on commit 5e7a1f2

Please sign in to comment.