diff --git a/src/app/core/data/collection-data.service.ts b/src/app/core/data/collection-data.service.ts index 5d74591bc24..d77fcf8c367 100644 --- a/src/app/core/data/collection-data.service.ts +++ b/src/app/core/data/collection-data.service.ts @@ -66,7 +66,47 @@ export class CollectionDataService extends ComColDataService { } /** - * Get all collections the user is authorized to submit to + * Get all collections the user is admin of + * + * @param query limit the returned collection to those with metadata values + * matching the query terms. + * @param options The [[FindListOptions]] object + * @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's + * no valid cached version. Defaults to true + * @param reRequestOnStale Whether or not the request should automatically be re- + * requested after the response becomes stale + * @param linksToFollow List of {@link FollowLinkConfig} that indicate which + * {@link HALLink}s should be automatically resolved + * @return Observable>> + * collection list + */ + getAdminAuthorizedCollection(query: string, options: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig[]): Observable>> { + const searchHref = 'findAdminAuthorized'; + return this.getAuthorizedCollection(query, options, useCachedVersionIfAvailable, reRequestOnStale, searchHref, ...linksToFollow); + } + + /** + * Get all collections the user is authorized to edit + * + * @param query limit the returned collection to those with metadata values + * matching the query terms. + * @param options The [[FindListOptions]] object + * @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's + * no valid cached version. Defaults to true + * @param reRequestOnStale Whether or not the request should automatically be re- + * requested after the response becomes stale + * @param linksToFollow List of {@link FollowLinkConfig} that indicate which + * {@link HALLink}s should be automatically resolved + * @return Observable>> + * collection list + */ + getEditAuthorizedCollection(query: string,options: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig[]): Observable>> { + const searchHref = 'findEditAuthorized'; + return this.getAuthorizedCollection(query, options, useCachedVersionIfAvailable, reRequestOnStale, searchHref, ...linksToFollow); + } + + /** + * Get all collections the user is authorized to submit * * @param query limit the returned collection to those with metadata values * matching the query terms. @@ -77,7 +117,27 @@ export class CollectionDataService extends ComColDataService { * requested after the response becomes stale * @param linksToFollow List of {@link FollowLinkConfig} that indicate which * {@link HALLink}s should be automatically resolved + * @return Observable>> + * collection list + */ + getSubmitAuthorizedCollection(query: string,options: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig[]): Observable>> { + const searchHref = 'findSubmitAuthorized'; + return this.getAuthorizedCollection(query, options, useCachedVersionIfAvailable, reRequestOnStale, searchHref, ...linksToFollow); + } + + /** + * Get all collections the user is authorized to perform a specific action on + * + * @param query limit the returned collection to those with metadata values + * matching the query terms. + * @param options The [[FindListOptions]] object + * @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's + * no valid cached version. Defaults to true + * @param reRequestOnStale Whether or not the request should automatically be re- + * requested after the response becomes stale * @param searchHref The backend search endpoint to use (default to submit) + * @param linksToFollow List of {@link FollowLinkConfig} that indicate which + * {@link HALLink}s should be automatically resolved * @return Observable>> * collection list */ diff --git a/src/app/core/data/community-data.service.ts b/src/app/core/data/community-data.service.ts index 2ecba9d2bc1..f5c16225d90 100644 --- a/src/app/core/data/community-data.service.ts +++ b/src/app/core/data/community-data.service.ts @@ -40,6 +40,66 @@ export class CommunityDataService extends ComColDataService { super('communities', requestService, rdbService, objectCache, halService, comparator, notificationsService, bitstreamDataService); } + /** + * Get all communities the user is admin of + * + * @param query limit the returned collection to those with metadata values + * matching the query terms. + * @param options The [[FindListOptions]] object + * @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's + * no valid cached version. Defaults to true + * @param reRequestOnStale Whether or not the request should automatically be re- + * requested after the response becomes stale + * @param linksToFollow List of {@link FollowLinkConfig} that indicate which + * {@link HALLink}s should be automatically resolved + * @return Observable>> + * community list + */ + getAdminAuthorizedCommunity(query: string, options: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig[]): Observable>> { + const searchHref = 'findAdminAuthorized'; + return this.getAuthorizedCommunity(query, options, useCachedVersionIfAvailable, reRequestOnStale, searchHref, ...linksToFollow); + } + + /** + * Get all communities the user is authorized to add a new subcommunity or collection to + * + * @param query limit the returned collection to those with metadata values + * matching the query terms. + * @param options The [[FindListOptions]] object + * @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's + * no valid cached version. Defaults to true + * @param reRequestOnStale Whether or not the request should automatically be re- + * requested after the response becomes stale + * @param linksToFollow List of {@link FollowLinkConfig} that indicate which + * {@link HALLink}s should be automatically resolved + * @return Observable>> + * community list + */ + getAddAuthorizedCommunity(query: string, options: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig[]): Observable>> { + const searchHref = 'findAddAuthorized'; + return this.getAuthorizedCommunity(query, options, useCachedVersionIfAvailable, reRequestOnStale, searchHref, ...linksToFollow); + } + + /** + * Get all communities the user is authorized to edit + * + * @param query limit the returned collection to those with metadata values + * matching the query terms. + * @param options The [[FindListOptions]] object + * @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's + * no valid cached version. Defaults to true + * @param reRequestOnStale Whether or not the request should automatically be re- + * requested after the response becomes stale + * @param linksToFollow List of {@link FollowLinkConfig} that indicate which + * {@link HALLink}s should be automatically resolved + * @return Observable>> + * community list + */ + getEditAuthorizedCommunity(query: string, options: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig[]): Observable>> { + const searchHref = 'findEditAuthorized'; + return this.getAuthorizedCommunity(query, options, useCachedVersionIfAvailable, reRequestOnStale, searchHref, ...linksToFollow); + } + /** * Get all communities the user is authorized to submit to * @@ -50,13 +110,13 @@ export class CommunityDataService extends ComColDataService { * no valid cached version. Defaults to true * @param reRequestOnStale Whether or not the request should automatically be re- * requested after the response becomes stale + * @param searchHref The search endpoint to use, defaults to 'findAdminAuthorized' * @param linksToFollow List of {@link FollowLinkConfig} that indicate which * {@link HALLink}s should be automatically resolved * @return Observable>> * community list */ - getAuthorizedCommunity(query: string, options: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig[]): Observable>> { - const searchHref = 'findAdminAuthorized'; + getAuthorizedCommunity(query: string, options: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, searchHref: string = 'findAdminAuthorized', ...linksToFollow: FollowLinkConfig[]): Observable>> { options = Object.assign({}, options, { searchParams: [new RequestParam('query', query)], }); diff --git a/src/app/core/data/item-data.service.ts b/src/app/core/data/item-data.service.ts index cb7e62c8fe6..f71daaccaf2 100644 --- a/src/app/core/data/item-data.service.ts +++ b/src/app/core/data/item-data.service.ts @@ -34,6 +34,7 @@ import { NotificationsService } from '../notification-system/notifications.servi import { Bundle } from '../shared/bundle.model'; import { Collection } from '../shared/collection.model'; import { ExternalSourceEntry } from '../shared/external-source-entry.model'; +import { FollowLinkConfig } from '../shared/follow-link-config.model'; import { GenericConstructor } from '../shared/generic-constructor'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { Item } from '../shared/item.model'; @@ -58,6 +59,10 @@ import { PatchData, PatchDataImpl, } from './base/patch-data'; +import { + SearchData, + SearchDataImpl, +} from './base/search-data'; import { BundleDataService } from './bundle-data.service'; import { DSOChangeAnalyzer } from './dso-change-analyzer.service'; import { FindListOptions } from './find-list-options.model'; @@ -81,6 +86,7 @@ import { StatusCodeOnlyResponseParsingService } from './status-code-only-respons */ export abstract class BaseItemDataService extends IdentifiableDataService implements CreateData, PatchData, DeleteData { private createData: CreateData; + private searchData: SearchData; private patchData: PatchData; private deleteData: DeleteData; @@ -99,6 +105,7 @@ export abstract class BaseItemDataService extends IdentifiableDataService super(linkPath, requestService, rdbService, objectCache, halService, undefined, constructIdEndpoint); this.createData = new CreateDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, notificationsService, this.responseMsToLive); + this.searchData = new SearchDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, this.responseMsToLive); this.patchData = new PatchDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, comparator, this.responseMsToLive, this.constructIdEndpoint); this.deleteData = new DeleteDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, notificationsService, this.responseMsToLive, this.constructIdEndpoint); } @@ -348,6 +355,26 @@ export abstract class BaseItemDataService extends IdentifiableDataService ); } + /** + * Find the list of items for which the current user has editing rights. + * + * @param query limit the returned collection to those with metadata values + * matching the query terms + * @param options The [[FindListOptions]] object + * @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's + * no valid cached version. Defaults to true + * @param reRequestOnStale Whether or not the request should automatically be re- + * requested after the response becomes stale + * @param linksToFollow List of {@link FollowLinkConfig} that indicate which + * {@link HALLink}s should be automatically resolved + * @return Observable>> + * item list + */ + public findEditAuthorized(query: string, options: FindListOptions, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig[]): Observable>> { + options = { ...options, searchParams: [new RequestParam('query', query)] }; + return this.searchBy('findEditAuthorized', options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow); + } + /** * Invalidate the cache of the item * @param itemUUID @@ -364,6 +391,24 @@ export abstract class BaseItemDataService extends IdentifiableDataService this.patchData.commitUpdates(method); } + /** + * Make a new FindListRequest with given search method + * + * @param searchMethod The search method for the object + * @param options The [[FindListOptions]] object + * @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's + * no valid cached version. Defaults to true + * @param reRequestOnStale Whether or not the request should automatically be re- + * requested after the response becomes stale + * @param linksToFollow List of {@link FollowLinkConfig} that indicate which + * {@link HALLink}s should be automatically resolved + * @return {Observable>} + * Return an observable that emits response from the server + */ + public searchBy(searchMethod: string, options?: FindListOptions, useCachedVersionIfAvailable?: boolean, reRequestOnStale?: boolean, ...linksToFollow: FollowLinkConfig[]): Observable>> { + return this.searchData.searchBy(searchMethod, options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow); + } + /** * Send a patch request for a specified object * @param {T} object The object to send a patch request for diff --git a/src/app/shared/dso-selector/dso-selector/authorized-collection-selector/authorized-collection-selector.component.spec.ts b/src/app/shared/dso-selector/dso-selector/authorized-collection-selector/authorized-collection-selector.component.spec.ts index 3f834120c57..a98af16a8e7 100644 --- a/src/app/shared/dso-selector/dso-selector/authorized-collection-selector/authorized-collection-selector.component.spec.ts +++ b/src/app/shared/dso-selector/dso-selector/authorized-collection-selector/authorized-collection-selector.component.spec.ts @@ -7,6 +7,7 @@ import { import { RouterTestingModule } from '@angular/router/testing'; import { CollectionDataService } from '@dspace/core/data/collection-data.service'; import { NotificationsService } from '@dspace/core/notification-system/notifications.service'; +import { ActionType } from '@dspace/core/resource-policy/models/action-type.model'; import { Collection } from '@dspace/core/shared/collection.model'; import { DSpaceObjectType } from '@dspace/core/shared/dspace-object-type.model'; import { createPaginatedList } from '@dspace/core/testing/utils.test'; @@ -33,7 +34,9 @@ describe('AuthorizedCollectionSelectorComponent', () => { id: 'authorized-collection', }); collectionService = jasmine.createSpyObj('collectionService', { - getAuthorizedCollection: createSuccessfulRemoteDataObject$(createPaginatedList([collection])), + getSubmitAuthorizedCollection: createSuccessfulRemoteDataObject$(createPaginatedList([collection])), + getAdminAuthorizedCollection: createSuccessfulRemoteDataObject$(createPaginatedList([collection])), + getEditAuthorizedCollection: createSuccessfulRemoteDataObject$(createPaginatedList([collection])), getAuthorizedCollectionByEntityType: createSuccessfulRemoteDataObject$(createPaginatedList([collection])), }); notificationsService = jasmine.createSpyObj('notificationsService', ['error']); @@ -60,23 +63,41 @@ describe('AuthorizedCollectionSelectorComponent', () => { }); describe('search', () => { - describe('when has no entity type', () => { - it('should call getAuthorizedCollection and return the authorized collection in a SearchResult', (done) => { - component.search('', 1).subscribe((resultRD) => { - expect(collectionService.getAuthorizedCollection).toHaveBeenCalled(); - expect(resultRD.payload.page.length).toEqual(1); - expect(resultRD.payload.page[0].indexableObject).toEqual(collection); - done(); + describe('when action type is ADD', () => { + describe('when has no entity type', () => { + it('should call getSubmitAuthorizedCollection and return the authorized collection in a SearchResult', (done) => { + component.action = ActionType.ADD; + fixture.detectChanges(); + component.search('', 1).subscribe((resultRD) => { + expect(collectionService.getSubmitAuthorizedCollection).toHaveBeenCalled(); + expect(resultRD.payload.page.length).toEqual(1); + expect(resultRD.payload.page[0].indexableObject).toEqual(collection); + done(); + }); + }); + }); + + describe('when has entity type', () => { + it('should call getAuthorizedCollectionByEntityType and return the authorized collection in a SearchResult', (done) => { + component.entityType = 'test'; + component.action = ActionType.ADD; + fixture.detectChanges(); + component.search('', 1).subscribe((resultRD) => { + expect(collectionService.getAuthorizedCollectionByEntityType).toHaveBeenCalled(); + expect(resultRD.payload.page.length).toEqual(1); + expect(resultRD.payload.page[0].indexableObject).toEqual(collection); + done(); + }); }); }); }); - describe('when has entity type', () => { - it('should call getAuthorizedCollectionByEntityType and return the authorized collection in a SearchResult', (done) => { - component.entityType = 'test'; + describe('when action type is WRITE', () => { + it('should call getEditAuthorizedCollection', (done) => { + component.action = ActionType.WRITE; fixture.detectChanges(); component.search('', 1).subscribe((resultRD) => { - expect(collectionService.getAuthorizedCollectionByEntityType).toHaveBeenCalled(); + expect(collectionService.getEditAuthorizedCollection).toHaveBeenCalled(); expect(resultRD.payload.page.length).toEqual(1); expect(resultRD.payload.page[0].indexableObject).toEqual(collection); done(); @@ -84,18 +105,15 @@ describe('AuthorizedCollectionSelectorComponent', () => { }); }); - describe('when using searchHref', () => { - it('should call getAuthorizedCollection with "findAdminAuthorized" when overridden', (done) => { - component.searchHref = 'findAdminAuthorized'; - - component.search('', 1).subscribe(() => { - expect(collectionService.getAuthorizedCollection).toHaveBeenCalledWith( - '', jasmine.any(Object), true, false, 'findAdminAuthorized', jasmine.anything(), - ); + describe('when action is not provided', () => { + it('should call getAdminAuthorizedCollection', (done) => { + component.search('', 1).subscribe((resultRD) => { + expect(collectionService.getAdminAuthorizedCollection).toHaveBeenCalled(); + expect(resultRD.payload.page.length).toEqual(1); + expect(resultRD.payload.page[0].indexableObject).toEqual(collection); done(); }); }); }); - }); }); diff --git a/src/app/shared/dso-selector/dso-selector/authorized-collection-selector/authorized-collection-selector.component.ts b/src/app/shared/dso-selector/dso-selector/authorized-collection-selector/authorized-collection-selector.component.ts index 76a77f0c7cf..17afaf36e1a 100644 --- a/src/app/shared/dso-selector/dso-selector/authorized-collection-selector/authorized-collection-selector.component.ts +++ b/src/app/shared/dso-selector/dso-selector/authorized-collection-selector/authorized-collection-selector.component.ts @@ -19,6 +19,7 @@ import { } from '@dspace/core/data/paginated-list.model'; import { RemoteData } from '@dspace/core/data/remote-data'; import { NotificationsService } from '@dspace/core/notification-system/notifications.service'; +import { ActionType } from '@dspace/core/resource-policy/models/action-type.model'; import { Collection } from '@dspace/core/shared/collection.model'; import { DSpaceObject } from '@dspace/core/shared/dspace-object.model'; import { followLink } from '@dspace/core/shared/follow-link-config.model'; @@ -66,10 +67,9 @@ export class AuthorizedCollectionSelectorComponent extends DSOSelectorComponent @Input() entityType: string; /** - * Search endpoint to use for finding authorized collections. - * Defaults to 'findSubmitAuthorized', but can be overridden (e.g. to 'findAdminAuthorized') + * The action type to determine which authorized collections to fetch, defaults to ADMIN */ - @Input() searchHref = 'findSubmitAuthorized'; + @Input() action: ActionType = ActionType.ADMIN; constructor( protected searchService: SearchService, @@ -101,15 +101,24 @@ export class AuthorizedCollectionSelectorComponent extends DSOSelectorComponent elementsPerPage: this.defaultPagination.pageSize, }; - if (this.entityType) { + if (this.action === ActionType.WRITE) { searchListService$ = this.collectionDataService - .getAuthorizedCollectionByEntityType( - query, - this.entityType, - findOptions); + .getEditAuthorizedCollection(query, findOptions, useCache, false, followLink('parentCommunity')); + } else if (this.action === ActionType.ADD) { + if (this.entityType) { + searchListService$ = this.collectionDataService + .getAuthorizedCollectionByEntityType( + query, + this.entityType, + findOptions); + } else { + searchListService$ = this.collectionDataService + .getSubmitAuthorizedCollection(query, findOptions, useCache, false, followLink('parentCommunity')); + } } else { + // By default, search for admin authorized collections searchListService$ = this.collectionDataService - .getAuthorizedCollection(query, findOptions, useCache, false, this.searchHref, followLink('parentCommunity')); + .getAdminAuthorizedCollection(query, findOptions, useCache, false, followLink('parentCommunity')); } return searchListService$.pipe( getFirstCompletedRemoteData(), diff --git a/src/app/shared/dso-selector/dso-selector/authorized-community-selector/authorized-community-selector.component.spec.ts b/src/app/shared/dso-selector/dso-selector/authorized-community-selector/authorized-community-selector.component.spec.ts index ff8125f556e..a5e21d419fa 100644 --- a/src/app/shared/dso-selector/dso-selector/authorized-community-selector/authorized-community-selector.component.spec.ts +++ b/src/app/shared/dso-selector/dso-selector/authorized-community-selector/authorized-community-selector.component.spec.ts @@ -6,6 +6,7 @@ import { } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; import { NotificationsService } from '@dspace/core/notification-system/notifications.service'; +import { ActionType } from '@dspace/core/resource-policy/models/action-type.model'; import { createPaginatedList } from '@dspace/core/testing/utils.test'; import { createSuccessfulRemoteDataObject$ } from '@dspace/core/utilities/remote-data.utils'; import { TranslateModule } from '@ngx-translate/core'; @@ -33,7 +34,9 @@ describe('AuthorizedCommunitySelectorComponent', () => { id: 'authorized-community', }); communityService = jasmine.createSpyObj('communityService', { - getAuthorizedCommunity: createSuccessfulRemoteDataObject$(createPaginatedList([community])), + getAddAuthorizedCommunity: createSuccessfulRemoteDataObject$(createPaginatedList([community])), + getEditAuthorizedCommunity: createSuccessfulRemoteDataObject$(createPaginatedList([community])), + getAdminAuthorizedCommunity: createSuccessfulRemoteDataObject$(createPaginatedList([community])), }); notificationsService = jasmine.createSpyObj('notificationsService', ['error']); TestBed.configureTestingModule({ @@ -59,12 +62,38 @@ describe('AuthorizedCommunitySelectorComponent', () => { }); describe('search', () => { - it('should call getAuthorizedCommunity and return the authorized community in a SearchResult', (done) => { - component.search('', 1).subscribe((resultRD) => { - expect(communityService.getAuthorizedCommunity).toHaveBeenCalled(); - expect(resultRD.payload.page.length).toEqual(1); - expect(resultRD.payload.page[0].indexableObject).toEqual(community); - done(); + describe('when action type is ADD', () => { + it('should call getAddAuthorizedCommunity and return the authorized community in a SearchResult', (done) => { + component.action = ActionType.ADD; + fixture.detectChanges(); + component.search('', 1).subscribe((resultRD) => { + expect(communityService.getAddAuthorizedCommunity).toHaveBeenCalled(); + expect(resultRD.payload.page.length).toEqual(1); + expect(resultRD.payload.page[0].indexableObject).toEqual(community); + done(); + }); + }); + }); + describe('when action type is WRITE', () => { + it('should call getEditAuthorizedCommunity and return the authorized community in a SearchResult', (done) => { + component.action = ActionType.WRITE; + fixture.detectChanges(); + component.search('', 1).subscribe((resultRD) => { + expect(communityService.getEditAuthorizedCommunity).toHaveBeenCalled(); + expect(resultRD.payload.page.length).toEqual(1); + expect(resultRD.payload.page[0].indexableObject).toEqual(community); + done(); + }); + }); + }); + describe('when action type is not provided', () => { + it('should call getAdminAuthorizedCommunity and return the authorized community in a SearchResult', (done) => { + component.search('', 1).subscribe((resultRD) => { + expect(communityService.getAdminAuthorizedCommunity).toHaveBeenCalled(); + expect(resultRD.payload.page.length).toEqual(1); + expect(resultRD.payload.page[0].indexableObject).toEqual(community); + done(); + }); }); }); }); diff --git a/src/app/shared/dso-selector/dso-selector/authorized-community-selector/authorized-community-selector.component.ts b/src/app/shared/dso-selector/dso-selector/authorized-community-selector/authorized-community-selector.component.ts index 551c5090f38..33fb0d9648f 100644 --- a/src/app/shared/dso-selector/dso-selector/authorized-community-selector/authorized-community-selector.component.ts +++ b/src/app/shared/dso-selector/dso-selector/authorized-community-selector/authorized-community-selector.component.ts @@ -2,12 +2,16 @@ import { AsyncPipe, NgClass, } from '@angular/common'; -import { Component } from '@angular/core'; +import { + Component, + Input, +} from '@angular/core'; import { FormsModule, ReactiveFormsModule, } from '@angular/forms'; import { NotificationsService } from '@dspace/core/notification-system/notifications.service'; +import { ActionType } from '@dspace/core/resource-policy/models/action-type.model'; import { followLink } from '@dspace/core/shared/follow-link-config.model'; import { CommunitySearchResult } from '@dspace/core/shared/object-collection/community-search-result.model'; import { SearchResult } from '@dspace/core/shared/search/models/search-result.model'; @@ -57,9 +61,11 @@ import { DSOSelectorComponent } from '../dso-selector.component'; * Component rendering a list of communities to select from */ export class AuthorizedCommunitySelectorComponent extends DSOSelectorComponent { + /** - * If present this value is used to filter community list by entity type + * The action type to determine which authorized communities to fetch */ + @Input() action: ActionType = ActionType.ADMIN; constructor( protected searchService: SearchService, @@ -91,9 +97,17 @@ export class AuthorizedCommunitySelectorComponent extends DSOSelectorComponent { elementsPerPage: this.defaultPagination.pageSize, }; - searchListService$ = this.communityDataService - .getAuthorizedCommunity(query, findOptions, useCache, false, followLink('parentCommunity')); - + if (this.action === ActionType.WRITE) { + searchListService$ = this.communityDataService + .getEditAuthorizedCommunity(query, findOptions, useCache, false, followLink('parentCommunity')); + } else if (this.action === ActionType.ADD) { + searchListService$ = this.communityDataService + .getAddAuthorizedCommunity(query, findOptions, useCache, false, followLink('parentCommunity')); + } else { + // By default, search for admin authorized communities + searchListService$ = this.communityDataService + .getAdminAuthorizedCommunity(query, findOptions, useCache, false, followLink('parentCommunity')); + } return searchListService$.pipe( getFirstCompletedRemoteData(), map((rd) => Object.assign(new RemoteData(null, null, null, null), rd, { diff --git a/src/app/shared/dso-selector/dso-selector/authorized-item-selector/authorized-item-selector.component.spec.ts b/src/app/shared/dso-selector/dso-selector/authorized-item-selector/authorized-item-selector.component.spec.ts new file mode 100644 index 00000000000..e75fb595d5e --- /dev/null +++ b/src/app/shared/dso-selector/dso-selector/authorized-item-selector/authorized-item-selector.component.spec.ts @@ -0,0 +1,71 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { + ComponentFixture, + TestBed, + waitForAsync, +} from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NotificationsService } from '@dspace/core/notification-system/notifications.service'; +import { createPaginatedList } from '@dspace/core/testing/utils.test'; +import { createSuccessfulRemoteDataObject$ } from '@dspace/core/utilities/remote-data.utils'; +import { TranslateModule } from '@ngx-translate/core'; +import { ItemDataService } from 'src/app/core/data/item-data.service'; +import { Item } from 'src/app/core/shared/item.model'; +import { SearchService } from 'src/app/shared/search/search.service'; + +import { DSpaceObjectType } from '../../../../core/shared/dspace-object-type.model'; +import { ThemedLoadingComponent } from '../../../loading/themed-loading.component'; +import { ListableObjectComponentLoaderComponent } from '../../../object-collection/shared/listable-object/listable-object-component-loader.component'; +import { VarDirective } from '../../../utils/var.directive'; +import { AuthorizedItemSelectorComponent } from './authorized-item-selector.component'; + +describe('AuthorizedItemSelectorComponent', () => { + let component: AuthorizedItemSelectorComponent; + let fixture: ComponentFixture; + + let itemService; + let item; + + let notificationsService: NotificationsService; + + beforeEach(waitForAsync(() => { + item = Object.assign(new Item(), { + id: 'authorized-item', + }); + itemService = jasmine.createSpyObj('itemService', { + findEditAuthorized: createSuccessfulRemoteDataObject$(createPaginatedList([item])), + }); + notificationsService = jasmine.createSpyObj('notificationsService', ['error']); + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([]), AuthorizedItemSelectorComponent, VarDirective], + providers: [ + { provide: SearchService, useValue: {} }, + { provide: ItemDataService, useValue: itemService }, + { provide: NotificationsService, useValue: notificationsService }, + ], + schemas: [NO_ERRORS_SCHEMA], + }) + .overrideComponent(AuthorizedItemSelectorComponent, { + remove: { imports: [ListableObjectComponentLoaderComponent, ThemedLoadingComponent] }, + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AuthorizedItemSelectorComponent); + component = fixture.componentInstance; + component.types = [DSpaceObjectType.ITEM]; + fixture.detectChanges(); + }); + + describe('search', () => { + it('should call findEditAuthorized and return the authorized item in a SearchResult', (done) => { + component.search('', 1).subscribe((resultRD) => { + expect(itemService.findEditAuthorized).toHaveBeenCalled(); + expect(resultRD.payload.page.length).toEqual(1); + expect(resultRD.payload.page[0].indexableObject).toEqual(item); + done(); + }); + }); + }); +}); diff --git a/src/app/shared/dso-selector/dso-selector/authorized-item-selector/authorized-item-selector.component.ts b/src/app/shared/dso-selector/dso-selector/authorized-item-selector/authorized-item-selector.component.ts new file mode 100644 index 00000000000..87513e3f216 --- /dev/null +++ b/src/app/shared/dso-selector/dso-selector/authorized-item-selector/authorized-item-selector.component.ts @@ -0,0 +1,108 @@ +import { + AsyncPipe, + NgClass, +} from '@angular/common'; +import { + Component, + Input, +} from '@angular/core'; +import { + FormsModule, + ReactiveFormsModule, +} from '@angular/forms'; +import { NotificationsService } from '@dspace/core/notification-system/notifications.service'; +import { followLink } from '@dspace/core/shared/follow-link-config.model'; +import { ItemSearchResult } from '@dspace/core/shared/object-collection/item-search-result.model'; +import { SearchResult } from '@dspace/core/shared/search/models/search-result.model'; +import { hasValue } from '@dspace/shared/utils/empty.util'; +import { + TranslateModule, + TranslateService, +} from '@ngx-translate/core'; +import { InfiniteScrollModule } from 'ngx-infinite-scroll'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { ItemDataService } from 'src/app/core/data/item-data.service'; +import { Item } from 'src/app/core/shared/item.model'; +import { SearchService } from 'src/app/shared/search/search.service'; + +import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service'; +import { FindListOptions } from '../../../../core/data/find-list-options.model'; +import { + buildPaginatedList, + PaginatedList, +} from '../../../../core/data/paginated-list.model'; +import { RemoteData } from '../../../../core/data/remote-data'; +import { DSpaceObject } from '../../../../core/shared/dspace-object.model'; +import { getFirstCompletedRemoteData } from '../../../../core/shared/operators'; +import { HoverClassDirective } from '../../../hover-class.directive'; +import { ThemedLoadingComponent } from '../../../loading/themed-loading.component'; +import { ListableObjectComponentLoaderComponent } from '../../../object-collection/shared/listable-object/listable-object-component-loader.component'; +import { SelectorActionType } from '../../modal-wrappers/dso-selector-modal-wrapper.component'; +import { DSOSelectorComponent } from '../dso-selector.component'; + +@Component({ + selector: 'ds-authorized-item-selector', + styleUrls: ['../dso-selector.component.scss'], + templateUrl: '../dso-selector.component.html', + imports: [ + AsyncPipe, + FormsModule, + HoverClassDirective, + InfiniteScrollModule, + ListableObjectComponentLoaderComponent, + NgClass, + ReactiveFormsModule, + ThemedLoadingComponent, + TranslateModule, + ], +}) +/** + * Component rendering a list of item to select from for editing + */ +export class AuthorizedItemSelectorComponent extends DSOSelectorComponent { + + constructor( + protected searchService: SearchService, + protected itemDataService: ItemDataService, + protected notifcationsService: NotificationsService, + protected translate: TranslateService, + protected dsoNameService: DSONameService, + ) { + super(searchService, notifcationsService, translate, dsoNameService); + } + + @Input() action: SelectorActionType = SelectorActionType.EDIT; + + /** + * Get a query to send for retrieving the current DSO + */ + getCurrentDSOQuery(): string { + return this.currentDSOId; + } + + /** + * Perform a search for authorized collections with the current query and page + * @param query Query to search objects for + * @param page Page to retrieve + * @param useCache Whether or not to use the cache + */ + search(query: string, page: number, useCache: boolean = true): Observable>>> { + let searchListService$: Observable>> = null; + const findOptions: FindListOptions = { + currentPage: page, + elementsPerPage: this.defaultPagination.pageSize, + }; + + // By default, search for edit authorized items + searchListService$ = this.itemDataService + .findEditAuthorized(query, findOptions, useCache, false, followLink('owningCollection')); + + return searchListService$.pipe( + getFirstCompletedRemoteData(), + map((rd) => Object.assign(new RemoteData(null, null, null, null), rd, { + payload: hasValue(rd.payload) ? buildPaginatedList(rd.payload.pageInfo, rd.payload.page.map((item) => Object.assign(new ItemSearchResult(), { indexableObject: item }))) : null, + })), + ); + } +} diff --git a/src/app/shared/dso-selector/modal-wrappers/create-collection-parent-selector/create-collection-parent-selector.component.html b/src/app/shared/dso-selector/modal-wrappers/create-collection-parent-selector/create-collection-parent-selector.component.html index 1c3a12996eb..c77a741c7ae 100644 --- a/src/app/shared/dso-selector/modal-wrappers/create-collection-parent-selector/create-collection-parent-selector.component.html +++ b/src/app/shared/dso-selector/modal-wrappers/create-collection-parent-selector/create-collection-parent-selector.component.html @@ -9,6 +9,7 @@ } diff --git a/src/app/shared/dso-selector/modal-wrappers/create-collection-parent-selector/create-collection-parent-selector.component.ts b/src/app/shared/dso-selector/modal-wrappers/create-collection-parent-selector/create-collection-parent-selector.component.ts index b43f7011039..f17bef6df6f 100644 --- a/src/app/shared/dso-selector/modal-wrappers/create-collection-parent-selector/create-collection-parent-selector.component.ts +++ b/src/app/shared/dso-selector/modal-wrappers/create-collection-parent-selector/create-collection-parent-selector.component.ts @@ -12,6 +12,7 @@ import { SortDirection, SortOptions, } from '@dspace/core/cache/models/sort-options.model'; +import { ActionType } from '@dspace/core/resource-policy/models/action-type.model'; import { DSpaceObject } from '@dspace/core/shared/dspace-object.model'; import { DSpaceObjectType } from '@dspace/core/shared/dspace-object-type.model'; import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; @@ -44,6 +45,7 @@ export class CreateCollectionParentSelectorComponent extends DSOSelectorModalWra objectType = DSpaceObjectType.COLLECTION; selectorTypes = [DSpaceObjectType.COMMUNITY]; action = SelectorActionType.CREATE; + rpActionType = ActionType.ADD; header = 'dso-selector.create.collection.sub-level'; defaultSort = new SortOptions(environment.comcolSelectionSort.sortField, environment.comcolSelectionSort.sortDirection as SortDirection); diff --git a/src/app/shared/dso-selector/modal-wrappers/create-community-parent-selector/create-community-parent-selector.component.html b/src/app/shared/dso-selector/modal-wrappers/create-community-parent-selector/create-community-parent-selector.component.html index b465c24e407..b135aaf7678 100644 --- a/src/app/shared/dso-selector/modal-wrappers/create-community-parent-selector/create-community-parent-selector.component.html +++ b/src/app/shared/dso-selector/modal-wrappers/create-community-parent-selector/create-community-parent-selector.component.html @@ -18,6 +18,7 @@ {{'dso-selector.create.community.sub-level' | translate}} diff --git a/src/app/shared/dso-selector/modal-wrappers/create-community-parent-selector/create-community-parent-selector.component.ts b/src/app/shared/dso-selector/modal-wrappers/create-community-parent-selector/create-community-parent-selector.component.ts index 55d73d5fe21..992a64b3af3 100644 --- a/src/app/shared/dso-selector/modal-wrappers/create-community-parent-selector/create-community-parent-selector.component.ts +++ b/src/app/shared/dso-selector/modal-wrappers/create-community-parent-selector/create-community-parent-selector.component.ts @@ -14,6 +14,7 @@ import { } from '@dspace/core/cache/models/sort-options.model'; import { AuthorizationDataService } from '@dspace/core/data/feature-authorization/authorization-data.service'; import { FeatureID } from '@dspace/core/data/feature-authorization/feature-id'; +import { ActionType } from '@dspace/core/resource-policy/models/action-type.model'; import { DSpaceObject } from '@dspace/core/shared/dspace-object.model'; import { DSpaceObjectType } from '@dspace/core/shared/dspace-object-type.model'; import { hasValue } from '@dspace/shared/utils/empty.util'; @@ -53,6 +54,7 @@ export class CreateCommunityParentSelectorComponent extends DSOSelectorModalWrap objectType = DSpaceObjectType.COMMUNITY; selectorTypes = [DSpaceObjectType.COMMUNITY]; action = SelectorActionType.CREATE; + rpActionType = ActionType.ADD; defaultSort = new SortOptions(environment.comcolSelectionSort.sortField, environment.comcolSelectionSort.sortDirection as SortDirection); isAdmin$: Observable; diff --git a/src/app/shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component.html b/src/app/shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component.html index 30cc1d10702..60b3514c14c 100644 --- a/src/app/shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component.html +++ b/src/app/shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component.html @@ -10,6 +10,7 @@ diff --git a/src/app/shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component.ts b/src/app/shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component.ts index 5732e0ad3da..14ebb2309a4 100644 --- a/src/app/shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component.ts +++ b/src/app/shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component.ts @@ -13,6 +13,7 @@ import { SortDirection, SortOptions, } from '@dspace/core/cache/models/sort-options.model'; +import { ActionType } from '@dspace/core/resource-policy/models/action-type.model'; import { DSpaceObject } from '@dspace/core/shared/dspace-object.model'; import { DSpaceObjectType } from '@dspace/core/shared/dspace-object-type.model'; import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; @@ -44,6 +45,7 @@ export class CreateItemParentSelectorComponent extends DSOSelectorModalWrapperCo objectType = DSpaceObjectType.ITEM; selectorTypes = [DSpaceObjectType.COLLECTION]; action = SelectorActionType.CREATE; + rpActionType = ActionType.ADD; header = 'dso-selector.create.item.sub-level'; defaultSort = new SortOptions(environment.comcolSelectionSort.sortField, environment.comcolSelectionSort.sortDirection as SortDirection); diff --git a/src/app/shared/dso-selector/modal-wrappers/edit-collection-selector/edit-collection-selector.component.html b/src/app/shared/dso-selector/modal-wrappers/edit-collection-selector/edit-collection-selector.component.html index ceadcf2bb3f..df9c923a626 100644 --- a/src/app/shared/dso-selector/modal-wrappers/edit-collection-selector/edit-collection-selector.component.html +++ b/src/app/shared/dso-selector/modal-wrappers/edit-collection-selector/edit-collection-selector.component.html @@ -9,7 +9,7 @@ } diff --git a/src/app/shared/dso-selector/modal-wrappers/edit-collection-selector/edit-collection-selector.component.ts b/src/app/shared/dso-selector/modal-wrappers/edit-collection-selector/edit-collection-selector.component.ts index 9cb9cc2dd04..29a4f8082be 100644 --- a/src/app/shared/dso-selector/modal-wrappers/edit-collection-selector/edit-collection-selector.component.ts +++ b/src/app/shared/dso-selector/modal-wrappers/edit-collection-selector/edit-collection-selector.component.ts @@ -10,6 +10,7 @@ import { SortDirection, SortOptions, } from '@dspace/core/cache/models/sort-options.model'; +import { ActionType } from '@dspace/core/resource-policy/models/action-type.model'; import { DSpaceObject } from '@dspace/core/shared/dspace-object.model'; import { DSpaceObjectType } from '@dspace/core/shared/dspace-object-type.model'; import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; @@ -40,6 +41,7 @@ export class EditCollectionSelectorComponent extends DSOSelectorModalWrapperComp objectType = DSpaceObjectType.COLLECTION; selectorTypes = [DSpaceObjectType.COLLECTION]; action = SelectorActionType.EDIT; + rpActionType = ActionType.ADD; defaultSort = new SortOptions(environment.comcolSelectionSort.sortField, environment.comcolSelectionSort.sortDirection as SortDirection); constructor(protected activeModal: NgbActiveModal, protected route: ActivatedRoute, private router: Router) { diff --git a/src/app/shared/dso-selector/modal-wrappers/edit-community-selector/edit-community-selector.component.html b/src/app/shared/dso-selector/modal-wrappers/edit-community-selector/edit-community-selector.component.html index afdf67fc4b5..8a7ae38e75e 100644 --- a/src/app/shared/dso-selector/modal-wrappers/edit-community-selector/edit-community-selector.component.html +++ b/src/app/shared/dso-selector/modal-wrappers/edit-community-selector/edit-community-selector.component.html @@ -9,6 +9,7 @@ } diff --git a/src/app/shared/dso-selector/modal-wrappers/edit-community-selector/edit-community-selector.component.ts b/src/app/shared/dso-selector/modal-wrappers/edit-community-selector/edit-community-selector.component.ts index 944995d8c83..64e47d54022 100644 --- a/src/app/shared/dso-selector/modal-wrappers/edit-community-selector/edit-community-selector.component.ts +++ b/src/app/shared/dso-selector/modal-wrappers/edit-community-selector/edit-community-selector.component.ts @@ -10,6 +10,7 @@ import { SortDirection, SortOptions, } from '@dspace/core/cache/models/sort-options.model'; +import { ActionType } from '@dspace/core/resource-policy/models/action-type.model'; import { DSpaceObject } from '@dspace/core/shared/dspace-object.model'; import { DSpaceObjectType } from '@dspace/core/shared/dspace-object-type.model'; import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; @@ -41,6 +42,7 @@ export class EditCommunitySelectorComponent extends DSOSelectorModalWrapperCompo objectType = DSpaceObjectType.COMMUNITY; selectorTypes = [DSpaceObjectType.COMMUNITY]; action = SelectorActionType.EDIT; + rpActionType = ActionType.ADD; defaultSort = new SortOptions(environment.comcolSelectionSort.sortField, environment.comcolSelectionSort.sortDirection as SortDirection); constructor(protected activeModal: NgbActiveModal, protected route: ActivatedRoute, private router: Router) { diff --git a/src/app/shared/dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component.html b/src/app/shared/dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component.html index 210f6a6d209..9d7ce82ce51 100644 --- a/src/app/shared/dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component.html +++ b/src/app/shared/dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component.html @@ -7,6 +7,9 @@ @if (header) { {{header | translate}} } - + diff --git a/src/app/shared/dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component.spec.ts b/src/app/shared/dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component.spec.ts index 215c7738d9b..4bb40cf65da 100644 --- a/src/app/shared/dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component.spec.ts +++ b/src/app/shared/dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component.spec.ts @@ -18,7 +18,7 @@ import { createSuccessfulRemoteDataObject } from '@dspace/core/utilities/remote- import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; import { TranslateModule } from '@ngx-translate/core'; -import { DSOSelectorComponent } from '../../dso-selector/dso-selector.component'; +import { AuthorizedItemSelectorComponent } from '../../dso-selector/authorized-item-selector/authorized-item-selector.component'; import { EditItemSelectorComponent } from './edit-item-selector.component'; describe('EditItemSelectorComponent', () => { @@ -67,7 +67,7 @@ describe('EditItemSelectorComponent', () => { }) .overrideComponent(EditItemSelectorComponent, { remove: { - imports: [DSOSelectorComponent], + imports: [AuthorizedItemSelectorComponent], }, }) .compileComponents(); diff --git a/src/app/shared/dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component.ts b/src/app/shared/dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component.ts index bd064bfe1c6..44675e9a06b 100644 --- a/src/app/shared/dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component.ts +++ b/src/app/shared/dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component.ts @@ -7,6 +7,7 @@ import { ActivatedRoute, Router, } from '@angular/router'; +import { ActionType } from '@dspace/core/resource-policy/models/action-type.model'; import { DSpaceObject } from '@dspace/core/shared/dspace-object.model'; import { DSpaceObjectType } from '@dspace/core/shared/dspace-object-type.model'; import { Item } from '@dspace/core/shared/item.model'; @@ -14,7 +15,7 @@ import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; import { TranslateModule } from '@ngx-translate/core'; import { getItemEditRoute } from '../../../../item-page/item-page-routing-paths'; -import { DSOSelectorComponent } from '../../dso-selector/dso-selector.component'; +import { AuthorizedItemSelectorComponent } from '../../dso-selector/authorized-item-selector/authorized-item-selector.component'; import { DSOSelectorModalWrapperComponent, SelectorActionType, @@ -29,7 +30,7 @@ import { selector: 'ds-base-edit-item-selector', templateUrl: 'edit-item-selector.component.html', imports: [ - DSOSelectorComponent, + AuthorizedItemSelectorComponent, TranslateModule, ], }) @@ -37,6 +38,7 @@ export class EditItemSelectorComponent extends DSOSelectorModalWrapperComponent objectType = DSpaceObjectType.ITEM; selectorTypes = [DSpaceObjectType.ITEM]; action = SelectorActionType.EDIT; + rpActionType = ActionType.WRITE; constructor(protected activeModal: NgbActiveModal, protected route: ActivatedRoute, private router: Router) { super(activeModal, route);