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
- `containss`: Contains case sensitive
- `ncontainss`: Doesn't contain case sensitive
- `null`: Is null/Is not null
#### 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>_in`: Matches 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.
@ -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.
::: note
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
- `_in`: Matches 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

View File

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

View File

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

View File

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

View File

@ -10,6 +10,8 @@
const _ = require('lodash');
const AWS = require('aws-sdk');
const trimParam = str => typeof str === "string" ? str.trim() : undefined
module.exports = {
provider: 'aws-s3',
name: 'Amazon Web Service S3',
@ -55,15 +57,15 @@ module.exports = {
init: (config) => {
// configure AWS S3 bucket connection
AWS.config.update({
accessKeyId: config.public,
secretAccessKey: config.private,
accessKeyId: trimParam(config.public),
secretAccessKey: trimParam(config.private),
region: config.region
});
const S3 = new AWS.S3({
apiVersion: '2006-03-01',
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 value;
return Boolean(value);
}
case 'integer':
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
@ -109,8 +120,8 @@ const buildQuery = ({ model, filters = {}, ...rest }) => {
// cast value or array of values
const castedValue = Array.isArray(value)
? value.map(val => castValueToType({ type, value: val }))
: castValueToType({ type, value: value });
? value.map(val => castValue({ type, operator, value: val }))
: castValue({ type, operator, value: value });
return { field, operator, value: castedValue };
});

View File

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

View File

@ -81,6 +81,14 @@ const productFixtures = [
rank: 91,
big_rank: 926372323421,
},
{
name: 'Product 4',
description: 'Product description 4',
price: null,
decimal_field: 12.22,
rank: 99,
big_rank: 999999999999,
},
];
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', () => {
test('Should match with insensitive case', async () => {
const res1 = await rq({
@ -985,11 +1022,14 @@ describe('Filtering API', () => {
},
});
expect(res.body).toEqual([
[
data.products[3],
data.products[0],
data.products[2],
data.products[1],
]);
].forEach(expectedPost => {
expect(res.body).toEqual(expect.arrayContaining([expectedPost]));
});
});
});