mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-10 03:23:07 +00:00
[TLC-249] Register DOI operation and button in item status page
This commit is contained in:
@@ -32,4 +32,5 @@ export enum FeatureID {
|
||||
CanSynchronizeWithORCID = 'canSynchronizeWithORCID',
|
||||
CanSubmit = 'canSubmit',
|
||||
CanEditItem = 'canEditItem',
|
||||
CanRegisterDOI = 'canRegisterDOI',
|
||||
}
|
||||
|
62
src/app/core/data/identifier-data.service.ts
Normal file
62
src/app/core/data/identifier-data.service.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { dataService } from '../cache/builders/build-decorators';
|
||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
import { DataService } from './data.service';
|
||||
import { RequestService } from './request.service';
|
||||
import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
|
||||
import { CoreState } from '../core-state.model';
|
||||
import { AccessStatusObject } from 'src/app/shared/object-list/access-status-badge/access-status.model';
|
||||
import { ACCESS_STATUS } from 'src/app/shared/object-list/access-status-badge/access-status.resource-type';
|
||||
import { Observable } from 'rxjs';
|
||||
import { RemoteData } from './remote-data';
|
||||
import { Item } from '../shared/item.model';
|
||||
import {IDENTIFIERS} from '../../shared/object-list/identifier-data/identifier-data.resource-type';
|
||||
import {IdentifierData} from '../../shared/object-list/identifier-data/identifier-data.model';
|
||||
import {getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload} from '../shared/operators';
|
||||
import {map, startWith} from 'rxjs/operators';
|
||||
import {ConfigurationProperty} from '../shared/configuration-property.model';
|
||||
import {ConfigurationDataService} from './configuration-data.service';
|
||||
|
||||
@Injectable()
|
||||
@dataService(IDENTIFIERS)
|
||||
export class IdentifierDataService extends DataService<IdentifierData> {
|
||||
|
||||
protected linkPath = 'identifiers';
|
||||
|
||||
constructor(
|
||||
protected comparator: DefaultChangeAnalyzer<IdentifierData>,
|
||||
protected halService: HALEndpointService,
|
||||
protected http: HttpClient,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected objectCache: ObjectCacheService,
|
||||
protected rdbService: RemoteDataBuildService,
|
||||
protected requestService: RequestService,
|
||||
protected store: Store<CoreState>,
|
||||
private configurationService: ConfigurationDataService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link RemoteData} of {@link IdentifierData} representing identifiers for this item
|
||||
* @param item Item we are querying
|
||||
*/
|
||||
getIdentifierDataFor(item: Item): Observable<RemoteData<IdentifierData>> {
|
||||
return this.findByHref(item._links.identifiers.href, false, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Should we allow registration of new DOIs via the item status page?
|
||||
*/
|
||||
public getIdentifierRegistrationConfiguration(): Observable<string[]> {
|
||||
return this.configurationService.findByPropertyName('identifiers.item-status.register').pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
map((propertyRD: RemoteData<ConfigurationProperty>) => propertyRD.hasSucceeded ? propertyRD.payload.values : [])
|
||||
);
|
||||
}
|
||||
}
|
@@ -232,6 +232,36 @@ export abstract class BaseItemDataService extends IdentifiableDataService<Item>
|
||||
return this.rdbService.buildFromRequestUUID(requestId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the endpoint for an item's bundles
|
||||
* @param itemId
|
||||
*/
|
||||
public getIdentifiersEndpoint(itemId: string): Observable<string> {
|
||||
return this.halService.getEndpoint(this.linkPath).pipe(
|
||||
switchMap((url: string) => this.halService.getEndpoint('identifiers', `${url}/${itemId}`))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a DOI for a given item
|
||||
* @param itemId
|
||||
*/
|
||||
public registerDOI(itemId: string): Observable<RemoteData<any>> {
|
||||
const requestId = this.requestService.generateRequestId();
|
||||
const hrefObs = this.getIdentifiersEndpoint(itemId);
|
||||
hrefObs.pipe(
|
||||
take(1)
|
||||
).subscribe((href) => {
|
||||
const options: HttpOptions = Object.create({});
|
||||
let headers = new HttpHeaders();
|
||||
headers = headers.append('Content-Type', 'application/json');
|
||||
options.headers = headers;
|
||||
const request = new PostRequest(requestId, href, JSON.stringify({}), options);
|
||||
this.requestService.send(request);
|
||||
});
|
||||
return this.rdbService.buildFromRequestUUID(requestId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the endpoint to move the item
|
||||
* @param itemId
|
||||
|
@@ -24,6 +24,8 @@ import { Bitstream } from './bitstream.model';
|
||||
import { ACCESS_STATUS } from 'src/app/shared/object-list/access-status-badge/access-status.resource-type';
|
||||
import { AccessStatusObject } from 'src/app/shared/object-list/access-status-badge/access-status.model';
|
||||
import { HandleObject } from './handle-object.model';
|
||||
import { IDENTIFIERS } from '../../shared/object-list/identifier-data/identifier-data.resource-type';
|
||||
import { IdentifierData } from '../../shared/object-list/identifier-data/identifier-data.model';
|
||||
|
||||
/**
|
||||
* Class representing a DSpace Item
|
||||
@@ -76,6 +78,7 @@ export class Item extends DSpaceObject implements ChildHALResource, HandleObject
|
||||
version: HALLink;
|
||||
thumbnail: HALLink;
|
||||
accessStatus: HALLink;
|
||||
identifiers: HALLink;
|
||||
self: HALLink;
|
||||
};
|
||||
|
||||
@@ -121,6 +124,13 @@ export class Item extends DSpaceObject implements ChildHALResource, HandleObject
|
||||
@link(ACCESS_STATUS)
|
||||
accessStatus?: Observable<RemoteData<AccessStatusObject>>;
|
||||
|
||||
/**
|
||||
* The identifier data for this Item
|
||||
* Will be undefined unless the identifiers {@link HALLink} has been resolved.
|
||||
*/
|
||||
@link(IDENTIFIERS)
|
||||
identifiers?: Observable<RemoteData<IdentifierData>>;
|
||||
|
||||
/**
|
||||
* Method that returns as which type of object this object should be rendered
|
||||
*/
|
||||
|
@@ -34,6 +34,9 @@ import { ItemAuthorizationsComponent } from './item-authorizations/item-authoriz
|
||||
import { ObjectValuesPipe } from '../../shared/utils/object-values-pipe';
|
||||
import { ResourcePoliciesModule } from '../../shared/resource-policies/resource-policies.module';
|
||||
import { ItemVersionsModule } from '../versions/item-versions.module';
|
||||
import { IdentifierDataService } from '../../core/data/identifier-data.service';
|
||||
import { IdentifierDataComponent } from '../../shared/object-list/identifier-data/identifier-data.component';
|
||||
import { ItemRegisterDoiComponent } from './item-register-doi/item-registerdoi.component';
|
||||
import { DsoSharedModule } from '../../dso-shared/dso-shared.module';
|
||||
|
||||
|
||||
@@ -76,10 +79,13 @@ import { DsoSharedModule } from '../../dso-shared/dso-shared.module';
|
||||
ItemMoveComponent,
|
||||
ItemEditBitstreamDragHandleComponent,
|
||||
VirtualMetadataComponent,
|
||||
ItemAuthorizationsComponent
|
||||
ItemAuthorizationsComponent,
|
||||
IdentifierDataComponent,
|
||||
ItemRegisterDoiComponent
|
||||
],
|
||||
providers: [
|
||||
BundleDataService,
|
||||
IdentifierDataService,
|
||||
ObjectValuesPipe
|
||||
],
|
||||
})
|
||||
|
@@ -5,3 +5,4 @@ export const ITEM_EDIT_PUBLIC_PATH = 'public';
|
||||
export const ITEM_EDIT_DELETE_PATH = 'delete';
|
||||
export const ITEM_EDIT_MOVE_PATH = 'move';
|
||||
export const ITEM_EDIT_AUTHORIZATIONS_PATH = 'authorizations';
|
||||
export const ITEM_EDIT_REGISTER_DOI_PATH = 'registerdoi';
|
||||
|
@@ -10,6 +10,7 @@ import { ItemStatusComponent } from './item-status/item-status.component';
|
||||
import { ItemBitstreamsComponent } from './item-bitstreams/item-bitstreams.component';
|
||||
import { ItemCollectionMapperComponent } from './item-collection-mapper/item-collection-mapper.component';
|
||||
import { ItemMoveComponent } from './item-move/item-move.component';
|
||||
import { ItemRegisterDoiComponent } from './item-register-doi/item-registerdoi.component'
|
||||
import { ItemRelationshipsComponent } from './item-relationships/item-relationships.component';
|
||||
import { I18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||
import { ItemVersionHistoryComponent } from './item-version-history/item-version-history.component';
|
||||
@@ -26,7 +27,8 @@ import {
|
||||
ITEM_EDIT_PRIVATE_PATH,
|
||||
ITEM_EDIT_PUBLIC_PATH,
|
||||
ITEM_EDIT_REINSTATE_PATH,
|
||||
ITEM_EDIT_WITHDRAW_PATH
|
||||
ITEM_EDIT_WITHDRAW_PATH,
|
||||
ITEM_EDIT_REGISTER_DOI_PATH
|
||||
} from './edit-item-page.routing-paths';
|
||||
import { ItemPageReinstateGuard } from './item-page-reinstate.guard';
|
||||
import { ItemPageWithdrawGuard } from './item-page-withdraw.guard';
|
||||
@@ -38,6 +40,7 @@ import { ItemPageRelationshipsGuard } from './item-page-relationships.guard';
|
||||
import { ItemPageVersionHistoryGuard } from './item-page-version-history.guard';
|
||||
import { ItemPageCollectionMapperGuard } from './item-page-collection-mapper.guard';
|
||||
import { ThemedDsoEditMetadataComponent } from '../../dso-shared/dso-edit-metadata/themed-dso-edit-metadata.component';
|
||||
import { ItemPageRegisterDoiGuard } from './item-page-registerdoi.guard';
|
||||
|
||||
/**
|
||||
* Routing module that handles the routing for the Edit Item page administrator functionality
|
||||
@@ -142,6 +145,12 @@ import { ThemedDsoEditMetadataComponent } from '../../dso-shared/dso-edit-metada
|
||||
component: ItemMoveComponent,
|
||||
data: { title: 'item.edit.move.title' },
|
||||
},
|
||||
{
|
||||
path: ITEM_EDIT_REGISTER_DOI_PATH,
|
||||
component: ItemRegisterDoiComponent,
|
||||
canActivate: [ItemPageRegisterDoiGuard],
|
||||
data: { title: 'item.edit.registerdoi.title' },
|
||||
},
|
||||
{
|
||||
path: ITEM_EDIT_AUTHORIZATIONS_PATH,
|
||||
children: [
|
||||
@@ -186,6 +195,7 @@ import { ThemedDsoEditMetadataComponent } from '../../dso-shared/dso-edit-metada
|
||||
ItemPageRelationshipsGuard,
|
||||
ItemPageVersionHistoryGuard,
|
||||
ItemPageCollectionMapperGuard,
|
||||
ItemPageRegisterDoiGuard,
|
||||
]
|
||||
})
|
||||
export class EditItemPageRoutingModule {
|
||||
|
@@ -0,0 +1,31 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { DsoPageSingleFeatureGuard } from '../../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard';
|
||||
import { Item } from '../../core/shared/item.model';
|
||||
import { ItemPageResolver } from '../item-page.resolver';
|
||||
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
|
||||
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
|
||||
import { Observable, of as observableOf } from 'rxjs';
|
||||
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
|
||||
import { AuthService } from '../../core/auth/auth.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
/**
|
||||
* Guard for preventing unauthorized access to certain {@link Item} pages requiring DOI registration rights
|
||||
*/
|
||||
export class ItemPageRegisterDoiGuard extends DsoPageSingleFeatureGuard<Item> {
|
||||
constructor(protected resolver: ItemPageResolver,
|
||||
protected authorizationService: AuthorizationDataService,
|
||||
protected router: Router,
|
||||
protected authService: AuthService) {
|
||||
super(resolver, authorizationService, router, authService);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check DOI registration authorization rights
|
||||
*/
|
||||
getFeatureID(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<FeatureID> {
|
||||
return observableOf(FeatureID.CanRegisterDOI);
|
||||
}
|
||||
}
|
@@ -27,6 +27,6 @@ export class ItemPageStatusGuard extends DsoPageSomeFeatureGuard<Item> {
|
||||
* Check authorization rights
|
||||
*/
|
||||
getFeatureIDs(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<FeatureID[]> {
|
||||
return observableOf([FeatureID.CanManageMappings, FeatureID.WithdrawItem, FeatureID.ReinstateItem, FeatureID.CanManagePolicies, FeatureID.CanMakePrivate, FeatureID.CanDelete, FeatureID.CanMove]);
|
||||
return observableOf([FeatureID.CanManageMappings, FeatureID.WithdrawItem, FeatureID.ReinstateItem, FeatureID.CanManagePolicies, FeatureID.CanMakePrivate, FeatureID.CanDelete, FeatureID.CanMove, FeatureID.CanRegisterDOI]);
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,24 @@
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<h2>{{headerMessage | translate: {id: item.handle} }}</h2>
|
||||
<p>{{descriptionMessage | translate}}</p>
|
||||
<div *ngFor="let identifier of (identifiers$ | async)" class="w-100 p">
|
||||
<div *ngIf="(identifier.identifierType=='doi')">
|
||||
<p class="float-left">{{doiToUpdateMessage | translate}}: {{identifier.value}}
|
||||
({{"item.edit.identifiers.doi.status."+identifier.identifierStatus|translate}})
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<ds-modify-item-overview [item]="item"></ds-modify-item-overview>
|
||||
<div class="space-children-mr">
|
||||
<button (click)="performAction()" class="btn btn-outline-secondary perform-action">{{confirmMessage | translate}}
|
||||
</button>
|
||||
<button [routerLink]="[itemPageRoute, 'edit']" class="btn btn-outline-secondary cancel">
|
||||
{{cancelMessage| translate}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
@@ -0,0 +1,87 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { AbstractSimpleItemActionComponent } from '../simple-item-action/abstract-simple-item-action.component';
|
||||
import { RemoteData } from '../../../core/data/remote-data';
|
||||
import { Item } from '../../../core/shared/item.model';
|
||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||
import { ItemDataService } from '../../../core/data/item-data.service';
|
||||
import { getFirstCompletedRemoteData, getFirstSucceededRemoteData } from '../../../core/shared/operators';
|
||||
import { first, map } from 'rxjs/operators';
|
||||
import { hasValue } from '../../../shared/empty.util';
|
||||
import { Observable } from 'rxjs';
|
||||
import {getItemEditRoute, getItemPageRoute} from '../../item-page-routing-paths';
|
||||
import { IdentifierDataService } from '../../../core/data/identifier-data.service';
|
||||
import {Identifier} from '../../../shared/object-list/identifier-data/identifier.model';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-item-registerdoi',
|
||||
templateUrl: './item-registerdoi-component.html'
|
||||
})
|
||||
/**
|
||||
* Component responsible for rendering the Item Registe DOI page
|
||||
*/
|
||||
export class ItemRegisterDoiComponent extends AbstractSimpleItemActionComponent {
|
||||
|
||||
protected messageKey = 'registerdoi';
|
||||
doiToUpdateMessage = 'item.edit.' + this.messageKey + '.to-update';
|
||||
identifiers$: Observable<Identifier[]>;
|
||||
processing: boolean = false;
|
||||
|
||||
constructor(protected route: ActivatedRoute,
|
||||
protected router: Router,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected itemDataService: ItemDataService,
|
||||
protected translateService: TranslateService,
|
||||
protected identifierDataService: IdentifierDataService) {
|
||||
super(route, router, notificationsService, itemDataService, translateService);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.itemRD$ = this.route.data.pipe(
|
||||
map((data) => data.dso),
|
||||
getFirstSucceededRemoteData()
|
||||
)as Observable<RemoteData<Item>>;
|
||||
|
||||
this.itemRD$.pipe(first()).subscribe((rd) => {
|
||||
this.item = rd.payload;
|
||||
this.itemPageRoute = getItemPageRoute(this.item);
|
||||
this.identifiers$ = this.identifierDataService.getIdentifierDataFor(this.item).pipe(
|
||||
map((identifierRD) => {
|
||||
if (identifierRD.statusCode !== 401 && hasValue(identifierRD.payload)) {
|
||||
return identifierRD.payload.identifiers;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
this.confirmMessage = 'item.edit.' + this.messageKey + '.confirm';
|
||||
this.cancelMessage = 'item.edit.' + this.messageKey + '.cancel';
|
||||
this.headerMessage = 'item.edit.' + this.messageKey + '.header';
|
||||
this.descriptionMessage = 'item.edit.' + this.messageKey + '.description';
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the register DOI action to the item
|
||||
*/
|
||||
performAction() {
|
||||
this.registerDoi();
|
||||
}
|
||||
|
||||
registerDoi() {
|
||||
this.processing = true;
|
||||
this.itemDataService.registerDOI(this.item.id).pipe(getFirstCompletedRemoteData()).subscribe(
|
||||
(response: RemoteData<Item>) => {
|
||||
this.processing = false;
|
||||
this.router.navigateByUrl(getItemEditRoute(this.item));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@@ -8,6 +8,17 @@
|
||||
{{statusData[statusKey]}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngFor="let identifier of (identifiers$ | async)" class="w-100">
|
||||
<div *ngIf="(identifier.identifierType=='doi')">
|
||||
<div class="col-3 float-left status-label">
|
||||
{{identifier.identifierType.toLocaleUpperCase()}}
|
||||
</div>
|
||||
<div class="col-9 float-left status-label">{{identifier.value}}
|
||||
({{"item.edit.identifiers.doi.status."+identifier.identifierStatus|translate}})</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-3 float-left status-label">
|
||||
{{'item.edit.tabs.status.labels.itemPage' | translate}}:
|
||||
</div>
|
||||
@@ -18,4 +29,5 @@
|
||||
<div *ngFor="let operation of (operations$ | async)" class="w-100" [ngClass]="{'pt-3': operation}">
|
||||
<ds-item-operation *ngIf="operation" [operation]="operation"></ds-item-operation>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
@@ -3,14 +3,23 @@ import { fadeIn, fadeInOut } from '../../../shared/animations/fade';
|
||||
import { Item } from '../../../core/shared/item.model';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { ItemOperation } from '../item-operation/itemOperation.model';
|
||||
import { distinctUntilChanged, first, map, mergeMap, toArray } from 'rxjs/operators';
|
||||
import { BehaviorSubject, Observable, from as observableFrom } from 'rxjs';
|
||||
import {distinctUntilChanged, first, map, mergeMap, startWith, switchMap, toArray} from 'rxjs/operators';
|
||||
import {BehaviorSubject, Observable, from as observableFrom, Subscription, combineLatest, of} from 'rxjs';
|
||||
import { RemoteData } from '../../../core/data/remote-data';
|
||||
import { getItemEditRoute, getItemPageRoute } from '../../item-page-routing-paths';
|
||||
import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
|
||||
import { FeatureID } from '../../../core/data/feature-authorization/feature-id';
|
||||
import { hasValue } from '../../../shared/empty.util';
|
||||
import { getAllSucceededRemoteDataPayload } from '../../../core/shared/operators';
|
||||
import {
|
||||
getAllSucceededRemoteDataPayload,
|
||||
getFirstCompletedRemoteData, getFirstSucceededRemoteData,
|
||||
getFirstSucceededRemoteDataPayload
|
||||
} from '../../../core/shared/operators';
|
||||
import {IdentifierDataService} from '../../../core/data/identifier-data.service';
|
||||
import {IdentifierData} from '../../../shared/object-list/identifier-data/identifier-data.model';
|
||||
import {Identifier} from '../../../shared/object-list/identifier-data/identifier.model';
|
||||
import {ConfigurationProperty} from '../../../core/shared/configuration-property.model';
|
||||
import {ConfigurationDataService} from '../../../core/data/configuration-data.service';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-item-status',
|
||||
@@ -51,15 +60,32 @@ export class ItemStatusComponent implements OnInit {
|
||||
*/
|
||||
actionsKeys;
|
||||
|
||||
/**
|
||||
* Identifiers (handles, DOIs)
|
||||
*/
|
||||
identifiers$: Observable<Identifier[]>;
|
||||
|
||||
/**
|
||||
* Configuration and state variables regarding DOIs
|
||||
*/
|
||||
|
||||
public subs: Subscription[] = [];
|
||||
|
||||
/**
|
||||
* Route to the item's page
|
||||
*/
|
||||
itemPageRoute$: Observable<string>;
|
||||
|
||||
constructor(private route: ActivatedRoute,
|
||||
private authorizationService: AuthorizationDataService) {
|
||||
private authorizationService: AuthorizationDataService,
|
||||
private identifierDataService: IdentifierDataService,
|
||||
private configurationService: ConfigurationDataService,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise component
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.itemRD$ = this.route.parent.data.pipe(map((data) => data.dso));
|
||||
this.itemRD$.pipe(
|
||||
@@ -72,6 +98,35 @@ export class ItemStatusComponent implements OnInit {
|
||||
lastModified: item.lastModified
|
||||
});
|
||||
this.statusDataKeys = Object.keys(this.statusData);
|
||||
|
||||
// Observable for item identifiers (retrieved from embedded link)
|
||||
this.identifiers$ = this.identifierDataService.getIdentifierDataFor(item).pipe(
|
||||
map((identifierRD) => {
|
||||
if (identifierRD.statusCode !== 401 && hasValue(identifierRD.payload)) {
|
||||
return identifierRD.payload.identifiers;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
// Observable for configuration determining whether the Register DOI feature is enabled
|
||||
let registerConfigEnabled$: Observable<boolean> = this.configurationService.findByPropertyName('identifiers.item-status.register').pipe(
|
||||
map((enabled: RemoteData<ConfigurationProperty>) => {
|
||||
let show: boolean = false;
|
||||
if (enabled.hasSucceeded) {
|
||||
if (enabled.payload !== undefined && enabled.payload !== null) {
|
||||
if (enabled.payload.values !== undefined) {
|
||||
enabled.payload.values.forEach((value) => {
|
||||
show = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return show;
|
||||
})
|
||||
);
|
||||
|
||||
/*
|
||||
The key is used to build messages
|
||||
i18n example: 'item.edit.tabs.status.buttons.<key>.label'
|
||||
@@ -92,10 +147,45 @@ export class ItemStatusComponent implements OnInit {
|
||||
}
|
||||
operations.push(new ItemOperation('delete', this.getCurrentUrl(item) + '/delete', FeatureID.CanDelete, true));
|
||||
operations.push(new ItemOperation('move', this.getCurrentUrl(item) + '/move', FeatureID.CanMove, true));
|
||||
|
||||
this.operations$.next(operations);
|
||||
|
||||
observableFrom(operations).pipe(
|
||||
// Observable that reads identifiers and their status and, and config properties, and decides
|
||||
// if we're allowed to show a Register DOI feature
|
||||
let showRegister$: Observable<boolean> = combineLatest([this.identifiers$, registerConfigEnabled$]).pipe(
|
||||
distinctUntilChanged(),
|
||||
map(([identifiers, enabled]) => {
|
||||
let no_doi: boolean = true;
|
||||
let pending: boolean = false;
|
||||
if (identifiers !== undefined && identifiers !== null) {
|
||||
identifiers.forEach((identifier: Identifier) => {
|
||||
if (hasValue(identifier) && identifier.identifierType == 'doi') {
|
||||
// The item has some kind of DOI
|
||||
no_doi = false;
|
||||
if (identifier.identifierStatus == '10' || identifier.identifierStatus == '11'
|
||||
|| identifier.identifierStatus == null) {
|
||||
// The item's DOI is pending, minted or null.
|
||||
// It isn't registered, reserved, queued for registration or reservation or update, deleted
|
||||
// or queued for deletion.
|
||||
pending = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
// If there is no DOI, or a pending/minted/null DOI, and the config is enabled, return true
|
||||
return ((pending || no_doi) && enabled);
|
||||
})
|
||||
)
|
||||
|
||||
// Subscribe to changes from the showRegister check and rebuild operations list accordingly
|
||||
this.subs.push(showRegister$.subscribe((show) => {
|
||||
// Copy the static array first so we don't keep appending to it
|
||||
let tmp_operations = [...operations];
|
||||
if (show) {
|
||||
// Push the new Register DOI item operation
|
||||
tmp_operations.push(new ItemOperation('registerDOI', this.getCurrentUrl(item) + '/registerdoi', FeatureID.CanRegisterDOI));
|
||||
}
|
||||
// Check authorisations and merge into new operations list
|
||||
observableFrom(tmp_operations).pipe(
|
||||
mergeMap((operation) => {
|
||||
if (hasValue(operation.featureID)) {
|
||||
return this.authorizationService.isAuthorized(operation.featureID, item.self).pipe(
|
||||
@@ -108,11 +198,15 @@ export class ItemStatusComponent implements OnInit {
|
||||
}),
|
||||
toArray()
|
||||
).subscribe((ops) => this.operations$.next(ops));
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
this.itemPageRoute$ = this.itemRD$.pipe(
|
||||
getAllSucceededRemoteDataPayload(),
|
||||
map((item) => getItemPageRoute(item))
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -127,4 +221,10 @@ export class ItemStatusComponent implements OnInit {
|
||||
return hasValue(operation) ? operation.operationKey : undefined;
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.subs
|
||||
.filter((subscription) => hasValue(subscription))
|
||||
.forEach((subscription) => subscription.unsubscribe());
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,5 @@
|
||||
<ng-container>
|
||||
<div *ngIf="identifiers$ | async as identifiers">
|
||||
<span class="">{{ identifiers[0].value | translate }}</span>
|
||||
</div>
|
||||
</ng-container>
|
@@ -0,0 +1,56 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { catchError, map } from 'rxjs/operators';
|
||||
import { Observable, of as observableOf } from 'rxjs';
|
||||
import { hasValue } from '../../empty.util';
|
||||
import { environment } from 'src/environments/environment';
|
||||
import { Item } from 'src/app/core/shared/item.model';
|
||||
import { AccessStatusDataService } from 'src/app/core/data/access-status-data.service';
|
||||
import {IdentifierData} from './identifier-data.model';
|
||||
import {IdentifierDataService} from '../../../core/data/identifier-data.service';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-identifier-data',
|
||||
templateUrl: './identifier-data.component.html'
|
||||
})
|
||||
/**
|
||||
* Component rendering the access status of an item as a badge
|
||||
*/
|
||||
export class IdentifierDataComponent {
|
||||
|
||||
@Input() item: Item;
|
||||
identifiers$: Observable<IdentifierData>;
|
||||
|
||||
/**
|
||||
* Whether to show the access status badge or not
|
||||
*/
|
||||
showAccessStatus: boolean;
|
||||
|
||||
/**
|
||||
* Initialize instance variables
|
||||
*
|
||||
* @param {IdentifierDataService} identifierDataService
|
||||
*/
|
||||
constructor(private identifierDataService: IdentifierDataService) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
if (this.item == null) {
|
||||
// Do not show the badge if the feature is inactive or if the item is null.
|
||||
return;
|
||||
}
|
||||
if (this.item.identifiers == null) {
|
||||
// In case the access status has not been loaded, do it individually.
|
||||
this.item.identifiers = this.identifierDataService.getIdentifierDataFor(this.item);
|
||||
}
|
||||
this.identifiers$ = this.item.identifiers.pipe(
|
||||
map((identifierRD) => {
|
||||
if (identifierRD.statusCode !== 401 && hasValue(identifierRD.payload)) {
|
||||
return identifierRD.payload;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}),
|
||||
// EMpty array if none
|
||||
//map((identifiers: IdentifierData) => hasValue(identifiers.identifiers) ? identifiers.identifiers : [])
|
||||
);
|
||||
}
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
import { autoserialize, deserialize } from 'cerialize';
|
||||
import { typedObject } from 'src/app/core/cache/builders/build-decorators';
|
||||
import { CacheableObject } from 'src/app/core/cache/cacheable-object.model';
|
||||
import { HALLink } from 'src/app/core/shared/hal-link.model';
|
||||
import { ResourceType } from 'src/app/core/shared/resource-type';
|
||||
import { excludeFromEquals } from 'src/app/core/utilities/equals.decorators';
|
||||
import { IDENTIFIERS } from './identifier-data.resource-type';
|
||||
import {Identifier} from './identifier.model';
|
||||
|
||||
@typedObject
|
||||
export class IdentifierData implements CacheableObject {
|
||||
static type = IDENTIFIERS;
|
||||
/**
|
||||
* The type for this IdentifierData
|
||||
*/
|
||||
@excludeFromEquals
|
||||
@autoserialize
|
||||
type: ResourceType;
|
||||
|
||||
/**
|
||||
* The
|
||||
*/
|
||||
@autoserialize
|
||||
identifiers: Identifier[];
|
||||
|
||||
/**
|
||||
* The {@link HALLink}s for this IdentifierData
|
||||
*/
|
||||
@deserialize
|
||||
_links: {
|
||||
self: HALLink;
|
||||
};
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
import { ResourceType } from 'src/app/core/shared/resource-type';
|
||||
|
||||
/**
|
||||
* The resource type for Identifiers
|
||||
*
|
||||
* Needs to be in a separate file to prevent circular
|
||||
* dependencies in webpack.
|
||||
*/
|
||||
export const IDENTIFIERS = new ResourceType('identifiers');
|
@@ -0,0 +1,12 @@
|
||||
import {autoserialize} from 'cerialize';
|
||||
|
||||
export class Identifier {
|
||||
@autoserialize
|
||||
value: string;
|
||||
@autoserialize
|
||||
identifierType: string;
|
||||
@autoserialize
|
||||
identifierStatus: string;
|
||||
@autoserialize
|
||||
type: string;
|
||||
}
|
@@ -1918,6 +1918,46 @@
|
||||
|
||||
"item.edit.tabs.item-mapper.title": "Item Edit - Collection Mapper",
|
||||
|
||||
"item.edit.identifiers.doi.status.1": "Queued for registration",
|
||||
|
||||
"item.edit.identifiers.doi.status.2": "Queued for reservation",
|
||||
|
||||
"item.edit.identifiers.doi.status.3": "Registered",
|
||||
|
||||
"item.edit.identifiers.doi.status.4": "Reserved",
|
||||
|
||||
"item.edit.identifiers.doi.status.5": "Reserved",
|
||||
|
||||
"item.edit.identifiers.doi.status.6": "Registered",
|
||||
|
||||
"item.edit.identifiers.doi.status.7": "Queued for registration",
|
||||
|
||||
"item.edit.identifiers.doi.status.8": "Queued for deletion",
|
||||
|
||||
"item.edit.identifiers.doi.status.9": "Deleted",
|
||||
|
||||
"item.edit.identifiers.doi.status.10": "Pending approval",
|
||||
|
||||
"item.edit.identifiers.doi.status.11": "Minted, not registered",
|
||||
|
||||
"item.edit.tabs.status.buttons.registerDOI.label": "Register a new or pending identifier",
|
||||
|
||||
"item.edit.tabs.status.buttons.registerDOI.button": "Register DOI...",
|
||||
|
||||
"item.edit.registerdoi.header": "Register a new or pending DOI",
|
||||
|
||||
"item.edit.registerdoi.description": "Review any pending identifiers and item metadata below and click Confirm to proceed with DOI registration, or Cancel to back out",
|
||||
|
||||
"item.edit.registerdoi.confirm": "Confirm",
|
||||
|
||||
"item.edit.registerdoi.cancel": "Cancel",
|
||||
|
||||
"item.edit.registerdoi.success": "DOI registered successfully. Refresh Item Status page to see new DOI details.",
|
||||
|
||||
"item.edit.registerdoi.error": "Error registering DOI",
|
||||
|
||||
"item.edit.registerdoi.to-update": "The following DOI has already been minted and will be queued for registration online",
|
||||
|
||||
"item.edit.item-mapper.buttons.add": "Map item to selected collections",
|
||||
|
||||
"item.edit.item-mapper.buttons.remove": "Remove item's mapping for selected collections",
|
||||
|
Reference in New Issue
Block a user