Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/app/core/data/collection-data.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,11 @@ export class CollectionDataService extends ComColDataService<Collection> {
* requested after the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
* {@link HALLink}s should be automatically resolved
* @param searchHref The backend search endpoint to use (default to submit)
* @return Observable<RemoteData<PaginatedList<Collection>>>
* collection list
*/
getAuthorizedCollection(query: string, options: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<Collection>[]): Observable<RemoteData<PaginatedList<Collection>>> {
const searchHref = 'findSubmitAuthorized';
getAuthorizedCollection(query: string, options: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, searchHref: string = 'findSubmitAuthorized', ...linksToFollow: FollowLinkConfig<Collection>[]): Observable<RemoteData<PaginatedList<Collection>>> {
options = Object.assign({}, options, {
searchParams: [new RequestParam('query', query)],
});
Expand Down
28 changes: 28 additions & 0 deletions src/app/core/data/community-data.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ import { isNotEmpty } from '../../shared/empty.util';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { RequestParam } from '../cache/models/request-param.model';
import { ObjectCacheService } from '../cache/object-cache.service';
import { Community } from '../shared/community.model';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import { getAllCompletedRemoteData } from '../shared/operators';
import { BitstreamDataService } from './bitstream-data.service';
import { ComColDataService } from './comcol-data.service';
import { DSOChangeAnalyzer } from './dso-change-analyzer.service';
Expand All @@ -38,6 +40,32 @@ export class CommunityDataService extends ComColDataService<Community> {
super('communities', requestService, rdbService, objectCache, halService, comparator, notificationsService, bitstreamDataService);
}

/**
* Get all communities the user is authorized to submit to
*
* @param query limit the returned community 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<RemoteData<PaginatedList<Community>>>
* community list
*/
getAuthorizedCommunity(query: string, options: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<Community>[]): Observable<RemoteData<PaginatedList<Community>>> {
const searchHref = 'findAdminAuthorized';
options = Object.assign({}, options, {
searchParams: [new RequestParam('query', query)],
});

return this.searchBy(searchHref, options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow).pipe(
getAllCompletedRemoteData(),
);
}

// this method is overridden in order to make it public
getEndpoint() {
return this.halService.getEndpoint(this.linkPath);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,12 @@ export class CollectionDropdownComponent implements OnInit, OnDestroy {
*/
@Input() entityType: string;

/**
* Search endpoint to use for finding authorized collections.
* Defaults to 'findSubmitAuthorized', but can be overridden (e.g. to 'findAdminAuthorized')
*/
@Input() searchHref = 'findSubmitAuthorized';

/**
* Emit to notify whether search is complete
*/
Expand Down Expand Up @@ -252,7 +258,7 @@ export class CollectionDropdownComponent implements OnInit, OnDestroy {
followLink('parentCommunity'));
} else {
searchListService$ = this.collectionDataService
.getAuthorizedCollection(query, findOptions, true, true, followLink('parentCommunity'));
.getAuthorizedCollection(query, findOptions, true, true, this.searchHref, followLink('parentCommunity'));
}
this.searchListCollection$ = searchListService$.pipe(
getFirstCompletedRemoteData(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,5 +83,19 @@ 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(),
);
done();
});
});
});

});
});
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ 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')
*/
@Input() searchHref = 'findSubmitAuthorized';

