diff --git a/static/src/js/App.jsx b/static/src/js/App.jsx index 85076eded..030d22ac6 100644 --- a/static/src/js/App.jsx +++ b/static/src/js/App.jsx @@ -16,8 +16,8 @@ import MetadataFormPage from '@/js/pages/MetadataFormPage/MetadataFormPage' import OrderOptionFormPage from '@/js/pages/OrderOptionFormPage/OrderOptionFormPage' import OrderOptionListPage from '@/js/pages/OrderOptionListPage/OrderOptionListPage' import OrderOptionPage from '@/js/pages/OrderOptionPage/OrderOptionPage' -// Import PermissionListPage from '@/js/pages/PermissionListPage/PermissionListPage' -// Import PermissionPage from '@/js/pages/PermissionPage/PermissionPage' +import PermissionListPage from '@/js/pages/PermissionListPage/PermissionListPage' +import PermissionPage from '@/js/pages/PermissionPage/PermissionPage' import ProviderPermissionsPage from '@/js/pages/ProviderPermissionsPage/ProviderPermissionsPage' import ProvidersPage from '@/js/pages/ProvidersPage/ProvidersPage' import RevisionListPage from '@/js/pages/RevisionListPage/RevisionListPage' @@ -31,7 +31,6 @@ import ErrorPageNotFound from '@/js/components/ErrorPageNotFound/ErrorPageNotFou import Layout from '@/js/components/Layout/Layout' import LayoutUnauthenticated from '@/js/components/LayoutUnauthenticated/LayoutUnauthenticated' import Notifications from '@/js/components/Notifications/Notifications' -// Import PermissionFormPage from '@/js/pages/PermissionFormPage/PermissionFormPage' import PublishPreview from '@/js/components/PublishPreview/PublishPreview' import TemplateForm from '@/js/components/TemplateForm/TemplateForm' import TemplateList from '@/js/components/TemplateList/TemplateList' @@ -40,6 +39,7 @@ import TemplatePreview from '@/js/components/TemplatePreview/TemplatePreview' import REDIRECTS from '@/js/constants/redirectsMap/redirectsMap' import withProviders from '@/js/providers/withProviders/withProviders' +import PermissionFormPage from './pages/PermissionFormPage/PermissionFormPage' import '../css/index.scss' @@ -140,36 +140,20 @@ export const App = () => { }, { path: '/permissions', - element: + element: }, - // { - // path: '/permissions', - // element: - // }, { path: '/permissions/new', - element: + element: }, - // { - // path: '/permissions/new', - // element: - // }, { path: '/permissions/:conceptId/edit', - element: + element: }, - // { - // path: '/permissions/:conceptId/edit', - // element: - // }, { path: '/permissions/:conceptId', - element: + element: }, - // { - // path: '/permissions/:conceptId', - // element: - // }, { path: '/order-options', element: diff --git a/static/src/js/components/CollectionSelector/CollectionSelector.jsx b/static/src/js/components/CollectionSelector/CollectionSelector.jsx index 2003a7ea9..56b22f7fa 100644 --- a/static/src/js/components/CollectionSelector/CollectionSelector.jsx +++ b/static/src/js/components/CollectionSelector/CollectionSelector.jsx @@ -192,7 +192,7 @@ const CollectionSelector = ({ onChange, formData }) => { } }, shortName: `${inputValue}*`, - limit: 20 + limit: 100 } }, onCompleted: (data) => { diff --git a/static/src/js/components/CollectionSelector/__tests__/CollectionSelector.test.jsx b/static/src/js/components/CollectionSelector/__tests__/CollectionSelector.test.jsx index ae82a18f4..e779f81f8 100644 --- a/static/src/js/components/CollectionSelector/__tests__/CollectionSelector.test.jsx +++ b/static/src/js/components/CollectionSelector/__tests__/CollectionSelector.test.jsx @@ -270,7 +270,7 @@ describe('CollectionSelector', () => { await user.type(searchField, 'C') - expect(await screen.findByText('Showing 2 items')).toBeInTheDocument() + expect(await screen.findByText('Showing 1 items')).toBeInTheDocument() }) }) diff --git a/static/src/js/components/Layout/Layout.jsx b/static/src/js/components/Layout/Layout.jsx index 99427b015..a9566f550 100644 --- a/static/src/js/components/Layout/Layout.jsx +++ b/static/src/js/components/Layout/Layout.jsx @@ -138,11 +138,11 @@ const Layout = ({ className, displayNav }) => { { to: '/templates/collections', title: 'Templates' + }, + { + to: '/permissions', + title: 'Permissions' } - // { - // to: '/permissions', - // title: 'Permissions' - // } ] }, { diff --git a/static/src/js/components/Layout/__tests__/Layout.test.jsx b/static/src/js/components/Layout/__tests__/Layout.test.jsx index d9aeaec2f..1bcfbfdbb 100644 --- a/static/src/js/components/Layout/__tests__/Layout.test.jsx +++ b/static/src/js/components/Layout/__tests__/Layout.test.jsx @@ -103,11 +103,11 @@ describe('Layout component', () => { { to: '/templates/collections', title: 'Templates' + }, + { + title: 'Permissions', + to: '/permissions' } - // { - // title: 'Permissions', - // to: '/permissions' - // } ] }, { @@ -215,11 +215,11 @@ describe('Layout component', () => { { to: '/templates/collections', title: 'Templates' + }, + { + title: 'Permissions', + to: '/permissions' } - // { - // title: 'Permissions', - // to: '/permissions' - // } ] }, { diff --git a/static/src/js/components/PermissionForm/PermissionForm.jsx b/static/src/js/components/PermissionForm/PermissionForm.jsx index 27dbcaee3..916870cca 100644 --- a/static/src/js/components/PermissionForm/PermissionForm.jsx +++ b/static/src/js/components/PermissionForm/PermissionForm.jsx @@ -1,5 +1,6 @@ import Form from '@rjsf/core' import React, { useEffect, useState } from 'react' +import PropTypes from 'prop-types' import Button from 'react-bootstrap/Button' import Col from 'react-bootstrap/Col' @@ -32,10 +33,10 @@ import { UPDATE_ACL } from '@/js/operations/mutations/updateAcl' import CollectionSelectorPage from '@/js/pages/CollectionSelectorPage/CollectionSelectorPage' +import { cloneDeep } from '@apollo/client/utilities' import { GET_COLLECTION_FOR_PERMISSION_FORM } from '@/js/operations/queries/getCollectionForPermissionForm' - import CustomArrayFieldTemplate from '../CustomArrayFieldTemplate/CustomArrayFieldTemplate' import CustomDateTimeWidget from '../CustomDateTimeWidget/CustomDateTimeWidget' import CustomFieldTemplate from '../CustomFieldTemplate/CustomFieldTemplate' @@ -164,7 +165,7 @@ const validate = (formData, errors) => { * * ) */ -const PermissionForm = () => { +const PermissionForm = ({ selectedCollectionsPageSize }) => { const { draft, originalDraft, @@ -217,16 +218,59 @@ const PermissionForm = () => { TitleField: CustomTitleFieldTemplate } - const { data } = useSuspenseQuery(GET_COLLECTION_FOR_PERMISSION_FORM, { + const [error, setError] = useState(null) + + const updateQuery = (prev, { fetchMoreResult }) => { + const newResult = cloneDeep(prev) + const { acl } = newResult + const { collections } = acl + const { items = [] } = collections || {} + + items.push(...fetchMoreResult.acl.collections.items) + + return newResult + } + + const { data, fetchMore } = useSuspenseQuery(GET_COLLECTION_FOR_PERMISSION_FORM, { skip: conceptId === 'new', variables: { conceptId, params: { - limit: 2000 + offset: 0, + limit: selectedCollectionsPageSize } } }) + useEffect(() => { + async function fetchData() { + if (!data) return + + const { acl } = data + const { collections } = acl + const { count, items = [] } = collections || {} + + if (items.length < count) { + await fetchMore({ + variables: { + conceptId, + params: { + offset: items.length, + limit: selectedCollectionsPageSize + } + }, + updateQuery + }) + } + } + + fetchData() + .catch((err) => { + errorLogger('Error fetching more collection permissions', 'PermissionForm: fetchData') + setError(err) + }) + }, [data]) + useEffect(() => { if (conceptId === 'new') { setDraft({}) @@ -234,6 +278,12 @@ const PermissionForm = () => { } }, [conceptId]) + useEffect(() => { + if (error) { + throw error + } + }, [error]) + /** * Updates the UI schema based on the granule access permission in the provided form data. * @@ -729,4 +779,12 @@ const PermissionForm = () => { ) } +PermissionForm.defaultProps = { + selectedCollectionsPageSize: 2000 +} + +PermissionForm.propTypes = { + selectedCollectionsPageSize: PropTypes.number +} + export default PermissionForm diff --git a/static/src/js/components/PermissionForm/__tests__/PermissionForm.test.jsx b/static/src/js/components/PermissionForm/__tests__/PermissionForm.test.jsx index 49ae735e4..02dec1e3e 100644 --- a/static/src/js/components/PermissionForm/__tests__/PermissionForm.test.jsx +++ b/static/src/js/components/PermissionForm/__tests__/PermissionForm.test.jsx @@ -1,6 +1,7 @@ import { render, screen, + waitFor, within } from '@testing-library/react' import userEvent from '@testing-library/user-event' @@ -33,6 +34,7 @@ import { GET_COLLECTION_FOR_PERMISSION_FORM } from '@/js/operations/queries/getCollectionForPermissionForm' import PermissionForm from '../PermissionForm' +import ErrorBoundary from '../../ErrorBoundary/ErrorBoundary' vi.mock('@/js/utils/errorLogger') vi.mock('@/js/hooks/useAvailableProviders') @@ -114,9 +116,11 @@ const setup = ({ - - + + + + + ) } path="new" @@ -125,9 +129,11 @@ const setup = ({ path=":conceptId/edit" element={ ( - - - + + + + + ) } /> @@ -199,7 +205,8 @@ describe('PermissionForm', () => { variables: { conceptId: 'ACL1000000-MMT', params: { - limit: 2000 + offset: 0, + limit: 1 } } }, @@ -234,7 +241,72 @@ describe('PermissionForm', () => { shortName: 'This is collection 2', entryTitle: 'Collection 1', version: '1' + } + ] + }, + groups: { + __typename: 'AclGroupList', + items: [ + { + __typename: 'AclGroup', + permissions: [ + 'read' + ], + userType: 'guest', + id: null, + name: null, + tag: null }, + { + __typename: 'AclGroup', + permissions: [ + 'read' + ], + userType: 'registered', + id: null, + name: null, + tag: null + } + ] + } + } + } + } + }, + { + request: { + query: GET_COLLECTION_FOR_PERMISSION_FORM, + variables: { + conceptId: 'ACL1000000-MMT', + params: { + offset: 1, + limit: 1 + } + } + }, + result: { + data: { + acl: { + __typename: 'Acl', + conceptId: 'ACL1000000-CMR', + identityType: 'Catalog Item', + location: 'https://cmr.sit.earthdata.nasa.gov:443/access-control/acls/ACL1200427411-CMR', + name: 'Mock ACL', + providerIdentity: null, + revisionId: 1, + systemIdentity: null, + catalogItemIdentity: { + __typename: 'CatalogItemIdentity', + collectionIdentifier: {}, + collectionApplicable: true, + granuleApplicable: false, + granuleIdentifier: null, + providerId: 'MM_2' + }, + collections: { + __typename: 'CollectionList', + count: 2, + items: [ { __typename: 'Collection', conceptId: 'C13000000-MMT_2', @@ -320,6 +392,110 @@ describe('PermissionForm', () => { expect(navigateSpy).toHaveBeenCalledTimes(1) expect(navigateSpy).toHaveBeenCalledWith('/permissions/ACL1000000-MMT') }) + + test('should render error when fetchMore fails', async () => { + const navigateSpy = vi.fn() + vi.spyOn(router, 'useNavigate').mockImplementation(() => navigateSpy) + setup({ + pageUrl: '/permissions/ACL1000000-MMT/edit', + mocks: [{ + request: { + query: GET_COLLECTION_FOR_PERMISSION_FORM, + variables: { + conceptId: 'ACL1000000-MMT', + params: { + offset: 0, + limit: 1 + } + } + }, + result: { + data: { + acl: { + __typename: 'Acl', + conceptId: 'ACL1000000-CMR', + identityType: 'Catalog Item', + location: 'https://cmr.sit.earthdata.nasa.gov:443/access-control/acls/ACL1200427411-CMR', + name: 'Mock ACL', + providerIdentity: null, + revisionId: 1, + systemIdentity: null, + catalogItemIdentity: { + __typename: 'CatalogItemIdentity', + collectionIdentifier: {}, + collectionApplicable: true, + granuleApplicable: false, + granuleIdentifier: null, + providerId: 'MM_2' + }, + collections: { + __typename: 'CollectionList', + count: 2, + items: [ + { + __typename: 'Collection', + conceptId: 'C12000000-MMT_2', + directDistributionInformation: null, + provider: 'MMT_2', + shortName: 'This is collection 2', + entryTitle: 'Collection 1', + version: '1' + } + ] + }, + groups: { + __typename: 'AclGroupList', + items: [ + { + __typename: 'AclGroup', + permissions: [ + 'read' + ], + userType: 'guest', + id: null, + name: null, + tag: null + }, + { + __typename: 'AclGroup', + permissions: [ + 'read' + ], + userType: 'registered', + id: null, + name: null, + tag: null + } + ] + } + } + } + } + }, + { + request: { + query: GET_COLLECTION_FOR_PERMISSION_FORM, + variables: { + conceptId: 'ACL1000000-MMT', + params: { + offset: 1, + limit: 1 + } + } + }, + error: new Error('An error occurred') + }] + }) + + await waitFor(() => { + expect(errorLogger).toHaveBeenCalledTimes(1) + }) + + expect(errorLogger).toHaveBeenCalledWith( + 'Error fetching more collection permissions', + 'PermissionForm: fetchData' + ) + }) }) describe('when filling out the form and only filling out search and order permission and submitting', () => { @@ -370,7 +546,8 @@ describe('PermissionForm', () => { variables: { conceptId: 'ACL1000000-MMT', params: { - limit: 2000 + offset: 0, + limit: 1 } } }, @@ -405,7 +582,72 @@ describe('PermissionForm', () => { shortName: 'This is collection 2', entryTitle: 'Collection 1', version: '1' + } + ] + }, + groups: { + __typename: 'AclGroupList', + items: [ + { + __typename: 'AclGroup', + permissions: [ + 'read' + ], + userType: 'guest', + id: null, + name: null, + tag: null }, + { + __typename: 'AclGroup', + permissions: [ + 'read' + ], + userType: 'registered', + id: null, + name: null, + tag: null + } + ] + } + } + } + } + }, + { + request: { + query: GET_COLLECTION_FOR_PERMISSION_FORM, + variables: { + conceptId: 'ACL1000000-MMT', + params: { + offset: 1, + limit: 1 + } + } + }, + result: { + data: { + acl: { + __typename: 'Acl', + conceptId: 'ACL1000000-CMR', + identityType: 'Catalog Item', + location: 'https://cmr.sit.earthdata.nasa.gov:443/access-control/acls/ACL1200427411-CMR', + name: 'Mock ACL', + providerIdentity: null, + revisionId: 1, + systemIdentity: null, + catalogItemIdentity: { + __typename: 'CatalogItemIdentity', + collectionIdentifier: {}, + collectionApplicable: true, + granuleApplicable: false, + granuleIdentifier: null, + providerId: 'MM_2' + }, + collections: { + __typename: 'CollectionList', + count: 2, + items: [ { __typename: 'Collection', conceptId: 'C13000000-MMT_2', @@ -583,7 +825,8 @@ describe('PermissionForm', () => { variables: { conceptId: 'ACL1000000-MMT', params: { - limit: 2000 + offset: 0, + limit: 1 } } }, @@ -618,7 +861,72 @@ describe('PermissionForm', () => { shortName: 'This is collection 2', entryTitle: 'Collection 1', version: '1' + } + ] + }, + groups: { + __typename: 'AclGroupList', + items: [ + { + __typename: 'AclGroup', + permissions: [ + 'read' + ], + userType: 'guest', + id: null, + name: null, + tag: null }, + { + __typename: 'AclGroup', + permissions: [ + 'read' + ], + userType: 'registered', + id: null, + name: null, + tag: null + } + ] + } + } + } + } + }, + { + request: { + query: GET_COLLECTION_FOR_PERMISSION_FORM, + variables: { + conceptId: 'ACL1000000-MMT', + params: { + offset: 1, + limit: 1 + } + } + }, + result: { + data: { + acl: { + __typename: 'Acl', + conceptId: 'ACL1000000-CMR', + identityType: 'Catalog Item', + location: 'https://cmr.sit.earthdata.nasa.gov:443/access-control/acls/ACL1200427411-CMR', + name: 'Mock ACL', + providerIdentity: null, + revisionId: 1, + systemIdentity: null, + catalogItemIdentity: { + __typename: 'CatalogItemIdentity', + collectionIdentifier: {}, + collectionApplicable: true, + granuleApplicable: false, + granuleIdentifier: null, + providerId: 'MM_2' + }, + collections: { + __typename: 'CollectionList', + count: 2, + items: [ { __typename: 'Collection', conceptId: 'C13000000-MMT_2', @@ -662,11 +970,16 @@ describe('PermissionForm', () => { { request: { query: GET_PERMISSION_COLLECTIONS, - variables: {} + variables: { + params: { + limit: 100 + } + } }, result: { data: { collections: { + count: 2, items: [ { conceptId: 'C1200444618-AMD_USAPDC', @@ -726,7 +1039,8 @@ describe('PermissionForm', () => { variables: { conceptId: 'ACL1000000-MMT', params: { - limit: 2000 + offset: 0, + limit: 1 } } }, @@ -761,7 +1075,72 @@ describe('PermissionForm', () => { shortName: 'This is collection 2', entryTitle: 'Collection 1', version: '1' + } + ] + }, + groups: { + __typename: 'AclGroupList', + items: [ + { + __typename: 'AclGroup', + permissions: [ + 'read' + ], + userType: 'guest', + id: null, + name: null, + tag: null }, + { + __typename: 'AclGroup', + permissions: [ + 'read' + ], + userType: 'registered', + id: null, + name: null, + tag: null + } + ] + } + } + } + } + }, + { + request: { + query: GET_COLLECTION_FOR_PERMISSION_FORM, + variables: { + conceptId: 'ACL1000000-MMT', + params: { + offset: 1, + limit: 1 + } + } + }, + result: { + data: { + acl: { + __typename: 'Acl', + conceptId: 'ACL1000000-CMR', + identityType: 'Catalog Item', + location: 'https://cmr.sit.earthdata.nasa.gov:443/access-control/acls/ACL1200427411-CMR', + name: 'Mock ACL', + providerIdentity: null, + revisionId: 1, + systemIdentity: null, + catalogItemIdentity: { + __typename: 'CatalogItemIdentity', + collectionIdentifier: {}, + collectionApplicable: true, + granuleApplicable: false, + granuleIdentifier: null, + providerId: 'MM_2' + }, + collections: { + __typename: 'CollectionList', + count: 2, + items: [ { __typename: 'Collection', conceptId: 'C13000000-MMT_2', @@ -802,6 +1181,7 @@ describe('PermissionForm', () => { } } } + ] }) @@ -811,6 +1191,8 @@ describe('PermissionForm', () => { const submitButton = screen.getByRole('button', { name: 'Submit' }) await user.click(submitButton) + expect(await screen.findByText('Showing selected 2 items')).toBeInTheDocument() + expect(navigateSpy).toHaveBeenCalledTimes(1) expect(navigateSpy).toHaveBeenCalledWith('/permissions/ACL1000000-MMT') }) @@ -830,7 +1212,8 @@ describe('PermissionForm', () => { variables: { conceptId: 'ACL1000000-MMT', params: { - limit: 2000 + offset: 0, + limit: 1 } } }, @@ -911,6 +1294,7 @@ describe('PermissionForm', () => { result: { data: { collections: { + count: 2, items: [ { conceptId: 'C1200444618-AMD_USAPDC', diff --git a/static/src/js/operations/queries/getCollectionForPermissionForm.js b/static/src/js/operations/queries/getCollectionForPermissionForm.js index 8fc814666..d25aaa501 100644 --- a/static/src/js/operations/queries/getCollectionForPermissionForm.js +++ b/static/src/js/operations/queries/getCollectionForPermissionForm.js @@ -20,6 +20,7 @@ export const GET_COLLECTION_FOR_PERMISSION_FORM = gql` } } collections(params: $params) { + count items { conceptId, shortName,