diff --git a/examples/getstarted/api/address/models/Address.settings.json b/examples/getstarted/api/address/models/Address.settings.json index 8aae91bc97..dfc50b3aeb 100755 --- a/examples/getstarted/api/address/models/Address.settings.json +++ b/examples/getstarted/api/address/models/Address.settings.json @@ -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": { diff --git a/packages/strapi-plugin-content-manager/admin/src/components/CustomTable/Headers/Header.js b/packages/strapi-plugin-content-manager/admin/src/components/CustomTable/Headers/Header.js index 1fc5a6656c..293656820e 100644 --- a/packages/strapi-plugin-content-manager/admin/src/components/CustomTable/Headers/Header.js +++ b/packages/strapi-plugin-content-manager/admin/src/components/CustomTable/Headers/Header.js @@ -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, }; diff --git a/packages/strapi-plugin-content-manager/admin/src/components/CustomTable/Row/Cell.js b/packages/strapi-plugin-content-manager/admin/src/components/CustomTable/Row/Cell.js index 70d5d70816..fb70955fd6 100644 --- a/packages/strapi-plugin-content-manager/admin/src/components/CustomTable/Row/Cell.js +++ b/packages/strapi-plugin-content-manager/admin/src/components/CustomTable/Row/Cell.js @@ -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 ; +const Cell = ({ options }) => { + if (options.type === 'media') { + return ; } - if (type === 'relation') { - return ; + if (options.type === 'relation') { + return ; } return ( - {value} + {options.value} ); }; -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); diff --git a/packages/strapi-plugin-content-manager/admin/src/components/CustomTable/Row/index.js b/packages/strapi-plugin-content-manager/admin/src/components/CustomTable/Row/index.js index c3ededce9c..eba03944d3 100644 --- a/packages/strapi-plugin-content-manager/admin/src/components/CustomTable/Row/index.js +++ b/packages/strapi-plugin-content-manager/admin/src/components/CustomTable/Row/index.js @@ -70,10 +70,15 @@ function Row({ canCreate, canDelete, canUpdate, isBulkable, row, headers, goTo } cellFormatter(row) ) : ( )} diff --git a/packages/strapi-plugin-content-manager/admin/src/components/FilterPicker/index.js b/packages/strapi-plugin-content-manager/admin/src/components/FilterPicker/index.js index 5eea17569d..ba9a1d5ff9 100644 --- a/packages/strapi-plugin-content-manager/admin/src/components/FilterPicker/index.js +++ b/packages/strapi-plugin-content-manager/admin/src/components/FilterPicker/index.js @@ -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, diff --git a/packages/strapi-plugin-content-manager/admin/src/components/RelationPreviewList/RelationPreviewTooltip.js b/packages/strapi-plugin-content-manager/admin/src/components/RelationPreviewList/RelationPreviewTooltip.js new file mode 100644 index 0000000000..5db20917c1 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/components/RelationPreviewList/RelationPreviewTooltip.js @@ -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 ( + + + {isLoading ? ( + + + + ) : ( + <> + {relationData.map(item => ( + + + {getValueToDisplay(item)} + + + ))} + {relationData.length > 10 && [...]} + > + )} + + + ); +}; + +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; diff --git a/packages/strapi-plugin-content-manager/admin/src/components/RelationPreviewList/index.js b/packages/strapi-plugin-content-manager/admin/src/components/RelationPreviewList/index.js index 693a101ea4..c835aacd61 100644 --- a/packages/strapi-plugin-content-manager/admin/src/components/RelationPreviewList/index.js +++ b/packages/strapi-plugin-content-manager/admin/src/components/RelationPreviewList/index.js @@ -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 ( - {value ? value[mainField] : '-'} + {value ? value[mainField.name] : '-'} ); } const size = value ? value.length : 0; + const handleTooltipToggle = () => { + setDisplayTooltip(prev => !prev); + }; + return ( - - + + @@ -36,20 +60,31 @@ const RelationPreviewList = ({ metadatas: { mainField }, relationType, value }) })} + {size > 0 && tooltipIsDisplayed && ( + + )} ); }; -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); diff --git a/packages/strapi-plugin-content-manager/admin/src/components/Tooltip/index.js b/packages/strapi-plugin-content-manager/admin/src/components/Tooltip/index.js new file mode 100644 index 0000000000..91637bc938 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/components/Tooltip/index.js @@ -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; diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/ListView/Filter.js b/packages/strapi-plugin-content-manager/admin/src/containers/ListView/Filter.js index a56b8b3732..53fa1c52be 100644 --- a/packages/strapi-plugin-content-manager/admin/src/containers/ListView/Filter.js +++ b/packages/strapi-plugin-content-manager/admin/src/containers/ListView/Filter.js @@ -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); diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/ListView/index.js b/packages/strapi-plugin-content-manager/admin/src/containers/ListView/index.js index 3d4e95ddff..f7d0db673f 100644 --- a/packages/strapi-plugin-content-manager/admin/src/containers/ListView/index.js +++ b/packages/strapi-plugin-content-manager/admin/src/containers/ListView/index.js @@ -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} diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/ListView/reducer.js b/packages/strapi-plugin-content-manager/admin/src/containers/ListView/reducer.js index eaac04d6e4..bd043f846a 100644 --- a/packages/strapi-plugin-content-manager/admin/src/containers/ListView/reducer.js +++ b/packages/strapi-plugin-content-manager/admin/src/containers/ListView/reducer.js @@ -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: { diff --git a/packages/strapi-plugin-content-manager/admin/src/hooks/useFetchContentTypeLayout/utils/formatLayouts.js b/packages/strapi-plugin-content-manager/admin/src/hooks/useFetchContentTypeLayout/utils/formatLayouts.js index 4babf0c1ab..83ab2e1c9e 100644 --- a/packages/strapi-plugin-content-manager/admin/src/hooks/useFetchContentTypeLayout/utils/formatLayouts.js +++ b/packages/strapi-plugin-content-manager/admin/src/hooks/useFetchContentTypeLayout/utils/formatLayouts.js @@ -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, }; diff --git a/packages/strapi-plugin-content-manager/admin/src/hooks/useFetchContentTypeLayout/utils/tests/formatLayouts.test.js b/packages/strapi-plugin-content-manager/admin/src/hooks/useFetchContentTypeLayout/utils/tests/formatLayouts.test.js index 568909f0f4..4ff2be4ec3 100644 --- a/packages/strapi-plugin-content-manager/admin/src/hooks/useFetchContentTypeLayout/utils/tests/formatLayouts.test.js +++ b/packages/strapi-plugin-content-manager/admin/src/hooks/useFetchContentTypeLayout/utils/tests/formatLayouts.test.js @@ -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); + }); + }); }); diff --git a/packages/strapi-plugin-content-manager/admin/src/utils/formatFiltersToQuery.js b/packages/strapi-plugin-content-manager/admin/src/utils/formatFiltersToQuery.js index 442b5aa5fd..d64ab966a2 100644 --- a/packages/strapi-plugin-content-manager/admin/src/utils/formatFiltersToQuery.js +++ b/packages/strapi-plugin-content-manager/admin/src/utils/formatFiltersToQuery.js @@ -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; diff --git a/packages/strapi-plugin-content-manager/admin/src/utils/formatLayoutToApi.js b/packages/strapi-plugin-content-manager/admin/src/utils/formatLayoutToApi.js index dd5179e6e3..66f6b199f7 100644 --- a/packages/strapi-plugin-content-manager/admin/src/utils/formatLayoutToApi.js +++ b/packages/strapi-plugin-content-manager/admin/src/utils/formatLayoutToApi.js @@ -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; diff --git a/packages/strapi-plugin-content-manager/admin/src/utils/getMainFieldType.js b/packages/strapi-plugin-content-manager/admin/src/utils/getMainFieldType.js deleted file mode 100644 index 4f71e32e2f..0000000000 --- a/packages/strapi-plugin-content-manager/admin/src/utils/getMainFieldType.js +++ /dev/null @@ -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; diff --git a/packages/strapi-plugin-content-manager/admin/src/utils/index.js b/packages/strapi-plugin-content-manager/admin/src/utils/index.js index 8e7040e825..0bc1067acd 100644 --- a/packages/strapi-plugin-content-manager/admin/src/utils/index.js +++ b/packages/strapi-plugin-content-manager/admin/src/utils/index.js @@ -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'; diff --git a/packages/strapi-plugin-content-manager/admin/src/utils/tests/formatFiltersToQuery.test.js b/packages/strapi-plugin-content-manager/admin/src/utils/tests/formatFiltersToQuery.test.js index 6090ded717..b0e8cbba69 100644 --- a/packages/strapi-plugin-content-manager/admin/src/utils/tests/formatFiltersToQuery.test.js +++ b/packages/strapi-plugin-content-manager/admin/src/utils/tests/formatFiltersToQuery.test.js @@ -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' }, }, }, }; diff --git a/packages/strapi-plugin-content-manager/admin/src/utils/tests/formatLayoutToApi.test.js b/packages/strapi-plugin-content-manager/admin/src/utils/tests/formatLayoutToApi.test.js index 98c96d0ce2..397d558ec1 100644 --- a/packages/strapi-plugin-content-manager/admin/src/utils/tests/formatLayoutToApi.test.js +++ b/packages/strapi-plugin-content-manager/admin/src/utils/tests/formatLayoutToApi.test.js @@ -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(); + }); }); diff --git a/packages/strapi-plugin-content-manager/admin/src/utils/tests/getMainFieldType.test.js b/packages/strapi-plugin-content-manager/admin/src/utils/tests/getMainFieldType.test.js deleted file mode 100644 index 1a3f36274c..0000000000 --- a/packages/strapi-plugin-content-manager/admin/src/utils/tests/getMainFieldType.test.js +++ /dev/null @@ -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'); - }); -}); diff --git a/packages/strapi-plugin-content-manager/package.json b/packages/strapi-plugin-content-manager/package.json index 01f146df5f..6c8a30199f 100644 --- a/packages/strapi-plugin-content-manager/package.json +++ b/packages/strapi-plugin-content-manager/package.json @@ -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", diff --git a/yarn.lock b/yarn.lock index a57a48b68d..0c26ae179b 100644 --- a/yarn.lock +++ b/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==