diff --git a/src/app/core/app.module.ts b/src/app/core/app.module.ts index 87cc2591c..6aea37b90 100644 --- a/src/app/core/app.module.ts +++ b/src/app/core/app.module.ts @@ -36,6 +36,7 @@ import { MatBottomSheetModule } from '@angular/material/bottom-sheet'; import { MatProgressBarModule } from '@angular/material/progress-bar'; import { PageNotFoundComponent } from './page-not-found/page-not-found.component'; import { BaseComponent } from './base/base.component'; +import { PfdaToolbarComponent } from './base/pfda-toolbar/pfda-toolbar.component'; import { HomeComponent } from './home/home.component'; import { RegistrarsComponent } from './registrars/registrars.component'; import { SubstancesBrowseComponent } from './substances-browse/substances-browse.component'; @@ -124,6 +125,7 @@ import { PrivacyStatementModule } from './privacy-statement/privacy-statement.mo AppComponent, PageNotFoundComponent, BaseComponent, + PfdaToolbarComponent, HomeComponent, UnauthorizedComponent, SubstancesBrowseComponent, diff --git a/src/app/core/assets/icons/pfda/gsrs-logo-round-bw.svg b/src/app/core/assets/icons/pfda/gsrs-logo-round-bw.svg new file mode 100644 index 000000000..d33ecd415 --- /dev/null +++ b/src/app/core/assets/icons/pfda/gsrs-logo-round-bw.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/app/core/assets/icons/pfda/home.svg b/src/app/core/assets/icons/pfda/home.svg new file mode 100644 index 000000000..991b35a92 --- /dev/null +++ b/src/app/core/assets/icons/pfda/home.svg @@ -0,0 +1,13 @@ + diff --git a/src/app/core/assets/icons/pfda/profile.svg b/src/app/core/assets/icons/pfda/profile.svg new file mode 100644 index 000000000..3b6727689 --- /dev/null +++ b/src/app/core/assets/icons/pfda/profile.svg @@ -0,0 +1,10 @@ + + + diff --git a/src/app/core/assets/icons/pfda/questionmark.svg b/src/app/core/assets/icons/pfda/questionmark.svg new file mode 100644 index 000000000..52dafbde8 --- /dev/null +++ b/src/app/core/assets/icons/pfda/questionmark.svg @@ -0,0 +1,11 @@ + diff --git a/src/app/core/assets/icons/pfda/support.svg b/src/app/core/assets/icons/pfda/support.svg new file mode 100644 index 000000000..05368ee92 --- /dev/null +++ b/src/app/core/assets/icons/pfda/support.svg @@ -0,0 +1,14 @@ + \ No newline at end of file diff --git a/src/app/core/assets/images/pfda/pfda-logo.png b/src/app/core/assets/images/pfda/pfda-logo.png new file mode 100644 index 000000000..980274504 Binary files /dev/null and b/src/app/core/assets/images/pfda/pfda-logo.png differ diff --git a/src/app/core/auth/auth.module.ts b/src/app/core/auth/auth.module.ts index 5d458787c..109295219 100644 --- a/src/app/core/auth/auth.module.ts +++ b/src/app/core/auth/auth.module.ts @@ -17,6 +17,8 @@ import { RouterModule } from '@angular/router'; import { DecodeUriPipe } from '@gsrs-core/auth/user-downloads/download-monitor/decodeURI.pipe'; import { FileSizePipe } from '@gsrs-core/auth/user-downloads/download-monitor/fileSize.pipe'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { SessionExpirationComponent } from './session-expiration/session-expiration.component'; +import { SessionExpirationDialogComponent } from './session-expiration/session-expiration-dialog/session-expiration-dialog.component'; @NgModule({ imports: [ @@ -39,13 +41,19 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; UserProfileComponent, UserDownloadsComponent, DownloadMonitorComponent, + SessionExpirationComponent, + SessionExpirationDialogComponent, DecodeUriPipe, FileSizePipe ], + entryComponents: [ + SessionExpirationDialogComponent + ], exports: [ LoginComponent, UserProfileComponent, DownloadMonitorComponent, + SessionExpirationComponent, UserDownloadsComponent ] }) diff --git a/src/app/core/auth/session-expiration/session-expiration-dialog/session-expiration-dialog.component.html b/src/app/core/auth/session-expiration/session-expiration-dialog/session-expiration-dialog.component.html new file mode 100644 index 000000000..c35a6869c --- /dev/null +++ b/src/app/core/auth/session-expiration/session-expiration-dialog/session-expiration-dialog.component.html @@ -0,0 +1,11 @@ +

+ {{dialogTitle}} +

