mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
[CST-12109] request reinstate for normal users
This commit is contained in:
@@ -6,7 +6,10 @@
|
||||
<ds-alert [type]="AlertTypeEnum.Warning">
|
||||
<div class="d-flex justify-content-between flex-wrap">
|
||||
<span class="align-self-center">{{'item.alerts.withdrawn' | translate}}</span>
|
||||
<a routerLink="/home" class="btn btn-primary btn-sm">{{"404.link.home-page" | translate}}</a>
|
||||
<div class="gap-2 d-flex">
|
||||
<a routerLink="/home" class="btn btn-primary btn-sm">{{"404.link.home-page" | translate}}</a>
|
||||
<a *ngIf="showReinstateButton$() | async" class="btn btn-primary btn-sm" (click)="openReinstateModal()">{{ 'item.alerts.reinstate-request' | translate}}</a>
|
||||
</div>
|
||||
</div>
|
||||
</ds-alert>
|
||||
</div>
|
||||
|
@@ -4,16 +4,37 @@ import { TranslateModule } from '@ngx-translate/core';
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { Item } from '../../core/shared/item.model';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
|
||||
import { of } from 'rxjs';
|
||||
import { DsoWithdrawnReinstateModalService } from '../../shared/dso-page/dso-withdrawn-reinstate-service/dso-withdrawn-reinstate-modal.service';
|
||||
import { CorrectionTypeDataService } from '../../core/submission/correctiontype-data.service';
|
||||
|
||||
describe('ItemAlertsComponent', () => {
|
||||
let component: ItemAlertsComponent;
|
||||
let fixture: ComponentFixture<ItemAlertsComponent>;
|
||||
let item: Item;
|
||||
let authorizationService;
|
||||
let dsoWithdrawnReinstateModalService;
|
||||
let correctionTypeDataService;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
authorizationService = jasmine.createSpyObj('authorizationService', {
|
||||
isAuthorized: of(true)
|
||||
});
|
||||
dsoWithdrawnReinstateModalService = jasmine.createSpyObj('dsoWithdrawnReinstateModalService', {
|
||||
openCreateWithdrawnReinstateModal: {}
|
||||
});
|
||||
correctionTypeDataService = jasmine.createSpyObj('correctionTypeDataService', {
|
||||
findByItem: of({})
|
||||
});
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ItemAlertsComponent],
|
||||
imports: [TranslateModule.forRoot()],
|
||||
providers: [
|
||||
{ provide: AuthorizationDataService, useValue: authorizationService },
|
||||
{ provide: DsoWithdrawnReinstateModalService, useValue: dsoWithdrawnReinstateModalService },
|
||||
{ provide: CorrectionTypeDataService, useValue: correctionTypeDataService }
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
})
|
||||
.compileComponents();
|
||||
|
@@ -1,6 +1,12 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { Item } from '../../core/shared/item.model';
|
||||
import { AlertType } from '../../shared/alert/alert-type';
|
||||
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
|
||||
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
|
||||
import { Observable, combineLatest, map } from 'rxjs';
|
||||
import { DsoWithdrawnReinstateModalService, REQUEST_REINSTATE } from '../../shared/dso-page/dso-withdrawn-reinstate-service/dso-withdrawn-reinstate-modal.service';
|
||||
import { CorrectionTypeDataService } from '../../core/submission/correctiontype-data.service';
|
||||
import { getFirstCompletedRemoteData, getPaginatedListPayload, getRemoteDataPayload } from 'src/app/core/shared/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-item-alerts',
|
||||
@@ -21,4 +27,37 @@ export class ItemAlertsComponent {
|
||||
* @type {AlertType}
|
||||
*/
|
||||
public AlertTypeEnum = AlertType;
|
||||
|
||||
constructor(
|
||||
private authService: AuthorizationDataService,
|
||||
private dsoWithdrawnReinstateModalService: DsoWithdrawnReinstateModalService,
|
||||
private correctionTypeDataService: CorrectionTypeDataService
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether to show the reinstate button.
|
||||
* The button is shown if the user is not an admin and the item has a reinstate request.
|
||||
* @returns An Observable that emits a boolean value indicating whether to show the reinstate button.
|
||||
*/
|
||||
showReinstateButton$(): Observable<boolean> {
|
||||
const correction$ = this.correctionTypeDataService.findByItem(this.item.uuid, true).pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
getRemoteDataPayload(),
|
||||
getPaginatedListPayload()
|
||||
);
|
||||
const isAdmin$ = this.authService.isAuthorized(FeatureID.AdministratorOf);
|
||||
return combineLatest([isAdmin$, correction$]).pipe(
|
||||
map(([isAdmin, correction]) => {
|
||||
return !isAdmin && correction.some((correctionType) => correctionType.topic === REQUEST_REINSTATE);
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the reinstate modal for the item.
|
||||
*/
|
||||
openReinstateModal() {
|
||||
this.dsoWithdrawnReinstateModalService.openCreateWithdrawnReinstateModal(this.item, 'request-reinstate', this.item.isArchived);
|
||||
}
|
||||
}
|
||||
|
@@ -26,6 +26,7 @@ import { QualityAssuranceSourceService } from './qa/source/quality-assurance-sou
|
||||
import {
|
||||
QualityAssuranceSourceDataService
|
||||
} from '../core/notifications/qa/source/quality-assurance-source-data.service';
|
||||
import { GetEPersonDataPipe } from './qa/events/get-ePerson-data.pipe';
|
||||
|
||||
const MODULES = [
|
||||
CommonModule,
|
||||
@@ -55,7 +56,11 @@ const PROVIDERS = [
|
||||
QualityAssuranceSourceService,
|
||||
QualityAssuranceTopicDataService,
|
||||
QualityAssuranceSourceDataService,
|
||||
QualityAssuranceEventDataService
|
||||
QualityAssuranceEventDataService,
|
||||
];
|
||||
|
||||
const PIPES = [
|
||||
GetEPersonDataPipe
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
@@ -65,7 +70,8 @@ const PROVIDERS = [
|
||||
declarations: [
|
||||
...COMPONENTS,
|
||||
...DIRECTIVES,
|
||||
...ENTRY_COMPONENTS
|
||||
...ENTRY_COMPONENTS,
|
||||
...PIPES
|
||||
],
|
||||
providers: [
|
||||
...PROVIDERS
|
||||
@@ -75,7 +81,8 @@ const PROVIDERS = [
|
||||
],
|
||||
exports: [
|
||||
...COMPONENTS,
|
||||
...DIRECTIVES
|
||||
...DIRECTIVES,
|
||||
...PIPES
|
||||
]
|
||||
})
|
||||
|
||||
|
26
src/app/notifications/qa/events/get-ePerson-data.pipe.ts
Normal file
26
src/app/notifications/qa/events/get-ePerson-data.pipe.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import { Observable, tap } from 'rxjs';
|
||||
import { EPersonDataService } from '../../../core/eperson/eperson-data.service';
|
||||
import { EPerson } from '../../../core/eperson/models/eperson.model';
|
||||
import { getFirstCompletedRemoteData, getRemoteDataPayload } from '../../../core/shared/operators';
|
||||
|
||||
@Pipe({
|
||||
name: 'dsGetEPersonData'
|
||||
})
|
||||
export class GetEPersonDataPipe implements PipeTransform {
|
||||
constructor(private ePersonDataService: EPersonDataService) { }
|
||||
|
||||
/**
|
||||
* Transforms the personId into an Observable of EPerson.
|
||||
* @param personId The ID of the person.
|
||||
* @returns An Observable of EPerson.
|
||||
*/
|
||||
transform(personId: string): Observable<EPerson> {
|
||||
return this.ePersonDataService.findById(personId, true).pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
getRemoteDataPayload(),
|
||||
tap((ePerson: EPerson) => {console.log(ePerson)})
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@@ -36,9 +36,14 @@
|
||||
<th *ngIf="hasDetailColumn() && showTopic.indexOf('/PROJECT') !== -1" scope="col" class="content-col">
|
||||
{{'quality-assurance.event.table.project-details' | translate}}
|
||||
</th>
|
||||
<th *ngIf="hasDetailColumn() && (showTopic.indexOf('/REINSTATE') !== -1 || showTopic.indexOf('/WITHDRAWN') !== -1)" scope="col" class="content-col">
|
||||
{{'quality-assurance.event.table.reasons' | translate}}
|
||||
</th>
|
||||
<ng-container *ngIf="hasDetailColumn() && (showTopic.indexOf('/REINSTATE') !== -1 || showTopic.indexOf('/WITHDRAWN') !== -1)">
|
||||
<th scope="col" class="content-col">
|
||||
{{'quality-assurance.event.table.reasons' | translate}}
|
||||
</th>
|
||||
<th scope="col" class="content-col">
|
||||
{{'quality-assurance.event.table.person-who-requested' | translate}}
|
||||
</th>
|
||||
</ng-container>
|
||||
<th scope="col" class="button-col">{{'quality-assurance.event.table.actions' | translate}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -76,14 +81,24 @@
|
||||
{{ (showMore ? 'quality-assurance.event.table.less': 'quality-assurance.event.table.more') | translate }}
|
||||
</button>
|
||||
</td>
|
||||
|
||||
<td *ngIf="showTopic.indexOf('/REINSTATE') !== -1 || showTopic.indexOf('/WITHDRAWN') !== -1">
|
||||
<p>
|
||||
<span *ngIf="eventElement.event.message">
|
||||
<span class="badge badge-info">{{eventElement.event.message.reason}}</span><br>
|
||||
</span>
|
||||
</p>
|
||||
</td>
|
||||
<ng-container *ngIf="showTopic.indexOf('/REINSTATE') !== -1 || showTopic.indexOf('/WITHDRAWN') !== -1">
|
||||
<td>
|
||||
<p>
|
||||
<span *ngIf="eventElement.event.message">
|
||||
<span class="badge badge-info">{{eventElement.event.message.reason}}</span><br>
|
||||
</span>
|
||||
</p>
|
||||
</td>
|
||||
<td>
|
||||
<p>
|
||||
<span *ngIf="eventElement.event.originalId">
|
||||
<a target="_blank" [routerLink]="getEntityPageRoute(eventElement.event.originalId)">
|
||||
{{ (eventElement.event.originalId | dsGetEPersonData | async)?.name }}
|
||||
</a>
|
||||
</span>
|
||||
</p>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<td *ngIf="showTopic.indexOf('/PROJECT') !== -1">
|
||||
<p>
|
||||
|
@@ -34,6 +34,7 @@ import { AuthorizationDataService } from '../../../core/data/feature-authorizati
|
||||
import { FeatureID } from '../../../core/data/feature-authorization/feature-id';
|
||||
import { NoContent } from '../../../core/shared/NoContent.model';
|
||||
import {environment} from '../../../../environments/environment';
|
||||
import { getEntityPageRoute } from 'src/app/item-page/item-page-routing-paths';
|
||||
|
||||
/**
|
||||
* Component to display the Quality Assurance event list.
|
||||
@@ -458,4 +459,8 @@ export class QualityAssuranceEventsComponent implements OnInit, OnDestroy {
|
||||
delete(qaEvent: QualityAssuranceEventData): Observable<RemoteData<NoContent>> {
|
||||
return this.qualityAssuranceEventRestService.deleteQAEvent(qaEvent);
|
||||
}
|
||||
|
||||
getEntityPageRoute(itemId: string): string {
|
||||
return getEntityPageRoute('person', itemId);
|
||||
}
|
||||
}
|
||||
|
@@ -23,7 +23,7 @@ import { getDSORoute } from '../../app-routing-paths';
|
||||
import { ResearcherProfileDataService } from '../../core/profile/researcher-profile-data.service';
|
||||
import { NotificationsService } from '../notifications/notifications.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { DsoWithdrawnReinstateModalService } from './dso-withdrawn-reinstate-service/dso-withdrawn-reinstate-modal.service';
|
||||
import { DsoWithdrawnReinstateModalService, REQUEST_REINSTATE, REQUEST_WITHDRAWN } from './dso-withdrawn-reinstate-service/dso-withdrawn-reinstate-modal.service';
|
||||
import { AuthService } from '../../core/auth/auth.service';
|
||||
import { FindListOptions } from '../../core/data/find-list-options.model';
|
||||
import { RequestParam } from '../../core/cache/models/request-param.model';
|
||||
@@ -193,7 +193,7 @@ export class DSOEditMenuResolver implements Resolve<{ [key: string]: MenuSection
|
||||
{
|
||||
id: 'withdrawn-item',
|
||||
active: false,
|
||||
visible: dso.isArchived && correction?.totalElements > 0,
|
||||
visible: dso.isArchived && correction?.page.some((c) => c.topic === REQUEST_WITHDRAWN),
|
||||
model: {
|
||||
type: MenuItemType.ONCLICK,
|
||||
text:'item.page.withdrawn',
|
||||
@@ -207,7 +207,7 @@ export class DSOEditMenuResolver implements Resolve<{ [key: string]: MenuSection
|
||||
{
|
||||
id: 'reinstate-item',
|
||||
active: false,
|
||||
visible: dso.isWithdrawn && correction?.totalElements > 0,
|
||||
visible: dso.isWithdrawn && correction?.page.some((c) => c.topic === REQUEST_REINSTATE),
|
||||
model: {
|
||||
type: MenuItemType.ONCLICK,
|
||||
text:'item.page.reinstate',
|
||||
|
@@ -15,6 +15,10 @@ import { NotificationsService } from '../../notifications/notifications.service'
|
||||
import { take } from 'rxjs/operators';
|
||||
import { getFirstCompletedRemoteData } from '../../../core/shared/operators';
|
||||
import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
|
||||
import { Item } from 'src/app/core/shared/item.model';
|
||||
|
||||
export const REQUEST_WITHDRAWN = 'REQUEST/WITHDRAWN';
|
||||
export const REQUEST_REINSTATE = 'REQUEST/REINSTATE';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
@@ -34,7 +38,7 @@ export class DsoWithdrawnReinstateModalService {
|
||||
/**
|
||||
* Open the create withdrawn modal for the provided dso
|
||||
*/
|
||||
openCreateWithdrawnReinstateModal(dso, correctionType: string, state: boolean): void {
|
||||
openCreateWithdrawnReinstateModal(dso: Item, correctionType: string, state: boolean): void {
|
||||
const target = dso.id;
|
||||
// Open modal
|
||||
const activeModal = this.modalService.open(ItemWithdrawnReinstateModalComponent);
|
||||
|
@@ -1916,6 +1916,10 @@
|
||||
|
||||
"item.alerts.withdrawn": "This item has been withdrawn",
|
||||
|
||||
"item.alerts.reinstate-request": "Request reinstate",
|
||||
|
||||
"quality-assurance.event.table.person-who-requested": "Requested by",
|
||||
|
||||
"item.edit.authorizations.heading": "With this editor you can view and alter the policies of an item, plus alter policies of individual item components: bundles and bitstreams. Briefly, an item is a container of bundles, and bundles are containers of bitstreams. Containers usually have ADD/REMOVE/READ/WRITE policies, while bitstreams only have READ/WRITE policies.",
|
||||
|
||||
"item.edit.authorizations.title": "Edit item's Policies",
|
||||
|
Reference in New Issue
Block a user