mirror of
https://github.com/strapi/strapi.git
synced 2025-09-25 08:19:07 +00:00
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:
commit
0a754813b2
@ -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": {
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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);
|
||||
|
@ -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>
|
||||
|
@ -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,
|
||||
|
@ -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;
|
@ -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);
|
||||
|
@ -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;
|
@ -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);
|
||||
|
||||
|
@ -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}
|
||||
|
@ -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: {
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
@ -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';
|
||||
|
@ -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' },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -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');
|
||||
});
|
||||
});
|
@ -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",
|
||||
|
10
yarn.lock
10
yarn.lock
@ -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==
|
||||
|
Loading…
x
Reference in New Issue
Block a user