mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-17 15:03:07 +00:00
Merge branch 'DSpace:main' into feat/i18n
This commit is contained in:
@@ -20,25 +20,33 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr *ngFor="let eperson of (ePeopleMembersOfGroup | async)?.page">
|
<tr *ngFor="let epersonDTO of (ePeopleMembersOfGroup | async)?.page">
|
||||||
<td class="align-middle">{{eperson.id}}</td>
|
<td class="align-middle">{{epersonDTO.eperson.id}}</td>
|
||||||
<td class="align-middle">
|
<td class="align-middle">
|
||||||
<a [routerLink]="getEPersonEditRoute(eperson.id)">
|
<a [routerLink]="getEPersonEditRoute(epersonDTO.eperson.id)">
|
||||||
{{ dsoNameService.getName(eperson) }}
|
{{ dsoNameService.getName(epersonDTO.eperson) }}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle">
|
<td class="align-middle">
|
||||||
{{messagePrefix + '.table.email' | translate}}: {{ eperson.email ? eperson.email : '-' }}<br/>
|
{{messagePrefix + '.table.email' | translate}}: {{ epersonDTO.eperson.email ? epersonDTO.eperson.email : '-' }}<br/>
|
||||||
{{messagePrefix + '.table.netid' | translate}}: {{ eperson.netid ? eperson.netid : '-' }}
|
{{messagePrefix + '.table.netid' | translate}}: {{ epersonDTO.eperson.netid ? epersonDTO.eperson.netid : '-' }}
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle">
|
<td class="align-middle">
|
||||||
<div class="btn-group edit-field">
|
<div class="btn-group edit-field">
|
||||||
<button (click)="deleteMemberFromGroup(eperson)"
|
<button (click)="deleteMemberFromGroup(epersonDTO.eperson)"
|
||||||
|
*ngIf="epersonDTO.ableToDelete"
|
||||||
[disabled]="actionConfig.remove.disabled"
|
[disabled]="actionConfig.remove.disabled"
|
||||||
[ngClass]="['btn btn-sm', actionConfig.remove.css]"
|
[ngClass]="['btn btn-sm', actionConfig.remove.css]"
|
||||||
title="{{messagePrefix + '.table.edit.buttons.remove' | translate: { name: dsoNameService.getName(eperson) } }}">
|
title="{{messagePrefix + '.table.edit.buttons.remove' | translate: { name: dsoNameService.getName(epersonDTO.eperson) } }}">
|
||||||
<i [ngClass]="actionConfig.remove.icon"></i>
|
<i [ngClass]="actionConfig.remove.icon"></i>
|
||||||
</button>
|
</button>
|
||||||
|
<button *ngIf="!epersonDTO.ableToDelete"
|
||||||
|
(click)="addMemberToGroup(epersonDTO.eperson)"
|
||||||
|
[disabled]="actionConfig.add.disabled"
|
||||||
|
[ngClass]="['btn btn-sm', actionConfig.add.css]"
|
||||||
|
title="{{messagePrefix + '.table.edit.buttons.add' | translate: { name: dsoNameService.getName(epersonDTO.eperson) } }}">
|
||||||
|
<i [ngClass]="actionConfig.add.icon"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@@ -222,13 +222,13 @@ describe('MembersListComponent', () => {
|
|||||||
|
|
||||||
describe('if first delete button is pressed', () => {
|
describe('if first delete button is pressed', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
spyOn(component, 'search').and.callThrough();
|
||||||
const deleteButton: DebugElement = fixture.debugElement.query(By.css('#ePeopleMembersOfGroup tbody .fa-trash-alt'));
|
const deleteButton: DebugElement = fixture.debugElement.query(By.css('#ePeopleMembersOfGroup tbody .fa-trash-alt'));
|
||||||
deleteButton.nativeElement.click();
|
deleteButton.nativeElement.click();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
it('then no ePerson remains as a member of the active group.', () => {
|
it('should trigger the search to add the user back to the search table', () => {
|
||||||
const epersonsFound = fixture.debugElement.queryAll(By.css('#ePeopleMembersOfGroup tbody tr'));
|
expect(component.search).toHaveBeenCalled();
|
||||||
expect(epersonsFound.length).toEqual(0);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -264,13 +264,13 @@ describe('MembersListComponent', () => {
|
|||||||
|
|
||||||
describe('if first add button is pressed', () => {
|
describe('if first add button is pressed', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
spyOn(component, 'search').and.callThrough();
|
||||||
const addButton: DebugElement = fixture.debugElement.query(By.css('#epersonsSearch tbody .fa-plus'));
|
const addButton: DebugElement = fixture.debugElement.query(By.css('#epersonsSearch tbody .fa-plus'));
|
||||||
addButton.nativeElement.click();
|
addButton.nativeElement.click();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
it('then all (two) ePersons are member of the active group. No non-members left', () => {
|
it('should trigger the search to remove the user from the search table', () => {
|
||||||
epersonsFound = fixture.debugElement.queryAll(By.css('#epersonsSearch tbody tr'));
|
expect(component.search).toHaveBeenCalled();
|
||||||
expect(epersonsFound.length).toEqual(0);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -24,21 +24,29 @@ import {
|
|||||||
} from '@ngx-translate/core';
|
} from '@ngx-translate/core';
|
||||||
import {
|
import {
|
||||||
BehaviorSubject,
|
BehaviorSubject,
|
||||||
|
combineLatest as observableCombineLatest,
|
||||||
Observable,
|
Observable,
|
||||||
|
ObservedValueOf,
|
||||||
|
of as observableOf,
|
||||||
Subscription,
|
Subscription,
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
import {
|
import {
|
||||||
|
defaultIfEmpty,
|
||||||
map,
|
map,
|
||||||
switchMap,
|
switchMap,
|
||||||
take,
|
take,
|
||||||
} from 'rxjs/operators';
|
} from 'rxjs/operators';
|
||||||
|
|
||||||
import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service';
|
import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service';
|
||||||
import { PaginatedList } from '../../../../core/data/paginated-list.model';
|
import {
|
||||||
|
buildPaginatedList,
|
||||||
|
PaginatedList,
|
||||||
|
} from '../../../../core/data/paginated-list.model';
|
||||||
import { RemoteData } from '../../../../core/data/remote-data';
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
import { EPersonDataService } from '../../../../core/eperson/eperson-data.service';
|
import { EPersonDataService } from '../../../../core/eperson/eperson-data.service';
|
||||||
import { GroupDataService } from '../../../../core/eperson/group-data.service';
|
import { GroupDataService } from '../../../../core/eperson/group-data.service';
|
||||||
import { EPerson } from '../../../../core/eperson/models/eperson.model';
|
import { EPerson } from '../../../../core/eperson/models/eperson.model';
|
||||||
|
import { EpersonDtoModel } from '../../../../core/eperson/models/eperson-dto.model';
|
||||||
import { Group } from '../../../../core/eperson/models/group.model';
|
import { Group } from '../../../../core/eperson/models/group.model';
|
||||||
import { PaginationService } from '../../../../core/pagination/pagination.service';
|
import { PaginationService } from '../../../../core/pagination/pagination.service';
|
||||||
import {
|
import {
|
||||||
@@ -137,7 +145,7 @@ export class MembersListComponent implements OnInit, OnDestroy {
|
|||||||
/**
|
/**
|
||||||
* List of EPeople members of currently active group being edited
|
* List of EPeople members of currently active group being edited
|
||||||
*/
|
*/
|
||||||
ePeopleMembersOfGroup: BehaviorSubject<PaginatedList<EPerson>> = new BehaviorSubject<PaginatedList<EPerson>>(undefined);
|
ePeopleMembersOfGroup: BehaviorSubject<PaginatedList<EpersonDtoModel>> = new BehaviorSubject(undefined);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pagination config used to display the list of EPeople that are result of EPeople search
|
* Pagination config used to display the list of EPeople that are result of EPeople search
|
||||||
@@ -226,10 +234,35 @@ export class MembersListComponent implements OnInit, OnDestroy {
|
|||||||
return rd;
|
return rd;
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
getRemoteDataPayload())
|
switchMap((epersonListRD: RemoteData<PaginatedList<EPerson>>) => {
|
||||||
.subscribe((paginatedListOfEPersons: PaginatedList<EPerson>) => {
|
const dtos$ = observableCombineLatest([...epersonListRD.payload.page.map((member: EPerson) => {
|
||||||
this.ePeopleMembersOfGroup.next(paginatedListOfEPersons);
|
const dto$: Observable<EpersonDtoModel> = observableCombineLatest(
|
||||||
}));
|
this.isMemberOfGroup(member), (isMember: ObservedValueOf<Observable<boolean>>) => {
|
||||||
|
const epersonDtoModel: EpersonDtoModel = new EpersonDtoModel();
|
||||||
|
epersonDtoModel.eperson = member;
|
||||||
|
epersonDtoModel.ableToDelete = isMember;
|
||||||
|
return epersonDtoModel;
|
||||||
|
});
|
||||||
|
return dto$;
|
||||||
|
})]);
|
||||||
|
return dtos$.pipe(defaultIfEmpty([]), map((dtos: EpersonDtoModel[]) => {
|
||||||
|
return buildPaginatedList(epersonListRD.payload.pageInfo, dtos);
|
||||||
|
}));
|
||||||
|
}),
|
||||||
|
).subscribe((paginatedListOfDTOs: PaginatedList<EpersonDtoModel>) => {
|
||||||
|
this.ePeopleMembersOfGroup.next(paginatedListOfDTOs);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We always return true since this is only used by the top section (which represents all the users part of the group
|
||||||
|
* in {@link MembersListComponent})
|
||||||
|
*
|
||||||
|
* @param possibleMember EPerson that is a possible member (being tested) of the group currently being edited
|
||||||
|
*/
|
||||||
|
isMemberOfGroup(possibleMember: EPerson): Observable<boolean> {
|
||||||
|
return observableOf(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -128,6 +128,7 @@ describe('RelationshipDataService', () => {
|
|||||||
const itemService = jasmine.createSpyObj('itemService', {
|
const itemService = jasmine.createSpyObj('itemService', {
|
||||||
findById: (uuid) => createSuccessfulRemoteDataObject(relatedItems.find((relatedItem) => relatedItem.id === uuid)),
|
findById: (uuid) => createSuccessfulRemoteDataObject(relatedItems.find((relatedItem) => relatedItem.id === uuid)),
|
||||||
findByHref: createSuccessfulRemoteDataObject$(relatedItems[0]),
|
findByHref: createSuccessfulRemoteDataObject$(relatedItems[0]),
|
||||||
|
getIDHrefObs: (uuid: string) => observableOf(`https://demo.dspace.org/server/api/core/items/${uuid}`),
|
||||||
});
|
});
|
||||||
|
|
||||||
const getRequestEntry$ = (successful: boolean) => {
|
const getRequestEntry$ = (successful: boolean) => {
|
||||||
@@ -244,6 +245,16 @@ describe('RelationshipDataService', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('searchByItemsAndType', () => {
|
||||||
|
it('should call addDependency for each item to invalidate the request when one of the items is update', () => {
|
||||||
|
spyOn(service as any, 'addDependency');
|
||||||
|
|
||||||
|
service.searchByItemsAndType(relationshipType.id, item.id, relationshipType.leftwardType, ['item-id-1', 'item-id-2']);
|
||||||
|
|
||||||
|
expect((service as any).addDependency).toHaveBeenCalledTimes(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('resolveMetadataRepresentation', () => {
|
describe('resolveMetadataRepresentation', () => {
|
||||||
const parentItem: Item = Object.assign(new Item(), {
|
const parentItem: Item = Object.assign(new Item(), {
|
||||||
id: 'parent-item',
|
id: 'parent-item',
|
||||||
|
@@ -574,13 +574,18 @@ export class RelationshipDataService extends IdentifiableDataService<Relationshi
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.searchBy(
|
const searchRD$: Observable<RemoteData<PaginatedList<Relationship>>> = this.searchBy(
|
||||||
'byItemsAndType',
|
'byItemsAndType',
|
||||||
{
|
{
|
||||||
searchParams: searchParams,
|
searchParams: searchParams,
|
||||||
},
|
},
|
||||||
) as Observable<RemoteData<PaginatedList<Relationship>>>;
|
) as Observable<RemoteData<PaginatedList<Relationship>>>;
|
||||||
|
|
||||||
|
arrayOfItemIds.forEach((itemId: string) => {
|
||||||
|
this.addDependency(searchRD$, this.itemService.getIDHrefObs(encodeURIComponent(itemId)));
|
||||||
|
});
|
||||||
|
|
||||||
|
return searchRD$;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -107,13 +107,17 @@ export class EPersonDataService extends IdentifiableDataService<EPerson> impleme
|
|||||||
* @param scope Scope of the EPeople search, default byMetadata
|
* @param scope Scope of the EPeople search, default byMetadata
|
||||||
* @param query Query of search
|
* @param query Query of search
|
||||||
* @param options Options of search request
|
* @param options Options of search request
|
||||||
|
* @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
|
||||||
*/
|
*/
|
||||||
public searchByScope(scope: string, query: string, options: FindListOptions = {}, useCachedVersionIfAvailable?: boolean): Observable<RemoteData<PaginatedList<EPerson>>> {
|
public searchByScope(scope: string, query: string, options: FindListOptions = {}, useCachedVersionIfAvailable?: boolean, reRequestOnStale = true): Observable<RemoteData<PaginatedList<EPerson>>> {
|
||||||
switch (scope) {
|
switch (scope) {
|
||||||
case 'metadata':
|
case 'metadata':
|
||||||
return this.getEpeopleByMetadata(query.trim(), options, useCachedVersionIfAvailable);
|
return this.getEpeopleByMetadata(query.trim(), options, useCachedVersionIfAvailable, reRequestOnStale);
|
||||||
case 'email':
|
case 'email':
|
||||||
return this.getEPersonByEmail(query.trim()).pipe(
|
return this.getEPersonByEmail(query.trim(), useCachedVersionIfAvailable, reRequestOnStale).pipe(
|
||||||
map((rd: RemoteData<EPerson | NoContent>) => {
|
map((rd: RemoteData<EPerson | NoContent>) => {
|
||||||
if (rd.hasSucceeded) {
|
if (rd.hasSucceeded) {
|
||||||
// Turn the single EPerson or NoContent in to a PaginatedList<EPerson>
|
// Turn the single EPerson or NoContent in to a PaginatedList<EPerson>
|
||||||
@@ -145,7 +149,7 @@ export class EPersonDataService extends IdentifiableDataService<EPerson> impleme
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
return this.getEpeopleByMetadata(query.trim(), options, useCachedVersionIfAvailable);
|
return this.getEpeopleByMetadata(query.trim(), options, useCachedVersionIfAvailable, reRequestOnStale);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -185,6 +185,7 @@ describe('EditItemRelationshipsService', () => {
|
|||||||
|
|
||||||
expect(itemService.invalidateByHref).toHaveBeenCalledWith(currentItem.self);
|
expect(itemService.invalidateByHref).toHaveBeenCalledWith(currentItem.self);
|
||||||
expect(itemService.invalidateByHref).toHaveBeenCalledWith(relationshipItem1.self);
|
expect(itemService.invalidateByHref).toHaveBeenCalledWith(relationshipItem1.self);
|
||||||
|
expect(itemService.invalidateByHref).toHaveBeenCalledWith(relationshipItem2.self);
|
||||||
|
|
||||||
expect(notificationsService.success).toHaveBeenCalledTimes(1);
|
expect(notificationsService.success).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
@@ -265,6 +266,116 @@ describe('EditItemRelationshipsService', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('isProvidedItemTypeLeftType', () => {
|
||||||
|
it('should return true if the provided item corresponds to the left type of the relationship', (done) => {
|
||||||
|
const relationshipType = Object.assign(new RelationshipType(), {
|
||||||
|
leftType: createSuccessfulRemoteDataObject$({ id: 'leftType' }),
|
||||||
|
rightType: createSuccessfulRemoteDataObject$({ id: 'rightType' }),
|
||||||
|
});
|
||||||
|
const itemType = Object.assign(new ItemType(), { id: 'leftType' } );
|
||||||
|
const item = Object.assign(new Item(), { uuid: 'item-uuid' });
|
||||||
|
|
||||||
|
const result = service.isProvidedItemTypeLeftType(relationshipType, itemType, item);
|
||||||
|
result.subscribe((resultValue) => {
|
||||||
|
expect(resultValue).toBeTrue();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false if the provided item corresponds to the right type of the relationship', (done) => {
|
||||||
|
const relationshipType = Object.assign(new RelationshipType(), {
|
||||||
|
leftType: createSuccessfulRemoteDataObject$({ id: 'leftType' }),
|
||||||
|
rightType: createSuccessfulRemoteDataObject$({ id: 'rightType' }),
|
||||||
|
});
|
||||||
|
const itemType = Object.assign(new ItemType(), { id: 'rightType' } );
|
||||||
|
const item = Object.assign(new Item(), { uuid: 'item-uuid' });
|
||||||
|
|
||||||
|
const result = service.isProvidedItemTypeLeftType(relationshipType, itemType, item);
|
||||||
|
result.subscribe((resultValue) => {
|
||||||
|
expect(resultValue).toBeFalse();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return undefined if the provided item corresponds does not match any of the relationship types', (done) => {
|
||||||
|
const relationshipType = Object.assign(new RelationshipType(), {
|
||||||
|
leftType: createSuccessfulRemoteDataObject$({ id: 'leftType' }),
|
||||||
|
rightType: createSuccessfulRemoteDataObject$({ id: 'rightType' }),
|
||||||
|
});
|
||||||
|
const itemType = Object.assign(new ItemType(), { id: 'something-else' } );
|
||||||
|
const item = Object.assign(new Item(), { uuid: 'item-uuid' });
|
||||||
|
|
||||||
|
const result = service.isProvidedItemTypeLeftType(relationshipType, itemType, item);
|
||||||
|
result.subscribe((resultValue) => {
|
||||||
|
expect(resultValue).toBeUndefined();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('relationshipMatchesBothSameTypes', () => {
|
||||||
|
it('should return true if both left and right type of the relationship type are the same and match the provided itemtype', (done) => {
|
||||||
|
const relationshipType = Object.assign(new RelationshipType(), {
|
||||||
|
leftType: createSuccessfulRemoteDataObject$({ id: 'sameType' }),
|
||||||
|
rightType: createSuccessfulRemoteDataObject$({ id:'sameType' }),
|
||||||
|
leftwardType: 'isDepartmentOfDivision',
|
||||||
|
rightwardType: 'isDivisionOfDepartment',
|
||||||
|
});
|
||||||
|
const itemType = Object.assign(new ItemType(), { id: 'sameType' } );
|
||||||
|
|
||||||
|
const result = service.shouldDisplayBothRelationshipSides(relationshipType, itemType);
|
||||||
|
result.subscribe((resultValue) => {
|
||||||
|
expect(resultValue).toBeTrue();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should return false if both left and right type of the relationship type are the same and match the provided itemtype but the leftwardType & rightwardType is identical', (done) => {
|
||||||
|
const relationshipType = Object.assign(new RelationshipType(), {
|
||||||
|
leftType: createSuccessfulRemoteDataObject$({ id: 'sameType' }),
|
||||||
|
rightType: createSuccessfulRemoteDataObject$({ id: 'sameType' }),
|
||||||
|
leftwardType: 'isOrgUnitOfOrgUnit',
|
||||||
|
rightwardType: 'isOrgUnitOfOrgUnit',
|
||||||
|
});
|
||||||
|
const itemType = Object.assign(new ItemType(), { id: 'sameType' });
|
||||||
|
|
||||||
|
const result = service.shouldDisplayBothRelationshipSides(relationshipType, itemType);
|
||||||
|
result.subscribe((resultValue) => {
|
||||||
|
expect(resultValue).toBeFalse();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should return false if both left and right type of the relationship type are the same and do not match the provided itemtype', (done) => {
|
||||||
|
const relationshipType = Object.assign(new RelationshipType(), {
|
||||||
|
leftType: createSuccessfulRemoteDataObject$({ id: 'sameType' }),
|
||||||
|
rightType: createSuccessfulRemoteDataObject$({ id: 'sameType' }),
|
||||||
|
leftwardType: 'isDepartmentOfDivision',
|
||||||
|
rightwardType: 'isDivisionOfDepartment',
|
||||||
|
});
|
||||||
|
const itemType = Object.assign(new ItemType(), { id: 'something-else' } );
|
||||||
|
|
||||||
|
const result = service.shouldDisplayBothRelationshipSides(relationshipType, itemType);
|
||||||
|
result.subscribe((resultValue) => {
|
||||||
|
expect(resultValue).toBeFalse();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should return false if both left and right type of the relationship type are different', (done) => {
|
||||||
|
const relationshipType = Object.assign(new RelationshipType(), {
|
||||||
|
leftType: createSuccessfulRemoteDataObject$({ id: 'leftType' }),
|
||||||
|
rightType: createSuccessfulRemoteDataObject$({ id: 'rightType' }),
|
||||||
|
leftwardType: 'isAuthorOfPublication',
|
||||||
|
rightwardType: 'isPublicationOfAuthor',
|
||||||
|
});
|
||||||
|
const itemType = Object.assign(new ItemType(), { id: 'leftType' } );
|
||||||
|
|
||||||
|
const result = service.shouldDisplayBothRelationshipSides(relationshipType, itemType);
|
||||||
|
result.subscribe((resultValue) => {
|
||||||
|
expect(resultValue).toBeFalse();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('displayNotifications', () => {
|
describe('displayNotifications', () => {
|
||||||
it('should show one success notification when multiple requests succeeded', () => {
|
it('should show one success notification when multiple requests succeeded', () => {
|
||||||
service.displayNotifications([
|
service.displayNotifications([
|
||||||
|
@@ -3,6 +3,7 @@ import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
|||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import {
|
import {
|
||||||
BehaviorSubject,
|
BehaviorSubject,
|
||||||
|
combineLatest as observableCombineLatest,
|
||||||
EMPTY,
|
EMPTY,
|
||||||
Observable,
|
Observable,
|
||||||
Subscription,
|
Subscription,
|
||||||
@@ -28,8 +29,14 @@ import { ObjectUpdatesService } from '../../../core/data/object-updates/object-u
|
|||||||
import { RelationshipDataService } from '../../../core/data/relationship-data.service';
|
import { RelationshipDataService } from '../../../core/data/relationship-data.service';
|
||||||
import { RemoteData } from '../../../core/data/remote-data';
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
import { Item } from '../../../core/shared/item.model';
|
import { Item } from '../../../core/shared/item.model';
|
||||||
|
import { ItemType } from '../../../core/shared/item-relationships/item-type.model';
|
||||||
import { Relationship } from '../../../core/shared/item-relationships/relationship.model';
|
import { Relationship } from '../../../core/shared/item-relationships/relationship.model';
|
||||||
|
import { RelationshipType } from '../../../core/shared/item-relationships/relationship-type.model';
|
||||||
import { NoContent } from '../../../core/shared/NoContent.model';
|
import { NoContent } from '../../../core/shared/NoContent.model';
|
||||||
|
import {
|
||||||
|
getFirstSucceededRemoteData,
|
||||||
|
getRemoteDataPayload,
|
||||||
|
} from '../../../core/shared/operators';
|
||||||
import { hasValue } from '../../../shared/empty.util';
|
import { hasValue } from '../../../shared/empty.util';
|
||||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||||
|
|
||||||
@@ -70,7 +77,17 @@ export class EditItemRelationshipsService {
|
|||||||
// process each update one by one, while waiting for the previous to finish
|
// process each update one by one, while waiting for the previous to finish
|
||||||
concatMap((update: FieldUpdate) => {
|
concatMap((update: FieldUpdate) => {
|
||||||
if (update.changeType === FieldChangeType.REMOVE) {
|
if (update.changeType === FieldChangeType.REMOVE) {
|
||||||
return this.deleteRelationship(update.field as DeleteRelationship).pipe(take(1));
|
return this.deleteRelationship(update.field as DeleteRelationship).pipe(
|
||||||
|
take(1),
|
||||||
|
switchMap((deleteRD: RemoteData<NoContent>) => {
|
||||||
|
if (deleteRD.hasSucceeded) {
|
||||||
|
return this.itemService.invalidateByHref((update.field as DeleteRelationship).relatedItem._links.self.href).pipe(
|
||||||
|
map(() => deleteRD),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return [deleteRD];
|
||||||
|
}),
|
||||||
|
);
|
||||||
} else if (update.changeType === FieldChangeType.ADD) {
|
} else if (update.changeType === FieldChangeType.ADD) {
|
||||||
return this.addRelationship(update.field as RelationshipIdentifiable).pipe(
|
return this.addRelationship(update.field as RelationshipIdentifiable).pipe(
|
||||||
take(1),
|
take(1),
|
||||||
@@ -181,6 +198,55 @@ export class EditItemRelationshipsService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isProvidedItemTypeLeftType(relationshipType: RelationshipType, itemType: ItemType, item: Item): Observable<boolean> {
|
||||||
|
return this.getRelationshipLeftAndRightType(relationshipType).pipe(
|
||||||
|
map(([leftType, rightType]: [ItemType, ItemType]) => {
|
||||||
|
if (leftType.id === itemType.id) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rightType.id === itemType.id) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// should never happen...
|
||||||
|
console.warn(`The item ${item.uuid} is not on the right or the left side of relationship type ${relationshipType.uuid}`);
|
||||||
|
return undefined;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether both side of the relationship need to be displayed on the edit relationship page or not.
|
||||||
|
*
|
||||||
|
* @param relationshipType The relationship type
|
||||||
|
* @param itemType The item type
|
||||||
|
*/
|
||||||
|
shouldDisplayBothRelationshipSides(relationshipType: RelationshipType, itemType: ItemType): Observable<boolean> {
|
||||||
|
return this.getRelationshipLeftAndRightType(relationshipType).pipe(
|
||||||
|
map(([leftType, rightType]: [ItemType, ItemType]) => {
|
||||||
|
return leftType.id === itemType.id && rightType.id === itemType.id && relationshipType.leftwardType !== relationshipType.rightwardType;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getRelationshipLeftAndRightType(relationshipType: RelationshipType): Observable<[ItemType, ItemType]> {
|
||||||
|
const leftType$: Observable<ItemType> = relationshipType.leftType.pipe(
|
||||||
|
getFirstSucceededRemoteData(),
|
||||||
|
getRemoteDataPayload(),
|
||||||
|
);
|
||||||
|
|
||||||
|
const rightType$: Observable<ItemType> = relationshipType.rightType.pipe(
|
||||||
|
getFirstSucceededRemoteData(),
|
||||||
|
getRemoteDataPayload(),
|
||||||
|
);
|
||||||
|
|
||||||
|
return observableCombineLatest([
|
||||||
|
leftType$,
|
||||||
|
rightType$,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -197,6 +263,5 @@ export class EditItemRelationshipsService {
|
|||||||
*/
|
*/
|
||||||
getNotificationContent(key: string): string {
|
getNotificationContent(key: string): string {
|
||||||
return this.translateService.instant(this.notificationsPrefix + key + '.content');
|
return this.translateService.instant(this.notificationsPrefix + key + '.content');
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,31 @@
|
|||||||
|
<ng-container *ngIf="shouldDisplayBothRelationshipSides$ | async">
|
||||||
|
<ds-edit-relationship-list
|
||||||
|
[url]="url"
|
||||||
|
[item]="item"
|
||||||
|
[itemType]="itemType"
|
||||||
|
[relationshipType]="relationshipType"
|
||||||
|
[hasChanges]="hasChanges"
|
||||||
|
[currentItemIsLeftItem$]="isLeftItem$"
|
||||||
|
class="d-block mb-4"
|
||||||
|
></ds-edit-relationship-list>
|
||||||
|
<ds-edit-relationship-list
|
||||||
|
[url]="url"
|
||||||
|
[item]="item"
|
||||||
|
[itemType]="itemType"
|
||||||
|
[relationshipType]="relationshipType"
|
||||||
|
[hasChanges]="hasChanges"
|
||||||
|
[currentItemIsLeftItem$]="isRightItem$"
|
||||||
|
></ds-edit-relationship-list>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container *ngIf="(shouldDisplayBothRelationshipSides$ | async) === false">
|
||||||
|
<ds-edit-relationship-list
|
||||||
|
[url]="url"
|
||||||
|
[item]="item"
|
||||||
|
[itemType]="itemType"
|
||||||
|
[relationshipType]="relationshipType"
|
||||||
|
[hasChanges]="hasChanges"
|
||||||
|
[currentItemIsLeftItem$]="currentItemIsLeftItem$"
|
||||||
|
></ds-edit-relationship-list>
|
||||||
|
</ng-container>
|
||||||
|
|
@@ -0,0 +1,122 @@
|
|||||||
|
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||||
|
import {
|
||||||
|
ComponentFixture,
|
||||||
|
TestBed,
|
||||||
|
waitForAsync,
|
||||||
|
} from '@angular/core/testing';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { cold } from 'jasmine-marbles';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
|
||||||
|
import { Item } from '../../../../core/shared/item.model';
|
||||||
|
import { ItemType } from '../../../../core/shared/item-relationships/item-type.model';
|
||||||
|
import { RelationshipType } from '../../../../core/shared/item-relationships/relationship-type.model';
|
||||||
|
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-data.utils';
|
||||||
|
import { EditItemRelationshipsService } from '../edit-item-relationships.service';
|
||||||
|
import { EditRelationshipListComponent } from '../edit-relationship-list/edit-relationship-list.component';
|
||||||
|
import { EditRelationshipListWrapperComponent } from './edit-relationship-list-wrapper.component';
|
||||||
|
|
||||||
|
describe('EditRelationshipListWrapperComponent', () => {
|
||||||
|
let editItemRelationshipsService: EditItemRelationshipsService;
|
||||||
|
let comp: EditRelationshipListWrapperComponent;
|
||||||
|
let fixture: ComponentFixture<EditRelationshipListWrapperComponent>;
|
||||||
|
|
||||||
|
const leftType = Object.assign(new ItemType(), { id: 'leftType', label: 'leftTypeString' });
|
||||||
|
const rightType = Object.assign(new ItemType(), { id: 'rightType', label: 'rightTypeString' });
|
||||||
|
|
||||||
|
const relationshipType = Object.assign(new RelationshipType(), {
|
||||||
|
id: '1',
|
||||||
|
leftMaxCardinality: null,
|
||||||
|
leftMinCardinality: 0,
|
||||||
|
leftType: createSuccessfulRemoteDataObject$(leftType),
|
||||||
|
leftwardType: 'isOrgUnitOfOrgUnit',
|
||||||
|
rightMaxCardinality: null,
|
||||||
|
rightMinCardinality: 0,
|
||||||
|
rightType: createSuccessfulRemoteDataObject$(rightType),
|
||||||
|
rightwardType: 'isOrgUnitOfOrgUnit',
|
||||||
|
uuid: 'relationshiptype-1',
|
||||||
|
});
|
||||||
|
|
||||||
|
const item = Object.assign(new Item(), { uuid: 'item-uuid' });
|
||||||
|
|
||||||
|
beforeEach(waitForAsync(() => {
|
||||||
|
|
||||||
|
editItemRelationshipsService = jasmine.createSpyObj('editItemRelationshipsService', {
|
||||||
|
isProvidedItemTypeLeftType: observableOf(true),
|
||||||
|
shouldDisplayBothRelationshipSides: observableOf(false),
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
EditRelationshipListWrapperComponent,
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{ provide: EditItemRelationshipsService, useValue: editItemRelationshipsService },
|
||||||
|
],
|
||||||
|
schemas: [
|
||||||
|
CUSTOM_ELEMENTS_SCHEMA,
|
||||||
|
],
|
||||||
|
}).overrideComponent(EditRelationshipListWrapperComponent, {
|
||||||
|
remove: {
|
||||||
|
imports: [
|
||||||
|
EditRelationshipListComponent,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}).compileComponents();
|
||||||
|
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(EditRelationshipListWrapperComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
comp.relationshipType = relationshipType;
|
||||||
|
comp.itemType = leftType;
|
||||||
|
comp.item = item;
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('onInit', () => {
|
||||||
|
it('should render the component', () => {
|
||||||
|
expect(comp).toBeTruthy();
|
||||||
|
});
|
||||||
|
it('should set currentItemIsLeftItem$ and bothItemsMatchType$ based on the provided relationshipType, itemType and item', () => {
|
||||||
|
expect(editItemRelationshipsService.isProvidedItemTypeLeftType).toHaveBeenCalledWith(relationshipType, leftType, item);
|
||||||
|
expect(editItemRelationshipsService.shouldDisplayBothRelationshipSides).toHaveBeenCalledWith(relationshipType, leftType);
|
||||||
|
|
||||||
|
expect(comp.currentItemIsLeftItem$.getValue()).toEqual(true);
|
||||||
|
expect(comp.shouldDisplayBothRelationshipSides$).toBeObservable(cold('(a|)', { a: false }));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the current item is left', () => {
|
||||||
|
it('should render one relationship list section', () => {
|
||||||
|
const relationshipLists = fixture.debugElement.queryAll(By.css('ds-edit-relationship-list'));
|
||||||
|
expect(relationshipLists.length).toEqual(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the current item is right', () => {
|
||||||
|
it('should render one relationship list section', () => {
|
||||||
|
(editItemRelationshipsService.isProvidedItemTypeLeftType as jasmine.Spy).and.returnValue(observableOf(false));
|
||||||
|
comp.ngOnInit();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const relationshipLists = fixture.debugElement.queryAll(By.css('ds-edit-relationship-list'));
|
||||||
|
expect(relationshipLists.length).toEqual(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the current item is both left and right', () => {
|
||||||
|
it('should render two relationship list sections', () => {
|
||||||
|
(editItemRelationshipsService.shouldDisplayBothRelationshipSides as jasmine.Spy).and.returnValue(observableOf(true));
|
||||||
|
comp.ngOnInit();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const relationshipLists = fixture.debugElement.queryAll(By.css('ds-edit-relationship-list'));
|
||||||
|
expect(relationshipLists.length).toEqual(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@@ -0,0 +1,111 @@
|
|||||||
|
import {
|
||||||
|
AsyncPipe,
|
||||||
|
NgIf,
|
||||||
|
} from '@angular/common';
|
||||||
|
import {
|
||||||
|
Component,
|
||||||
|
EventEmitter,
|
||||||
|
Input,
|
||||||
|
OnDestroy,
|
||||||
|
OnInit,
|
||||||
|
Output,
|
||||||
|
} from '@angular/core';
|
||||||
|
import {
|
||||||
|
BehaviorSubject,
|
||||||
|
Observable,
|
||||||
|
Subscription,
|
||||||
|
} from 'rxjs';
|
||||||
|
|
||||||
|
import { Item } from '../../../../core/shared/item.model';
|
||||||
|
import { ItemType } from '../../../../core/shared/item-relationships/item-type.model';
|
||||||
|
import { RelationshipType } from '../../../../core/shared/item-relationships/relationship-type.model';
|
||||||
|
import { hasValue } from '../../../../shared/empty.util';
|
||||||
|
import { EditItemRelationshipsService } from '../edit-item-relationships.service';
|
||||||
|
import { EditRelationshipListComponent } from '../edit-relationship-list/edit-relationship-list.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-edit-relationship-list-wrapper',
|
||||||
|
styleUrls: ['./edit-relationship-list-wrapper.component.scss'],
|
||||||
|
templateUrl: './edit-relationship-list-wrapper.component.html',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
AsyncPipe,
|
||||||
|
EditRelationshipListComponent,
|
||||||
|
NgIf,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* A component creating a list of editable relationships of a certain type
|
||||||
|
* The relationships are rendered as a list of related items
|
||||||
|
*/
|
||||||
|
export class EditRelationshipListWrapperComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The item to display related items for
|
||||||
|
*/
|
||||||
|
@Input() item: Item;
|
||||||
|
|
||||||
|
@Input() itemType: ItemType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The URL to the current page
|
||||||
|
* Used to fetch updates for the current item from the store
|
||||||
|
*/
|
||||||
|
@Input() url: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The label of the relationship-type we're rendering a list for
|
||||||
|
*/
|
||||||
|
@Input() relationshipType: RelationshipType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If updated information has changed
|
||||||
|
*/
|
||||||
|
@Input() hasChanges!: Observable<boolean>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The event emmiter to submit the new information
|
||||||
|
*/
|
||||||
|
@Output() submitModal: EventEmitter<void> = new EventEmitter();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observable that emits true if {@link itemType} is on the left-hand side of {@link relationshipType},
|
||||||
|
* false if it is on the right-hand side and undefined in the rare case that it is on neither side.
|
||||||
|
*/
|
||||||
|
currentItemIsLeftItem$: BehaviorSubject<boolean> = new BehaviorSubject(undefined);
|
||||||
|
|
||||||
|
|
||||||
|
isLeftItem$ = new BehaviorSubject(true);
|
||||||
|
|
||||||
|
isRightItem$ = new BehaviorSubject(false);
|
||||||
|
|
||||||
|
shouldDisplayBothRelationshipSides$: Observable<boolean>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array to track all subscriptions and unsubscribe them onDestroy
|
||||||
|
* @type {Array}
|
||||||
|
*/
|
||||||
|
private subs: Subscription[] = [];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected editItemRelationshipsService: EditItemRelationshipsService,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.subs.push(this.editItemRelationshipsService.isProvidedItemTypeLeftType(this.relationshipType, this.itemType, this.item)
|
||||||
|
.subscribe((nextValue: boolean) => {
|
||||||
|
this.currentItemIsLeftItem$.next(nextValue);
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.shouldDisplayBothRelationshipSides$ = this.editItemRelationshipsService.shouldDisplayBothRelationshipSides(this.relationshipType, this.itemType);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.subs
|
||||||
|
.filter((subscription) => hasValue(subscription))
|
||||||
|
.forEach((subscription) => subscription.unsubscribe());
|
||||||
|
}
|
||||||
|
}
|
@@ -15,7 +15,10 @@ import {
|
|||||||
import { provideMockStore } from '@ngrx/store/testing';
|
import { provideMockStore } from '@ngrx/store/testing';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { cold } from 'jasmine-marbles';
|
import { cold } from 'jasmine-marbles';
|
||||||
import { of as observableOf } from 'rxjs';
|
import {
|
||||||
|
BehaviorSubject,
|
||||||
|
of as observableOf,
|
||||||
|
} from 'rxjs';
|
||||||
|
|
||||||
import { APP_CONFIG } from '../../../../../config/app-config.interface';
|
import { APP_CONFIG } from '../../../../../config/app-config.interface';
|
||||||
import { environment } from '../../../../../environments/environment.test';
|
import { environment } from '../../../../../environments/environment.test';
|
||||||
@@ -82,6 +85,7 @@ describe('EditRelationshipListComponent', () => {
|
|||||||
let relationships: Relationship[];
|
let relationships: Relationship[];
|
||||||
let relationshipType: RelationshipType;
|
let relationshipType: RelationshipType;
|
||||||
let paginationOptions: PaginationComponentOptions;
|
let paginationOptions: PaginationComponentOptions;
|
||||||
|
let currentItemIsLeftItem$ = new BehaviorSubject<boolean>(true);
|
||||||
|
|
||||||
const resetComponent = () => {
|
const resetComponent = () => {
|
||||||
fixture = TestBed.createComponent(EditRelationshipListComponent);
|
fixture = TestBed.createComponent(EditRelationshipListComponent);
|
||||||
@@ -92,6 +96,7 @@ describe('EditRelationshipListComponent', () => {
|
|||||||
comp.url = url;
|
comp.url = url;
|
||||||
comp.relationshipType = relationshipType;
|
comp.relationshipType = relationshipType;
|
||||||
comp.hasChanges = observableOf(false);
|
comp.hasChanges = observableOf(false);
|
||||||
|
comp.currentItemIsLeftItem$ = currentItemIsLeftItem$;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -193,6 +198,9 @@ describe('EditRelationshipListComponent', () => {
|
|||||||
[relationships[0].uuid]: fieldUpdate1,
|
[relationships[0].uuid]: fieldUpdate1,
|
||||||
[relationships[1].uuid]: fieldUpdate2,
|
[relationships[1].uuid]: fieldUpdate2,
|
||||||
}),
|
}),
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
|
initialize: () => {
|
||||||
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -325,6 +333,7 @@ describe('EditRelationshipListComponent', () => {
|
|||||||
leftwardType: 'isAuthorOfPublication',
|
leftwardType: 'isAuthorOfPublication',
|
||||||
rightwardType: 'isPublicationOfAuthor',
|
rightwardType: 'isPublicationOfAuthor',
|
||||||
});
|
});
|
||||||
|
currentItemIsLeftItem$ = new BehaviorSubject<boolean>(true);
|
||||||
relationshipService.getItemRelationshipsByLabel.calls.reset();
|
relationshipService.getItemRelationshipsByLabel.calls.reset();
|
||||||
resetComponent();
|
resetComponent();
|
||||||
});
|
});
|
||||||
@@ -349,6 +358,7 @@ describe('EditRelationshipListComponent', () => {
|
|||||||
leftwardType: 'isPublicationOfAuthor',
|
leftwardType: 'isPublicationOfAuthor',
|
||||||
rightwardType: 'isAuthorOfPublication',
|
rightwardType: 'isAuthorOfPublication',
|
||||||
});
|
});
|
||||||
|
currentItemIsLeftItem$ = new BehaviorSubject<boolean>(false);
|
||||||
relationshipService.getItemRelationshipsByLabel.calls.reset();
|
relationshipService.getItemRelationshipsByLabel.calls.reset();
|
||||||
resetComponent();
|
resetComponent();
|
||||||
});
|
});
|
||||||
|
@@ -43,6 +43,7 @@ import {
|
|||||||
AppConfig,
|
AppConfig,
|
||||||
} from '../../../../../config/app-config.interface';
|
} from '../../../../../config/app-config.interface';
|
||||||
import { LinkService } from '../../../../core/cache/builders/link.service';
|
import { LinkService } from '../../../../core/cache/builders/link.service';
|
||||||
|
import { RequestParam } from '../../../../core/cache/models/request-param.model';
|
||||||
import { FieldChangeType } from '../../../../core/data/object-updates/field-change-type.model';
|
import { FieldChangeType } from '../../../../core/data/object-updates/field-change-type.model';
|
||||||
import { FieldUpdate } from '../../../../core/data/object-updates/field-update.model';
|
import { FieldUpdate } from '../../../../core/data/object-updates/field-update.model';
|
||||||
import { FieldUpdates } from '../../../../core/data/object-updates/field-updates.model';
|
import { FieldUpdates } from '../../../../core/data/object-updates/field-updates.model';
|
||||||
@@ -59,6 +60,7 @@ import { Relationship } from '../../../../core/shared/item-relationships/relatio
|
|||||||
import { RelationshipType } from '../../../../core/shared/item-relationships/relationship-type.model';
|
import { RelationshipType } from '../../../../core/shared/item-relationships/relationship-type.model';
|
||||||
import {
|
import {
|
||||||
getAllSucceededRemoteData,
|
getAllSucceededRemoteData,
|
||||||
|
getFirstCompletedRemoteData,
|
||||||
getFirstSucceededRemoteData,
|
getFirstSucceededRemoteData,
|
||||||
getFirstSucceededRemoteDataPayload,
|
getFirstSucceededRemoteDataPayload,
|
||||||
getRemoteDataPayload,
|
getRemoteDataPayload,
|
||||||
@@ -67,6 +69,7 @@ import {
|
|||||||
hasNoValue,
|
hasNoValue,
|
||||||
hasValue,
|
hasValue,
|
||||||
hasValueOperator,
|
hasValueOperator,
|
||||||
|
isNotEmpty,
|
||||||
} from '../../../../shared/empty.util';
|
} from '../../../../shared/empty.util';
|
||||||
import { DsDynamicLookupRelationModalComponent } from '../../../../shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component';
|
import { DsDynamicLookupRelationModalComponent } from '../../../../shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component';
|
||||||
import { RelationshipOptions } from '../../../../shared/form/builder/models/relationship-options.model';
|
import { RelationshipOptions } from '../../../../shared/form/builder/models/relationship-options.model';
|
||||||
@@ -143,7 +146,7 @@ export class EditRelationshipListComponent implements OnInit, OnDestroy {
|
|||||||
* Observable that emits true if {@link itemType} is on the left-hand side of {@link relationshipType},
|
* Observable that emits true if {@link itemType} is on the left-hand side of {@link relationshipType},
|
||||||
* false if it is on the right-hand side and undefined in the rare case that it is on neither side.
|
* false if it is on the right-hand side and undefined in the rare case that it is on neither side.
|
||||||
*/
|
*/
|
||||||
private currentItemIsLeftItem$: BehaviorSubject<boolean> = new BehaviorSubject(undefined);
|
@Input() currentItemIsLeftItem$: BehaviorSubject<boolean> = new BehaviorSubject(undefined);
|
||||||
|
|
||||||
relatedEntityType$: Observable<ItemType>;
|
relatedEntityType$: Observable<ItemType>;
|
||||||
|
|
||||||
@@ -243,17 +246,14 @@ export class EditRelationshipListComponent implements OnInit, OnDestroy {
|
|||||||
* Get the relevant label for this relationship type
|
* Get the relevant label for this relationship type
|
||||||
*/
|
*/
|
||||||
private getLabel(): Observable<string> {
|
private getLabel(): Observable<string> {
|
||||||
return observableCombineLatest([
|
return this.currentItemIsLeftItem$.pipe(
|
||||||
this.relationshipType.leftType,
|
map((currentItemIsLeftItem) => {
|
||||||
this.relationshipType.rightType,
|
if (currentItemIsLeftItem) {
|
||||||
].map((itemTypeRD) => itemTypeRD.pipe(
|
return this.relationshipType.leftwardType;
|
||||||
getFirstSucceededRemoteData(),
|
} else {
|
||||||
getRemoteDataPayload(),
|
return this.relationshipType.rightwardType;
|
||||||
))).pipe(
|
}
|
||||||
map((itemTypes: ItemType[]) => [
|
}),
|
||||||
this.relationshipType.leftwardType,
|
|
||||||
this.relationshipType.rightwardType,
|
|
||||||
][itemTypes.findIndex((itemType) => itemType.id === this.itemType.id)]),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,6 +281,7 @@ export class EditRelationshipListComponent implements OnInit, OnDestroy {
|
|||||||
modalComp.toAdd = [];
|
modalComp.toAdd = [];
|
||||||
modalComp.toRemove = [];
|
modalComp.toRemove = [];
|
||||||
modalComp.isPending = false;
|
modalComp.isPending = false;
|
||||||
|
modalComp.hiddenQuery = '-search.resourceid:' + this.item.uuid;
|
||||||
|
|
||||||
this.item.owningCollection.pipe(
|
this.item.owningCollection.pipe(
|
||||||
getFirstSucceededRemoteDataPayload(),
|
getFirstSucceededRemoteDataPayload(),
|
||||||
@@ -309,7 +310,7 @@ export class EditRelationshipListComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.loading$.next(true);
|
this.loading$.next(isNotEmpty(modalComp.toAdd) || isNotEmpty(modalComp.toRemove));
|
||||||
// emit the last page again to trigger a fieldupdates refresh
|
// emit the last page again to trigger a fieldupdates refresh
|
||||||
this.relationshipsRd$.next(this.relationshipsRd$.getValue());
|
this.relationshipsRd$.next(this.relationshipsRd$.getValue());
|
||||||
});
|
});
|
||||||
@@ -327,6 +328,7 @@ export class EditRelationshipListComponent implements OnInit, OnDestroy {
|
|||||||
} else {
|
} else {
|
||||||
modalComp.toRemove.push(searchResult);
|
modalComp.toRemove.push(searchResult);
|
||||||
}
|
}
|
||||||
|
this.loading$.next(isNotEmpty(modalComp.toAdd) || isNotEmpty(modalComp.toRemove));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -366,6 +368,7 @@ export class EditRelationshipListComponent implements OnInit, OnDestroy {
|
|||||||
type: this.relationshipType,
|
type: this.relationshipType,
|
||||||
originalIsLeft: isLeft,
|
originalIsLeft: isLeft,
|
||||||
originalItem: this.item,
|
originalItem: this.item,
|
||||||
|
relatedItem,
|
||||||
relationship,
|
relationship,
|
||||||
} as RelationshipIdentifiable;
|
} as RelationshipIdentifiable;
|
||||||
return this.objectUpdatesService.saveRemoveFieldUpdate(this.url,update);
|
return this.objectUpdatesService.saveRemoveFieldUpdate(this.url,update);
|
||||||
@@ -399,6 +402,11 @@ export class EditRelationshipListComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
modalComp.toAdd = [];
|
modalComp.toAdd = [];
|
||||||
modalComp.toRemove = [];
|
modalComp.toRemove = [];
|
||||||
|
this.loading$.next(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
modalComp.closeEv = () => {
|
||||||
|
this.loading$.next(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.relatedEntityType$
|
this.relatedEntityType$
|
||||||
@@ -454,24 +462,6 @@ export class EditRelationshipListComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
this.relationshipMessageKey$ = this.getRelationshipMessageKey();
|
this.relationshipMessageKey$ = this.getRelationshipMessageKey();
|
||||||
|
|
||||||
this.subs.push(this.relationshipLeftAndRightType$.pipe(
|
|
||||||
map(([leftType, rightType]: [ItemType, ItemType]) => {
|
|
||||||
if (leftType.id === this.itemType.id) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rightType.id === this.itemType.id) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// should never happen...
|
|
||||||
console.warn(`The item ${this.item.uuid} is not on the right or the left side of relationship type ${this.relationshipType.uuid}`);
|
|
||||||
return undefined;
|
|
||||||
}),
|
|
||||||
).subscribe((nextValue: boolean) => {
|
|
||||||
this.currentItemIsLeftItem$.next(nextValue);
|
|
||||||
}));
|
|
||||||
|
|
||||||
|
|
||||||
// initialize the pagination options
|
// initialize the pagination options
|
||||||
this.paginationConfig = new PaginationComponentOptions();
|
this.paginationConfig = new PaginationComponentOptions();
|
||||||
@@ -494,23 +484,32 @@ export class EditRelationshipListComponent implements OnInit, OnDestroy {
|
|||||||
observableCombineLatest([
|
observableCombineLatest([
|
||||||
currentPagination$,
|
currentPagination$,
|
||||||
this.currentItemIsLeftItem$,
|
this.currentItemIsLeftItem$,
|
||||||
|
this.relatedEntityType$,
|
||||||
]).pipe(
|
]).pipe(
|
||||||
switchMap(([currentPagination, currentItemIsLeftItem]: [PaginationComponentOptions, boolean]) => {
|
switchMap(([currentPagination, currentItemIsLeftItem, relatedEntityType]: [PaginationComponentOptions, boolean, ItemType]) => {
|
||||||
// get the relationships for the current item, relationshiptype and page
|
// get the relationships for the current page, item, relationship type and related entity type
|
||||||
return this.relationshipService.getItemRelationshipsByLabel(
|
return this.relationshipService.getItemRelationshipsByLabel(
|
||||||
this.item,
|
this.item,
|
||||||
currentItemIsLeftItem ? this.relationshipType.leftwardType : this.relationshipType.rightwardType,
|
currentItemIsLeftItem ? this.relationshipType.leftwardType : this.relationshipType.rightwardType,
|
||||||
{
|
{
|
||||||
elementsPerPage: currentPagination.pageSize,
|
elementsPerPage: currentPagination.pageSize,
|
||||||
currentPage: currentPagination.currentPage,
|
currentPage: currentPagination.currentPage,
|
||||||
|
searchParams: [
|
||||||
|
new RequestParam('relatedEntityType', relatedEntityType.label),
|
||||||
|
],
|
||||||
},
|
},
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
...linksToFollow,
|
...linksToFollow,
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
).subscribe((rd: RemoteData<PaginatedList<Relationship>>) => {
|
tap((rd: RemoteData<PaginatedList<Relationship>>) => {
|
||||||
this.relationshipsRd$.next(rd);
|
this.relationshipsRd$.next(rd);
|
||||||
|
}),
|
||||||
|
getAllSucceededRemoteData(),
|
||||||
|
getRemoteDataPayload(),
|
||||||
|
).subscribe((relationshipPaginatedList: PaginatedList<Relationship>) => {
|
||||||
|
this.objectUpdatesService.initialize(this.url, relationshipPaginatedList.page, new Date());
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -533,10 +532,24 @@ export class EditRelationshipListComponent implements OnInit, OnDestroy {
|
|||||||
this.relationshipService.isLeftItem(relationship, this.item).pipe(
|
this.relationshipService.isLeftItem(relationship, this.item).pipe(
|
||||||
// emit an array containing both the relationship and whether it's the left item,
|
// emit an array containing both the relationship and whether it's the left item,
|
||||||
// as we'll need both
|
// as we'll need both
|
||||||
map((isLeftItem: boolean) => [relationship, isLeftItem]),
|
switchMap((isLeftItem: boolean) => {
|
||||||
|
if (isLeftItem) {
|
||||||
|
return relationship.rightItem.pipe(
|
||||||
|
getFirstCompletedRemoteData(),
|
||||||
|
getRemoteDataPayload(),
|
||||||
|
map((relatedItem: Item) => [relationship, isLeftItem, relatedItem]),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return relationship.leftItem.pipe(
|
||||||
|
getFirstCompletedRemoteData(),
|
||||||
|
getRemoteDataPayload(),
|
||||||
|
map((relatedItem: Item) => [relationship, isLeftItem, relatedItem]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
map(([relationship, isLeftItem]: [Relationship, boolean]) => {
|
map(([relationship, isLeftItem, relatedItem]: [Relationship, boolean, Item]) => {
|
||||||
// turn it into a RelationshipIdentifiable, an
|
// turn it into a RelationshipIdentifiable, an
|
||||||
const nameVariant =
|
const nameVariant =
|
||||||
isLeftItem ? relationship.rightwardValue : relationship.leftwardValue;
|
isLeftItem ? relationship.rightwardValue : relationship.leftwardValue;
|
||||||
@@ -546,6 +559,7 @@ export class EditRelationshipListComponent implements OnInit, OnDestroy {
|
|||||||
relationship,
|
relationship,
|
||||||
originalIsLeft: isLeftItem,
|
originalIsLeft: isLeftItem,
|
||||||
originalItem: this.item,
|
originalItem: this.item,
|
||||||
|
relatedItem: relatedItem,
|
||||||
nameVariant,
|
nameVariant,
|
||||||
} as RelationshipIdentifiable;
|
} as RelationshipIdentifiable;
|
||||||
}),
|
}),
|
||||||
|
@@ -131,9 +131,7 @@ export class EditRelationshipComponent implements OnChanges {
|
|||||||
this.leftItem$,
|
this.leftItem$,
|
||||||
this.rightItem$,
|
this.rightItem$,
|
||||||
]).pipe(
|
]).pipe(
|
||||||
map((items: Item[]) =>
|
map(([leftItem, rightItem]: [Item, Item]) => leftItem.uuid === this.editItem.uuid ? rightItem : leftItem),
|
||||||
items.find((item) => item.uuid !== this.editItem.uuid),
|
|
||||||
),
|
|
||||||
take(1),
|
take(1),
|
||||||
).subscribe((relatedItem) => {
|
).subscribe((relatedItem) => {
|
||||||
this.relatedItem$.next(relatedItem);
|
this.relatedItem$.next(relatedItem);
|
||||||
|
@@ -5,13 +5,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<div *ngIf="relationshipTypes$ | async as relationshipTypes; else loading" class="mb-4">
|
<div *ngIf="relationshipTypes$ | async as relationshipTypes; else loading" class="mb-4">
|
||||||
<div *ngFor="let relationshipType of relationshipTypes; trackBy: trackById" class="mb-4">
|
<div *ngFor="let relationshipType of relationshipTypes; trackBy: trackById" class="mb-4">
|
||||||
<ds-edit-relationship-list
|
<ds-edit-relationship-list-wrapper
|
||||||
[url]="url"
|
[url]="url"
|
||||||
[item]="item"
|
[item]="item"
|
||||||
[itemType]="entityType"
|
[itemType]="entityType"
|
||||||
[relationshipType]="relationshipType"
|
[relationshipType]="relationshipType"
|
||||||
[hasChanges]="hasChanges$"
|
[hasChanges]="hasChanges$"
|
||||||
></ds-edit-relationship-list>
|
></ds-edit-relationship-list-wrapper>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="button-row bottom">
|
<div class="button-row bottom">
|
||||||
|
@@ -50,6 +50,7 @@ import { compareArraysUsingIds } from '../../simple/item-types/shared/item-relat
|
|||||||
import { AbstractItemUpdateComponent } from '../abstract-item-update/abstract-item-update.component';
|
import { AbstractItemUpdateComponent } from '../abstract-item-update/abstract-item-update.component';
|
||||||
import { EditItemRelationshipsService } from './edit-item-relationships.service';
|
import { EditItemRelationshipsService } from './edit-item-relationships.service';
|
||||||
import { EditRelationshipListComponent } from './edit-relationship-list/edit-relationship-list.component';
|
import { EditRelationshipListComponent } from './edit-relationship-list/edit-relationship-list.component';
|
||||||
|
import { EditRelationshipListWrapperComponent } from './edit-relationship-list-wrapper/edit-relationship-list-wrapper.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-item-relationships',
|
selector: 'ds-item-relationships',
|
||||||
@@ -65,6 +66,7 @@ import { EditRelationshipListComponent } from './edit-relationship-list/edit-rel
|
|||||||
ThemedLoadingComponent,
|
ThemedLoadingComponent,
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
VarDirective,
|
VarDirective,
|
||||||
|
EditRelationshipListWrapperComponent,
|
||||||
],
|
],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
})
|
})
|
||||||
|
@@ -37,6 +37,7 @@ describe('ModifyItemOverviewComponent', () => {
|
|||||||
fixture = TestBed.createComponent(ModifyItemOverviewComponent);
|
fixture = TestBed.createComponent(ModifyItemOverviewComponent);
|
||||||
comp = fixture.componentInstance;
|
comp = fixture.componentInstance;
|
||||||
comp.item = mockItem;
|
comp.item = mockItem;
|
||||||
|
comp.ngOnChanges();
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
@@ -5,7 +5,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
Component,
|
Component,
|
||||||
Input,
|
Input,
|
||||||
OnInit,
|
OnChanges,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
|
||||||
@@ -21,12 +21,12 @@ import { MetadataMap } from '../../../core/shared/metadata.models';
|
|||||||
/**
|
/**
|
||||||
* Component responsible for rendering a table containing the metadatavalues from the to be edited item
|
* Component responsible for rendering a table containing the metadatavalues from the to be edited item
|
||||||
*/
|
*/
|
||||||
export class ModifyItemOverviewComponent implements OnInit {
|
export class ModifyItemOverviewComponent implements OnChanges {
|
||||||
|
|
||||||
@Input() item: Item;
|
@Input() item: Item;
|
||||||
metadata: MetadataMap;
|
metadata: MetadataMap;
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnChanges(): void {
|
||||||
this.metadata = this.item.metadata;
|
this.metadata = this.item?.metadata;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -9,7 +9,7 @@ import { map } from 'rxjs/operators';
|
|||||||
import { BrowseDefinitionDataService } from '../../../../core/browse/browse-definition-data.service';
|
import { BrowseDefinitionDataService } from '../../../../core/browse/browse-definition-data.service';
|
||||||
import { BrowseDefinition } from '../../../../core/shared/browse-definition.model';
|
import { BrowseDefinition } from '../../../../core/shared/browse-definition.model';
|
||||||
import { Item } from '../../../../core/shared/item.model';
|
import { Item } from '../../../../core/shared/item.model';
|
||||||
import { getRemoteDataPayload } from '../../../../core/shared/operators';
|
import { getFirstCompletedRemoteData } from '../../../../core/shared/operators';
|
||||||
import { MetadataValuesComponent } from '../../../field-components/metadata-values/metadata-values.component';
|
import { MetadataValuesComponent } from '../../../field-components/metadata-values/metadata-values.component';
|
||||||
import { ImageField } from './image-field';
|
import { ImageField } from './image-field';
|
||||||
|
|
||||||
@@ -75,8 +75,8 @@ export class ItemPageFieldComponent {
|
|||||||
*/
|
*/
|
||||||
get browseDefinition(): Observable<BrowseDefinition> {
|
get browseDefinition(): Observable<BrowseDefinition> {
|
||||||
return this.browseDefinitionDataService.findByFields(this.fields).pipe(
|
return this.browseDefinitionDataService.findByFields(this.fields).pipe(
|
||||||
getRemoteDataPayload(),
|
getFirstCompletedRemoteData(),
|
||||||
map((def) => def),
|
map((def) => def.payload),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,6 +2,7 @@ import { InjectionToken } from '@angular/core';
|
|||||||
import {
|
import {
|
||||||
combineLatest as observableCombineLatest,
|
combineLatest as observableCombineLatest,
|
||||||
Observable,
|
Observable,
|
||||||
|
of as observableOf,
|
||||||
zip as observableZip,
|
zip as observableZip,
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
import {
|
import {
|
||||||
@@ -53,17 +54,19 @@ export const compareArraysUsingIds = <T extends { id: string }>() =>
|
|||||||
/**
|
/**
|
||||||
* Operator for turning a list of relationships into a list of the relevant items
|
* Operator for turning a list of relationships into a list of the relevant items
|
||||||
* @param {string} thisId The item's id of which the relations belong to
|
* @param {string} thisId The item's id of which the relations belong to
|
||||||
* @returns {(source: Observable<Relationship[]>) => Observable<Item[]>}
|
|
||||||
*/
|
*/
|
||||||
export const relationsToItems = (thisId: string) =>
|
export const relationsToItems = (thisId: string): (source: Observable<Relationship[]>) => Observable<Item[]> =>
|
||||||
(source: Observable<Relationship[]>): Observable<Item[]> =>
|
(source: Observable<Relationship[]>): Observable<Item[]> =>
|
||||||
source.pipe(
|
source.pipe(
|
||||||
mergeMap((rels: Relationship[]) =>
|
mergeMap((relationships: Relationship[]) => {
|
||||||
observableZip(
|
if (relationships.length === 0) {
|
||||||
...rels.map((rel: Relationship) => observableCombineLatest(rel.leftItem, rel.rightItem)),
|
return observableOf([]);
|
||||||
),
|
}
|
||||||
),
|
return observableZip(
|
||||||
map((arr) =>
|
...relationships.map((rel: Relationship) => observableCombineLatest([rel.leftItem, rel.rightItem])),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
map((arr: [RemoteData<Item>, RemoteData<Item>][]) =>
|
||||||
arr
|
arr
|
||||||
.filter(([leftItem, rightItem]) => leftItem.hasSucceeded && rightItem.hasSucceeded)
|
.filter(([leftItem, rightItem]) => leftItem.hasSucceeded && rightItem.hasSucceeded)
|
||||||
.map(([leftItem, rightItem]) => {
|
.map(([leftItem, rightItem]) => {
|
||||||
|
@@ -22,7 +22,7 @@ import { Item } from '../../../core/shared/item.model';
|
|||||||
import { MetadataValue } from '../../../core/shared/metadata.models';
|
import { MetadataValue } from '../../../core/shared/metadata.models';
|
||||||
import { MetadataRepresentation } from '../../../core/shared/metadata-representation/metadata-representation.model';
|
import { MetadataRepresentation } from '../../../core/shared/metadata-representation/metadata-representation.model';
|
||||||
import { MetadatumRepresentation } from '../../../core/shared/metadata-representation/metadatum/metadatum-representation.model';
|
import { MetadatumRepresentation } from '../../../core/shared/metadata-representation/metadatum/metadatum-representation.model';
|
||||||
import { getRemoteDataPayload } from '../../../core/shared/operators';
|
import { getFirstCompletedRemoteData } from '../../../core/shared/operators';
|
||||||
import { ThemedLoadingComponent } from '../../../shared/loading/themed-loading.component';
|
import { ThemedLoadingComponent } from '../../../shared/loading/themed-loading.component';
|
||||||
import { MetadataFieldWrapperComponent } from '../../../shared/metadata-field-wrapper/metadata-field-wrapper.component';
|
import { MetadataFieldWrapperComponent } from '../../../shared/metadata-field-wrapper/metadata-field-wrapper.component';
|
||||||
import { MetadataRepresentationLoaderComponent } from '../../../shared/metadata-representation/metadata-representation-loader.component';
|
import { MetadataRepresentationLoaderComponent } from '../../../shared/metadata-representation/metadata-representation-loader.component';
|
||||||
@@ -112,8 +112,8 @@ export class MetadataRepresentationListComponent extends AbstractIncrementalList
|
|||||||
searchKeyArray = searchKeyArray.concat(BrowseService.toSearchKeyArray(field));
|
searchKeyArray = searchKeyArray.concat(BrowseService.toSearchKeyArray(field));
|
||||||
});
|
});
|
||||||
return this.browseDefinitionDataService.findByFields(this.metadataFields).pipe(
|
return this.browseDefinitionDataService.findByFields(this.metadataFields).pipe(
|
||||||
getRemoteDataPayload(),
|
getFirstCompletedRemoteData(),
|
||||||
map((def) => Object.assign(new MetadatumRepresentation(this.itemType, def), metadatum)),
|
map((def) => Object.assign(new MetadatumRepresentation(this.itemType, def.payload), metadatum)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
@@ -19,6 +19,7 @@
|
|||||||
[repeatable]="repeatable"
|
[repeatable]="repeatable"
|
||||||
[context]="context"
|
[context]="context"
|
||||||
[query]="query"
|
[query]="query"
|
||||||
|
[hiddenQuery]="hiddenQuery"
|
||||||
[relationshipType]="relationshipType"
|
[relationshipType]="relationshipType"
|
||||||
[isLeft]="isLeft"
|
[isLeft]="isLeft"
|
||||||
[item]="item"
|
[item]="item"
|
||||||
@@ -85,7 +86,7 @@
|
|||||||
<button class="btn btn-primary submit"
|
<button class="btn btn-primary submit"
|
||||||
[disabled]="(toAdd.length === 0 && toRemove.length === 0) || isPending"
|
[disabled]="(toAdd.length === 0 && toRemove.length === 0) || isPending"
|
||||||
(click)="submitEv()">
|
(click)="submitEv()">
|
||||||
<span *ngIf="isPending" class="spinner-border spinner-border-sm" role="status"
|
<span *ngIf="isPending" class="spinner-border spinner-border-sm mr-1" role="status"
|
||||||
aria-hidden="true"></span>
|
aria-hidden="true"></span>
|
||||||
<i *ngIf="!isPending" class="fas fa-save"></i>
|
<i *ngIf="!isPending" class="fas fa-save"></i>
|
||||||
<span class="d-none d-sm-inline"> {{"item.edit.metadata.save-button" | translate}}</span>
|
<span class="d-none d-sm-inline"> {{"item.edit.metadata.save-button" | translate}}</span>
|
||||||
|
@@ -27,7 +27,6 @@ import { RemoteDataBuildService } from '../../../../../core/cache/builders/remot
|
|||||||
import { ExternalSourceDataService } from '../../../../../core/data/external-source-data.service';
|
import { ExternalSourceDataService } from '../../../../../core/data/external-source-data.service';
|
||||||
import { LookupRelationService } from '../../../../../core/data/lookup-relation.service';
|
import { LookupRelationService } from '../../../../../core/data/lookup-relation.service';
|
||||||
import { RelationshipDataService } from '../../../../../core/data/relationship-data.service';
|
import { RelationshipDataService } from '../../../../../core/data/relationship-data.service';
|
||||||
import { RelationshipTypeDataService } from '../../../../../core/data/relationship-type-data.service';
|
|
||||||
import { Collection } from '../../../../../core/shared/collection.model';
|
import { Collection } from '../../../../../core/shared/collection.model';
|
||||||
import { ExternalSource } from '../../../../../core/shared/external-source.model';
|
import { ExternalSource } from '../../../../../core/shared/external-source.model';
|
||||||
import { Item } from '../../../../../core/shared/item.model';
|
import { Item } from '../../../../../core/shared/item.model';
|
||||||
@@ -139,7 +138,6 @@ describe('DsDynamicLookupRelationModalComponent', () => {
|
|||||||
{
|
{
|
||||||
provide: RelationshipDataService, useValue: { getNameVariant: () => observableOf(nameVariant) },
|
provide: RelationshipDataService, useValue: { getNameVariant: () => observableOf(nameVariant) },
|
||||||
},
|
},
|
||||||
{ provide: RelationshipTypeDataService, useValue: {} },
|
|
||||||
{ provide: RemoteDataBuildService, useValue: rdbService },
|
{ provide: RemoteDataBuildService, useValue: rdbService },
|
||||||
{
|
{
|
||||||
provide: Store, useValue: {
|
provide: Store, useValue: {
|
||||||
|
@@ -39,7 +39,6 @@ import { FindListOptions } from '../../../../../core/data/find-list-options.mode
|
|||||||
import { LookupRelationService } from '../../../../../core/data/lookup-relation.service';
|
import { LookupRelationService } from '../../../../../core/data/lookup-relation.service';
|
||||||
import { PaginatedList } from '../../../../../core/data/paginated-list.model';
|
import { PaginatedList } from '../../../../../core/data/paginated-list.model';
|
||||||
import { RelationshipDataService } from '../../../../../core/data/relationship-data.service';
|
import { RelationshipDataService } from '../../../../../core/data/relationship-data.service';
|
||||||
import { RelationshipTypeDataService } from '../../../../../core/data/relationship-type-data.service';
|
|
||||||
import { Context } from '../../../../../core/shared/context.model';
|
import { Context } from '../../../../../core/shared/context.model';
|
||||||
import { DSpaceObject } from '../../../../../core/shared/dspace-object.model';
|
import { DSpaceObject } from '../../../../../core/shared/dspace-object.model';
|
||||||
import { ExternalSource } from '../../../../../core/shared/external-source.model';
|
import { ExternalSource } from '../../../../../core/shared/external-source.model';
|
||||||
@@ -149,6 +148,11 @@ export class DsDynamicLookupRelationModalComponent implements OnInit, OnDestroy
|
|||||||
|
|
||||||
query: string;
|
query: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A hidden query that will be used but not displayed in the url/searchbar
|
||||||
|
*/
|
||||||
|
hiddenQuery: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A map of subscriptions within this component
|
* A map of subscriptions within this component
|
||||||
*/
|
*/
|
||||||
@@ -211,7 +215,6 @@ export class DsDynamicLookupRelationModalComponent implements OnInit, OnDestroy
|
|||||||
public modal: NgbActiveModal,
|
public modal: NgbActiveModal,
|
||||||
private selectableListService: SelectableListService,
|
private selectableListService: SelectableListService,
|
||||||
private relationshipService: RelationshipDataService,
|
private relationshipService: RelationshipDataService,
|
||||||
private relationshipTypeService: RelationshipTypeDataService,
|
|
||||||
private externalSourceService: ExternalSourceDataService,
|
private externalSourceService: ExternalSourceDataService,
|
||||||
private lookupRelationService: LookupRelationService,
|
private lookupRelationService: LookupRelationService,
|
||||||
private searchConfigService: SearchConfigurationService,
|
private searchConfigService: SearchConfigurationService,
|
||||||
@@ -278,6 +281,7 @@ export class DsDynamicLookupRelationModalComponent implements OnInit, OnDestroy
|
|||||||
this.toAdd = [];
|
this.toAdd = [];
|
||||||
this.toRemove = [];
|
this.toRemove = [];
|
||||||
this.modal.close();
|
this.modal.close();
|
||||||
|
this.closeEv();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -372,13 +376,19 @@ export class DsDynamicLookupRelationModalComponent implements OnInit, OnDestroy
|
|||||||
|
|
||||||
/* eslint-disable no-empty,@typescript-eslint/no-empty-function */
|
/* eslint-disable no-empty,@typescript-eslint/no-empty-function */
|
||||||
/**
|
/**
|
||||||
* Called when discard button is clicked, emit discard event to parent to conclude functionality
|
* Called when close button is clicked
|
||||||
|
*/
|
||||||
|
closeEv(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when discard button is clicked
|
||||||
*/
|
*/
|
||||||
discardEv(): void {
|
discardEv(): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when submit button is clicked, emit submit event to parent to conclude functionality
|
* Called when submit button is clicked
|
||||||
*/
|
*/
|
||||||
submitEv(): void {
|
submitEv(): void {
|
||||||
}
|
}
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
[configuration]="this.relationship.searchConfiguration"
|
[configuration]="this.relationship.searchConfiguration"
|
||||||
[context]="context"
|
[context]="context"
|
||||||
[fixedFilterQuery]="this.relationship.filter"
|
[fixedFilterQuery]="this.relationship.filter"
|
||||||
|
[hiddenQuery]="hiddenQuery"
|
||||||
[inPlaceSearch]="true"
|
[inPlaceSearch]="true"
|
||||||
[linkType]="linkTypes.ExternalLink"
|
[linkType]="linkTypes.ExternalLink"
|
||||||
[searchFormPlaceholder]="'submission.sections.describe.relationship-lookup.search-tab.search-form.placeholder'"
|
[searchFormPlaceholder]="'submission.sections.describe.relationship-lookup.search-tab.search-form.placeholder'"
|
||||||
|
@@ -129,6 +129,11 @@ export class DsDynamicLookupRelationSearchTabComponent implements OnInit, OnDest
|
|||||||
*/
|
*/
|
||||||
@Input() isEditRelationship: boolean;
|
@Input() isEditRelationship: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A hidden query that will be used but not displayed in the url/searchbar
|
||||||
|
*/
|
||||||
|
@Input() hiddenQuery: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send an event to deselect an object from the list
|
* Send an event to deselect an object from the list
|
||||||
*/
|
*/
|
||||||
|
@@ -26,7 +26,7 @@ import { DsDynamicLookupRelationSearchTabComponent } from './dynamic-lookup-rela
|
|||||||
})
|
})
|
||||||
export class ThemedDynamicLookupRelationSearchTabComponent extends ThemedComponent<DsDynamicLookupRelationSearchTabComponent> {
|
export class ThemedDynamicLookupRelationSearchTabComponent extends ThemedComponent<DsDynamicLookupRelationSearchTabComponent> {
|
||||||
protected inAndOutputNames: (keyof DsDynamicLookupRelationSearchTabComponent & keyof this)[] = ['relationship', 'listId',
|
protected inAndOutputNames: (keyof DsDynamicLookupRelationSearchTabComponent & keyof this)[] = ['relationship', 'listId',
|
||||||
'query', 'repeatable', 'selection$', 'context', 'relationshipType', 'item', 'isLeft', 'toRemove', 'isEditRelationship',
|
'query', 'hiddenQuery', 'repeatable', 'selection$', 'context', 'relationshipType', 'item', 'isLeft', 'toRemove', 'isEditRelationship',
|
||||||
'deselectObject', 'selectObject', 'resultFound'];
|
'deselectObject', 'selectObject', 'resultFound'];
|
||||||
|
|
||||||
@Input() relationship: RelationshipOptions;
|
@Input() relationship: RelationshipOptions;
|
||||||
@@ -35,6 +35,8 @@ export class ThemedDynamicLookupRelationSearchTabComponent extends ThemedCompone
|
|||||||
|
|
||||||
@Input() query: string;
|
@Input() query: string;
|
||||||
|
|
||||||
|
@Input() hiddenQuery: string;
|
||||||
|
|
||||||
@Input() repeatable: boolean;
|
@Input() repeatable: boolean;
|
||||||
|
|
||||||
@Input() selection$: Observable<ListableObject[]>;
|
@Input() selection$: Observable<ListableObject[]>;
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
Component,
|
Component,
|
||||||
Injector,
|
Injector,
|
||||||
|
Input,
|
||||||
OnDestroy,
|
OnDestroy,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
@@ -37,14 +38,14 @@ export abstract class ClaimedTaskActionsAbstractComponent extends MyDSpaceReload
|
|||||||
/**
|
/**
|
||||||
* The workflow task option the child component represents
|
* The workflow task option the child component represents
|
||||||
*/
|
*/
|
||||||
abstract option: string;
|
@Input() option: string;
|
||||||
|
|
||||||
object: ClaimedTask;
|
object: ClaimedTask;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The item object that belonging to the ClaimedTask object
|
* The item object that belonging to the ClaimedTask object
|
||||||
*/
|
*/
|
||||||
item: Item;
|
@Input() item: Item;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Anchor used to reload the pool task.
|
* Anchor used to reload the pool task.
|
||||||
@@ -56,7 +57,7 @@ export abstract class ClaimedTaskActionsAbstractComponent extends MyDSpaceReload
|
|||||||
/**
|
/**
|
||||||
* The workflowitem object that belonging to the ClaimedTask object
|
* The workflowitem object that belonging to the ClaimedTask object
|
||||||
*/
|
*/
|
||||||
workflowitem: WorkflowItem;
|
@Input() workflowitem: WorkflowItem;
|
||||||
|
|
||||||
protected constructor(protected injector: Injector,
|
protected constructor(protected injector: Injector,
|
||||||
protected router: Router,
|
protected router: Router,
|
||||||
|
@@ -39,10 +39,6 @@ export const WORKFLOW_TASK_OPTION_APPROVE = 'submit_approve';
|
|||||||
* Component for displaying and processing the approve action on a workflow task item
|
* Component for displaying and processing the approve action on a workflow task item
|
||||||
*/
|
*/
|
||||||
export class ClaimedTaskActionsApproveComponent extends ClaimedTaskActionsAbstractComponent {
|
export class ClaimedTaskActionsApproveComponent extends ClaimedTaskActionsAbstractComponent {
|
||||||
/**
|
|
||||||
* This component represents the approve option
|
|
||||||
*/
|
|
||||||
option = WORKFLOW_TASK_OPTION_APPROVE;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected injector: Injector,
|
protected injector: Injector,
|
||||||
|
@@ -39,8 +39,6 @@ export const WORKFLOW_TASK_OPTION_DECLINE_TASK = 'submit_decline_task';
|
|||||||
*/
|
*/
|
||||||
export class ClaimedTaskActionsDeclineTaskComponent extends ClaimedTaskActionsAbstractComponent {
|
export class ClaimedTaskActionsDeclineTaskComponent extends ClaimedTaskActionsAbstractComponent {
|
||||||
|
|
||||||
option = WORKFLOW_TASK_OPTION_DECLINE_TASK;
|
|
||||||
|
|
||||||
constructor(protected injector: Injector,
|
constructor(protected injector: Injector,
|
||||||
protected router: Router,
|
protected router: Router,
|
||||||
protected notificationsService: NotificationsService,
|
protected notificationsService: NotificationsService,
|
||||||
|
@@ -34,10 +34,6 @@ export const WORKFLOW_TASK_OPTION_EDIT_METADATA = 'submit_edit_metadata';
|
|||||||
* Component for displaying the edit metadata action on a workflow task item
|
* Component for displaying the edit metadata action on a workflow task item
|
||||||
*/
|
*/
|
||||||
export class ClaimedTaskActionsEditMetadataComponent extends ClaimedTaskActionsAbstractComponent {
|
export class ClaimedTaskActionsEditMetadataComponent extends ClaimedTaskActionsAbstractComponent {
|
||||||
/**
|
|
||||||
* This component represents the edit metadata option
|
|
||||||
*/
|
|
||||||
option = WORKFLOW_TASK_OPTION_EDIT_METADATA;
|
|
||||||
|
|
||||||
constructor(protected injector: Injector,
|
constructor(protected injector: Injector,
|
||||||
protected router: Router,
|
protected router: Router,
|
||||||
|
@@ -14,10 +14,7 @@ import {
|
|||||||
|
|
||||||
import { RequestService } from '../../../../core/data/request.service';
|
import { RequestService } from '../../../../core/data/request.service';
|
||||||
import { SearchService } from '../../../../core/shared/search/search.service';
|
import { SearchService } from '../../../../core/shared/search/search.service';
|
||||||
import {
|
import { ADVANCED_WORKFLOW_ACTION_RATING } from '../../../../workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component';
|
||||||
ADVANCED_WORKFLOW_ACTION_RATING,
|
|
||||||
ADVANCED_WORKFLOW_TASK_OPTION_RATING,
|
|
||||||
} from '../../../../workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component';
|
|
||||||
import { NotificationsService } from '../../../notifications/notifications.service';
|
import { NotificationsService } from '../../../notifications/notifications.service';
|
||||||
import { AdvancedClaimedTaskActionsAbstractComponent } from '../abstract/advanced-claimed-task-actions-abstract.component';
|
import { AdvancedClaimedTaskActionsAbstractComponent } from '../abstract/advanced-claimed-task-actions-abstract.component';
|
||||||
|
|
||||||
@@ -33,11 +30,6 @@ import { AdvancedClaimedTaskActionsAbstractComponent } from '../abstract/advance
|
|||||||
})
|
})
|
||||||
export class AdvancedClaimedTaskActionRatingComponent extends AdvancedClaimedTaskActionsAbstractComponent {
|
export class AdvancedClaimedTaskActionRatingComponent extends AdvancedClaimedTaskActionsAbstractComponent {
|
||||||
|
|
||||||
/**
|
|
||||||
* This component represents the advanced select option
|
|
||||||
*/
|
|
||||||
option = ADVANCED_WORKFLOW_TASK_OPTION_RATING;
|
|
||||||
|
|
||||||
workflowType = ADVANCED_WORKFLOW_ACTION_RATING;
|
workflowType = ADVANCED_WORKFLOW_ACTION_RATING;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@@ -50,10 +50,6 @@ export const WORKFLOW_TASK_OPTION_REJECT = 'submit_reject';
|
|||||||
* Component for displaying and processing the reject action on a workflow task item
|
* Component for displaying and processing the reject action on a workflow task item
|
||||||
*/
|
*/
|
||||||
export class ClaimedTaskActionsRejectComponent extends ClaimedTaskActionsAbstractComponent implements OnInit {
|
export class ClaimedTaskActionsRejectComponent extends ClaimedTaskActionsAbstractComponent implements OnInit {
|
||||||
/**
|
|
||||||
* This component represents the reject option
|
|
||||||
*/
|
|
||||||
option = WORKFLOW_TASK_OPTION_REJECT;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The reject form group
|
* The reject form group
|
||||||
|
@@ -36,10 +36,6 @@ export const WORKFLOW_TASK_OPTION_RETURN_TO_POOL = 'return_to_pool';
|
|||||||
* Component for displaying and processing the return to pool action on a workflow task item
|
* Component for displaying and processing the return to pool action on a workflow task item
|
||||||
*/
|
*/
|
||||||
export class ClaimedTaskActionsReturnToPoolComponent extends ClaimedTaskActionsAbstractComponent {
|
export class ClaimedTaskActionsReturnToPoolComponent extends ClaimedTaskActionsAbstractComponent {
|
||||||
/**
|
|
||||||
* This component represents the return to pool option
|
|
||||||
*/
|
|
||||||
option = WORKFLOW_TASK_OPTION_RETURN_TO_POOL;
|
|
||||||
|
|
||||||
constructor(protected injector: Injector,
|
constructor(protected injector: Injector,
|
||||||
protected router: Router,
|
protected router: Router,
|
||||||
|
@@ -14,10 +14,7 @@ import {
|
|||||||
|
|
||||||
import { RequestService } from '../../../../core/data/request.service';
|
import { RequestService } from '../../../../core/data/request.service';
|
||||||
import { SearchService } from '../../../../core/shared/search/search.service';
|
import { SearchService } from '../../../../core/shared/search/search.service';
|
||||||
import {
|
import { ADVANCED_WORKFLOW_ACTION_SELECT_REVIEWER } from '../../../../workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component';
|
||||||
ADVANCED_WORKFLOW_ACTION_SELECT_REVIEWER,
|
|
||||||
ADVANCED_WORKFLOW_TASK_OPTION_SELECT_REVIEWER,
|
|
||||||
} from '../../../../workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component';
|
|
||||||
import { NotificationsService } from '../../../notifications/notifications.service';
|
import { NotificationsService } from '../../../notifications/notifications.service';
|
||||||
import { AdvancedClaimedTaskActionsAbstractComponent } from '../abstract/advanced-claimed-task-actions-abstract.component';
|
import { AdvancedClaimedTaskActionsAbstractComponent } from '../abstract/advanced-claimed-task-actions-abstract.component';
|
||||||
|
|
||||||
@@ -33,11 +30,6 @@ import { AdvancedClaimedTaskActionsAbstractComponent } from '../abstract/advance
|
|||||||
})
|
})
|
||||||
export class AdvancedClaimedTaskActionSelectReviewerComponent extends AdvancedClaimedTaskActionsAbstractComponent {
|
export class AdvancedClaimedTaskActionSelectReviewerComponent extends AdvancedClaimedTaskActionsAbstractComponent {
|
||||||
|
|
||||||
/**
|
|
||||||
* This component represents the advanced select option
|
|
||||||
*/
|
|
||||||
option = ADVANCED_WORKFLOW_TASK_OPTION_SELECT_REVIEWER;
|
|
||||||
|
|
||||||
workflowType = ADVANCED_WORKFLOW_ACTION_SELECT_REVIEWER;
|
workflowType = ADVANCED_WORKFLOW_ACTION_SELECT_REVIEWER;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@@ -1,8 +1,10 @@
|
|||||||
import {
|
import {
|
||||||
|
ADVANCED_WORKFLOW_ACTION_RATING,
|
||||||
ADVANCED_WORKFLOW_TASK_OPTION_RATING,
|
ADVANCED_WORKFLOW_TASK_OPTION_RATING,
|
||||||
AdvancedWorkflowActionRatingComponent,
|
AdvancedWorkflowActionRatingComponent,
|
||||||
} from '../../../../workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component';
|
} from '../../../../workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component';
|
||||||
import {
|
import {
|
||||||
|
ADVANCED_WORKFLOW_ACTION_SELECT_REVIEWER,
|
||||||
ADVANCED_WORKFLOW_TASK_OPTION_SELECT_REVIEWER,
|
ADVANCED_WORKFLOW_TASK_OPTION_SELECT_REVIEWER,
|
||||||
AdvancedWorkflowActionSelectReviewerComponent,
|
AdvancedWorkflowActionSelectReviewerComponent,
|
||||||
} from '../../../../workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component';
|
} from '../../../../workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component';
|
||||||
@@ -54,8 +56,8 @@ export const WORKFLOW_TASK_OPTION_DECORATOR_MAP = new Map<string, WorkflowTaskOp
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
export const ADVANCED_WORKFLOW_TASK_OPTION_DECORATOR_MAP = new Map<string, AdvancedWorkflowTaskOptionComponent>([
|
export const ADVANCED_WORKFLOW_TASK_OPTION_DECORATOR_MAP = new Map<string, AdvancedWorkflowTaskOptionComponent>([
|
||||||
[ADVANCED_WORKFLOW_TASK_OPTION_RATING, AdvancedWorkflowActionRatingComponent],
|
[ADVANCED_WORKFLOW_ACTION_RATING, AdvancedWorkflowActionRatingComponent],
|
||||||
[ADVANCED_WORKFLOW_TASK_OPTION_SELECT_REVIEWER, AdvancedWorkflowActionSelectReviewerComponent],
|
[ADVANCED_WORKFLOW_ACTION_SELECT_REVIEWER, AdvancedWorkflowActionSelectReviewerComponent],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -138,6 +138,7 @@ const searchServiceStub = jasmine.createSpyObj('SearchService', {
|
|||||||
trackSearch: {},
|
trackSearch: {},
|
||||||
}) as SearchService;
|
}) as SearchService;
|
||||||
const queryParam = 'test query';
|
const queryParam = 'test query';
|
||||||
|
const hiddenQuery = 'hidden query';
|
||||||
const scopeParam = '7669c72a-3f2a-451f-a3b9-9210e7a4c02f';
|
const scopeParam = '7669c72a-3f2a-451f-a3b9-9210e7a4c02f';
|
||||||
|
|
||||||
const defaultSearchOptions = new PaginatedSearchOptions({ pagination });
|
const defaultSearchOptions = new PaginatedSearchOptions({ pagination });
|
||||||
@@ -279,6 +280,7 @@ describe('SearchComponent', () => {
|
|||||||
comp = fixture.componentInstance; // SearchComponent test instance
|
comp = fixture.componentInstance; // SearchComponent test instance
|
||||||
comp.inPlaceSearch = false;
|
comp.inPlaceSearch = false;
|
||||||
comp.paginationId = paginationId;
|
comp.paginationId = paginationId;
|
||||||
|
comp.hiddenQuery = hiddenQuery;
|
||||||
|
|
||||||
spyOn((comp as any), 'getSearchOptions').and.returnValue(paginatedSearchOptions$.asObservable());
|
spyOn((comp as any), 'getSearchOptions').and.returnValue(paginatedSearchOptions$.asObservable());
|
||||||
});
|
});
|
||||||
|
@@ -130,6 +130,11 @@ export class SearchComponent implements OnDestroy, OnInit {
|
|||||||
*/
|
*/
|
||||||
@Input() fixedFilterQuery: string;
|
@Input() fixedFilterQuery: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A hidden query that will be used but not displayed in the url/searchbar
|
||||||
|
*/
|
||||||
|
@Input() hiddenQuery: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If this is true, the request will only be sent if there's
|
* If this is true, the request will only be sent if there's
|
||||||
* no valid cached version. Defaults to true
|
* no valid cached version. Defaults to true
|
||||||
@@ -513,8 +518,18 @@ export class SearchComponent implements OnDestroy, OnInit {
|
|||||||
if (this.configuration === 'supervision') {
|
if (this.configuration === 'supervision') {
|
||||||
followLinks.push(followLink<WorkspaceItem>('supervisionOrders', { isOptional: true }) as any);
|
followLinks.push(followLink<WorkspaceItem>('supervisionOrders', { isOptional: true }) as any);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const searchOptionsWithHidden = Object.assign (new PaginatedSearchOptions({}), searchOptions);
|
||||||
|
if (isNotEmpty(this.hiddenQuery)) {
|
||||||
|
if (isNotEmpty(searchOptionsWithHidden.query)) {
|
||||||
|
searchOptionsWithHidden.query = searchOptionsWithHidden.query + ' AND ' + this.hiddenQuery;
|
||||||
|
} else {
|
||||||
|
searchOptionsWithHidden.query = this.hiddenQuery;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.service.search(
|
this.service.search(
|
||||||
searchOptions,
|
searchOptionsWithHidden,
|
||||||
undefined,
|
undefined,
|
||||||
this.useCachedVersionIfAvailable,
|
this.useCachedVersionIfAvailable,
|
||||||
true,
|
true,
|
||||||
@@ -523,7 +538,7 @@ export class SearchComponent implements OnDestroy, OnInit {
|
|||||||
.subscribe((results: RemoteData<SearchObjects<DSpaceObject>>) => {
|
.subscribe((results: RemoteData<SearchObjects<DSpaceObject>>) => {
|
||||||
if (results.hasSucceeded) {
|
if (results.hasSucceeded) {
|
||||||
if (this.trackStatistics) {
|
if (this.trackStatistics) {
|
||||||
this.service.trackSearch(searchOptions, results.payload);
|
this.service.trackSearch(searchOptionsWithHidden, results.payload);
|
||||||
}
|
}
|
||||||
if (results.payload?.page?.length > 0) {
|
if (results.payload?.page?.length > 0) {
|
||||||
this.resultFound.emit(results.payload);
|
this.resultFound.emit(results.payload);
|
||||||
|
@@ -32,6 +32,7 @@ export class ThemedSearchComponent extends ThemedComponent<SearchComponent> {
|
|||||||
'context',
|
'context',
|
||||||
'configuration',
|
'configuration',
|
||||||
'fixedFilterQuery',
|
'fixedFilterQuery',
|
||||||
|
'hiddenQuery',
|
||||||
'useCachedVersionIfAvailable',
|
'useCachedVersionIfAvailable',
|
||||||
'inPlaceSearch',
|
'inPlaceSearch',
|
||||||
'linkType',
|
'linkType',
|
||||||
@@ -65,6 +66,8 @@ export class ThemedSearchComponent extends ThemedComponent<SearchComponent> {
|
|||||||
|
|
||||||
@Input() fixedFilterQuery: string;
|
@Input() fixedFilterQuery: string;
|
||||||
|
|
||||||
|
@Input() hiddenQuery: string;
|
||||||
|
|
||||||
@Input() useCachedVersionIfAvailable: boolean;
|
@Input() useCachedVersionIfAvailable: boolean;
|
||||||
|
|
||||||
@Input() inPlaceSearch: boolean;
|
@Input() inPlaceSearch: boolean;
|
||||||
|
@@ -224,6 +224,38 @@ describe('ReviewersListComponent', () => {
|
|||||||
})).not.toBeTruthy();
|
})).not.toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should replace the value when a new member is added when multipleReviewers is false', () => {
|
||||||
|
spyOn(component.selectedReviewersUpdated, 'emit');
|
||||||
|
component.multipleReviewers = false;
|
||||||
|
component.selectedReviewers = [EPersonMock];
|
||||||
|
|
||||||
|
component.addMemberToGroup(EPersonMock2);
|
||||||
|
|
||||||
|
expect(component.selectedReviewers).toEqual([EPersonMock2]);
|
||||||
|
expect(component.selectedReviewersUpdated.emit).toHaveBeenCalledWith([EPersonMock2]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add the value when a new member is added when multipleReviewers is true', () => {
|
||||||
|
spyOn(component.selectedReviewersUpdated, 'emit');
|
||||||
|
component.multipleReviewers = true;
|
||||||
|
component.selectedReviewers = [EPersonMock];
|
||||||
|
|
||||||
|
component.addMemberToGroup(EPersonMock2);
|
||||||
|
|
||||||
|
expect(component.selectedReviewers).toEqual([EPersonMock, EPersonMock2]);
|
||||||
|
expect(component.selectedReviewersUpdated.emit).toHaveBeenCalledWith([EPersonMock, EPersonMock2]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should delete the member when present', () => {
|
||||||
|
spyOn(component.selectedReviewersUpdated, 'emit');
|
||||||
|
component.selectedReviewers = [EPersonMock];
|
||||||
|
|
||||||
|
component.deleteMemberFromGroup(EPersonMock);
|
||||||
|
|
||||||
|
expect(component.selectedReviewers).toEqual([]);
|
||||||
|
expect(component.selectedReviewersUpdated.emit).toHaveBeenCalledWith([]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when a group is selected', () => {
|
describe('when a group is selected', () => {
|
||||||
@@ -245,37 +277,4 @@ describe('ReviewersListComponent', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should replace the value when a new member is added when multipleReviewers is false', () => {
|
|
||||||
spyOn(component.selectedReviewersUpdated, 'emit');
|
|
||||||
component.multipleReviewers = false;
|
|
||||||
component.selectedReviewers = [EPersonMock];
|
|
||||||
|
|
||||||
component.addMemberToGroup(EPersonMock2);
|
|
||||||
|
|
||||||
expect(component.selectedReviewers).toEqual([EPersonMock2]);
|
|
||||||
expect(component.selectedReviewersUpdated.emit).toHaveBeenCalledWith([EPersonMock2]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should add the value when a new member is added when multipleReviewers is true', () => {
|
|
||||||
spyOn(component.selectedReviewersUpdated, 'emit');
|
|
||||||
component.multipleReviewers = true;
|
|
||||||
component.selectedReviewers = [EPersonMock];
|
|
||||||
|
|
||||||
component.addMemberToGroup(EPersonMock2);
|
|
||||||
|
|
||||||
expect(component.selectedReviewers).toEqual([EPersonMock, EPersonMock2]);
|
|
||||||
expect(component.selectedReviewersUpdated.emit).toHaveBeenCalledWith([EPersonMock, EPersonMock2]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should delete the member when present', () => {
|
|
||||||
spyOn(component.selectedReviewersUpdated, 'emit');
|
|
||||||
component.selectedReviewers = [EPersonMock];
|
|
||||||
|
|
||||||
component.deleteMemberFromGroup(EPersonMock);
|
|
||||||
|
|
||||||
expect(component.selectedReviewers).toEqual([]);
|
|
||||||
expect(component.selectedReviewersUpdated.emit).toHaveBeenCalledWith([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@@ -26,6 +26,10 @@ import {
|
|||||||
TranslateModule,
|
TranslateModule,
|
||||||
TranslateService,
|
TranslateService,
|
||||||
} from '@ngx-translate/core';
|
} from '@ngx-translate/core';
|
||||||
|
import {
|
||||||
|
Observable,
|
||||||
|
of as observableOf,
|
||||||
|
} from 'rxjs';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
EPersonListActionConfig,
|
EPersonListActionConfig,
|
||||||
@@ -36,10 +40,12 @@ import { PaginatedList } from '../../../../core/data/paginated-list.model';
|
|||||||
import { EPersonDataService } from '../../../../core/eperson/eperson-data.service';
|
import { EPersonDataService } from '../../../../core/eperson/eperson-data.service';
|
||||||
import { GroupDataService } from '../../../../core/eperson/group-data.service';
|
import { GroupDataService } from '../../../../core/eperson/group-data.service';
|
||||||
import { EPerson } from '../../../../core/eperson/models/eperson.model';
|
import { EPerson } from '../../../../core/eperson/models/eperson.model';
|
||||||
|
import { EpersonDtoModel } from '../../../../core/eperson/models/eperson-dto.model';
|
||||||
import { Group } from '../../../../core/eperson/models/group.model';
|
import { Group } from '../../../../core/eperson/models/group.model';
|
||||||
import { PaginationService } from '../../../../core/pagination/pagination.service';
|
import { PaginationService } from '../../../../core/pagination/pagination.service';
|
||||||
import { getFirstSucceededRemoteDataPayload } from '../../../../core/shared/operators';
|
import { getFirstSucceededRemoteDataPayload } from '../../../../core/shared/operators';
|
||||||
import { ContextHelpDirective } from '../../../../shared/context-help.directive';
|
import { ContextHelpDirective } from '../../../../shared/context-help.directive';
|
||||||
|
import { hasValue } from '../../../../shared/empty.util';
|
||||||
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
|
||||||
import { PaginationComponent } from '../../../../shared/pagination/pagination.component';
|
import { PaginationComponent } from '../../../../shared/pagination/pagination.component';
|
||||||
|
|
||||||
@@ -101,7 +107,7 @@ export class ReviewersListComponent extends MembersListComponent implements OnIn
|
|||||||
super(groupService, ePersonDataService, translateService, notificationsService, formBuilder, paginationService, router, dsoNameService);
|
super(groupService, ePersonDataService, translateService, notificationsService, formBuilder, paginationService, router, dsoNameService);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
override ngOnInit(): void {
|
||||||
this.searchForm = this.formBuilder.group(({
|
this.searchForm = this.formBuilder.group(({
|
||||||
scope: 'metadata',
|
scope: 'metadata',
|
||||||
query: '',
|
query: '',
|
||||||
@@ -114,6 +120,7 @@ export class ReviewersListComponent extends MembersListComponent implements OnIn
|
|||||||
if (this.groupId === null) {
|
if (this.groupId === null) {
|
||||||
this.retrieveMembers(this.config.currentPage);
|
this.retrieveMembers(this.config.currentPage);
|
||||||
} else {
|
} else {
|
||||||
|
this.unsubFrom(SubKey.ActiveGroup);
|
||||||
this.subs.set(SubKey.ActiveGroup, this.groupService.findById(this.groupId).pipe(
|
this.subs.set(SubKey.ActiveGroup, this.groupService.findById(this.groupId).pipe(
|
||||||
getFirstSucceededRemoteDataPayload(),
|
getFirstSucceededRemoteDataPayload(),
|
||||||
).subscribe((activeGroup: Group) => {
|
).subscribe((activeGroup: Group) => {
|
||||||
@@ -136,25 +143,37 @@ export class ReviewersListComponent extends MembersListComponent implements OnIn
|
|||||||
retrieveMembers(page: number): void {
|
retrieveMembers(page: number): void {
|
||||||
this.config.currentPage = page;
|
this.config.currentPage = page;
|
||||||
if (this.groupId === null) {
|
if (this.groupId === null) {
|
||||||
this.unsubFrom(SubKey.Members);
|
const paginatedListOfEPersons: PaginatedList<EpersonDtoModel> = new PaginatedList();
|
||||||
const paginatedListOfEPersons: PaginatedList<EPerson> = new PaginatedList();
|
paginatedListOfEPersons.page = this.selectedReviewers.map((ePerson: EPerson) => Object.assign(new EpersonDtoModel(), {
|
||||||
paginatedListOfEPersons.page = this.selectedReviewers;
|
eperson: ePerson,
|
||||||
|
ableToDelete: this.isMemberOfGroup(ePerson),
|
||||||
|
}));
|
||||||
this.ePeopleMembersOfGroup.next(paginatedListOfEPersons);
|
this.ePeopleMembersOfGroup.next(paginatedListOfEPersons);
|
||||||
} else {
|
} else {
|
||||||
super.retrieveMembers(page);
|
super.retrieveMembers(page);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the given {@link possibleMember} is part of the {@link selectedReviewers}.
|
||||||
|
*
|
||||||
|
* @param possibleMember The {@link EPerson} that needs to be checked
|
||||||
|
*/
|
||||||
|
isMemberOfGroup(possibleMember: EPerson): Observable<boolean> {
|
||||||
|
return observableOf(hasValue(this.selectedReviewers.find((reviewer: EPerson) => reviewer.id === possibleMember.id)));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the {@link eperson} from the {@link selectedReviewers}
|
* Removes the {@link eperson} from the {@link selectedReviewers}
|
||||||
*
|
*
|
||||||
* @param eperson The {@link EPerson} to remove
|
* @param eperson The {@link EPerson} to remove
|
||||||
*/
|
*/
|
||||||
deleteMemberFromGroup(eperson: EPerson) {
|
deleteMemberFromGroup(eperson: EPerson) {
|
||||||
const index = this.selectedReviewers.indexOf(eperson);
|
const index = this.selectedReviewers.findIndex((reviewer: EPerson) => reviewer.id === eperson.id);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
this.selectedReviewers.splice(index, 1);
|
this.selectedReviewers.splice(index, 1);
|
||||||
}
|
}
|
||||||
|
this.retrieveMembers(this.config.currentPage);
|
||||||
this.selectedReviewersUpdated.emit(this.selectedReviewers);
|
this.selectedReviewersUpdated.emit(this.selectedReviewers);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,6 +188,7 @@ export class ReviewersListComponent extends MembersListComponent implements OnIn
|
|||||||
this.selectedReviewers = [];
|
this.selectedReviewers = [];
|
||||||
}
|
}
|
||||||
this.selectedReviewers.push(eperson);
|
this.selectedReviewers.push(eperson);
|
||||||
|
this.retrieveMembers(this.config.currentPage);
|
||||||
this.selectedReviewersUpdated.emit(this.selectedReviewers);
|
this.selectedReviewersUpdated.emit(this.selectedReviewers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { Location } from '@angular/common';
|
import { Location } from '@angular/common';
|
||||||
import {
|
import {
|
||||||
Directive,
|
Directive,
|
||||||
|
Input,
|
||||||
OnInit,
|
OnInit,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import {
|
import {
|
||||||
@@ -42,7 +43,9 @@ import { NotificationsService } from '../shared/notifications/notifications.serv
|
|||||||
standalone: true,
|
standalone: true,
|
||||||
})
|
})
|
||||||
export abstract class WorkflowItemActionPageDirective implements OnInit {
|
export abstract class WorkflowItemActionPageDirective implements OnInit {
|
||||||
public type;
|
|
||||||
|
@Input() type: string;
|
||||||
|
|
||||||
public wfi$: Observable<WorkflowItem>;
|
public wfi$: Observable<WorkflowItem>;
|
||||||
public item$: Observable<Item>;
|
public item$: Observable<Item>;
|
||||||
protected previousQueryParameters?: Params;
|
protected previousQueryParameters?: Params;
|
||||||
|
11233
src/assets/i18n/ar.json5
11233
src/assets/i18n/ar.json5
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user