diff --git a/src/admin/components/elements/DocumentDrawer/types.ts b/src/admin/components/elements/DocumentDrawer/types.ts index 6c88e4379c..9592b2a750 100644 --- a/src/admin/components/elements/DocumentDrawer/types.ts +++ b/src/admin/components/elements/DocumentDrawer/types.ts @@ -1,14 +1,10 @@ import React, { HTMLAttributes } from 'react'; -import { SanitizedCollectionConfig } from '../../../../collections/config/types'; +import { Props as EditViewProps } from '../../views/collections/Edit/types'; export type DocumentDrawerProps = { collectionSlug: string id?: string - onSave?: (json: { - doc: Record - message: string - collectionConfig: SanitizedCollectionConfig - }) => void + onSave?: EditViewProps['onSave'] customHeader?: React.ReactNode drawerSlug?: string } diff --git a/src/admin/components/forms/field-types/Relationship/AddNew/index.tsx b/src/admin/components/forms/field-types/Relationship/AddNew/index.tsx index d778373feb..2032413eb7 100644 --- a/src/admin/components/forms/field-types/Relationship/AddNew/index.tsx +++ b/src/admin/components/forms/field-types/Relationship/AddNew/index.tsx @@ -11,12 +11,21 @@ import { getTranslation } from '../../../../../../utilities/getTranslation'; import Tooltip from '../../../../elements/Tooltip'; import { useDocumentDrawer } from '../../../../elements/DocumentDrawer'; import { useConfig } from '../../../../utilities/Config'; +import { Props as EditViewProps } from '../../../../views/collections/Edit/types'; +import { Value } from '../types'; import './index.scss'; const baseClass = 'relationship-add-new'; -export const AddNewRelation: React.FC = ({ path, hasMany, relationTo, value, setValue, dispatchOptions }) => { +export const AddNewRelation: React.FC = ({ + path, + hasMany, + relationTo, + value, + setValue, + dispatchOptions, +}) => { const relatedCollections = useRelatedCollections(relationTo); const { permissions } = useAuth(); const [show, setShow] = useState(false); @@ -36,30 +45,43 @@ export const AddNewRelation: React.FC = ({ path, hasMany, relationTo, val collectionSlug: collectionConfig?.slug, }); - const onSave = useCallback((json) => { - const newValue = Array.isArray(relationTo) ? { - relationTo: collectionConfig.slug, - value: json.doc.id, - } : json.doc.id; + const onSave: EditViewProps['onSave'] = useCallback(({ + operation, + doc, + }) => { + if (operation === 'create') { + const newValue: Value = Array.isArray(relationTo) ? { + relationTo: collectionConfig.slug, + value: doc.id, + } : doc.id; - dispatchOptions({ - type: 'ADD', - collection: collectionConfig, - docs: [ - json.doc, - ], - sort: true, - i18n, - config, - }); + // ensure the value is not already in the array + const isNewValue = Array.isArray(relationTo) && Array.isArray(value) + ? !value.some((v) => v && typeof v === 'object' && v.value === doc.id) + : value !== doc.id; - if (hasMany) { - setValue([...(Array.isArray(value) ? value : []), newValue]); - } else { - setValue(newValue); + if (isNewValue) { + dispatchOptions({ + type: 'ADD', + collection: collectionConfig, + docs: [ + doc, + ], + sort: true, + i18n, + config, + }); + + + if (hasMany) { + setValue([...(Array.isArray(value) ? value : []), newValue]); + } else { + setValue(newValue); + } + } + + setSelectedCollection(undefined); } - - setSelectedCollection(undefined); }, [relationTo, collectionConfig, dispatchOptions, i18n, hasMany, setValue, value, config]); const onPopopToggle = useCallback((state) => { diff --git a/src/admin/components/forms/field-types/Relationship/AddNew/types.ts b/src/admin/components/forms/field-types/Relationship/AddNew/types.ts index 64cf5d8a0e..bffb2bc31b 100644 --- a/src/admin/components/forms/field-types/Relationship/AddNew/types.ts +++ b/src/admin/components/forms/field-types/Relationship/AddNew/types.ts @@ -1,11 +1,12 @@ import React from 'react'; -import { Action } from '../types'; +import { Action, OptionGroup, Value } from '../types'; export type Props = { hasMany: boolean relationTo: string | string[] path: string - value: unknown + value: Value | Value[] + options: OptionGroup[] setValue: (value: unknown) => void dispatchOptions: React.Dispatch } diff --git a/src/admin/components/forms/field-types/Relationship/index.tsx b/src/admin/components/forms/field-types/Relationship/index.tsx index 360bc9f51e..f1d1dfa735 100644 --- a/src/admin/components/forms/field-types/Relationship/index.tsx +++ b/src/admin/components/forms/field-types/Relationship/index.tsx @@ -450,7 +450,7 @@ const Relationship: React.FC = (props) => { /> {!readOnly && allowCreate && ( )} diff --git a/src/admin/components/views/collections/Edit/Default.tsx b/src/admin/components/views/collections/Edit/Default.tsx index 2d7ed5d16e..8fe8bb15eb 100644 --- a/src/admin/components/views/collections/Edit/Default.tsx +++ b/src/admin/components/views/collections/Edit/Default.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useCallback } from 'react'; import { Link } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { useConfig } from '../../../utilities/Config'; @@ -43,7 +43,7 @@ const DefaultEditView: React.FC = (props) => { collection, isEditing, data, - onSave, + onSave: onSaveFromProps, permissions, isLoading, internalState, @@ -78,6 +78,15 @@ const DefaultEditView: React.FC = (props) => { isEditing && `${baseClass}--is-editing`, ].filter(Boolean).join(' '); + const onSave = useCallback((json) => { + if (typeof onSaveFromProps === 'function') { + onSaveFromProps({ + ...json, + operation: id ? 'update' : 'create', + }); + } + }, [id, onSaveFromProps]); + const operation = isEditing ? 'update' : 'create'; return ( diff --git a/src/admin/components/views/collections/Edit/types.ts b/src/admin/components/views/collections/Edit/types.ts index 0560a0671b..1e250fc61a 100644 --- a/src/admin/components/views/collections/Edit/types.ts +++ b/src/admin/components/views/collections/Edit/types.ts @@ -11,7 +11,12 @@ export type IndexProps = { export type Props = IndexProps & { data: Document - onSave?: () => void + onSave?: (json: Record & { + doc: Record + message: string + collectionConfig: SanitizedCollectionConfig + operation: 'create' | 'update', + }) => void id?: string permissions: CollectionPermission isLoading: boolean diff --git a/test/fields-relationship/collectionSlugs.ts b/test/fields-relationship/collectionSlugs.ts index 0cc108185b..9ad67eeef2 100644 --- a/test/fields-relationship/collectionSlugs.ts +++ b/test/fields-relationship/collectionSlugs.ts @@ -1,4 +1,3 @@ - export const slug = 'fields-relationship'; export const relationOneSlug = 'relation-one'; diff --git a/test/fields-relationship/e2e.spec.ts b/test/fields-relationship/e2e.spec.ts index f02c058db7..12ac494bcd 100644 --- a/test/fields-relationship/e2e.spec.ts +++ b/test/fields-relationship/e2e.spec.ts @@ -277,6 +277,33 @@ describe('fields - relationship', () => { await expect(documentDrawer).toBeVisible(); }); + test('should open document drawer and append newly created docs onto the parent field', async () => { + await page.goto(url.edit(docWithExistingRelations.id)); + + const field = page.locator('#field-relationshipHasMany'); + + // open the document drawer + const addNewButton = await field.locator('button.relationship-add-new__add-button.doc-drawer__toggler'); + await addNewButton.click(); + const documentDrawer = await page.locator('[id^=doc-drawer_relation-one_1_]'); + await expect(documentDrawer).toBeVisible(); + + // fill in the field and save the document, keep the drawer open for further testing + const drawerField = await documentDrawer.locator('#field-name'); + await drawerField.fill('Newly created document'); + const saveButton = await documentDrawer.locator('#action-save'); + await saveButton.click(); + await expect(page.locator('.Toastify')).toContainText('successfully'); + + // count the number of values in the field to ensure only one was added + await expect(await page.locator('#field-relationshipHasMany .value-container .rs__multi-value')).toHaveCount(1); + + // save the same document again to ensure the relationship field doesn't receive duplicative values + await saveButton.click(); + await expect(page.locator('.Toastify')).toContainText('successfully'); + await expect(await page.locator('#field-relationshipHasMany .value-container .rs__multi-value')).toHaveCount(1); + }); + describe('existing relationships', () => { test('should highlight existing relationship', async () => { await page.goto(url.edit(docWithExistingRelations.id)); @@ -363,25 +390,21 @@ describe('fields - relationship', () => { }); }); - describe('externally update field', () => { + describe('externally update relationship field', () => { beforeAll(async () => { - url = new AdminUrlUtil(serverURL, relationUpdatedExternallySlug); - await page.goto(url.create); + const externalRelationURL = new AdminUrlUtil(serverURL, relationUpdatedExternallySlug); + await page.goto(externalRelationURL.create); }); test('has many, one collection', async () => { - await page.goto(url.create); - await page.locator('#field-relationHasMany + .pre-populate-field-ui button').click(); await wait(300); - await expect(page.locator('#field-relationHasMany .rs__value-container > .rs__multi-value')).toHaveCount(15); }); test('has many, many collections', async () => { await page.locator('#field-relationToManyHasMany + .pre-populate-field-ui button').click(); await wait(300); - await expect(page.locator('#field-relationToManyHasMany .rs__value-container > .rs__multi-value')).toHaveCount(15); }); });