+
+ {{dialogMessage}} +
+
+ + + +
diff --git a/src/app/core/auth/session-expiration/session-expiration-dialog/session-expiration-dialog.component.scss b/src/app/core/auth/session-expiration/session-expiration-dialog/session-expiration-dialog.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/core/auth/session-expiration/session-expiration-dialog/session-expiration-dialog.component.spec.ts b/src/app/core/auth/session-expiration/session-expiration-dialog/session-expiration-dialog.component.spec.ts new file mode 100644 index 000000000..b3156517c --- /dev/null +++ b/src/app/core/auth/session-expiration/session-expiration-dialog/session-expiration-dialog.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SessionExpirationDialogComponent } from './session-expiration-dialog.component'; + +describe('SessionExpirationDialogComponent', () => { + let component: SessionExpirationDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ SessionExpirationDialogComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SessionExpirationDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/core/auth/session-expiration/session-expiration-dialog/session-expiration-dialog.component.ts b/src/app/core/auth/session-expiration/session-expiration-dialog/session-expiration-dialog.component.ts new file mode 100644 index 000000000..434d87a4f --- /dev/null +++ b/src/app/core/auth/session-expiration/session-expiration-dialog/session-expiration-dialog.component.ts @@ -0,0 +1,79 @@ +import { Component, OnInit, Inject } from '@angular/core'; +import { Router } from '@angular/router'; +import { HttpClient } from '@angular/common/http'; +import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { ConfigService, SessionExpirationWarning } from '@gsrs-core/config'; + +@Component({ + selector: 'app-session-expiration-dialog', + templateUrl: './session-expiration-dialog.component.html', + styleUrls: ['./session-expiration-dialog.component.scss'] +}) +export class SessionExpirationDialogComponent implements OnInit { + sessionExpirationWarning: SessionExpirationWarning = null; + sessionExpiringAt: number; + timeRemainingSeconds: number; + dialogTitle: string; + dialogMessage: string; + private updateDialogInterval: any; + + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: any, + // N.B. injected services has to come after data + private router: Router, + private http: HttpClient + ) { + this.sessionExpirationWarning = data.sessionExpirationWarning; + this.sessionExpiringAt = data.sessionExpiringAt; + } + + ngOnInit() { + // If SessionExpirationWarning is not found in configData, the intervals are never set + // and this component is inert + this.updateDialogInterval = setInterval(() => { this.updateDialog(); }); + } + + ngOnDestroy() { + clearInterval(this.updateDialogInterval); + } + + getCurrentTime() { + return Math.floor((new Date()).getTime() / 1000); + } + + updateDialog() { + const currentTime = this.getCurrentTime() + this.timeRemainingSeconds = this.sessionExpiringAt - currentTime; + + if (this.timeRemainingSeconds > 0) { + const remainingMinutes = Math.floor(this.timeRemainingSeconds / 60); + const reminaingSeconds = String(this.timeRemainingSeconds % 60).padStart(2, '0'); + this.dialogTitle = "Session Ending Soon" + this.dialogMessage = `You will be logged out in ${remainingMinutes}:${reminaingSeconds}` + } + else { + this.dialogTitle = "Session Ended" + this.dialogMessage = "Your session has expired, please login again." + } + } + + closeDialog() { + this.dialogRef.close(false); + } + + extendSession() { + const url = this.sessionExpirationWarning.extendSessionApiUrl; + this.http.get(url).subscribe( + data => { + this.dialogRef.close(true); + }, + err => { console.log("Error extending session: ", err) }, + () => { } + ); + } + + login() { + window.location.assign('/login'); + } +} diff --git a/src/app/core/auth/session-expiration/session-expiration.component.html b/src/app/core/auth/session-expiration/session-expiration.component.html new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/core/auth/session-expiration/session-expiration.component.spec.ts b/src/app/core/auth/session-expiration/session-expiration.component.spec.ts new file mode 100644 index 000000000..ae9bf0c23 --- /dev/null +++ b/src/app/core/auth/session-expiration/session-expiration.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SessionExpirationComponent } from './session-expiration.component'; + +describe('SessionExpirationComponent', () => { + let component: SessionExpirationComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ SessionExpirationComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SessionExpirationComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/core/auth/session-expiration/session-expiration.component.ts b/src/app/core/auth/session-expiration/session-expiration.component.ts new file mode 100644 index 000000000..75d5d929a --- /dev/null +++ b/src/app/core/auth/session-expiration/session-expiration.component.ts @@ -0,0 +1,131 @@ +import { Router, Event as NavigationEvent, NavigationStart } from '@angular/router'; +import { Component, OnInit } from '@angular/core'; +import { MatDialog} from '@angular/material/dialog'; + +import { OverlayContainer } from '@angular/cdk/overlay'; +import { HttpClient } from '@angular/common/http'; +import { ConfigService, SessionExpirationWarning } from '@gsrs-core/config'; +import { AuthService } from '../auth.service'; +import { SessionExpirationDialogComponent } from './session-expiration-dialog/session-expiration-dialog.component' +import { Subscription } from 'rxjs'; + +@Component({ + selector: 'app-session-expiration', + templateUrl: './session-expiration.component.html' +}) +export class SessionExpirationComponent implements OnInit { + sessionExpirationWarning: SessionExpirationWarning = null; + sessionExpiringAt: number; + private overlayContainer: HTMLElement; + private subscriptions: Array = []; + private expirationTimer: any; + + constructor( + private router: Router, + private configService: ConfigService, + private authService: AuthService, + private http: HttpClient, + private dialog: MatDialog, + private overlayContainerService: OverlayContainer + ) { + this.sessionExpirationWarning = configService.configData.sessionExpirationWarning; + this.overlayContainer = this.overlayContainerService.getContainerElement(); + } + + ngOnInit() { + // If SessionExpirationWarning is not found in configData, the intervals are never set + // and this component is inert + const authSubscription = this.authService.getAuth().subscribe(auth => { + if (this.sessionExpirationWarning) { + if (auth) { + this.resetExpirationTimer(); + } + else { + // User has logged out while timeout is active + this.clearExpirationTimer(); + } + } + }); + this.subscriptions.push(authSubscription); + + // This component seems to be destroyed and recreated on route change, so maybe + // the following isn't necessary: + // const routerSubscription = this.router.events.subscribe((event: NavigationEvent) => { + // if (event instanceof NavigationStart && this.expirationTimer) { + // this.extendSession(); + // } + // }); + // this.subscriptions.push(routerSubscription); + } + + ngOnDestroy() { + this.subscriptions.forEach(subscription => { + subscription.unsubscribe(); + }); + this.clearExpirationTimer(); + } + + getCurrentTime() { + return Math.floor((new Date()).getTime() / 1000); + } + + clearExpirationTimer() { + if (this.expirationTimer) { + clearTimeout(this.expirationTimer); + this.expirationTimer = null; + } + } + + resetExpirationTimer() { + this.clearExpirationTimer(); + + const currentTime = this.getCurrentTime() + this.sessionExpiringAt = currentTime + this.sessionExpirationWarning.maxSessionDurationMinutes * 60; + + const timeRemainingSeconds = this.sessionExpiringAt - currentTime; + const timeBeforeDisplayingDialogMs = (timeRemainingSeconds - 61) * 1000; + if (timeBeforeDisplayingDialogMs > 0) { + this.expirationTimer = setTimeout( () => { + this.openDialog(); + }, timeBeforeDisplayingDialogMs); + } + else { + this.login(); + } + } + + openDialog() { + const dialogRef = this.dialog.open(SessionExpirationDialogComponent, { + data: { + 'sessionExpirationWarning': this.sessionExpirationWarning, + 'sessionExpiringAt': this.sessionExpiringAt + }, + width: '650px', + autoFocus: false, + disableClose: true + }); + this.overlayContainer.style.zIndex = '1501'; + const dialogSubscription = dialogRef.afterClosed().subscribe(response => { + this.overlayContainer.style.zIndex = null; + if (response) { + // Session was extended + this.resetExpirationTimer(); + } + }); + } + + extendSession() { + const url = this.sessionExpirationWarning.extendSessionApiUrl; + this.http.get(url).subscribe( + data => { + this.resetExpirationTimer(); + }, + err => { console.log("Error extending session: ", err) }, + () => { } + ); + } + + login() { + window.location.assign('/login'); + } +} diff --git a/src/app/core/base/base.component.html b/src/app/core/base/base.component.html index ece895904..0a89e8a48 100644 --- a/src/app/core/base/base.component.html +++ b/src/app/core/base/base.component.html @@ -1,4 +1,4 @@ - + +
+ +
+ \ No newline at end of file diff --git a/src/app/core/base/base.component.ts b/src/app/core/base/base.component.ts index 623ca00bd..68e2ba44e 100644 --- a/src/app/core/base/base.component.ts +++ b/src/app/core/base/base.component.ts @@ -3,6 +3,7 @@ import { Router, RouterEvent, NavigationExtras, ActivatedRoute, NavigationStart, import { Environment } from '../../../environments/environment.model'; import { AuthService } from '../auth/auth.service'; import { Auth } from '../auth/auth.model'; +import { SessionExpirationComponent } from '../auth/session-expiration/session-expiration.component' import { ConfigService } from '../config/config.service'; import { OverlayContainer } from '@angular/cdk/overlay'; import { LoadingService } from '../loading/loading.service'; @@ -45,6 +46,7 @@ export class BaseComponent implements OnInit, OnDestroy { appId: string; clasicBaseHref: string; navItems: Array; + customToolbarComponent: string = ''; canRegister = false; registerNav: Array; searchNav: Array; @@ -73,6 +75,7 @@ export class BaseComponent implements OnInit, OnDestroy { private utilsService: UtilsService, private wildCardService: WildcardService ) { + this.wildCardService.wildCardObservable.subscribe((data) => { this.wildCardText = data; }); diff --git a/src/app/core/base/pfda-toolbar/pfda-toolbar.component.html b/src/app/core/base/pfda-toolbar/pfda-toolbar.component.html new file mode 100644 index 000000000..63df8dd5b --- /dev/null +++ b/src/app/core/base/pfda-toolbar/pfda-toolbar.component.html @@ -0,0 +1,91 @@ + +
+ +
+ + +
+ + +
Back Home
+
+
+ +
+ + +
+ +
GSRS
+
+
+ + + + + + + +
+ +
Support
+
+
+ + +
+ +
Getting Started
+
+
+ + +
diff --git a/src/app/core/base/pfda-toolbar/pfda-toolbar.component.scss b/src/app/core/base/pfda-toolbar/pfda-toolbar.component.scss new file mode 100644 index 000000000..8a863ed32 --- /dev/null +++ b/src/app/core/base/pfda-toolbar/pfda-toolbar.component.scss @@ -0,0 +1,88 @@ +$pfda-padding-main: 32px; +$pfda-navbar-height: 64px; +$pfda-navbar-blue: #343E4D; +$pfda-navbar-active-blue: #2f5373; +$pfda-navbar-item-hover: #a0c2e0; +$pfda-navbar-spacer-colour: #5f768a; + +$screenSmall: 850px; +$screenMedium: 1045px; + + +.pfda-toolbar { + background-color: $pfda-navbar-blue; + color: white; + font-family: "Lato","Helvetica Neue",Helvetica,Arial,sans-serif; + font-size: 13px; + font-weight: 400; + line-height: 16px; + padding: 0 8px; + height: auto; + + a { + color: inherit; + text-decoration: none; + } + + @media(min-width: $screenSmall) { + padding: 0 $pfda-padding-main; + height: $pfda-navbar-height; + } +} + +.pfda-toolbar-inner { + display: flex; + align-items: center; +} + +.pfda-logo img { + width: 144px; + margin-top: 4px; +} + +.pfda-toolbar-button { + display: flex; + flex-flow: column nowrap; + align-self: flex-end; + align-items: center; + justify-content: center; + padding: 10px 6px; + + &:hover { + color: $pfda-navbar-item-hover; + } + + &.active { + color: white; + background-color: $pfda-navbar-active-blue; + } + + &-icon, .mat-icon { + display: flex; + align-items: flex-end; + height: 18px; + margin: 3px 0px 2px 0px; + } + + &-title { + display: none; + margin-top: 4px; + + @media(min-width: $screenSmall) { + display: inline; + } + } +} + +.pfda-toolbar-spacer { + border-right: 1px solid $pfda-navbar-spacer-colour; + width: 1px; + height: 38px; + margin: 0px 4px; +} + +.pfda-user-button { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} diff --git a/src/app/core/base/pfda-toolbar/pfda-toolbar.component.spec.ts b/src/app/core/base/pfda-toolbar/pfda-toolbar.component.spec.ts new file mode 100644 index 000000000..59dc19d1f --- /dev/null +++ b/src/app/core/base/pfda-toolbar/pfda-toolbar.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PfdaToolbarComponent } from './pfda-toolbar.component'; + +describe('PfdaToolbarComponent', () => { + let component: PfdaToolbarComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ PfdaToolbarComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(PfdaToolbarComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/core/base/pfda-toolbar/pfda-toolbar.component.ts b/src/app/core/base/pfda-toolbar/pfda-toolbar.component.ts new file mode 100644 index 000000000..1bc5a3ab2 --- /dev/null +++ b/src/app/core/base/pfda-toolbar/pfda-toolbar.component.ts @@ -0,0 +1,83 @@ +import { Component, OnInit } from '@angular/core'; +import { Router, ActivatedRoute, NavigationExtras } from '@angular/router'; +import { ConfigService } from '../../config/config.service'; +import { OverlayContainer } from '@angular/cdk/overlay'; +import { AuthService } from '../../auth/auth.service'; +import { SubstanceTextSearchService } from '@gsrs-core/substance-text-search/substance-text-search.service'; +import { Auth } from '../../auth/auth.model'; +import { Subscription } from 'rxjs'; + +@Component({ + selector: 'app-pfda-toolbar', + templateUrl: './pfda-toolbar.component.html', + styleUrls: ['./pfda-toolbar.component.scss'] +}) +export class PfdaToolbarComponent implements OnInit { + pfdaBaseUrl: string; + logoSrcPath: string; + homeIconPath: string; + auth?: Auth; + searchValue: string; + private overlayContainer: HTMLElement; + private subscriptions: Array = []; + + constructor( + private router: Router, + private activatedRoute: ActivatedRoute, + private configService: ConfigService, + private overlayContainerService: OverlayContainer, + private substanceTextSearchService: SubstanceTextSearchService, + public authService: AuthService + ) { + const authSubscription = this.authService.getAuth().subscribe(auth => { + this.auth = auth; + }); + this.subscriptions.push(authSubscription); + } + + ngOnInit() { + this.pfdaBaseUrl = this.configService.configData.pfdaBaseUrl || '/'; + + const baseHref = this.configService.environment.baseHref || '/' + this.logoSrcPath = `${baseHref}assets/images/pfda/pfda-logo.png`; + this.homeIconPath = `${baseHref}assets/images/pfda/home.svg`; + + this.overlayContainer = this.overlayContainerService.getContainerElement(); + + if (this.activatedRoute.snapshot.queryParamMap.has('search')) { + this.searchValue = this.activatedRoute.snapshot.queryParamMap.get('search'); + } + + const paramsSubscription = this.activatedRoute.queryParamMap.subscribe(params => { + this.searchValue = params.get('search'); + }); + this.subscriptions.push(paramsSubscription); + + this.substanceTextSearchService.registerSearchComponent('main-substance-search'); + const cleanSearchSubscription = this.substanceTextSearchService.setSearchComponentValueEvent('main-substance-search') + .subscribe(value => { + this.searchValue = value; + }); + this.subscriptions.push(cleanSearchSubscription); + } + + processSubstanceSearch(searchValue: string) { + this.navigateToSearchResults(searchValue); + } + + navigateToSearchResults(searchTerm: string) { + const navigationExtras: NavigationExtras = { + queryParams: searchTerm ? { 'search': searchTerm } : null + }; + + this.router.navigate(['/browse-substance'], navigationExtras); + } + + increaseMenuZindex(): void { + this.overlayContainer.style.zIndex = '1001'; + } + + removeZindex(): void { + this.overlayContainer.style.zIndex = null; + } +} diff --git a/src/app/core/config/config.model.ts b/src/app/core/config/config.model.ts index 73bde3bac..7ef252644 100644 --- a/src/app/core/config/config.model.ts +++ b/src/app/core/config/config.model.ts @@ -34,6 +34,11 @@ export interface Config { advancedSearchFacetDisplay?: boolean; facetDisplay?: Array; relationshipsVisualizationUri?: string; + customToolbarComponent?: string; + sessionExpirationWarning?: SessionExpirationWarning; + disableReferenceDocumentUpload?: boolean; + externalSiteWarning?: ExternalSiteWarning; + pfdaBaseUrl?: string; homeDynamicLinks?: Array; registrarDynamicLinks?: Array; registrarDynamicLinks2?: Array; @@ -57,6 +62,7 @@ export interface Config { userProfile?: any; stagingArea?: StagingAreaSettings; privacyStatement: string; + appId?: string; } export interface StagingAreaSettings { @@ -119,3 +125,14 @@ export interface AuthenticateAs { apiKey?: string, apiToken?: string; } + +export interface SessionExpirationWarning { + maxSessionDurationMinutes: number; + extendSessionApiUrl: string; +} + +export interface ExternalSiteWarning { + enabled: boolean; + dialogTitle: string; + dialogMessage: string; +} diff --git a/src/app/core/config/config.pfda.json b/src/app/core/config/config.pfda.json new file mode 100644 index 000000000..dc35f4eb0 --- /dev/null +++ b/src/app/core/config/config.pfda.json @@ -0,0 +1,548 @@ +{ + "version": "2.7.1", + "substanceDetailsCards": [ + { + "card": "substance-overview", + "title": "overview" + }, + { + "card": "substance-primary-definition", + "title": "primary definition", + "filters": [ + { + "filterName": "equals", + "propertyToCheck": "definitionType", + "value": "ALTERNATIVE" + } + ] + }, + { + "card": "substance-alternative-definition", + "type": "SUBSTANCE->SUB_ALTERNATE", + "title": "variant concepts", + "filters": [ + { + "filterName": "exists", + "propertyToCheck": "relationships" + }, + { + "filterName": "substanceRelationships", + "value": "SUBSTANCE->SUB_ALTERNATE" + } + ] + }, + { + "card": "structure-details", + "title": "structure", + "filters": [ + { + "filterName": "equals", + "propertyToCheck": "substanceClass", + "value": "chemical|polymer" + }, + { + "filterName": "exists", + "propertyToCheck": "structure" + } + ] + }, + { + "card": "substance-names", + "title": "names", + "filters": [ + { + "filterName": "exists", + "propertyToCheck": "names" + } + ] + }, + { + "card": "substance-codes", + "type": "classification", + "filters": [ + { + "filterName": "exists", + "propertyToCheck": "codes" + }, + { + "filterName": "substanceCodes", + "value": "classification" + } + ] + }, + { + "card": "substance-codes", + "type": "identifiers", + "filters": [ + { + "filterName": "exists", + "propertyToCheck": "codes" + }, + { + "filterName": "substanceCodes", + "value": "identifiers" + } + ] + }, + { + "card": "substance-subunits", + "title": "subunits", + "filters": [ + { + "filterName": "equals", + "propertyToCheck": "substanceClass", + "value": "protein" + }, + { + "filterName": "exists", + "propertyToCheck": "protein.subunits" + } + ] + }, + { + "card": "substance-subunits", + "title": "subunits", + "filters": [ + { + "filterName": "equals", + "propertyToCheck": "substanceClass", + "value": "nucleicAcid" + }, + { + "filterName": "exists", + "propertyToCheck": "nucleicAcid.subunits" + } + ] + }, + { + "card": "substance-glycosylation", + "title": "glycosylation", + "filters": [ + { + "filterName": "equals", + "propertyToCheck": "substanceClass", + "value": "protein" + }, + { + "filterName": "anyExists", + "propertyToCheck": "protein.glycosylation.glycosylationType|protein.glycosylation.CGlycosylationSites|protein.glycosylation.NGlycosylationSites|protein.glycosylation.OGlycosylationSites" + } + ] + }, + { + "card": "substance-disulfide-links", + "title": "disulfide links", + "filters": [ + { + "filterName": "equals", + "propertyToCheck": "substanceClass", + "value": "protein" + }, + { + "filterName": "exists", + "propertyToCheck": "protein.disulfideLinks" + } + ] + }, + { + "card": "substance-other-links", + "title": "other links", + "filters": [ + { + "filterName": "equals", + "propertyToCheck": "substanceClass", + "value": "protein" + }, + { + "filterName": "exists", + "propertyToCheck": "protein.otherLinks" + } + ] + }, + { + "card": "substance-relationships", + "type": "IMPURITY", + "title": "impurities", + "filters": [ + { + "filterName": "exists", + "propertyToCheck": "relationships" + }, + { + "filterName": "substanceRelationships", + "value": "IMPURITY" + } + ] + }, + { + "card": "substance-relationships", + "type": "METABOLITE", + "title": "metabolites", + "filters": [ + { + "filterName": "exists", + "propertyToCheck": "relationships" + }, + { + "filterName": "substanceRelationships", + "value": "METABOLITE" + } + ] + }, + { + "card": "substance-relationships", + "type": "ACTIVE MOIETY", + "title": "active moiety", + "filters": [ + { + "filterName": "exists", + "propertyToCheck": "relationships" + }, + { + "filterName": "substanceRelationships", + "value": "ACTIVE MOIETY" + } + ] + }, + { + "card": "substance-relationships", + "type": "CONSTITUENT", + "title": "constituents", + "filters": [ + { + "filterName": "exists", + "propertyToCheck": "relationships" + }, + { + "filterName": "substanceRelationships", + "value": "CONSTITUENT" + } + ] + }, + { + "card": "substance-relationships", + "type": "SUB_CONCEPT->SUBSTANCE", + "title": "variant concepts", + "filters": [ + { + "filterName": "exists", + "propertyToCheck": "relationships" + }, + { + "filterName": "substanceRelationships", + "value": "SUB_CONCEPT->SUBSTANCE" + } + ] + }, + { + "card": "substance-relationships", + "type": "RELATIONSHIPS", + "title": "relationships", + "filters": [ + { + "filterName": "exists", + "propertyToCheck": "relationships" + }, + { + "filterName": "substanceRelationships", + "value": [ + "METABOLITE", + "IMPURITY", + "ACTIVE MOIETY", + "CONSTITUENT", + "SUB_CONCEPT->SUBSTANCE" + ] + } + ] + }, + { + "card": "substance-notes", + "title": "notes", + "filters": [ + { + "filterName": "exists", + "propertyToCheck": "notes" + } + ] + }, + { + "card": "substance-audit-info", + "title": "audit info" + }, + { + "card": "substance-references", + "title": "references", + "filters": [ + { + "filterName": "exists", + "propertyToCheck": "references" + } + ] + }, + { + "card": "substance-moieties", + "title": "moieties", + "filters": [ + { + "filterName": "equals", + "propertyToCheck": "substanceClass", + "value": "chemical" + }, + { + "filterName": "exists", + "propertyToCheck": "moieties" + } + ] + }, + { + "card": "substance-concept-definition", + "title": "concept definition", + "filters": [ + { + "filterName": "equals", + "propertyToCheck": "substanceClass", + "value": "concept" + } + ] + }, + { + "card": "substance-polymer-structure", + "title": "display structure", + "filters": [ + { + "filterName": "equals", + "propertyToCheck": "substanceClass", + "value": "polymer" + } + ] + }, + { + "card": "substance-monomers", + "title": "monomers", + "filters": [ + { + "filterName": "equals", + "propertyToCheck": "substanceClass", + "value": "polymer" + }, + { + "filterName": "exists", + "propertyToCheck": "polymer.monomers" + } + ] + }, + { + "card": "substance-structural-units", + "title": "Structural Units", + "filters": [ + { + "filterName": "equals", + "propertyToCheck": "substanceClass", + "value": "polymer" + }, + { + "filterName": "exists", + "propertyToCheck": "polymer.structuralUnits" + } + ] + }, + { + "card": "substance-mixture-components", + "title": "mixture components", + "filters": [ + { + "filterName": "equals", + "propertyToCheck": "substanceClass", + "value": "mixture" + }, + { + "filterName": "exists", + "propertyToCheck": "mixture.components" + } + ] + }, + { + "card": "substance-constituents", + "title": "specified substance constituents", + "filters": [ + { + "filterName": "equals", + "propertyToCheck": "substanceClass", + "value": "specifiedSubstanceG1" + }, + { + "filterName": "exists", + "propertyToCheck": "specifiedSubstance.constituents" + } + ] + }, + { + "card": "substance-mixture-source", + "title": "mixture source", + "filters": [ + { + "filterName": "equals", + "propertyToCheck": "substanceClass", + "value": "mixture" + }, + { + "filterName": "exists", + "propertyToCheck": "mixture.parentSubstance" + } + ] + }, + { + "card": "substance-modifications", + "title": "substance modifications", + "filters": [ + { + "filterName": "anyExists", + "propertyToCheck": "modifications.structuralModifications|modifications.physicalModifications|modifications.agentModifications" + } + ] + }, + { + "card": "substance-na-sugars", + "title": "sugars", + "filters": [ + { + "filterName": "equals", + "propertyToCheck": "substanceClass", + "value": "nucleicAcid" + }, + { + "filterName": "exists", + "propertyToCheck": "nucleicAcid.sugars" + } + ] + }, + { + "card": "substance-na-linkages", + "title": "linkages", + "filters": [ + { + "filterName": "equals", + "propertyToCheck": "substanceClass", + "value": "nucleicAcid" + }, + { + "filterName": "exists", + "propertyToCheck": "nucleicAcid.linkages" + } + ] + }, + { + "card": "substance-properties", + "title": "characteristic attributes", + "filters": [ + { + "filterName": "exists", + "propertyToCheck": "properties" + } + ] + }, + { + "card": "substance-history", + "title": "history", + "filters": [ + { + "filterName": "hasCredentials", + "propertyToCheck": "admin" + } + ] + }, + { + "card": "substance-ssg-definition", + "title": "definition", + "filters": [ + { + "filterName": "exists", + "propertyToCheck": "specifiedSubstanceG3.definition" + } + ] + }, + { + "card": "substance-ssg-parent-substance", + "title": "parent substance", + "filters": [ + { + "filterName": "exists", + "propertyToCheck": "specifiedSubstanceG3.parentSubstance" + } + ] + }, + { + "card": "substance-ssg-grade", + "title": "grade", + "filters": [ + { + "filterName": "exists", + "propertyToCheck": "specifiedSubstanceG3.grade" + } + ] + } + ], + "facets": { + "substances": { + "default": [ + "Deprecated", + "Record Status", + "Substance Class", + "GInAS Tag", + "Code System", + "ATC Level 1", + "ATC Level 2", + "ATC Level 3", + "ATC Level 4", + "DME Reactions", + "Moiety Type", + "Molecular Weight", + "SubstanceStereochemistry", + "Relationships", + "Record Level Access", + "Display Name Level Access", + "Definition Level Access", + "Protein Type", + "Protein Subtype", + "modified" + ], + "admin": [ + "Record Created By", + "root_lastEdited", + "root_lastEditedBy", + "root_approved", + "Approved By", + "Material Type", + "Family", + "Parts" + ] + } + }, + "codeSystemOrder": [ + "BDNUM", + "CAS", + "WHO-ATC", + "EVMPD", + "NCI" + ], + "substanceSelectorProperties": [ + "root_names_name", + "root_approvalID", + "root_codes_BDNUM", + "root_codes_CAS", + "root_codes_ECHA\\ \\(EC\/EINECS\\)" + ], + "contactEmail": "precisionfda-support@dnanexus.com", + "sessionExpirationWarning": { + "extendSessionApiUrl": "/api/update_active", + "maxSessionDurationMinutes": 15 + }, + "disableReferenceDocumentUpload": true, + "externalSiteWarning": { + "enabled": true, + "dialogTitle": "Accessing External Site", + "dialogMessage" : "You will be making an API call outside of the precisionFDA boundary. Do you want to continue?" + }, + "googleAnalyticsId": "", + "customToolbarComponent": "precisionFDA" +} \ No newline at end of file diff --git a/src/app/core/facets-manager/facet-display.pipe.ts b/src/app/core/facets-manager/facet-display.pipe.ts index 755554476..1fd0afcac 100644 --- a/src/app/core/facets-manager/facet-display.pipe.ts +++ b/src/app/core/facets-manager/facet-display.pipe.ts @@ -5,6 +5,7 @@ import { ConfigService } from '../config/config.service'; name: 'facetDisplay' }) export class FacetDisplayPipe implements PipeTransform { + constructor( public configService: ConfigService ) { } @@ -46,6 +47,18 @@ export class FacetDisplayPipe implements PipeTransform { } } } + if(this.configService && this.configService.configData.facetDisplay) { + let returned = name; + this.configService.configData.facetDisplay.forEach(facet => { + if (name === facet.value) { + returned = facet.display; + return facet.display; + } + }); + if (returned !== name) { + return returned; + } + } if (name.toLowerCase() === 'substancestereochemistry') { return 'Stereochemistry'; } @@ -70,6 +83,7 @@ export class FacetDisplayPipe implements PipeTransform { if (name === 'GInAS Tag') { return 'Source Tag'; } + if (name === 'GInAS Domain') { return 'Domain'; } diff --git a/src/app/core/name-resolver/external-site-warning-dialog/external-site-warning-dialog.component.html b/src/app/core/name-resolver/external-site-warning-dialog/external-site-warning-dialog.component.html new file mode 100644 index 000000000..7ff20f703 --- /dev/null +++ b/src/app/core/name-resolver/external-site-warning-dialog/external-site-warning-dialog.component.html @@ -0,0 +1,12 @@ +

+ {{externalSiteWarning.dialogTitle}} +

+
+ {{externalSiteWarning.dialogMessage}} +
+
+ Don't Ask Again + + + +
diff --git a/src/app/core/name-resolver/external-site-warning-dialog/external-site-warning-dialog.component.scss b/src/app/core/name-resolver/external-site-warning-dialog/external-site-warning-dialog.component.scss new file mode 100644 index 000000000..af8ab23c1 --- /dev/null +++ b/src/app/core/name-resolver/external-site-warning-dialog/external-site-warning-dialog.component.scss @@ -0,0 +1,16 @@ +.dialog-title { + margin-top: 12px; +} + +.dialog-actions { + margin-top: 12px; + + button { + margin-left: 12px; + } +} + +.dialog-checkbox { + font-size: 14px; + margin: 12px; +} diff --git a/src/app/core/name-resolver/external-site-warning-dialog/external-site-warning-dialog.component.spec.ts b/src/app/core/name-resolver/external-site-warning-dialog/external-site-warning-dialog.component.spec.ts new file mode 100644 index 000000000..705322fcd --- /dev/null +++ b/src/app/core/name-resolver/external-site-warning-dialog/external-site-warning-dialog.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ExternalSiteWarningDialogComponent } from './external-site-warning-dialog.component'; + +describe('ExternalSiteWarningDialogComponent', () => { + let component: ExternalSiteWarningDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ ExternalSiteWarningDialogComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ExternalSiteWarningDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/core/name-resolver/external-site-warning-dialog/external-site-warning-dialog.component.ts b/src/app/core/name-resolver/external-site-warning-dialog/external-site-warning-dialog.component.ts new file mode 100644 index 000000000..fcd877e8c --- /dev/null +++ b/src/app/core/name-resolver/external-site-warning-dialog/external-site-warning-dialog.component.ts @@ -0,0 +1,33 @@ +import { Component, OnInit } from '@angular/core'; +import { MatDialogRef} from '@angular/material/dialog'; +import { ConfigService, ExternalSiteWarning } from '@gsrs-core/config'; + +@Component({ + selector: 'app-external-site-warning-dialog', + templateUrl: './external-site-warning-dialog.component.html', + styleUrls: ['./external-site-warning-dialog.component.scss'] +}) +export class ExternalSiteWarningDialogComponent implements OnInit { + externalSiteWarning: ExternalSiteWarning; + dontAskAgain: boolean; + + constructor( + public dialogRef: MatDialogRef, + private configService: ConfigService + ) { } + + ngOnInit() { + this.externalSiteWarning = this.configService.configData.externalSiteWarning; + this.dontAskAgain = localStorage.getItem('externalSiteWarningDontAskAgain') === 'true'; + } + + acceptDialog() { + localStorage.setItem('externalSiteWarningDontAskAgain', this.dontAskAgain.toString()); + + this.dialogRef.close(true); + } + + cancelDialog() { + this.dialogRef.close(false); + } +} diff --git a/src/app/core/name-resolver/name-resolver.component.ts b/src/app/core/name-resolver/name-resolver.component.ts index 2696ae0f1..41913222e 100644 --- a/src/app/core/name-resolver/name-resolver.component.ts +++ b/src/app/core/name-resolver/name-resolver.component.ts @@ -8,6 +8,10 @@ import { ResolverResponse } from '../structure/structure-post-response.model'; import { SubstanceService } from '../substance/substance.service'; import { StructureService } from '../structure/structure.service'; import {SafeUrl} from '@angular/platform-browser'; +import { ConfigService, ExternalSiteWarning } from '@gsrs-core/config'; +import { MatDialog } from '@angular/material/dialog'; +import { OverlayContainer } from '@angular/cdk/overlay'; +import { ExternalSiteWarningDialogComponent } from './external-site-warning-dialog/external-site-warning-dialog.component'; @Component({ selector: 'app-name-resolver', @@ -22,14 +26,23 @@ export class NameResolverComponent implements OnInit { matchedNames: PagingResponse; @Output() structureSelected = new EventEmitter(); @Input() startingName?: string; + // External site warning dialog for precisionFDA + externalSiteWarning: ExternalSiteWarning; + private overlayContainer: HTMLElement; constructor( + private configService: ConfigService, private loadingService: LoadingService, private substanceService: SubstanceService, - private structureService: StructureService - ) { } + private structureService: StructureService, + private dialog: MatDialog, + private overlayContainerService: OverlayContainer + ) { } ngOnInit() { + this.externalSiteWarning = this.configService.configData.externalSiteWarning; + this.overlayContainer = this.overlayContainerService.getContainerElement(); + if (this.startingName) { this.resolverControl.setValue(this.startingName); setTimeout( () => { @@ -45,6 +58,15 @@ export class NameResolverComponent implements OnInit { } resolveName(name: string): void { + if (this.shouldShowExternalSiteWarningDialog() === true) { + this.showExternalSiteWarningDialog(); + return; + } + + this.resolveNameInternal(name); + } + + resolveNameInternal(name: string): void { this.errorMessage = ''; this.resolvedNames = []; this.matchedNames = null; @@ -71,4 +93,24 @@ export class NameResolverComponent implements OnInit { this.structureSelected.emit(molfile); } + shouldShowExternalSiteWarningDialog(): boolean { + if (!this.externalSiteWarning || !this.externalSiteWarning.enabled) { + return false; + } + return localStorage.getItem('externalSiteWarningDontAskAgain') != 'true'; + } + + showExternalSiteWarningDialog(): void { + const dialogRef = this.dialog.open(ExternalSiteWarningDialogComponent, { + width: '800px', + autoFocus: false + }); + this.overlayContainer.style.zIndex = '1002'; + const dialogSubscription = dialogRef.afterClosed().subscribe(response => { + this.overlayContainer.style.zIndex = null; + if (response) { + this.resolveNameInternal(this.resolverControl.value); + } + }); + } } diff --git a/src/app/core/name-resolver/name-resolver.module.ts b/src/app/core/name-resolver/name-resolver.module.ts index 7f0cce2a0..57cc89b1d 100644 --- a/src/app/core/name-resolver/name-resolver.module.ts +++ b/src/app/core/name-resolver/name-resolver.module.ts @@ -4,10 +4,12 @@ import { NameResolverComponent } from './name-resolver.component'; import { ReactiveFormsModule, FormsModule } from '@angular/forms'; import { MatInputModule } from '@angular/material/input'; import { MatButtonModule } from '@angular/material/button'; +import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatIconModule } from '@angular/material/icon'; import { RouterModule } from '@angular/router'; import { LoadingModule } from '../loading/loading.module'; import { NameResolverDialogComponent } from './name-resolver-dialog.component'; +import { ExternalSiteWarningDialogComponent } from './external-site-warning-dialog/external-site-warning-dialog.component'; import { SubstanceImageModule } from '@gsrs-core/substance/substance-image.module'; @NgModule({ @@ -17,6 +19,7 @@ import { SubstanceImageModule } from '@gsrs-core/substance/substance-image.modul FormsModule, MatInputModule, MatButtonModule, + MatCheckboxModule, MatIconModule, RouterModule, LoadingModule, @@ -24,14 +27,16 @@ import { SubstanceImageModule } from '@gsrs-core/substance/substance-image.modul ], declarations: [ NameResolverComponent, - NameResolverDialogComponent + NameResolverDialogComponent, + ExternalSiteWarningDialogComponent ], exports: [ NameResolverComponent, NameResolverDialogComponent ], entryComponents: [ - NameResolverDialogComponent + NameResolverDialogComponent, + ExternalSiteWarningDialogComponent ] }) export class NameResolverModule { } diff --git a/src/app/core/substance-details/substance-details.component.html b/src/app/core/substance-details/substance-details.component.html index 758409cb9..488aefcc7 100644 --- a/src/app/core/substance-details/substance-details.component.html +++ b/src/app/core/substance-details/substance-details.component.html @@ -17,13 +17,13 @@
+

Staging Area Record

-
{{substance.approvalID}}
diff --git a/src/app/core/substance-form/references/reference-form.component.html b/src/app/core/substance-form/references/reference-form.component.html index c5ef69d5e..88f1f125b 100644 --- a/src/app/core/substance-form/references/reference-form.component.html +++ b/src/app/core/substance-form/references/reference-form.component.html @@ -40,7 +40,7 @@
-
+
- - - + + +
+
+ + + +
\ No newline at end of file diff --git a/src/app/core/substance-form/submit-success-dialog/submit-success-dialog.component.scss b/src/app/core/substance-form/submit-success-dialog/submit-success-dialog.component.scss index e69de29bb..6c4b2ba41 100644 --- a/src/app/core/substance-form/submit-success-dialog/submit-success-dialog.component.scss +++ b/src/app/core/substance-form/submit-success-dialog/submit-success-dialog.component.scss @@ -0,0 +1,3 @@ +.dialog-actions { + margin-top: 12px; +} diff --git a/src/app/core/substance-form/submit-success-dialog/submit-success-dialog.component.ts b/src/app/core/substance-form/submit-success-dialog/submit-success-dialog.component.ts index a7ea3437b..d259704b9 100644 --- a/src/app/core/substance-form/submit-success-dialog/submit-success-dialog.component.ts +++ b/src/app/core/substance-form/submit-success-dialog/submit-success-dialog.component.ts @@ -1,5 +1,6 @@ import { Component, OnInit, Inject } from '@angular/core'; import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { ConfigService } from '@gsrs-core/config'; @Component({ selector: 'app-submit-success-dialog', @@ -7,14 +8,38 @@ import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; styleUrls: ['./submit-success-dialog.component.scss'] }) export class SubmitSuccessDialogComponent implements OnInit { + dialogTitle: string; + dialogMessage: string = "Update was performed."; + fileUrl: string = null; + appId = ""; public isCoreSubstance = 'true'; public staging = false; constructor( public dialogRef: MatDialogRef, + private configService: ConfigService, @Inject(MAT_DIALOG_DATA) public data: any - ) { } + ) { + switch (data.type) { + case 'submit': + this.dialogTitle = 'Submission Success'; + this.dialogMessage = 'Substance was submitted successfully'; + break; + case 'approve': + this.dialogTitle = 'Submission Approved'; + this.dialogMessage = 'Substance was approved successfully'; + break; + } + + // If data.fileUrl is not null, this is assumed to be GSRS running within the precisionFDA + // whereby we display a button that reveals the substance file uploaded onto the pFDA environment + if (data.fileUrl) { + this.fileUrl = data.fileUrl; + this.dialogTitle = 'Substance Saved'; + this.dialogMessage = 'The substance was saved successfully as a file in your pFDA My Home area.'; + } + } ngOnInit() { if (this.data) { @@ -25,9 +50,11 @@ export class SubmitSuccessDialogComponent implements OnInit { this.staging = true; } } + this.appId = (this.configService.configData && this.configService.configData.appId) || null; } - dismissDialog(action: 'continue' | 'browse' | 'view' | 'home' | 'staging'): void { + + dismissDialog(action: 'continue' | 'browse' | 'view' | 'home' | 'staging'|'viewInPfda'): void { this.dialogRef.close(action); } diff --git a/src/app/core/substance-form/substance-form.component.ts b/src/app/core/substance-form/substance-form.component.ts index 52347cc36..680df4298 100644 --- a/src/app/core/substance-form/substance-form.component.ts +++ b/src/app/core/substance-form/substance-form.component.ts @@ -894,7 +894,7 @@ getDrafts() { this.loadingService.setLoading(false); this.isLoading = false; this.validationMessages = null; - this.openSuccessDialog('approve'); + this.openSuccessDialog({ type: 'approve' }); this.submissionMessage = 'Substance was approved successfully'; this.showSubmissionMessages = true; this.validationResult = false; @@ -923,7 +923,7 @@ getDrafts() { if (!this.id) { this.id = response.uuid; } - this.openSuccessDialog('staging'); + this.openSuccessDialog({ type: 'staging' }); }) } @@ -943,7 +943,7 @@ getDrafts() { if (!this.id) { this.id = response.uuid; } - this.openSuccessDialog(); + this.openSuccessDialog({ type: 'submit', fileUrl: response.fileUrl }); }, (error: SubstanceFormResults) => { this.showSubmissionMessages = true; this.loadingService.setLoading(false); @@ -1149,8 +1149,15 @@ getDrafts() { return old; } - openSuccessDialog(type?: string): void { - const dialogRef = this.dialog.open(SubmitSuccessDialogComponent, {data: {'type':type}}); + openSuccessDialog({ type, fileUrl }: { type?: 'submit'|'approve'|'staging', fileUrl?: string }): void { + const dialogRef = this.dialog.open(SubmitSuccessDialogComponent, { + data: { + type: type, + fileUrl: fileUrl ? fileUrl : null + }, + disableClose: true + }); + this.overlayContainer.style.zIndex = '1002'; const dialogSubscription = dialogRef.afterClosed().pipe(take(1)).subscribe((response?: 'continue' | 'browse' | 'view' | 'staging') => { @@ -1164,6 +1171,9 @@ getDrafts() { this.router.navigate(['/admin/staging-area']); } else if (response === 'view') { this.router.navigate(['/substances', this.id]); + } else if (response === 'viewInPfda') { + // View the submitted substance file in the user's precisionFDA home + window.location.assign(fileUrl); } else { this.submissionMessage = 'Substance was saved successfully!'; if (type && type === 'approve') { @@ -1171,7 +1181,7 @@ getDrafts() { } this.showSubmissionMessages = true; this.validationResult = false; - if (type && type === 'staging') { + if (type && type == 'staging') { this.submissionMessage = 'Edits to staged substance were saved successfully'; } else { setTimeout(() => { diff --git a/src/app/core/substance-form/substance-form.model.ts b/src/app/core/substance-form/substance-form.model.ts index 332b08243..b4e42b844 100644 --- a/src/app/core/substance-form/substance-form.model.ts +++ b/src/app/core/substance-form/substance-form.model.ts @@ -15,6 +15,7 @@ export interface SubstanceFormDefinition { status?: string; approvalID?: string; _name?: string; + _name_html?: string; _nameHTML?: string; relationships?: Array; tags?: Array; @@ -26,6 +27,7 @@ export interface SubstanceFormResults { valid?: boolean; validationMessages?: Array; serverError?: any; + fileUrl?: string; // For precisionFDA } export interface ValidationResults { diff --git a/src/app/core/substance-form/substance-form.service.ts b/src/app/core/substance-form/substance-form.service.ts index 93618a667..180639ed2 100644 --- a/src/app/core/substance-form/substance-form.service.ts +++ b/src/app/core/substance-form/substance-form.service.ts @@ -1617,6 +1617,12 @@ export class SubstanceFormService implements OnDestroy { } else if (this.privateSubstance.substanceClass === 'mixture') { this.substanceSubunitsEmitter.next(this.privateSubstance.mixture.components); } + + // For precisionFDA + if (substance.fileUrl) { + results.fileUrl = substance.fileUrl; + } + this.substanceChangeReasonEmitter.next(this.privateSubstance.changeReason); this.substanceService.getSubstanceDetails(results.uuid).subscribe(resp => { this.privateSubstance = resp; diff --git a/src/app/core/substance-selector/substance-selector.component.html b/src/app/core/substance-selector/substance-selector.component.html index cd33373c6..3ff863bf1 100644 --- a/src/app/core/substance-selector/substance-selector.component.html +++ b/src/app/core/substance-selector/substance-selector.component.html @@ -26,6 +26,7 @@
+
{{header}}
diff --git a/src/app/core/substance/substance.model.ts b/src/app/core/substance/substance.model.ts index ec52fffa2..405a8440a 100644 --- a/src/app/core/substance/substance.model.ts +++ b/src/app/core/substance/substance.model.ts @@ -79,6 +79,7 @@ export interface SubstanceDetail extends SubstanceBase, SubstanceBaseExtended { specifiedSubstanceG3?: SpecifiedSubstanceG3; specifiedSubstanceG4m?: SpecifiedSubstanceG4m; _matchContext?: MatchContext; + fileUrl?: string; // For precisionFDA } export interface StructurallyDiverse extends SubstanceBase { diff --git a/src/app/core/substances-browse/substance-summary-card/substance-summary-card.component.html b/src/app/core/substances-browse/substance-summary-card/substance-summary-card.component.html index 758bc1792..d828d6cac 100644 --- a/src/app/core/substances-browse/substance-summary-card/substance-summary-card.component.html +++ b/src/app/core/substances-browse/substance-summary-card/substance-summary-card.component.html @@ -1,5 +1,6 @@ +