Merge branch 'master' into chore/strapi-new-improvments

This commit is contained in:
Alexandre BODIN 2019-07-05 18:41:36 +02:00 committed by GitHub
commit 250cfac82d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 128 additions and 13 deletions

View File

@ -31,6 +31,7 @@ Filters are used as a suffix of a field name:
- `ncontains`: Doesn't contain - `ncontains`: Doesn't contain
- `containss`: Contains case sensitive - `containss`: Contains case sensitive
- `ncontainss`: Doesn't contain case sensitive - `ncontainss`: Doesn't contain case sensitive
- `null`: Is null/Is not null
#### Examples #### Examples

View File

@ -199,6 +199,7 @@ You can also apply different parameters to the query to make more complex querie
- `<field>_containss`: Contains sensitive. - `<field>_containss`: Contains sensitive.
- `<field>_in`: Matches any value in the array of values. - `<field>_in`: Matches any value in the array of values.
- `<field>_nin`: Doesn't match any value in the array of values. - `<field>_nin`: Doesn't match any value in the array of values.
- `<field>_null`: Equals null/Not equals null
Return the second decade of users which have an email that contains `@strapi.io` ordered by username. Return the second decade of users which have an email that contains `@strapi.io` ordered by username.
@ -695,7 +696,6 @@ module.exports = {
In this example, the policy `isAuthenticated` located in the `users-permissions` plugin will be executed first. Then, the `isOwner` policy located in the `Post` API `./api/post/config/policies/isOwner.js`. Next, it will execute the `logging` policy located in `./config/policies/logging.js`. Finally, the resolver will be executed. In this example, the policy `isAuthenticated` located in the `users-permissions` plugin will be executed first. Then, the `isOwner` policy located in the `Post` API `./api/post/config/policies/isOwner.js`. Next, it will execute the `logging` policy located in `./config/policies/logging.js`. Finally, the resolver will be executed.
::: note ::: note
There is no custom resolver in that case, so it will execute the default resolver (Post.find) provided by the Shadow CRUD feature. There is no custom resolver in that case, so it will execute the default resolver (Post.find) provided by the Shadow CRUD feature.
::: :::

View File

@ -28,6 +28,7 @@ Easily filter results according to fields values.
- `_containss`: Contains case sensitive - `_containss`: Contains case sensitive
- `_in`: Matches any value in the array of values - `_in`: Matches any value in the array of values
- `_nin`: Doesn't match any value in the array of values - `_nin`: Doesn't match any value in the array of values
- `_null`: Equals null/Not equals null
#### Examples #### Examples

View File

@ -245,6 +245,9 @@ const buildWhereClause = ({ qb, field, operator, value }) => {
return qb.where(field, 'like', `%${value}%`); return qb.where(field, 'like', `%${value}%`);
case 'ncontainss': case 'ncontainss':
return qb.whereNot(field, 'like', `%${value}%`); return qb.whereNot(field, 'like', `%${value}%`);
case 'null': {
return value ? qb.whereNull(field) : qb.whereNotNull(field);
}
default: default:
throw new Error(`Unhandled whereClause : ${field} ${operator} ${value}`); throw new Error(`Unhandled whereClause : ${field} ${operator} ${value}`);

View File

@ -438,6 +438,9 @@ const buildWhereClause = ({ field, operator, value }) => {
$not: new RegExp(val), $not: new RegExp(val),
}, },
}; };
case 'null': {
return value ? { [field]: { $eq: null } } : { [field]: { $ne: null } };
}
default: default:
throw new Error(`Unhandled whereClause : ${field} ${operator} ${value}`); throw new Error(`Unhandled whereClause : ${field} ${operator} ${value}`);

View File

@ -25,6 +25,12 @@ const postModel = {
type: 'biginteger', type: 'biginteger',
}, },
}, },
{
name: 'nullable',
params: {
type: 'string',
},
},
], ],
connection: 'default', connection: 'default',
name: 'post', name: 'post',
@ -54,8 +60,8 @@ describe('Test Graphql API End to End', () => {
describe('Test CRUD', () => { describe('Test CRUD', () => {
const postsPayload = [ const postsPayload = [
{ name: 'post 1', bigint: 1316130638171 }, { name: 'post 1', bigint: 1316130638171, nullable: 'value' },
{ name: 'post 2', bigint: 1416130639261 }, { name: 'post 2', bigint: 1416130639261, nullable: null },
]; ];
let data = { let data = {
posts: [], posts: [],
@ -69,6 +75,7 @@ describe('Test Graphql API End to End', () => {
post { post {
name name
bigint bigint
nullable
} }
} }
} }
@ -100,6 +107,7 @@ describe('Test Graphql API End to End', () => {
id id
name name
bigint bigint
nullable
} }
} }
`, `,
@ -126,6 +134,7 @@ describe('Test Graphql API End to End', () => {
id id
name name
bigint bigint
nullable
} }
} }
`, `,
@ -147,6 +156,7 @@ describe('Test Graphql API End to End', () => {
id id
name name
bigint bigint
nullable
} }
} }
`, `,
@ -168,6 +178,7 @@ describe('Test Graphql API End to End', () => {
id id
name name
bigint bigint
nullable
} }
} }
`, `,
@ -229,7 +240,7 @@ describe('Test Graphql API End to End', () => {
], ],
[ [
{ {
name_in: ['post 1', 'post 2'], name_in: ['post 1', 'post 2', 'post 3'],
}, },
postsPayload, postsPayload,
], ],
@ -239,6 +250,18 @@ describe('Test Graphql API End to End', () => {
}, },
[postsPayload[0]], [postsPayload[0]],
], ],
[
{
nullable_null: true,
},
[postsPayload[1]],
],
[
{
nullable_null: false,
},
[postsPayload[0]],
],
])('List posts with where clause %o', async (where, expected) => { ])('List posts with where clause %o', async (where, expected) => {
const res = await graphqlQuery({ const res = await graphqlQuery({
query: /* GraphQL */ ` query: /* GraphQL */ `
@ -246,6 +269,7 @@ describe('Test Graphql API End to End', () => {
posts(where: $where) { posts(where: $where) {
name name
bigint bigint
nullable
} }
} }
`, `,
@ -278,6 +302,7 @@ describe('Test Graphql API End to End', () => {
id id
name name
bigint bigint
nullable
} }
} }
`, `,
@ -360,4 +385,4 @@ describe('Test Graphql API End to End', () => {
} }
}); });
}); });
}); });

