Skip to content

Commit

Permalink
Merge pull request #1139 from ORCID/sendNotificationsDialog
Browse files Browse the repository at this point in the history
send notifications dialog
  • Loading branch information
auumgn authored Mar 20, 2024
2 parents e097794 + 23f4867 commit 96e31cc
Show file tree
Hide file tree
Showing 17 changed files with 299 additions and 9 deletions.
2 changes: 1 addition & 1 deletion ui/src/app/affiliation/affiliation-delete.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ActivatedRoute, Router } from '@angular/router'

import { NgbActiveModal, NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'

import { AffiliationService } from './service/affiliations.service'
import { AffiliationService } from './service/affiliation.service'
import { EventService } from 'src/app/shared/service/event.service'
import { AlertService } from 'src/app/shared/service/alert.service'
import { IAffiliation } from './model/affiliation.model'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'

import { AffiliationImportDialogComponent } from './affiliation-import-dialog.component'
import { AffiliationService } from './service/affiliations.service'
import { AffiliationService } from './service/affiliation.service'
import { EventService } from '../shared/service/event.service'
import { FileUploadService } from '../shared/service/file-upload.service'
import { HttpClientTestingModule } from '@angular/common/http/testing'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Component, OnDestroy, OnInit } from '@angular/core'
import { IAffiliation } from './model/affiliation.model'
import { AffiliationService } from './service/affiliations.service'
import { AffiliationService } from './service/affiliation.service'
import { NgbActiveModal, NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'
import { EventService } from '../shared/service/event.service'
import { FileUploadService } from '../shared/service/file-upload.service'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { HttpClientModule } from '@angular/common/http'
import { RouterTestingModule } from '@angular/router/testing'
import { NO_ERRORS_SCHEMA } from '@angular/core'
import { LocalizePipe } from '../shared/pipe/localize'
import { AffiliationService } from './service/affiliations.service'
import { AffiliationService } from './service/affiliation.service'
import { ReactiveFormsModule } from '@angular/forms'
import { of } from 'rxjs'
import { DateUtilService } from '../shared/service/date-util.service'
Expand Down
2 changes: 1 addition & 1 deletion ui/src/app/affiliation/affiliation-update.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { AbstractControl, FormBuilder, FormGroup, ValidatorFn, Validators } from
import { ActivatedRoute } from '@angular/router'
import * as moment from 'moment'
import { IAffiliation, Affiliation } from './model/affiliation.model'
import { AffiliationService } from './service/affiliations.service'
import { AffiliationService } from './service/affiliation.service'
import { DateUtilService } from '../shared/service/date-util.service'

import {
Expand Down
4 changes: 3 additions & 1 deletion ui/src/app/affiliation/affiliation.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ import {
AffiliationImportDialogComponent,
AffiliationImportPopupComponent,
} from './affiliation-import-dialog.component'
import { AffiliationDeleteDialogComponent, AffiliationDeletePopupComponent } from './affiliation-delete.component'
import { AffiliationUpdateComponent } from './affiliation-update.component'
import { SendNotificationsDialogComponent } from './send-notifications-dialog.component'
import { AffiliationDeleteDialogComponent, AffiliationDeletePopupComponent } from './affiliation-delete.component'

@NgModule({
imports: [
Expand All @@ -29,6 +30,7 @@ import { AffiliationUpdateComponent } from './affiliation-update.component'
AffiliationImportDialogComponent,
AffiliationImportPopupComponent,
AffiliationUpdateComponent,
SendNotificationsDialogComponent,
AffiliationDeleteDialogComponent,
AffiliationDeletePopupComponent,
],
Expand Down
13 changes: 12 additions & 1 deletion ui/src/app/affiliation/affiliation.route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ import { ActivatedRouteSnapshot, ResolveFn, RouterStateSnapshot, Routes } from '
import { AuthGuard } from '../account/auth.guard'
import { Observable, filter, of, take } from 'rxjs'
import { inject } from '@angular/core'
import { AffiliationService } from './service/affiliations.service'
import { AffiliationService } from './service/affiliation.service'
import { Affiliation } from './model/affiliation.model'
import { AffiliationsComponent } from './affiliations.component'
import { AffiliationDetailComponent } from './affiliation-detail.component'
import { AffiliationImportPopupComponent } from './affiliation-import-dialog.component'
import { AffiliationUpdateComponent } from './affiliation-update.component'
import { SendNotificationsPopupComponent } from './send-notifications-dialog.component'
import { AffiliationDeletePopupComponent } from './affiliation-delete.component'

export const AffiliationResolver: ResolveFn<Affiliation | null> = (
Expand Down Expand Up @@ -62,6 +63,16 @@ export const affiliationRoutes: Routes = [
canActivate: [AuthGuard],
outlet: 'popup',
},
{
path: 'notifications',
component: SendNotificationsPopupComponent,
data: {
authorities: ['ASSERTION_SERVICE_ENABLED'],
pageTitle: 'gatewayApp.assertionServiceAssertion.home.title.string',
},
canActivate: [AuthGuard],
outlet: 'popup',
},
],
},
{
Expand Down
2 changes: 1 addition & 1 deletion ui/src/app/affiliation/affiliations.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { Event } from '../shared/model/event.model'
import { RouterModule } from '@angular/router'
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'
import { AffiliationsComponent } from './affiliations.component'
import { AffiliationService } from './service/affiliations.service'
import { AffiliationService } from './service/affiliation.service'
import { Affiliation, AffiliationPage } from './model/affiliation.model'
describe('AffiliationsComponent', () => {
let component: AffiliationsComponent
Expand Down
2 changes: 1 addition & 1 deletion ui/src/app/affiliation/affiliations.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
faSortUp,
faTimes,
} from '@fortawesome/free-solid-svg-icons'
import { AffiliationService } from './service/affiliations.service'
import { AffiliationService } from './service/affiliation.service'
import { LanguageService } from '../shared/service/language.service'
import { AccountService } from '../account'
import { AlertService } from '../shared/service/alert.service'
Expand Down
26 changes: 26 additions & 0 deletions ui/src/app/affiliation/send-notifications-dialog.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<form name="notificationForm" (ngSubmit)="send()">
<div class="modal-header">
<h4 class="modal-title" i18n="@@gatewayApp.assertionServiceAssertion.notifications.title.string">Send permission notifications</h4>
<button type="button" class="close" (click)="close()" aria-hidden="true">&times;</button>
</div>
<div class="modal-body">
<div class="alert alert-danger" *ngIf="requestAlreadyInProgress" i18n="@@gatewayApp.assertionServiceAssertion.notifications.alreadyInProgress.string">
A request to send notifications to your users is already in progress. Please try again later.
</div>
<p i18n="@@gatewayApp.assertionServiceAssertion.notifications.description.string">Are you sure that you would like ORCID to send permission links to your researchers for the affiliations that are pending?</p>
<h6 class="mb-1 font-weight-bold" i18n="@@gatewayApp.assertionServiceAssertion.notifications.languageHeader.string">Notification language</h6>
<p i18n="@@gatewayApp.assertionServiceAssertion.notifications.languageDescription.string">What language should the notification be sent in?</p>
<select class="form-control w-66" id="langKey" name="langKey" [(ngModel)]="language">
<option *ngFor="let lang of languages | keyvalue" [ngValue]="lang.key">{{lang.value.name}}</option>
</select>
</div>

<div class="modal-footer">
<button type="button" class="btn btn-outline-primary" (click)="close()">
<fa-icon [icon]="'ban'"></fa-icon>&nbsp;<span i18n="@@entity.action.cancel.string">Cancel</span>
</button>
<button id="jhi-confirm-csv-upload" type="submit" class="btn btn-primary">
<fa-icon [icon]="faPaperPlane"></fa-icon>&nbsp;<span i18n="@@gatewayApp.assertionServiceAssertion.notifications.sendNotifications.string">Send notifications</span>
</button>
</div>
</form>
Empty file.
101 changes: 101 additions & 0 deletions ui/src/app/affiliation/send-notifications-dialog.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'

import { SendNotificationsDialogComponent } from './send-notifications-dialog.component'
import { NotificationService } from './service/notification.service'
import { EventService } from '../shared/service/event.service'
import { AccountService } from '../account'
import { MemberService } from '../member/service/member.service'
import { AlertService } from '../shared/service/alert.service'
import { LanguageService } from '../shared/service/language.service'
import { HttpClientTestingModule } from '@angular/common/http/testing'
import { FormBuilder } from '@angular/forms'
import { NgbModal, NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { ErrorService } from '../error/service/error.service'
import { FileUploadService } from '../shared/service/file-upload.service'
import { UserService } from '../user/service/user.service'
import { of } from 'rxjs'

describe('SendNotificationsDialogComponent', () => {
let component: SendNotificationsDialogComponent
let fixture: ComponentFixture<SendNotificationsDialogComponent>

let notificationServiceSpy: jasmine.SpyObj<NotificationService>
let eventServiceSpy: jasmine.SpyObj<EventService>
let alertServiceSpy: jasmine.SpyObj<AlertService>
let languageServiceSpy: jasmine.SpyObj<LanguageService>
let memberServiceSpy: jasmine.SpyObj<MemberService>
let accountServiceSpy: jasmine.SpyObj<AccountService>

beforeEach(() => {
eventServiceSpy = jasmine.createSpyObj('EventService', ['broadcast', 'on'])
alertServiceSpy = jasmine.createSpyObj('AlertService', ['broadcast', 'on'])
memberServiceSpy = jasmine.createSpyObj('MemberService', ['find'])
accountServiceSpy = jasmine.createSpyObj('AccountService', ['getAccountData'])
notificationServiceSpy = jasmine.createSpyObj('NotificationService', ['updateStatuses', 'requestInProgress'])
languageServiceSpy = jasmine.createSpyObj('LanguageService', [
'getAllLanguages',
'getCurrentLanguage',
'changeLanguage',
])

TestBed.configureTestingModule({
declarations: [SendNotificationsDialogComponent],
imports: [HttpClientTestingModule],
providers: [
FormBuilder,
NgbModal,
NgbActiveModal,
{ provide: AccountService, useValue: accountServiceSpy },
{ provide: EventService, useValue: eventServiceSpy },
{ provide: AlertService, useValue: alertServiceSpy },
{ provide: MemberService, useValue: memberServiceSpy },
{ provide: LanguageService, useValue: languageServiceSpy },
{ provide: NotificationService, useValue: notificationServiceSpy },
{ provide: ErrorService, useValue: {} },
],
}).compileComponents()

fixture = TestBed.createComponent(SendNotificationsDialogComponent)
component = fixture.componentInstance

eventServiceSpy = TestBed.inject(EventService) as jasmine.SpyObj<EventService>
accountServiceSpy = TestBed.inject(AccountService) as jasmine.SpyObj<AccountService>
alertServiceSpy = TestBed.inject(AlertService) as jasmine.SpyObj<AlertService>
languageServiceSpy = TestBed.inject(LanguageService) as jasmine.SpyObj<LanguageService>
memberServiceSpy = TestBed.inject(MemberService) as jasmine.SpyObj<MemberService>
})

it('should create', () => {
accountServiceSpy.getAccountData.and.returnValue(
of({
activated: true,
authorities: ['test', 'test'],
email: '[email protected]',
firstName: 'name',
langKey: 'en',
lastName: 'surname',
imageUrl: 'url',
salesforceId: 'sfid',
loggedAs: false,
loginAs: 'sfid',
mainContact: false,
mfaEnabled: true,
})
)

expect(component).toBeTruthy()
})

it('send should call notification service', () => {
notificationServiceSpy.requestInProgress.and.returnValue(of({ inProgress: false }))
notificationServiceSpy.updateStatuses.and.returnValue(of({}))
component.send()
expect(notificationServiceSpy.updateStatuses).toHaveBeenCalled()
})

it('send should not call notification service when request is already in progress', () => {
notificationServiceSpy.requestInProgress.and.returnValue(of({ inProgress: true }))
component.send()
expect(notificationServiceSpy.updateStatuses).toHaveBeenCalledTimes(0)
})
})
119 changes: 119 additions & 0 deletions ui/src/app/affiliation/send-notifications-dialog.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { Component, OnDestroy, OnInit } from '@angular/core'
import { NotificationService } from './service/notification.service'
import { NgbActiveModal, NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'
import { EventService } from '../shared/service/event.service'
import { AlertService } from '../shared/service/alert.service'
import { IUser } from '../user/model/user.model'
import { faPaperPlane } from '@fortawesome/free-solid-svg-icons'
import { IMember } from '../member/model/member.model'
import { MemberService } from '../member/service/member.service'
import { LanguageService } from '../shared/service/language.service'
import { AccountService } from '../account'
import { IAccount } from '../account/model/account.model'
import { AlertType, EventType } from '../app.constants'
import { ActivatedRoute, Router } from '@angular/router'

@Component({
selector: 'app-send-notifications-dialog',
templateUrl: './send-notifications-dialog.component.html',
styleUrls: ['./send-notifications-dialog.component.scss'],
})
export class SendNotificationsDialogComponent implements OnInit {
faPaperPlane = faPaperPlane
requestAlreadyInProgress = false
languages: { [langCode: string]: { name: string } } | undefined
language = ''
account: IUser | undefined

constructor(
protected notificationService: NotificationService,
public activeModal: NgbActiveModal,
protected eventService: EventService,
protected alertService: AlertService,
private languageService: LanguageService,
private memberService: MemberService,
private accountService: AccountService
) {}

ngOnInit() {
this.languages = this.languageService.getAllLanguages()

this.accountService.getAccountData().subscribe((account) => {
this.memberService.find(account!.salesforceId).subscribe((member) => {
if (member) {
this.language = member.defaultLanguage || 'en'
} else {
this.language = 'en'
}
})
})
}

clear() {
this.activeModal.dismiss(true)
window.history.back()
}

send() {
console.log('this.language is ', this.language)

this.notificationService.requestInProgress().subscribe((res: any) => {
if (res.inProgress) {
this.requestAlreadyInProgress = true
} else {
this.notificationService.updateStatuses(this.language).subscribe(() => {
this.alertService.broadcast(AlertType.NOTIFICATION_IN_PROGRESS)
this.close()
})
}
})
}

close() {
this.eventService.broadcast({
type: EventType.SEND_NOTIFICATIONS,
payload: 'Send notifications',
})
this.activeModal.dismiss(true)
}
}

@Component({
selector: 'app-send-notifications-popup',
template: '',
})
export class SendNotificationsPopupComponent implements OnInit, OnDestroy {
protected ngbModalRef: NgbModalRef | undefined | null

constructor(
protected activatedRoute: ActivatedRoute,
protected router: Router,
protected modalService: NgbModal
) {}

ngOnInit() {
this.activatedRoute.data.subscribe(({ assertion }) => {
setTimeout(() => {
this.ngbModalRef = this.modalService.open(SendNotificationsDialogComponent as Component, {
size: 'lg',
backdrop: 'static',
})
this.ngbModalRef.componentInstance.assertion = assertion
this.ngbModalRef.result.then(
(result) => {
this.router.navigate(['/affiliations', { outlets: { popup: null } }])
this.ngbModalRef = null
},
(reason) => {
this.router.navigate(['/affiliations', { outlets: { popup: null } }])
this.ngbModalRef = null
}
)
}, 0)
})
}

ngOnDestroy() {
this.ngbModalRef = null
}
}
28 changes: 28 additions & 0 deletions ui/src/app/affiliation/service/notification.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Injectable } from '@angular/core'
import { HttpClient, HttpResponse } from '@angular/common/http'
import { Observable } from 'rxjs'
import * as moment from 'moment'
import { map } from 'rxjs/operators'
import { AffiliationPage, IAffiliation, IAffiliationPage } from '../model/affiliation.model'
import { createRequestOption } from 'src/app/shared/request-util'
import { AffiliationService } from './affiliation.service'

@Injectable({ providedIn: 'root' })
export class NotificationService {
resourceUrl: string

constructor(
private http: HttpClient,
private affiliationService: AffiliationService
) {
this.resourceUrl = this.affiliationService.resourceUrl + '/notification-request'
}

updateStatuses(language: string): Observable<any> {
return this.http.post<any>(this.resourceUrl, { language })
}

requestInProgress(): Observable<any> {
return this.http.get<any>(this.resourceUrl)
}
}
1 change: 1 addition & 0 deletions ui/src/app/app.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export enum AlertType {
USER_CREATED = 'User created. Invite sent.',
USER_UPDATED = 'User updated successfully',
USER_DELETED = 'User deleted successfully',
NOTIFICATION_IN_PROGRESS = 'Notification in progress',
AFFILIATION_CREATED = 'Affiliation created',
AFFILIATION_UPDATED = 'Affiliation updated',
AFFILIATION_DELETED = 'Affiliation deleted',
Expand Down
Loading

0 comments on commit 96e31cc

Please sign in to comment.