constructor(
protected searchService: SearchService,
protected collectionDataService: CollectionDataService,
Expand Down Expand Up @@ -104,7 +110,7 @@ export class AuthorizedCollectionSelectorComponent extends DSOSelectorComponent
findOptions);
} else {
searchListService$ = this.collectionDataService
.getAuthorizedCollection(query, findOptions, useCache, false, followLink('parentCommunity'));
.getAuthorizedCollection(query, findOptions, useCache, false, this.searchHref, followLink('parentCommunity'));
}
return searchListService$.pipe(
getFirstCompletedRemoteData(),
Expand Down
Original file line number Diff line number Diff line change
@@ -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 { TranslateModule } from '@ngx-translate/core';

import { CommunityDataService } from '../../../../core/data/community-data.service';
import { Community } from '../../../../core/shared/community.model';
import { DSpaceObjectType } from '../../../../core/shared/dspace-object-type.model';
import { SearchService } from '../../../../core/shared/search/search.service';
import { ThemedLoadingComponent } from '../../../loading/themed-loading.component';
import { NotificationsService } from '../../../notifications/notifications.service';
import { ListableObjectComponentLoaderComponent } from '../../../object-collection/shared/listable-object/listable-object-component-loader.component';
import { createSuccessfulRemoteDataObject$ } from '../../../remote-data.utils';
import { createPaginatedList } from '../../../testing/utils.test';
import { VarDirective } from '../../../utils/var.directive';
import { AuthorizedCommunitySelectorComponent } from './authorized-community-selector.component';

describe('AuthorizedCommunitySelectorComponent', () => {
let component: AuthorizedCommunitySelectorComponent;
let fixture: ComponentFixture<AuthorizedCommunitySelectorComponent>;

let communityService;
let community;

let notificationsService: NotificationsService;

beforeEach(waitForAsync(() => {
community = Object.assign(new Community(), {
id: 'authorized-community',
});
communityService = jasmine.createSpyObj('communityService', {
getAuthorizedCommunity: createSuccessfulRemoteDataObject$(createPaginatedList([community])),
});
notificationsService = jasmine.createSpyObj('notificationsService', ['error']);
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([]), AuthorizedCommunitySelectorComponent, VarDirective],
providers: [
{ provide: SearchService, useValue: {} },
{ provide: CommunityDataService, useValue: communityService },
{ provide: NotificationsService, useValue: notificationsService },
],
schemas: [NO_ERRORS_SCHEMA],
})
.overrideComponent(AuthorizedCommunitySelectorComponent, {
remove: { imports: [ListableObjectComponentLoaderComponent, ThemedLoadingComponent] },
})
.compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(AuthorizedCommunitySelectorComponent);
component = fixture.componentInstance;
component.types = [DSpaceObjectType.COMMUNITY];
fixture.detectChanges();
});

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();
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import {
AsyncPipe,
NgClass,
} from '@angular/common';
import { Component } from '@angular/core';
import {
FormsModule,
ReactiveFormsModule,
} from '@angular/forms';
import {
TranslateModule,
TranslateService,
} from '@ngx-translate/core';
import { InfiniteScrollModule } from 'ngx-infinite-scroll';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service';
import { CommunityDataService } from '../../../../core/data/community-data.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 { Community } from '../../../../core/shared/community.model';
import { DSpaceObject } from '../../../../core/shared/dspace-object.model';
import { getFirstCompletedRemoteData } from '../../../../core/shared/operators';
import { SearchService } from '../../../../core/shared/search/search.service';
import { hasValue } from '../../../empty.util';
import { HoverClassDirective } from '../../../hover-class.directive';
import { ThemedLoadingComponent } from '../../../loading/themed-loading.component';
import { NotificationsService } from '../../../notifications/notifications.service';
import { CommunitySearchResult } from '../../../object-collection/shared/community-search-result.model';
import { ListableObjectComponentLoaderComponent } from '../../../object-collection/shared/listable-object/listable-object-component-loader.component';
import { SearchResult } from '../../../search/models/search-result.model';
import { followLink } from '../../../utils/follow-link-config.model';
import { DSOSelectorComponent } from '../dso-selector.component';

@Component({
selector: 'ds-authorized-community-selector',
styleUrls: ['../dso-selector.component.scss'],
templateUrl: '../dso-selector.component.html',
standalone: true,
imports: [
AsyncPipe,
FormsModule,
HoverClassDirective,
InfiniteScrollModule,
ListableObjectComponentLoaderComponent,
NgClass,
ReactiveFormsModule,
ThemedLoadingComponent,
TranslateModule,
],
})
/**
* 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
*/

constructor(
protected searchService: SearchService,
protected communityDataService: CommunityDataService,
protected notifcationsService: NotificationsService,
protected translate: TranslateService,
protected dsoNameService: DSONameService,
) {
super(searchService, notifcationsService, translate, dsoNameService);
}

/**
* Get a query to send for retrieving the current DSO
*/
getCurrentDSOQuery(): string {
return this.currentDSOId;
}

/**
* Perform a search for authorized communities 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<RemoteData<PaginatedList<SearchResult<DSpaceObject>>>> {
let searchListService$: Observable<RemoteData<PaginatedList<Community>>> = null;
const findOptions: FindListOptions = {
currentPage: page,
elementsPerPage: this.defaultPagination.pageSize,
};

searchListService$ = this.communityDataService
.getAuthorizedCommunity(query, findOptions, useCache, false, followLink('parentCommunity'));

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((col) => Object.assign(new CommunitySearchResult(), { indexableObject: col }))) : null,
})),
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<div>
<div class="modal-header">{{'dso-selector.'+ action + '.' + objectType.toString().toLowerCase() + '.head' | translate}}
<button type="button" class="btn-close" (click)="close()" aria-label="Close">
</button>
</div>
<div class="modal-body">
@if (header) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NOTE: I don't believe this @if syntax will work for DSpace 7, because it was introduced in Angular 17 (which we first used in DSpace 8). In DSpace 7, this same behavior was controlled via *ngIf.

So, this just means that I'm only attempting to backport this to DSpace 8.x and 9.x. If we want to backport also to 7.6.x, we'd need a separate PR.

<span class="h5 px-2">{{header | translate}}</span>
}
<ds-authorized-community-selector [currentDSOId]="dsoRD?.payload.uuid"
[types]="selectorTypes"
(onSelect)="selectObject($event)"></ds-authorized-community-selector>
</div>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { Community } from '../../../../core/shared/community.model';
import { MetadataValue } from '../../../../core/shared/metadata.models';
import { createSuccessfulRemoteDataObject } from '../../../remote-data.utils';
import { RouterStub } from '../../../testing/router.stub';
import { DSOSelectorComponent } from '../../dso-selector/dso-selector.component';
import { AuthorizedCommunitySelectorComponent } from '../../dso-selector/authorized-community-selector/authorized-community-selector.component';
import { CreateCollectionParentSelectorComponent } from './create-collection-parent-selector.component';

describe('CreateCollectionParentSelectorComponent', () => {
Expand Down Expand Up @@ -64,7 +64,7 @@ describe('CreateCollectionParentSelectorComponent', () => {
schemas: [NO_ERRORS_SCHEMA],
})
.overrideComponent(CreateCollectionParentSelectorComponent, {
remove: { imports: [DSOSelectorComponent] },
remove: { imports: [AuthorizedCommunitySelectorComponent] },
})
.compileComponents();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
} from '../../../../core/cache/models/sort-options.model';
import { DSpaceObject } from '../../../../core/shared/dspace-object.model';
import { DSpaceObjectType } from '../../../../core/shared/dspace-object-type.model';
import { DSOSelectorComponent } from '../../dso-selector/dso-selector.component';
import { AuthorizedCommunitySelectorComponent } from '../../dso-selector/authorized-community-selector/authorized-community-selector.component';
import {
DSOSelectorModalWrapperComponent,
SelectorActionType,
Expand All @@ -34,10 +34,10 @@ import {

@Component({
selector: 'ds-base-create-collection-parent-selector',
templateUrl: '../dso-selector-modal-wrapper.component.html',
templateUrl: './create-collection-parent-selector.component.html',
standalone: true,
imports: [
DSOSelectorComponent,
AuthorizedCommunitySelectorComponent,
TranslateModule,
],
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
</div>
}
<span class="h5 px-2">{{'dso-selector.create.community.sub-level' | translate}}</span>
<ds-dso-selector [currentDSOId]="dsoRD?.payload.uuid" [types]="selectorTypes" [sort]="defaultSort" (onSelect)="selectObject($event)"></ds-dso-selector>
<ds-authorized-community-selector [currentDSOId]="dsoRD?.payload.uuid"
[types]="selectorTypes"
(onSelect)="selectObject($event)"></ds-authorized-community-selector>
</div>
</div>

Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { Community } from '../../../../core/shared/community.model';
import { MetadataValue } from '../../../../core/shared/metadata.models';
import { createSuccessfulRemoteDataObject } from '../../../remote-data.utils';
import { RouterStub } from '../../../testing/router.stub';
import { DSOSelectorComponent } from '../../dso-selector/dso-selector.component';
import { AuthorizedCommunitySelectorComponent } from '../../dso-selector/authorized-community-selector/authorized-community-selector.component';
import { CreateCommunityParentSelectorComponent } from './create-community-parent-selector.component';

describe('CreateCommunityParentSelectorComponent', () => {
Expand Down Expand Up @@ -69,7 +69,7 @@ describe('CreateCommunityParentSelectorComponent', () => {
schemas: [NO_ERRORS_SCHEMA],
})
.overrideComponent(CreateCommunityParentSelectorComponent, {
remove: { imports: [DSOSelectorComponent] },
remove: { imports: [AuthorizedCommunitySelectorComponent] },
})
.compileComponents();

Expand Down
Loading