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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
-
+