Skip to content

Commit 8e1aea5

Browse files
committed
Make filters work
1 parent 0237d43 commit 8e1aea5

File tree

8 files changed

+195
-29
lines changed

8 files changed

+195
-29
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
"express": "~4.15.3",
6767
"express-graphql": "^0.6.6",
6868
"graphql": "~0.10.3",
69+
"graphql-errors": "^2.1.0",
6970
"graphql-tag": "~2.0.0",
7071
"graphql-tools": "~1.1.0",
7172
"inflection": "~1.12.0",

src/introspection/getFieldsFromEntities.js

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,27 +15,28 @@ import getValuesFromEntities from './getValuesFromEntities';
1515
* {
1616
* "id": 2,
1717
* "title": "Sic Dolor amet",
18-
* "views": 65,
1918
* "user_id": 456,
2019
* },
2120
* ];
2221
* const types = getFieldsFromEntities(entities);
2322
* // {
24-
* // id: { type: graphql.GraphQLString },
25-
* // title: { type: graphql.GraphQLString },
26-
* // views: { type: graphql.GraphQLInt },
27-
* // user_id: { type: graphql.GraphQLString },
23+
* // id: { type: new GraphQLNonNull(GraphQLString) },
24+
* // title: { type: new GraphQLNonNull(GraphQLString) },
25+
* // views: { type: GraphQLInt },
26+
* // user_id: { type: new GraphQLNonNull(GraphQLString) },
2827
* // };
2928
*/
30-
export default entities => {
29+
export default (entities, checkRequired = true) => {
3130
const fieldValues = getValuesFromEntities(entities);
3231
const nbValues = entities.length;
3332
return Object.keys(fieldValues).reduce((fields, fieldName) => {
3433
fields[fieldName] = {
3534
type: getTypeFromValues(
3635
fieldName,
3736
fieldValues[fieldName],
38-
fieldValues[fieldName].length === nbValues,
37+
checkRequired
38+
? fieldValues[fieldName].length === nbValues
39+
: false,
3940
),
4041
};
4142
return fields;
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { GraphQLInputObjectType, GraphQLString } from 'graphql';
2+
import getFieldsFromEntities from './getFieldsFromEntities';
3+
import { getTypeFromKey } from '../nameConverter';
4+
5+
/**
6+
* Get a list of GraphQLObjectType for filtering data
7+
*
8+
* @example
9+
* const data = {
10+
* "posts": [
11+
* {
12+
* "id": 1,
13+
* "title": "Lorem Ipsum",
14+
* "views": 254,
15+
* "user_id": 123,
16+
* },
17+
* {
18+
* "id": 2,
19+
* "title": "Sic Dolor amet",
20+
* "views": 65,
21+
* "user_id": 456,
22+
* },
23+
* ],
24+
* "users": [
25+
* {
26+
* "id": 123,
27+
* "name": "John Doe"
28+
* },
29+
* {
30+
* "id": 456,
31+
* "name": "Jane Doe"
32+
* }
33+
* ],
34+
* };
35+
* const types = getFilterTypesFromData(data);
36+
* // {
37+
* // posts: new GraphQLInputObjectType({
38+
* // name: "PostFilter",
39+
* // fields: {
40+
* // q: { type: GraphQLString },
41+
* // id: { type: GraphQLString },
42+
* // title: { type: GraphQLString },
43+
* // views: { type: GraphQLInt },
44+
* // user_id: { type: GraphQLString },
45+
* // }
46+
* // }),
47+
* // users: new GraphQLObjectType({
48+
* // name: "UserFilter",
49+
* // fields: {
50+
* // q: { type: GraphQLString },
51+
* // id: { type: GraphQLString },
52+
* // name: { type: GraphQLString },
53+
* // }
54+
* // }),
55+
* // }
56+
*/
57+
export default data =>
58+
Object.keys(data).reduce(
59+
(types, key) => ({
60+
...types,
61+
[getTypeFromKey(key)]: new GraphQLInputObjectType({
62+
name: `${getTypeFromKey(key)}Filter`,
63+
fields: {
64+
q: { type: GraphQLString },
65+
...getFieldsFromEntities(data[key], false),
66+
},
67+
}),
68+
}),
69+
{},
70+
);
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import getFilterTypesFromData from './getFilterTypesFromData';
2+
3+
const data = {
4+
posts: [
5+
{
6+
id: 1,
7+
title: 'Lorem Ipsum',
8+
views: 254,
9+
user_id: 123,
10+
},
11+
{
12+
id: 2,
13+
title: 'Sic Dolor amet',
14+
views: 65,
15+
user_id: 456,
16+
},
17+
],
18+
users: [
19+
{
20+
id: 123,
21+
name: 'John Doe',
22+
},
23+
{
24+
id: 456,
25+
name: 'Jane Doe',
26+
},
27+
],
28+
};
29+
30+
/*
31+
const PostType = new GraphQLObjectType({
32+
name: 'PostFilter',
33+
fields: {
34+
q: { type: GraphQLString },
35+
id: { type: GraphQLID },
36+
title: { type: GraphQLString },
37+
views: { type: GraphQLInt },
38+
user_id: { type: GraphQLID },
39+
},
40+
});
41+
const UsersType = new GraphQLObjectType({
42+
name: 'UserFilter',
43+
fields: {
44+
q: { type: GraphQLString },
45+
id: { type: GraphQLID },
46+
name: { type: GraphQLString },
47+
},
48+
});
49+
*/
50+
51+
test('creates one filter type per entity', () => {
52+
const filterTypes = getFilterTypesFromData(data);
53+
expect(Object.values(filterTypes).map(type => type.toString())).toEqual([
54+
'PostFilter',
55+
'UserFilter',
56+
]);
57+
});
58+
59+
test('creates one filter field per entity field', () => {
60+
const filterTypes = getFilterTypesFromData(data);
61+
const PostFilterFields = filterTypes.Post.getFields();
62+
expect(PostFilterFields.id.type.toString()).toEqual('ID');
63+
expect(PostFilterFields.title.type.toString()).toEqual('String');
64+
expect(PostFilterFields.views.type.toString()).toEqual('Int');
65+
expect(PostFilterFields.user_id.type.toString()).toEqual('ID');
66+
const CommentFilterFields = filterTypes.User.getFields();
67+
expect(CommentFilterFields.id.type.toString()).toEqual('ID');
68+
expect(CommentFilterFields.name.type.toString()).toEqual('String');
69+
});
70+
71+
test('creates one q field per entity field', () => {
72+
const filterTypes = getFilterTypesFromData(data);
73+
const PostFilterFields = filterTypes.Post.getFields();
74+
expect(PostFilterFields.q.type.toString()).toEqual('String');
75+
const CommentFilterFields = filterTypes.User.getFields();
76+
expect(CommentFilterFields.q.type.toString()).toEqual('String');
77+
});

src/introspection/getSchemaFromData.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
import { pluralize, camelize } from 'inflection';
1414

1515
import getTypesFromData from './getTypesFromData';
16+
import getFilterTypesFromData from './getFilterTypesFromData';
1617
import { isRelationshipField } from '../relationships';
1718
import { getRelatedType } from '../nameConverter';
1819

@@ -82,6 +83,8 @@ export default data => {
8283
return types;
8384
}, {});
8485

86+
const filterTypesByName = getFilterTypesFromData(data);
87+
8588
const listMetadataType = new GraphQLObjectType({
8689
name: 'ListMetadata',
8790
fields: {
@@ -105,7 +108,7 @@ export default data => {
105108
perPage: { type: GraphQLInt },
106109
sortField: { type: GraphQLString },
107110
sortOrder: { type: GraphQLString },
108-
filter: { type: GraphQLString },
111+
filter: { type: filterTypesByName[type.name] },
109112
},
110113
};
111114
fields[`_all${camelize(pluralize(type.name))}Meta`] = {

src/jsonGraphqlExpress.js

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import graphqlHTTP from 'express-graphql';
22
import { printSchema } from 'graphql';
3-
const { makeExecutableSchema } = require('graphql-tools');
3+
import { maskErrors } from 'graphql-errors';
4+
import { makeExecutableSchema } from 'graphql-tools';
45
import getSchemaFromData from './introspection/getSchemaFromData';
56
import resolver from './resolver';
67

@@ -48,11 +49,14 @@ import resolver from './resolver';
4849
*
4950
* app.listen(PORT);
5051
*/
51-
export default data =>
52-
graphqlHTTP({
53-
schema: makeExecutableSchema({
54-
typeDefs: printSchema(getSchemaFromData(data)),
55-
resolvers: resolver(data),
56-
}),
52+
export default data => {
53+
const schema = makeExecutableSchema({
54+
typeDefs: printSchema(getSchemaFromData(data)),
55+
resolvers: resolver(data),
56+
});
57+
maskErrors(schema);
58+
return graphqlHTTP({
59+
schema,
5760
graphiql: true,
5861
});
62+
};

src/resolver/Query/all.js

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
export default entityData => (
22
_,
3-
{ sortField, sortOrder = 'asc', page, perPage = 25, filter = '{}' },
3+
{ sortField, sortOrder = 'asc', page, perPage = 25, filter = {} },
44
) => {
5-
const filters = JSON.parse(filter);
65
let items = [...entityData];
76

87
if (sortField) {
@@ -17,42 +16,42 @@ export default entityData => (
1716
return 0;
1817
});
1918
}
20-
if (filters.ids) {
21-
items = items.filter(d => filters.ids.includes(d.id.toString()));
19+
if (filter.ids) {
20+
items = items.filter(d => filter.ids.includes(d.id.toString()));
2221
} else {
23-
Object.keys(filters).filter(key => key !== 'q').forEach(key => {
22+
Object.keys(filter).filter(key => key !== 'q').forEach(key => {
2423
if (key.indexOf('_lte') !== -1) {
2524
// less than or equal
2625
const realKey = key.replace(/(_lte)$/, '');
27-
items = items.filter(d => d[realKey] <= filters[key]);
26+
items = items.filter(d => d[realKey] <= filter[key]);
2827
return;
2928
}
3029
if (key.indexOf('_gte') !== -1) {
3130
// less than or equal
3231
const realKey = key.replace(/(_gte)$/, '');
33-
items = items.filter(d => d[realKey] >= filters[key]);
32+
items = items.filter(d => d[realKey] >= filter[key]);
3433
return;
3534
}
3635
if (key.indexOf('_lt') !== -1) {
3736
// less than or equal
3837
const realKey = key.replace(/(_lt)$/, '');
39-
items = items.filter(d => d[realKey] < filters[key]);
38+
items = items.filter(d => d[realKey] < filter[key]);
4039
return;
4140
}
4241
if (key.indexOf('_gt') !== -1) {
4342
// less than or equal
4443
const realKey = key.replace(/(_gt)$/, '');
45-
items = items.filter(d => d[realKey] > filters[key]);
44+
items = items.filter(d => d[realKey] > filter[key]);
4645
return;
4746
}
4847

49-
items = items.filter(d => d[key] == filters[key]);
48+
items = items.filter(d => d[key] == filter[key]);
5049
});
5150

52-
if (filters.q) {
51+
if (filter.q) {
5352
items = items.filter(d =>
54-
Object.keys(d).some(key =>
55-
d[key].toString().includes(filters.q),
53+
Object.keys(d).some(
54+
key => d[key] && d[key].toString().includes(filter.q),
5655
),
5756
);
5857
}

yarn.lock

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -923,7 +923,7 @@ babel-register@^6.24.1:
923923
mkdirp "^0.5.1"
924924
source-map-support "^0.4.2"
925925

926-
babel-runtime@^6.18.0, babel-runtime@^6.22.0:
926+
babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.6.1:
927927
version "6.23.0"
928928
resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.23.0.tgz#0a9489f144de70efb3ce4300accdb329e2fc543b"
929929
dependencies:
@@ -2230,6 +2230,13 @@ graphql-anywhere@^3.0.1:
22302230
version "3.1.0"
22312231
resolved "https://registry.yarnpkg.com/graphql-anywhere/-/graphql-anywhere-3.1.0.tgz#3ea0d8e8646b5cee68035016a9a7557c15c21e96"
22322232

2233+
graphql-errors@^2.1.0:
2234+
version "2.1.0"
2235+
resolved "https://registry.yarnpkg.com/graphql-errors/-/graphql-errors-2.1.0.tgz#831c8c491b354859ee7a0c07bff101af64731195"
2236+
dependencies:
2237+
babel-runtime "^6.6.1"
2238+
uuid "^2.0.2"
2239+
22332240
graphql-tag@^2.0.0, graphql-tag@~2.0.0:
22342241
version "2.0.0"
22352242
resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.0.0.tgz#f3efe3b4d64f33bfe8479ae06a461c9d72f2a6fe"
@@ -4716,6 +4723,10 @@ utils-merge@1.0.0:
47164723
version "1.0.0"
47174724
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.0.tgz#0294fb922bb9375153541c4f7096231f287c8af8"
47184725

4726+
uuid@^2.0.2:
4727+
version "2.0.3"
4728+
resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a"
4729+
47194730
uuid@^3.0.0, uuid@^3.0.1:
47204731
version "3.1.0"
47214732
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04"

0 commit comments

Comments
 (0)