feat(widget-relation): support nested field references in relation widget (#2391)

This commit is contained in:
Henry
2019-06-24 17:32:02 -06:00
committed by Shawn Erquhart
parent 50dc371ebc
commit d6964b50b3
4 changed files with 108 additions and 15 deletions

View File

@ -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;
}, '');

View File

@ -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,
};
});
};

View File

@ -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 });