Merge pull request #8784 from strapi/relational-fields/many-values-tooltip

Display x-to-many relation preview values in content manager list
This commit is contained in:
cyril lopez 2020-12-07 14:06:26 +01:00 committed by GitHub
commit 0a754813b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 500 additions and 134 deletions

View File

@ -6,7 +6,7 @@
"description": ""
},
"options": {
"draftAndPublish": true,
"draftAndPublish": false,
"increments": true,
"timestamps": [
"created_at",
@ -19,8 +19,8 @@
"type": "string"
},
"categories": {
"via": "addresses",
"collection": "category",
"via": "addresses",
"dominant": true
},
"cover": {

View File

@ -12,7 +12,7 @@ const Header = ({ fieldSchema: { type }, metadatas: { label, sortable, mainField
let sortField = name;
if (type === 'relation') {
sortField = `${name}.${mainField}`;
sortField = `${name}.${mainField.name}`;
}
const handleClick = () => {
@ -50,7 +50,7 @@ Header.propTypes = {
metadatas: PropTypes.shape({
label: PropTypes.string.isRequired,
sortable: PropTypes.bool.isRequired,
mainField: PropTypes.string,
mainField: PropTypes.object,
}).isRequired,
name: PropTypes.string.isRequired,
};

View File

@ -5,33 +5,34 @@ import RelationPreviewList from '../../RelationPreviewList';
import Truncate from '../../Truncate';
import Truncated from '../../Truncated';
const RowCell = ({ metadatas, type, value, relationType }) => {
if (type === 'media') {
return <MediaPreviewList files={value} />;
const Cell = ({ options }) => {
if (options.type === 'media') {
return <MediaPreviewList files={options.value} />;
}
if (type === 'relation') {
return <RelationPreviewList relationType={relationType} metadatas={metadatas} value={value} />;
if (options.type === 'relation') {
return <RelationPreviewList options={options} />;
}
return (
<Truncate>
<Truncated title={value}>{value}</Truncated>
<Truncated title={options.value}>{options.value}</Truncated>
</Truncate>
);
};
RowCell.defaultProps = {
type: null,
value: null,
relationType: null,
Cell.propTypes = {
options: PropTypes.shape({
cellId: PropTypes.string.isRequired,
metadatas: PropTypes.shape({
mainField: PropTypes.object,
}).isRequired,
name: PropTypes.string.isRequired,
relationType: PropTypes.string,
rowId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
type: PropTypes.string,
value: PropTypes.any,
}).isRequired,
};
RowCell.propTypes = {
metadatas: PropTypes.object.isRequired,
relationType: PropTypes.string,
type: PropTypes.string,
value: PropTypes.any,
};
export default memo(RowCell);
export default memo(Cell);

View File

@ -70,10 +70,15 @@ function Row({ canCreate, canDelete, canUpdate, isBulkable, row, headers, goTo }
cellFormatter(row)
) : (
<Cell
type={type}
metadatas={metadatas}
relationType={relationType}
value={memoizedDisplayedValue(name, type)}
options={{
rowId: row.id,
relationType,
type,
name,
value: memoizedDisplayedValue(name, type),
cellId: key,
metadatas,
}}
/>
)}
</td>

View File

@ -13,7 +13,7 @@ import {
} from 'strapi-helper-plugin';
import pluginId from '../../pluginId';
import { formatFiltersToQuery, getTrad, getMainFieldType } from '../../utils';
import { formatFiltersToQuery, getTrad } from '../../utils';
import Container from '../Container';
import FilterPickerOption from '../FilterPickerOption';
import { Flex, Span, Wrapper } from './components';
@ -24,7 +24,6 @@ const NOT_ALLOWED_FILTERS = ['json', 'component', 'media', 'richtext', 'dynamicz
function FilterPicker({
contentType,
editRelations,
filters,
isOpen,
metadatas,
@ -192,12 +191,12 @@ function FilterPicker({
const attributeType = get(contentType, ['attributes', filter.name, 'type'], '');
if (attributeType === 'relation') {
return getMainFieldType(editRelations, filter.name);
return get(metadatas, [filter.name, 'list', 'mainField', 'schema', 'type'], 'string');
}
return attributeType;
},
[contentType, editRelations]
[contentType, metadatas]
);
return (
@ -241,13 +240,11 @@ function FilterPicker({
}
FilterPicker.defaultProps = {
editRelations: [],
name: '',
};
FilterPicker.propTypes = {
contentType: PropTypes.object.isRequired,
editRelations: PropTypes.array,
filters: PropTypes.array.isRequired,
isOpen: PropTypes.bool.isRequired,
metadatas: PropTypes.object.isRequired,

View File

@ -0,0 +1,98 @@
import React, { useState, useEffect, useCallback, useRef, useLayoutEffect } from 'react';
import { Text, Padded } from '@buffetjs/core';
import { LoadingIndicator, request } from 'strapi-helper-plugin';
import PropTypes from 'prop-types';
import getRequestUrl from '../../utils/getRequestUrl';
import Tooltip from '../Tooltip';
import getDisplayedValue from '../CustomTable/Row/utils/getDisplayedValue';
const RelationPreviewTooltip = ({ tooltipId, rowId, mainField, name }) => {
const [isLoading, setIsLoading] = useState(true);
const [relationData, setRelationData] = useState([]);
const tooltipRef = useRef();
const { endPoint } = mainField.queryInfos;
const fetchRelationData = useCallback(
async signal => {
const requestURL = getRequestUrl(`${endPoint}/${rowId}/${name}`);
try {
const { results } = await request(requestURL, {
method: 'GET',
signal,
});
setRelationData(results);
setIsLoading(false);
} catch (err) {
console.error({ err });
setIsLoading(false);
}
},
[endPoint, name, rowId]
);
useEffect(() => {
const abortController = new AbortController();
const { signal } = abortController;
const timeout = setTimeout(() => {
fetchRelationData(signal);
}, 500);
return () => {
clearTimeout(timeout);
abortController.abort();
};
}, [fetchRelationData]);
const getValueToDisplay = useCallback(
item => {
return getDisplayedValue(mainField.schema.type, item[mainField.name], mainField.name);
},
[mainField]
);
// Used to update the position after the loader
useLayoutEffect(() => {
if (!isLoading && tooltipRef.current) {
tooltipRef.current.updatePosition();
}
}, [isLoading]);
return (
<Tooltip ref={tooltipRef} id={tooltipId}>
<div>
{isLoading ? (
<Padded left right size="sm">
<LoadingIndicator small />
</Padded>
) : (
<>
{relationData.map(item => (
<Padded key={item.id} top bottom size="xs">
<Text ellipsis color="white">
{getValueToDisplay(item)}
</Text>
</Padded>
))}
{relationData.length > 10 && <Text color="white">[...]</Text>}
</>
)}
</div>
</Tooltip>
);
};
RelationPreviewTooltip.propTypes = {
tooltipId: PropTypes.string.isRequired,
mainField: PropTypes.shape({
name: PropTypes.string.isRequired,
schema: PropTypes.object.isRequired,
queryInfos: PropTypes.object.isRequired,
}).isRequired,
name: PropTypes.string.isRequired,
rowId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
};
export default RelationPreviewTooltip;

View File

@ -1,29 +1,53 @@
import React from 'react';
import React, { memo, useState, useMemo } from 'react';
import PropTypes from 'prop-types';
import { Flex, Padded, Count } from '@buffetjs/core';
import { useIntl } from 'react-intl';
import { getTrad } from '../../utils';
import Truncate from '../Truncate';
import Truncated from '../Truncated';
import CountWrapper from './CountWrapper';
import RelationPreviewTooltip from './RelationPreviewTooltip';
const RelationPreviewList = ({ metadatas: { mainField }, relationType, value }) => {
const RelationPreviewList = ({
options: {
metadatas: { mainField },
relationType,
value,
rowId,
cellId,
name,
},
}) => {
const { formatMessage } = useIntl();
const [tooltipIsDisplayed, setDisplayTooltip] = useState(false);
const isSingle = ['oneWay', 'oneToOne', 'manyToOne'].includes(relationType);
const tooltipId = useMemo(() => `${rowId}-${cellId}`, [rowId, cellId]);
if (isSingle) {
return (
<Truncate>
<Truncated>{value ? value[mainField] : '-'}</Truncated>
<Truncated>{value ? value[mainField.name] : '-'}</Truncated>
</Truncate>
);
}
const size = value ? value.length : 0;
const handleTooltipToggle = () => {
setDisplayTooltip(prev => !prev);
};
return (
<Truncate>
<Flex>
<Truncate style={{ maxWidth: 'fit-content' }}>
<Flex
// This is useful to avoid the render of every tooltips of the list at the same time.
// https://github.com/wwayne/react-tooltip/issues/524
onMouseEnter={handleTooltipToggle}
onMouseLeave={handleTooltipToggle}
data-for={tooltipId}
data-tip={JSON.stringify(value)}
>
<CountWrapper>
<Count count={size} />
</CountWrapper>
@ -36,20 +60,31 @@ const RelationPreviewList = ({ metadatas: { mainField }, relationType, value })
})}
</Truncated>
</Flex>
{size > 0 && tooltipIsDisplayed && (
<RelationPreviewTooltip
name={name}
rowId={rowId}
tooltipId={tooltipId}
value={value}
mainField={mainField}
/>
)}
</Truncate>
);
};
RelationPreviewList.defaultProps = {
value: null,
};
RelationPreviewList.propTypes = {
metadatas: PropTypes.shape({
mainField: PropTypes.string.isRequired,
options: PropTypes.shape({
cellId: PropTypes.string.isRequired,
metadatas: PropTypes.shape({
mainField: PropTypes.object.isRequired,
}).isRequired,
name: PropTypes.string.isRequired,
relationType: PropTypes.string,
rowId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
type: PropTypes.string,
value: PropTypes.any,
}).isRequired,
relationType: PropTypes.string.isRequired,
value: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
};
export default RelationPreviewList;
export default memo(RelationPreviewList);

View File

@ -0,0 +1,20 @@
import ReactTooltip from 'react-tooltip';
import styled from 'styled-components';
// Add !important to customize CSS is recommended by react-tooltip in the official readme
const Tooltip = styled(ReactTooltip).attrs(({ theme }) => ({
// Pre set the tooltip static props.
place: 'bottom',
effect: 'solid',
delayShow: 500,
arrowColor: 'transparent',
backgroundColor: theme.main.colors.greyDark,
}))`
padding: 0.5rem 0.7rem !important;
opacity: 1 !important;
border-radius: ${({ theme }) => theme.main.sizes.borderRadius} !important;
max-width: 400px;
max-height: 400px;
`;
export default Tooltip;

View File

@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import { get, toString } from 'lodash';
import moment from 'moment';
import { FilterButton } from 'strapi-helper-plugin';
import { dateFormats, formatFiltersToQuery, getMainFieldType } from '../../utils';
import { dateFormats, formatFiltersToQuery } from '../../utils';
function Filter({
contentType,
@ -20,8 +20,7 @@ function Filter({
let type = attributeType;
if (attributeType === 'relation') {
const editRelations = get(contentType, ['layouts', 'editRelations'], []);
type = getMainFieldType(editRelations, name);
type = get(contentType, ['metadatas', name, 'list', 'mainField', 'schema', 'type'], 'string');
}
let displayedValue = toString(value);

View File

@ -86,7 +86,6 @@ function ListView({
contentType: {
attributes,
metadatas,
layouts: { editRelations },
settings: {
defaultSortBy,
defaultSortOrder,
@ -380,7 +379,6 @@ function ListView({
filters={filters}
isOpen={isFilterPickerOpen}
metadatas={metadatas}
editRelations={editRelations}
name={label}
toggleFilterPickerState={toggleFilterPickerState}
setQuery={setQuery}

View File

@ -91,7 +91,7 @@ const listViewReducer = (state = initialState, action) =>
let metas = metadatas[name].list;
if (attributes[name].type === 'relation') {
const mainField = metadatas[name].edit.mainField;
const { mainField } = metadatas[name].list;
metas = { ...metas, mainField };
}
@ -107,6 +107,7 @@ const listViewReducer = (state = initialState, action) =>
header => header.name !== name
);
}
break;
}
case ON_DELETE_DATA_SUCCEEDED: {

View File

@ -3,23 +3,19 @@ import { mergeMetasWithSchema } from '../../../utils';
import pluginId from '../../../pluginId';
// editRelations is an array of strings...
const formatEditRelationsLayoutWithMetas = (obj, models) => {
const formatted = obj.layouts.editRelations.reduce((acc, current) => {
const fieldSchema = get(obj, ['attributes', current], {});
const metadatas = get(obj, ['metadatas', current, 'edit'], {});
const formatEditRelationsLayoutWithMetas = (contentTypeConfiguration, models) => {
const formatted = contentTypeConfiguration.layouts.editRelations.reduce((acc, current) => {
const fieldSchema = get(contentTypeConfiguration, ['attributes', current], {});
const metadatas = get(contentTypeConfiguration, ['metadatas', current, 'edit'], {});
const size = 6;
const mainField = get(obj, ['metadatas', current, 'edit', 'mainField'], 'id');
const targetModelUid = get(obj, ['attributes', current, 'targetModel'], '');
const relationModel = models.find(model => model.uid === targetModelUid);
const mainFieldSchema = get(relationModel, ['attributes', mainField], {});
const queryInfos = generateRelationQueryInfos(obj, current, models);
const queryInfos = generateRelationQueryInfos(contentTypeConfiguration, current, models);
acc.push({
name: current,
size,
fieldSchema,
metadatas: { ...metadatas, mainFieldSchema },
metadatas,
queryInfos,
});
@ -34,11 +30,13 @@ const formatLayouts = (initialData, models) => {
const formattedCTEditLayout = formatLayoutWithMetas(data.contentType, null, models);
const ctUid = data.contentType.uid;
const formattedEditRelationsLayout = formatEditRelationsLayoutWithMetas(data.contentType, models);
const formattedListLayout = formatListLayoutWithMetas(data.contentType);
const formattedListLayout = formatListLayoutWithMetas(data.contentType, models);
const formattedMetadatasLayout = formatMetadatasRelations(data.contentType, models);
set(data, ['contentType', 'layouts', 'edit'], formattedCTEditLayout);
set(data, ['contentType', 'layouts', 'editRelations'], formattedEditRelationsLayout);
set(data, ['contentType', 'layouts', 'list'], formattedListLayout);
set(data, ['contentType', 'metadatas'], formattedMetadatasLayout);
Object.keys(data.components).forEach(compoUID => {
const formattedCompoEditLayout = formatLayoutWithMetas(
@ -53,21 +51,48 @@ const formatLayouts = (initialData, models) => {
return data;
};
const formatLayoutWithMetas = (obj, ctUid, models) => {
const formatted = obj.layouts.edit.reduce((acc, current) => {
const formatMetadatasRelations = (contentTypeConfiguration, models) => {
const formattedRelationsMetadatas = contentTypeConfiguration.layouts.editRelations.reduce(
(acc, current) => {
const currentMetadatas = get(contentTypeConfiguration, ['metadatas', current], {});
return {
...acc,
[current]: {
...currentMetadatas,
list: {
...currentMetadatas.list,
mainField: getMainField(current, contentTypeConfiguration, models),
},
},
};
},
{}
);
return { ...contentTypeConfiguration.metadatas, ...formattedRelationsMetadatas };
};
const formatLayoutWithMetas = (contentTypeConfiguration, ctUid, models) => {
const formatted = contentTypeConfiguration.layouts.edit.reduce((acc, current) => {
const row = current.map(attribute => {
const fieldSchema = get(obj, ['attributes', attribute.name], {});
const fieldSchema = get(contentTypeConfiguration, ['attributes', attribute.name], {});
const data = {
...attribute,
fieldSchema,
metadatas: get(obj, ['metadatas', attribute.name, 'edit'], {}),
metadatas: get(contentTypeConfiguration, ['metadatas', attribute.name, 'edit'], {}),
};
if (fieldSchema.type === 'relation') {
const queryInfos = ctUid
? generateRelationQueryInfosForComponents(obj, attribute.name, ctUid, models)
: generateRelationQueryInfos(obj, attribute.name, models);
? generateRelationQueryInfosForComponents(
contentTypeConfiguration,
attribute.name,
ctUid,
models
)
: generateRelationQueryInfos(contentTypeConfiguration, attribute.name, models);
set(data, 'queryInfos', queryInfos);
}
@ -83,17 +108,17 @@ const formatLayoutWithMetas = (obj, ctUid, models) => {
return formatted;
};
const formatListLayoutWithMetas = obj => {
const formatted = obj.layouts.list.reduce((acc, current) => {
const fieldSchema = get(obj, ['attributes', current], {});
let metadatas = get(obj, ['metadatas', current, 'list'], {});
const formatListLayoutWithMetas = (contentTypeConfiguration, models) => {
const formatted = contentTypeConfiguration.layouts.list.reduce((acc, current) => {
const fieldSchema = get(contentTypeConfiguration, ['attributes', current], {});
let metadatas = get(contentTypeConfiguration, ['metadatas', current, 'list'], {});
const type = fieldSchema.type;
if (type === 'relation') {
metadatas = {
...metadatas,
mainField: get(obj, ['metadatas', current, 'edit', 'mainField'], 'id'),
mainField: getMainField(current, contentTypeConfiguration, models),
};
}
@ -105,11 +130,15 @@ const formatListLayoutWithMetas = obj => {
return formatted;
};
const generateRelationQueryInfos = (obj, fieldName, models) => {
const uid = obj.uid;
const generateRelationQueryInfos = (contentTypeConfiguration, fieldName, models) => {
const uid = contentTypeConfiguration.uid;
const endPoint = `/${pluginId}/relations/${uid}/${fieldName}`;
const mainField = get(obj, ['metadatas', fieldName, 'edit', 'mainField'], '');
const targetModel = get(obj, ['attributes', fieldName, 'targetModel'], '');
const mainField = get(
contentTypeConfiguration,
['metadatas', fieldName, 'edit', 'mainField'],
''
);
const targetModel = get(contentTypeConfiguration, ['attributes', fieldName, 'targetModel'], '');
const shouldDisplayRelationLink = getDisplayedModels(models).indexOf(targetModel) !== -1;
const queryInfos = {
@ -122,17 +151,26 @@ const generateRelationQueryInfos = (obj, fieldName, models) => {
return queryInfos;
};
const generateRelationQueryInfosForComponents = (obj, fieldName, ctUid, models) => {
const generateRelationQueryInfosForComponents = (
contentTypeConfiguration,
fieldName,
ctUid,
models
) => {
const endPoint = `/${pluginId}/relations/${ctUid}/${fieldName}`;
const mainField = get(obj, ['metadatas', fieldName, 'edit', 'mainField'], '');
const targetModel = get(obj, ['attributes', fieldName, 'targetModel'], '');
const mainField = get(
contentTypeConfiguration,
['metadatas', fieldName, 'edit', 'mainField'],
''
);
const targetModel = get(contentTypeConfiguration, ['attributes', fieldName, 'targetModel'], '');
const shouldDisplayRelationLink = getDisplayedModels(models).indexOf(targetModel) !== -1;
const queryInfos = {
endPoint,
containsKey: `${mainField}_contains`,
defaultParams: {
_component: obj.uid,
_component: contentTypeConfiguration.uid,
},
shouldDisplayRelationLink,
};
@ -143,12 +181,38 @@ const generateRelationQueryInfosForComponents = (obj, fieldName, ctUid, models)
const getDisplayedModels = models =>
models.filter(model => model.isDisplayed).map(({ uid }) => uid);
const getMainField = (relationField, contentTypeConfiguration, models) => {
const mainField = get(
contentTypeConfiguration,
['metadatas', relationField, 'edit', 'mainField'],
'id'
);
const targetModelUid = get(
contentTypeConfiguration,
['attributes', relationField, 'targetModel'],
''
);
const relationModel = models.find(model => model.uid === targetModelUid);
const mainFieldSchema = get(relationModel, ['attributes', mainField], {});
return {
name: mainField,
schema: mainFieldSchema,
queryInfos: {
endPoint: `collection-types/${contentTypeConfiguration.uid}`,
defaultParams: {},
},
};
};
export default formatLayouts;
export {
formatEditRelationsLayoutWithMetas,
formatLayoutWithMetas,
formatListLayoutWithMetas,
formatMetadatasRelations,
generateRelationQueryInfos,
generateRelationQueryInfosForComponents,
getMainField,
getDisplayedModels,
};

View File

@ -2,8 +2,10 @@ import formatLayouts, {
formatEditRelationsLayoutWithMetas,
formatLayoutWithMetas,
formatListLayoutWithMetas,
formatMetadatasRelations,
generateRelationQueryInfos,
generateRelationQueryInfosForComponents,
getMainField,
getDisplayedModels,
} from '../formatLayouts';
@ -49,9 +51,6 @@ describe('Content Manager | hooks | useFetchContentTypeLayout | utils ', () => {
},
metadatas: {
mainField: 'name',
mainFieldSchema: {
type: 'string',
},
},
queryInfos: {
endPoint: '/content-manager/relations/application::address.address/categories',
@ -488,16 +487,29 @@ describe('Content Manager | hooks | useFetchContentTypeLayout | utils ', () => {
describe('formatListLayoutWithMetas', () => {
it('should format the list layout correctly', () => {
const data = {
uid: 'address',
layouts: {
list: ['test'],
list: ['test', 'categories'],
},
metadatas: {
test: {
list: { ok: true },
},
categories: {
list: {
ok: true,
},
edit: {
mainField: 'name',
},
},
},
attributes: {
test: { type: 'string' },
categories: {
type: 'relation',
targetModel: 'category',
},
},
};
const expected = [
@ -507,9 +519,92 @@ describe('Content Manager | hooks | useFetchContentTypeLayout | utils ', () => {
metadatas: { ok: true },
fieldSchema: { type: 'string' },
},
{
name: 'categories',
key: '__categories_key__',
metadatas: {
ok: true,
mainField: {
name: 'name',
schema: {
type: 'string',
},
queryInfos: { defaultParams: {}, endPoint: 'collection-types/address' },
},
},
fieldSchema: { type: 'relation', targetModel: 'category' },
},
];
const models = [
{
uid: 'category',
attributes: {
name: {
type: 'string',
},
},
},
];
expect(formatListLayoutWithMetas(data)).toEqual(expected);
expect(formatListLayoutWithMetas(data, models)).toEqual(expected);
});
});
describe('formatMetadatasRelations', () => {
it('should format metadatas correctly', () => {
const contentTypeConfiguration = {
uid: 'address',
metadatas: {
categories: {
list: {
ok: true,
},
edit: {
ok: false,
mainField: 'name',
},
},
},
attributes: {
categories: {
targetModel: 'category',
},
},
layouts: {
editRelations: ['categories'],
},
};
const models = [
{
uid: 'category',
attributes: {
name: {
type: 'string',
},
},
},
];
const actual = formatMetadatasRelations(contentTypeConfiguration, models);
const expected = {
categories: {
list: {
ok: true,
mainField: {
name: 'name',
schema: {
type: 'string',
},
queryInfos: { defaultParams: {}, endPoint: 'collection-types/address' },
},
},
edit: {
ok: false,
mainField: 'name',
},
},
};
expect(actual).toEqual(expected);
});
});
@ -556,4 +651,42 @@ describe('Content Manager | hooks | useFetchContentTypeLayout | utils ', () => {
expect(getDisplayedModels(models)[0]).toEqual('testtest');
});
});
describe('getMainField', () => {
it('should return the main field object correctly', () => {
const relationField = 'categories';
const contentTypeConfiguration = {
uid: 'address',
metadatas: {
categories: {
edit: {
mainField: 'name',
},
},
},
attributes: {
categories: {
targetModel: 'category',
},
},
};
const models = [
{
uid: 'category',
attributes: {
name: {
type: 'string',
},
},
},
];
const actual = getMainField(relationField, contentTypeConfiguration, models);
const expected = {
name: 'name',
queryInfos: { defaultParams: {}, endPoint: 'collection-types/address' },
schema: { type: 'string' },
};
expect(actual).toEqual(expected);
});
});
});

View File

@ -1,10 +1,10 @@
import { get } from 'lodash';
const formatFilterName = (name, metadatas) => {
const mainField = get(metadatas, [name, 'edit', 'mainField'], null);
const mainField = get(metadatas, [name, 'list', 'mainField', 'name'], null);
if (mainField) {
return `${name}.${metadatas[name].edit.mainField}`;
return `${name}.${mainField}`;
}
return name;

View File

@ -1,4 +1,6 @@
const formatLayoutToApi = ({ layouts, ...rest }) => {
import { omit, get } from 'lodash';
const formatLayoutToApi = ({ layouts, metadatas, ...rest }) => {
const list = layouts.list.map(obj => {
if (obj.name) {
return obj.name;
@ -7,6 +9,19 @@ const formatLayoutToApi = ({ layouts, ...rest }) => {
return obj;
});
const editRelations = layouts.editRelations.map(({ name }) => name);
const formattedRelationsMetadatas = editRelations.reduce((acc, current) => {
const currentMetadatas = get(metadatas, [current], {});
return {
...acc,
[current]: {
...currentMetadatas,
list: omit(currentMetadatas.list, ['mainField']),
},
};
}, {});
const edit = layouts.edit.map(row =>
row.map(({ name, size }) => ({
name,
@ -14,7 +29,11 @@ const formatLayoutToApi = ({ layouts, ...rest }) => {
}))
);
return { ...rest, layouts: { edit, editRelations, list } };
return {
...rest,
layouts: { edit, editRelations, list },
metadatas: { ...metadatas, ...formattedRelationsMetadatas },
};
};
export default formatLayoutToApi;

View File

@ -1,9 +0,0 @@
import { get } from 'lodash';
const getMainFieldType = (editRelations, relationName) => {
const relationSchema = editRelations.find(relation => relation.name === relationName);
return get(relationSchema, ['metadatas', 'mainFieldSchema', 'type'], null);
};
export default getMainFieldType;

View File

@ -8,7 +8,6 @@ export { default as formatLayoutToApi } from './formatLayoutToApi';
export { default as generatePermissionsObject } from './generatePermissionsObject';
export { default as getFieldName } from './getFieldName';
export { default as getInjectedComponents } from './getComponents';
export { default as getMainFieldType } from './getMainFieldType';
export { default as getMaxTempKey } from './getMaxTempKey';
export { default as getRequestUrl } from './getRequestUrl';
export { default as getTrad } from './getTrad';

View File

@ -5,13 +5,13 @@ describe('CONTENT MANAGER | utils', () => {
it('should return the filters query', () => {
const metadatas = {
categories: {
edit: {
mainField: 'name',
list: {
mainField: { name: 'name' },
},
},
like: {
edit: {
mainField: 'numbers',
list: {
mainField: { name: 'numbers' },
},
},
};

View File

@ -20,4 +20,29 @@ describe('CONTENT MANAGER | utils | formatLayoutToApi', () => {
expect(formatLayoutToApi({ layouts }).layouts.list).toEqual(['test']);
});
it('should remove the mainField in the metadatas relations list', () => {
const layouts = {
list: ['test'],
edit: [],
editRelations: [{ name: 'categories' }],
};
const metadatas = {
categories: {
list: {
mainField: {
name: 'name',
schema: {
type: 'string',
},
},
data: 1,
},
},
};
expect(
formatLayoutToApi({ layouts, metadatas }).metadatas.categories.list.mainField
).toBeUndefined();
});
});

View File

@ -1,28 +0,0 @@
import getMainFieldType from '../getMainFieldType';
describe('CONTENT MANAGER | UTILS | getMainFieldType', () => {
const editRelationsSchemas = [
{
name: 'categories',
metadatas: {
mainFieldSchema: {
type: 'string',
},
},
},
{
name: 'likes',
metadatas: {
mainFieldSchema: {
type: 'number',
},
},
},
];
it('should return null if the relation schema does not exist', () => {
expect(getMainFieldType(editRelationsSchemas, 'address')).toEqual(null);
});
it('should return the main field type', () => {
expect(getMainFieldType(editRelationsSchemas, 'categories')).toEqual('string');
});
});

View File

@ -40,6 +40,7 @@
"react-redux": "^7.0.2",
"react-router": "^5.0.0",
"react-router-dom": "^5.0.0",
"react-tooltip": "4.2.11",
"reactstrap": "8.4.1",
"redux": "^4.0.1",
"redux-immutable": "^4.0.0",

View File

@ -15578,6 +15578,14 @@ react-test-renderer@^16.0.0-0, react-test-renderer@^16.9.0:
react-is "^16.8.6"
scheduler "^0.19.1"
react-tooltip@4.2.11:
version "4.2.11"
resolved "https://registry.yarnpkg.com/react-tooltip/-/react-tooltip-4.2.11.tgz#244d4d1833c583160c4e6c95a8a89e0fb320e18a"
integrity sha512-exREte3mK/qbeuQpFbEL3ImdF5/TSAb+x/T7pkVfKmgVcfQLZKHSgLN+Msv7ZOHxaWNJwuCrSsCAy/iTGoPigg==
dependencies:
prop-types "^15.7.2"
uuid "^7.0.3"
react-transition-group@4.4.1, react-transition-group@^4.3.0:
version "4.4.1"
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.1.tgz#63868f9325a38ea5ee9535d828327f85773345c9"
@ -19198,7 +19206,7 @@ uuid@^3.0.1, uuid@^3.1.0, uuid@^3.2.1, uuid@^3.3.2, uuid@^3.3.3, uuid@^3.4.0:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
uuid@^7.0.1:
uuid@^7.0.1, uuid@^7.0.3:
version "7.0.3"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b"
integrity sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==