Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
b775cd5
[CST-7757] Subscriptions porting (wip)
rezartatis Aug 16, 2021
f15ca85
[CST-7757] Subscriptions porting (wip)
Dec 23, 2022
3ab552e
[CST-7757] Subscriptions porting (wip)
Dec 23, 2022
a6cd39c
[CST-7757] Subscriptions porting (wip)
Dec 23, 2022
af0b7e7
Merge branch 'main' into CST-7757
atarix83 Dec 27, 2022
72a42b0
[CST-7757] Refactoring code
atarix83 Dec 27, 2022
88e191e
[CST-7757] Fix lint errors
atarix83 Dec 28, 2022
88eefc6
[CST-7757] fix tests
atarix83 Dec 28, 2022
27d5097
[CST-7757] Add subscribe button to all dso pages
atarix83 Dec 28, 2022
90a9238
[CST-7757] fix pagination
atarix83 Dec 28, 2022
710b116
[CST-7757] Rename type property to subscriptionType
atarix83 Dec 29, 2022
1cc03f4
Merge branch 'main' into CST-7757
atarix83 Jan 16, 2023
28351f2
[CST-7757] Fix merge with main
atarix83 Jan 16, 2023
b09de30
[CST-7757] renamed param
Micheleboychuk Jan 31, 2023
b1011ed
[CST-7757] Remove subscription button from item pages
Feb 3, 2023
a4d94b0
[CST-7757] Response parameters fixed; submit button disabled if frequ…
Feb 3, 2023
0f8d2d7
[CST-7757] D-W-M fixed
Feb 3, 2023
0dfb37d
[CST-7757] Show object name
Feb 6, 2023
cbfddc6
Merge branch 'main-gh4s' into CST-7757
Feb 6, 2023
eae7b0d
[CST-7757] Minor fixes
Feb 6, 2023
7a46e6d
[CST-7757] Labels renamed
Feb 6, 2023
6bfcc25
[CST-7757] subscriptions data service tests
Feb 8, 2023
c8e7ac3
[CST-7757] subscription modal tests
Feb 8, 2023
325d78d
[CST-7757] modal fixes
Feb 8, 2023
27f541e
[CST-7757] modal fixes
Feb 8, 2023
516b91f
[CST-7757] Hide delete info for new subscriptions and fix issue with …
Feb 9, 2023
d65c12a
Merge branch 'main-gh4s' into CST-7757
Feb 11, 2023
851fe78
[CST-7757] Missing label
Feb 11, 2023
078bdd2
[CST-7757] Fix delete message
Feb 13, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/app/app-routing-paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`;
}
6 changes: 6 additions & 0 deletions src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
]
}
Expand Down
5 changes: 4 additions & 1 deletion src/app/collection-page/collection-page.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@
[title]="'collection.page.news'">
</ds-comcol-page-content>
</header>
<ds-dso-edit-menu></ds-dso-edit-menu>
<ds-dso-edit-menu></ds-dso-edit-menu>
<div class="pl-2 space-children-mr">
<ds-dso-page-subscription-button [dso]="collection"></ds-dso-page-subscription-button>
</div>
</div>
<section class="comcol-page-browse-section">
<!-- Browse-By Links -->
Expand Down
3 changes: 3 additions & 0 deletions src/app/community-page/community-page.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
</ds-comcol-page-content>
</header>
<ds-dso-edit-menu></ds-dso-edit-menu>
<div class="pl-2 space-children-mr">
<ds-dso-page-subscription-button [dso]="communityPayload"></ds-dso-page-subscription-button>
</div>
</div>
<section class="comcol-page-browse-section">

Expand Down
2 changes: 2 additions & 0 deletions src/app/core/core.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ 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 { IdentifierData } from '../shared/object-list/identifier-data/identifier-data.model';
import { Subscription } from '../shared/subscriptions/models/subscription.model';

/**
* When not in production, endpoint responses can be mocked for testing purposes
Expand Down Expand Up @@ -365,6 +366,7 @@ export const models =
OrcidHistory,
AccessStatusObject,
IdentifierData,
Subscription,
];

@NgModule({
Expand Down
1 change: 1 addition & 0 deletions src/app/core/data/feature-authorization/feature-id.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,5 @@ export enum FeatureID {
CanSubmit = 'canSubmit',
CanEditItem = 'canEditItem',
CanRegisterDOI = 'canRegisterDOI',
CanSubscribe = 'canSubscribeDso',
}
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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) {
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<button *ngIf="isAuthorized$ | async" data-test="subscription-button"
(click)="openSubscriptionModal()"
[ngbTooltip]="'subscriptions.tooltip' | translate"
[title]="'subscriptions.tooltip' | translate"
[attr.aria-label]="'subscriptions.tooltip' | translate"
class="subscription-button btn btn-dark btn-sm">
<i class="fas fa-bell fa-fw"></i>
</button>
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { DsoPageSubscriptionButtonComponent } from './dso-page-subscription-button.component';
import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
import { of as observableOf } from 'rxjs';
import { NgbModalModule } from '@ng-bootstrap/ng-bootstrap';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { Item } from '../../../core/shared/item.model';
import { ITEM } from '../../../core/shared/item.resource-type';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { TranslateLoaderMock } from '../../mocks/translate-loader.mock';

describe('DsoPageSubscriptionButtonComponent', () => {
let component: DsoPageSubscriptionButtonComponent;
let fixture: ComponentFixture<DsoPageSubscriptionButtonComponent>;
let de: DebugElement;

const authorizationService = jasmine.createSpyObj('authorizationService', {
isAuthorized: jasmine.createSpy('isAuthorized') // observableOf(true)
});

const mockItem = Object.assign(new Item(), {
id: 'fake-id',
uuid: 'fake-id',
handle: 'fake/handle',
lastModified: '2018',
type: ITEM,
_links: {
self: {
href: 'https://localhost:8000/items/fake-id'
}
}
});

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [
NgbModalModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useClass: TranslateLoaderMock
}
})
],
declarations: [ DsoPageSubscriptionButtonComponent ],
providers: [
{ provide: AuthorizationDataService, useValue: authorizationService },
]
})
.compileComponents();
});

beforeEach(() => {
fixture = TestBed.createComponent(DsoPageSubscriptionButtonComponent);
component = fixture.componentInstance;
de = fixture.debugElement;
component.dso = mockItem;
});

describe('when is authorized', () => {
beforeEach(() => {
authorizationService.isAuthorized.and.returnValue(observableOf(true));
fixture.detectChanges();
});

it('should display subscription button', () => {
expect(de.query(By.css(' [data-test="subscription-button"]'))).toBeTruthy();
});
});

describe('when is not authorized', () => {
beforeEach(() => {
authorizationService.isAuthorized.and.returnValue(observableOf(false));
fixture.detectChanges();
});

it('should not display subscription button', () => {
expect(de.query(By.css(' [data-test="subscription-button"]'))).toBeNull();
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Component, Input, OnInit } from '@angular/core';

import { Observable, of } from 'rxjs';
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';

import { DSpaceObject } from '../../../core/shared/dspace-object.model';
import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
import { SubscriptionModalComponent } from '../../subscriptions/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']
})
/**
* Display a button that opens the modal to manage subscriptions
*/
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;

/**
* DSpaceObject that is being viewed
*/
@Input() dso: DSpaceObject;

constructor(
protected authorizationService: AuthorizationDataService,
private modalService: NgbModal,
) {
}

/**
* check if the current DSpaceObject can be subscribed by the user
*/
ngOnInit(): void {
this.isAuthorized$ = this.authorizationService.isAuthorized(FeatureID.CanSubscribe, this.dso.self);
}

/**
* Open the modal to subscribe to the related DSpaceObject
*/
public openSubscriptionModal() {
this.modalRef = this.modalService.open(SubscriptionModalComponent);
this.modalRef.componentInstance.dso = this.dso;
}

}
5 changes: 5 additions & 0 deletions src/app/shared/shared.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,10 @@ import {
AdvancedClaimedTaskActionRatingComponent
} from './mydspace-actions/claimed-task/rating/advanced-claimed-task-action-rating.component';
import { ClaimedTaskActionsDeclineTaskComponent } from './mydspace-actions/claimed-task/decline-task/claimed-task-actions-decline-task.component';
import {
DsoPageSubscriptionButtonComponent
} from './dso-page/dso-page-subscription-button/dso-page-subscription-button.component';


const MODULES = [
CommonModule,
Expand Down Expand Up @@ -351,6 +355,7 @@ const COMPONENTS = [
ItemPageTitleFieldComponent,
ThemedSearchNavbarComponent,
ListableNotificationObjectComponent,
DsoPageSubscriptionButtonComponent,
MetadataFieldWrapperComponent,
ContextHelpWrapperComponent,
];
Expand Down
73 changes: 73 additions & 0 deletions src/app/shared/subscriptions/models/subscription.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { Observable } from 'rxjs';
import { autoserialize, deserialize, inheritSerialization } from 'cerialize';

import { link, 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';
import { RemoteData } from '../../../core/data/remote-data';
import { EPERSON } from '../../../core/eperson/models/eperson.resource-type';
import { DSPACE_OBJECT } from '../../../core/shared/dspace-object.resource-type';

@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;
resource: HALLink;
};

/**
* The logo for this Community
* Will be undefined unless the logo {@link HALLink} has been resolved.
*/
@link(EPERSON)
eperson?: Observable<RemoteData<EPerson>>;

/**
* The logo for this Community
* Will be undefined unless the logo {@link HALLink} has been resolved.
*/
@link(DSPACE_OBJECT)
resource?: Observable<RemoteData<DSpaceObject>>;
/**
* The embedded ePerson & dSpaceObject for this Subscription
*/
/* @deserialize
_embedded: {
ePerson: EPerson;
dSpaceObject: DSpaceObject;
};*/
}

export interface SubscriptionParameterList {
id: string;
name: string;
value: string;
}
10 changes: 10 additions & 0 deletions src/app/shared/subscriptions/models/subscription.resource-type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { ResourceType } from '../../../core/shared/resource-type';

/**
* The resource type for Subscription
*
* Needs to be in a separate file to prevent circular
* dependencies in webpack.
*/

export const SUBSCRIPTION = new ResourceType('subscription');
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<form *ngIf="subscriptionForm" [formGroup]="subscriptionForm" (ngSubmit)="submit()" data-test="subscription-form">
<div class="modal-header">
<h4 class="modal-title">{{'subscriptions.modal.title' | translate}}</h4>
<button type="button" class="close" aria-label="Close" (click)="activeModal.close()">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p class="mb-3"><strong>{{dso.name}}</strong>
<span *ngIf="!!dso" class="float-right"><ds-type-badge *ngIf="!!dso" [object]="dso"></ds-type-badge></span>
</p>
<div>
<fieldset *ngFor="let subscriptionType of subscriptionForm?.controls | keyvalue" formGroupName="{{subscriptionType.key}}" class="form-group form-row">
<legend class="col-md-4 col-form-label float-md-left pt-0">
{{ 'subscriptions.modal.new-subscription-form.type.' + subscriptionType.key | translate }}:
</legend>
<div class="col-md-8">
<input type="hidden" formControlName="subscriptionId" [value]="subscriptionType?.value?.controls['subscriptionId'].value" >
<div class="form-check" formGroupName="frequencies" *ngFor="let frequency of frequencyDefaultValues">
<input type="checkbox" [id]="'checkbox-' + frequency" class="form-check-input" [formControlName]="frequency"/>
<label class="form-check-label"
[for]="'checkbox-' + frequency">{{ 'subscriptions.modal.new-subscription-form.frequency.' + frequency | translate }}</label>
</div>
</div>
<ds-alert *ngIf="!!submitted && subscriptionType?.value?.controls['frequencies'].errors?.required" [type]="'alert-danger'">
{{ 'context-menu.actions.subscription.frequency.required' | translate }}
</ds-alert>
</fieldset>
</div>
<p class="text-muted" *ngIf="(showDeleteInfo$ | async)">{{'subscriptions.modal.delete-info' | translate}}</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary"
(click)="activeModal.close()">
{{'subscriptions.modal.close' | translate}}
</button>
<button type="submit" class="btn btn-success" [disabled]="(processing$ | async) || !isValid">
<span *ngIf="(processing$ | async)">
<i class='fas fa-circle-notch fa-spin'></i> {{'subscriptions.modal.new-subscription-form.processing' | translate}}
</span>
<span *ngIf="!(processing$ | async)">
{{'subscriptions.modal.new-subscription-form.submit' | translate}}
</span>
</button>
</div>
</form>
Loading