diff --git a/src/app/app-routing-paths.ts b/src/app/app-routing-paths.ts index e9a6376884..fe2837c6e3 100644 --- a/src/app/app-routing-paths.ts +++ b/src/app/app-routing-paths.ts @@ -126,3 +126,9 @@ export function getRequestCopyModulePath() { } export const HEALTH_PAGE_PATH = 'health'; + +export const SUBSCRIPTIONS_MODULE_PATH = 'subscriptions'; + +export function getSubscriptionsModuleRoute() { + return `/${SUBSCRIPTIONS_MODULE_PATH}`; +} diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index d426b041ce..9779c2ab27 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -230,6 +230,12 @@ import { ThemedPageErrorComponent } from './page-error/themed-page-error.compone loadChildren: () => import('./access-control/access-control.module').then((m) => m.AccessControlModule), canActivate: [GroupAdministratorGuard], }, + { + path: 'subscriptions', + loadChildren: () => import('./subscriptions-page/subscriptions-page-routing.module') + .then((m) => m.SubscriptionsPageRoutingModule), + canActivate: [AuthenticatedGuard] + }, { path: '**', pathMatch: 'full', component: ThemedPageNotFoundComponent }, ] } diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index ede23ba43b..6ac92d95f2 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -170,6 +170,7 @@ import { OrcidHistory } from './orcid/model/orcid-history.model'; import { OrcidAuthService } from './orcid/orcid-auth.service'; import { VocabularyDataService } from './submission/vocabularies/vocabulary.data.service'; import { VocabularyEntryDetailsDataService } from './submission/vocabularies/vocabulary-entry-details.data.service'; +import { Subscription } from '../shared/subscriptions/models/subscription.model'; /** * When not in production, endpoint responses can be mocked for testing purposes @@ -356,7 +357,8 @@ export const models = ResearcherProfile, OrcidQueue, OrcidHistory, - AccessStatusObject + AccessStatusObject, + Subscription ]; @NgModule({ diff --git a/src/app/core/data/feature-authorization/feature-id.ts b/src/app/core/data/feature-authorization/feature-id.ts index 3cb18bf515..d381a8214d 100644 --- a/src/app/core/data/feature-authorization/feature-id.ts +++ b/src/app/core/data/feature-authorization/feature-id.ts @@ -29,5 +29,6 @@ export enum FeatureID { CanViewUsageStatistics = 'canViewUsageStatistics', CanSendFeedback = 'canSendFeedback', CanClaimItem = 'canClaimItem', - CanSynchronizeWithORCID = 'canSynchronizeWithORCID' + CanSynchronizeWithORCID = 'canSynchronizeWithORCID', + CanSubscribe = 'canSubscribeDso', } diff --git a/src/app/item-page/simple/item-types/publication/publication.component.html b/src/app/item-page/simple/item-types/publication/publication.component.html index 181480b789..d248ee3416 100644 --- a/src/app/item-page/simple/item-types/publication/publication.component.html +++ b/src/app/item-page/simple/item-types/publication/publication.component.html @@ -15,6 +15,7 @@ [tooltipMsgCreate]="'item.page.version.create'" [tooltipMsgHasDraft]="'item.page.version.hasDraft'"> +
diff --git a/src/app/shared/auth-nav-menu/user-menu/user-menu.component.html b/src/app/shared/auth-nav-menu/user-menu/user-menu.component.html index e730b0d85c..5643f3b9a8 100644 --- a/src/app/shared/auth-nav-menu/user-menu/user-menu.component.html +++ b/src/app/shared/auth-nav-menu/user-menu/user-menu.component.html @@ -6,6 +6,8 @@ {{'nav.profile' | translate}} {{'nav.mydspace' | translate}} + {{'nav.subscriptions' | translate}} +
diff --git a/src/app/shared/auth-nav-menu/user-menu/user-menu.component.ts b/src/app/shared/auth-nav-menu/user-menu/user-menu.component.ts index 22b076c31a..114c711a9b 100644 --- a/src/app/shared/auth-nav-menu/user-menu/user-menu.component.ts +++ b/src/app/shared/auth-nav-menu/user-menu/user-menu.component.ts @@ -8,7 +8,7 @@ import { AppState } from '../../../app.reducer'; import { isAuthenticationLoading } from '../../../core/auth/selectors'; import { MYDSPACE_ROUTE } from '../../../my-dspace-page/my-dspace-page.component'; import { AuthService } from '../../../core/auth/auth.service'; -import { getProfileModuleRoute } from '../../../app-routing-paths'; +import { getProfileModuleRoute, getSubscriptionsModuleRoute } from '../../../app-routing-paths'; /** * This component represents the user nav menu. @@ -48,6 +48,11 @@ export class UserMenuComponent implements OnInit { */ public profileRoute = getProfileModuleRoute(); + /** + * The profile page route + */ + public subscriptionsRoute = getSubscriptionsModuleRoute(); + constructor(private store: Store, private authService: AuthService) { } diff --git a/src/app/shared/dso-page/dso-page-subscription-button/dso-page-subscription-button.component.html b/src/app/shared/dso-page/dso-page-subscription-button/dso-page-subscription-button.component.html new file mode 100644 index 0000000000..9956163944 --- /dev/null +++ b/src/app/shared/dso-page/dso-page-subscription-button/dso-page-subscription-button.component.html @@ -0,0 +1,6 @@ + diff --git a/src/app/shared/dso-page/dso-page-subscription-button/dso-page-subscription-button.component.scss b/src/app/shared/dso-page/dso-page-subscription-button/dso-page-subscription-button.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/shared/dso-page/dso-page-subscription-button/dso-page-subscription-button.component.spec.ts b/src/app/shared/dso-page/dso-page-subscription-button/dso-page-subscription-button.component.spec.ts new file mode 100644 index 0000000000..20a99cb926 --- /dev/null +++ b/src/app/shared/dso-page/dso-page-subscription-button/dso-page-subscription-button.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DsoPageSubscriptionButtonComponent } from './dso-page-subscription-button.component'; + +describe('DsoPageSubscriptionButtonComponent', () => { + let component: DsoPageSubscriptionButtonComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ DsoPageSubscriptionButtonComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(DsoPageSubscriptionButtonComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/shared/dso-page/dso-page-subscription-button/dso-page-subscription-button.component.ts b/src/app/shared/dso-page/dso-page-subscription-button/dso-page-subscription-button.component.ts new file mode 100644 index 0000000000..526a99355f --- /dev/null +++ b/src/app/shared/dso-page/dso-page-subscription-button/dso-page-subscription-button.component.ts @@ -0,0 +1,57 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { Observable, of } from 'rxjs'; +import { DSpaceObject } from '../../../core/shared/dspace-object.model'; +import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; +import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; +import { AuthService } from '../../../core/auth/auth.service'; +import { EPerson } from '../../../core/eperson/models/eperson.model'; +import { take } from 'rxjs/operators'; +import { + SubscriptionModalComponent +} from '../../subscriptions/components/subscription-modal/subscription-modal.component'; +import { FeatureID } from '../../../core/data/feature-authorization/feature-id'; + +@Component({ + selector: 'ds-dso-page-subscription-button', + templateUrl: './dso-page-subscription-button.component.html', + styleUrls: ['./dso-page-subscription-button.component.scss'] +}) +export class DsoPageSubscriptionButtonComponent implements OnInit { + + /** + * Whether the current user is authorized to edit the DSpaceObject + */ + isAuthorized$: Observable = of(false); + + /** + * Reference to NgbModal + */ + public modalRef: NgbModalRef; + + /** + * EPerson id of the logged user + */ + ePersonId: string; + + /** + * DSpaceObject that is being viewed + */ + @Input() + dso: DSpaceObject; + + constructor( + protected authorizationService: AuthorizationDataService, + private modalService: NgbModal, + ) { + } + + ngOnInit(): void { + this.isAuthorized$ = this.authorizationService.isAuthorized(FeatureID.CanSubscribe, this.dso.self); + } + + public openSubscriptionModal() { + this.modalRef = this.modalService.open(SubscriptionModalComponent); + this.modalRef.componentInstance.dso = this.dso; + } + +} diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 13fdd3e12c..797587750b 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -315,6 +315,7 @@ import { MenuModule } from './menu/menu.module'; import { ListableNotificationObjectComponent } from './object-list/listable-notification-object/listable-notification-object.component'; +import { DsoPageSubscriptionButtonComponent } from './dso-page/dso-page-subscription-button/dso-page-subscription-button.component'; const MODULES = [ CommonModule, @@ -510,8 +511,8 @@ const SHARED_ITEM_PAGE_COMPONENTS = [ GenericItemPageFieldComponent, MetadataRepresentationListComponent, RelatedItemsComponent, - DsoPageOrcidButtonComponent - + DsoPageOrcidButtonComponent, + DsoPageSubscriptionButtonComponent, ]; const PROVIDERS = [ diff --git a/src/app/shared/subscriptions/components/subscription-edit-modal/subscription-edit-modal.component.html b/src/app/shared/subscriptions/components/subscription-edit-modal/subscription-edit-modal.component.html new file mode 100644 index 0000000000..7614005dff --- /dev/null +++ b/src/app/shared/subscriptions/components/subscription-edit-modal/subscription-edit-modal.component.html @@ -0,0 +1,44 @@ + + + diff --git a/src/app/shared/subscriptions/components/subscription-edit-modal/subscription-edit-modal.component.scss b/src/app/shared/subscriptions/components/subscription-edit-modal/subscription-edit-modal.component.scss new file mode 100644 index 0000000000..fb9db5b0ff --- /dev/null +++ b/src/app/shared/subscriptions/components/subscription-edit-modal/subscription-edit-modal.component.scss @@ -0,0 +1,4 @@ +.alert{ + font-weight: bold; + color:red; +} diff --git a/src/app/shared/subscriptions/components/subscription-edit-modal/subscription-edit-modal.component.spec.ts b/src/app/shared/subscriptions/components/subscription-edit-modal/subscription-edit-modal.component.spec.ts new file mode 100644 index 0000000000..d406fa2c1a --- /dev/null +++ b/src/app/shared/subscriptions/components/subscription-edit-modal/subscription-edit-modal.component.spec.ts @@ -0,0 +1,116 @@ +import { ComponentFixture, ComponentFixtureAutoDetect, fakeAsync, TestBed, waitForAsync } from '@angular/core/testing'; +import { cold } from 'jasmine-marbles'; + +// Import modules +import { CommonModule } from '@angular/common'; +import { ReactiveFormsModule } from '@angular/forms'; +import { By } from '@angular/platform-browser'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { DebugElement } from '@angular/core'; + +import { SubscriptionEditModalComponent } from './subscription-edit-modal.component'; + +// Import mocks +import { TranslateLoaderMock } from '../../../mocks/translate-loader.mock'; +import { subscription } from '../../../testing/subscriptions-data.mock'; +import { ItemInfo } from '../../../testing/relationships-mocks'; + +// Import utils +import { NotificationsService } from '../../../notifications/notifications.service'; +import { NotificationsServiceStub } from '../../../testing/notifications-service.stub'; +import { SubscriptionService } from '../../subscription.service'; +import { Subscription } from '../../models/subscription.model'; + + +describe('SubscriptionEditModalComponent', () => { + let component: SubscriptionEditModalComponent; + let fixture: ComponentFixture; + let de: DebugElement; + + const subscriptionServiceStub = jasmine.createSpyObj('SubscriptionService', { + updateSubscription: jasmine.createSpy('updateSubscription'), + }); + + + beforeEach(waitForAsync (() => { + TestBed.configureTestingModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock + } + }), + ], + declarations: [ SubscriptionEditModalComponent ], + providers: [ + { provide: ComponentFixtureAutoDetect, useValue: true }, + { provide: NotificationsService, useValue: NotificationsServiceStub }, + { provide: SubscriptionService, useValue: subscriptionServiceStub }, + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SubscriptionEditModalComponent); + component = fixture.componentInstance; + component.eperson = 'testid123'; + component.dso = ItemInfo.payload; + + de = fixture.debugElement; + + subscriptionServiceStub.updateSubscription.and.returnValue(cold('a|', { + a: {} + })); + + + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('No Subscription inserted', () => { + it('should not show form', () => { + expect(de.query(By.css('form'))).toBeNull(); + }); + }); + + + describe('Subscription inserted', () => { + + beforeEach(fakeAsync(() => { + component.subscription = Object.assign(new Subscription(), subscription); + component.ngOnInit(); + fixture.detectChanges(); + })); + + it('when insert subscription show form', () => { + expect(de.query(By.css('form'))).toBeTruthy(); + }); + + it('should have right checkboxes checked', () => { + expect(de.query(By.css('#checkbox-0'))?.nativeElement?.checked).toEqual(true); + expect(de.query(By.css('#checkbox-1'))?.nativeElement?.checked).toEqual(true); + expect(de.query(By.css('#checkbox-2'))?.nativeElement?.checked).toEqual(false); + }); + + it('on checkbox clicked should change form values', () => { + const checkbox = de.query(By.css('#checkbox-2')).nativeElement; + checkbox.click(); + + expect(de.query(By.css('#checkbox-2'))?.nativeElement?.checked).toEqual(true); + expect(component.subscriptionParameterList?.value?.length).toEqual(3); + }); + + it('on submit clicked update should have been called', () => { + const button = de.query(By.css('.btn-success')).nativeElement; + button.click(); + expect(subscriptionServiceStub.updateSubscription).toHaveBeenCalled(); + }); + }); +}); diff --git a/src/app/shared/subscriptions/components/subscription-edit-modal/subscription-edit-modal.component.ts b/src/app/shared/subscriptions/components/subscription-edit-modal/subscription-edit-modal.component.ts new file mode 100644 index 0000000000..46c895dc18 --- /dev/null +++ b/src/app/shared/subscriptions/components/subscription-edit-modal/subscription-edit-modal.component.ts @@ -0,0 +1,190 @@ +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; + +import { FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms'; + +import { Subscription } from '../../models/subscription.model'; + +import { BehaviorSubject } from 'rxjs'; + +import { DSpaceObject } from '../../../../core/shared/dspace-object.model'; + +import { SubscriptionService } from '../../subscription.service'; +import { NotificationsService } from '../../../notifications/notifications.service'; + +import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; + +@Component({ + selector: 'ds-subscription-edit-modal', + templateUrl: './subscription-edit-modal.component.html', + styleUrls: ['./subscription-edit-modal.component.scss'] +}) +export class SubscriptionEditModalComponent implements OnInit { + + + /** + * DSpaceObject of the subscription + */ + @Input() dso: DSpaceObject; + + /** + * EPerson of the subscription + */ + @Input() eperson: string; + + /** + * List of subscription for the dso object and eperson relation + */ + @Input() subscription!: Subscription; + + /** + * Close event emit to close modal + */ + @Output() close: EventEmitter = new EventEmitter(); + + /** + * Reload event emit to refresh informations + */ + @Output() reload: EventEmitter = new EventEmitter(); + + /** + * A boolean representing if a request operation is pending + * @type {BehaviorSubject} + */ + public processing$ = new BehaviorSubject(false); + + /** + * Reactive form group that will be used to add subscriptions + */ + subscriptionForm: FormGroup; + + /** + * Used to show validation errors when user submits + */ + submitted = false; + + /** + * Reference to NgbModal + */ + public modalRef: NgbModalRef; + + + /** + * Frequencies to be shown as checkboxes + */ + frequencies = [ + {name: 'daily' ,value: 'D'}, + {name: 'monthly' ,value: 'M'}, + {name: 'weekly' ,value: 'W'}, + ]; + + constructor(private formGroup: FormBuilder, + private notificationsService: NotificationsService, + private subscriptionService: SubscriptionService + ) {} + + /** + * When component starts initialize starting functionality + */ + ngOnInit(): void { + this.initSubscription(); + } + + /** + * If the subscription is passed start the form with the information of subscription + */ + initSubscription(): void { + if (!!this.subscription) { + this.buildFormBuilder(this.subscription); + } + } + + /** + * Function to get subscriptionParameterList form array cleaner + */ + get subscriptionParameterList(): FormArray { + return this.subscriptionForm.get('subscriptionParameterList') as FormArray; + } + + /** + * When frequency checkboxes are being changed we add/remove frequencies from subscriptionParameterList + */ + selectCheckbox(event,frequency): void { + if (event.target.checked) { + this.addFrequency(frequency); + } else { + this.removeFrequency(frequency); + } + } + + /** + * Start the form with preinserted informations + */ + buildFormBuilder(subscription): void { + + this.subscriptionForm = this.formGroup.group({ + id: subscription.id, + type: subscription.subscriptionType, + subscriptionParameterList: this.formGroup.array([], Validators.required) + }); + + subscription.subscriptionParameterList.forEach( (parameter) => { + this.addFrequency(parameter.value); + }); + } + + /** + * Add a new frequency to the subscriptionParameterList form array + */ + addFrequency(frequency): void { + this.subscriptionParameterList.push( + this.formGroup.group({ + name: 'frequency', + value: frequency + }) + ); + } + + /** + * Remove frequency from subscriptionParameterList form array + */ + removeFrequency(frequency): void { + const index = this.subscriptionParameterList.controls.findIndex(el => el.value.value === frequency); + this.subscriptionParameterList.removeAt(index); + } + + /** + * When user saves it will check if form is valid and send request to update subscription + */ + submit(): void { + this.submitted = true; + if (this.subscriptionForm.valid) { + if (this.subscriptionForm.value.id) { + this.updateForm(this.subscriptionForm.value); + } + } + } + + /** + * Sends request to update a new subscription, refreshes the table of subscriptions and notifies about summary page + */ + updateForm(body): void { + this.subscriptionService.updateSubscription(body,this.eperson,this.dso.uuid).subscribe( (res) => { + this.reload.emit(); + this.close.emit(); + }); + } + + /** + * When close button is pressed emit function to close modal + */ + c(text): void { + this.close.emit(text); + } + + /** + * Returns if a specific frequency exists in the subscriptionParameterList + */ + getIsChecked(frequency): boolean { + return !!this.subscriptionForm.get('subscriptionParameterList').value.find(el => el.value === frequency.value); + } +} diff --git a/src/app/shared/subscriptions/components/subscription-modal/subscription-modal.component.html b/src/app/shared/subscriptions/components/subscription-modal/subscription-modal.component.html new file mode 100644 index 0000000000..53119d9a15 --- /dev/null +++ b/src/app/shared/subscriptions/components/subscription-modal/subscription-modal.component.html @@ -0,0 +1,46 @@ +
+ + + + +
diff --git a/src/app/shared/subscriptions/components/subscription-modal/subscription-modal.component.scss b/src/app/shared/subscriptions/components/subscription-modal/subscription-modal.component.scss new file mode 100644 index 0000000000..62dd1105e7 --- /dev/null +++ b/src/app/shared/subscriptions/components/subscription-modal/subscription-modal.component.scss @@ -0,0 +1,12 @@ +.alert{ + font-weight: bold; + color:red; +} + +// .modal-footer{ +// justify-content: space-between; +// } + +.add-button{ + padding: 0px 15px 15px 15px; +} \ No newline at end of file diff --git a/src/app/shared/subscriptions/components/subscription-modal/subscription-modal.component.spec.ts b/src/app/shared/subscriptions/components/subscription-modal/subscription-modal.component.spec.ts new file mode 100644 index 0000000000..088ec3004c --- /dev/null +++ b/src/app/shared/subscriptions/components/subscription-modal/subscription-modal.component.spec.ts @@ -0,0 +1,223 @@ +import { ComponentFixture, ComponentFixtureAutoDetect, TestBed } from '@angular/core/testing'; + +import { of as observableOf } from 'rxjs'; + +// Import modules +import { CommonModule } from '@angular/common'; +import { ReactiveFormsModule } from '@angular/forms'; +import { By } from '@angular/platform-browser'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { DebugElement } from '@angular/core'; + +import { SubscriptionModalComponent } from './subscription-modal.component'; + +// Import mocks +import { TranslateLoaderMock } from '../../../mocks/translate-loader.mock'; +import { findByEPersonAndDsoRes, findByEPersonAndDsoResEmpty } from '../../../testing/subscriptions-data.mock'; +import { ItemInfo } from '../../../testing/relationships-mocks'; + +// Import utils +import { NotificationsService } from '../../../notifications/notifications.service'; +import { SubscriptionService } from '../../subscription.service'; + +import { createSuccessfulRemoteDataObject$ } from '../../../remote-data.utils'; + + +describe('SubscriptionModalComponent', () => { + let component: SubscriptionModalComponent; + let fixture: ComponentFixture; + let de: DebugElement; + + let subscriptionServiceStub; + const notificationServiceStub = { + notificationWithAnchor() { + return true; + } + }; + + + describe('when empty subscriptions', () => { + + beforeEach(async () => { + + subscriptionServiceStub = jasmine.createSpyObj('SubscriptionService', { + getSubscriptionByPersonDSO: observableOf(findByEPersonAndDsoResEmpty), + createSubscription: createSuccessfulRemoteDataObject$({}), + updateSubscription: createSuccessfulRemoteDataObject$({}), + }); + + await TestBed.configureTestingModule({ + imports: [ + CommonModule, + NgbModule, + ReactiveFormsModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock + } + }), + ], + declarations: [SubscriptionModalComponent], + providers: [ + { provide: ComponentFixtureAutoDetect, useValue: true }, + { provide: NotificationsService, useValue: notificationServiceStub }, + { provide: SubscriptionService, useValue: subscriptionServiceStub }, + ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(SubscriptionModalComponent); + component = fixture.componentInstance; + component.ePersonId = 'testid123'; + component.dso = ItemInfo.payload; + de = fixture.debugElement; + + await fixture.whenStable(); + await fixture.whenRenderingDone(); + + fixture.detectChanges(); + + }); + + it('should be no table', () => { + expect(de.query(By.css('table'))).toBeNull(); + }); + + it('should show empty form', () => { + expect(de.query(By.css('form'))).toBeTruthy(); + }); + + it('should show form with empty checkboxes', () => { + expect(de.query(By.css('#checkbox-0'))?.nativeElement?.checked).toEqual(false); + expect(de.query(By.css('#checkbox-1'))?.nativeElement?.checked).toEqual(false); + expect(de.query(By.css('#checkbox-2'))?.nativeElement?.checked).toEqual(false); + }); + + }); + + + describe('when we have subscriptions', () => { + + beforeEach(async () => { + + subscriptionServiceStub = jasmine.createSpyObj('SubscriptionService', { + getSubscriptionByPersonDSO: observableOf(findByEPersonAndDsoRes), + createSubscription: createSuccessfulRemoteDataObject$({}), + updateSubscription: createSuccessfulRemoteDataObject$({}), + }); + + await TestBed.configureTestingModule({ + imports: [ + CommonModule, + NgbModule, + ReactiveFormsModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock + } + }), + ], + declarations: [SubscriptionModalComponent], + providers: [ + { provide: ComponentFixtureAutoDetect, useValue: true }, + { provide: NotificationsService, useValue: notificationServiceStub }, + { provide: SubscriptionService, useValue: subscriptionServiceStub }, + ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(SubscriptionModalComponent); + component = fixture.componentInstance; + component.ePersonId = 'testid123'; + component.dso = ItemInfo.payload; + de = fixture.debugElement; + await fixture.whenStable(); + await fixture.whenRenderingDone(); + + fixture.detectChanges(); + + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should render 2 subscriptions', () => { + expect(de.queryAll(By.css('tbody > tr')).length).toEqual(2); + }); + + it('should show no form', () => { + expect(de.query(By.css('form'))).toBeNull(); + }); + + it('should have 2 edit buttons', () => { + expect(de.queryAll(By.css('.btn-outline-primary')).length).toEqual(2); + }); + + it('should have 2 delete buttons', () => { + expect(de.queryAll(By.css('.btn-outline-danger')).length).toEqual(2); + }); + + describe('When creating new subscription', () => { + + beforeEach(() => { + // add button click + const button = de.query(By.css('.btn-success')).nativeElement; + button.click(); + }); + + + it('should show form when add button click event', () => { + expect(de.query(By.css('form'))).toBeTruthy(); + }); + + it('should show form with empty checkboxes', () => { + expect(de.query(By.css('#checkbox-0'))?.nativeElement?.checked).toEqual(false); + expect(de.query(By.css('#checkbox-1'))?.nativeElement?.checked).toEqual(false); + expect(de.query(By.css('#checkbox-2'))?.nativeElement?.checked).toEqual(false); + }); + + it('should call create request when submit click event', () => { + const checkbox = de.query(By.css('#checkbox-2')).nativeElement; + checkbox.click(); + + const button = de.queryAll(By.css('.btn-success'))[1].nativeElement; + button.click(); + expect(subscriptionServiceStub.createSubscription).toHaveBeenCalled(); + }); + + }); + + + describe('When updating subscription', () => { + + beforeEach(() => { + // edit button click + const button = de.query(By.css('.btn-outline-primary')).nativeElement; + button.click(); + }); + + it('should show form when edit button click event', () => { + expect(de.query(By.css('form'))).toBeTruthy(); + }); + + it('should show form with empty checkboxes', () => { + expect(de.query(By.css('#checkbox-0'))?.nativeElement?.checked).toEqual(false); + expect(de.query(By.css('#checkbox-1'))?.nativeElement?.checked).toEqual(true); + expect(de.query(By.css('#checkbox-2'))?.nativeElement?.checked).toEqual(false); + }); + + it('should call update request when submit click event', () => { + const button = de.queryAll(By.css('.btn-success'))[1].nativeElement; + button.click(); + expect(subscriptionServiceStub.updateSubscription).toHaveBeenCalled(); + }); + + }); + + }); + +}); diff --git a/src/app/shared/subscriptions/components/subscription-modal/subscription-modal.component.ts b/src/app/shared/subscriptions/components/subscription-modal/subscription-modal.component.ts new file mode 100644 index 0000000000..c249cc592b --- /dev/null +++ b/src/app/shared/subscriptions/components/subscription-modal/subscription-modal.component.ts @@ -0,0 +1,306 @@ +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; + +import { + AbstractControl, + FormArray, + FormBuilder, + FormControl, + FormGroup, + ValidatorFn, + Validators +} from '@angular/forms'; + +import { Subscription } from '../../models/subscription.model'; + +import { BehaviorSubject, Observable } from 'rxjs'; + +import { DSpaceObject } from '../../../../core/shared/dspace-object.model'; + +import { SubscriptionService } from '../../subscription.service'; +import { NotificationsService } from '../../../notifications/notifications.service'; +import { NotificationType } from '../../../notifications/models/notification-type'; +import { NotificationOptions } from '../../../notifications/models/notification-options.model'; + +import { NgbActiveModal, NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; + +import { PaginatedList } from '../../../../core/data/paginated-list.model'; + +import { hasValue } from '../../../empty.util'; +import { ConfirmationModalComponent } from '../../../confirmation-modal/confirmation-modal.component'; + +import { filter, map, switchMap, take, tap } from 'rxjs/operators'; +import { NoContent } from '../../../../core/shared/NoContent.model'; +import { RemoteData } from '../../../../core/data/remote-data'; +import { getFirstSucceededRemoteDataPayload } from '../../../../core/shared/operators'; +import { EPerson } from '../../../../core/eperson/models/eperson.model'; +import { AuthService } from '../../../../core/auth/auth.service'; + +@Component({ + selector: 'ds-subscription-modal', + templateUrl: './subscription-modal.component.html', + styleUrls: ['./subscription-modal.component.scss'] +}) +export class SubscriptionModalComponent implements OnInit { + + /** + * DSpaceObject of which to get the subscriptions + */ + @Input() dso: DSpaceObject; + + /** + * List of subscription for the dso object and eperson relation + */ + subscriptions: Subscription[]; + + /** + * A boolean representing if a request operation is pending + * @type {BehaviorSubject} + */ + public processing$ = new BehaviorSubject(false); + + /** + * Reactive form group that will be used to add subscriptions + */ + subscriptionForm: FormGroup; + + /** + * Used to show validation errors when user submits + */ + submitted = false; + + ePersonId$: Observable; + + /** + * Types of subscription to be shown on select + */ + subscriptionTypes = [ 'content', 'statistics' ]; + + /** + * Frequencies to be shown as checkboxes + */ + frequencies = [ 'D', 'M', 'W' ]; + + constructor( + private formBuilder: FormBuilder, + private modalService: NgbModal, + private notificationsService: NotificationsService, + private subscriptionService: SubscriptionService, + public activeModal: NgbActiveModal, + private authService: AuthService, + ) { + } + + /** + * When component starts initialize starting functionality + */ + ngOnInit(): void { + + this.ePersonId$ = this.authService.getAuthenticatedUserFromStore().pipe( + take(1), + map((ePerson) => ePerson.uuid), + ); + + this.subscriptionForm = this.formBuilder.group({}); + + for (let f of this.frequencies) { + this.subscriptionForm.addControl(f, this.formBuilder.control(false)); + } + + // TODO iterate over subscription types + + /*this.subscriptionForm = this.formBuilder.group({}); + for (let t of this.subscriptionTypes) { + this.subscriptionForm.addControl(t, this.formBuilder.group({})); + for (let f of this.frequencies) { + this.subscriptionForm[t].addControl(f, this.formBuilder.control(false)); + } + }*/ + + + this.initSubscription(); + + } + + /** + * Get subscription for the eperson & dso object relation + * If no subscription start with an empty form + */ + initSubscription(): void { + this.processing$.next(true); + this.ePersonId$.pipe( + tap(console.log), + switchMap((ePersonId: string) => this.getSubscription(ePersonId, this.dso?.uuid)), + getFirstSucceededRemoteDataPayload(), + ).subscribe({ + next: (res: PaginatedList) => { + if (res.pageInfo.totalElements > 0) { + this.subscriptions = res.page; + + // TODO loop over subscription types + // for (let type of this.subscriptionTypes) { + const type = 'content'; // remove + const subscription = this.subscriptions.find((s) => s.subscriptionType === type); + // TODO manage multiple subscriptions with same tipe (there should be only one) + for (let parameter of subscription.subscriptionParameterList.filter((p) => p.name === 'frequency')) { + this.subscriptionForm.controls[parameter.value]?.setValue(true); + } + // } + + } + this.processing$.next(false); + }, + error: err => { + this.processing$.next(false); + } + }); + } + + /** + * Function to get subscriptions based on the eperson & dso + * + * @param ePersonId Eperson that is logged in + * @param uuid DSpaceObject id that subscriptions are related to + */ + getSubscription(ePersonId: string, uuid: string): Observable>> { + return this.subscriptionService.getSubscriptionByPersonDSO(ePersonId, uuid); + } + + + + submit() { + + // TODO + + /* + - remove subscription if no checkbox is selected + - add subscription if it does not exist + - edit subscription if it already exists + */ + + const body = { + type: 'content', + subscriptionParameterList: [] + }; + + for (let frequency of this.frequencies) { + if (this.subscriptionForm.value[frequency]) { + body.subscriptionParameterList.push( + { + name: 'frequency', + value: frequency, + } + ); + } + } + + // this.subscriptionService.createSubscription(body, this.ePersonId, this.dso.uuid).subscribe((res) => { + // // this.refresh(); + // // this.notify(); + // // this.processing$.next(false); + // }, + // err => { + // // this.processing$.next(false); + // } + // ); + + } + + + /** + * Sends request to create a new subscription, refreshes the table of subscriptions and notifies about summary page + */ + /*createForm(body): void { + this.subscriptionService.createSubscription(body, this.ePersonId, this.dso.uuid).subscribe((res) => { + this.refresh(); + this.notify(); + this.processing$.next(false); + }, + err => { + this.processing$.next(false); + } + ); + }*/ + + /** + * Sends request to update a subscription, refreshes the table of subscriptions and notifies about summary page + */ + /*updateForm(body) { + this.subscriptionService.updateSubscription(body, this.ePersonId, this.dso.uuid).subscribe((res) => { + this.refresh(); + this.notify(); + this.processing$.next(false); + }, + err => { + this.processing$.next(false); + } + ); + }*/ + + + /** + * Sends the request to delete the subscription with a specific id + */ + /*deleteSubscription(id): Observable { + return this.subscriptionService.deleteSubscription(id); + }*/ + + /** + * Creates a notification with the link to the subscription summary page + */ + /*notify(): void { + const options = new NotificationOptions(); + options.timeOut = 0; + const link = '/subscriptions'; + this.notificationsService.notificationWithAnchor( + NotificationType.Success, + options, + link, + 'context-menu.actions.subscription.notification.here-text', + 'context-menu.actions.subscription.notification.content', + 'here' + ); + }*/ + + /** + * When an action is done it will reinitialize the table and remove subscription form + */ + /*refresh(): void { + this.initSubscription(); + this.subscriptionForm = null; + this.submitted = false; + }*/ + + /** + * Returns if a specific frequency exists in the subscriptionParameterList + */ + getIsChecked(frequency): boolean { + return !!this.subscriptionForm.get('subscriptionParameterList').value.find(el => el.value === frequency.value); + } + + /** + * Deletes Subscription, show notification on success/failure & updates list + * + * @param subscription Subscription to be deleted + */ + /*deleteSubscriptionPopup(subscription: Subscription): void { + if (hasValue(subscription.id)) { + const modalRef = this.modalService.open(ConfirmationModalComponent); + modalRef.componentInstance.dso = this.dso; + modalRef.componentInstance.headerLabel = 'confirmation-modal.delete-subscription.header'; + modalRef.componentInstance.infoLabel = 'confirmation-modal.delete-subscription.info'; + modalRef.componentInstance.cancelLabel = 'confirmation-modal.delete-subscription.cancel'; + modalRef.componentInstance.confirmLabel = 'confirmation-modal.delete-subscription.confirm'; + modalRef.componentInstance.brandColor = 'danger'; + modalRef.componentInstance.confirmIcon = 'fas fa-trash'; + + modalRef.componentInstance.response.pipe( + take(1), + filter((confirm: boolean) => confirm), + switchMap(() => this.deleteSubscription(subscription.id)) + ).subscribe(() => { + this.refresh(); + }); + + } + }*/ +} diff --git a/src/app/shared/subscriptions/components/subscription-view/subscription-view.component.html b/src/app/shared/subscriptions/components/subscription-view/subscription-view.component.html new file mode 100644 index 0000000000..1ff192b638 --- /dev/null +++ b/src/app/shared/subscriptions/components/subscription-view/subscription-view.component.html @@ -0,0 +1,29 @@ + + {{dso.type}} +

{{dso.name}}

+ + + {{subscription.subscriptionType}} + + + + + {{ 'subscriptions.frequency.'+ parameterList.value | translate}}, + + + + +
+ + +
+ + + + + + diff --git a/src/app/shared/subscriptions/components/subscription-view/subscription-view.component.scss b/src/app/shared/subscriptions/components/subscription-view/subscription-view.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/shared/subscriptions/components/subscription-view/subscription-view.component.spec.ts b/src/app/shared/subscriptions/components/subscription-view/subscription-view.component.spec.ts new file mode 100644 index 0000000000..e32112c5a0 --- /dev/null +++ b/src/app/shared/subscriptions/components/subscription-view/subscription-view.component.spec.ts @@ -0,0 +1,120 @@ +import { ComponentFixture, ComponentFixtureAutoDetect, TestBed } from '@angular/core/testing'; + +// Import modules +import { CommonModule } from '@angular/common'; +import { ReactiveFormsModule } from '@angular/forms'; +import { BrowserModule, By } from '@angular/platform-browser'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { SharedModule } from '../../../shared.module'; +import { DebugElement } from '@angular/core'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { SubscriptionViewComponent } from './subscription-view.component'; + +// Import mocks +import { TranslateLoaderMock } from '../../../mocks/translate-loader.mock'; +import { ItemInfo } from '../../../testing/relationships-mocks'; +import { findByEPersonAndDsoResEmpty, subscription } from '../../../testing/subscriptions-data.mock'; + +// Import utils +import { NotificationsService } from '../../../notifications/notifications.service'; +import { NotificationsServiceStub } from '../../../testing/notifications-service.stub'; +import { SubscriptionService } from '../../subscription.service'; +import { Subscription } from '../../models/subscription.model'; + +import { of as observableOf } from 'rxjs'; + +import { createSuccessfulRemoteDataObject$ } from '../../../remote-data.utils'; + + +describe('SubscriptionViewComponent', () => { + let component: SubscriptionViewComponent; + let fixture: ComponentFixture; + let de: DebugElement; + let modalService; + + const subscriptionServiceStub = jasmine.createSpyObj('SubscriptionService', { + getSubscriptionByPersonDSO: observableOf(findByEPersonAndDsoResEmpty), + deleteSubscription: createSuccessfulRemoteDataObject$({}), + updateSubscription: createSuccessfulRemoteDataObject$({}), + }); + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ + CommonModule, + NgbModule, + ReactiveFormsModule, + BrowserModule, + RouterTestingModule, + SharedModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock + } + }), + ], + declarations: [ SubscriptionViewComponent ], + providers: [ + { provide: ComponentFixtureAutoDetect, useValue: true }, + { provide: NotificationsService, useValue: NotificationsServiceStub }, + { provide: SubscriptionService, useValue: subscriptionServiceStub }, + ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(SubscriptionViewComponent); + component = fixture.componentInstance; + component.eperson = 'testid123'; + component.dso = ItemInfo.payload; + component.subscription = Object.assign(new Subscription(), subscription); + de = fixture.debugElement; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should have dso object info', () => { + expect(de.query(By.css('.dso-info > span'))).toBeTruthy(); + expect(de.query(By.css('.dso-info > p > a'))).toBeTruthy(); + }); + + it('should have subscription type info', () => { + expect(de.query(By.css('.subscription-type'))).toBeTruthy(); + }); + + it('should have subscription paramenter info', () => { + expect(de.query(By.css('.subscription-parmenters > span'))).toBeTruthy(); + }); + + it('should have subscription action info', () => { + expect(de.query(By.css('.btn-outline-primary'))).toBeTruthy(); + expect(de.query(By.css('.btn-outline-danger'))).toBeTruthy(); + }); + + it('should open modal when clicked edit button', () => { + modalService = (component as any).modalService; + const modalSpy = spyOn(modalService, 'open'); + + const editBtn = de.query(By.css('.btn-outline-primary')).nativeElement; + editBtn.click(); + + expect(modalService.open).toHaveBeenCalled(); + }); + + it('should call delete function when clicked delete button', () => { + const deleteSpy = spyOn(component, 'deleteSubscriptionPopup'); + + const deleteBtn = de.query(By.css('.btn-outline-danger')).nativeElement; + deleteBtn.click(); + + expect(deleteSpy).toHaveBeenCalled(); + }); + +}); diff --git a/src/app/shared/subscriptions/components/subscription-view/subscription-view.component.ts b/src/app/shared/subscriptions/components/subscription-view/subscription-view.component.ts new file mode 100644 index 0000000000..cb831534b6 --- /dev/null +++ b/src/app/shared/subscriptions/components/subscription-view/subscription-view.component.ts @@ -0,0 +1,102 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { Subscription } from '../../models/subscription.model'; +import { DSpaceObject } from '../../../../core/shared/dspace-object.model'; + +import { take } from 'rxjs/operators'; + +import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; +import { hasValue } from '../../../empty.util'; +import { ConfirmationModalComponent } from '../../../confirmation-modal/confirmation-modal.component'; +import { SubscriptionService } from '../../subscription.service'; + +@Component({ + // eslint-disable-next-line @angular-eslint/component-selector + selector: '[ds-subscription-view]', + templateUrl: './subscription-view.component.html', + styleUrls: ['./subscription-view.component.scss'] +}) +export class SubscriptionViewComponent { + + /** + * Subscription to be rendered + */ + @Input() subscription: Subscription; + + /** + * DSpaceObject of the subscription + */ + @Input() dso: DSpaceObject; + + /** + * EPerson of the subscription + */ + @Input() eperson: string; + + /** + * When an action is made emit a reload event + */ + @Output() reload = new EventEmitter(); + + /** + * Reference to NgbModal + */ + public modalRef: NgbModalRef; + + constructor( + private modalService: NgbModal, + private subscriptionService: SubscriptionService, + ) { } + + + /** + * Open modal + * + * @param content + */ + public openSubscription(content: any) { + this.modalRef = this.modalService.open(content); + } + + /** + * Return the prefix of the route to the dso object page ( e.g. "items") + */ + getPageRoutePrefix(): string { + let routePrefix; + switch (this.dso.type.toString()) { + case 'community': + routePrefix = '/communities'; + break; + case 'collection': + routePrefix = '/collections'; + break; + case 'item': + routePrefix = '/items'; + break; + } + return routePrefix; + } + + /** + * Deletes Subscription, show notification on success/failure & updates list + * @param subscription Subscription to be deleted + */ + deleteSubscriptionPopup(subscription: Subscription) { + if (hasValue(subscription.id)) { + const modalRef = this.modalService.open(ConfirmationModalComponent); + modalRef.componentInstance.dso = this.dso; + modalRef.componentInstance.headerLabel = 'confirmation-modal.delete-subscription.header'; + modalRef.componentInstance.infoLabel = 'confirmation-modal.delete-subscription.info'; + modalRef.componentInstance.cancelLabel = 'confirmation-modal.delete-subscription.cancel'; + modalRef.componentInstance.confirmLabel = 'confirmation-modal.delete-subscription.confirm'; + modalRef.componentInstance.brandColor = 'danger'; + modalRef.componentInstance.confirmIcon = 'fas fa-trash'; + modalRef.componentInstance.response.pipe(take(1)).subscribe((confirm: boolean) => { + if (confirm) { + this.subscriptionService.deleteSubscription(subscription.id).subscribe( (res) => { + this.reload.emit(); + }); + } + }); + } + } +} diff --git a/src/app/shared/subscriptions/models/subscription.model.ts b/src/app/shared/subscriptions/models/subscription.model.ts new file mode 100644 index 0000000000..b5d6977d39 --- /dev/null +++ b/src/app/shared/subscriptions/models/subscription.model.ts @@ -0,0 +1,57 @@ +import { autoserialize, deserialize, inheritSerialization } from 'cerialize'; +import { typedObject } from '../../../core/cache/builders/build-decorators'; + +import { DSpaceObject } from '../../../core/shared/dspace-object.model'; +import { HALLink } from '../../../core/shared/hal-link.model'; +import { SUBSCRIPTION } from './subscription.resource-type'; +import { EPerson } from '../../../core/eperson/models/eperson.model'; + + +@typedObject +@inheritSerialization(DSpaceObject) +export class Subscription extends DSpaceObject { + static type = SUBSCRIPTION; + + /** + * A string representing subscription type + */ + @autoserialize + public id: string; + + /** + * A string representing subscription type + */ + @autoserialize + public subscriptionType: string; + + /** + * An array of parameters for the subscription + */ + @autoserialize + public subscriptionParameterList: SubscriptionParameterList[]; + + /** + * The {@link HALLink}s for this Subscription + */ + @deserialize + _links: { + self: HALLink; + ePerson: HALLink; + dSpaceObject: HALLink; + }; + + /** + * The embedded ePerson & dSpaceObject for this Subscription + */ + @deserialize + _embedded: { + ePerson: EPerson; + dSpaceObject: DSpaceObject; + }; +} + +export interface SubscriptionParameterList { + id: string; + name: string; + value: string; +} diff --git a/src/app/shared/subscriptions/models/subscription.resource-type.ts b/src/app/shared/subscriptions/models/subscription.resource-type.ts new file mode 100644 index 0000000000..50098e53c5 --- /dev/null +++ b/src/app/shared/subscriptions/models/subscription.resource-type.ts @@ -0,0 +1,10 @@ +import { ResourceType } from '../../../core/shared/resource-type'; + +/** + * The resource type for Group + * + * Needs to be in a separate file to prevent circular + * dependencies in webpack. + */ + +export const SUBSCRIPTION = new ResourceType('subscription'); diff --git a/src/app/shared/subscriptions/subscription.service.ts b/src/app/shared/subscriptions/subscription.service.ts new file mode 100644 index 0000000000..5010286e72 --- /dev/null +++ b/src/app/shared/subscriptions/subscription.service.ts @@ -0,0 +1,166 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; + +import { Store } from '@ngrx/store'; +import { Observable } from 'rxjs'; +import { distinctUntilChanged, filter, map, switchMap, take } from 'rxjs/operators'; + + +import { NotificationsService } from '../notifications/notifications.service'; +import { RemoteDataBuildService } from '../../core/cache/builders/remote-data-build.service'; +import { RequestParam } from '../../core/cache/models/request-param.model'; +import { ObjectCacheService } from '../../core/cache/object-cache.service'; +import { DSOChangeAnalyzer } from '../../core/data/dso-change-analyzer.service'; +import { PaginatedList } from '../../core/data/paginated-list.model'; +import { RemoteData } from '../../core/data/remote-data'; +import { CreateRequest, PutRequest } from '../../core/data/request.models'; +import { FindListOptions } from '../../core/data/find-list-options.model'; +import { RestRequest } from '../../core/data/rest-request.model'; + +import { RequestService } from '../../core/data/request.service'; +import { HALEndpointService } from '../../core/shared/hal-endpoint.service'; +import { Subscription } from './models/subscription.model'; +import { dataService } from '../../core/data/base/data-service.decorator'; +import { SUBSCRIPTION } from './models/subscription.resource-type'; +import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; +import { NoContent } from '../../core/shared/NoContent.model'; +import { isNotEmpty, isNotEmptyOperator } from '../empty.util'; + +import { getFirstCompletedRemoteData } from '../../core/shared/operators'; +import { sendRequest } from 'src/app/core/shared/request.operators'; +import { IdentifiableDataService } from '../../core/data/base/identifiable-data.service'; +import { DeleteDataImpl } from '../../core/data/base/delete-data'; +import { SearchDataImpl } from '../../core/data/base/search-data'; +import { FindAllData } from '../../core/data/base/find-all-data'; +import { followLink } from '../utils/follow-link-config.model'; + +/** + * Provides methods to retrieve subscription resources from the REST API related CRUD actions. + */ +@Injectable({ + providedIn: 'root' +}) +@dataService(SUBSCRIPTION) +export class SubscriptionService extends IdentifiableDataService { + protected findByEpersonLinkPath = 'findByEPerson'; + + private deleteData: DeleteDataImpl; + private findAllData: FindAllData; + private searchData: SearchDataImpl; + + constructor( + protected comparator: DSOChangeAnalyzer, + protected http: HttpClient, + protected notificationsService: NotificationsService, + protected requestService: RequestService, + protected rdbService: RemoteDataBuildService, + protected store: Store, + protected objectCache: ObjectCacheService, + protected halService: HALEndpointService, + protected nameService: DSONameService, + ) { + super('subscriptions', requestService, rdbService, objectCache, halService); + + this.searchData = new SearchDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, this.responseMsToLive); + this.deleteData = new DeleteDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, notificationsService, this.responseMsToLive, this.constructIdEndpoint); + } + /** + * Get subscriptions for a given item or community or collection & eperson. + * + * @param eperson The eperson to search for + * @param uuid The uuid of the dsobjcet to search for + */ + getSubscriptionByPersonDSO(eperson: string, uuid: string): Observable>> { + + const optionsWithObject = Object.assign(new FindListOptions(), { + searchParams: [ + new RequestParam('dspace_object_id', uuid), + new RequestParam('eperson_id', eperson) + ] + }); + + return this.searchData.searchBy('findByEPersonAndDso', optionsWithObject, false, true); + } + + /** + * Create a subscription for a given item or community or collection. + * + * @param subscription The subscription to create + * @param ePerson The ePerson to create for + * @param uuid The uuid of the dsobjcet to create for + */ + createSubscription(subscription, ePerson: string, uuid: string): Observable> { + + return this.halService.getEndpoint(this.linkPath).pipe( + isNotEmptyOperator(), + take(1), + map((endpointUrl: string) => `${endpointUrl}?dspace_object_id=${uuid}&eperson_id=${ePerson}`), + map((endpointURL: string) => new CreateRequest(this.requestService.generateRequestId(), endpointURL, JSON.stringify(subscription))), + sendRequest(this.requestService), + switchMap((restRequest: RestRequest) => this.rdbService.buildFromRequestUUID(restRequest.uuid)), + getFirstCompletedRemoteData(), + ) as Observable>; + } + + /** + * Update a subscription for a given item or community or collection. + * + * @param subscription The subscription to update + * @param ePerson The ePerson to update for + * @param uuid The uuid of the dsobjcet to update for + */ + updateSubscription(subscription, ePerson: string, uuid: string) { + + return this.halService.getEndpoint(this.linkPath).pipe( + isNotEmptyOperator(), + take(1), + map((endpointUrl: string) => `${endpointUrl}/${subscription.id}?dspace_object_id=${uuid}&eperson_id=${ePerson}`), + map((endpointURL: string) => new PutRequest(this.requestService.generateRequestId(), endpointURL, JSON.stringify(subscription))), + sendRequest(this.requestService), + switchMap((restRequest: RestRequest) => this.rdbService.buildFromRequestUUID(restRequest.uuid)), + getFirstCompletedRemoteData(), + ) as Observable>; + } + + + /** + * Deletes the subscription with a give id + * + * @param id the id of Subscription to delete + */ + deleteSubscription(id: string): Observable> { + return this.halService.getEndpoint(this.linkPath).pipe( + filter((href: string) => isNotEmpty(href)), + distinctUntilChanged(), + switchMap((endpointUrl) => this.deleteData.delete(id)), + getFirstCompletedRemoteData(), + ); + } + + /** + * Retrieves the list of subscription with {@link dSpaceObject} and {@link ePerson} + * + * @param options options for the find all request + */ + findAllSubscriptions(options?): Observable>> { + return this.findAllData.findAll(options, true, true, followLink('dSpaceObject'), followLink('ePerson')); + } + + + /** + * Retrieves the list of subscription with {@link dSpaceObject} and {@link ePerson} + * + * @param ePersonId The eperson id + * @param options The options for the find all request + */ + findByEPerson(ePersonId: string, options?: FindListOptions): Observable>> { + const optionsWithObject = Object.assign(new FindListOptions(), options, { + searchParams: [ + new RequestParam('uuid', ePersonId) + ] + }); + + return this.searchData.searchBy(this.findByEpersonLinkPath, optionsWithObject, true, true, followLink('dSpaceObject'), followLink('ePerson')); + } + +} diff --git a/src/app/shared/subscriptions/subscriptions.module.ts b/src/app/shared/subscriptions/subscriptions.module.ts new file mode 100644 index 0000000000..9b72493f67 --- /dev/null +++ b/src/app/shared/subscriptions/subscriptions.module.ts @@ -0,0 +1,31 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ReactiveFormsModule } from '@angular/forms'; + +import { SubscriptionViewComponent } from './components/subscription-view/subscription-view.component'; +import { SubscriptionModalComponent } from './components/subscription-modal/subscription-modal.component'; +import { SubscriptionEditModalComponent } from './components/subscription-edit-modal/subscription-edit-modal.component'; +import { TranslateModule } from '@ngx-translate/core'; +import { RouterModule } from '@angular/router'; + +const COMPONENTS = [ + SubscriptionViewComponent, + SubscriptionModalComponent, + SubscriptionEditModalComponent, +]; + +@NgModule({ + declarations: [ + ...COMPONENTS + ], + imports: [ + CommonModule, + ReactiveFormsModule, + TranslateModule, + RouterModule + ], + exports: [ + ...COMPONENTS + ] +}) +export class SubscriptionsModule { } diff --git a/src/app/shared/testing/notifications-service.stub.ts b/src/app/shared/testing/notifications-service.stub.ts index 154c5b4351..829b65fc1d 100644 --- a/src/app/shared/testing/notifications-service.stub.ts +++ b/src/app/shared/testing/notifications-service.stub.ts @@ -9,6 +9,9 @@ export class NotificationsServiceStub { remove = jasmine.createSpy('remove'); removeAll = jasmine.createSpy('removeAll'); + notificationWithAnchor() { + } + private getDefaultOptions(): NotificationOptions { return new NotificationOptions(); } diff --git a/src/app/shared/testing/router.stub.ts b/src/app/shared/testing/router.stub.ts index 8630e16b2e..31bee0b6fe 100644 --- a/src/app/shared/testing/router.stub.ts +++ b/src/app/shared/testing/router.stub.ts @@ -9,4 +9,10 @@ export class RouterStub { navigateByUrl(url): void { this.url = url; } + createUrlTree(commands, navigationExtras = {}) { + return '/testing-url'; + } + serializeUrl(commands, navExtras = {}) { + return '/testing-url'; + } } diff --git a/src/app/shared/testing/subscriptions-data.mock.ts b/src/app/shared/testing/subscriptions-data.mock.ts new file mode 100644 index 0000000000..971f69786e --- /dev/null +++ b/src/app/shared/testing/subscriptions-data.mock.ts @@ -0,0 +1,4467 @@ +export const findAllSubscriptionRes = { + 'type': { + 'value': 'paginated-list' + }, + 'pageInfo': { + 'elementsPerPage': 10, + 'totalElements': 10, + 'totalPages': 1, + 'currentPage': 1 + }, + '_links': { + 'self': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions?page=0&size=10' + }, + 'search': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/search' + }, + 'page': [ + { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/21' + }, + { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/22' + }, + { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/23' + }, + { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/24' + }, + { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/25' + }, + { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/26' + }, + { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/27' + }, + { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/30' + }, + { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/31' + }, + { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/32' + } + ] + }, + 'page': [ + { + 'id': 21, + 'subscriptionType': 'content', + 'subscriptionParameterList': [ + { + 'id': 77, + 'name': 'frequency', + 'value': 'D' + }, + { + 'id': 78, + 'name': 'frequency', + 'value': 'M' + } + ], + '_links': { + 'dSpaceObject': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/21/dSpaceObject' + }, + 'ePerson': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/21/ePerson' + }, + 'self': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/21' + } + }, + '_embedded': { + 'ePerson': { + 'id': '335647b6-8a52-4ecb-a8c1-7ebabb199bda', + 'uuid': '335647b6-8a52-4ecb-a8c1-7ebabb199bda', + 'name': 'dspacedemo+admin@gmail.com', + 'handle': null, + 'metadata': { + 'dspace.agreements.cookies': [ + { + 'value': '{\'authentication\':true,\'preferences\':true,\'acknowledgement\':true,\'google-analytics\':true}', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dspace.agreements.end-user': [ + { + 'value': 'true', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'eperson.firstname': [ + { + 'value': 'Demo', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'eperson.lastname': [ + { + 'value': 'Site Administrator', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ] + }, + 'netid': null, + 'lastActive': '2021-09-01T12:06:19.000+00:00', + 'canLogIn': true, + 'email': 'dspacedemo+admin@gmail.com', + 'requireCertificate': false, + 'selfRegistered': false, + 'type': 'eperson', + '_links': { + 'groups': { + 'href': 'https://dspacecris7.4science.cloud/server/api/eperson/epersons/335647b6-8a52-4ecb-a8c1-7ebabb199bda/groups' + }, + 'self': { + 'href': 'https://dspacecris7.4science.cloud/server/api/eperson/epersons/335647b6-8a52-4ecb-a8c1-7ebabb199bda' + } + } + }, + 'dSpaceObject': { + 'id': '092b59e8-8159-4e70-98b5-93ec60bd3431', + 'uuid': '092b59e8-8159-4e70-98b5-93ec60bd3431', + 'name': 'Bollini, Andrea', + 'handle': '123456789/43', + 'metadata': { + 'creativework.datePublished': [ + { + 'value': null, + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'cris.orcid.access-token': [ + { + 'value': 'a456a1c3-1c9e-45f8-9e11-f273fa58de2e', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'dspace.orcid.authenticated': [ + { + 'value': 'true', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'cris.orcid.refresh-token': [ + { + 'value': '13a63f03-7c49-4dad-b39b-070f73cc7ac1', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'dspace.orcid.scope': [ + { + 'value': '/authenticate', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + }, + { + 'value': '/read-limited', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 1, + 'securityLevel': 0 + }, + { + 'value': '/activities/update', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 2, + 'securityLevel': 0 + }, + { + 'value': '/person/update', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 3, + 'securityLevel': 0 + } + ], + 'dspace.orcid.sync-fundings': [ + { + 'value': 'ALL', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'dspace.orcid.sync-mode': [ + { + 'value': 'MANUAL', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'dspace.orcid.sync-profile': [ + { + 'value': 'AFFILIATION', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + }, + { + 'value': 'EDUCATION', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 1, + 'securityLevel': 0 + }, + { + 'value': 'BIOGRAPHICAL', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 2, + 'securityLevel': 0 + }, + { + 'value': 'IDENTIFIERS', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 3, + 'securityLevel': 0 + } + ], + 'cris.orcid.sync-projects': [ + { + 'value': 'ALL', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'dspace.orcid.sync-publications': [ + { + 'value': 'ALL', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'dspace.orcid.webhook': [ + { + 'value': '2021-05-26T12:47:27.971367', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'dspace.object.owner': [ + { + 'value': 'Demo Site Administrator', + 'language': null, + 'authority': '335647b6-8a52-4ecb-a8c1-7ebabb199bda', + 'confidence': 600, + 'place': 0, + 'securityLevel': 0 + } + ], + 'cris.policy.group': [ + { + 'value': 'Administrator', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'cris.sourceId': [ + { + 'value': '335647b6-8a52-4ecb-a8c1-7ebabb199bda', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'cris.workspace.shared': [ + { + 'value': 'fg', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'crisrp.country': [ + { + 'value': 'IT', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'crisrp.education': [ + { + 'value': 'Università degli Studi di Milano Bicocca', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + }, + { + 'value': 'Sapienza Università di Roma', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 1, + 'securityLevel': 0 + } + ], + 'crisrp.education.end': [ + { + 'value': '2008', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + }, + { + 'value': '2003', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 1, + 'securityLevel': 0 + } + ], + 'crisrp.education.role': [ + { + 'value': 'Master post experience 2nd level', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + }, + { + 'value': 'Graduate Studies - Mathematics, Physics', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 1, + 'securityLevel': 0 + } + ], + 'crisrp.education.start': [ + { + 'value': '2007', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + }, + { + 'value': '1998', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 1, + 'securityLevel': 0 + } + ], + 'crisrp.name.translated': [ + { + 'value': null, + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0, + 'securityLevel': 0 + } + ], + 'crisrp.site.title': [ + { + 'value': 'LinkedIn', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + }, + { + 'value': 'GitHub', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 1, + 'securityLevel': 0 + } + ], + 'dc.date.accessioned': [ + { + 'value': '2020-09-14T09:36:02Z', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'dc.date.available': [ + { + 'value': '2020-09-14T09:36:02Z', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'dc.description.abstract': [ + { + 'value': 'I’m responsible for all the technological aspects of the company proposal, from the final solutions to tools, methodologies and technologies adopted for the production and support activities. Among my responsibilities, I define the infrastructure that best fits the project requirements. We provide support on premis on the customer data center and worldwide cloud providers. Our hosted solutions are powered by Amazon Web Services, our experience in their services allow us to offer best in class solutions, scalable and cost-optimized.\n\nI’m in charge of the planning, estimation and execution of the projects from the technical perspective, and of the preparation of technical annexes for national and international tenders.\n\nI lead the teams of software architects and developers, assuring the adoption of best practices and up-to-date technologies. I’m in charge of the scouting and integration of new technologies and products within our solutions with a particular focus on Open Source and Open Standards. I’m directly involved with open-source and domain communities to assure that our solutions remain aligned with the international trends both from the technical perspective and for the functional requirements.', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'dc.description.provenance': [ + { + 'value': 'Made available in DSpace on 2020-09-14T09:36:02Z (GMT). No. of bitstreams: 0', + 'language': 'en', + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'dc.identifier.uri': [ + { + 'value': 'https://dspacecris7.4science.cloud/handle/123456789/43', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'dc.title': [ + { + 'value': 'Bollini, Andrea', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'dspace.entity.type': [ + { + 'value': 'Person', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'oairecerif.affiliation.endDate': [ + { + 'value': '#PLACEHOLDER_PARENT_METADATA_VALUE#', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + }, + { + 'value': '2016', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 1, + 'securityLevel': 0 + }, + { + 'value': '2016', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 2, + 'securityLevel': 0 + } + ], + 'oairecerif.affiliation.role': [ + { + 'value': 'CTO', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + }, + { + 'value': 'Head of Open Source & Open Standards Strategy', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 1, + 'securityLevel': 0 + }, + { + 'value': 'Head of Open Source & Open Standards Strategy', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 2, + 'securityLevel': 0 + } + ], + 'oairecerif.affiliation.startDate': [ + { + 'value': '2016', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + }, + { + 'value': '2012', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 1, + 'securityLevel': 0 + }, + { + 'value': '2012', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 2, + 'securityLevel': 0 + } + ], + 'oairecerif.identifier.url': [ + { + 'value': 'https://www.linkedin.com/in/andreabollini/', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + }, + { + 'value': 'https://github.com/abollini', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 1, + 'securityLevel': 0 + } + ], + 'oairecerif.person.affiliation': [ + { + 'value': '4Science', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + }, + { + 'value': 'CINECA', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 1, + 'securityLevel': 0 + }, + { + 'value': 'CINECA', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 2, + 'securityLevel': 0 + } + ], + 'oairecerif.person.gender': [ + { + 'value': 'm', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'person.affiliation.name': [ + { + 'value': '4Science', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'person.email': [ + { + 'value': 'andrea.bollini@4science.it', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'person.familyName': [ + { + 'value': 'Bollini', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'person.givenName': [ + { + 'value': 'Andrea', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'person.identifier.orcid': [ + { + 'value': '0000-0003-0864-8867', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'person.identifier.scopus-author-id': [ + { + 'value': '55484808800', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'person.jobTitle': [ + { + 'value': 'CTO', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'person.knowsLanguage': [ + { + 'value': 'en', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + }, + { + 'value': 'en', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 1, + 'securityLevel': 0 + } + ] + }, + 'inArchive': true, + 'discoverable': true, + 'withdrawn': false, + 'lastModified': '2021-08-23T10:44:57.768+00:00', + 'entityType': 'Person', + 'type': 'item', + '_links': { + 'bundles': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431/bundles' + }, + 'mappedCollections': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431/mappedCollections' + }, + 'owningCollection': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431/owningCollection' + }, + 'relationships': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431/relationships' + }, + 'version': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431/version' + }, + 'templateItemOf': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431/templateItemOf' + }, + 'metrics': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431/metrics' + }, + 'thumbnail': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431/thumbnail' + }, + 'self': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431' + } + } + } + }, + 'type': 'subscription' + }, + { + 'id': 22, + 'subscriptionType': 'content', + 'subscriptionParameterList': [ + { + 'id': 138, + 'name': 'frequency', + 'value': 'D' + } + ], + '_links': { + 'dSpaceObject': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/22/dSpaceObject' + }, + 'ePerson': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/22/ePerson' + }, + 'self': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/22' + } + }, + '_embedded': { + 'ePerson': { + 'id': '335647b6-8a52-4ecb-a8c1-7ebabb199bda', + 'uuid': '335647b6-8a52-4ecb-a8c1-7ebabb199bda', + 'name': 'dspacedemo+admin@gmail.com', + 'handle': null, + 'metadata': { + 'dspace.agreements.cookies': [ + { + 'value': '{\'authentication\':true,\'preferences\':true,\'acknowledgement\':true,\'google-analytics\':true}', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dspace.agreements.end-user': [ + { + 'value': 'true', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'eperson.firstname': [ + { + 'value': 'Demo', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'eperson.lastname': [ + { + 'value': 'Site Administrator', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ] + }, + 'netid': null, + 'lastActive': '2021-09-01T12:06:19.000+00:00', + 'canLogIn': true, + 'email': 'dspacedemo+admin@gmail.com', + 'requireCertificate': false, + 'selfRegistered': false, + 'type': 'eperson', + '_links': { + 'groups': { + 'href': 'https://dspacecris7.4science.cloud/server/api/eperson/epersons/335647b6-8a52-4ecb-a8c1-7ebabb199bda/groups' + }, + 'self': { + 'href': 'https://dspacecris7.4science.cloud/server/api/eperson/epersons/335647b6-8a52-4ecb-a8c1-7ebabb199bda' + } + } + }, + 'dSpaceObject': { + 'id': '092b59e8-8159-4e70-98b5-93ec60bd3431', + 'uuid': '092b59e8-8159-4e70-98b5-93ec60bd3431', + 'name': 'Bollini, Andrea', + 'handle': '123456789/43', + 'metadata': { + 'creativework.datePublished': [ + { + 'value': null, + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'cris.orcid.access-token': [ + { + 'value': 'a456a1c3-1c9e-45f8-9e11-f273fa58de2e', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'dspace.orcid.authenticated': [ + { + 'value': 'true', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'cris.orcid.refresh-token': [ + { + 'value': '13a63f03-7c49-4dad-b39b-070f73cc7ac1', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'dspace.orcid.scope': [ + { + 'value': '/authenticate', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + }, + { + 'value': '/read-limited', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 1, + 'securityLevel': 0 + }, + { + 'value': '/activities/update', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 2, + 'securityLevel': 0 + }, + { + 'value': '/person/update', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 3, + 'securityLevel': 0 + } + ], + 'dspace.orcid.sync-fundings': [ + { + 'value': 'ALL', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'dspace.orcid.sync-mode': [ + { + 'value': 'MANUAL', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'dspace.orcid.sync-profile': [ + { + 'value': 'AFFILIATION', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + }, + { + 'value': 'EDUCATION', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 1, + 'securityLevel': 0 + }, + { + 'value': 'BIOGRAPHICAL', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 2, + 'securityLevel': 0 + }, + { + 'value': 'IDENTIFIERS', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 3, + 'securityLevel': 0 + } + ], + 'cris.orcid.sync-projects': [ + { + 'value': 'ALL', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'dspace.orcid.sync-publications': [ + { + 'value': 'ALL', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'dspace.orcid.webhook': [ + { + 'value': '2021-05-26T12:47:27.971367', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'dspace.object.owner': [ + { + 'value': 'Demo Site Administrator', + 'language': null, + 'authority': '335647b6-8a52-4ecb-a8c1-7ebabb199bda', + 'confidence': 600, + 'place': 0, + 'securityLevel': 0 + } + ], + 'cris.policy.group': [ + { + 'value': 'Administrator', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'cris.sourceId': [ + { + 'value': '335647b6-8a52-4ecb-a8c1-7ebabb199bda', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'cris.workspace.shared': [ + { + 'value': 'fg', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'crisrp.country': [ + { + 'value': 'IT', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'crisrp.education': [ + { + 'value': 'Università degli Studi di Milano Bicocca', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + }, + { + 'value': 'Sapienza Università di Roma', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 1, + 'securityLevel': 0 + } + ], + 'crisrp.education.end': [ + { + 'value': '2008', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + }, + { + 'value': '2003', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 1, + 'securityLevel': 0 + } + ], + 'crisrp.education.role': [ + { + 'value': 'Master post experience 2nd level', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + }, + { + 'value': 'Graduate Studies - Mathematics, Physics', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 1, + 'securityLevel': 0 + } + ], + 'crisrp.education.start': [ + { + 'value': '2007', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + }, + { + 'value': '1998', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 1, + 'securityLevel': 0 + } + ], + 'crisrp.name.translated': [ + { + 'value': null, + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0, + 'securityLevel': 0 + } + ], + 'crisrp.site.title': [ + { + 'value': 'LinkedIn', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + }, + { + 'value': 'GitHub', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 1, + 'securityLevel': 0 + } + ], + 'dc.date.accessioned': [ + { + 'value': '2020-09-14T09:36:02Z', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'dc.date.available': [ + { + 'value': '2020-09-14T09:36:02Z', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'dc.description.abstract': [ + { + 'value': 'I’m responsible for all the technological aspects of the company proposal, from the final solutions to tools, methodologies and technologies adopted for the production and support activities. Among my responsibilities, I define the infrastructure that best fits the project requirements. We provide support on premis on the customer data center and worldwide cloud providers. Our hosted solutions are powered by Amazon Web Services, our experience in their services allow us to offer best in class solutions, scalable and cost-optimized.\n\nI’m in charge of the planning, estimation and execution of the projects from the technical perspective, and of the preparation of technical annexes for national and international tenders.\n\nI lead the teams of software architects and developers, assuring the adoption of best practices and up-to-date technologies. I’m in charge of the scouting and integration of new technologies and products within our solutions with a particular focus on Open Source and Open Standards. I’m directly involved with open-source and domain communities to assure that our solutions remain aligned with the international trends both from the technical perspective and for the functional requirements.', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'dc.description.provenance': [ + { + 'value': 'Made available in DSpace on 2020-09-14T09:36:02Z (GMT). No. of bitstreams: 0', + 'language': 'en', + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'dc.identifier.uri': [ + { + 'value': 'https://dspacecris7.4science.cloud/handle/123456789/43', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'dc.title': [ + { + 'value': 'Bollini, Andrea', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'dspace.entity.type': [ + { + 'value': 'Person', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'oairecerif.affiliation.endDate': [ + { + 'value': '#PLACEHOLDER_PARENT_METADATA_VALUE#', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + }, + { + 'value': '2016', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 1, + 'securityLevel': 0 + }, + { + 'value': '2016', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 2, + 'securityLevel': 0 + } + ], + 'oairecerif.affiliation.role': [ + { + 'value': 'CTO', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + }, + { + 'value': 'Head of Open Source & Open Standards Strategy', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 1, + 'securityLevel': 0 + }, + { + 'value': 'Head of Open Source & Open Standards Strategy', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 2, + 'securityLevel': 0 + } + ], + 'oairecerif.affiliation.startDate': [ + { + 'value': '2016', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + }, + { + 'value': '2012', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 1, + 'securityLevel': 0 + }, + { + 'value': '2012', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 2, + 'securityLevel': 0 + } + ], + 'oairecerif.identifier.url': [ + { + 'value': 'https://www.linkedin.com/in/andreabollini/', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + }, + { + 'value': 'https://github.com/abollini', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 1, + 'securityLevel': 0 + } + ], + 'oairecerif.person.affiliation': [ + { + 'value': '4Science', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + }, + { + 'value': 'CINECA', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 1, + 'securityLevel': 0 + }, + { + 'value': 'CINECA', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 2, + 'securityLevel': 0 + } + ], + 'oairecerif.person.gender': [ + { + 'value': 'm', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'person.affiliation.name': [ + { + 'value': '4Science', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'person.email': [ + { + 'value': 'andrea.bollini@4science.it', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'person.familyName': [ + { + 'value': 'Bollini', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'person.givenName': [ + { + 'value': 'Andrea', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'person.identifier.orcid': [ + { + 'value': '0000-0003-0864-8867', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'person.identifier.scopus-author-id': [ + { + 'value': '55484808800', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'person.jobTitle': [ + { + 'value': 'CTO', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'person.knowsLanguage': [ + { + 'value': 'en', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + }, + { + 'value': 'en', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 1, + 'securityLevel': 0 + } + ] + }, + 'inArchive': true, + 'discoverable': true, + 'withdrawn': false, + 'lastModified': '2021-08-23T10:44:57.768+00:00', + 'entityType': 'Person', + 'type': 'item', + '_links': { + 'bundles': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431/bundles' + }, + 'mappedCollections': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431/mappedCollections' + }, + 'owningCollection': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431/owningCollection' + }, + 'relationships': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431/relationships' + }, + 'version': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431/version' + }, + 'templateItemOf': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431/templateItemOf' + }, + 'metrics': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431/metrics' + }, + 'thumbnail': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431/thumbnail' + }, + 'self': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431' + } + } + } + }, + 'type': 'subscription' + }, + { + 'id': 23, + 'subscriptionType': 'content', + 'subscriptionParameterList': [ + { + 'id': 80, + 'name': 'frequency', + 'value': 'D' + }, + { + 'id': 81, + 'name': 'frequency', + 'value': 'M' + } + ], + '_links': { + 'dSpaceObject': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/23/dSpaceObject' + }, + 'ePerson': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/23/ePerson' + }, + 'self': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/23' + } + }, + '_embedded': { + 'ePerson': { + 'id': '335647b6-8a52-4ecb-a8c1-7ebabb199bda', + 'uuid': '335647b6-8a52-4ecb-a8c1-7ebabb199bda', + 'name': 'dspacedemo+admin@gmail.com', + 'handle': null, + 'metadata': { + 'dspace.agreements.cookies': [ + { + 'value': '{\'authentication\':true,\'preferences\':true,\'acknowledgement\':true,\'google-analytics\':true}', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dspace.agreements.end-user': [ + { + 'value': 'true', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'eperson.firstname': [ + { + 'value': 'Demo', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'eperson.lastname': [ + { + 'value': 'Site Administrator', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ] + }, + 'netid': null, + 'lastActive': '2021-09-01T12:06:19.000+00:00', + 'canLogIn': true, + 'email': 'dspacedemo+admin@gmail.com', + 'requireCertificate': false, + 'selfRegistered': false, + 'type': 'eperson', + '_links': { + 'groups': { + 'href': 'https://dspacecris7.4science.cloud/server/api/eperson/epersons/335647b6-8a52-4ecb-a8c1-7ebabb199bda/groups' + }, + 'self': { + 'href': 'https://dspacecris7.4science.cloud/server/api/eperson/epersons/335647b6-8a52-4ecb-a8c1-7ebabb199bda' + } + } + }, + 'dSpaceObject': { + 'id': '092b59e8-8159-4e70-98b5-93ec60bd3431', + 'uuid': '092b59e8-8159-4e70-98b5-93ec60bd3431', + 'name': 'Bollini, Andrea', + 'handle': '123456789/43', + 'metadata': { + 'creativework.datePublished': [ + { + 'value': null, + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'cris.orcid.access-token': [ + { + 'value': 'a456a1c3-1c9e-45f8-9e11-f273fa58de2e', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'dspace.orcid.authenticated': [ + { + 'value': 'true', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'cris.orcid.refresh-token': [ + { + 'value': '13a63f03-7c49-4dad-b39b-070f73cc7ac1', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'dspace.orcid.scope': [ + { + 'value': '/authenticate', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + }, + { + 'value': '/read-limited', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 1, + 'securityLevel': 0 + }, + { + 'value': '/activities/update', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 2, + 'securityLevel': 0 + }, + { + 'value': '/person/update', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 3, + 'securityLevel': 0 + } + ], + 'dspace.orcid.sync-fundings': [ + { + 'value': 'ALL', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'dspace.orcid.sync-mode': [ + { + 'value': 'MANUAL', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'dspace.orcid.sync-profile': [ + { + 'value': 'AFFILIATION', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + }, + { + 'value': 'EDUCATION', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 1, + 'securityLevel': 0 + }, + { + 'value': 'BIOGRAPHICAL', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 2, + 'securityLevel': 0 + }, + { + 'value': 'IDENTIFIERS', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 3, + 'securityLevel': 0 + } + ], + 'cris.orcid.sync-projects': [ + { + 'value': 'ALL', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'dspace.orcid.sync-publications': [ + { + 'value': 'ALL', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'dspace.orcid.webhook': [ + { + 'value': '2021-05-26T12:47:27.971367', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'dspace.object.owner': [ + { + 'value': 'Demo Site Administrator', + 'language': null, + 'authority': '335647b6-8a52-4ecb-a8c1-7ebabb199bda', + 'confidence': 600, + 'place': 0, + 'securityLevel': 0 + } + ], + 'cris.policy.group': [ + { + 'value': 'Administrator', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'cris.sourceId': [ + { + 'value': '335647b6-8a52-4ecb-a8c1-7ebabb199bda', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'cris.workspace.shared': [ + { + 'value': 'fg', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'crisrp.country': [ + { + 'value': 'IT', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'crisrp.education': [ + { + 'value': 'Università degli Studi di Milano Bicocca', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + }, + { + 'value': 'Sapienza Università di Roma', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 1, + 'securityLevel': 0 + } + ], + 'crisrp.education.end': [ + { + 'value': '2008', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + }, + { + 'value': '2003', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 1, + 'securityLevel': 0 + } + ], + 'crisrp.education.role': [ + { + 'value': 'Master post experience 2nd level', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + }, + { + 'value': 'Graduate Studies - Mathematics, Physics', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 1, + 'securityLevel': 0 + } + ], + 'crisrp.education.start': [ + { + 'value': '2007', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + }, + { + 'value': '1998', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 1, + 'securityLevel': 0 + } + ], + 'crisrp.name.translated': [ + { + 'value': null, + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0, + 'securityLevel': 0 + } + ], + 'crisrp.site.title': [ + { + 'value': 'LinkedIn', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + }, + { + 'value': 'GitHub', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 1, + 'securityLevel': 0 + } + ], + 'dc.date.accessioned': [ + { + 'value': '2020-09-14T09:36:02Z', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'dc.date.available': [ + { + 'value': '2020-09-14T09:36:02Z', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'dc.description.abstract': [ + { + 'value': 'I’m responsible for all the technological aspects of the company proposal, from the final solutions to tools, methodologies and technologies adopted for the production and support activities. Among my responsibilities, I define the infrastructure that best fits the project requirements. We provide support on premis on the customer data center and worldwide cloud providers. Our hosted solutions are powered by Amazon Web Services, our experience in their services allow us to offer best in class solutions, scalable and cost-optimized.\n\nI’m in charge of the planning, estimation and execution of the projects from the technical perspective, and of the preparation of technical annexes for national and international tenders.\n\nI lead the teams of software architects and developers, assuring the adoption of best practices and up-to-date technologies. I’m in charge of the scouting and integration of new technologies and products within our solutions with a particular focus on Open Source and Open Standards. I’m directly involved with open-source and domain communities to assure that our solutions remain aligned with the international trends both from the technical perspective and for the functional requirements.', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'dc.description.provenance': [ + { + 'value': 'Made available in DSpace on 2020-09-14T09:36:02Z (GMT). No. of bitstreams: 0', + 'language': 'en', + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'dc.identifier.uri': [ + { + 'value': 'https://dspacecris7.4science.cloud/handle/123456789/43', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'dc.title': [ + { + 'value': 'Bollini, Andrea', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'dspace.entity.type': [ + { + 'value': 'Person', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'oairecerif.affiliation.endDate': [ + { + 'value': '#PLACEHOLDER_PARENT_METADATA_VALUE#', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + }, + { + 'value': '2016', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 1, + 'securityLevel': 0 + }, + { + 'value': '2016', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 2, + 'securityLevel': 0 + } + ], + 'oairecerif.affiliation.role': [ + { + 'value': 'CTO', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + }, + { + 'value': 'Head of Open Source & Open Standards Strategy', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 1, + 'securityLevel': 0 + }, + { + 'value': 'Head of Open Source & Open Standards Strategy', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 2, + 'securityLevel': 0 + } + ], + 'oairecerif.affiliation.startDate': [ + { + 'value': '2016', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + }, + { + 'value': '2012', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 1, + 'securityLevel': 0 + }, + { + 'value': '2012', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 2, + 'securityLevel': 0 + } + ], + 'oairecerif.identifier.url': [ + { + 'value': 'https://www.linkedin.com/in/andreabollini/', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + }, + { + 'value': 'https://github.com/abollini', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 1, + 'securityLevel': 0 + } + ], + 'oairecerif.person.affiliation': [ + { + 'value': '4Science', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + }, + { + 'value': 'CINECA', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 1, + 'securityLevel': 0 + }, + { + 'value': 'CINECA', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 2, + 'securityLevel': 0 + } + ], + 'oairecerif.person.gender': [ + { + 'value': 'm', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'person.affiliation.name': [ + { + 'value': '4Science', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'person.email': [ + { + 'value': 'andrea.bollini@4science.it', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'person.familyName': [ + { + 'value': 'Bollini', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'person.givenName': [ + { + 'value': 'Andrea', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'person.identifier.orcid': [ + { + 'value': '0000-0003-0864-8867', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'person.identifier.scopus-author-id': [ + { + 'value': '55484808800', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'person.jobTitle': [ + { + 'value': 'CTO', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + } + ], + 'person.knowsLanguage': [ + { + 'value': 'en', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 0, + 'securityLevel': 0 + }, + { + 'value': 'en', + 'language': null, + 'authority': null, + 'confidence': 0, + 'place': 1, + 'securityLevel': 0 + } + ] + }, + 'inArchive': true, + 'discoverable': true, + 'withdrawn': false, + 'lastModified': '2021-08-23T10:44:57.768+00:00', + 'entityType': 'Person', + 'type': 'item', + '_links': { + 'bundles': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431/bundles' + }, + 'mappedCollections': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431/mappedCollections' + }, + 'owningCollection': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431/owningCollection' + }, + 'relationships': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431/relationships' + }, + 'version': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431/version' + }, + 'templateItemOf': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431/templateItemOf' + }, + 'metrics': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431/metrics' + }, + 'thumbnail': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431/thumbnail' + }, + 'self': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431' + } + } + } + }, + 'type': 'subscription' + }, + { + 'id': 24, + 'subscriptionType': 'content', + 'subscriptionParameterList': [ + { + 'id': 82, + 'name': 'frequency', + 'value': 'D' + }, + { + 'id': 83, + 'name': 'frequency', + 'value': 'M' + } + ], + '_links': { + 'dSpaceObject': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/24/dSpaceObject' + }, + 'ePerson': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/24/ePerson' + }, + 'self': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/24' + } + }, + '_embedded': { + 'ePerson': { + 'id': '335647b6-8a52-4ecb-a8c1-7ebabb199bda', + 'uuid': '335647b6-8a52-4ecb-a8c1-7ebabb199bda', + 'name': 'dspacedemo+admin@gmail.com', + 'handle': null, + 'metadata': { + 'dspace.agreements.cookies': [ + { + 'value': '{\'authentication\':true,\'preferences\':true,\'acknowledgement\':true,\'google-analytics\':true}', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dspace.agreements.end-user': [ + { + 'value': 'true', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'eperson.firstname': [ + { + 'value': 'Demo', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'eperson.lastname': [ + { + 'value': 'Site Administrator', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ] + }, + 'netid': null, + 'lastActive': '2021-09-01T12:06:19.000+00:00', + 'canLogIn': true, + 'email': 'dspacedemo+admin@gmail.com', + 'requireCertificate': false, + 'selfRegistered': false, + 'type': 'eperson', + '_links': { + 'groups': { + 'href': 'https://dspacecris7.4science.cloud/server/api/eperson/epersons/335647b6-8a52-4ecb-a8c1-7ebabb199bda/groups' + }, + 'self': { + 'href': 'https://dspacecris7.4science.cloud/server/api/eperson/epersons/335647b6-8a52-4ecb-a8c1-7ebabb199bda' + } + } + }, + 'dSpaceObject': { + 'id': '90f3b9ce-db65-479c-90d7-8c794abf942c', + 'uuid': '90f3b9ce-db65-479c-90d7-8c794abf942c', + 'name': 'DSpace-CRIS', + 'handle': '123456789/34', + 'metadata': { + 'crispj.coinvestigators': [ + { + 'value': 'Mornati, Susanna', + 'language': null, + 'authority': '1325093a-1ef4-4d87-920d-02ce544fea00', + 'confidence': 600, + 'place': 0 + }, + { + 'value': 'Lombardi, Corrado', + 'language': null, + 'authority': 'b5ad6864-012d-4989-8e0d-4acfa1156fd9', + 'confidence': 600, + 'place': 1 + } + ], + 'crispj.coordinator': [ + { + 'value': '4Science', + 'language': null, + 'authority': 'a14ba215-c0f0-4b74-b21a-06359bfabd45', + 'confidence': 600, + 'place': 0 + } + ], + 'crispj.investigator': [ + { + 'value': 'Bollini, Andrea', + 'language': null, + 'authority': '092b59e8-8159-4e70-98b5-93ec60bd3431', + 'confidence': 600, + 'place': 0 + } + ], + 'dc.date.accessioned': [ + { + 'value': '2020-09-05T16:33:33Z', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.date.available': [ + { + 'value': '2020-09-05T16:33:33Z', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.description.abstract': [ + { + 'value': 'DSpace-CRIS is the first free open-source extension of DSpace for the Research Data and Information Management ever developed. Differently from other (commercial) CRIS/RIMS (star), DSpace-CRIS has the institutional repository as its core component, providing high visibility on the web to all the collected information and objects. DSpace-CRIS broadens DSpace functionality and expands its data model while remaining aligned with its code base. \n\nDSpace-CRIS adopts/is compliant with international standards and practices to facilitate interoperability and data transfer:\n\n- ORCID API v2 (complete compliance including pull/push of info for profiles, publications, projects). ORCID API v3 compliance is being released.\n\n- Signposting and ResourceSync (which implement COAR NGR Recommended Behaviors)\n\n- OpenAIRE Guidelines for Literature Repository Managers v4, for Data Archives, for CRIS Managers v1.1.1 (based on CERIF, released Nov. 2019)\n\n- PlanS (by Coalition S)\n\n- FAIR principles\n\nThe main characteristic of DSpace-CRIS is its flexible data model, which allows you to collect and manage research data and information typical of a CRIS system, to define entities and attributes with their reciprocal links. If you would just want to enhance the management of authors, provide name variants and IDs such as the ORCiD, exploit the varied ecosystem of persistent identifiers, link researchers to projects, awards, etc., DSpace-CRIS flexible data model can support this without aggravating the management burden of a normal institutional repository, while giving a great added value. Besides, it has useful features such as the collaboration network graph, aggregated (by researcher, by department) bibliometrics and statistics with graphic reporting, CVs and bibliographies, integration with ORCiD API v.3 and much more, you can explore them vie the menu items here on the left. \n\nIts flexibility allows to configure different data models and metadata schemas, providing the community with new and creative uses of DSpace, such as DSpace-GLAM (Galleries, Libraries, Archives and Museums) for the Cultural Heritage.', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.description.provenance': [ + { + 'value': 'Made available in DSpace on 2020-09-05T16:33:33Z (GMT). No. of bitstreams: 1\nlayers.png: 295537 bytes, checksum: 001e24b05d2c5d6cd76e88580f10cb9b (MD5)', + 'language': 'en', + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.identifier.uri': [ + { + 'value': 'https://dspacecris7.4science.cloud/handle/123456789/34', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.subject': [ + { + 'value': 'cerif', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + }, + { + 'value': 'datamanagement', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 1 + }, + { + 'value': 'opensource', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 2 + } + ], + 'dc.title': [ + { + 'value': 'DSpace-CRIS', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.type': [ + { + 'value': 'applied research', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dspace.entity.type': [ + { + 'value': 'Project', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'oairecerif.acronym': [ + { + 'value': 'DSC', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'oairecerif.oamandate': [ + { + 'value': 'true', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'oairecerif.oamandate.url': [ + { + 'value': 'https://www.4science.it/en/open-source/', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'oairecerif.project.startDate': [ + { + 'value': '2009-04', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'oairecerif.project.status': [ + { + 'value': 'ongoing', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ] + }, + 'inArchive': true, + 'discoverable': true, + 'withdrawn': false, + 'lastModified': '2021-08-02T14:19:24.927+00:00', + 'entityType': 'Project', + 'type': 'item', + '_links': { + 'bundles': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/bundles' + }, + 'mappedCollections': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/mappedCollections' + }, + 'owningCollection': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/owningCollection' + }, + 'relationships': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/relationships' + }, + 'version': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/version' + }, + 'templateItemOf': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/templateItemOf' + }, + 'metrics': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/metrics' + }, + 'thumbnail': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/thumbnail' + }, + 'self': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c' + } + } + } + }, + 'type': 'subscription' + }, + { + 'id': 25, + 'subscriptionType': 'content', + 'subscriptionParameterList': [ + { + 'id': 84, + 'name': 'frequency', + 'value': 'D' + }, + { + 'id': 85, + 'name': 'frequency', + 'value': 'M' + }, + { + 'id': 86, + 'name': 'frequency', + 'value': 'W' + } + ], + '_links': { + 'dSpaceObject': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/25/dSpaceObject' + }, + 'ePerson': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/25/ePerson' + }, + 'self': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/25' + } + }, + '_embedded': { + 'ePerson': { + 'id': '335647b6-8a52-4ecb-a8c1-7ebabb199bda', + 'uuid': '335647b6-8a52-4ecb-a8c1-7ebabb199bda', + 'name': 'dspacedemo+admin@gmail.com', + 'handle': null, + 'metadata': { + 'dspace.agreements.cookies': [ + { + 'value': '{\'authentication\':true,\'preferences\':true,\'acknowledgement\':true,\'google-analytics\':true}', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dspace.agreements.end-user': [ + { + 'value': 'true', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'eperson.firstname': [ + { + 'value': 'Demo', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'eperson.lastname': [ + { + 'value': 'Site Administrator', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ] + }, + 'netid': null, + 'lastActive': '2021-09-01T12:06:19.000+00:00', + 'canLogIn': true, + 'email': 'dspacedemo+admin@gmail.com', + 'requireCertificate': false, + 'selfRegistered': false, + 'type': 'eperson', + '_links': { + 'groups': { + 'href': 'https://dspacecris7.4science.cloud/server/api/eperson/epersons/335647b6-8a52-4ecb-a8c1-7ebabb199bda/groups' + }, + 'self': { + 'href': 'https://dspacecris7.4science.cloud/server/api/eperson/epersons/335647b6-8a52-4ecb-a8c1-7ebabb199bda' + } + } + }, + 'dSpaceObject': { + 'id': '90f3b9ce-db65-479c-90d7-8c794abf942c', + 'uuid': '90f3b9ce-db65-479c-90d7-8c794abf942c', + 'name': 'DSpace-CRIS', + 'handle': '123456789/34', + 'metadata': { + 'crispj.coinvestigators': [ + { + 'value': 'Mornati, Susanna', + 'language': null, + 'authority': '1325093a-1ef4-4d87-920d-02ce544fea00', + 'confidence': 600, + 'place': 0 + }, + { + 'value': 'Lombardi, Corrado', + 'language': null, + 'authority': 'b5ad6864-012d-4989-8e0d-4acfa1156fd9', + 'confidence': 600, + 'place': 1 + } + ], + 'crispj.coordinator': [ + { + 'value': '4Science', + 'language': null, + 'authority': 'a14ba215-c0f0-4b74-b21a-06359bfabd45', + 'confidence': 600, + 'place': 0 + } + ], + 'crispj.investigator': [ + { + 'value': 'Bollini, Andrea', + 'language': null, + 'authority': '092b59e8-8159-4e70-98b5-93ec60bd3431', + 'confidence': 600, + 'place': 0 + } + ], + 'dc.date.accessioned': [ + { + 'value': '2020-09-05T16:33:33Z', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.date.available': [ + { + 'value': '2020-09-05T16:33:33Z', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.description.abstract': [ + { + 'value': 'DSpace-CRIS is the first free open-source extension of DSpace for the Research Data and Information Management ever developed. Differently from other (commercial) CRIS/RIMS (star), DSpace-CRIS has the institutional repository as its core component, providing high visibility on the web to all the collected information and objects. DSpace-CRIS broadens DSpace functionality and expands its data model while remaining aligned with its code base. \n\nDSpace-CRIS adopts/is compliant with international standards and practices to facilitate interoperability and data transfer:\n\n- ORCID API v2 (complete compliance including pull/push of info for profiles, publications, projects). ORCID API v3 compliance is being released.\n\n- Signposting and ResourceSync (which implement COAR NGR Recommended Behaviors)\n\n- OpenAIRE Guidelines for Literature Repository Managers v4, for Data Archives, for CRIS Managers v1.1.1 (based on CERIF, released Nov. 2019)\n\n- PlanS (by Coalition S)\n\n- FAIR principles\n\nThe main characteristic of DSpace-CRIS is its flexible data model, which allows you to collect and manage research data and information typical of a CRIS system, to define entities and attributes with their reciprocal links. If you would just want to enhance the management of authors, provide name variants and IDs such as the ORCiD, exploit the varied ecosystem of persistent identifiers, link researchers to projects, awards, etc., DSpace-CRIS flexible data model can support this without aggravating the management burden of a normal institutional repository, while giving a great added value. Besides, it has useful features such as the collaboration network graph, aggregated (by researcher, by department) bibliometrics and statistics with graphic reporting, CVs and bibliographies, integration with ORCiD API v.3 and much more, you can explore them vie the menu items here on the left. \n\nIts flexibility allows to configure different data models and metadata schemas, providing the community with new and creative uses of DSpace, such as DSpace-GLAM (Galleries, Libraries, Archives and Museums) for the Cultural Heritage.', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.description.provenance': [ + { + 'value': 'Made available in DSpace on 2020-09-05T16:33:33Z (GMT). No. of bitstreams: 1\nlayers.png: 295537 bytes, checksum: 001e24b05d2c5d6cd76e88580f10cb9b (MD5)', + 'language': 'en', + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.identifier.uri': [ + { + 'value': 'https://dspacecris7.4science.cloud/handle/123456789/34', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.subject': [ + { + 'value': 'cerif', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + }, + { + 'value': 'datamanagement', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 1 + }, + { + 'value': 'opensource', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 2 + } + ], + 'dc.title': [ + { + 'value': 'DSpace-CRIS', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.type': [ + { + 'value': 'applied research', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dspace.entity.type': [ + { + 'value': 'Project', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'oairecerif.acronym': [ + { + 'value': 'DSC', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'oairecerif.oamandate': [ + { + 'value': 'true', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'oairecerif.oamandate.url': [ + { + 'value': 'https://www.4science.it/en/open-source/', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'oairecerif.project.startDate': [ + { + 'value': '2009-04', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'oairecerif.project.status': [ + { + 'value': 'ongoing', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ] + }, + 'inArchive': true, + 'discoverable': true, + 'withdrawn': false, + 'lastModified': '2021-08-02T14:19:24.927+00:00', + 'entityType': 'Project', + 'type': 'item', + '_links': { + 'bundles': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/bundles' + }, + 'mappedCollections': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/mappedCollections' + }, + 'owningCollection': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/owningCollection' + }, + 'relationships': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/relationships' + }, + 'version': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/version' + }, + 'templateItemOf': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/templateItemOf' + }, + 'metrics': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/metrics' + }, + 'thumbnail': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/thumbnail' + }, + 'self': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c' + } + } + } + }, + 'type': 'subscription' + }, + { + 'id': 26, + 'subscriptionType': 'content', + 'subscriptionParameterList': [ + { + 'id': 87, + 'name': 'frequency', + 'value': 'D' + }, + { + 'id': 88, + 'name': 'frequency', + 'value': 'M' + } + ], + '_links': { + 'dSpaceObject': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/26/dSpaceObject' + }, + 'ePerson': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/26/ePerson' + }, + 'self': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/26' + } + }, + '_embedded': { + 'ePerson': { + 'id': '335647b6-8a52-4ecb-a8c1-7ebabb199bda', + 'uuid': '335647b6-8a52-4ecb-a8c1-7ebabb199bda', + 'name': 'dspacedemo+admin@gmail.com', + 'handle': null, + 'metadata': { + 'dspace.agreements.cookies': [ + { + 'value': '{\'authentication\':true,\'preferences\':true,\'acknowledgement\':true,\'google-analytics\':true}', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dspace.agreements.end-user': [ + { + 'value': 'true', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'eperson.firstname': [ + { + 'value': 'Demo', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'eperson.lastname': [ + { + 'value': 'Site Administrator', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ] + }, + 'netid': null, + 'lastActive': '2021-09-01T12:06:19.000+00:00', + 'canLogIn': true, + 'email': 'dspacedemo+admin@gmail.com', + 'requireCertificate': false, + 'selfRegistered': false, + 'type': 'eperson', + '_links': { + 'groups': { + 'href': 'https://dspacecris7.4science.cloud/server/api/eperson/epersons/335647b6-8a52-4ecb-a8c1-7ebabb199bda/groups' + }, + 'self': { + 'href': 'https://dspacecris7.4science.cloud/server/api/eperson/epersons/335647b6-8a52-4ecb-a8c1-7ebabb199bda' + } + } + }, + 'dSpaceObject': { + 'id': '90f3b9ce-db65-479c-90d7-8c794abf942c', + 'uuid': '90f3b9ce-db65-479c-90d7-8c794abf942c', + 'name': 'DSpace-CRIS', + 'handle': '123456789/34', + 'metadata': { + 'crispj.coinvestigators': [ + { + 'value': 'Mornati, Susanna', + 'language': null, + 'authority': '1325093a-1ef4-4d87-920d-02ce544fea00', + 'confidence': 600, + 'place': 0 + }, + { + 'value': 'Lombardi, Corrado', + 'language': null, + 'authority': 'b5ad6864-012d-4989-8e0d-4acfa1156fd9', + 'confidence': 600, + 'place': 1 + } + ], + 'crispj.coordinator': [ + { + 'value': '4Science', + 'language': null, + 'authority': 'a14ba215-c0f0-4b74-b21a-06359bfabd45', + 'confidence': 600, + 'place': 0 + } + ], + 'crispj.investigator': [ + { + 'value': 'Bollini, Andrea', + 'language': null, + 'authority': '092b59e8-8159-4e70-98b5-93ec60bd3431', + 'confidence': 600, + 'place': 0 + } + ], + 'dc.date.accessioned': [ + { + 'value': '2020-09-05T16:33:33Z', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.date.available': [ + { + 'value': '2020-09-05T16:33:33Z', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.description.abstract': [ + { + 'value': 'DSpace-CRIS is the first free open-source extension of DSpace for the Research Data and Information Management ever developed. Differently from other (commercial) CRIS/RIMS (star), DSpace-CRIS has the institutional repository as its core component, providing high visibility on the web to all the collected information and objects. DSpace-CRIS broadens DSpace functionality and expands its data model while remaining aligned with its code base. \n\nDSpace-CRIS adopts/is compliant with international standards and practices to facilitate interoperability and data transfer:\n\n- ORCID API v2 (complete compliance including pull/push of info for profiles, publications, projects). ORCID API v3 compliance is being released.\n\n- Signposting and ResourceSync (which implement COAR NGR Recommended Behaviors)\n\n- OpenAIRE Guidelines for Literature Repository Managers v4, for Data Archives, for CRIS Managers v1.1.1 (based on CERIF, released Nov. 2019)\n\n- PlanS (by Coalition S)\n\n- FAIR principles\n\nThe main characteristic of DSpace-CRIS is its flexible data model, which allows you to collect and manage research data and information typical of a CRIS system, to define entities and attributes with their reciprocal links. If you would just want to enhance the management of authors, provide name variants and IDs such as the ORCiD, exploit the varied ecosystem of persistent identifiers, link researchers to projects, awards, etc., DSpace-CRIS flexible data model can support this without aggravating the management burden of a normal institutional repository, while giving a great added value. Besides, it has useful features such as the collaboration network graph, aggregated (by researcher, by department) bibliometrics and statistics with graphic reporting, CVs and bibliographies, integration with ORCiD API v.3 and much more, you can explore them vie the menu items here on the left. \n\nIts flexibility allows to configure different data models and metadata schemas, providing the community with new and creative uses of DSpace, such as DSpace-GLAM (Galleries, Libraries, Archives and Museums) for the Cultural Heritage.', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.description.provenance': [ + { + 'value': 'Made available in DSpace on 2020-09-05T16:33:33Z (GMT). No. of bitstreams: 1\nlayers.png: 295537 bytes, checksum: 001e24b05d2c5d6cd76e88580f10cb9b (MD5)', + 'language': 'en', + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.identifier.uri': [ + { + 'value': 'https://dspacecris7.4science.cloud/handle/123456789/34', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.subject': [ + { + 'value': 'cerif', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + }, + { + 'value': 'datamanagement', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 1 + }, + { + 'value': 'opensource', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 2 + } + ], + 'dc.title': [ + { + 'value': 'DSpace-CRIS', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.type': [ + { + 'value': 'applied research', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dspace.entity.type': [ + { + 'value': 'Project', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'oairecerif.acronym': [ + { + 'value': 'DSC', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'oairecerif.oamandate': [ + { + 'value': 'true', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'oairecerif.oamandate.url': [ + { + 'value': 'https://www.4science.it/en/open-source/', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'oairecerif.project.startDate': [ + { + 'value': '2009-04', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'oairecerif.project.status': [ + { + 'value': 'ongoing', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ] + }, + 'inArchive': true, + 'discoverable': true, + 'withdrawn': false, + 'lastModified': '2021-08-02T14:19:24.927+00:00', + 'entityType': 'Project', + 'type': 'item', + '_links': { + 'bundles': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/bundles' + }, + 'mappedCollections': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/mappedCollections' + }, + 'owningCollection': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/owningCollection' + }, + 'relationships': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/relationships' + }, + 'version': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/version' + }, + 'templateItemOf': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/templateItemOf' + }, + 'metrics': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/metrics' + }, + 'thumbnail': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/thumbnail' + }, + 'self': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c' + } + } + } + }, + 'type': 'subscription' + }, + { + 'id': 27, + 'subscriptionType': 'content', + 'subscriptionParameterList': [ + { + 'id': 89, + 'name': 'frequency', + 'value': 'D' + }, + { + 'id': 90, + 'name': 'frequency', + 'value': 'M' + }, + { + 'id': 91, + 'name': 'frequency', + 'value': 'W' + } + ], + '_links': { + 'dSpaceObject': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/27/dSpaceObject' + }, + 'ePerson': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/27/ePerson' + }, + 'self': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/27' + } + }, + '_embedded': { + 'ePerson': { + 'id': '335647b6-8a52-4ecb-a8c1-7ebabb199bda', + 'uuid': '335647b6-8a52-4ecb-a8c1-7ebabb199bda', + 'name': 'dspacedemo+admin@gmail.com', + 'handle': null, + 'metadata': { + 'dspace.agreements.cookies': [ + { + 'value': '{\'authentication\':true,\'preferences\':true,\'acknowledgement\':true,\'google-analytics\':true}', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dspace.agreements.end-user': [ + { + 'value': 'true', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'eperson.firstname': [ + { + 'value': 'Demo', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'eperson.lastname': [ + { + 'value': 'Site Administrator', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ] + }, + 'netid': null, + 'lastActive': '2021-09-01T12:06:19.000+00:00', + 'canLogIn': true, + 'email': 'dspacedemo+admin@gmail.com', + 'requireCertificate': false, + 'selfRegistered': false, + 'type': 'eperson', + '_links': { + 'groups': { + 'href': 'https://dspacecris7.4science.cloud/server/api/eperson/epersons/335647b6-8a52-4ecb-a8c1-7ebabb199bda/groups' + }, + 'self': { + 'href': 'https://dspacecris7.4science.cloud/server/api/eperson/epersons/335647b6-8a52-4ecb-a8c1-7ebabb199bda' + } + } + }, + 'dSpaceObject': { + 'id': '90f3b9ce-db65-479c-90d7-8c794abf942c', + 'uuid': '90f3b9ce-db65-479c-90d7-8c794abf942c', + 'name': 'DSpace-CRIS', + 'handle': '123456789/34', + 'metadata': { + 'crispj.coinvestigators': [ + { + 'value': 'Mornati, Susanna', + 'language': null, + 'authority': '1325093a-1ef4-4d87-920d-02ce544fea00', + 'confidence': 600, + 'place': 0 + }, + { + 'value': 'Lombardi, Corrado', + 'language': null, + 'authority': 'b5ad6864-012d-4989-8e0d-4acfa1156fd9', + 'confidence': 600, + 'place': 1 + } + ], + 'crispj.coordinator': [ + { + 'value': '4Science', + 'language': null, + 'authority': 'a14ba215-c0f0-4b74-b21a-06359bfabd45', + 'confidence': 600, + 'place': 0 + } + ], + 'crispj.investigator': [ + { + 'value': 'Bollini, Andrea', + 'language': null, + 'authority': '092b59e8-8159-4e70-98b5-93ec60bd3431', + 'confidence': 600, + 'place': 0 + } + ], + 'dc.date.accessioned': [ + { + 'value': '2020-09-05T16:33:33Z', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.date.available': [ + { + 'value': '2020-09-05T16:33:33Z', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.description.abstract': [ + { + 'value': 'DSpace-CRIS is the first free open-source extension of DSpace for the Research Data and Information Management ever developed. Differently from other (commercial) CRIS/RIMS (star), DSpace-CRIS has the institutional repository as its core component, providing high visibility on the web to all the collected information and objects. DSpace-CRIS broadens DSpace functionality and expands its data model while remaining aligned with its code base. \n\nDSpace-CRIS adopts/is compliant with international standards and practices to facilitate interoperability and data transfer:\n\n- ORCID API v2 (complete compliance including pull/push of info for profiles, publications, projects). ORCID API v3 compliance is being released.\n\n- Signposting and ResourceSync (which implement COAR NGR Recommended Behaviors)\n\n- OpenAIRE Guidelines for Literature Repository Managers v4, for Data Archives, for CRIS Managers v1.1.1 (based on CERIF, released Nov. 2019)\n\n- PlanS (by Coalition S)\n\n- FAIR principles\n\nThe main characteristic of DSpace-CRIS is its flexible data model, which allows you to collect and manage research data and information typical of a CRIS system, to define entities and attributes with their reciprocal links. If you would just want to enhance the management of authors, provide name variants and IDs such as the ORCiD, exploit the varied ecosystem of persistent identifiers, link researchers to projects, awards, etc., DSpace-CRIS flexible data model can support this without aggravating the management burden of a normal institutional repository, while giving a great added value. Besides, it has useful features such as the collaboration network graph, aggregated (by researcher, by department) bibliometrics and statistics with graphic reporting, CVs and bibliographies, integration with ORCiD API v.3 and much more, you can explore them vie the menu items here on the left. \n\nIts flexibility allows to configure different data models and metadata schemas, providing the community with new and creative uses of DSpace, such as DSpace-GLAM (Galleries, Libraries, Archives and Museums) for the Cultural Heritage.', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.description.provenance': [ + { + 'value': 'Made available in DSpace on 2020-09-05T16:33:33Z (GMT). No. of bitstreams: 1\nlayers.png: 295537 bytes, checksum: 001e24b05d2c5d6cd76e88580f10cb9b (MD5)', + 'language': 'en', + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.identifier.uri': [ + { + 'value': 'https://dspacecris7.4science.cloud/handle/123456789/34', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.subject': [ + { + 'value': 'cerif', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + }, + { + 'value': 'datamanagement', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 1 + }, + { + 'value': 'opensource', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 2 + } + ], + 'dc.title': [ + { + 'value': 'DSpace-CRIS', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.type': [ + { + 'value': 'applied research', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dspace.entity.type': [ + { + 'value': 'Project', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'oairecerif.acronym': [ + { + 'value': 'DSC', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'oairecerif.oamandate': [ + { + 'value': 'true', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'oairecerif.oamandate.url': [ + { + 'value': 'https://www.4science.it/en/open-source/', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'oairecerif.project.startDate': [ + { + 'value': '2009-04', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'oairecerif.project.status': [ + { + 'value': 'ongoing', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ] + }, + 'inArchive': true, + 'discoverable': true, + 'withdrawn': false, + 'lastModified': '2021-08-02T14:19:24.927+00:00', + 'entityType': 'Project', + 'type': 'item', + '_links': { + 'bundles': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/bundles' + }, + 'mappedCollections': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/mappedCollections' + }, + 'owningCollection': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/owningCollection' + }, + 'relationships': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/relationships' + }, + 'version': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/version' + }, + 'templateItemOf': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/templateItemOf' + }, + 'metrics': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/metrics' + }, + 'thumbnail': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/thumbnail' + }, + 'self': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c' + } + } + } + }, + 'type': 'subscription' + }, + { + 'id': 30, + 'subscriptionType': 'statistics', + 'subscriptionParameterList': [ + { + 'id': 96, + 'name': 'frequency', + 'value': 'M' + }, + { + 'id': 97, + 'name': 'frequency', + 'value': 'D' + } + ], + '_links': { + 'dSpaceObject': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/30/dSpaceObject' + }, + 'ePerson': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/30/ePerson' + }, + 'self': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/30' + } + }, + '_embedded': { + 'ePerson': { + 'id': '335647b6-8a52-4ecb-a8c1-7ebabb199bda', + 'uuid': '335647b6-8a52-4ecb-a8c1-7ebabb199bda', + 'name': 'dspacedemo+admin@gmail.com', + 'handle': null, + 'metadata': { + 'dspace.agreements.cookies': [ + { + 'value': '{\'authentication\':true,\'preferences\':true,\'acknowledgement\':true,\'google-analytics\':true}', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dspace.agreements.end-user': [ + { + 'value': 'true', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'eperson.firstname': [ + { + 'value': 'Demo', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'eperson.lastname': [ + { + 'value': 'Site Administrator', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ] + }, + 'netid': null, + 'lastActive': '2021-09-01T12:06:19.000+00:00', + 'canLogIn': true, + 'email': 'dspacedemo+admin@gmail.com', + 'requireCertificate': false, + 'selfRegistered': false, + 'type': 'eperson', + '_links': { + 'groups': { + 'href': 'https://dspacecris7.4science.cloud/server/api/eperson/epersons/335647b6-8a52-4ecb-a8c1-7ebabb199bda/groups' + }, + 'self': { + 'href': 'https://dspacecris7.4science.cloud/server/api/eperson/epersons/335647b6-8a52-4ecb-a8c1-7ebabb199bda' + } + } + }, + 'dSpaceObject': { + 'id': 'b2140cd5-bfdf-4b5b-83fb-8bab4c899b40', + 'uuid': 'b2140cd5-bfdf-4b5b-83fb-8bab4c899b40', + 'name': 'DSpace administration issues: the community admin patch', + 'handle': '123456789/107', + 'metadata': { + 'dc.contributor.author': [ + { + 'value': 'Donohue, Tim', + 'language': null, + 'authority': 'fcae3ff0-6a04-4385-8cae-04a38bbe4969', + 'confidence': 600, + 'place': 0 + }, + { + 'value': 'Bollini, Andrea', + 'language': null, + 'authority': '092b59e8-8159-4e70-98b5-93ec60bd3431', + 'confidence': 600, + 'place': 1 + } + ], + 'dc.date.accessioned': [ + { + 'value': '2020-12-06T22:35:52Z', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.date.available': [ + { + 'value': '2020-12-06T22:35:52Z', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.date.issued': [ + { + 'value': '2005', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.description.abstract': [ + { + 'value': 'A large or medium repository, but also a small repository in some special cases, needs to allow a more decentralized management of administrative activities as: creation of new communities, creation of new collections, management of submitter and workflow groups, editing of published items, access policies and so on. Until now, DSpace allows only a partial decentralization of this functionalities thought into the role of COLLECTION ADMIN. After highlighting these needs, we will introduce the new role of COMMUNITY ADMIN and the changes made to the COLLECTION ADMIN role by our patch so to fix most of the previous needs. We will talk about the \'long history\' of this patch, made for the first time by Andrea against the 1.2 series and next kept updated, bug free and XMLUI aware by Tim from the 1.4 series. This \'pass the buck\', from Andrea to Tim and again together with some other people, shows how useful is for anyone to share results, experiences and customizations with the community so to get them back improved, reducing the cost of locale maintenance, debug and bug fix. We will close the presentation with the good news of the inclusion of this patch in the DSpace codebase for both XMLUI and JSPUI interfaces and also giving some notices about possible improvements in the next future. DSpace User Group Meeting 2009', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.description.provenance': [ + { + 'value': 'Made available in DSpace on 2020-12-06T22:35:52Z (GMT). No. of bitstreams: 0\n Previous issue date: 2005', + 'language': 'en', + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.identifier.other': [ + { + 'value': 'od______1149::b43204759808a65356e6cc3ba285d29f', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.identifier.uri': [ + { + 'value': 'https://dspacecris7.4science.cloud/handle/123456789/107', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.language.iso': [ + { + 'value': 'en', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.source': [ + { + 'value': 'Göteborgs universitets publikationer - e-publicering och e-arkiv', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.subject': [ + { + 'value': 'dspace', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + }, + { + 'value': 'open source', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 1 + } + ], + 'dc.title': [ + { + 'value': 'DSpace administration issues: the community admin patch', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.type': [ + { + 'value': 'Controlled Vocabulary for Resource Type Genres::text::conference object', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dspace.entity.type': [ + { + 'value': 'Publication', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'oairecerif.author.affiliation': [ + { + 'value': '#PLACEHOLDER_PARENT_METADATA_VALUE#', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + }, + { + 'value': '#PLACEHOLDER_PARENT_METADATA_VALUE#', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 1 + } + ] + }, + 'inArchive': true, + 'discoverable': true, + 'withdrawn': false, + 'lastModified': '2021-05-31T21:41:41.737+00:00', + 'entityType': 'Publication', + 'type': 'item', + '_links': { + 'bundles': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40/bundles' + }, + 'mappedCollections': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40/mappedCollections' + }, + 'owningCollection': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40/owningCollection' + }, + 'relationships': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40/relationships' + }, + 'version': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40/version' + }, + 'templateItemOf': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40/templateItemOf' + }, + 'metrics': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40/metrics' + }, + 'thumbnail': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40/thumbnail' + }, + 'self': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40' + } + } + } + }, + 'type': 'subscription' + }, + { + 'id': 31, + 'subscriptionType': 'content', + 'subscriptionParameterList': [ + { + 'id': 98, + 'name': 'frequency', + 'value': 'D' + }, + { + 'id': 99, + 'name': 'frequency', + 'value': 'M' + } + ], + '_links': { + 'dSpaceObject': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/31/dSpaceObject' + }, + 'ePerson': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/31/ePerson' + }, + 'self': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/31' + } + }, + '_embedded': { + 'ePerson': { + 'id': '335647b6-8a52-4ecb-a8c1-7ebabb199bda', + 'uuid': '335647b6-8a52-4ecb-a8c1-7ebabb199bda', + 'name': 'dspacedemo+admin@gmail.com', + 'handle': null, + 'metadata': { + 'dspace.agreements.cookies': [ + { + 'value': '{\'authentication\':true,\'preferences\':true,\'acknowledgement\':true,\'google-analytics\':true}', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dspace.agreements.end-user': [ + { + 'value': 'true', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'eperson.firstname': [ + { + 'value': 'Demo', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'eperson.lastname': [ + { + 'value': 'Site Administrator', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ] + }, + 'netid': null, + 'lastActive': '2021-09-01T12:06:19.000+00:00', + 'canLogIn': true, + 'email': 'dspacedemo+admin@gmail.com', + 'requireCertificate': false, + 'selfRegistered': false, + 'type': 'eperson', + '_links': { + 'groups': { + 'href': 'https://dspacecris7.4science.cloud/server/api/eperson/epersons/335647b6-8a52-4ecb-a8c1-7ebabb199bda/groups' + }, + 'self': { + 'href': 'https://dspacecris7.4science.cloud/server/api/eperson/epersons/335647b6-8a52-4ecb-a8c1-7ebabb199bda' + } + } + }, + 'dSpaceObject': { + 'id': 'b2140cd5-bfdf-4b5b-83fb-8bab4c899b40', + 'uuid': 'b2140cd5-bfdf-4b5b-83fb-8bab4c899b40', + 'name': 'DSpace administration issues: the community admin patch', + 'handle': '123456789/107', + 'metadata': { + 'dc.contributor.author': [ + { + 'value': 'Donohue, Tim', + 'language': null, + 'authority': 'fcae3ff0-6a04-4385-8cae-04a38bbe4969', + 'confidence': 600, + 'place': 0 + }, + { + 'value': 'Bollini, Andrea', + 'language': null, + 'authority': '092b59e8-8159-4e70-98b5-93ec60bd3431', + 'confidence': 600, + 'place': 1 + } + ], + 'dc.date.accessioned': [ + { + 'value': '2020-12-06T22:35:52Z', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.date.available': [ + { + 'value': '2020-12-06T22:35:52Z', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.date.issued': [ + { + 'value': '2005', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.description.abstract': [ + { + 'value': 'A large or medium repository, but also a small repository in some special cases, needs to allow a more decentralized management of administrative activities as: creation of new communities, creation of new collections, management of submitter and workflow groups, editing of published items, access policies and so on. Until now, DSpace allows only a partial decentralization of this functionalities thought into the role of COLLECTION ADMIN. After highlighting these needs, we will introduce the new role of COMMUNITY ADMIN and the changes made to the COLLECTION ADMIN role by our patch so to fix most of the previous needs. We will talk about the \'long history\' of this patch, made for the first time by Andrea against the 1.2 series and next kept updated, bug free and XMLUI aware by Tim from the 1.4 series. This \'pass the buck\', from Andrea to Tim and again together with some other people, shows how useful is for anyone to share results, experiences and customizations with the community so to get them back improved, reducing the cost of locale maintenance, debug and bug fix. We will close the presentation with the good news of the inclusion of this patch in the DSpace codebase for both XMLUI and JSPUI interfaces and also giving some notices about possible improvements in the next future. DSpace User Group Meeting 2009', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.description.provenance': [ + { + 'value': 'Made available in DSpace on 2020-12-06T22:35:52Z (GMT). No. of bitstreams: 0\n Previous issue date: 2005', + 'language': 'en', + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.identifier.other': [ + { + 'value': 'od______1149::b43204759808a65356e6cc3ba285d29f', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.identifier.uri': [ + { + 'value': 'https://dspacecris7.4science.cloud/handle/123456789/107', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.language.iso': [ + { + 'value': 'en', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.source': [ + { + 'value': 'Göteborgs universitets publikationer - e-publicering och e-arkiv', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.subject': [ + { + 'value': 'dspace', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + }, + { + 'value': 'open source', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 1 + } + ], + 'dc.title': [ + { + 'value': 'DSpace administration issues: the community admin patch', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.type': [ + { + 'value': 'Controlled Vocabulary for Resource Type Genres::text::conference object', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dspace.entity.type': [ + { + 'value': 'Publication', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'oairecerif.author.affiliation': [ + { + 'value': '#PLACEHOLDER_PARENT_METADATA_VALUE#', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + }, + { + 'value': '#PLACEHOLDER_PARENT_METADATA_VALUE#', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 1 + } + ] + }, + 'inArchive': true, + 'discoverable': true, + 'withdrawn': false, + 'lastModified': '2021-05-31T21:41:41.737+00:00', + 'entityType': 'Publication', + 'type': 'item', + '_links': { + 'bundles': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40/bundles' + }, + 'mappedCollections': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40/mappedCollections' + }, + 'owningCollection': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40/owningCollection' + }, + 'relationships': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40/relationships' + }, + 'version': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40/version' + }, + 'templateItemOf': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40/templateItemOf' + }, + 'metrics': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40/metrics' + }, + 'thumbnail': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40/thumbnail' + }, + 'self': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40' + } + } + } + }, + 'type': 'subscription' + }, + { + 'id': 32, + 'subscriptionType': 'content', + 'subscriptionParameterList': [ + { + 'id': 100, + 'name': 'frequency', + 'value': 'W' + } + ], + '_links': { + 'dSpaceObject': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/32/dSpaceObject' + }, + 'ePerson': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/32/ePerson' + }, + 'self': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/32' + } + }, + '_embedded': { + 'ePerson': { + 'id': '335647b6-8a52-4ecb-a8c1-7ebabb199bda', + 'uuid': '335647b6-8a52-4ecb-a8c1-7ebabb199bda', + 'name': 'dspacedemo+admin@gmail.com', + 'handle': null, + 'metadata': { + 'dspace.agreements.cookies': [ + { + 'value': '{\'authentication\':true,\'preferences\':true,\'acknowledgement\':true,\'google-analytics\':true}', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dspace.agreements.end-user': [ + { + 'value': 'true', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'eperson.firstname': [ + { + 'value': 'Demo', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'eperson.lastname': [ + { + 'value': 'Site Administrator', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ] + }, + 'netid': null, + 'lastActive': '2021-09-01T12:06:19.000+00:00', + 'canLogIn': true, + 'email': 'dspacedemo+admin@gmail.com', + 'requireCertificate': false, + 'selfRegistered': false, + 'type': 'eperson', + '_links': { + 'groups': { + 'href': 'https://dspacecris7.4science.cloud/server/api/eperson/epersons/335647b6-8a52-4ecb-a8c1-7ebabb199bda/groups' + }, + 'self': { + 'href': 'https://dspacecris7.4science.cloud/server/api/eperson/epersons/335647b6-8a52-4ecb-a8c1-7ebabb199bda' + } + } + }, + 'dSpaceObject': { + 'id': 'b2140cd5-bfdf-4b5b-83fb-8bab4c899b40', + 'uuid': 'b2140cd5-bfdf-4b5b-83fb-8bab4c899b40', + 'name': 'DSpace administration issues: the community admin patch', + 'handle': '123456789/107', + 'metadata': { + 'dc.contributor.author': [ + { + 'value': 'Donohue, Tim', + 'language': null, + 'authority': 'fcae3ff0-6a04-4385-8cae-04a38bbe4969', + 'confidence': 600, + 'place': 0 + }, + { + 'value': 'Bollini, Andrea', + 'language': null, + 'authority': '092b59e8-8159-4e70-98b5-93ec60bd3431', + 'confidence': 600, + 'place': 1 + } + ], + 'dc.date.accessioned': [ + { + 'value': '2020-12-06T22:35:52Z', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.date.available': [ + { + 'value': '2020-12-06T22:35:52Z', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.date.issued': [ + { + 'value': '2005', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.description.abstract': [ + { + 'value': 'A large or medium repository, but also a small repository in some special cases, needs to allow a more decentralized management of administrative activities as: creation of new communities, creation of new collections, management of submitter and workflow groups, editing of published items, access policies and so on. Until now, DSpace allows only a partial decentralization of this functionalities thought into the role of COLLECTION ADMIN. After highlighting these needs, we will introduce the new role of COMMUNITY ADMIN and the changes made to the COLLECTION ADMIN role by our patch so to fix most of the previous needs. We will talk about the \'long history\' of this patch, made for the first time by Andrea against the 1.2 series and next kept updated, bug free and XMLUI aware by Tim from the 1.4 series. This \'pass the buck\', from Andrea to Tim and again together with some other people, shows how useful is for anyone to share results, experiences and customizations with the community so to get them back improved, reducing the cost of locale maintenance, debug and bug fix. We will close the presentation with the good news of the inclusion of this patch in the DSpace codebase for both XMLUI and JSPUI interfaces and also giving some notices about possible improvements in the next future. DSpace User Group Meeting 2009', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.description.provenance': [ + { + 'value': 'Made available in DSpace on 2020-12-06T22:35:52Z (GMT). No. of bitstreams: 0\n Previous issue date: 2005', + 'language': 'en', + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.identifier.other': [ + { + 'value': 'od______1149::b43204759808a65356e6cc3ba285d29f', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.identifier.uri': [ + { + 'value': 'https://dspacecris7.4science.cloud/handle/123456789/107', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.language.iso': [ + { + 'value': 'en', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.source': [ + { + 'value': 'Göteborgs universitets publikationer - e-publicering och e-arkiv', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.subject': [ + { + 'value': 'dspace', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + }, + { + 'value': 'open source', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 1 + } + ], + 'dc.title': [ + { + 'value': 'DSpace administration issues: the community admin patch', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dc.type': [ + { + 'value': 'Controlled Vocabulary for Resource Type Genres::text::conference object', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'dspace.entity.type': [ + { + 'value': 'Publication', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'oairecerif.author.affiliation': [ + { + 'value': '#PLACEHOLDER_PARENT_METADATA_VALUE#', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + }, + { + 'value': '#PLACEHOLDER_PARENT_METADATA_VALUE#', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 1 + } + ] + }, + 'inArchive': true, + 'discoverable': true, + 'withdrawn': false, + 'lastModified': '2021-05-31T21:41:41.737+00:00', + 'entityType': 'Publication', + 'type': 'item', + '_links': { + 'bundles': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40/bundles' + }, + 'mappedCollections': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40/mappedCollections' + }, + 'owningCollection': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40/owningCollection' + }, + 'relationships': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40/relationships' + }, + 'version': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40/version' + }, + 'templateItemOf': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40/templateItemOf' + }, + 'metrics': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40/metrics' + }, + 'thumbnail': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40/thumbnail' + }, + 'self': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40' + } + } + } + }, + 'type': 'subscription' + } + ] +}; + +export const findByEPersonAndDsoRes = { + 'type': { + 'value': 'paginated-list' + }, + 'pageInfo': { + 'elementsPerPage': 20, + 'totalElements': 2, + 'totalPages': 1, + 'currentPage': 1 + }, + '_links': { + 'self': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/search/findByEPersonAndDso?dspace_object_id=092b59e8-8159-4e70-98b5-93ec60bd3431&eperson_id=335647b6-8a52-4ecb-a8c1-7ebabb199bda' + }, + 'page': [ + { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/22' + }, + { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/48' + } + ] + }, + 'page': [ + { + 'id': 22, + 'subscriptionType': 'content', + 'subscriptionParameterList': [ + { + 'id': 161, + 'name': 'frequency', + 'value': 'M' + } + ], + '_links': { + 'dSpaceObject': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/22/dSpaceObject' + }, + 'ePerson': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/22/ePerson' + }, + 'self': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/22' + } + }, + 'type': 'subscription' + }, + { + 'id': 48, + 'subscriptionType': 'statistics', + 'subscriptionParameterList': [ + { + 'id': 159, + 'name': 'frequency', + 'value': 'D' + }, + { + 'id': 160, + 'name': 'frequency', + 'value': 'M' + } + ], + '_links': { + 'dSpaceObject': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/48/dSpaceObject' + }, + 'ePerson': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/48/ePerson' + }, + 'self': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/48' + } + }, + 'type': 'subscription' + } + ] +}; + + +export const findByEPersonAndDsoResEmpty = { + 'type': { + 'value': 'paginated-list' + }, + 'pageInfo': { + 'elementsPerPage': 0, + 'totalElements': 0, + 'totalPages': 1, + 'currentPage': 1 + }, + '_links': { + 'self': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/search/findByEPersonAndDso?dspace_object_id=092b59e8-8159-4e70-98b5-93ec60bd3431&eperson_id=335647b6-8a52-4ecb-a8c1-7ebabb199bda' + }, + 'page': [ + { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/22' + }, + { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/48' + } + ] + }, + 'page': [] +}; + +export const subscription = { + 'id': 21, + 'type': 'subscription', + 'subscriptionParameterList': [ + { + 'id': 77, + 'name': 'frequency', + 'value': 'D' + }, + { + 'id': 78, + 'name': 'frequency', + 'value': 'M' + } + ], + 'subscriptionType': 'content', + '_links': { + 'dSpaceObject': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/21/dSpaceObject' + }, + 'ePerson': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/21/ePerson' + }, + 'self': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/21' + } + } +}; + + diff --git a/src/app/shared/testing/test-module.ts b/src/app/shared/testing/test-module.ts index 7d45dd41f3..85fa295dc2 100644 --- a/src/app/shared/testing/test-module.ts +++ b/src/app/shared/testing/test-module.ts @@ -25,9 +25,10 @@ import { BrowserOnlyMockPipe } from './browser-only-mock.pipe'; NgComponentOutletDirectiveStub, BrowserOnlyMockPipe, ], - exports: [ - QueryParamsDirectiveStub - ], + exports: [ + QueryParamsDirectiveStub, + RouterLinkDirectiveStub + ], schemas: [ CUSTOM_ELEMENTS_SCHEMA ] diff --git a/src/app/subscriptions-page/subscriptions-page-routing.module.ts b/src/app/subscriptions-page/subscriptions-page-routing.module.ts new file mode 100644 index 0000000000..149c9a415f --- /dev/null +++ b/src/app/subscriptions-page/subscriptions-page-routing.module.ts @@ -0,0 +1,27 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; +import { SubscriptionsPageModule } from './subscriptions-page.module'; +import { SubscriptionsPageComponent } from './subscriptions-page.component'; + + +@NgModule({ + imports: [ + SubscriptionsPageModule, + RouterModule.forChild([ + { + path: '', + data: { + title: 'subscriptions.title', + }, + children: [ + { + path: '', + component: SubscriptionsPageComponent, + }, + ] + }, + ]) + ] +}) +export class SubscriptionsPageRoutingModule { +} diff --git a/src/app/subscriptions-page/subscriptions-page.component.html b/src/app/subscriptions-page/subscriptions-page.component.html new file mode 100644 index 0000000000..bfbdbe72fc --- /dev/null +++ b/src/app/subscriptions-page/subscriptions-page.component.html @@ -0,0 +1,45 @@ +
+
+
+

{{'subscriptions.title' | translate}}

+
+
+ + + + +
+ + + + + + + + + + + + + +
{{'subscriptions.table.dso' | translate}}{{'subscriptions.table.subscription_type' | translate}}{{'subscriptions.table.subscription_frequency' | translate}}{{'subscriptions.table.action' | translate}}
+
+
+ +
+ {{ 'subscriptions.table.empty.message' | translate }} +
+ +
+
+ +
+
+
+
diff --git a/src/app/subscriptions-page/subscriptions-page.component.scss b/src/app/subscriptions-page/subscriptions-page.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/subscriptions-page/subscriptions-page.component.spec.ts b/src/app/subscriptions-page/subscriptions-page.component.spec.ts new file mode 100644 index 0000000000..77d2f6805a --- /dev/null +++ b/src/app/subscriptions-page/subscriptions-page.component.spec.ts @@ -0,0 +1,127 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core'; + + +// Import modules +import { CommonModule } from '@angular/common'; +import { BrowserModule, By } from '@angular/platform-browser'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { ActivatedRoute } from '@angular/router'; +import { RouterTestingModule } from '@angular/router/testing'; + + +// Import components +import { SubscriptionsPageComponent } from './subscriptions-page.component'; + +// Import services +import { PaginationService } from '../core/pagination/pagination.service'; +import { SubscriptionService } from '../shared/subscriptions/subscription.service'; +import { PaginationServiceStub } from '../shared/testing/pagination-service.stub'; +import { AuthService } from '../core/auth/auth.service'; + +// Import utils +import { HostWindowService } from '../shared/host-window.service'; +import { HostWindowServiceStub } from '../shared/testing/host-window-service.stub'; + + +// Import mocks +import { TranslateLoaderMock } from '../shared/mocks/translate-loader.mock'; +import { findAllSubscriptionRes } from '../shared/testing/subscriptions-data.mock'; +import { MockActivatedRoute } from '../shared/mocks/active-router.mock'; +import { of as observableOf } from 'rxjs'; +import { EPersonMock } from '../shared/testing/eperson.mock'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { VarDirective } from '../shared/utils/var.directive'; +import { + SubscriptionViewComponent +} from '../shared/subscriptions/components/subscription-view/subscription-view.component'; + + +describe('SubscriptionsPageComponent', () => { + let component: SubscriptionsPageComponent; + let fixture: ComponentFixture; + let de: DebugElement; + + const authServiceStub = jasmine.createSpyObj('authorizationService', { + getAuthenticatedUserFromStore: observableOf(EPersonMock) + }); + + const subscriptionServiceStub = jasmine.createSpyObj('SubscriptionService', { + findByEPerson: observableOf(findAllSubscriptionRes) + }); + const paginationService = new PaginationServiceStub(); + + beforeEach(waitForAsync( () => { + TestBed.configureTestingModule({ + imports: [ + CommonModule, + BrowserModule, + RouterTestingModule.withRoutes([]), + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock + } + }), + NoopAnimationsModule + ], + declarations: [ SubscriptionsPageComponent, SubscriptionViewComponent, VarDirective ], + providers:[ + { provide: SubscriptionService, useValue: subscriptionServiceStub }, + { provide: HostWindowService, useValue: new HostWindowServiceStub(0) }, + { provide: ActivatedRoute, useValue: new MockActivatedRoute() }, + { provide: AuthService, useValue: authServiceStub }, + { provide: PaginationService, useValue: paginationService } + ], + schemas: [NO_ERRORS_SCHEMA] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SubscriptionsPageComponent); + component = fixture.componentInstance; + de = fixture.debugElement; + fixture.detectChanges(); + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); + + describe('when table', () => { + + it('should show table', async() => { + await fixture.whenStable(); + fixture.detectChanges(); + const table = de.query(By.css('table')); + expect(table).toBeTruthy(); + }); + + }); + + it('should show all the results', () => { + expect(de.queryAll(By.css('tbody > tr')).length).toEqual(10); + }); + + it('should have dso object info', () => { + expect(de.query(By.css('.dso-info > span'))).toBeTruthy(); + expect(de.query(By.css('.dso-info > p > a'))).toBeTruthy(); + }); + + it('should have subscription type info', () => { + expect(de.query(By.css('.subscription-type'))).toBeTruthy(); + }); + + it('should have subscription paramenter info', () => { + expect(de.query(By.css('.subscription-parmenters > span'))).toBeTruthy(); + }); + + it('should have subscription action info', () => { + expect(de.query(By.css('.btn-outline-primary'))).toBeTruthy(); + expect(de.query(By.css('.btn-outline-danger'))).toBeTruthy(); + }); + + +}); diff --git a/src/app/subscriptions-page/subscriptions-page.component.ts b/src/app/subscriptions-page/subscriptions-page.component.ts new file mode 100644 index 0000000000..b81b7c5617 --- /dev/null +++ b/src/app/subscriptions-page/subscriptions-page.component.ts @@ -0,0 +1,117 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { BehaviorSubject, Subscription as rxSubscription } from 'rxjs'; +import { switchMap, take } from 'rxjs/operators'; +import { Subscription } from '../shared/subscriptions/models/subscription.model'; +import { buildPaginatedList, PaginatedList } from '../core/data/paginated-list.model'; +import { SubscriptionService } from '../shared/subscriptions/subscription.service'; +import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; +import { PaginationService } from '../core/pagination/pagination.service'; +import { PageInfo } from '../core/shared/page-info.model'; +import { AuthService } from '../core/auth/auth.service'; +import { EPerson } from '../core/eperson/models/eperson.model'; + +@Component({ + selector: 'ds-subscriptions-page', + templateUrl: './subscriptions-page.component.html', + styleUrls: ['./subscriptions-page.component.scss'] +}) +export class SubscriptionsPageComponent implements OnInit, OnDestroy { + + /** + * The subscriptions to show on this page, as an Observable list. + */ + subscriptions$: BehaviorSubject> = new BehaviorSubject(buildPaginatedList(new PageInfo(), [])); + + /** + * The current pagination configuration for the page used by the FindAll method + * Currently simply renders subscriptions + */ + config: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), { + id: 'elp', + pageSize: 10, + currentPage: 1 + }); + + /** + * Subscription to be unsubscribed + */ + sub: rxSubscription; + + /** + * A boolean representing if is loading + */ + loading$: BehaviorSubject = new BehaviorSubject(false); + + /** + * EPerson id of the logged in user + */ + eperson: string; + + constructor( + private paginationService: PaginationService, + private authService: AuthService, + private subscriptionService: SubscriptionService + ) { } + + /** + * Subscribe the pagination service to send a request with specific pagination + * When page is changed it will request the new subscriptions for the new page config + */ + ngOnInit(): void { + this.authService.getAuthenticatedUserFromStore().pipe(take(1)).subscribe( (eperson: EPerson) => { + this.eperson = eperson.id; + + this.sub = this.paginationService.getCurrentPagination(this.config.id, this.config).pipe( + switchMap((findListOptions) => { + this.loading$.next(true); + return this.subscriptionService.findByEPerson(this.eperson,{ + currentPage: findListOptions.currentPage, + elementsPerPage: findListOptions.pageSize + }); + } + ) + ).subscribe({ + next: (res: any) => { + this.subscriptions$.next(res); + this.loading$.next(false); + }, + error: () => { + this.loading$.next(false); + } + }); + }); + } + + /** + * When an action is made and the information is changed refresh the information + */ + refresh(): void { + this.paginationService.getCurrentPagination(this.config.id, this.config).pipe( + take(1), + switchMap((findListOptions) => { + this.loading$.next(true); + return this.subscriptionService.findByEPerson(this.eperson,{ + currentPage: findListOptions.currentPage, + elementsPerPage: findListOptions.pageSize + }); + } + ) + ).subscribe({ + next: (res: any) => { + this.subscriptions$.next(res); + this.loading$.next(false); + }, + error: () => { + this.loading$.next(false); + } + }); + } + + /** + * Unsubscribe from pagination subscription + */ + ngOnDestroy(): void { + this.sub.unsubscribe(); + } + +} diff --git a/src/app/subscriptions-page/subscriptions-page.module.ts b/src/app/subscriptions-page/subscriptions-page.module.ts new file mode 100644 index 0000000000..f7a4dc3344 --- /dev/null +++ b/src/app/subscriptions-page/subscriptions-page.module.ts @@ -0,0 +1,15 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { SubscriptionsPageComponent } from './subscriptions-page.component'; +import { SharedModule } from '../shared/shared.module'; +import { SubscriptionsModule } from '../shared/subscriptions/subscriptions.module'; + +@NgModule({ + declarations: [SubscriptionsPageComponent], + imports: [ + CommonModule, + SharedModule, + SubscriptionsModule, + ] +}) +export class SubscriptionsPageModule { } diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index d043d61ae5..12a21beaa2 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -2298,6 +2298,22 @@ "item.page.claim.tooltip": "Claim this item as profile", + "item.page.subscriptions.tooltip": "Subscribe", + + "item.page.subscriptions.modal.title": "Subscriptions", + + "item.page.subscriptions.modal.close": "Close", + + "item.page.subscriptions.modal.new-subscription-form.type.content": "Content", + + "item.page.subscriptions.modal.new-subscription-form.frequency.D": "daily", + + "item.page.subscriptions.modal.new-subscription-form.frequency.M": "monthly", + + "item.page.subscriptions.modal.new-subscription-form.frequency.W": "weekly", + + "item.page.subscriptions.modal.new-subscription-form.submit": "Submit", + "item.preview.dc.identifier.uri": "Identifier:", "item.preview.dc.contributor.author": "Authors:",