@@ -3,21 +3,20 @@ import { fadeIn, fadeInOut } from '../../../shared/animations/fade';
33import { Item } from '../../../core/shared/item.model' ;
44import { ActivatedRoute } from '@angular/router' ;
55import { ItemOperation } from '../item-operation/itemOperation.model' ;
6- import { distinctUntilChanged , map , mergeMap , switchMap , toArray } from 'rxjs/operators' ;
7- import { BehaviorSubject , Observable , Subscription } from 'rxjs' ;
6+ import { concatMap , distinctUntilChanged , first , map , mergeMap , switchMap , toArray } from 'rxjs/operators' ;
7+ import { BehaviorSubject , combineLatest , Observable , of , Subscription } from 'rxjs' ;
88import { RemoteData } from '../../../core/data/remote-data' ;
99import { getItemEditRoute , getItemPageRoute } from '../../item-page-routing-paths' ;
1010import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service' ;
1111import { FeatureID } from '../../../core/data/feature-authorization/feature-id' ;
12- import { hasValue , isNotEmpty } from '../../../shared/empty.util' ;
13- import {
14- getAllSucceededRemoteDataPayload , getFirstCompletedRemoteData , getFirstSucceededRemoteData , getRemoteDataPayload ,
15- } from '../../../core/shared/operators' ;
12+ import { hasValue } from '../../../shared/empty.util' ;
13+ import { getAllSucceededRemoteDataPayload , getFirstCompletedRemoteData , } from '../../../core/shared/operators' ;
1614import { IdentifierDataService } from '../../../core/data/identifier-data.service' ;
1715import { Identifier } from '../../../shared/object-list/identifier-data/identifier.model' ;
1816import { ConfigurationProperty } from '../../../core/shared/configuration-property.model' ;
1917import { ConfigurationDataService } from '../../../core/data/configuration-data.service' ;
2018import { IdentifierData } from '../../../shared/object-list/identifier-data/identifier-data.model' ;
19+ import { OrcidAuthService } from '../../../core/orcid/orcid-auth.service' ;
2120
2221@Component ( {
2322 selector : 'ds-item-status' ,
@@ -73,6 +72,7 @@ export class ItemStatusComponent implements OnInit {
7372 private authorizationService : AuthorizationDataService ,
7473 private identifierDataService : IdentifierDataService ,
7574 private configurationService : ConfigurationDataService ,
75+ private orcidAuthService : OrcidAuthService
7676 ) {
7777 }
7878
@@ -82,14 +82,16 @@ export class ItemStatusComponent implements OnInit {
8282 ngOnInit ( ) : void {
8383 this . itemRD$ = this . route . parent . data . pipe ( map ( ( data ) => data . dso ) ) ;
8484 this . itemRD$ . pipe (
85+ first ( ) ,
8586 map ( ( data : RemoteData < Item > ) => data . payload )
86- ) . subscribe ( ( item : Item ) => {
87- this . statusData = Object . assign ( {
88- id : item . id ,
89- handle : item . handle ,
90- lastModified : item . lastModified
91- } ) ;
92- this . statusDataKeys = Object . keys ( this . statusData ) ;
87+ ) . pipe (
88+ switchMap ( ( item : Item ) => {
89+ this . statusData = Object . assign ( {
90+ id : item . id ,
91+ handle : item . handle ,
92+ lastModified : item . lastModified
93+ } ) ;
94+ this . statusDataKeys = Object . keys ( this . statusData ) ;
9395
9496 // Observable for item identifiers (retrieved from embedded link)
9597 this . identifiers$ = this . identifierDataService . getIdentifierDataFor ( item ) . pipe (
@@ -105,99 +107,108 @@ export class ItemStatusComponent implements OnInit {
105107 // Observable for configuration determining whether the Register DOI feature is enabled
106108 let registerConfigEnabled$ : Observable < boolean > = this . configurationService . findByPropertyName ( 'identifiers.item-status.register-doi' ) . pipe (
107109 getFirstCompletedRemoteData ( ) ,
108- map ( ( rd : RemoteData < ConfigurationProperty > ) => {
109- // If the config property is exposed via rest and has a value set, return it
110- if ( rd . hasSucceeded && hasValue ( rd . payload ) && isNotEmpty ( rd . payload . values ) ) {
111- return rd . payload . values [ 0 ] === 'true' ;
112- }
113- // Otherwise, return false
114- return false ;
115- } )
110+ map ( ( enabledRD : RemoteData < ConfigurationProperty > ) => enabledRD . hasSucceeded && enabledRD . payload . values . length > 0 )
116111 ) ;
117112
118- /*
119- Construct a base list of operations.
120- The key is used to build messages
121- i18n example: 'item.edit.tabs.status.buttons.<key>.label'
122- The value is supposed to be a href for the button
123- */
124- const operations : ItemOperation [ ] = [ ] ;
125- operations . push ( new ItemOperation ( 'authorizations' , this . getCurrentUrl ( item ) + '/authorizations' , FeatureID . CanManagePolicies , true ) ) ;
126- operations . push ( new ItemOperation ( 'mappedCollections' , this . getCurrentUrl ( item ) + '/mapper' , FeatureID . CanManageMappings , true ) ) ;
127- if ( item . isWithdrawn ) {
128- operations . push ( new ItemOperation ( 'reinstate' , this . getCurrentUrl ( item ) + '/reinstate' , FeatureID . ReinstateItem , true ) ) ;
129- } else {
130- operations . push ( new ItemOperation ( 'withdraw' , this . getCurrentUrl ( item ) + '/withdraw' , FeatureID . WithdrawItem , true ) ) ;
131- }
132- if ( item . isDiscoverable ) {
133- operations . push ( new ItemOperation ( 'private' , this . getCurrentUrl ( item ) + '/private' , FeatureID . CanMakePrivate , true ) ) ;
134- } else {
135- operations . push ( new ItemOperation ( 'public' , this . getCurrentUrl ( item ) + '/public' , FeatureID . CanMakePrivate , true ) ) ;
136- }
137- operations . push ( new ItemOperation ( 'delete' , this . getCurrentUrl ( item ) + '/delete' , FeatureID . CanDelete , true ) ) ;
138- operations . push ( new ItemOperation ( 'move' , this . getCurrentUrl ( item ) + '/move' , FeatureID . CanMove , true ) ) ;
139- this . operations$ . next ( operations ) ;
140-
141- /*
142- When the identifier data stream changes, determine whether the register DOI button should be shown or not.
143- This is based on whether the DOI is in the right state (minted or pending, not already queued for registration
144- or registered) and whether the configuration property identifiers.item-status.register-doi is true
113+ /**
114+ * Construct a base list of operations.
115+ * The key is used to build messages
116+ * i18n example: 'item.edit.tabs.status.buttons.<key>.label'
117+ * The value is supposed to be a href for the button
145118 */
146- this . identifierDataService . getIdentifierDataFor ( item ) . pipe (
147- getFirstSucceededRemoteData ( ) ,
148- getRemoteDataPayload ( ) ,
149- mergeMap ( ( data : IdentifierData ) => {
150- let identifiers = data . identifiers ;
151- let no_doi = true ;
152- let pending = false ;
153- if ( identifiers !== undefined && identifiers !== null ) {
154- identifiers . forEach ( ( identifier : Identifier ) => {
155- if ( hasValue ( identifier ) && identifier . identifierType === 'doi' ) {
156- // The item has some kind of DOI
157- no_doi = false ;
158- if ( identifier . identifierStatus === 'PENDING' || identifier . identifierStatus === 'MINTED'
159- || identifier . identifierStatus == null ) {
160- // The item's DOI is pending, minted or null.
161- // It isn't registered, reserved, queued for registration or reservation or update, deleted
162- // or queued for deletion.
163- pending = true ;
164- }
119+ const currentUrl = this . getCurrentUrl ( item ) ;
120+ const inititalOperations : ItemOperation [ ] = [
121+ new ItemOperation ( 'authorizations' , `${ currentUrl } /authorizations` , FeatureID . CanManagePolicies , true ) ,
122+ new ItemOperation ( 'mappedCollections' , `${ currentUrl } /mapper` , FeatureID . CanManageMappings , true ) ,
123+ item . isWithdrawn
124+ ? new ItemOperation ( 'reinstate' , `${ currentUrl } /reinstate` , FeatureID . ReinstateItem , true )
125+ : new ItemOperation ( 'withdraw' , `${ currentUrl } /withdraw` , FeatureID . WithdrawItem , true ) ,
126+ item . isDiscoverable
127+ ? new ItemOperation ( 'private' , `${ currentUrl } /private` , FeatureID . CanMakePrivate , true )
128+ : new ItemOperation ( 'public' , `${ currentUrl } /public` , FeatureID . CanMakePrivate , true ) ,
129+ new ItemOperation ( 'move' , `${ currentUrl } /move` , FeatureID . CanMove , true ) ,
130+ new ItemOperation ( 'delete' , `${ currentUrl } /delete` , FeatureID . CanDelete , true )
131+ ] ;
132+
133+ this . operations$ . next ( inititalOperations ) ;
134+
135+ /**
136+ * When the identifier data stream changes, determine whether the register DOI button should be shown or not.
137+ * This is based on whether the DOI is in the right state (minted or pending, not already queued for registration
138+ * or registered) and whether the configuration property identifiers.item-status.register-doi is true
139+ */
140+ const ops$ = this . identifierDataService . getIdentifierDataFor ( item ) . pipe (
141+ getFirstCompletedRemoteData ( ) ,
142+ mergeMap ( ( dataRD : RemoteData < IdentifierData > ) => {
143+ if ( dataRD . hasSucceeded ) {
144+ let identifiers = dataRD . payload . identifiers ;
145+ let no_doi = true ;
146+ let pending = false ;
147+ if ( identifiers !== undefined && identifiers !== null ) {
148+ identifiers . forEach ( ( identifier : Identifier ) => {
149+ if ( hasValue ( identifier ) && identifier . identifierType === 'doi' ) {
150+ // The item has some kind of DOI
151+ no_doi = false ;
152+ if ( [ 'PENDING' , 'MINTED' , null ] . includes ( identifier . identifierStatus ) ) {
153+ // The item's DOI is pending, minted or null.
154+ // It isn't registered, reserved, queued for registration or reservation or update, deleted
155+ // or queued for deletion.
156+ pending = true ;
157+ }
158+ }
159+ } ) ;
165160 }
166- } ) ;
167- }
168- // If there is no DOI, or a pending/minted/null DOI, and the config is enabled, return true
169- return registerConfigEnabled$ . pipe (
170- map ( ( enabled : boolean ) => {
171- return enabled && ( pending || no_doi ) ;
161+ // If there is no DOI, or a pending/minted/null DOI, and the config is enabled, return true
162+ return registerConfigEnabled$ . pipe (
163+ map ( ( enabled : boolean ) => {
164+ return enabled && ( pending || no_doi ) ;
165+ }
166+ ) ) ;
167+ } else {
168+ return of ( false ) ;
169+ }
170+ } ) ,
171+ // Switch map pushes the register DOI operation onto a copy of the base array then returns to the pipe
172+ switchMap ( ( showDoi : boolean ) => {
173+ const ops = [ ...inititalOperations ] ;
174+ if ( showDoi ) {
175+ const op = new ItemOperation ( 'register-doi' , `${ currentUrl } /register-doi` , FeatureID . CanRegisterDOI , true ) ;
176+ ops . splice ( ops . length - 1 , 0 , op ) ; // Add item before last
177+ }
178+ return inititalOperations ;
179+ } ) ,
180+ concatMap ( ( op : ItemOperation ) => {
181+ if ( hasValue ( op . featureID ) ) {
182+ return this . authorizationService . isAuthorized ( op . featureID , item . self ) . pipe (
183+ distinctUntilChanged ( ) ,
184+ map ( ( authorized ) => {
185+ op . setDisabled ( ! authorized ) ;
186+ op . setAuthorized ( authorized ) ;
187+ return op ;
188+ } )
189+ ) ;
172190 }
173- ) ) ;
174- } ) ,
175- // Switch map pushes the register DOI operation onto a copy of the base array then returns to the pipe
176- switchMap ( ( showDoi : boolean ) => {
177- let ops = [ ...operations ] ;
178- if ( showDoi ) {
179- ops . push ( new ItemOperation ( 'register-doi' , this . getCurrentUrl ( item ) + '/register-doi' , FeatureID . CanRegisterDOI , true ) ) ;
180- }
181- return ops ;
182- } ) ,
183- // Merge map checks and transforms each operation in the array based on whether it is authorized or not (disabled)
184- mergeMap ( ( op : ItemOperation ) => {
185- if ( hasValue ( op . featureID ) ) {
186- return this . authorizationService . isAuthorized ( op . featureID , item . self ) . pipe (
187- distinctUntilChanged ( ) ,
188- map ( ( authorized ) => new ItemOperation ( op . operationKey , op . operationUrl , op . featureID , ! authorized , authorized ) )
189- ) ;
190- } else {
191191 return [ op ] ;
192- }
193- } ) ,
194- // Wait for all operations to be emitted and return as an array
195- toArray ( ) ,
196- ) . subscribe ( ( data ) => {
197- // Update the operations$ subject that draws the administrative buttons on the status page
198- this . operations$ . next ( data ) ;
199- } ) ;
200- } ) ;
192+ } ) ,
193+ toArray ( )
194+ ) ;
195+
196+ let orcidOps$ = of ( [ ] ) ;
197+ if ( this . orcidAuthService . isLinkedToOrcid ( item ) ) {
198+ orcidOps$ = this . orcidAuthService . onlyAdminCanDisconnectProfileFromOrcid ( ) . pipe (
199+ map ( ( canDisconnect ) => {
200+ if ( canDisconnect ) {
201+ return [ new ItemOperation ( 'unlinkOrcid' , `${ currentUrl } /unlink-orcid` ) ] ;
202+ }
203+ return [ ] ;
204+ } )
205+ ) ;
206+ }
207+
208+ return combineLatest ( [ ops$ , orcidOps$ ] ) ;
209+ } ) ,
210+ map ( ( [ ops , orcidOps ] : [ ItemOperation [ ] , ItemOperation [ ] ] ) => [ ...ops , ...orcidOps ] )
211+ ) . subscribe ( ( ops ) => this . operations$ . next ( ops ) ) ;
201212
202213 this . itemPageRoute$ = this . itemRD$ . pipe (
203214 getAllSucceededRemoteDataPayload ( ) ,
@@ -206,6 +217,7 @@ export class ItemStatusComponent implements OnInit {
206217
207218 }
208219
220+
209221 /**
210222 * Get the current url without query params
211223 * @returns {string } url
0 commit comments