View File

@ -10,6 +10,8 @@
const _ = require('lodash'); const _ = require('lodash');
const AWS = require('aws-sdk'); const AWS = require('aws-sdk');
const trimParam = str => typeof str === "string" ? str.trim() : undefined
module.exports = { module.exports = {
provider: 'aws-s3', provider: 'aws-s3',
name: 'Amazon Web Service S3', name: 'Amazon Web Service S3',
@ -55,15 +57,15 @@ module.exports = {
init: (config) => { init: (config) => {
// configure AWS S3 bucket connection // configure AWS S3 bucket connection
AWS.config.update({ AWS.config.update({
accessKeyId: config.public, accessKeyId: trimParam(config.public),
secretAccessKey: config.private, secretAccessKey: trimParam(config.private),
region: config.region region: config.region
}); });
const S3 = new AWS.S3({ const S3 = new AWS.S3({
apiVersion: '2006-03-01', apiVersion: '2006-03-01',
params: { params: {
Bucket: config.bucket Bucket: trimParam(config.bucket)
} }
}); });

View File

@ -365,5 +365,33 @@ describe('convertRestQueryParams', () => {
], ],
}); });
}); });
test('Null', () => {
expect(
convertRestQueryParams({ 'content.text_null': true })
).toMatchObject({
where: [
{
field: 'content.text',
operator: 'null',
value: true,
},
],
});
});
test('Not Null', () => {
expect(
convertRestQueryParams({ 'content.text_null': false })
).toMatchObject({
where: [
{
field: 'content.text',
operator: 'null',
value: false,
},
],
});
});
}); });
}); });

View File

@ -66,7 +66,7 @@ const castValueToType = ({ type, value }) => {
return false; return false;
} }
return value; return Boolean(value);
} }
case 'integer': case 'integer':
case 'biginteger': case 'biginteger':
@ -79,6 +79,17 @@ const castValueToType = ({ type, value }) => {
} }
}; };
/**
* Cast basic values based on attribute type
* @param {Object} options - Options
* @param {string} options.type - type of the atribute
* @param {*} options.value - value tu cast
* @param {string} options.operator - name of operator
*/
const castValue = ({ type, value, operator}) => {
if (operator === 'null') return castValueToType({ type: 'boolean', value })
return castValueToType({ type, value})
}
/** /**
* *
* @param {Object} options - Options * @param {Object} options - Options
@ -109,8 +120,8 @@ const buildQuery = ({ model, filters = {}, ...rest }) => {
// cast value or array of values // cast value or array of values
const castedValue = Array.isArray(value) const castedValue = Array.isArray(value)
? value.map(val => castValueToType({ type, value: val })) ? value.map(val => castValue({ type, operator, value: val }))
: castValueToType({ type, value: value }); : castValue({ type, operator, value: value });
return { field, operator, value: castedValue }; return { field, operator, value: castedValue };
}); });

View File

@ -132,6 +132,7 @@ const VALID_OPERATORS = [
'lte', 'lte',
'gt', 'gt',
'gte', 'gte',
'null',
]; ];
/** /**

View File

@ -81,6 +81,14 @@ const productFixtures = [
rank: 91, rank: 91,
big_rank: 926372323421, big_rank: 926372323421,
}, },
{
name: 'Product 4',
description: 'Product description 4',
price: null,
decimal_field: 12.22,
rank: 99,
big_rank: 999999999999,
},
]; ];
async function createFixtures() { async function createFixtures() {
@ -194,6 +202,35 @@ describe('Filtering API', () => {
}); });
}); });
describe('Filter null', () => {
test('Should return only one match', async () => {
const res = await rq({
method: 'GET',
url: '/products',
qs: {
price_null: true,
},
});
expect(Array.isArray(res.body)).toBe(true);
expect(res.body.length).toBe(1);
expect(res.body[0]).toMatchObject(data.products[3]);
});
test('Should return three matches', async () => {
const res = await rq({
method: 'GET',
url: '/products',
qs: {
price_null: false,
},
});
expect(Array.isArray(res.body)).toBe(true);
expect(res.body.length).toBe(3);
});
});
describe('Filter contains insensitive', () => { describe('Filter contains insensitive', () => {
test('Should match with insensitive case', async () => { test('Should match with insensitive case', async () => {
const res1 = await rq({ const res1 = await rq({
@ -985,11 +1022,14 @@ describe('Filtering API', () => {
}, },
}); });
expect(res.body).toEqual([ [
data.products[3],
data.products[0], data.products[0],
data.products[2], data.products[2],
data.products[1], data.products[1],
]); ].forEach(expectedPost => {
expect(res.body).toEqual(expect.arrayContaining([expectedPost]));
});
}); });
}); });