-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Feature/array in fields #6517
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature/array in fields #6517
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,14 +21,21 @@ Where: | |
| - `<true|false>` signifies either `true` or `false` Boolean literal. Use `true` | ||
| to include the property or `false` to exclude it from results. | ||
|
|
||
| also can be used an array of strings with the properties | ||
|
|
||
| <pre> | ||
| { fields: [<i>propertyName</i>, <i>propertyName</i>, ... ] } | ||
| </pre> | ||
|
|
||
| By default, queries return all model properties in results. However, if you | ||
| specify at least one fields filter with a value of `true`, then by default the | ||
| query will include **only** those you specifically include with filters. | ||
| specify at least one fields filter with a value of `true` or put it in the | ||
| array, then by default the query will include **only** those you specifically | ||
| include with filters. | ||
|
|
||
| ### REST API | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nitpick: @mdbetancourt could you also update the shortcut for the REST API section? thanks. |
||
|
|
||
| <pre> | ||
| filter[fields][<i>propertyName</i>]=true|false&filter[fields][<i>propertyName</i>]=true|false... | ||
| filter[fields]=<i>propertyName</i>&filter[fields]=<i>propertyName</i>... | ||
| </pre> | ||
|
|
||
| Note that to include more than one field in REST, use multiple filters. | ||
|
|
@@ -47,14 +54,14 @@ fields, you can specify all required properties as: | |
| {% include code-caption.html content="Node.js API" %} | ||
|
|
||
| ```ts | ||
| await customerRepository.find({fields: {name: true, address: true}}); | ||
| await customerRepository.find({fields: ['name', 'address']}); | ||
| ``` | ||
|
|
||
| {% include code-caption.html content="REST" %} | ||
|
|
||
| Its equivalent stringified JSON format: | ||
|
|
||
| `/customers?filter={"fields":{"name":true,"address":true}}` | ||
| `/customers?filter={"fields":["name","address"]}` | ||
|
|
||
| Returns: | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -301,7 +301,17 @@ describe('FilterBuilder', () => { | |
| }, | ||
| }); | ||
| }); | ||
|
|
||
| it('builds a filter object with array', () => { | ||
| const filterBuilder = new FilterBuilder(); | ||
| filterBuilder.fields(['a', 'b']); | ||
| const filter = filterBuilder.build(); | ||
| expect(filter).to.eql({ | ||
| fields: { | ||
| a: true, | ||
| b: true, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it necessary to convert We have test cases to verify that all connectors support both formats, see I believe that internally, juggler will convert My conclusion is that @raymondfeng @jannyHou @hacksparrow what do you think?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hi @bajtos thank you for you feedback, i saw some disadvantage about that:
but FilterBuilder allow do builder.fields('property').fields(['property2']).fields({ property3: true }).build()what is filter.fields? builder = builder.fields('property') // adding property to fields
// ..some code
builder.fields({property: false}) // i remove itsome edges case like this need to be cover (so fields function become more complex) if we keep the array (we need check if exists and remove it), instead with object that's not a problem because we merge the object
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you for a detailed explanation! Based on your arguments, I am fine to keep representing fields as an object internally inside
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 appreciate all the considerations. Agree with do the conversion here. |
||
| }, | ||
| }); | ||
| }); | ||
| it('builds a filter object with limit/offset', () => { | ||
| const filterBuilder = new FilterBuilder(); | ||
| filterBuilder.limit(10).offset(5); | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -159,7 +159,9 @@ export type Order<MT = AnyObject> = {[P in keyof MT]: Direction}; | |||||
| * Example: | ||||||
| * `{afieldname: true}` | ||||||
| */ | ||||||
| export type Fields<MT = AnyObject> = {[P in keyof MT]?: boolean}; | ||||||
| export type Fields<MT = AnyObject> = | ||||||
| | {[P in keyof MT]?: boolean} | ||||||
| | Extract<keyof MT, string>[]; | ||||||
|
|
||||||
| /** | ||||||
| * Inclusion of related items | ||||||
|
|
@@ -560,16 +562,21 @@ export class FilterBuilder<MT extends object = AnyObject> { | |||||
| * @param f - A field name to be included, an array of field names to be | ||||||
| * included, or an Fields object for the inclusion/exclusion | ||||||
| */ | ||||||
| fields(...f: (Fields<MT> | (keyof MT)[] | keyof MT)[]): this { | ||||||
| fields(...f: (Fields<MT> | Extract<keyof MT, string>)[]): this { | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we still need to specify all forms? I think
Suggested change
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. that cover calling it with builder.fields('property', 'property2')while Fields<MT> only cover builder.fields(['property', 'property2'])
// and
builder.fields({property: true, property2: true})so i kept it in order to no introduce a breaking change
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see, I did not realize +1 to preserve backwards compatibility 👍🏻 |
||||||
| if (!this.filter.fields) { | ||||||
| this.filter.fields = {}; | ||||||
| } else if (Array.isArray(this.filter.fields)) { | ||||||
| this.filter.fields = this.filter.fields.reduce( | ||||||
| (prev, current) => ({...prev, [current]: true}), | ||||||
| {}, | ||||||
|
Comment on lines
+569
to
+571
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As I mentioned above, I think we should keep
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i did it to keep some consistency to make more easy the following code (instead of check if fields is an array or an object) also i used object aproach instead of array because object is more versatile. how could be converted the following object into an array?: { password: false }
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point 👍🏻 As I wrote in #6517 (comment), you convinced me it's better to store fields as an object internally. |
||||||
| ); | ||||||
| } | ||||||
| const fields = this.filter.fields; | ||||||
| for (const field of f) { | ||||||
| if (Array.isArray(field)) { | ||||||
| (field as (keyof MT)[]).forEach(i => (fields[i] = true)); | ||||||
| field.forEach(i => (fields[i] = true)); | ||||||
| } else if (typeof field === 'string') { | ||||||
| fields[field as keyof MT] = true; | ||||||
| fields[field] = true; | ||||||
| } else { | ||||||
| Object.assign(fields, field); | ||||||
| } | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,13 +25,15 @@ import { | |
| describe('getFilterJsonSchemaFor', () => { | ||
| let ajv: Ajv.Ajv; | ||
| let customerFilterSchema: JsonSchema; | ||
| let dynamicCustomerFilterSchema: JsonSchema; | ||
| let customerFilterExcludingWhereSchema: JsonSchema; | ||
| let customerFilterExcludingIncludeSchema: JsonSchema; | ||
| let orderFilterSchema: JsonSchema; | ||
|
|
||
| beforeEach(() => { | ||
| ajv = new Ajv(); | ||
| customerFilterSchema = getFilterJsonSchemaFor(Customer); | ||
| dynamicCustomerFilterSchema = getFilterJsonSchemaFor(DynamicCustomer); | ||
| customerFilterExcludingWhereSchema = getFilterJsonSchemaFor(Customer, { | ||
| exclude: ['where'], | ||
| }); | ||
|
|
@@ -127,6 +129,51 @@ describe('getFilterJsonSchemaFor', () => { | |
| ]); | ||
| }); | ||
|
|
||
| it('allows free-form properties in "fields" for non-strict models"', () => { | ||
| const filter = {fields: ['test', 'id']}; | ||
| ajv.validate(dynamicCustomerFilterSchema, filter); | ||
| expect(ajv.errors ?? []).to.be.empty(); | ||
| }); | ||
|
|
||
| it('allows only defined properties in "fields" for strict models"', () => { | ||
| const filter = {fields: ['test']}; | ||
| ajv.validate(customerFilterSchema, filter); | ||
| expect(ajv.errors ?? []).to.containDeep([ | ||
| { | ||
| keyword: 'enum', | ||
| dataPath: '.fields[0]', | ||
| params: {allowedValues: ['id', 'name']}, | ||
| message: 'should be equal to one of the allowed values', | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nitpick:
Does error msg contain more details of the allowed values?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. {
keyword: 'enum',
dataPath: '.fields[0]',
schemaPath: '#/properties/fields/oneOf/1/items/enum',
params: { allowedValues: [ 'id', 'name' ] },
message: 'should be equal to one of the allowed values'
}this is the object |
||
| }, | ||
| ]); | ||
| }); | ||
|
|
||
| it('rejects "fields" with duplicated items for strict models', () => { | ||
| const filter = {fields: ['id', 'id']}; | ||
| ajv.validate(customerFilterSchema, filter); | ||
| expect(ajv.errors ?? []).to.containDeep([ | ||
| { | ||
| keyword: 'uniqueItems', | ||
| dataPath: '.fields', | ||
| message: | ||
| 'should NOT have duplicate items (items ## 1 and 0 are identical)', | ||
| }, | ||
| ]); | ||
| }); | ||
|
|
||
| it('rejects "fields" with duplicated items for non-strict models', () => { | ||
| const filter = {fields: ['test', 'test']}; | ||
| ajv.validate(dynamicCustomerFilterSchema, filter); | ||
| expect(ajv.errors ?? []).to.containDeep([ | ||
| { | ||
| keyword: 'uniqueItems', | ||
| dataPath: '.fields', | ||
| message: | ||
| 'should NOT have duplicate items (items ## 1 and 0 are identical)', | ||
| }, | ||
| ]); | ||
| }); | ||
|
|
||
| it('describes "include" as an array for models with relations', () => { | ||
| const filter = {include: 'invalid-include'}; | ||
| ajv.validate(customerFilterSchema, filter); | ||
|
|
@@ -499,3 +546,11 @@ class Customer extends Entity { | |
| @hasMany(() => Order) | ||
| orders?: Order[]; | ||
| } | ||
|
|
||
| @model({ | ||
| settings: {strict: false}, | ||
| }) | ||
| class DynamicCustomer extends Entity { | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| [key: string]: any; | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion for re-structuring the initial sections to this:
@bajtos and others thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The suggested structure looks good to me.
Just note that we support array + object format also for REST API, not just in Node.js (TypeScript).