Skip to content

Commit

Permalink
docs: finish data source tutorial
Browse files Browse the repository at this point in the history
This pretty much immediately needs a rewrite (see #31), but
it’s good enough for now.

close #21
  • Loading branch information
jlengstorf committed Oct 21, 2017
1 parent 049ed1b commit 96afd3f
Show file tree
Hide file tree
Showing 10 changed files with 811 additions and 13 deletions.
26 changes: 26 additions & 0 deletions docs/_content/api/errors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
title: Error Handling
---

GrAMPS comes with optional error handling.

-
{:toc}

##### `GrampsError([errorData])`

TKTK

###### Parameters

- `TKTK`: TKTK

###### Return Value

TKTK

###### Example

```js
// TKTK
```
92 changes: 92 additions & 0 deletions docs/_content/api/testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
---
title: Test Helpers
---

These functions are used to make it easier to write tests for GrAMPS data sources.

-
{:toc}

##### `expectMockFields(resolver, fieldArray)`

Creates Jest tests for each field in `fieldArray` to ensure that the `resolver` returns a mock value for it.

> **NOTE:** This helper is intended for use with mock resolvers.
###### Parameters

- `resolver`: a mock resolver for a given GraphQL type
- `fieldArray`: an array of field names that should be mocked

###### Return Value

Returns a [Jest test](https://facebook.github.io/jest/docs/en/api.html#testname-fn).

###### Example

Assuming type `PFX_MyType` with two fields, `fieldOne` and `fieldTwo`, which both have mock resolvers defined:

```js
import resolvers from '../src/resolvers';

describe('PFX_MyType', () => {
const mockResolver = resolvers.mockResolvers.PFX_MyType();
expectMockFields(mockResolver, ['fieldOne', 'fieldTwo']);
});
```

##### `expectMockList(resolver, fieldArray)`

Creates Jest tests for each field in `fieldArray` to ensure that the `resolver` returns an instance of [`MockList`](http://dev.apollodata.com/tools/graphql-tools/mocking.html#Using-MockList-in-resolvers).

> **NOTE:** This helper is intended for use with mock resolvers.
###### Parameters

- `resolver`: a mock resolver for a given GraphQL type
- `fieldArray`: an array of field names that should be mocked

###### Return Value

Returns an array of [Jest tests](https://facebook.github.io/jest/docs/en/api.html#testname-fn).

###### Example

Assuming type `PFX_MyType` with two fields, `fieldOne` and `fieldTwo`, which both use `MockList` to generate an array of mock data:

```js
import resolvers from '../src/resolvers';

describe('PFX_MyType', () => {
const mockResolver = resolvers.mockResolvers.PFX_MyType();
expectMockList(mockResolver, ['fieldOne', 'fieldTwo']);
});
```

##### `expectNullable(resolver, fieldArray)`

Creates Jest tests for each field in `fieldArray` to ensure that the `resolver` returns `null` if a value isn’t found for the given field.

> **NOTE:** This helper is intended for use with field resolvers.
###### Parameters

- `resolver`: a mock resolver for a given GraphQL type
- `fieldArray`: an array of field names that should be mocked

###### Return Value

Returns an array of [Jest tests](https://facebook.github.io/jest/docs/en/api.html#testname-fn).

###### Example

Assuming type `PFX_MyType` with one nullable field, `fieldOne`:

```js
import resolvers from '../src/resolvers';

describe('PFX_MyType', () => {
const resolver = resolvers.dataResolvers.PFX_MyType;
expectNullable(resolver, ['fieldOne']);
});
```
88 changes: 88 additions & 0 deletions docs/_content/data-source/testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
---
title: How to Test GrAMPS Data Sources
weight: 450
---

TKTK

## How to Convert Keyed Objects to Arrays

Something that can be tricky with GraphQL is the expectation that we know _exactly_ what fields are going to arrive. But we don’t always have that information — for example, the `filmography` field that comes back from the IMDB API when searching a person ([example](https://www.theimdbapi.org/api/find/person?name=jim+carrey)) is an object with keys that refer to the type of appearance that person made. Since we don’t have a master list of all available positions someone can hold, we aren’t able to define the schema with expected object keys.

Instead, we need to transform a response like this:

```json
{
"filmography": {
"actor": [
{
"imdb_id": "tt1234567",
"...": "..."
}
],
"director": [
{
"imdb_id": "tt7654321",
"...": "..."
}
]
}
}
```

Into something more like this:

```json
{
"filmography": [
{
"position": "actor",
"imdb_id": "tt1234567",
"...": "..."
},
{
"position": "director",
"imdb_id": "tt7654321",
"...": "..."
}
]
}
```

This way, we can create a predictable data shape for our schema, despite not knowing exactly what the value of `position` will be.

The resolver we end up writing to account for this might look a little intimidating at first, especially if you’re not familiar with [array methods](https://mzl.la/2yX77TK) and/or [ES2015+ syntax](https://git.io/vdNeC), but let’s take a look, then break down what’s happening.

```js
IMDB_Person: {
// Convert the filmography object into an array for filtering/typing.
filmography: ({ filmography }, { filter = 'all' }) =>
Object.keys(filmography)
.reduce(
(works, position) =>
works.concat(
filmography[position].map(work => ({
position,
...work,
})),
),
[],
)
.filter(work => filter === 'all' || work.position === filter),
},
```

Let’s walk through this function step-by-step:

1. First, we use [`Object.keys()`](https://mzl.la/2hWjc0P) to get an array of
the `filmography` object‘s keys (e.g. `['actor', 'director']`)
2. Next, we use [`.reduce()`](https://mzl.la/2xe0nPv) to transform our array
of key names into an array of actual filmography objects. The `works` argument contains the entries we’ve added so far (this starts as an empty array), and `position` is the current key (e.g. `actor`)
3. In the reducer, we use [`.concat()`](https://mzl.la/2gxku5y) to combine the
`works` array with a new array containing filmography objects
4. The filmography objects are created by accessing the current position’s
array (e.g. if `position = 'actor'`, then `filmography[position] === filmography.actor`)
5. Using [`.map()`](https://mzl.la/2ipT3v7), we loop through the position’s
array of works and return a new object, which is created by adding the position (e.g. `position: 'actor'`), then adding all the other fields from the original object with the spread operator
6. After we have the full array of works, we can apply an optional filter to
the array (e.g. the query can be `filmography(filter: 'actor')`) using the `.filter()` method
1 change: 1 addition & 0 deletions docs/_content/data-source/tutorial-connector.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Now that we’ve [set up the data source]({{ site.github.url }}/data-source/tuto
- [Create a Model]({{ site.github.url }}/data-source/tutorial-model)
- [Write a GraphQL Schema]({{ site.github.url }}/data-source/tutorial-schema)
- [Write Resolvers]({{ site.github.url }}/data-source/tutorial-resolvers)
- [Use Development Modes]({{ site.github.url }}/data-source/tutorial-dev)

## In This Section
{:.no_toc}
Expand Down
86 changes: 86 additions & 0 deletions docs/_content/data-source/tutorial-dev.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
---
title: "Tutorial: Development Modes"
weight: 60
---

At this point, we have our [connector]({{ site.github.url }}/data-source/tutorial-connector), [model]({{ site.github.url }}/data-source/tutorial-model), [schema]({{ site.github.url }}/data-source/tutorial-schema), and [resolvers]({{ site.github.url }}/data-source/tutorial-resolvers) built and ready to go. All that’s left to do is fire up our data source in mock and live mode to make sure it’s working.

## Table of Contents
{:.no_toc}

- [Initial Data Source Setup]({{ site.github.url }}/data-source/tutorial-setup)
- [Create a Connector]({{ site.github.url }}/data-source/tutorial-connector)
- [Create a Model]({{ site.github.url }}/data-source/tutorial-model)
- [Write a GraphQL Schema]({{ site.github.url }}/data-source/tutorial-schema)
- [Write Resolvers]({{ site.github.url }}/data-source/tutorial-resolvers)
- **> [Use Development Modes]({{ site.github.url }}/data-source/tutorial-dev)**

## In This Section
{:.no_toc}

-
{:toc}

## Run the Data Source in Mock Data Mode

To test the data source with mock data, open your terminal and run the following command:

```bash
yarn mock-data
```

Then open `http://localhost:8080/graphiql` in your browser and run a test query. You’ll see the GraphiQL user interface, and you can make a test query like this:

```graphql
query searchMoviesByTitle($options: IMDB_MovieSearchInput) {
searchMoviesByTitle(options: $options) {
imdb_id
title
genre
year
}
}
```

The result will be mock data that looks something like this:

{% include figure.html
src="/assets/img/data-source-mock-data.png"
alt="A GrAMPS data source running in mock data mode"
caption="A GrAMPS data source running in mock data mode"
%}

## Run the Data Source in Live Data Mode

To use live data, stop the server (`control` + `C`), then run this command:

```bash
yarn live-data
```

Then go back to `http://localhost:8080/graphiql` and run the same command we used with the mock data:

```graphql
query searchMoviesByTitle($options: IMDB_MovieSearchInput) {
searchMoviesByTitle(options: $options) {
imdb_id
title
genre
year
}
}
```

This time you’ll get back a response with live data!

> **NOTE:** Unfortunately, it turns out the IMDB API is pretty unreliable and
> frequently goes down. It’s
> [on our todo list](https://github.com/gramps-graphql/gramps-express/issues/31)
> to update this tutorial with a more reliable data source. (Want to contribute?
> We’d love to have you participate!)
## Further Reading

- [Review the full source code of the GrAMPS data source for the IMBDAPI](https://github.com/gramps-graphql/data-source-imdbapi)
- [Learn more about testing GrAMPS data sources]({{ site.github.url}}/data-source/testing)
- [Learn how to publish GrAMPS data sources]({{ site.github.url}}/data-source/publishing)
3 changes: 2 additions & 1 deletion docs/_content/data-source/tutorial-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Now that the [connector is ready]({{ site.github.url }}/data-source/tutorial-con
- **> [Create a Model]({{ site.github.url }}/data-source/tutorial-model)**
- [Write a GraphQL Schema]({{ site.github.url }}/data-source/tutorial-schema)
- [Write Resolvers]({{ site.github.url }}/data-source/tutorial-resolvers)
- [Use Development Modes]({{ site.github.url }}/data-source/tutorial-dev)

## In This Section
{:.no_toc}
Expand Down Expand Up @@ -183,7 +184,7 @@ And let’s also make sure that our `getQueryString()` helper is dropping empty
});
```

Finally, let’s make sure we get a [`GrampsError`]({{ site.github.url }}/api/errors#grampserror) if the request fails:
Finally, let’s make sure we get a [`GrampsError`]({{ site.github.url }}/api/errors/#grampserrorerrordata) if the request fails:

```js
it('throws a GrampsError if something goes wrong', async () => {
Expand Down
Loading

0 comments on commit 96afd3f

Please sign in to comment.