From dfdbc77a67c120b035b1939dba595b68f74e344c Mon Sep 17 00:00:00 2001 From: blackbaud-brandonhare Date: Mon, 12 Mar 2018 15:32:23 -0400 Subject: [PATCH 01/10] Added required parameters. --- runtime/config-params.ts | 2 +- runtime/params.ts | 30 ++++++++++++++-------- src/app/app.component.ts | 55 ++++++++++++++++++++-------------------- 3 files changed, 48 insertions(+), 39 deletions(-) diff --git a/runtime/config-params.ts b/runtime/config-params.ts index 830031da..2eddef58 100644 --- a/runtime/config-params.ts +++ b/runtime/config-params.ts @@ -1 +1 @@ -export type SkyuxConfigParams = string[] | {[key: string]: boolean | {value?: any}}; +export type SkyuxConfigParams = string[] | { [key: string]: boolean | { value?: any; required?: boolean } }; diff --git a/runtime/params.ts b/runtime/params.ts index 68f4ce81..f2ecbd47 100644 --- a/runtime/params.ts +++ b/runtime/params.ts @@ -16,13 +16,10 @@ function getUrlSearchParams(url: string): URLSearchParams { } export class SkyAppRuntimeConfigParams { + private params: { [key: string]: string } = {}; + private requiredParams: string[] = []; - private params: {[key: string]: string} = {}; - - constructor( - url: string, - configParams: SkyuxConfigParams - ) { + constructor(url: string, configParams: SkyuxConfigParams) { let allowed: string[]; // The default params value in Builder's skyuxconfig.json has been changed @@ -47,8 +44,13 @@ export class SkyAppRuntimeConfigParams { // A boolean value may be present to simply indicate that a parameter is allowed. // If the type is object, look for additional config properties. if (typeof configParam === 'object') { + const isRequired = configParam.required; const paramValue = configParam.value; + if (isRequired) { + this.requiredParams.push(paramName); + } + if (paramValue) { this.params[paramName] = paramValue; } @@ -60,11 +62,11 @@ export class SkyAppRuntimeConfigParams { const urlSearchParams = getUrlSearchParams(url); // Get uppercase keys. - const allowedKeysUC = allowed.map(key => key.toUpperCase()); + const allowedKeysUC = allowed.map((key) => key.toUpperCase()); const urlSearchParamKeys = Array.from(urlSearchParams.paramsMap.keys()); // Filter to allowed params and override default values. - urlSearchParamKeys.forEach(givenKey => { + urlSearchParamKeys.forEach((givenKey) => { const givenKeyUC = givenKey.toUpperCase(); allowedKeysUC.forEach((allowedKeyUC, index) => { if (givenKeyUC === allowedKeyUC) { @@ -113,6 +115,15 @@ export class SkyAppRuntimeConfigParams { return Object.keys(this.params); } + /** + * Returns all required params. + * @name getAllRequired + * @returns {array} + */ + public getAllRequired(): string[] { + return this.requiredParams; + } + /** * Adds the current params to the supplied url. * @name getUrl @@ -124,7 +135,7 @@ export class SkyAppRuntimeConfigParams { const delimiter = url.indexOf('?') === -1 ? '?' : '&'; let joined: string[] = []; - this.getAllKeys().forEach(key => { + this.getAllKeys().forEach((key) => { if (!urlSearchParams.has(key)) { joined.push(`${key}=${encodeURIComponent(this.get(key))}`); } @@ -132,5 +143,4 @@ export class SkyAppRuntimeConfigParams { return joined.length === 0 ? url : `${url}${delimiter}${joined.join('&')}`; } - } diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 16d0c979..be7630e4 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,14 +1,6 @@ -import { - Component, - NgZone, - OnInit, - Optional -} from '@angular/core'; +import { Component, NgZone, OnInit, Optional } from '@angular/core'; -import { - NavigationEnd, - Router -} from '@angular/router'; +import { NavigationEnd, Router } from '@angular/router'; import { BBOmnibar, @@ -85,24 +77,22 @@ export class AppComponent implements OnInit { @Optional() private zone?: NgZone, @Optional() private omnibarProvider?: SkyAppOmnibarProvider ) { - this.styleLoader.loadStyles() - .then((result?: any) => { - this.isReady = true; + this.styleLoader.loadStyles().then((result?: any) => { + this.isReady = true; - if (result && result.error) { - console.log(result.error.message); - } + if (result && result.error) { + console.log(result.error.message); + } - // Let the isReady property take effect on the CSS class that hides/shows - // content based on when styles are loaded. - setTimeout(() => { - viewport.visible.next(true); - }); + // Let the isReady property take effect on the CSS class that hides/shows + // content based on when styles are loaded. + setTimeout(() => { + viewport.visible.next(true); }); + }); } public ngOnInit() { - // Without this code, navigating to a new route doesn't cause the window to be // scrolled to the top like the browser does automatically with non-SPA navigation // when no route fragment is present. @@ -140,11 +130,9 @@ export class AppComponent implements OnInit { private setNav(omnibarConfig: any) { const skyuxConfig = this.config.skyux; - const baseUrl = - ( - skyuxConfig.host.url + - this.config.runtime.app.base.substr(0, this.config.runtime.app.base.length - 1) - ).toLowerCase(); + const baseUrl = ( + skyuxConfig.host.url + this.config.runtime.app.base.substr(0, this.config.runtime.app.base.length - 1) + ).toLowerCase(); let nav: BBOmnibarNavigation; @@ -215,6 +203,7 @@ export class AppComponent implements OnInit { private initShellComponents() { const omnibarConfig = this.config.skyux.omnibar; const helpConfig = this.config.skyux.help; + const requiredParams = this.config.runtime.params.getAllRequired(); const skyuxHost = (this.windowRef.nativeWindow as any).SKYUX_HOST; const loadOmnibar = (args?: SkyAppOmnibarReadyArgs) => { @@ -244,6 +233,17 @@ export class AppComponent implements OnInit { }); }; + if (requiredParams.length > 0) { + const hasAllRequiredParams = requiredParams.some((param: string) => { + return this.config.runtime.params.get(param) !== undefined; + }); + + if (!hasAllRequiredParams) { + this.windowRef.nativeWindow.location.href = 'https://host.nxt.blackbaud.com/errors/notfound'; + return; + } + } + if (omnibarConfig) { if (this.omnibarProvider) { this.omnibarProvider.ready().then(loadOmnibar); @@ -253,7 +253,6 @@ export class AppComponent implements OnInit { } if (helpConfig && this.helpInitService) { - if (this.config.runtime.params.has('svcid')) { helpConfig.extends = this.config.runtime.params.get('svcid'); } From 0e61ac46aed6debf640f8332ded14de8fbb09f4f Mon Sep 17 00:00:00 2001 From: blackbaud-brandonhare Date: Mon, 12 Mar 2018 15:34:30 -0400 Subject: [PATCH 02/10] Adding types. --- runtime/params.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runtime/params.ts b/runtime/params.ts index f2ecbd47..3bd90975 100644 --- a/runtime/params.ts +++ b/runtime/params.ts @@ -62,11 +62,11 @@ export class SkyAppRuntimeConfigParams { const urlSearchParams = getUrlSearchParams(url); // Get uppercase keys. - const allowedKeysUC = allowed.map((key) => key.toUpperCase()); + const allowedKeysUC = allowed.map((key: string) => key.toUpperCase()); const urlSearchParamKeys = Array.from(urlSearchParams.paramsMap.keys()); // Filter to allowed params and override default values. - urlSearchParamKeys.forEach((givenKey) => { + urlSearchParamKeys.forEach((givenKey: string) => { const givenKeyUC = givenKey.toUpperCase(); allowedKeysUC.forEach((allowedKeyUC, index) => { if (givenKeyUC === allowedKeyUC) { From 0e20e4dfcf0342f2e8ad4f116a2370d5393ddb9e Mon Sep 17 00:00:00 2001 From: blackbaud-brandonhare Date: Mon, 12 Mar 2018 15:35:16 -0400 Subject: [PATCH 03/10] Added type. --- runtime/params.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/params.ts b/runtime/params.ts index 3bd90975..df07eac7 100644 --- a/runtime/params.ts +++ b/runtime/params.ts @@ -135,7 +135,7 @@ export class SkyAppRuntimeConfigParams { const delimiter = url.indexOf('?') === -1 ? '?' : '&'; let joined: string[] = []; - this.getAllKeys().forEach((key) => { + this.getAllKeys().forEach((key: string) => { if (!urlSearchParams.has(key)) { joined.push(`${key}=${encodeURIComponent(this.get(key))}`); } From a17f4759e642d1047c9de1a9264cf7fa4ae2a6d4 Mon Sep 17 00:00:00 2001 From: blackbaud-brandonhare Date: Mon, 12 Mar 2018 15:39:03 -0400 Subject: [PATCH 04/10] Fixed formatting. --- runtime/params.ts | 5 ++++- src/app/app.component.ts | 12 ++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/runtime/params.ts b/runtime/params.ts index df07eac7..98c1f99b 100644 --- a/runtime/params.ts +++ b/runtime/params.ts @@ -19,7 +19,10 @@ export class SkyAppRuntimeConfigParams { private params: { [key: string]: string } = {}; private requiredParams: string[] = []; - constructor(url: string, configParams: SkyuxConfigParams) { + constructor( + url: string, + configParams: SkyuxConfigParams + ) { let allowed: string[]; // The default params value in Builder's skyuxconfig.json has been changed diff --git a/src/app/app.component.ts b/src/app/app.component.ts index be7630e4..7ec9ed53 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,6 +1,14 @@ -import { Component, NgZone, OnInit, Optional } from '@angular/core'; +import { + Component, + NgZone, + OnInit, + Optional +} from '@angular/core'; -import { NavigationEnd, Router } from '@angular/router'; +import { + NavigationEnd, + Router +} from '@angular/router'; import { BBOmnibar, From 6eb1cd0062199f4197d53f1dc87fa8770f8d6bba Mon Sep 17 00:00:00 2001 From: blackbaud-brandonhare Date: Tue, 13 Mar 2018 07:15:56 -0400 Subject: [PATCH 05/10] fixed formatting. --- src/app/app.component.ts | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 7ec9ed53..121b255c 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -85,19 +85,20 @@ export class AppComponent implements OnInit { @Optional() private zone?: NgZone, @Optional() private omnibarProvider?: SkyAppOmnibarProvider ) { - this.styleLoader.loadStyles().then((result?: any) => { - this.isReady = true; + this.styleLoader.loadStyles() + .then((result?: any) => { + this.isReady = true; - if (result && result.error) { - console.log(result.error.message); - } + if (result && result.error) { + console.log(result.error.message); + } - // Let the isReady property take effect on the CSS class that hides/shows - // content based on when styles are loaded. - setTimeout(() => { - viewport.visible.next(true); + // Let the isReady property take effect on the CSS class that hides/shows + // content based on when styles are loaded. + setTimeout(() => { + viewport.visible.next(true); + }); }); - }); } public ngOnInit() { @@ -138,9 +139,11 @@ export class AppComponent implements OnInit { private setNav(omnibarConfig: any) { const skyuxConfig = this.config.skyux; - const baseUrl = ( - skyuxConfig.host.url + this.config.runtime.app.base.substr(0, this.config.runtime.app.base.length - 1) - ).toLowerCase(); + const baseUrl = + ( + skyuxConfig.host.url + + this.config.runtime.app.base.substr(0, this.config.runtime.app.base.length - 1) + ).toLowerCase(); let nav: BBOmnibarNavigation; From 48b5a406514df4d9f44ed502902e01e92eb71981 Mon Sep 17 00:00:00 2001 From: blackbaud-brandonhare Date: Tue, 13 Mar 2018 07:16:44 -0400 Subject: [PATCH 06/10] Fixed space. --- src/app/app.component.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 121b255c..13fa3cc9 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -102,6 +102,7 @@ export class AppComponent implements OnInit { } public ngOnInit() { + // Without this code, navigating to a new route doesn't cause the window to be // scrolled to the top like the browser does automatically with non-SPA navigation // when no route fragment is present. From 74e5f57bb8ad714885385e811fcad83e6ed65fd6 Mon Sep 17 00:00:00 2001 From: blackbaud-brandonhare Date: Tue, 13 Mar 2018 07:18:49 -0400 Subject: [PATCH 07/10] Fixed formatting. --- runtime/params.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/runtime/params.ts b/runtime/params.ts index 98c1f99b..0efd2478 100644 --- a/runtime/params.ts +++ b/runtime/params.ts @@ -65,11 +65,11 @@ export class SkyAppRuntimeConfigParams { const urlSearchParams = getUrlSearchParams(url); // Get uppercase keys. - const allowedKeysUC = allowed.map((key: string) => key.toUpperCase()); + const allowedKeysUC = allowed.map(key => key.toUpperCase()); const urlSearchParamKeys = Array.from(urlSearchParams.paramsMap.keys()); // Filter to allowed params and override default values. - urlSearchParamKeys.forEach((givenKey: string) => { + urlSearchParamKeys.forEach(givenKey => { const givenKeyUC = givenKey.toUpperCase(); allowedKeysUC.forEach((allowedKeyUC, index) => { if (givenKeyUC === allowedKeyUC) { @@ -138,7 +138,7 @@ export class SkyAppRuntimeConfigParams { const delimiter = url.indexOf('?') === -1 ? '?' : '&'; let joined: string[] = []; - this.getAllKeys().forEach((key: string) => { + this.getAllKeys().forEach(key => { if (!urlSearchParams.has(key)) { joined.push(`${key}=${encodeURIComponent(this.get(key))}`); } From f3cac37976b9df32f562ad285929de7fa8c466b6 Mon Sep 17 00:00:00 2001 From: blackbaud-brandonhare Date: Tue, 13 Mar 2018 10:23:55 -0400 Subject: [PATCH 08/10] Changes based on feedback. --- runtime/params.ts | 27 ++++++++++++++++----------- src/app/app.component.ts | 13 +++---------- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/runtime/params.ts b/runtime/params.ts index 0efd2478..d4a128f8 100644 --- a/runtime/params.ts +++ b/runtime/params.ts @@ -47,10 +47,9 @@ export class SkyAppRuntimeConfigParams { // A boolean value may be present to simply indicate that a parameter is allowed. // If the type is object, look for additional config properties. if (typeof configParam === 'object') { - const isRequired = configParam.required; const paramValue = configParam.value; - if (isRequired) { + if (configParam.required) { this.requiredParams.push(paramName); } @@ -88,6 +87,21 @@ export class SkyAppRuntimeConfigParams { return this.params && this.params.hasOwnProperty(key); } + /** + * Are all the required params defined?. + * @name hasAllRequiredParams + * @returns {array} + */ + public hasAllRequiredParams(): boolean { + if (this.requiredParams.length === 0) { + return true; + } + + return this.requiredParams.some((param: string) => { + return this.params[param] !== undefined; + }); + } + /** * Returns the value of the requested param. * @name get @@ -118,15 +132,6 @@ export class SkyAppRuntimeConfigParams { return Object.keys(this.params); } - /** - * Returns all required params. - * @name getAllRequired - * @returns {array} - */ - public getAllRequired(): string[] { - return this.requiredParams; - } - /** * Adds the current params to the supplied url. * @name getUrl diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 13fa3cc9..b2666fdb 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -215,7 +215,6 @@ export class AppComponent implements OnInit { private initShellComponents() { const omnibarConfig = this.config.skyux.omnibar; const helpConfig = this.config.skyux.help; - const requiredParams = this.config.runtime.params.getAllRequired(); const skyuxHost = (this.windowRef.nativeWindow as any).SKYUX_HOST; const loadOmnibar = (args?: SkyAppOmnibarReadyArgs) => { @@ -245,15 +244,9 @@ export class AppComponent implements OnInit { }); }; - if (requiredParams.length > 0) { - const hasAllRequiredParams = requiredParams.some((param: string) => { - return this.config.runtime.params.get(param) !== undefined; - }); - - if (!hasAllRequiredParams) { - this.windowRef.nativeWindow.location.href = 'https://host.nxt.blackbaud.com/errors/notfound'; - return; - } + if (!this.config.runtime.params.hasAllRequiredParams()) { + this.windowRef.nativeWindow.location.href = 'https://host.nxt.blackbaud.com/errors/notfound'; + return; } if (omnibarConfig) { From b5217072a394a2a12a98af548dbbe0e19e46c78a Mon Sep 17 00:00:00 2001 From: blackbaud-brandonhare Date: Tue, 13 Mar 2018 13:20:51 -0400 Subject: [PATCH 09/10] Fixed test. --- runtime/params.spec.ts | 38 +++++++++++++++++++++++++ src/app/app.component.spec.ts | 52 +++++++++++++++++++++++------------ 2 files changed, 72 insertions(+), 18 deletions(-) diff --git a/runtime/params.spec.ts b/runtime/params.spec.ts index 4965320d..4c877a34 100644 --- a/runtime/params.spec.ts +++ b/runtime/params.spec.ts @@ -124,4 +124,42 @@ describe('SkyAppRuntimeConfigParams', () => { expect(params.get('a2')).toBe('c'); }); + it('should allow queryparam values to be required', () => { + const params: SkyAppRuntimeConfigParams = new SkyAppRuntimeConfigParams( + '', + { + a1: { + value: 'test', + required: true + } + } + ); + + expect(params.hasAllRequiredParams()).toBe(true); + }); + + it('should expose a `hasAllRequiredParams` method for testing if all required params are defined', () => { + const params: SkyAppRuntimeConfigParams = new SkyAppRuntimeConfigParams( + '', + { + a1: { + required: true + } + } + ); + + expect(params.hasAllRequiredParams()).toBe(false); + }); + + it('should expose a `hasAllRequiredParams` method that returns true if no required params are defined', () => { + const params: SkyAppRuntimeConfigParams = new SkyAppRuntimeConfigParams( + '', + { + a1: true + } + ); + + expect(params.hasAllRequiredParams()).toBe(true); + }); + }); diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts index 5e3035fd..3441eb34 100644 --- a/src/app/app.component.spec.ts +++ b/src/app/app.component.spec.ts @@ -37,6 +37,7 @@ import { AppComponent } from './app.component'; describe('AppComponent', () => { let mockSkyuxHost: any; + let mockWindow: any; let comp: AppComponent; let fixture: ComponentFixture; let parseParams: any; @@ -47,12 +48,20 @@ describe('AppComponent', () => { let skyAppConfig: any; let viewport: SkyAppViewportService; - const location = 'my-custom-location'; - class MockHelpInitService { public load() { } } + class MockWindow { + public nativeWindow = { + location: { + href: '' + }, + SKYUX_HOST: mockSkyuxHost, + scroll: () => scrollCalled = true + }; + } + const mockHelpInitService = new MockHelpInitService(); function setup( @@ -61,6 +70,7 @@ describe('AppComponent', () => { styleLoadPromise?: Promise, omnibarProvider?: any ) { + mockWindow = new MockWindow(); let providers: any[] = [ { provide: Router, @@ -78,13 +88,7 @@ describe('AppComponent', () => { }, { provide: SkyAppWindowRef, - useValue: { - nativeWindow: { - location: location, - SKYUX_HOST: mockSkyuxHost, - scroll: () => scrollCalled = true - } - } + useValue: mockWindow }, { provide: SkyAppConfig, @@ -131,11 +135,11 @@ describe('AppComponent', () => { ], providers: providers }) - .compileComponents() - .then(() => { - fixture = TestBed.createComponent(AppComponent); - comp = fixture.componentInstance; - }); + .compileComponents() + .then(() => { + fixture = TestBed.createComponent(AppComponent); + comp = fixture.componentInstance; + }); } function validateOmnibarProvider( @@ -184,6 +188,7 @@ describe('AppComponent', () => { }, params: { has: (key: any) => false, + hasAllRequiredParams: () => true, parse: (p: any) => parseParams = p } }, @@ -438,8 +443,8 @@ describe('AppComponent', () => { experimental: true, nav: { services: [ - { }, - { } + {}, + {} ] } }; @@ -456,7 +461,7 @@ describe('AppComponent', () => { experimental: true, nav: { services: [ - { }, + {}, { selected: true } ] } @@ -701,7 +706,7 @@ describe('AppComponent', () => { skyAppConfig.runtime.params.has = (key: any) => key === false; - mockSkyuxHost = { }; + mockSkyuxHost = {}; const expectedCall = { productId: 'test-config', extends: 'bb-help', locale: '' }; skyAppConfig.skyux.help = { productId: 'test-config', extends: 'bb-help' }; @@ -776,6 +781,17 @@ describe('AppComponent', () => { }); })); + it('should redirect if all required params are not defined', async(() => { + skyAppConfig.runtime.params.hasAllRequiredParams = () => false; + + setup(skyAppConfig).then(() => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(mockWindow.nativeWindow.location.href).toBe('https://host.nxt.blackbaud.com/errors/notfound'); + }); + }); + })); + it( 'should load the omnibar when the omnibar provider\'s ready() promise is resolved', fakeAsync(() => { From 09c308ebb099509e815af1b1ee4438d8788086a4 Mon Sep 17 00:00:00 2001 From: blackbaud-brandonhare Date: Fri, 16 Mar 2018 11:00:27 -0400 Subject: [PATCH 10/10] Fixed check for required params. --- runtime/params.spec.ts | 17 +++++++++++++++++ runtime/params.ts | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/runtime/params.spec.ts b/runtime/params.spec.ts index 4c877a34..a9f58336 100644 --- a/runtime/params.spec.ts +++ b/runtime/params.spec.ts @@ -162,4 +162,21 @@ describe('SkyAppRuntimeConfigParams', () => { expect(params.hasAllRequiredParams()).toBe(true); }); + it('should expose a `hasAllRequiredParams` method that returns false if any required params are undefined', () => { + const params: SkyAppRuntimeConfigParams = new SkyAppRuntimeConfigParams( + '', + { + a1: { + value: '1', + required: true + }, + a2: { + required: true + } + } + ); + + expect(params.hasAllRequiredParams()).toBe(false); + }); + }); diff --git a/runtime/params.ts b/runtime/params.ts index d4a128f8..9ae44684 100644 --- a/runtime/params.ts +++ b/runtime/params.ts @@ -97,7 +97,7 @@ export class SkyAppRuntimeConfigParams { return true; } - return this.requiredParams.some((param: string) => { + return this.requiredParams.every((param: string) => { return this.params[param] !== undefined; }); }