mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
[CST-7757] Subscriptions porting (wip)
This commit is contained in:

committed by
Davide Negretti

parent
4915f10b0e
commit
b775cd5ab0
@@ -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}`;
|
||||
}
|
||||
|
@@ -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 },
|
||||
]
|
||||
}
|
||||
|
@@ -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({
|
||||
|
@@ -29,5 +29,6 @@ export enum FeatureID {
|
||||
CanViewUsageStatistics = 'canViewUsageStatistics',
|
||||
CanSendFeedback = 'canSendFeedback',
|
||||
CanClaimItem = 'canClaimItem',
|
||||
CanSynchronizeWithORCID = 'canSynchronizeWithORCID'
|
||||
CanSynchronizeWithORCID = 'canSynchronizeWithORCID',
|
||||
CanSubscribe = 'canSubscribeDso',
|
||||
}
|
||||
|
@@ -15,6 +15,7 @@
|
||||
[tooltipMsgCreate]="'item.page.version.create'"
|
||||
[tooltipMsgHasDraft]="'item.page.version.hasDraft'"></ds-dso-page-version-button>
|
||||
<ds-dso-page-edit-button [pageRoute]="itemPageRoute" [dso]="object" [tooltipMsg]="'publication.page.edit'"></ds-dso-page-edit-button>
|
||||
<ds-dso-page-subscription-button [dso]="object"></ds-dso-page-subscription-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
|
@@ -6,6 +6,8 @@
|
||||
</span>
|
||||
<a [ngClass]="inExpandableNavbar ? 'nav-item nav-link' : 'dropdown-item'" [routerLink]="[profileRoute]" routerLinkActive="active">{{'nav.profile' | translate}}</a>
|
||||
<a [ngClass]="inExpandableNavbar ? 'nav-item nav-link' : 'dropdown-item'" [routerLink]="[mydspaceRoute]" routerLinkActive="active">{{'nav.mydspace' | translate}}</a>
|
||||
<a [ngClass]="inExpandableNavbar ? 'nav-item nav-link' : 'dropdown-item'" [routerLink]="[subscriptionsRoute]" routerLinkActive="active">{{'nav.subscriptions' | translate}}</a>
|
||||
|
||||
<div class="dropdown-divider"></div>
|
||||
<ds-log-out *ngIf="!inExpandableNavbar" data-test="log-out-component"></ds-log-out>
|
||||
</div>
|
||||
|
@@ -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<AppState>,
|
||||
private authService: AuthService) {
|
||||
}
|
||||
|
@@ -0,0 +1,6 @@
|
||||
<button *ngIf="isAuthorized$ | async"
|
||||
(click)="openSubscriptionModal()"
|
||||
[ngbTooltip]="'item.page.subscriptions.tooltip' | translate"
|
||||
class="subscription-button btn btn-dark btn-sm">
|
||||
<i class="fas fa-bell fa-fw"></i>
|
||||
</button>
|
@@ -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<DsoPageSubscriptionButtonComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ DsoPageSubscriptionButtonComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(DsoPageSubscriptionButtonComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@@ -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<boolean> = 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;
|
||||
}
|
||||
|
||||
}
|
@@ -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 = [
|
||||
|
@@ -0,0 +1,44 @@
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">{{'item.page.subscriptions.modal.title' | translate}}</h4>
|
||||
<button type="button" class="close" aria-label="Close" (click)="c('cancel')">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div *ngIf="!!subscriptionForm" class="modal-body">
|
||||
<form [formGroup]="subscriptionForm">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="col-md-12">
|
||||
<div class="custom-control custom-checkbox">
|
||||
<label class="checkbox-label">
|
||||
{{ subscriptionForm.get('type').value == 'content' ? 'Content:' : 'Statistics:' }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12" *ngFor="let frequency of frequencies; let j=index">
|
||||
<input [id]="'checkbox-'+j" (change)="selectCheckbox($event,frequency.value)" [checked]="getIsChecked(frequency)" value="{{frequency.value}}" type="checkbox" />
|
||||
<label [for]="'checkbox-'+j">{{ 'context-menu.actions.subscription.'+frequency.name | translate }}</label>
|
||||
</div>
|
||||
<div *ngIf="!!submitted" class="alert">
|
||||
<div *ngIf="subscriptionForm.get('subscriptionParameterList').errors?.required">
|
||||
{{ 'context-menu.actions.subscription.frequency.required' | translate }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="c('cancel')">{{'context-menu.actions.request-correction.confirm.cancel' | translate}}</button>
|
||||
|
||||
<button type="button" (click)="submit()" class="btn btn-success" [disabled]="(processing$ | async)">
|
||||
<span *ngIf="(processing$ | async)"><i class='fas fa-circle-notch fa-spin'></i> {{'submission.workflow.tasks.generic.processing' | translate}}</span>
|
||||
<span *ngIf="!(processing$ | async)">{{'context-menu.actions.subscription.confirm.submit' | translate}}</span>
|
||||
</button>
|
||||
|
||||
</div>
|
@@ -0,0 +1,4 @@
|
||||
.alert{
|
||||
font-weight: bold;
|
||||
color:red;
|
||||
}
|
@@ -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<SubscriptionEditModalComponent>;
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
@@ -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<string> = new EventEmitter<string>();
|
||||
|
||||
/**
|
||||
* Reload event emit to refresh informations
|
||||
*/
|
||||
@Output() reload: EventEmitter<string> = new EventEmitter<string>();
|
||||
|
||||
/**
|
||||
* A boolean representing if a request operation is pending
|
||||
* @type {BehaviorSubject<boolean>}
|
||||
*/
|
||||
public processing$ = new BehaviorSubject<boolean>(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);
|
||||
}
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
<form [formGroup]="subscriptionForm" (ngSubmit)="submit()">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">{{'item.page.subscriptions.modal.title' | translate}}</h4>
|
||||
<button type="button" class="close" aria-label="Close" (click)="activeModal.close()">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
<div>
|
||||
|
||||
<fieldset class="form-group form-row">
|
||||
|
||||
<legend class="col-md-4 col-form-label float-md-left pt-0">
|
||||
{{ 'item.page.subscriptions.modal.new-subscription-form.type.content' | translate }}:
|
||||
</legend>
|
||||
|
||||
<div class="col-md-8">
|
||||
|
||||
<div class="form-check" *ngFor="let frequency of this.subscriptionForm.controls | keyvalue">
|
||||
<input type="checkbox" [id]="'checkbox-' + frequency" class="form-check-input" [disabled]="processing$ | async"
|
||||
[formControlName]="frequency.key"/>
|
||||
<label class="form-check-label"
|
||||
[for]="'checkbox-' + frequency.key">{{ 'item.page.subscriptions.modal.new-subscription-form.frequency.' + frequency.key | translate }}</label>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</fieldset>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-success" [disabled]="(processing$ | async)">
|
||||
<span *ngIf="(processing$ | async)"><i
|
||||
class='fas fa-circle-notch fa-spin'></i> {{'item.page.subscriptions.modal.new-subscription-form.processing' | translate}}</span>
|
||||
<span
|
||||
*ngIf="!(processing$ | async)">{{'item.page.subscriptions.modal.new-subscription-form.submit' | translate}}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary"
|
||||
(click)="activeModal.close()">{{'item.page.subscriptions.modal.close' | translate}}</button>
|
||||
|
||||
</div>
|
||||
|
||||
</form>
|
@@ -0,0 +1,12 @@
|
||||
.alert{
|
||||
font-weight: bold;
|
||||
color:red;
|
||||
}
|
||||
|
||||
// .modal-footer{
|
||||
// justify-content: space-between;
|
||||
// }
|
||||
|
||||
.add-button{
|
||||
padding: 0px 15px 15px 15px;
|
||||
}
|
@@ -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<SubscriptionModalComponent>;
|
||||
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();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
@@ -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<boolean>}
|
||||
*/
|
||||
public processing$ = new BehaviorSubject<boolean>(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<string>;
|
||||
|
||||
/**
|
||||
* 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<Subscription>) => {
|
||||
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<RemoteData<PaginatedList<Subscription>>> {
|
||||
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<NoContent> {
|
||||
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();
|
||||
});
|
||||
|
||||
}
|
||||
}*/
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
<td class="dso-info">
|
||||
<span *ngIf="!!dso" class="badge badge-info">{{dso.type}}</span>
|
||||
<p><a *ngIf="!!dso" [routerLink]="[getPageRoutePrefix(), dso.id]">{{dso.name}}</a></p>
|
||||
</td>
|
||||
<td>
|
||||
<span *ngIf="!!subscription" class="subscription-type">{{subscription.subscriptionType}}</span>
|
||||
</td>
|
||||
<td class="subscription-parmenters">
|
||||
<ng-container *ngIf="!!subscription">
|
||||
<span *ngFor="let parameterList of subscription.subscriptionParameterList; let i = index">
|
||||
{{ 'subscriptions.frequency.'+ parameterList.value | translate}}<span *ngIf="i < subscription.subscriptionParameterList.length-1 ">,</span>
|
||||
</span>
|
||||
</ng-container>
|
||||
</td>
|
||||
<td class="subscription-actions">
|
||||
<div class="btn-group edit-field">
|
||||
<button (click)="$event.preventDefault();openSubscription(subscriptionModal);" class="btn btn-outline-primary btn-sm access-control-editEPersonButton" title="Edit">
|
||||
<i class="fas fa-edit fa-fw"></i>
|
||||
</button>
|
||||
<button (click)="deleteSubscriptionPopup(subscription)" class="btn btn-outline-danger btn-sm access-control-deleteEPersonButton" title="Delete">
|
||||
<i class="fas fa-trash-alt fa-fw"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<ng-template #subscriptionModal let-c="close" let-d="dismiss">
|
||||
<ds-subscription-edit-modal (reload)="reload.emit({})" (close)="c('cancel')" [subscription]="subscription" [dso]="dso" [eperson]="eperson"></ds-subscription-edit-modal>
|
||||
</ng-template>
|
||||
|
@@ -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<SubscriptionViewComponent>;
|
||||
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();
|
||||
});
|
||||
|
||||
});
|
@@ -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();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
57
src/app/shared/subscriptions/models/subscription.model.ts
Normal file
57
src/app/shared/subscriptions/models/subscription.model.ts
Normal file
@@ -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;
|
||||
}
|
@@ -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');
|
166
src/app/shared/subscriptions/subscription.service.ts
Normal file
166
src/app/shared/subscriptions/subscription.service.ts
Normal file
@@ -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<Subscription> {
|
||||
protected findByEpersonLinkPath = 'findByEPerson';
|
||||
|
||||
private deleteData: DeleteDataImpl<Subscription>;
|
||||
private findAllData: FindAllData<Subscription>;
|
||||
private searchData: SearchDataImpl<Subscription>;
|
||||
|
||||
constructor(
|
||||
protected comparator: DSOChangeAnalyzer<Subscription>,
|
||||
protected http: HttpClient,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected requestService: RequestService,
|
||||
protected rdbService: RemoteDataBuildService,
|
||||
protected store: Store<any>,
|
||||
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<RemoteData<PaginatedList<Subscription>>> {
|
||||
|
||||
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<RemoteData<Subscription>> {
|
||||
|
||||
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<RemoteData<Subscription>>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<RemoteData<Subscription>>;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Deletes the subscription with a give id
|
||||
*
|
||||
* @param id the id of Subscription to delete
|
||||
*/
|
||||
deleteSubscription(id: string): Observable<RemoteData<NoContent>> {
|
||||
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<RemoteData<PaginatedList<Subscription>>> {
|
||||
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<RemoteData<PaginatedList<Subscription>>> {
|
||||
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'));
|
||||
}
|
||||
|
||||
}
|
31
src/app/shared/subscriptions/subscriptions.module.ts
Normal file
31
src/app/shared/subscriptions/subscriptions.module.ts
Normal file
@@ -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 { }
|
@@ -9,6 +9,9 @@ export class NotificationsServiceStub {
|
||||
remove = jasmine.createSpy('remove');
|
||||
removeAll = jasmine.createSpy('removeAll');
|
||||
|
||||
notificationWithAnchor() {
|
||||
}
|
||||
|
||||
private getDefaultOptions(): NotificationOptions {
|
||||
return new NotificationOptions();
|
||||
}
|
||||
|
@@ -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';
|
||||
}
|
||||
}
|
||||
|
4467
src/app/shared/testing/subscriptions-data.mock.ts
Normal file
4467
src/app/shared/testing/subscriptions-data.mock.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -25,9 +25,10 @@ import { BrowserOnlyMockPipe } from './browser-only-mock.pipe';
|
||||
NgComponentOutletDirectiveStub,
|
||||
BrowserOnlyMockPipe,
|
||||
],
|
||||
exports: [
|
||||
QueryParamsDirectiveStub
|
||||
],
|
||||
exports: [
|
||||
QueryParamsDirectiveStub,
|
||||
RouterLinkDirectiveStub
|
||||
],
|
||||
schemas: [
|
||||
CUSTOM_ELEMENTS_SCHEMA
|
||||
]
|
||||
|
@@ -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 {
|
||||
}
|
45
src/app/subscriptions-page/subscriptions-page.component.html
Normal file
45
src/app/subscriptions-page/subscriptions-page.component.html
Normal file
@@ -0,0 +1,45 @@
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-12 m-40">
|
||||
<h2>{{'subscriptions.title' | translate}}</h2>
|
||||
<div class="row">
|
||||
<div class="col-md-12 m-40">
|
||||
<ds-loading *ngIf="loading$ | async"></ds-loading>
|
||||
<ng-container *ngVar="(subscriptions$ | async) as subscriptions">
|
||||
|
||||
<ds-pagination
|
||||
*ngIf="subscriptions?.pageInfo?.totalElements > 0 && !(loading$ | async)"
|
||||
[paginationOptions]="config"
|
||||
[pageInfoState]="subscriptions?.pageInfo"
|
||||
[collectionSize]="subscriptions?.pageInfo?.totalPages"
|
||||
[hideGear]="true"
|
||||
[hidePagerWhenSinglePage]="true">
|
||||
<div class="table-responsive">
|
||||
<table id="formats" class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">{{'subscriptions.table.dso' | translate}}</th>
|
||||
<th scope="col">{{'subscriptions.table.subscription_type' | translate}}</th>
|
||||
<th scope="col">{{'subscriptions.table.subscription_frequency' | translate}}</th>
|
||||
<th scope="col">{{'subscriptions.table.action' | translate}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ds-subscription-view *ngFor="let subscription of subscriptions?.page" [eperson]="subscription?._embedded?.ePerson.id" [dso]="subscription?._embedded?.dSpaceObject" [subscription]="subscription" (reload)="refresh()">
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</ds-pagination>
|
||||
|
||||
<div *ngIf="subscriptions?.pageInfo?.totalElements == 0 && !(loading$ | async)">
|
||||
{{ 'subscriptions.table.empty.message' | translate }}
|
||||
</div>
|
||||
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
127
src/app/subscriptions-page/subscriptions-page.component.spec.ts
Normal file
127
src/app/subscriptions-page/subscriptions-page.component.spec.ts
Normal file
@@ -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<SubscriptionsPageComponent>;
|
||||
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();
|
||||
});
|
||||
|
||||
|
||||
});
|
117
src/app/subscriptions-page/subscriptions-page.component.ts
Normal file
117
src/app/subscriptions-page/subscriptions-page.component.ts
Normal file
@@ -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<PaginatedList<Subscription>> = new BehaviorSubject(buildPaginatedList<Subscription>(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<boolean> = new BehaviorSubject<boolean>(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();
|
||||
}
|
||||
|
||||
}
|
15
src/app/subscriptions-page/subscriptions-page.module.ts
Normal file
15
src/app/subscriptions-page/subscriptions-page.module.ts
Normal file
@@ -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 { }
|
@@ -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:",
|
||||
|
Reference in New Issue
Block a user