feat(widget-relation): support nested field references in relation widget (#2391)
This commit is contained in:
@ -130,7 +130,12 @@ const commitMessageFormatter = (type, config, { slug, path, collection }) => {
|
||||
|
||||
const extractSearchFields = searchFields => entry =>
|
||||
searchFields.reduce((acc, field) => {
|
||||
const f = entry.data[field];
|
||||
let nestedFields = field.split('.');
|
||||
let f = entry.data;
|
||||
for (let i = 0; i < nestedFields.length; i++) {
|
||||
f = f[nestedFields[i]];
|
||||
if (!f) break;
|
||||
}
|
||||
return f ? `${acc} ${f}` : acc;
|
||||
}, '');
|
||||
|
||||
|
@ -72,7 +72,7 @@ export default class RelationControl extends React.Component {
|
||||
if (value) {
|
||||
const listValue = List.isList(value) ? value : List([value]);
|
||||
listValue.forEach(val => {
|
||||
const hit = hits.find(i => i.data[valueField] === val);
|
||||
const hit = hits.find(i => this.parseNestedFields(i.data, valueField) === val);
|
||||
if (hit) {
|
||||
onChange(value, {
|
||||
[field.get('name')]: {
|
||||
@ -113,21 +113,38 @@ export default class RelationControl extends React.Component {
|
||||
}
|
||||
};
|
||||
|
||||
parseNestedFields = (targetObject, field) => {
|
||||
let nestedField = field.split('.');
|
||||
let f = targetObject;
|
||||
for (let i = 0; i < nestedField.length; i++) {
|
||||
f = f[nestedField[i]];
|
||||
if (!f) break;
|
||||
}
|
||||
if (typeof f === 'object' && f !== null) {
|
||||
return JSON.stringify(f);
|
||||
}
|
||||
return f;
|
||||
};
|
||||
|
||||
parseHitOptions = hits => {
|
||||
const { field } = this.props;
|
||||
const valueField = field.get('valueField');
|
||||
const displayField = field.get('displayFields') || field.get('valueField');
|
||||
|
||||
return hits.map(hit => {
|
||||
let labelReturn;
|
||||
if (List.isList(displayField)) {
|
||||
labelReturn = displayField
|
||||
.toJS()
|
||||
.map(key => this.parseNestedFields(hit.data, key))
|
||||
.join(' ');
|
||||
} else {
|
||||
labelReturn = this.parseNestedFields(hit.data, displayField);
|
||||
}
|
||||
return {
|
||||
data: hit.data,
|
||||
value: hit.data[valueField],
|
||||
label: List.isList(displayField)
|
||||
? displayField
|
||||
.toJS()
|
||||
.map(key => hit.data[key])
|
||||
.join(' ')
|
||||
: hit.data[displayField],
|
||||
value: this.parseNestedFields(hit.data, valueField),
|
||||
label: labelReturn,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
@ -16,6 +16,22 @@ const fieldConfig = {
|
||||
valueField: 'title',
|
||||
};
|
||||
|
||||
const deeplyNestedFieldConfig = {
|
||||
name: 'post',
|
||||
collection: 'posts',
|
||||
displayFields: ['title', 'slug', 'deeply.nested.post.field'],
|
||||
searchFields: ['deeply.nested.post.field'],
|
||||
valueField: 'title',
|
||||
};
|
||||
|
||||
const nestedFieldConfig = {
|
||||
name: 'post',
|
||||
collection: 'posts',
|
||||
displayFields: ['title', 'slug', 'nested.field_1'],
|
||||
searchFields: ['nested.field_1', 'nested.field_2'],
|
||||
valueField: 'title',
|
||||
};
|
||||
|
||||
const generateHits = length => {
|
||||
const hits = Array.from({ length }, (val, idx) => {
|
||||
const title = `Post # ${idx + 1}`;
|
||||
@ -25,6 +41,31 @@ const generateHits = length => {
|
||||
|
||||
return [
|
||||
...hits,
|
||||
{
|
||||
collection: 'posts',
|
||||
data: {
|
||||
title: 'Deeply nested post',
|
||||
slug: 'post-deeply-nested',
|
||||
deeply: {
|
||||
nested: {
|
||||
post: {
|
||||
field: 'Deeply nested field',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
collection: 'posts',
|
||||
data: {
|
||||
title: 'Nested post',
|
||||
slug: 'post-nested',
|
||||
nested: {
|
||||
field_1: 'Nested field 1',
|
||||
field_2: 'Nested field 2',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
collection: 'posts',
|
||||
data: { title: 'YAML post', slug: 'post-yaml', body: 'Body yaml' },
|
||||
@ -51,6 +92,14 @@ class RelationController extends React.Component {
|
||||
const queryHits = generateHits(25);
|
||||
if (last(args) === 'YAML') {
|
||||
return Promise.resolve({ payload: { response: { hits: [last(queryHits)] } } });
|
||||
} else if (last(args) === 'Nested') {
|
||||
return Promise.resolve({
|
||||
payload: { response: { hits: [queryHits[queryHits.length - 2]] } },
|
||||
});
|
||||
} else if (last(args) === 'Deeply nested') {
|
||||
return Promise.resolve({
|
||||
payload: { response: { hits: [queryHits[queryHits.length - 3]] } },
|
||||
});
|
||||
}
|
||||
return Promise.resolve({ payload: { response: { hits: queryHits } } });
|
||||
});
|
||||
@ -159,6 +208,28 @@ describe('Relation widget', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should update option list based on nested search term', async () => {
|
||||
const field = fromJS(nestedFieldConfig);
|
||||
const { getAllByText, input } = setup({ field });
|
||||
fireEvent.change(input, { target: { value: 'Nested' } });
|
||||
|
||||
await wait(() => {
|
||||
expect(getAllByText('Nested post post-nested Nested field 1')).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('should update option list based on deeply nested search term', async () => {
|
||||
const field = fromJS(deeplyNestedFieldConfig);
|
||||
const { getAllByText, input } = setup({ field });
|
||||
fireEvent.change(input, { target: { value: 'Deeply nested' } });
|
||||
|
||||
await wait(() => {
|
||||
expect(
|
||||
getAllByText('Deeply nested post post-deeply-nested Deeply nested field'),
|
||||
).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with multiple', () => {
|
||||
it('should call onChange with correct selectedItem value and metadata', async () => {
|
||||
const field = fromJS({ ...fieldConfig, multiple: true });
|
||||
|
Reference in New Issue
Block a user