Skip to content

Commit 0e6f93b

Browse files
authored
feat: nested fields and integer values for filter relation widget (#7177)
1 parent 28b1d63 commit 0e6f93b

File tree

3 files changed

+93
-12
lines changed

3 files changed

+93
-12
lines changed

packages/decap-cms-widget-relation/src/RelationControl.js

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -351,11 +351,19 @@ export default class RelationControl extends React.Component {
351351

352352
const options = hits.reduce((acc, hit) => {
353353
if (
354-
filters.every(
355-
filter =>
356-
Object.prototype.hasOwnProperty.call(hit.data, filter.field) &&
357-
filter.values.includes(hit.data[filter.field]),
358-
)
354+
filters.every(filter => {
355+
// check if the value for the (nested) filter field is in the filter values
356+
const fieldKeys = filter.field.split('.');
357+
let value = hit.data;
358+
for (let i = 0; i < fieldKeys.length; i++) {
359+
if (Object.prototype.hasOwnProperty.call(value, fieldKeys[i])) {
360+
value = value[fieldKeys[i]];
361+
} else {
362+
return false;
363+
}
364+
}
365+
return filter.values.includes(value);
366+
})
359367
) {
360368
const valuesPaths = stringTemplate.expandPath({ data: hit.data, path: valueField });
361369
for (let i = 0; i < valuesPaths.length; i++) {

packages/decap-cms-widget-relation/src/__tests__/relation.spec.js

Lines changed: 79 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,20 @@ const filterStringFieldConfig = {
7777
],
7878
};
7979

80+
const filterIntegerFieldConfig = {
81+
name: 'post',
82+
collection: 'posts',
83+
display_fields: ['title', 'slug'],
84+
search_fields: ['title', 'body'],
85+
value_field: 'title',
86+
filters: [
87+
{
88+
field: 'num',
89+
values: [1, 5, 9],
90+
},
91+
],
92+
};
93+
8094
const multipleFiltersFieldConfig = {
8195
name: 'post',
8296
collection: 'posts',
@@ -109,13 +123,28 @@ const emptyFilterFieldConfig = {
109123
],
110124
};
111125

126+
const nestedFilterFieldConfig = {
127+
name: 'post',
128+
collection: 'posts',
129+
display_fields: ['title', 'slug'],
130+
search_fields: ['title', 'body'],
131+
value_field: 'title',
132+
filters: [
133+
{
134+
field: 'deeply.nested.post.field',
135+
values: ['Deeply nested field'],
136+
},
137+
],
138+
};
139+
112140
function generateHits(length) {
113141
const hits = Array.from({ length }, (val, idx) => {
114142
const title = `Post # ${idx + 1}`;
115143
const slug = `post-number-${idx + 1}`;
116144
const draft = idx % 2 === 0;
145+
const num = idx + 1;
117146
const path = `posts/${slug}.md`;
118-
return { collection: 'posts', data: { title, slug, draft }, slug, path };
147+
return { collection: 'posts', data: { title, slug, draft, num }, slug, path };
119148
});
120149

121150
return [
@@ -338,7 +367,9 @@ describe('Relation widget', () => {
338367
const value = 'Post # 1';
339368
const label = 'Post # 1 post-number-1';
340369
const metadata = {
341-
post: { posts: { 'Post # 1': { title: 'Post # 1', draft: true, slug: 'post-number-1' } } },
370+
post: {
371+
posts: { 'Post # 1': { title: 'Post # 1', draft: true, num: 1, slug: 'post-number-1' } },
372+
},
342373
};
343374

344375
fireEvent.keyDown(input, { key: 'ArrowDown' });
@@ -356,7 +387,9 @@ describe('Relation widget', () => {
356387
const { getByText, onChangeSpy, setQueryHitsSpy } = setup({ field, value });
357388
const label = 'Post # 1 post-number-1';
358389
const metadata = {
359-
post: { posts: { 'Post # 1': { title: 'Post # 1', draft: true, slug: 'post-number-1' } } },
390+
post: {
391+
posts: { 'Post # 1': { title: 'Post # 1', draft: true, num: 1, slug: 'post-number-1' } },
392+
},
360393
};
361394

362395
setQueryHitsSpy(generateHits(1));
@@ -405,7 +438,9 @@ describe('Relation widget', () => {
405438
const label = 'post-number-1 post-number-1 md';
406439
const metadata = {
407440
post: {
408-
posts: { 'post-number-1': { title: 'Post # 1', draft: true, slug: 'post-number-1' } },
441+
posts: {
442+
'post-number-1': { title: 'Post # 1', draft: true, num: 1, slug: 'post-number-1' },
443+
},
409444
},
410445
};
411446

@@ -462,10 +497,14 @@ describe('Relation widget', () => {
462497
const field = fromJS({ ...fieldConfig, multiple: true });
463498
const { getByText, input, onChangeSpy } = setup({ field });
464499
const metadata1 = {
465-
post: { posts: { 'Post # 1': { title: 'Post # 1', draft: true, slug: 'post-number-1' } } },
500+
post: {
501+
posts: { 'Post # 1': { title: 'Post # 1', draft: true, num: 1, slug: 'post-number-1' } },
502+
},
466503
};
467504
const metadata2 = {
468-
post: { posts: { 'Post # 2': { title: 'Post # 2', draft: false, slug: 'post-number-2' } } },
505+
post: {
506+
posts: { 'Post # 2': { title: 'Post # 2', draft: false, num: 2, slug: 'post-number-2' } },
507+
},
469508
};
470509

471510
fireEvent.keyDown(input, { key: 'ArrowDown' });
@@ -593,6 +632,29 @@ describe('Relation widget', () => {
593632
});
594633
});
595634

635+
it('should list 3 option hits on initial load using a filter on integer value', async () => {
636+
const field = fromJS(filterIntegerFieldConfig);
637+
const { getAllByText, input } = setup({ field });
638+
const expectedOptions = [
639+
'Post # 1 post-number-1',
640+
'Post # 5 post-number-5',
641+
'Post # 9 post-number-9',
642+
];
643+
fireEvent.keyDown(input, { key: 'ArrowDown' });
644+
645+
await waitFor(() => {
646+
const displayedOptions = getAllByText(/^Post # (\d{1,2}) post-number-\1$/);
647+
expect(displayedOptions).toHaveLength(expectedOptions.length);
648+
for (let i = 0; i < expectedOptions.length; i++) {
649+
const expectedOption = expectedOptions[i];
650+
const optionFound = displayedOptions.some(
651+
option => option.textContent === expectedOption,
652+
);
653+
expect(optionFound).toBe(true);
654+
}
655+
});
656+
});
657+
596658
it('should list 4 option hits on initial load using multiple filters', async () => {
597659
const field = fromJS(multipleFiltersFieldConfig);
598660
const { getAllByText, input } = setup({ field });
@@ -626,5 +688,16 @@ describe('Relation widget', () => {
626688
expect(() => getAllByText(/^Post # (\d{1,2}) post-number-\1$/)).toThrow(Error);
627689
});
628690
});
691+
692+
it('should list 1 option hit on initial load on nested filter field', async () => {
693+
const field = fromJS(nestedFilterFieldConfig);
694+
const { getAllByText, input } = setup({ field });
695+
fireEvent.keyDown(input, { key: 'ArrowDown' });
696+
697+
await waitFor(() => {
698+
expect(() => getAllByText(/^Post # (\d{1,2}) post-number-\1$/)).toThrow(Error);
699+
expect(getAllByText('Deeply nested post post-deeply-nested')).toHaveLength(1);
700+
});
701+
});
629702
});
630703
});

packages/decap-cms-widget-relation/src/schema.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export default {
1515
type: 'object',
1616
properties: {
1717
field: { type: 'string' },
18-
values: { type: 'array', minItems: 1, items: { type: ['string', 'boolean'] } },
18+
values: { type: 'array', minItems: 1, items: { type: ['string', 'boolean', 'integer'] } },
1919
},
2020
required: ['field', 'values'],
2121
},

0 commit comments

Comments
 (0)