From ae384306eb70ce584ca4908187408c7bbc69acc5 Mon Sep 17 00:00:00 2001 From: Jacob Fletcher Date: Mon, 22 May 2023 17:13:35 -0400 Subject: [PATCH 1/4] chore: threads operation through the default edit view onSave handler --- .../components/elements/DocumentDrawer/types.ts | 8 ++------ .../forms/field-types/Relationship/AddNew/index.tsx | 3 ++- .../components/views/collections/Edit/Default.tsx | 13 +++++++++++-- .../components/views/collections/Edit/types.ts | 7 ++++++- 4 files changed, 21 insertions(+), 10 deletions(-) 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..95866014a4 100644 --- a/src/admin/components/forms/field-types/Relationship/AddNew/index.tsx +++ b/src/admin/components/forms/field-types/Relationship/AddNew/index.tsx @@ -11,6 +11,7 @@ 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 './index.scss'; @@ -36,7 +37,7 @@ export const AddNewRelation: React.FC = ({ path, hasMany, relationTo, val collectionSlug: collectionConfig?.slug, }); - const onSave = useCallback((json) => { + const onSave: EditViewProps['onSave'] = useCallback((json) => { const newValue = Array.isArray(relationTo) ? { relationTo: collectionConfig.slug, value: json.doc.id, 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 From a2a8ac9549bd67e6ab578772689684fd2bc64872 Mon Sep 17 00:00:00 2001 From: Jacob Fletcher Date: Mon, 22 May 2023 17:54:35 -0400 Subject: [PATCH 2/4] fix: prevents add new relationship modal from adding duplicative values to the parent doc #2688 --- .../field-types/Relationship/AddNew/index.tsx | 65 ++++++++++++------- .../field-types/Relationship/AddNew/types.ts | 5 +- .../forms/field-types/Relationship/index.tsx | 2 +- 3 files changed, 47 insertions(+), 25 deletions(-) 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 95866014a4..2032413eb7 100644 --- a/src/admin/components/forms/field-types/Relationship/AddNew/index.tsx +++ b/src/admin/components/forms/field-types/Relationship/AddNew/index.tsx @@ -12,12 +12,20 @@ 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); @@ -37,30 +45,43 @@ export const AddNewRelation: React.FC = ({ path, hasMany, relationTo, val collectionSlug: collectionConfig?.slug, }); - const onSave: EditViewProps['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 && ( )} From bbf114b82273a4f9747681fa37aff519fc8f5505 Mon Sep 17 00:00:00 2001 From: Jacob Fletcher Date: Mon, 22 May 2023 23:59:09 -0400 Subject: [PATCH 3/4] chore: writes e2e test for relationships created using the document drawer --- test/fields-relationship/e2e.spec.ts | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/test/fields-relationship/e2e.spec.ts b/test/fields-relationship/e2e.spec.ts index f02c058db7..7e578c703c 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)); @@ -365,7 +392,6 @@ describe('fields - relationship', () => { describe('externally update field', () => { beforeAll(async () => { - url = new AdminUrlUtil(serverURL, relationUpdatedExternallySlug); await page.goto(url.create); }); From 2809cb910c0d16adcc3905adfd45bd84fdc8824d Mon Sep 17 00:00:00 2001 From: Jacob Fletcher Date: Tue, 23 May 2023 10:37:52 -0400 Subject: [PATCH 4/4] chore: fixes broken test for externally updated relationships --- test/fields-relationship/collectionSlugs.ts | 1 - test/fields-relationship/e2e.spec.ts | 9 +++------ 2 files changed, 3 insertions(+), 7 deletions(-) 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 7e578c703c..12ac494bcd 100644 --- a/test/fields-relationship/e2e.spec.ts +++ b/test/fields-relationship/e2e.spec.ts @@ -390,24 +390,21 @@ describe('fields - relationship', () => { }); }); - describe('externally update field', () => { + describe('externally update relationship field', () => { beforeAll(async () => { - 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); }); });