mirror of
https://github.com/NationalSecurityAgency/skills-client.git
synced 2026-11-07 14:06:36 -10:00
Compare commits
98
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee35ce9540 | ||
|
|
7639042010 | ||
|
|
61a6c3c24f | ||
|
|
6598bcb28d | ||
|
|
537ccdeb5d | ||
|
|
05b6e51f88 | ||
|
|
7d3f375c1a | ||
|
|
1452bdd9be | ||
|
|
ca3de11171 | ||
|
|
cd997f6d5b | ||
|
|
54a54a5b00 | ||
|
|
c2f4df8afb | ||
|
|
4e4334a424 | ||
|
|
615a8db2d1 | ||
|
|
3ba071ac00 | ||
|
|
1dcc6bd29e | ||
|
|
386c634a3f | ||
|
|
f25bfb6252 | ||
|
|
1bb162bbd3 | ||
|
|
7e37d927ec | ||
|
|
9a488b0bc3 | ||
|
|
f2ca470d3f | ||
|
|
2ab9b7086c | ||
|
|
7b76ff01af | ||
|
|
8e4e2e4948 | ||
|
|
634e82d24e | ||
|
|
5544723938 | ||
|
|
72144d7734 | ||
|
|
8af1f62bd6 | ||
|
|
10c24e9bd7 | ||
|
|
38b0b502c3 | ||
|
|
563f9977b0 | ||
|
|
b78d329faa | ||
|
|
a777500389 | ||
|
|
c57c9d695f | ||
|
|
d866ce27db | ||
|
|
edb1c05d27 | ||
|
|
3d9e8c2571 | ||
|
|
843145e436 | ||
|
|
8a4fededb3 | ||
|
|
5325af1973 | ||
|
|
ed1238795c | ||
|
|
410a5fc740 | ||
|
|
b72d51c9ac | ||
|
|
1979f74084 | ||
|
|
76f1481557 | ||
|
|
af274ee955 | ||
|
|
e8e63fb4ef | ||
|
|
95a25b8f32 | ||
|
|
ba8194f6f9 | ||
|
|
40a6980846 | ||
|
|
44cd320c20 | ||
|
|
4e1eed3858 | ||
|
|
8173a7d564 | ||
|
|
4dd5e215b0 | ||
|
|
775cbff377 | ||
|
|
d58e9032a1 | ||
|
|
6c38e8f881 | ||
|
|
a67e38fa84 | ||
|
|
c23212f924 | ||
|
|
605356e400 | ||
|
|
2ad5fe410a | ||
|
|
f729048742 | ||
|
|
d27c5b28e2 | ||
|
|
570e7ddfa9 | ||
|
|
831c1c4209 | ||
|
|
eb23dc48d6 | ||
|
|
70372c52f9 | ||
|
|
e9f045a32a | ||
|
|
8954aba225 | ||
|
|
208af5c2ba | ||
|
|
36612b526e | ||
|
|
ca330f78ab | ||
|
|
4803c5181e | ||
|
|
3d2184ea4e | ||
|
|
9be1962510 | ||
|
|
23704d3171 | ||
|
|
b214a14669 | ||
|
|
a8f28f596b | ||
|
|
f93426c6ac | ||
|
|
0905b5b2b9 | ||
|
|
692d02cb5f | ||
|
|
58be278dd9 | ||
|
|
1ee43ef218 | ||
|
|
63af6a8bff | ||
|
|
fc4bf72202 | ||
|
|
085cf72470 | ||
|
|
e770f59643 | ||
|
|
4394a28fb8 | ||
|
|
51bdfb4ead | ||
|
|
e12308b801 | ||
|
|
18b1ba3a58 | ||
|
|
3fd50e096a | ||
|
|
51643e043e | ||
|
|
41ad8f9214 | ||
|
|
b94dec07fe | ||
|
|
4219fcdc97 | ||
|
|
e00e41651c |
No files matched your search
@@ -13,7 +13,7 @@
|
||||
<groupId>skills</groupId>
|
||||
<artifactId>skills-client-integration</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<version>3.1.1-SNAPSHOT</version>
|
||||
<version>3.3.1-SNAPSHOT</version>
|
||||
|
||||
<licenses>
|
||||
<license>
|
||||
@@ -38,23 +38,4 @@
|
||||
<node.version>v14.15.0</node.version>
|
||||
</properties>
|
||||
|
||||
<distributionManagement>
|
||||
<repository>
|
||||
<id>nexus-releases</id>
|
||||
<name>Release Repository</name>
|
||||
<uniqueVersion>false</uniqueVersion>
|
||||
<url>
|
||||
${nexusServer}/repository/maven-releases/
|
||||
</url>
|
||||
</repository>
|
||||
<snapshotRepository>
|
||||
<id>nexus-snapshots</id>
|
||||
<name>Snapshot Repository</name>
|
||||
<uniqueVersion>true</uniqueVersion>
|
||||
<url>
|
||||
${nexusServer}/repository/maven-snapshots/
|
||||
</url>
|
||||
</snapshotRepository>
|
||||
</distributionManagement>
|
||||
|
||||
</project>
|
||||
@@ -32,10 +32,14 @@ limitations under the License.
|
||||
<script type="text/javascript">
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const isSummaryOnly = params.get('isSummaryOnly') ? JSON.parse(params.get('isSummaryOnly')) : false;
|
||||
const internalBackButton = params.get('internalBackButton') ? JSON.parse(params.get('internalBackButton')) : false;
|
||||
|
||||
let clientDisplay = null;
|
||||
|
||||
const options = {
|
||||
autoScrollStrategy: 'top-of-page',
|
||||
isSummaryOnly,
|
||||
internalBackButton,
|
||||
};
|
||||
const themes = {
|
||||
default: {
|
||||
@@ -85,6 +89,16 @@ limitations under the License.
|
||||
window.location.search = params;
|
||||
};
|
||||
|
||||
const navigate = () => {
|
||||
clientDisplay.navigate('/subjects/subj0');
|
||||
}
|
||||
|
||||
const setInternalBackButton = () => {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
params.set('internalBackButton', !options.internalBackButton);
|
||||
window.location.search = params;
|
||||
}
|
||||
|
||||
const getTheme = () => {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
let themeName = params.get('themeName') ? params.get('themeName') : 'Bright (default)';
|
||||
@@ -98,6 +112,9 @@ limitations under the License.
|
||||
return version;
|
||||
};
|
||||
|
||||
const handleRouteChanged = (newPath) => {
|
||||
document.querySelector('#skillsDisplayPath').innerHTML = `Skills Display Path: [${newPath}]`;
|
||||
};
|
||||
axios.get("/api/config")
|
||||
.then((result) => {
|
||||
SkillsClient.SkillsConfiguration.configure(result.data);
|
||||
@@ -116,10 +133,11 @@ limitations under the License.
|
||||
defaultThemeElement.classList.add('active');
|
||||
}
|
||||
|
||||
const clientDisplay = new SkillsClient.SkillsDisplayJS({
|
||||
clientDisplay = new SkillsClient.SkillsDisplayJS({
|
||||
options,
|
||||
version: getVersion(),
|
||||
theme: theme.theme,
|
||||
handleRouteChanged: handleRouteChanged,
|
||||
});
|
||||
clientDisplay.attachTo(document.querySelector('#skills-client-display'));
|
||||
};
|
||||
@@ -143,10 +161,10 @@ limitations under the License.
|
||||
|
||||
<ul class="navbar-nav flex-row mr-auto">
|
||||
<li class="nav-item pr-3">
|
||||
<a class="nav-link" href="index.html">Report Skill Events</a>
|
||||
<a class="nav-link" href="index.html" data-cy="reportSkillsLink">Report Skill Events</a>
|
||||
</li>
|
||||
<li class="nav-item active pr-3">
|
||||
<a class="nav-link" href="javascript:void(0)">User Display</a>
|
||||
<a class="nav-link" href="javascript:void(0)" data-cy="userDisplayLink">User Display</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
@@ -169,7 +187,17 @@ limitations under the License.
|
||||
<input type="checkbox" class="custom-control-input" id="displayTypeControl" onclick="setDisplayType()" checked>
|
||||
<label class="custom-control-label" for="displayTypeControl">Summary Only</label>
|
||||
</div>
|
||||
<div class="d-inline custom-control custom-checkbox mx-2">
|
||||
<input type="checkbox" class="custom-control-input" id="internalBackButtonControl" onclick="setInternalBackButton()" checked>
|
||||
<label class="custom-control-label" for="internalBackButtonControl">Internal Back Button</label>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-outline-primary" type="button" data-cy="navigateButton" onclick="navigate()">
|
||||
Navigate
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<span id="skillsDisplayPath" data-cy="skillsDisplayPath"></span>
|
||||
</div>
|
||||
<div
|
||||
style="min-height: 860px">
|
||||
@@ -192,4 +220,5 @@ limitations under the License.
|
||||
|
||||
<script type="text/javascript">
|
||||
document.getElementById("displayTypeControl").checked = isSummaryOnly;
|
||||
document.getElementById("internalBackButtonControl").checked = internalBackButton;
|
||||
</script>
|
||||
@@ -60,10 +60,10 @@ limitations under the License.
|
||||
|
||||
<ul class="navbar-nav flex-row mr-auto">
|
||||
<li class="nav-item pr-3">
|
||||
<a class="nav-link" href="index.html">Report Skill Events</a>
|
||||
<a class="nav-link" href="index.html" data-cy="reportSkillsLink">Report Skill Events</a>
|
||||
</li>
|
||||
<li class="nav-item active pr-3">
|
||||
<a class="nav-link" href="javascript:void(0)">User Display</a>
|
||||
<a class="nav-link" href="javascript:void(0)" data-cy="userDisplayLink">User Display</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
@@ -72,10 +72,10 @@ limitations under the License.
|
||||
|
||||
<ul class="navbar-nav flex-row mr-auto">
|
||||
<li class="nav-item active pr-3">
|
||||
<a class="nav-link" href="javascript:void(0)">Report Skill Events</a>
|
||||
<a class="nav-link" href="javascript:void(0)" data-cy="reportSkillsLink">Report Skill Events</a>
|
||||
</li>
|
||||
<li class="nav-item pr-3">
|
||||
<a class="nav-link" href="clientDisplay.html">User Display</a>
|
||||
<a class="nav-link" href="clientDisplay.html" data-cy="userDisplayLink">User Display</a>
|
||||
</li>
|
||||
</ul>
|
||||
<button type="button" class="btn btn-primary">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "skills-int-client-js",
|
||||
"version": "3.1.1",
|
||||
"version": "3.3.1",
|
||||
"license": "Apache-2.0",
|
||||
"description": "SkillTree Client Lib Integration Tests - Native JS",
|
||||
"main": "index.js",
|
||||
@@ -16,16 +16,16 @@
|
||||
},
|
||||
"author": "SkillTree Team",
|
||||
"dependencies": {
|
||||
"@skilltree/skills-client-js": "3.1.1",
|
||||
"axios": "^0.21.1",
|
||||
"@skilltree/skills-client-js": "3.3.1",
|
||||
"axios": "0.21.1",
|
||||
"bootstrap": "4.5.2",
|
||||
"jquery": "^3.5.1",
|
||||
"jquery": "3.5.1",
|
||||
"js-beautify": "1.13.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"fs-extra": "^8.1.0",
|
||||
"license-check-and-add": "^3.0.4",
|
||||
"license-checker": "^25.0.1",
|
||||
"fs-extra": "8.1.0",
|
||||
"license-check-and-add": "3.0.4",
|
||||
"license-checker": "25.0.1",
|
||||
"live-server": "1.2.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>skills</groupId>
|
||||
<artifactId>skills-client-integration</artifactId>
|
||||
<version>3.1.1-SNAPSHOT</version>
|
||||
<version>3.3.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>skills-int-client-js</artifactId>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "skills-int-client-ng",
|
||||
"version": "3.1.1",
|
||||
"version": "3.3.1",
|
||||
"license": "Apache-2.0",
|
||||
"description": "SkillTree Client Lib - Angular",
|
||||
"author": "SkillTree Team",
|
||||
@@ -28,7 +28,7 @@
|
||||
"@angular/platform-browser-dynamic": "10.1.5",
|
||||
"@angular/router": "10.0.14",
|
||||
"@ng-bootstrap/ng-bootstrap": "7.0.0",
|
||||
"@skilltree/skills-client-ng": "3.1.1",
|
||||
"@skilltree/skills-client-ng": "3.3.1",
|
||||
"@types/js-beautify": "1.11.0",
|
||||
"axios": "0.21.1",
|
||||
"bootstrap": "4.5.3",
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>skills</groupId>
|
||||
<artifactId>skills-client-integration</artifactId>
|
||||
<version>3.1.1-SNAPSHOT</version>
|
||||
<version>3.3.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>skills-int-client-ng</artifactId>
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ import { NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { HighlightModule } from 'ngx-highlightjs';
|
||||
import { HighlightModule, HIGHLIGHT_OPTIONS } from 'ngx-highlightjs';
|
||||
import { SkillsLevelModule, SkillsDisplayModule, SkilltreeModule } from '@skilltree/skills-client-ng'
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
@@ -58,7 +58,15 @@ import { PlaceholderDirective } from './directives/placeholder.directive';
|
||||
{ path: '', component: ReportSkillsComponent },
|
||||
])
|
||||
],
|
||||
providers: [ExampleService],
|
||||
providers: [
|
||||
ExampleService,
|
||||
{
|
||||
provide: HIGHLIGHT_OPTIONS,
|
||||
useValue: {
|
||||
fullLibraryLoader: () => import('highlight.js'),
|
||||
}
|
||||
}
|
||||
],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule { }
|
||||
+14
-3
@@ -30,11 +30,22 @@ limitations under the License.
|
||||
<label class="custom-control-label" for="customSwitch1">Summary Only</label>
|
||||
</div>
|
||||
|
||||
<div class="custom-control custom-switch d-inline-block ml-3 border-right pr-2">
|
||||
<input type="checkbox" class="custom-control-input" id="customSwitch2" (click)="setInternalBackButtonUrlParam()"
|
||||
[checked]="displayOptions.internalBackButton">
|
||||
<label class="custom-control-label" for="customSwitch2">Internal Back Button</label>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-outline-primary" id="navButton" data-cy="navigateButton" (click)="navigate()">Navigate</button>
|
||||
|
||||
<a href="javascript:void(0)" class="ml-2" (click)="scrollTo('sample-code')">Show Source</a>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span id="skillsDisplayPath" data-cy="skillsDisplayPath">Skills Display Path: [{{ skillsDisplayRoutePath }}]</span>
|
||||
</div>
|
||||
|
||||
<div class="border rounded">
|
||||
<skills-display *ngIf="selectedTheme" [options]="displayOptions" [version]="version" [theme]="selectedTheme.theme">
|
||||
<skills-display #skillsDisplay *ngIf="selectedTheme" [options]="displayOptions" [version]="version" [theme]="selectedTheme.theme" [handleRouteChanged]="skillsDisplayRouteChanged">
|
||||
</skills-display>
|
||||
</div>
|
||||
|
||||
@@ -51,4 +62,4 @@ limitations under the License.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
+20
-2
@@ -13,7 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { themes } from './themes'
|
||||
import { ViewportScroller } from '@angular/common';
|
||||
@@ -26,11 +26,16 @@ import { js_beautify } from 'js-beautify'
|
||||
})
|
||||
export class SkillsDisplayComponent implements OnInit {
|
||||
|
||||
@ViewChild('skillsDisplay')
|
||||
skillsDisplay: any;
|
||||
|
||||
|
||||
displayOptions: any;
|
||||
version: number;
|
||||
selectedTheme: any;
|
||||
themes: Array<any> = themes;
|
||||
showSampleCode: boolean = false;
|
||||
skillsDisplayRoutePath: string = '/';
|
||||
|
||||
constructor(private route: ActivatedRoute, private router: Router, private viewportScroller: ViewportScroller) { }
|
||||
|
||||
@@ -40,15 +45,23 @@ export class SkillsDisplayComponent implements OnInit {
|
||||
this.displayOptions = {
|
||||
autoScrollStrategy: 'top-of-page',
|
||||
isSummaryOnly: params['isSummaryOnly'] ? JSON.parse(params['isSummaryOnly']) : false,
|
||||
internalBackButton: params['internalBackButton'] ? JSON.parse(params['internalBackButton']) : false,
|
||||
};
|
||||
this.selectedTheme = params['themeName'] ? this.findTheme(params['themeName']) : this.themes[0];
|
||||
});
|
||||
}
|
||||
navigate() {
|
||||
this.skillsDisplay.navigate('/subjects/subj0')
|
||||
}
|
||||
|
||||
findTheme(name: string) {
|
||||
return this.themes.find((item) => item.name === name);
|
||||
}
|
||||
|
||||
skillsDisplayRouteChanged = (newPath: string) => {
|
||||
this.skillsDisplayRoutePath = newPath;
|
||||
}
|
||||
|
||||
refreshPage() {
|
||||
setTimeout(() => {
|
||||
document.location.reload();
|
||||
@@ -66,6 +79,11 @@ export class SkillsDisplayComponent implements OnInit {
|
||||
this.refreshPage();
|
||||
}
|
||||
|
||||
setInternalBackButtonUrlParam() {
|
||||
this.displayOptions.internalBackButton = !this.displayOptions.internalBackButton;
|
||||
this.router.navigate(['angular/showSkills'], { queryParams: { internalBackButton: this.displayOptions.internalBackButton }, queryParamsHandling: 'merge' });
|
||||
this.refreshPage();
|
||||
}
|
||||
scrollTo(elementId: string) {
|
||||
this.showSampleCode = true;
|
||||
setTimeout(() => {
|
||||
@@ -77,7 +95,7 @@ export class SkillsDisplayComponent implements OnInit {
|
||||
let sampleCode = `
|
||||
<template>
|
||||
<skills-level /> <!-- You can display the user's current Level using the SkillsLevel component -->
|
||||
<skills-display :theme="selectedTheme" ${this.displayOptions.isSummaryOnly ? ':options="displayOptions"' : ''}/>
|
||||
<skills-display :theme="selectedTheme" ${(this.displayOptions.isSummaryOnly || this.displayOptions.internalBackButton) ? ':options="displayOptions"' : ''}/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
{
|
||||
"name": "skills-int-client-react",
|
||||
"version": "3.1.1",
|
||||
"version": "3.3.1",
|
||||
"license": "Apache-2.0",
|
||||
"description": "SkillTree Client Lib Integration Tests - React",
|
||||
"homepage": "/react/",
|
||||
"dependencies": {
|
||||
"@skilltree/skills-client-react": "3.1.1",
|
||||
"@skilltree/skills-client-react": "3.3.1",
|
||||
"axios": "0.21.1",
|
||||
"bootstrap": "4.5.3",
|
||||
"core-js": "3.6.5",
|
||||
"highlightjs": "9.16.2",
|
||||
"js-beautify": "1.13.0",
|
||||
"prop-types": "15.7.2",
|
||||
"react": "16.13.1",
|
||||
"react-bootstrap": "1.3.0",
|
||||
"react-dom": "16.13.1",
|
||||
"react-highlight": "0.12.0",
|
||||
"react-router": "5.2.0",
|
||||
"react-router-dom": "5.2.0",
|
||||
"prop-types": "15.7.2"
|
||||
"react-router-dom": "5.2.0"
|
||||
},
|
||||
"scripts": {
|
||||
"serve": "PORT=8083 react-scripts start",
|
||||
@@ -48,8 +48,8 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"license-check-and-add": "^3.0.4",
|
||||
"license-checker": "^25.0.1",
|
||||
"license-check-and-add": "3.0.4",
|
||||
"license-checker": "25.0.1",
|
||||
"react-scripts": "3.4.3"
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>skills</groupId>
|
||||
<artifactId>skills-client-integration</artifactId>
|
||||
<version>3.1.1-SNAPSHOT</version>
|
||||
<version>3.3.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>skills-int-client-react</artifactId>
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ const Navigation = () => {
|
||||
<Navbar.Collapse id="nav-collapse" is-nav="true">
|
||||
<Nav>
|
||||
<NavLink activeClassName="router-link-active" className="nav-link" to="/">Report Skill Events</NavLink>
|
||||
<NavLink activeClassName="router-link-active" className="nav-link" to="/showSkills">User Display</NavLink>
|
||||
<NavLink activeClassName="router-link-active" className="nav-link" to="/showSkills?isSummaryOnly=false&internalBackButton=false&themeName=Bright%20(default)&skillsVersion=">User Display</NavLink>
|
||||
</Nav>
|
||||
</Navbar.Collapse>
|
||||
<Button variant="primary">
|
||||
|
||||
@@ -32,6 +32,10 @@ const findTheme = (name) => {
|
||||
|
||||
const scrollToRef = (ref) => window.scrollTo(0, ref.current.offsetTop);
|
||||
|
||||
const handleRouteChanged = (newPath) => {
|
||||
document.querySelector('#skillsDisplayPath').innerHTML = `Skills Display Path: [${newPath}]`;
|
||||
};
|
||||
|
||||
function usePrevious(value){
|
||||
const ref = React.useRef();
|
||||
React.useEffect(() => {
|
||||
@@ -63,6 +67,15 @@ const ShowSkill = () => {
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
const [internalBackButton, setInternalBackButton] = React.useState(() => {
|
||||
const urlInternalBackButton = getUrlParam('internalBackButton');
|
||||
if ( urlInternalBackButton ) {
|
||||
return JSON.parse(urlInternalBackButton)
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
const [skillsVersion] = React.useState(() => {
|
||||
const skillsVersion = getUrlParam("skillsVersion");
|
||||
if(skillsVersion) {
|
||||
@@ -71,9 +84,10 @@ const ShowSkill = () => {
|
||||
return '';
|
||||
});
|
||||
|
||||
const [options, setOptions] = React.useState({ isSummaryOnly: isSummaryOnly, autoScrollStrategy: 'top-offset', scrollTopOffset: 110 });
|
||||
const [options, setOptions] = React.useState({ isSummaryOnly: isSummaryOnly, internalBackButton: internalBackButton, autoScrollStrategy: 'top-offset', scrollTopOffset: 110 });
|
||||
|
||||
const previousIsSummaryOnly = usePrevious(isSummaryOnly);
|
||||
const previousInternalBackButton = usePrevious(internalBackButton);
|
||||
const previousSelectedTheme = usePrevious(selectedTheme);
|
||||
const previousSkillsVersion = usePrevious(skillsVersion);
|
||||
|
||||
@@ -84,18 +98,23 @@ const ShowSkill = () => {
|
||||
params+=`${key}=${paramsObj[key]}&`;
|
||||
});
|
||||
params = params.replace(/&$/,'');
|
||||
history.push({pathname:history.location.pathname, search: params});
|
||||
history.replace({pathname:history.location.pathname, search: params});
|
||||
};
|
||||
|
||||
if (isSummaryOnly !== previousIsSummaryOnly || selectedTheme !== previousSelectedTheme) {
|
||||
saveUrlParam({'isSummaryOnly': isSummaryOnly, 'themeName': selectedTheme.name, 'skillsVersion': skillsVersion});
|
||||
if (isSummaryOnly !== previousIsSummaryOnly || internalBackButton !== previousInternalBackButton || selectedTheme !== previousSelectedTheme) {
|
||||
saveUrlParam({'isSummaryOnly': isSummaryOnly, 'internalBackButton': internalBackButton, 'themeName': selectedTheme.name, 'skillsVersion': skillsVersion});
|
||||
}
|
||||
|
||||
}, [isSummaryOnly, selectedTheme, skillsVersion, previousIsSummaryOnly, previousSelectedTheme, previousSkillsVersion, history]);
|
||||
}, [isSummaryOnly, internalBackButton, selectedTheme, skillsVersion, previousIsSummaryOnly, previousInternalBackButton, previousSelectedTheme, previousSkillsVersion, history]);
|
||||
|
||||
const sampleCodeRef = React.createRef();
|
||||
const skillsDisplayRef = React.createRef();
|
||||
const executeScroll = () => scrollToRef(sampleCodeRef);
|
||||
|
||||
const navigate = () => {
|
||||
skillsDisplayRef.current.navigate('/subjects/subj0');
|
||||
};
|
||||
|
||||
const style = {
|
||||
paddingTop: '55px'
|
||||
};
|
||||
@@ -121,12 +140,22 @@ const ShowSkill = () => {
|
||||
</DropdownButton>
|
||||
<Button variant={isSummaryOnly ? 'primary' : 'outline-primary'}
|
||||
onClick={() => { setIsSummaryOnly(!isSummaryOnly);
|
||||
setOptions({isSummaryOnly:!isSummaryOnly,autoScrollStrategy: 'top-offset', scrollTopOffset: 110}) }}>Summary Only</Button>
|
||||
setOptions({isSummaryOnly:!isSummaryOnly,internalBackButton:internalBackButton,autoScrollStrategy: 'top-offset', scrollTopOffset: 110}) }}>Summary Only</Button>
|
||||
|
||||
<Button variant={internalBackButton ? 'primary' : 'outline-primary'}
|
||||
onClick={() => { setInternalBackButton(!internalBackButton);
|
||||
setOptions({isSummaryOnly:isSummaryOnly,internalBackButton:!internalBackButton,autoScrollStrategy: 'top-offset', scrollTopOffset: 110}) }}>Internal Back Button</Button>
|
||||
|
||||
<Button variant={internalBackButton ? 'primary' : 'outline-primary'}
|
||||
onClick={navigate} data-cy="navigateButton">Navigate</Button>
|
||||
|
||||
<Button variant="link" onClick={executeScroll}>Show Source</Button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span id="skillsDisplayPath" data-cy="skillsDisplayPath"></span>
|
||||
</div>
|
||||
<div className="border rounded">
|
||||
<SkillsDisplay options={options} theme={selectedTheme.theme} version={skillsVersion}/>
|
||||
<SkillsDisplay ref={skillsDisplayRef} options={options} theme={selectedTheme.theme} version={skillsVersion} handleRouteChanged={handleRouteChanged}/>
|
||||
</div>
|
||||
|
||||
<div ref={sampleCodeRef}>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "skills-int-client-vue",
|
||||
"version": "3.1.1",
|
||||
"version": "3.3.1",
|
||||
"license": "Apache-2.0",
|
||||
"description": "SkillTree Client Lib Integration Tests - Vue",
|
||||
"scripts": {
|
||||
@@ -14,11 +14,11 @@
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@skilltree/skills-client-vue": "3.1.1",
|
||||
"@skilltree/skills-client-vue": "3.3.1",
|
||||
"axios": "0.21.1",
|
||||
"bootstrap": "4.5.3",
|
||||
"bootstrap-vue": "2.17.3",
|
||||
"core-js": "^3.2.1",
|
||||
"core-js": "3.2.1",
|
||||
"highlightjs": "9.16.2",
|
||||
"js-beautify": "1.13.0",
|
||||
"vue": "2.6.12",
|
||||
@@ -29,11 +29,11 @@
|
||||
"vuex": "3.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "4.5.7",
|
||||
"@vue/cli-plugin-eslint": "4.5.7",
|
||||
"@vue/cli-service": "4.5.7",
|
||||
"@vue/cli-plugin-babel": "4.5.13",
|
||||
"@vue/cli-plugin-eslint": "4.5.13",
|
||||
"@vue/cli-service": "4.5.13",
|
||||
"babel-eslint": "10.1.0",
|
||||
"eslint": "7.11.0",
|
||||
"eslint": "6.2.0",
|
||||
"eslint-plugin-vue": "7.0.1",
|
||||
"license-check-and-add": "3.0.4",
|
||||
"license-checker": "25.0.1",
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>skills</groupId>
|
||||
<artifactId>skills-client-integration</artifactId>
|
||||
<version>3.1.1-SNAPSHOT</version>
|
||||
<version>3.3.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>skills-int-client-vue</artifactId>
|
||||
|
||||
|
||||
+5
-2
@@ -107,8 +107,11 @@ limitations under the License.
|
||||
},
|
||||
data() {
|
||||
const existingAuthenticator = SkillsConfiguration.getAuthenticator();
|
||||
const authenticator = existingAuthenticator.replace(/\/users\/user1/, '/users/proj2/user2')
|
||||
const projectId = "proj2"
|
||||
const projectId = this.$store.state.proxiedProjectId;
|
||||
const proxiedUser = this.$store.state.proxiedUserId;
|
||||
const replaceRegex = /users\/[^/]+\/token/;
|
||||
|
||||
const authenticator = existingAuthenticator.replace(replaceRegex, `users/${projectId}/${proxiedUser}/token`);
|
||||
return {
|
||||
token: '',
|
||||
version: this.$route.query.skillsVersion ? JSON.parse(this.$route.query.skillsVersion) : 1000,
|
||||
|
||||
+40
-3
@@ -34,6 +34,13 @@ limitations under the License.
|
||||
switch>
|
||||
Summary Only
|
||||
</b-form-checkbox>
|
||||
<b-form-checkbox class="d-inline-block ml-3 border-right pr-2"
|
||||
v-model="displayOptions.internalBackButton"
|
||||
switch>
|
||||
Internal Back Button
|
||||
</b-form-checkbox>
|
||||
|
||||
<b-button variant="outline-primary" data-cy="navigateButton" @click="navigate">Navigate</b-button>
|
||||
|
||||
<b-link
|
||||
class="ml-2"
|
||||
@@ -41,12 +48,17 @@ limitations under the License.
|
||||
v-scroll-to="'#sample-code'"
|
||||
@click="showSampleCode = true">Show Source</b-link>
|
||||
</div>
|
||||
<div>
|
||||
<span id="skillsDisplayPath" data-cy="skillsDisplayPath">Skills Display Path: [{{skillsDisplayPath}}]</span>
|
||||
</div>
|
||||
<div class="border rounded">
|
||||
<skills-display
|
||||
<skills-display ref="skillsDisplayRef"
|
||||
v-if="selectedTheme"
|
||||
:options="displayOptions"
|
||||
:version="version"
|
||||
:theme="selectedTheme.theme"/>
|
||||
:theme="selectedTheme.theme"
|
||||
@route-changed="skillsDisplayRouteChanged"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="selectedTheme && showSampleCode"
|
||||
@@ -112,8 +124,10 @@ limitations under the License.
|
||||
displayOptions: {
|
||||
autoScrollStrategy: 'top-of-page',
|
||||
isSummaryOnly: this.$route.query.isSummaryOnly ? JSON.parse(this.$route.query.isSummaryOnly) : false,
|
||||
internalBackButton: this.$route.query.internalBackButton ? JSON.parse(this.$route.query.internalBackButton) : false,
|
||||
},
|
||||
showSampleCode: false,
|
||||
skillsDisplayPath: '',
|
||||
themes: this.getThemes(),
|
||||
selectedTheme: this.$route.query.themeName ? this.findTheme(this.$route.query.themeName) : this.getThemes()[0],
|
||||
};
|
||||
@@ -138,6 +152,9 @@ limitations under the License.
|
||||
'displayOptions.isSummaryOnly'() {
|
||||
this.setIsSummaryOnlyUrlParam();
|
||||
},
|
||||
'displayOptions.internalBackButton'() {
|
||||
this.setInternalBackButtonUrlParam();
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getThemes() {
|
||||
@@ -159,7 +176,19 @@ limitations under the License.
|
||||
this.$router.push({
|
||||
query: {
|
||||
themeName: this.selectedTheme.name,
|
||||
isSummaryOnly: this.displayOptions.isSummaryOnly
|
||||
isSummaryOnly: this.displayOptions.isSummaryOnly,
|
||||
internalBackButton: this.displayOptions.internalBackButton,
|
||||
}
|
||||
});
|
||||
this.refreshPage();
|
||||
},
|
||||
|
||||
setInternalBackButtonUrlParam() {
|
||||
this.$router.push({
|
||||
query: {
|
||||
themeName: this.selectedTheme.name,
|
||||
isSummaryOnly: this.displayOptions.isSummaryOnly,
|
||||
internalBackButton: this.displayOptions.internalBackButton,
|
||||
}
|
||||
});
|
||||
this.refreshPage();
|
||||
@@ -169,6 +198,14 @@ limitations under the License.
|
||||
return this.getThemes().find((item) => item.name === name);
|
||||
|
||||
},
|
||||
|
||||
skillsDisplayRouteChanged(newPath) {
|
||||
this.skillsDisplayPath = newPath;
|
||||
},
|
||||
|
||||
navigate() {
|
||||
this.$refs.skillsDisplayRef.navigate('/subjects/subj0');
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -21,6 +21,7 @@ import router from './router';
|
||||
import axios from 'axios';
|
||||
import { SkillsDirective, SkillsConfiguration, Logger } from '@skilltree/skills-client-vue';
|
||||
import VueHighlightJS from 'vue-highlightjs'
|
||||
import store from './store';
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
@@ -33,15 +34,23 @@ Vue.use(VueHighlightJS)
|
||||
axios.get("/api/config")
|
||||
.then((result) => {
|
||||
SkillsConfiguration.configure(result.data);
|
||||
SkillsConfiguration.configure(result.data);
|
||||
SkillsConfiguration.configure(result.data);
|
||||
store.commit('projectId', result.data.projectId);
|
||||
store.commit('authenticator', result.data.authenticator);
|
||||
store.commit('proxiedUserId', result.data.proxiedUserId);
|
||||
store.commit('proxiedProjectId', result.data.proxiedProjectId);
|
||||
})
|
||||
.then(() => {
|
||||
SkillsConfiguration.afterConfigure().then(() => {
|
||||
console.log('afterConfigure() - this should only be called once!');
|
||||
if (window.Cypress) {
|
||||
window.skillsLogger = Logger;
|
||||
}
|
||||
new Vue({
|
||||
render: h => h(App),
|
||||
router,
|
||||
store,
|
||||
}).$mount('#app');
|
||||
})
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2020 SkillTree
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import Vue from 'vue';
|
||||
import Vuex from 'vuex';
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
||||
export default new Vuex.Store({
|
||||
state: {
|
||||
projectId: '',
|
||||
authenticator: '',
|
||||
proxiedUserId: '',
|
||||
proxiedProjectId: '',
|
||||
},
|
||||
mutations: {
|
||||
projectId(state, projectId) {
|
||||
state.projectId = projectId;
|
||||
},
|
||||
authenticator(state, projectSearch) {
|
||||
state.projectSearch = projectSearch;
|
||||
},
|
||||
proxiedUserId(state, proxiedUserId) {
|
||||
state.proxiedUserId = proxiedUserId;
|
||||
},
|
||||
proxiedProjectId(state, proxiedProjectId) {
|
||||
state.proxiedProjectId = proxiedProjectId;
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -331,4 +331,139 @@ context('Angular Tests', () => {
|
||||
cy.wait('@reportClientVersion')
|
||||
})
|
||||
|
||||
if (Utils.skillsServiceVersionLaterThan('1.5.0')) {
|
||||
it('internal back button when when returning from an external page - multiple layers deep', () => {
|
||||
cy.createDefaultTinyProject()
|
||||
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
|
||||
|
||||
// visit client display
|
||||
cy.visit('/angular/showSkills?internalBackButton=true');
|
||||
|
||||
cy.clientDisplay().find('[data-cy=back]').should('not.exist');
|
||||
cy.clientDisplay().contains('User Skills');
|
||||
|
||||
// to subject page
|
||||
cy.cdClickSubj(0, 'Subject 0');
|
||||
|
||||
// navigate to Rank Overview that contains the back button
|
||||
cy.clientDisplay().find('[data-cy=myRank]').click();
|
||||
cy.clientDisplay().contains('Rank Overview');
|
||||
cy.clientDisplay().find('[data-cy=back]').should('exist');
|
||||
|
||||
// click the back button and verify that we are still in the
|
||||
// client display (Subject page)
|
||||
cy.cdBack('Subject 0');
|
||||
|
||||
// then back one more time and we should be back on the client display home page
|
||||
cy.cdBack('User Skills');
|
||||
});
|
||||
|
||||
it('browser back button works correctly when internal back button is not present', () => {
|
||||
cy.createDefaultTinyProject()
|
||||
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
|
||||
|
||||
// visit client display
|
||||
cy.visit('/angular/showSkills?internalBackButton=false');
|
||||
|
||||
cy.clientDisplay().find('[data-cy=back]').should('not.exist');
|
||||
cy.clientDisplay().contains('User Skills');
|
||||
|
||||
// to subject page
|
||||
cy.cdClickSubj(0, 'Subject 0');
|
||||
|
||||
// navigate to Rank Overview and that it does NOT contains the internal back button
|
||||
cy.clientDisplay().find('[data-cy=myRank]').click();
|
||||
cy.clientDisplay().contains('Rank Overview');
|
||||
cy.clientDisplay().find('[data-cy=back]').should('not.exist');
|
||||
|
||||
// click the browser back button and verify that we are still in the
|
||||
// client display (Subject page)
|
||||
cy.go('back') // browser back button
|
||||
cy.clientDisplay().contains('Subject 0');
|
||||
|
||||
// then back one more time and we should be back on the client display home page
|
||||
cy.go('back') // browser back button
|
||||
cy.clientDisplay().contains('User Skills');
|
||||
});
|
||||
|
||||
it('deep link and reload', () => {
|
||||
cy.createDefaultTinyProject()
|
||||
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
|
||||
|
||||
// navigate to Rank Overview via direct link
|
||||
cy.visit('/angular/showSkills?skillsClientDisplayPath=%2Frank');
|
||||
cy.clientDisplay().contains('Rank Overview');
|
||||
|
||||
// reload and confirm we are still on Rank Overview page
|
||||
cy.reload();
|
||||
cy.clientDisplay().contains('Rank Overview');
|
||||
});
|
||||
|
||||
it('back button after reload', () => {
|
||||
cy.createDefaultTinyProject()
|
||||
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
|
||||
|
||||
// visit client display
|
||||
cy.visit('/angular/showSkills?internalBackButton=false');
|
||||
|
||||
cy.clientDisplay().find('[data-cy=back]').should('not.exist');
|
||||
cy.clientDisplay().contains('User Skills');
|
||||
|
||||
// to subject page
|
||||
cy.cdClickSubj(0, 'Subject 0');
|
||||
|
||||
// navigate to Rank Overview and that it does NOT contains the internal back button
|
||||
cy.clientDisplay().find('[data-cy=myRank]').click();
|
||||
cy.clientDisplay().contains('Rank Overview');
|
||||
cy.clientDisplay().find('[data-cy=back]').should('not.exist');
|
||||
|
||||
cy.reload();
|
||||
cy.clientDisplay().contains('Rank Overview');
|
||||
|
||||
// click the browser back button and verify that we are still in the
|
||||
// client display (Subject page)
|
||||
cy.go('back') // browser back button
|
||||
cy.clientDisplay().contains('Subject 0');
|
||||
|
||||
// then back one more time and we should be back on the client display home page
|
||||
cy.go('back') // browser back button
|
||||
cy.clientDisplay().contains('User Skills');
|
||||
});
|
||||
|
||||
it('route change is passed to the client app', () => {
|
||||
cy.createDefaultTinyProject()
|
||||
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
|
||||
|
||||
// visit client display
|
||||
cy.visit('/angular/showSkills?internalBackButton=false');
|
||||
cy.get('[data-cy=skillsDisplayPath]').contains('Skills Display Path: [/]');
|
||||
|
||||
// to subject page
|
||||
cy.cdClickSubj(0, 'Subject 0');
|
||||
cy.get('[data-cy=skillsDisplayPath]').contains('Skills Display Path: [/subjects/subj0]');
|
||||
|
||||
// navigate to Rank Overview and that it does NOT contains the internal back button
|
||||
cy.clientDisplay().find('[data-cy=myRank]').click();
|
||||
cy.clientDisplay().contains('Rank Overview');
|
||||
|
||||
cy.get('[data-cy=skillsDisplayPath]').contains('Skills Display Path: [/subjects/subj0/rank]');
|
||||
});
|
||||
}
|
||||
|
||||
if (Utils.skillsServiceVersionLaterThan('1.6.0')) {
|
||||
it('navigate skills-display programatically', () => {
|
||||
cy.createDefaultTinyProject()
|
||||
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
|
||||
|
||||
// visit client display
|
||||
cy.visit('/angular/showSkills?internalBackButton=false');
|
||||
cy.get('[data-cy=skillsDisplayPath]').contains('Skills Display Path: [/]');
|
||||
cy.clientDisplay().contains('User Skills');
|
||||
|
||||
// to subject page
|
||||
cy.get('[data-cy=navigateButton]').click();
|
||||
cy.get('[data-cy=skillsDisplayPath]').contains('Skills Display Path: [/subjects/subj0]');
|
||||
});
|
||||
}
|
||||
|
||||
})
|
||||
@@ -287,4 +287,163 @@ context("Native JS Tests", () => {
|
||||
cy.contains('Level 1')
|
||||
})
|
||||
|
||||
if (Utils.skillsServiceVersionLaterThan('1.5.0')) {
|
||||
it('internal back button when when returning from an external page', () => {
|
||||
cy.createDefaultTinyProject()
|
||||
cy.intercept(Cypress.env('tokenUrl')).as('getToken')
|
||||
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
|
||||
|
||||
// visit client display
|
||||
cy.visit('/native/clientDisplay.html?internalBackButton=true');
|
||||
// cy.wait('@getToken')
|
||||
|
||||
cy.clientDisplay(true).find('[data-cy=back]').should('not.exist');
|
||||
cy.clientDisplay().contains('User Skills');
|
||||
|
||||
// navigate to Rank Overview that contains the back button
|
||||
cy.clientDisplay().find('[data-cy=myRank]').click();
|
||||
cy.clientDisplay().contains('Rank Overview');
|
||||
cy.clientDisplay().find('[data-cy=back]').should('exist');
|
||||
|
||||
// now visit the "Report Skills" (external) page
|
||||
cy.get('[data-cy=reportSkillsLink]').click()
|
||||
cy.contains('Report Skills Examples');
|
||||
|
||||
// switch back to the the client display
|
||||
cy.get('[data-cy=userDisplayLink]').click()
|
||||
});
|
||||
|
||||
it('internal back button when when returning from an external page - multiple layers deep', () => {
|
||||
cy.createDefaultTinyProject()
|
||||
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
|
||||
|
||||
// visit client display
|
||||
cy.visit('/native/clientDisplay.html?internalBackButton=true');
|
||||
|
||||
cy.clientDisplay().find('[data-cy=back]').should('not.exist');
|
||||
cy.clientDisplay().contains('User Skills');
|
||||
|
||||
// to subject page
|
||||
cy.cdClickSubj(0, 'Subject 0');
|
||||
|
||||
// navigate to Rank Overview that contains the back button
|
||||
cy.clientDisplay().find('[data-cy=myRank]').click();
|
||||
cy.clientDisplay().contains('Rank Overview');
|
||||
cy.clientDisplay().find('[data-cy=back]').should('exist');
|
||||
|
||||
// click the back button and verify that we are still in the
|
||||
// client display (Subject page)
|
||||
cy.cdBack('Subject 0');
|
||||
|
||||
// then back one more time and we should be back on the client display home page
|
||||
cy.cdBack('User Skills');
|
||||
});
|
||||
|
||||
it('browser back button works correctly when internal back button is not present', () => {
|
||||
cy.createDefaultTinyProject()
|
||||
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
|
||||
|
||||
// visit client display
|
||||
cy.visit("/native/clientDisplay.html?internalBackButton=false");
|
||||
|
||||
cy.clientDisplay().find('[data-cy=back]').should('not.exist');
|
||||
cy.clientDisplay().contains('User Skills');
|
||||
|
||||
// to subject page
|
||||
cy.cdClickSubj(0, 'Subject 0');
|
||||
|
||||
// navigate to Rank Overview and that it does NOT contains the internal back button
|
||||
cy.clientDisplay().find('[data-cy=myRank]').click();
|
||||
cy.clientDisplay().contains('Rank Overview');
|
||||
cy.clientDisplay().find('[data-cy=back]').should('not.exist');
|
||||
|
||||
// click the browser back button and verify that we are still in the
|
||||
// client display (Subject page)
|
||||
cy.go('back') // browser back button
|
||||
cy.clientDisplay().contains('Subject 0');
|
||||
|
||||
// then back one more time and we should be back on the client display home page
|
||||
cy.go('back') // browser back button
|
||||
cy.clientDisplay().contains('User Skills');
|
||||
});
|
||||
|
||||
it('deep link and reload', () => {
|
||||
cy.createDefaultTinyProject()
|
||||
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
|
||||
|
||||
// navigate to Rank Overview via direct link
|
||||
cy.visit('/native/clientDisplay.html?skillsClientDisplayPath=%2Frank#/showSkills');
|
||||
cy.clientDisplay().contains('Rank Overview');
|
||||
|
||||
// reload and confirm we are still on Rank Overview page
|
||||
cy.reload();
|
||||
cy.clientDisplay().contains('Rank Overview');
|
||||
});
|
||||
|
||||
it('back button after reload', () => {
|
||||
cy.createDefaultTinyProject()
|
||||
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
|
||||
|
||||
// visit client display
|
||||
cy.visit("/native/clientDisplay.html?internalBackButton=false");
|
||||
|
||||
cy.clientDisplay().find('[data-cy=back]').should('not.exist');
|
||||
cy.clientDisplay().contains('User Skills');
|
||||
|
||||
// to subject page
|
||||
cy.cdClickSubj(0, 'Subject 0');
|
||||
|
||||
// navigate to Rank Overview and that it does NOT contains the internal back button
|
||||
cy.clientDisplay().find('[data-cy=myRank]').click();
|
||||
cy.clientDisplay().contains('Rank Overview');
|
||||
cy.clientDisplay().find('[data-cy=back]').should('not.exist');
|
||||
|
||||
cy.reload();
|
||||
cy.clientDisplay().contains('Rank Overview');
|
||||
|
||||
// click the browser back button and verify that we are still in the
|
||||
// client display (Subject page)
|
||||
cy.go('back') // browser back button
|
||||
cy.clientDisplay().contains('Subject 0');
|
||||
|
||||
// then back one more time and we should be back on the client display home page
|
||||
cy.go('back') // browser back button
|
||||
cy.clientDisplay().contains('User Skills');
|
||||
});
|
||||
|
||||
it('route change is passed to the client app', () => {
|
||||
cy.createDefaultTinyProject()
|
||||
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
|
||||
|
||||
// visit client display
|
||||
cy.visit('/native/clientDisplay.html?internalBackButton=true');
|
||||
cy.get('[data-cy=skillsDisplayPath]').contains('Skills Display Path: [/]');
|
||||
|
||||
// to subject page
|
||||
cy.cdClickSubj(0, 'Subject 0');
|
||||
cy.get('[data-cy=skillsDisplayPath]').contains('Skills Display Path: [/subjects/subj0]');
|
||||
|
||||
// navigate to Rank Overview and that it does NOT contains the internal back button
|
||||
cy.clientDisplay().find('[data-cy=myRank]').click();
|
||||
cy.clientDisplay().contains('Rank Overview');
|
||||
|
||||
cy.get('[data-cy=skillsDisplayPath]').contains('Skills Display Path: [/subjects/subj0/rank]');
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
if (Utils.skillsServiceVersionLaterThan('1.6.0')) {
|
||||
it('navigate skills-display programatically', () => {
|
||||
cy.createDefaultTinyProject()
|
||||
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
|
||||
|
||||
// visit client display
|
||||
cy.visit('/native/clientDisplay.html?internalBackButton=true');
|
||||
cy.get('[data-cy=skillsDisplayPath]').contains('Skills Display Path: [/]');
|
||||
|
||||
// to subject page
|
||||
cy.get('[data-cy=navigateButton]').click();
|
||||
cy.get('[data-cy=skillsDisplayPath]').contains('Skills Display Path: [/subjects/subj0]');
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -256,4 +256,139 @@ context('React Tests', () => {
|
||||
cy.visit('/react/index.html#/')
|
||||
cy.wait('@reportClientVersion')
|
||||
})
|
||||
|
||||
if (Utils.skillsServiceVersionLaterThan('1.5.0')) {
|
||||
it('internal back button when when returning from an external page - multiple layers deep', () => {
|
||||
cy.createDefaultTinyProject()
|
||||
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
|
||||
|
||||
// visit client display
|
||||
cy.visit('/react/index.html#/showSkills?internalBackButton=true');
|
||||
|
||||
cy.clientDisplay().find('[data-cy=back]').should('not.exist');
|
||||
cy.clientDisplay().contains('User Skills');
|
||||
|
||||
// to subject page
|
||||
cy.cdClickSubj(0, 'Subject 0');
|
||||
|
||||
// navigate to Rank Overview that contains the back button
|
||||
cy.clientDisplay().find('[data-cy=myRank]').click();
|
||||
cy.clientDisplay().contains('Rank Overview');
|
||||
cy.clientDisplay().find('[data-cy=back]').should('exist');
|
||||
|
||||
// click the back button and verify that we are still in the
|
||||
// client display (Subject page)
|
||||
cy.cdBack('Subject 0');
|
||||
|
||||
// then back one more time and we should be back on the client display home page
|
||||
cy.cdBack('User Skills');
|
||||
});
|
||||
|
||||
it('browser back button works correctly when internal back button is not present', () => {
|
||||
cy.createDefaultTinyProject()
|
||||
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
|
||||
|
||||
// visit client display
|
||||
cy.visit('/react/index.html#/showSkills?internalBackButton=false');
|
||||
|
||||
cy.clientDisplay().find('[data-cy=back]').should('not.exist');
|
||||
cy.clientDisplay().contains('User Skills');
|
||||
|
||||
// to subject page
|
||||
cy.cdClickSubj(0, 'Subject 0');
|
||||
|
||||
// navigate to Rank Overview and that it does NOT contains the internal back button
|
||||
cy.clientDisplay().find('[data-cy=myRank]').click();
|
||||
cy.clientDisplay().contains('Rank Overview');
|
||||
cy.clientDisplay().find('[data-cy=back]').should('not.exist');
|
||||
|
||||
// click the browser back button and verify that we are still in the
|
||||
// client display (Subject page)
|
||||
cy.go('back') // browser back button
|
||||
cy.clientDisplay().contains('Subject 0');
|
||||
|
||||
// then back one more time and we should be back on the client display home page
|
||||
cy.go('back') // browser back button
|
||||
cy.clientDisplay().contains('User Skills');
|
||||
});
|
||||
|
||||
it('deep link and reload', () => {
|
||||
cy.createDefaultTinyProject()
|
||||
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
|
||||
|
||||
// navigate to Rank Overview via direct link
|
||||
cy.visit('/react/index.html?skillsClientDisplayPath=%2Frank#/showSkills');
|
||||
cy.clientDisplay().contains('Rank Overview');
|
||||
|
||||
// reload and confirm we are still on Rank Overview page
|
||||
cy.reload();
|
||||
cy.clientDisplay().contains('Rank Overview');
|
||||
});
|
||||
|
||||
it('back button after reload', () => {
|
||||
cy.createDefaultTinyProject()
|
||||
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
|
||||
|
||||
// visit client display
|
||||
cy.visit('/react/index.html#/showSkills?internalBackButton=false');
|
||||
|
||||
cy.clientDisplay().find('[data-cy=back]').should('not.exist');
|
||||
cy.clientDisplay().contains('User Skills');
|
||||
|
||||
// to subject page
|
||||
cy.cdClickSubj(0, 'Subject 0');
|
||||
|
||||
// navigate to Rank Overview and that it does NOT contains the internal back button
|
||||
cy.clientDisplay().find('[data-cy=myRank]').click();
|
||||
cy.clientDisplay().contains('Rank Overview');
|
||||
cy.clientDisplay().find('[data-cy=back]').should('not.exist');
|
||||
|
||||
cy.reload();
|
||||
cy.clientDisplay().contains('Rank Overview');
|
||||
|
||||
// click the browser back button and verify that we are still in the
|
||||
// client display (Subject page)
|
||||
cy.go('back') // browser back button
|
||||
cy.clientDisplay().contains('Subject 0');
|
||||
|
||||
// then back one more time and we should be back on the client display home page
|
||||
cy.go('back') // browser back button
|
||||
cy.clientDisplay().contains('User Skills');
|
||||
});
|
||||
|
||||
it('route change is passed to the client app', () => {
|
||||
cy.createDefaultTinyProject()
|
||||
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
|
||||
|
||||
// visit client display
|
||||
cy.visit('/react/index.html#/showSkills?internalBackButton=false');
|
||||
cy.get('[data-cy=skillsDisplayPath]').contains('Skills Display Path: [/]');
|
||||
|
||||
// to subject page
|
||||
cy.cdClickSubj(0, 'Subject 0');
|
||||
cy.get('[data-cy=skillsDisplayPath]').contains('Skills Display Path: [/subjects/subj0]');
|
||||
|
||||
// navigate to Rank Overview and that it does NOT contains the internal back button
|
||||
cy.clientDisplay().find('[data-cy=myRank]').click();
|
||||
cy.clientDisplay().contains('Rank Overview');
|
||||
|
||||
cy.get('[data-cy=skillsDisplayPath]').contains('Skills Display Path: [/subjects/subj0/rank]');
|
||||
});
|
||||
}
|
||||
|
||||
if (Utils.skillsServiceVersionLaterThan('1.6.0')) {
|
||||
it('navigate skills-display programatically', () => {
|
||||
cy.createDefaultTinyProject()
|
||||
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
|
||||
|
||||
// visit client display
|
||||
cy.visit('/react/index.html#/showSkills?internalBackButton=false');
|
||||
cy.get('[data-cy=skillsDisplayPath]').contains('Skills Display Path: [/]');
|
||||
cy.clientDisplay().contains('User Skills');
|
||||
|
||||
// to subject page
|
||||
cy.get('[data-cy=navigateButton]').click();
|
||||
cy.get('[data-cy=skillsDisplayPath]').contains('Skills Display Path: [/subjects/subj0]');
|
||||
});
|
||||
}
|
||||
})
|
||||
@@ -20,8 +20,10 @@ const homePage = '/vuejs#/'
|
||||
context('Vue Tests', () => {
|
||||
|
||||
const laterThan_1_4_0 = Utils.skillsServiceVersionLaterThan('1.4.0');
|
||||
const laterThan_1_5_0 = Utils.skillsServiceVersionLaterThan('1.5.0');
|
||||
const missingSkillErrorCode = laterThan_1_4_0 ? 404 : 400;
|
||||
const noThemeBackground = laterThan_1_4_0 ? 'rgba(0, 0, 0, 0)' : 'rgb(255, 255, 255)';
|
||||
const clientDisplayWithInternalBackButtonParams = laterThan_1_5_0 ? '?refreshPage=false&internalBackButton=true' : '?refreshPage=false';
|
||||
|
||||
it('level component should be reactive', () => {
|
||||
// cy.window().then(win => {
|
||||
@@ -410,7 +412,7 @@ context('Vue Tests', () => {
|
||||
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
|
||||
|
||||
// visit client display
|
||||
cy.visit('/vuejs#/showSkills?refreshPage=false')
|
||||
cy.visit('/vuejs#/showSkills'+clientDisplayWithInternalBackButtonParams)
|
||||
// cy.wait('@getToken')
|
||||
|
||||
cy.clientDisplay(true).find('[data-cy=back]').should('not.exist');
|
||||
@@ -436,7 +438,7 @@ context('Vue Tests', () => {
|
||||
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
|
||||
|
||||
// visit client display
|
||||
cy.visit('/vuejs#/showSkills?refreshPage=false')
|
||||
cy.visit('/vuejs#/showSkills'+clientDisplayWithInternalBackButtonParams)
|
||||
|
||||
cy.clientDisplay().find('[data-cy=back]').should('not.exist');
|
||||
cy.clientDisplay().contains('User Skills');
|
||||
@@ -467,5 +469,114 @@ context('Vue Tests', () => {
|
||||
});
|
||||
}
|
||||
|
||||
if (Utils.skillsServiceVersionLaterThan('1.5.0')) {
|
||||
it('browser back button works correctly when internal back button is not present', () => {
|
||||
cy.createDefaultTinyProject()
|
||||
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
|
||||
|
||||
// visit client display
|
||||
cy.visit('/vuejs#/showSkills?internalBackButton=false');
|
||||
|
||||
cy.clientDisplay().find('[data-cy=back]').should('not.exist');
|
||||
cy.clientDisplay().contains('User Skills');
|
||||
|
||||
// to subject page
|
||||
cy.cdClickSubj(0, 'Subject 0');
|
||||
|
||||
// navigate to Rank Overview and that it does NOT contains the internal back button
|
||||
cy.clientDisplay().find('[data-cy=myRank]').click();
|
||||
cy.clientDisplay().contains('Rank Overview');
|
||||
cy.clientDisplay().find('[data-cy=back]').should('not.exist');
|
||||
|
||||
// click the browser back button and verify that we are still in the
|
||||
// client display (Subject page)
|
||||
cy.go('back') // browser back button
|
||||
cy.clientDisplay().contains('Subject 0');
|
||||
|
||||
// then back one more time and we should be back on the client display home page
|
||||
cy.go('back') // browser back button
|
||||
cy.clientDisplay().contains('User Skills');
|
||||
});
|
||||
|
||||
it('deep link and reload', () => {
|
||||
cy.createDefaultTinyProject()
|
||||
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
|
||||
|
||||
// navigate to Rank Overview via direct link
|
||||
cy.visit('/vuejs/?skillsClientDisplayPath=%2Frank#/showSkills?refreshPage=true');
|
||||
cy.clientDisplay().contains('Rank Overview');
|
||||
|
||||
// reload and confirm we are still on Rank Overview page
|
||||
cy.reload();
|
||||
cy.clientDisplay().contains('Rank Overview');
|
||||
});
|
||||
|
||||
it('back button after reload', () => {
|
||||
cy.createDefaultTinyProject()
|
||||
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
|
||||
|
||||
// visit client display
|
||||
cy.visit('/vuejs#/showSkills?internalBackButton=false');
|
||||
|
||||
cy.clientDisplay().find('[data-cy=back]').should('not.exist');
|
||||
cy.clientDisplay().contains('User Skills');
|
||||
|
||||
// to subject page
|
||||
cy.cdClickSubj(0, 'Subject 0');
|
||||
|
||||
// navigate to Rank Overview and that it does NOT contains the internal back button
|
||||
cy.clientDisplay().find('[data-cy=myRank]').click();
|
||||
cy.clientDisplay().contains('Rank Overview');
|
||||
cy.clientDisplay().find('[data-cy=back]').should('not.exist');
|
||||
|
||||
cy.reload();
|
||||
cy.clientDisplay().contains('Rank Overview');
|
||||
|
||||
// click the browser back button and verify that we are still in the
|
||||
// client display (Subject page)
|
||||
cy.go('back') // browser back button
|
||||
cy.clientDisplay().contains('Subject 0');
|
||||
|
||||
// then back one more time and we should be back on the client display home page
|
||||
cy.go('back') // browser back button
|
||||
cy.clientDisplay().contains('User Skills');
|
||||
});
|
||||
|
||||
it('route change is passed to the client app', () => {
|
||||
cy.createDefaultTinyProject()
|
||||
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
|
||||
|
||||
// visit client display
|
||||
cy.visit('/vuejs#/showSkills?internalBackButton=false');
|
||||
cy.get('[data-cy=skillsDisplayPath]').contains('Skills Display Path: [/]');
|
||||
|
||||
// to subject page
|
||||
cy.cdClickSubj(0, 'Subject 0');
|
||||
cy.get('[data-cy=skillsDisplayPath]').contains('Skills Display Path: [/subjects/subj0]');
|
||||
|
||||
// navigate to Rank Overview and that it does NOT contains the internal back button
|
||||
cy.clientDisplay().find('[data-cy=myRank]').click();
|
||||
cy.clientDisplay().contains('Rank Overview');
|
||||
|
||||
cy.get('[data-cy=skillsDisplayPath]').contains('Skills Display Path: [/subjects/subj0/rank]');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if (Utils.skillsServiceVersionLaterThan('1.6.0')) {
|
||||
it('navigate skills-display programatically', () => {
|
||||
cy.createDefaultTinyProject()
|
||||
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
|
||||
|
||||
// visit client display
|
||||
cy.visit('/vuejs#/showSkills?internalBackButton=false');
|
||||
cy.get('[data-cy=skillsDisplayPath]').contains('Skills Display Path: [/]');
|
||||
cy.clientDisplay().contains('User Skills');
|
||||
|
||||
// to subject page
|
||||
cy.get('[data-cy=navigateButton]').click();
|
||||
cy.get('[data-cy=skillsDisplayPath]').contains('Skills Display Path: [/subjects/subj0]');
|
||||
});
|
||||
}
|
||||
|
||||
})
|
||||
@@ -44,7 +44,7 @@
|
||||
"dev:removeNpmLinks": "mvn --batch-mode clean package && java -cp target/skills-int-e2e-test-*.jar -Dloader.main=skills.RemoveNpmLinks org.springframework.boot.loader.PropertiesLauncher"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cypress": "6.2.0",
|
||||
"cypress": "6.6.0",
|
||||
"license-check-and-add": "3.0.4",
|
||||
"license-checker": "25.0.1",
|
||||
"semver": "7.3.2",
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>skills</groupId>
|
||||
<artifactId>skills-client-integration</artifactId>
|
||||
<version>3.1.1-SNAPSHOT</version>
|
||||
<version>3.3.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>skills-int-e2e-test</artifactId>
|
||||
<description>End to End tests for Skills client libs using skills-int-service app</description>
|
||||
|
||||
+2
@@ -16,4 +16,6 @@ skills.service.password=password
|
||||
skills.service.serviceUrl=http://localhost:8080
|
||||
skills.service.projectId=proj1
|
||||
skills.service.authenticator=http://localhost:8090/api/users/user1/token
|
||||
skills.service.proxiedProjectId=proj2
|
||||
skills.service.proxiedUserId=user2
|
||||
logging.pattern.console=EXAMPLES-APP %clr(%d{yy-MM-dd E HH:mm:ss.SSS}){blue} %clr(%-5p) %clr(${PID}){faint} %clr(---){faint} %clr([%8.15t]){cyan} %clr(%-40.40logger{0}){blue} %clr(:){red} %clr(%m){faint}%n
|
||||
+1
-1
@@ -50,7 +50,7 @@ class NpmProjBuilder {
|
||||
new NpmProj(name: "skills-int-client-js", doOthersLinkToMe: false, initialVersion: '2.0.0'),
|
||||
new NpmProj(name: "skills-int-client-vue", doOthersLinkToMe: false, initialVersion: '2.0.0'),
|
||||
new NpmProj(name: "skills-int-client-react", doOthersLinkToMe: false, reactApp: true, initialVersion: '2.0.0'),
|
||||
new NpmProj(name: "skills-int-client-ng", doOthersLinkToMe: false, initialVersion: '3.0.0'),
|
||||
new NpmProj(name: "skills-int-client-ng", doOthersLinkToMe: false, angularApp: true, initialVersion: '3.0.0'),
|
||||
]
|
||||
|
||||
private void assertExist() {
|
||||
|
||||
+6
-3
@@ -63,7 +63,7 @@ class SetupNpmLinks {
|
||||
projs.each { NpmProj npmProj ->
|
||||
if (!npmProj.isAngularModule()) {
|
||||
// angular modules were installed within createLinks()
|
||||
npmProj.exec("npm install")
|
||||
npmProj.exec("npm install --force")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -104,7 +104,10 @@ class SetupNpmLinks {
|
||||
titlePrinter.printTitle("link")
|
||||
projs.findAll({ it.hasLinksToOtherProjects }).each { NpmProj npmProj ->
|
||||
npmProj.skillsDependenciesFromPackageJson.each {
|
||||
titlePrinter.printSubTitle("Linking module [${it}]")
|
||||
titlePrinter.printSubTitle("Linking module [${it}] in project [${npmProj}]")
|
||||
if (npmProj.getAngularApp() || npmProj.getAngularModule()) {
|
||||
npmProj.exec("npm install --force")
|
||||
}
|
||||
npmProj.exec("npm link ${it}".toString())
|
||||
if (npmProj.getAngularModule()) {
|
||||
File origLoc = npmProj.loc
|
||||
@@ -133,7 +136,7 @@ class SetupNpmLinks {
|
||||
File origLoc
|
||||
if (it.isAngularModule()) {
|
||||
titlePrinter.printTitle("install and build angular module")
|
||||
it.exec("npm install")
|
||||
it.exec("npm install --force")
|
||||
it.exec("npm run build")
|
||||
origLoc = it.loc;
|
||||
it.loc = it.getAngularModuleLinkDir(true)
|
||||
|
||||
+2
-1
@@ -85,7 +85,8 @@ class TestDashboardBackwardCompat {
|
||||
Resource found = resources.find { it.filename.endsWith(patchFile) }
|
||||
if (found) {
|
||||
FileUtils.copyToFile(found.inputStream, new File(workDir, patchFile))
|
||||
new ProcessRunner(loc: workDir).run("git apply ${patchFile}")
|
||||
String result = new ProcessRunner(loc: workDir).run("git apply --verbose ${patchFile}").sout
|
||||
log.info(result)
|
||||
} else{
|
||||
log.info("Patch file [${patchFile} not found, contining with applying any patches.")
|
||||
}
|
||||
|
||||
+31
-11
@@ -1,3 +1,23 @@
|
||||
diff --git a/skills-client-integration/skills-int-client-ng/package.json b/skills-client-integration/skills-int-client-ng/package.json
|
||||
index ed5628a..d21af9c 100644
|
||||
--- a/skills-client-integration/skills-int-client-ng/package.json
|
||||
+++ b/skills-client-integration/skills-int-client-ng/package.json
|
||||
@@ -34,7 +34,7 @@
|
||||
"bootstrap": "4.5.0",
|
||||
"jquery": "3.5.1",
|
||||
"js-beautify": "1.10.2",
|
||||
- "ngx-highlightjs": "4.1.0-beta",
|
||||
+ "ngx-highlightjs": "4.1.3",
|
||||
"rxjs": "6.5.5",
|
||||
"tslib": "2.0.0",
|
||||
"zone.js": "0.10.3"
|
||||
@@ -61,4 +61,4 @@
|
||||
"tslint": "6.1.0",
|
||||
"typescript": "3.9.5"
|
||||
}
|
||||
-}
|
||||
\ No newline at end of file
|
||||
+}
|
||||
diff --git a/skills-client-integration/skills-int-e2e-test/cypress.json b/skills-client-integration/skills-int-e2e-test/cypress.json
|
||||
index 52f9451..a87626c 100644
|
||||
--- a/skills-client-integration/skills-int-e2e-test/cypress.json
|
||||
@@ -44,7 +64,7 @@ index cbe8938..5aea457 100644
|
||||
+ .should('have.css', 'background-color').and('equal', 'rgba(0, 0, 0, 0)');
|
||||
});
|
||||
})
|
||||
|
||||
|
||||
@@ -263,7 +263,7 @@ context('Angular Tests', () => {
|
||||
// verify that there is no background set
|
||||
// cypress always validates against rgb
|
||||
@@ -53,7 +73,7 @@ index cbe8938..5aea457 100644
|
||||
+ .should('have.css', 'background-color').and('equal', 'rgba(0, 0, 0, 0)');
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
diff --git a/skills-client-integration/skills-int-e2e-test/cypress/integration/js.specs.js b/skills-client-integration/skills-int-e2e-test/cypress/integration/js.specs.js
|
||||
index af68cba..95aa71e 100644
|
||||
--- a/skills-client-integration/skills-int-e2e-test/cypress/integration/js.specs.js
|
||||
@@ -66,7 +86,7 @@ index af68cba..95aa71e 100644
|
||||
+ .and("equal", "rgba(0, 0, 0, 0)");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -142,7 +142,7 @@ context("Native JS Tests", () => {
|
||||
cy.wrap(body)
|
||||
.find(".skills-page-title-text-color")
|
||||
@@ -75,7 +95,7 @@ index af68cba..95aa71e 100644
|
||||
+ .and("equal", "rgba(0, 0, 0, 0)");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -169,7 +169,7 @@ context("Native JS Tests", () => {
|
||||
cy.wrap(body)
|
||||
.find(".skills-page-title-text-color")
|
||||
@@ -84,7 +104,7 @@ index af68cba..95aa71e 100644
|
||||
+ .and("equal", "rgba(0, 0, 0, 0)");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
diff --git a/skills-client-integration/skills-int-e2e-test/cypress/integration/react.specs.js b/skills-client-integration/skills-int-e2e-test/cypress/integration/react.specs.js
|
||||
index d0d7a1d..3ca2982 100644
|
||||
--- a/skills-client-integration/skills-int-e2e-test/cypress/integration/react.specs.js
|
||||
@@ -97,7 +117,7 @@ index d0d7a1d..3ca2982 100644
|
||||
+ .should('have.css', 'background-color').and('equal', 'rgba(0, 0, 0, 0)');
|
||||
});
|
||||
})
|
||||
|
||||
|
||||
@@ -172,7 +172,7 @@ context('React Tests', () => {
|
||||
// verify that there is no background set
|
||||
// cypress always validates against rgb
|
||||
@@ -106,7 +126,7 @@ index d0d7a1d..3ca2982 100644
|
||||
+ .should('have.css', 'background-color').and('equal', 'rgba(0, 0, 0, 0)');
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
diff --git a/skills-client-integration/skills-int-e2e-test/cypress/integration/vue.specs.js b/skills-client-integration/skills-int-e2e-test/cypress/integration/vue.specs.js
|
||||
index b010108..95e7b93 100644
|
||||
--- a/skills-client-integration/skills-int-e2e-test/cypress/integration/vue.specs.js
|
||||
@@ -137,7 +157,7 @@ index b010108..95e7b93 100644
|
||||
+ .should('have.css', 'background-color').and('equal', 'rgba(0, 0, 0, 0)');
|
||||
});
|
||||
})
|
||||
|
||||
|
||||
@@ -259,7 +259,7 @@ context('Vue Tests', () => {
|
||||
// verify that there is no background set
|
||||
// cypress always validates against rgb
|
||||
@@ -146,18 +166,18 @@ index b010108..95e7b93 100644
|
||||
+ .should('have.css', 'background-color').and('equal', 'rgba(0, 0, 0, 0)');
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
diff --git a/skills-client-integration/skills-int-e2e-test/cypress/support/commands.js b/skills-client-integration/skills-int-e2e-test/cypress/support/commands.js
|
||||
index 6a65d80..689aa4f 100644
|
||||
--- a/skills-client-integration/skills-int-e2e-test/cypress/support/commands.js
|
||||
+++ b/skills-client-integration/skills-int-e2e-test/cypress/support/commands.js
|
||||
@@ -171,7 +171,7 @@ Cypress.Commands.add("cdClickSubj", (body, subjIndex, expectedTitle) => {
|
||||
|
||||
|
||||
Cypress.Commands.add("cdBack", (body, expectedTitle = 'User Skills') => {
|
||||
cy.wrap(body).find('[data-cy=back]').click()
|
||||
- cy.wrap(body).find('h2').contains(expectedTitle);
|
||||
+ cy.wrap(body).contains(expectedTitle);
|
||||
|
||||
|
||||
// back button should not exist on the home page, whose title is the default value
|
||||
if (expectedTitle === 'User Skills'){
|
||||
diff --git a/skills-client-integration/skills-int-e2e-test/package.json b/skills-client-integration/skills-int-e2e-test/package.json
|
||||
|
||||
+33
-13
@@ -1,3 +1,23 @@
|
||||
diff --git a/skills-client-integration/skills-int-client-ng/package.json b/skills-client-integration/skills-int-client-ng/package.json
|
||||
index 2b35e51..04e41c8 100644
|
||||
--- a/skills-client-integration/skills-int-client-ng/package.json
|
||||
+++ b/skills-client-integration/skills-int-client-ng/package.json
|
||||
@@ -34,7 +34,7 @@
|
||||
"bootstrap": "4.5.0",
|
||||
"jquery": "3.5.1",
|
||||
"js-beautify": "1.10.2",
|
||||
- "ngx-highlightjs": "4.1.0-beta",
|
||||
+ "ngx-highlightjs": "4.1.3",
|
||||
"rxjs": "6.5.5",
|
||||
"tslib": "2.0.0",
|
||||
"zone.js": "0.10.3"
|
||||
@@ -61,4 +61,4 @@
|
||||
"tslint": "6.1.0",
|
||||
"typescript": "3.9.5"
|
||||
}
|
||||
-}
|
||||
\ No newline at end of file
|
||||
+}
|
||||
diff --git a/skills-client-integration/skills-int-e2e-test/cypress.json b/skills-client-integration/skills-int-e2e-test/cypress.json
|
||||
index 52f9451..a87626c 100644
|
||||
--- a/skills-client-integration/skills-int-e2e-test/cypress.json
|
||||
@@ -43,7 +63,7 @@ index 4c944c8..b17b00d 100644
|
||||
- .should('have.css', 'background-color').and('equal', 'rgb(255, 255, 255)');
|
||||
+ .should('have.css', 'background-color').and('equal', 'rgba(0, 0, 0, 0)');
|
||||
})
|
||||
|
||||
|
||||
it('skill display - summary only', () => {
|
||||
@@ -257,7 +257,7 @@ context('Angular Tests', () => {
|
||||
// verify that there is no background set
|
||||
@@ -52,7 +72,7 @@ index 4c944c8..b17b00d 100644
|
||||
- .should('have.css', 'background-color').and('equal', 'rgb(255, 255, 255)');
|
||||
+ .should('have.css', 'background-color').and('equal', 'rgba(0, 0, 0, 0)');
|
||||
})
|
||||
|
||||
|
||||
it('skill display - theme', () => {
|
||||
diff --git a/skills-client-integration/skills-int-e2e-test/cypress/integration/js.specs.js b/skills-client-integration/skills-int-e2e-test/cypress/integration/js.specs.js
|
||||
index 4b4dd85..f3cb810 100644
|
||||
@@ -65,7 +85,7 @@ index 4b4dd85..f3cb810 100644
|
||||
- .should('have.css', 'background-color').and('equal', 'rgb(255, 255, 255)');
|
||||
+ .should('have.css', 'background-color').and('equal', 'rgba(0, 0, 0, 0)');
|
||||
});
|
||||
|
||||
|
||||
it("skill display - default options", () => {
|
||||
@@ -137,7 +137,7 @@ context("Native JS Tests", () => {
|
||||
// verify that there is no background set
|
||||
@@ -74,7 +94,7 @@ index 4b4dd85..f3cb810 100644
|
||||
- .should('have.css', 'background-color').and('equal', 'rgb(255, 255, 255)');
|
||||
+ .should('have.css', 'background-color').and('equal', 'rgba(0, 0, 0, 0)');
|
||||
});
|
||||
|
||||
|
||||
it("skill display - summary only", () => {
|
||||
@@ -159,7 +159,7 @@ context("Native JS Tests", () => {
|
||||
// verify that there is no background set
|
||||
@@ -83,7 +103,7 @@ index 4b4dd85..f3cb810 100644
|
||||
- .should('have.css', 'background-color').and('equal', 'rgb(255, 255, 255)');
|
||||
+ .should('have.css', 'background-color').and('equal', 'rgba(0, 0, 0, 0)');
|
||||
});
|
||||
|
||||
|
||||
it("skill display - theme", () => {
|
||||
diff --git a/skills-client-integration/skills-int-e2e-test/cypress/integration/react.specs.js b/skills-client-integration/skills-int-e2e-test/cypress/integration/react.specs.js
|
||||
index 5c51e84..c64f0b8 100644
|
||||
@@ -96,7 +116,7 @@ index 5c51e84..c64f0b8 100644
|
||||
- .should('have.css', 'background-color').and('equal', 'rgb(255, 255, 255)');
|
||||
+ .should('have.css', 'background-color').and('equal', 'rgba(0, 0, 0, 0)');
|
||||
})
|
||||
|
||||
|
||||
it('skill display - summary only', () => {
|
||||
@@ -167,7 +167,7 @@ context('React Tests', () => {
|
||||
// verify that there is no background set
|
||||
@@ -105,7 +125,7 @@ index 5c51e84..c64f0b8 100644
|
||||
- .should('have.css', 'background-color').and('equal', 'rgb(255, 255, 255)');
|
||||
+ .should('have.css', 'background-color').and('equal', 'rgba(0, 0, 0, 0)');
|
||||
})
|
||||
|
||||
|
||||
it('skill display - theme', () => {
|
||||
diff --git a/skills-client-integration/skills-int-e2e-test/cypress/integration/vue.specs.js b/skills-client-integration/skills-int-e2e-test/cypress/integration/vue.specs.js
|
||||
index 2544edf..0b035f5 100644
|
||||
@@ -136,7 +156,7 @@ index 2544edf..0b035f5 100644
|
||||
- .should('have.css', 'background-color').and('equal', 'rgb(255, 255, 255)');
|
||||
+ .should('have.css', 'background-color').and('equal', 'rgba(0, 0, 0, 0)');
|
||||
})
|
||||
|
||||
|
||||
it('Proxy Skills Display', () => {
|
||||
@@ -262,7 +262,7 @@ context('Vue Tests', () => {
|
||||
// verify that there is no background set
|
||||
@@ -145,7 +165,7 @@ index 2544edf..0b035f5 100644
|
||||
- .should('have.css', 'background-color').and('equal', 'rgb(255, 255, 255)');
|
||||
+ .should('have.css', 'background-color').and('equal', 'rgba(0, 0, 0, 0)');
|
||||
});
|
||||
|
||||
|
||||
cy.visit('/vuejs#/proxyShowSkills')
|
||||
@@ -287,7 +287,7 @@ context('Vue Tests', () => {
|
||||
// verify that there is no background set
|
||||
@@ -154,7 +174,7 @@ index 2544edf..0b035f5 100644
|
||||
- .should('have.css', 'background-color').and('equal', 'rgb(255, 255, 255)');
|
||||
+ .should('have.css', 'background-color').and('equal', 'rgba(0, 0, 0, 0)');
|
||||
})
|
||||
|
||||
|
||||
it('skill display - theme', () => {
|
||||
@@ -409,7 +409,7 @@ context('Vue Tests', () => {
|
||||
// verify that there is no background set
|
||||
@@ -164,17 +184,17 @@ index 2544edf..0b035f5 100644
|
||||
+ .should('have.css', 'background-color').and('equal', 'rgba(0, 0, 0, 0)');
|
||||
});*!/
|
||||
})*/
|
||||
|
||||
|
||||
diff --git a/skills-client-integration/skills-int-e2e-test/cypress/support/commands.js b/skills-client-integration/skills-int-e2e-test/cypress/support/commands.js
|
||||
index e899916..4761ae5 100644
|
||||
--- a/skills-client-integration/skills-int-e2e-test/cypress/support/commands.js
|
||||
+++ b/skills-client-integration/skills-int-e2e-test/cypress/support/commands.js
|
||||
@@ -173,7 +173,7 @@ Cypress.Commands.add("cdClickSubj", (subjIndex, expectedTitle) => {
|
||||
|
||||
|
||||
Cypress.Commands.add("cdBack", (expectedTitle = 'User Skills') => {
|
||||
cy.wrapIframe().find('[data-cy=back]').click()
|
||||
- cy.wrapIframe().find('h2').contains(expectedTitle);
|
||||
+ cy.wrapIframe().contains(expectedTitle);
|
||||
|
||||
|
||||
// back button should not exist on the home page, whose title is the default value
|
||||
if (expectedTitle === 'User Skills'){
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>skills</groupId>
|
||||
<artifactId>skills-client-integration</artifactId>
|
||||
<version>3.1.1-SNAPSHOT</version>
|
||||
<version>3.3.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>skills-int-service</artifactId>
|
||||
<description>SkillTree Client Lib Integration Tests</description>
|
||||
|
||||
+6
@@ -52,6 +52,8 @@ public class SkillsExampleApplication implements WebMvcConfigurer {
|
||||
String password;
|
||||
@JsonIgnore
|
||||
String authMode = "token";
|
||||
String proxiedProjectId;
|
||||
String proxiedUserId;
|
||||
|
||||
public String getServiceUrl() { return serviceUrl; }
|
||||
public String getProjectId() { return projectId; }
|
||||
@@ -59,6 +61,8 @@ public class SkillsExampleApplication implements WebMvcConfigurer {
|
||||
public String getUsername() { return username; }
|
||||
public String getPassword() { return password; }
|
||||
public String getAuthMode() { return authMode; }
|
||||
public String getProxiedProjectId() { return proxiedProjectId; }
|
||||
public String getProxiedUserId() { return proxiedUserId; }
|
||||
|
||||
public void setServiceUrl(String serviceUrl) { this.serviceUrl = serviceUrl; }
|
||||
public void setProjectId(String projectId) { this.projectId = projectId; }
|
||||
@@ -66,5 +70,7 @@ public class SkillsExampleApplication implements WebMvcConfigurer {
|
||||
public void setUsername(String username) { this.username = username; }
|
||||
public void setPassword(String password) { this.password = password; }
|
||||
public void setAuthMode(String authMode) { this.authMode = authMode; }
|
||||
public void setProxiedProjectId(String proxiedProjectId) { this.proxiedProjectId = proxiedProjectId; }
|
||||
public void setProxiedUserId(String proxiedUserId) { this.proxiedUserId = proxiedUserId; }
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,4 @@
|
||||
const presets = [
|
||||
[
|
||||
"@babel/env",
|
||||
],
|
||||
];
|
||||
|
||||
module.exports = { presets };
|
||||
module.exports = {
|
||||
presets: ['@babel/preset-env'],
|
||||
plugins: ['@babel/plugin-transform-runtime'],
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@skilltree/skills-client-js",
|
||||
"version": "3.1.1",
|
||||
"version": "3.3.1",
|
||||
"license": "Apache-2.0",
|
||||
"description": "SkillTree Client Lib - Native JS Lib",
|
||||
"main": "dist/skills-client-js.umd.min.js",
|
||||
@@ -24,6 +24,7 @@
|
||||
"devDependencies": {
|
||||
"@babel/cli": "7.11.6",
|
||||
"@babel/core": "7.11.6",
|
||||
"@babel/plugin-transform-runtime": "7.13.10",
|
||||
"@babel/polyfill": "7.11.5",
|
||||
"@babel/preset-env": "7.11.5",
|
||||
"@rollup/plugin-commonjs": "15.1.0",
|
||||
@@ -46,6 +47,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "0.21.1",
|
||||
"history": "5.0.0",
|
||||
"js-logger": "1.6.0",
|
||||
"postmate": "1.5.2",
|
||||
"sockjs-client": "1.5.0",
|
||||
|
||||
@@ -17,6 +17,7 @@ module.exports = {
|
||||
plugins: [
|
||||
eslint(),
|
||||
babel({
|
||||
runtimeHelpers: true,
|
||||
exclude: 'node_modules/**',
|
||||
}),
|
||||
resolve({
|
||||
|
||||
@@ -67,6 +67,10 @@ const exportObject = {
|
||||
authToken,
|
||||
oauthRedirect = false,
|
||||
}) {
|
||||
if (this.isInitialized()) {
|
||||
log.warn('SkillsConfiguration already initialized.');
|
||||
return;
|
||||
}
|
||||
if (!this.skillsClientVersion) {
|
||||
// this will be replaced at build time with the current skills-client-js
|
||||
// version. extensions of the plain JS client (vue, react, etc), should
|
||||
@@ -110,8 +114,8 @@ const exportObject = {
|
||||
}).catch((error) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('Error getting service status', error);
|
||||
setInitialized(this);
|
||||
setConfigureWasCalled();
|
||||
initializedResolvers.resolve();
|
||||
});
|
||||
},
|
||||
|
||||
@@ -171,6 +175,8 @@ SkillsConfiguration is a singleton and you only have to do this once. Please see
|
||||
logout() {
|
||||
initializedResolvers = null;
|
||||
waitForInitializePromise = null;
|
||||
configureCalled = false;
|
||||
initialized = false;
|
||||
initializeAfterConfigurePromise();
|
||||
this.setAuthToken(null);
|
||||
},
|
||||
|
||||
@@ -15,21 +15,28 @@
|
||||
*/
|
||||
import Postmate from 'postmate';
|
||||
import log from 'js-logger';
|
||||
import { createBrowserHistory } from 'history';
|
||||
|
||||
import SkillsConfiguration from '../config/SkillsConfiguration';
|
||||
import ErrorPageUtils from './ErrorPageUtils';
|
||||
import skillsService from '../SkillsService';
|
||||
|
||||
let uniqueId = 0;
|
||||
let history = null; // createBrowserHistory();
|
||||
let unlisten = () => {};
|
||||
|
||||
const skillsClientDisplayPath = 'skillsClientDisplayPath';
|
||||
const POP = 'POP';
|
||||
|
||||
export default class SkillsDisplayJS {
|
||||
/* eslint-disable object-curly-newline */
|
||||
constructor({ options, theme, version, userId } = {}) {
|
||||
constructor({ options, theme, version, handleRouteChanged, userId } = {}) {
|
||||
log.debug(`SkillsClient::SkillsDisplayJS::Constructing with options [${options}], theme [${theme}], version [${version}], userId [${userId}]`);
|
||||
this._validateOptions(options);
|
||||
this._options = { ...{ }, ...options };
|
||||
this._theme = theme;
|
||||
this._version = version;
|
||||
this._handleRouteChanged = handleRouteChanged;
|
||||
this._userId = userId;
|
||||
}
|
||||
|
||||
@@ -38,6 +45,12 @@ export default class SkillsDisplayJS {
|
||||
return uniqueId;
|
||||
}
|
||||
|
||||
navigate(path = this.skillsDisplayPath, skipParentHistory = true, replace = true) {
|
||||
if (this._childFrame && !(path == null)) {
|
||||
this._childFrame.call('navigate', { path, replace, query: { skipParentHistory } });
|
||||
}
|
||||
}
|
||||
|
||||
attachTo(selectorOrElement) {
|
||||
log.info(`SkillsClient::SkillsDisplayJS::attaching to [${selectorOrElement ? selectorOrElement.toString() : selectorOrElement}]`);
|
||||
let iframeContainer = selectorOrElement;
|
||||
@@ -65,6 +78,7 @@ export default class SkillsDisplayJS {
|
||||
theme: this.theme,
|
||||
minHeight: `${minHeight}px`,
|
||||
isSummaryOnly: this.options.isSummaryOnly,
|
||||
internalBackButton: this.options.internalBackButton == null ? false : this.options.internalBackButton,
|
||||
},
|
||||
});
|
||||
const iframe = document.querySelector(`.${className}`);
|
||||
@@ -73,6 +87,14 @@ export default class SkillsDisplayJS {
|
||||
iframeContainer.height = 0;
|
||||
iframeContainer.style.height = '0px';
|
||||
|
||||
history = createBrowserHistory();
|
||||
// Listen for changes to the current location from the back/forward browser buttons.
|
||||
unlisten = history.listen(({ action }) => {
|
||||
if (action === POP) {
|
||||
this.navigate(this.skillsDisplayPath || '/', true);
|
||||
}
|
||||
});
|
||||
|
||||
handshake.then((child) => {
|
||||
this._childFrame = child;
|
||||
child.on('height-changed', (data) => {
|
||||
@@ -81,8 +103,26 @@ export default class SkillsDisplayJS {
|
||||
iframeContainer.height = adjustedHeight;
|
||||
iframeContainer.style.height = `${adjustedHeight}px`;
|
||||
});
|
||||
child.on('route-changed', () => {
|
||||
log.debug('SkillsClient::SkillsDisplayJS::route-changed');
|
||||
child.on('route-changed', (params) => {
|
||||
const newPath = params ? params.path : null;
|
||||
log.debug(`SkillsClient::SkillsDisplayJS::route-changed - newPath [${newPath}]`);
|
||||
if (!(newPath == null)) {
|
||||
const routePath = newPath.endsWith('index.html') ? '/' : newPath;
|
||||
if (this._shouldUpdateHistory(params)) {
|
||||
// put the new path in the URL so that when the page is reloaded or
|
||||
// sent as a link the proper route will be set in the child iframe
|
||||
const queryParams = new URLSearchParams(window.location.search);
|
||||
queryParams.set(skillsClientDisplayPath, routePath);
|
||||
const newUrl = `${window.location.pathname}?${queryParams.toString()}${window.location.hash}`;
|
||||
history.push(newUrl, { skillsClientDisplayPath: newPath });
|
||||
}
|
||||
|
||||
// if the client has configured a handleRouteChanged call back, invoke it
|
||||
if (this._handleRouteChanged) {
|
||||
this._handleRouteChanged(routePath);
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.options.disableAutoScroll) {
|
||||
let scrollToElement = iframeContainer;
|
||||
|
||||
@@ -110,12 +150,14 @@ export default class SkillsDisplayJS {
|
||||
this.authenticationPromise = skillsService.getAuthenticationToken(this.configuration.authenticator, this.configuration.serviceUrl, this.configuration.projectId)
|
||||
.then((result) => {
|
||||
child.call('updateAuthenticationToken', result);
|
||||
this.navigate();
|
||||
})
|
||||
.finally(() => {
|
||||
this.authenticationPromise = null;
|
||||
});
|
||||
} else if (isPkiMode) {
|
||||
child.call('updateAuthenticationToken', 'pki');
|
||||
this.navigate();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -147,6 +189,14 @@ export default class SkillsDisplayJS {
|
||||
}
|
||||
}
|
||||
|
||||
_shouldUpdateHistory(params) {
|
||||
// if the query param `skipParentHistory` is true then do not update the history (set to true when the user is navigating with the back/forward browser buttons)
|
||||
const updateHistoryParam = (params.query.skipParentHistory !== 'true' && params.query.skipParentHistory !== true);
|
||||
// also, allow the client app to disable this feature, but enable it by default
|
||||
const updateHistoryOption = this.options.updateHistory == null || this.options.updateHistory === true;
|
||||
return updateHistoryOption && updateHistoryParam;
|
||||
}
|
||||
|
||||
set version(version) {
|
||||
log.info(`SkillsClient::SkillsDisplayJS::setting version [${version}]`);
|
||||
this._version = version;
|
||||
@@ -176,6 +226,12 @@ export default class SkillsDisplayJS {
|
||||
return { serviceUrl, authenticator, projectId };
|
||||
}
|
||||
|
||||
get skillsDisplayPath() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const clientDisplayPath = urlParams.get(skillsClientDisplayPath);
|
||||
return clientDisplayPath;
|
||||
}
|
||||
|
||||
_validateOptions(options) {
|
||||
const configOptions = [
|
||||
'authenticator',
|
||||
@@ -185,6 +241,8 @@ export default class SkillsDisplayJS {
|
||||
'autoScrollStrategy',
|
||||
'isSummaryOnly',
|
||||
'scrollTopOffset',
|
||||
'internalBackButton',
|
||||
'updateHistory',
|
||||
];
|
||||
const toTest = { ...this._options, ...options };
|
||||
const passedOptions = Object.keys(toTest);
|
||||
@@ -199,5 +257,8 @@ export default class SkillsDisplayJS {
|
||||
if (this._childFrame) {
|
||||
this._childFrame.destroy();
|
||||
}
|
||||
const queryParams = new URLSearchParams(window.location.search);
|
||||
queryParams.delete(skillsClientDisplayPath);
|
||||
unlisten();
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,8 @@ const FAILURE_EVENT = 'skills-report-error';
|
||||
const successHandlerCache = new Set();
|
||||
const errorHandlerCache = new Set();
|
||||
|
||||
let retryIntervalId = null;
|
||||
|
||||
const callSuccessHandlers = (event) => {
|
||||
successHandlerCache.forEach((it) => it(event));
|
||||
};
|
||||
@@ -63,6 +65,43 @@ const connectWebsocket = (serviceUrl) => {
|
||||
}
|
||||
};
|
||||
|
||||
const retryQueueKey = 'skillTreeRetryQueue';
|
||||
const defaultMaxRetryQueueSize = 1000;
|
||||
const defaultRetryInterval = 60000;
|
||||
const defaultMaxRetryAttempts = 1440;
|
||||
const retryErrors = function retryErrors() {
|
||||
const retryQueue = JSON.parse(localStorage.getItem(retryQueueKey));
|
||||
localStorage.removeItem(retryQueueKey);
|
||||
if (retryQueue !== null) {
|
||||
retryQueue.forEach((item) => {
|
||||
log.info(`SkillsClient::SkillsReporter::retryErrors retrying skillId [${item.skillId}], timestamp [${item.timestamp}], retryAttempt [${item.retryAttempt}]`);
|
||||
this.reportSkill(item.skillId, 0, item.timestamp, true, item.retryAttempt);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const addToRetryQueue = (skillId, timeReported, retryAttempt, xhr, maxQueueSize) => {
|
||||
log.info(`SkillsClient::SkillsReporter::addToRetryQueue [${skillId}], timeReported [${timeReported}], retryAttempt[${retryAttempt}], status [${xhr.status}]`);
|
||||
if (xhr.response) {
|
||||
const xhrResponse = JSON.parse(xhr.response);
|
||||
if (xhrResponse && xhrResponse.errorCode === 'SkillNotFound') {
|
||||
log.info('not adding to retry queue because the skillId does not exist.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
let retryQueue = JSON.parse(localStorage.getItem(retryQueueKey));
|
||||
if (retryQueue == null) {
|
||||
retryQueue = [];
|
||||
}
|
||||
if (retryQueue.length < maxQueueSize) {
|
||||
const timestamp = (timeReported == null) ? Date.now() : timeReported;
|
||||
retryQueue.push({ skillId, timestamp, retryAttempt });
|
||||
localStorage.setItem(retryQueueKey, JSON.stringify(retryQueue));
|
||||
} else {
|
||||
log.warn(`Max retry queue size has been reached (${maxQueueSize}), Unable to retry skillId [${skillId}]`);
|
||||
}
|
||||
};
|
||||
|
||||
const authenticateAndRetry = function authenticateAndRetry(userSkillId, attemptCount, resolve, reject) {
|
||||
log.info(`SkillsClient::SkillsReporter::authenticateAndRetry [${userSkillId}] attemptCount [${attemptCount}]`);
|
||||
skillsService.getAuthenticationToken(SkillsConfiguration.getAuthenticator(), SkillsConfiguration.getServiceUrl(), SkillsConfiguration.getProjectId())
|
||||
@@ -83,9 +122,12 @@ const authenticateAndRetry = function authenticateAndRetry(userSkillId, attemptC
|
||||
|
||||
const SkillsReporter = {
|
||||
configure({
|
||||
notifyIfSkillNotApplied,
|
||||
notifyIfSkillNotApplied, retryInterval = defaultRetryInterval, maxRetryQueueSize = defaultMaxRetryQueueSize, maxRetryAttempts = defaultMaxRetryAttempts,
|
||||
}) {
|
||||
this.notifyIfSkillNotApplied = notifyIfSkillNotApplied;
|
||||
this.retryInterval = retryInterval;
|
||||
this.maxRetryQueueSize = maxRetryQueueSize;
|
||||
this.maxRetryAttempts = maxRetryAttempts;
|
||||
},
|
||||
|
||||
addSuccessHandler(handler) {
|
||||
@@ -103,9 +145,14 @@ const SkillsReporter = {
|
||||
errorHandlerCache.add(handler);
|
||||
log.info(`SkillsClient::SkillsReporter::added error handler [${handler ? handler.toString() : handler}]`);
|
||||
},
|
||||
reportSkill(userSkillId, count = undefined) {
|
||||
reportSkill(userSkillId, count = undefined, timestamp = null, isRetry = false, retryAttempt = undefined) {
|
||||
log.info(`SkillsClient::SkillsReporter::reporting skill [${userSkillId}] count [${count}]`);
|
||||
SkillsConfiguration.validate();
|
||||
if (!this.retryEnabled) {
|
||||
log.info('SkillsClient::SkillsReporter::Enabling retries...');
|
||||
retryIntervalId = setInterval(() => { retryErrors.call(this); }, this.retryInterval || defaultRetryInterval);
|
||||
this.retryEnabled = true;
|
||||
}
|
||||
if (count >= 25) {
|
||||
const errorMessage = 'Unable to authenticate after 25 attempts';
|
||||
log.error(`SkillsReporter::${errorMessage}`);
|
||||
@@ -117,6 +164,11 @@ const SkillsReporter = {
|
||||
countInternal = count;
|
||||
}
|
||||
|
||||
let retryAttemptInternal = 1;
|
||||
if (retryAttempt !== undefined) {
|
||||
retryAttemptInternal = retryAttempt + 1;
|
||||
}
|
||||
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
if (!SkillsConfiguration.getAuthToken() && !SkillsConfiguration.isPKIMode()) {
|
||||
authenticateAndRetry.call(this, userSkillId, countInternal, resolve, reject);
|
||||
@@ -132,9 +184,20 @@ const SkillsReporter = {
|
||||
xhr.onreadystatechange = () => {
|
||||
// some browsers don't understand XMLHttpRequest.Done, which should be 4
|
||||
if (xhr.readyState === 4) {
|
||||
if (xhr.status !== 200 && xhr.status !== 0 && xhr.status !== 401) {
|
||||
reject(JSON.parse(xhr.response));
|
||||
} else if ((xhr.status === 401 || xhr.status === 0) && !SkillsConfiguration.isPKIMode()) {
|
||||
if (xhr.status !== 200 && xhr.status !== 401) {
|
||||
const maxRetryAttempts = this.maxRetryAttempts || defaultMaxRetryAttempts;
|
||||
const maxRetryQueueSize = this.maxRetryQueueSize || defaultMaxRetryQueueSize;
|
||||
if (retryAttemptInternal <= maxRetryAttempts) {
|
||||
addToRetryQueue(userSkillId, timestamp, retryAttemptInternal, xhr, maxRetryQueueSize);
|
||||
} else {
|
||||
log.warn(`Max retry attempts has been reached (${this.maxRetryAttempts}), Unable to retry skillId [${userSkillId}]`);
|
||||
}
|
||||
if (xhr.response) {
|
||||
reject(JSON.parse(xhr.response));
|
||||
} else {
|
||||
reject(new Error(`Error occurred reporting skill [${userSkillId}], status returned [${xhr.status}]`));
|
||||
}
|
||||
} else if ((xhr.status === 401) && !SkillsConfiguration.isPKIMode()) {
|
||||
authenticateAndRetry.call(this, userSkillId, countInternal, resolve, reject);
|
||||
} else {
|
||||
resolve(JSON.parse(xhr.response));
|
||||
@@ -142,15 +205,10 @@ const SkillsReporter = {
|
||||
}
|
||||
};
|
||||
|
||||
if (this.notifyIfSkillNotApplied) {
|
||||
const body = JSON.stringify({ notifyIfSkillNotApplied: this.notifyIfSkillNotApplied });
|
||||
xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
|
||||
xhr.send(body);
|
||||
log.info('SkillsClient::SkillsReporter::reporting skill request sent with notifyIfSkillNotApplied=true');
|
||||
} else {
|
||||
xhr.send();
|
||||
log.info('SkillsClient::SkillsReporter::reporting skill request sent with notifyIfSkillNotApplied=false');
|
||||
}
|
||||
const body = JSON.stringify({ timestamp, notifyIfSkillNotApplied: this.notifyIfSkillNotApplied, isRetry });
|
||||
xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
|
||||
xhr.send(body);
|
||||
log.info(`SkillsClient::SkillsReporter::reporting skill request sent: ${body}`);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -164,6 +222,12 @@ const SkillsReporter = {
|
||||
getConf() {
|
||||
return SkillsConfiguration;
|
||||
},
|
||||
|
||||
cancelRetryChecker() {
|
||||
clearInterval(retryIntervalId);
|
||||
this.retryEnabled = false;
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
export {
|
||||
|
||||
@@ -17,19 +17,28 @@ import mock from 'xhr-mock';
|
||||
import SkillsConfiguration from '../../src/config/SkillsConfiguration';
|
||||
|
||||
describe('SkillsConfiguration', () => {
|
||||
const flushPromises = () => new Promise(setImmediate);
|
||||
const mockStatusResponse = '{"status":"OK","clientLib":{"loggingEnabled":"false","loggingLevel":"DEBUG"}}';
|
||||
const mockVersionResponse = '{"success":true,"explanation":null}';
|
||||
let statusReqCount = 0;
|
||||
|
||||
// replace the real XHR object with the mock XHR object before each test
|
||||
beforeEach(() => {
|
||||
mock.setup();
|
||||
SkillsConfiguration.logout();
|
||||
const url = /.*\/api\/projects\/.*\/skillsClientVersion/;
|
||||
mock.post(url, (req, res) => res.status(200).body('{"success":true,"explanation":null}'));
|
||||
mock.get(/.*\/public\/status/, (req, res) => res.status(200).body('{"status":"OK","clientLib":{"loggingEnabled":"false","loggingLevel":"DEBUG"}}'));
|
||||
mock.post(url, (req, res) => res.status(200).body(mockVersionResponse));
|
||||
mock.get(/.*\/public\/status/, (req, res) => {
|
||||
statusReqCount++;
|
||||
return res.status(200).body(mockStatusResponse);
|
||||
});
|
||||
});
|
||||
|
||||
// put the real XHR object back and clear the mocks after each test
|
||||
afterEach(() => {
|
||||
SkillsConfiguration.logout();
|
||||
mock.teardown();
|
||||
statusReqCount = 0;
|
||||
});
|
||||
|
||||
describe('initialization', () => {
|
||||
@@ -43,9 +52,79 @@ describe('SkillsConfiguration', () => {
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('setAuthToken updates the authToken', () => {
|
||||
it('isInitialized is false when error occurs in configure', () => {
|
||||
expect(() => {
|
||||
SkillsConfiguration.configure({
|
||||
serviceUrl: 'http://somewhere',
|
||||
authenticator: 'pki',
|
||||
});
|
||||
|
||||
// when an error occurs during initialization isInitialized should be false, but wasConfigureCalled should be true
|
||||
expect(SkillsConfiguration.isInitialized()).toBe(false);
|
||||
expect(SkillsConfiguration.wasConfigureCalled()).toBe(true);
|
||||
expect(statusReqCount).toBe(1);
|
||||
}).toThrow('SkillTree: SkillsConfiguration.configure received invalid parameter for projectId=[undefined]');
|
||||
});
|
||||
|
||||
it('calling configure more than once after failure will attempt to configure again', async () => {
|
||||
expect.assertions(6);
|
||||
|
||||
mock.reset();
|
||||
mock.get(/.*\/public\/status/, (req, res) => {
|
||||
statusReqCount++;
|
||||
return res.status(503);
|
||||
});
|
||||
const mockAuthenticator = Math.random().toString();
|
||||
mock.get(mockAuthenticator, (req, res) => res.status(200).body('{"access_token": "token"}'));
|
||||
|
||||
const config = {
|
||||
projectId: Math.random().toString(),
|
||||
serviceUrl: Math.random().toString(),
|
||||
authenticator: mockAuthenticator,
|
||||
authToken: Math.random().toString(),
|
||||
};
|
||||
|
||||
SkillsConfiguration.configure(config);
|
||||
await flushPromises();
|
||||
expect(SkillsConfiguration.isInitialized()).toBe(false);
|
||||
expect(SkillsConfiguration.wasConfigureCalled()).toBe(true);
|
||||
expect(statusReqCount).toBe(1);
|
||||
|
||||
SkillsConfiguration.configure(config);
|
||||
await flushPromises();
|
||||
expect(SkillsConfiguration.isInitialized()).toBe(false);
|
||||
expect(SkillsConfiguration.wasConfigureCalled()).toBe(true);
|
||||
expect(statusReqCount).toBe(2);
|
||||
});
|
||||
|
||||
it('calling configure more than once without failure will NOT attempt to configure again', async () => {
|
||||
expect.assertions(6);
|
||||
const mockAuthenticator = Math.random().toString();
|
||||
mock.get(mockAuthenticator, (req, res) => res.status(200).body('{"access_token": "token"}'));
|
||||
const config = {
|
||||
projectId: Math.random().toString(),
|
||||
serviceUrl: Math.random().toString(),
|
||||
authenticator: mockAuthenticator,
|
||||
authToken: Math.random().toString(),
|
||||
};
|
||||
|
||||
SkillsConfiguration.configure(config);
|
||||
await flushPromises();
|
||||
expect(SkillsConfiguration.isInitialized()).toBe(true);
|
||||
expect(SkillsConfiguration.wasConfigureCalled()).toBe(true);
|
||||
expect(statusReqCount).toBe(1);
|
||||
|
||||
SkillsConfiguration.configure(config);
|
||||
await flushPromises();
|
||||
expect(SkillsConfiguration.isInitialized()).toBe(true);
|
||||
expect(SkillsConfiguration.wasConfigureCalled()).toBe(true);
|
||||
expect(statusReqCount).toBe(1);
|
||||
});
|
||||
|
||||
it('setAuthToken updates the authToken', async () => {
|
||||
const mockAuthToken = Math.random() + 1;
|
||||
SkillsConfiguration.configure({ authToken: mockAuthToken, projectId: 'projectId', serviceUrl: 'http://somewhere' });
|
||||
await flushPromises();
|
||||
|
||||
expect(SkillsConfiguration.getAuthToken()).toBe(mockAuthToken);
|
||||
|
||||
@@ -57,32 +136,36 @@ describe('SkillsConfiguration', () => {
|
||||
|
||||
describe('initializing', () => {
|
||||
describe('isPKIMode', () => {
|
||||
it('isPKIMode is false if authenticator is NOT \'pki\'', () => {
|
||||
it('isPKIMode is false if authenticator is NOT \'pki\'', async () => {
|
||||
const mockAuthenticator = Math.random().toString();
|
||||
|
||||
mock.get(mockAuthenticator, (req, res) => res.status(200).body('{"access_token": "token"}'));
|
||||
SkillsConfiguration.configure({
|
||||
authenticator: mockAuthenticator, projectId: 'proj', serviceUrl: 'http://somewhere',
|
||||
});
|
||||
await flushPromises();
|
||||
|
||||
expect(SkillsConfiguration.isPKIMode()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('isPKIMode is true if authenticator is \'pki\'', () => {
|
||||
it('isPKIMode is true if authenticator is \'pki\'', async () => {
|
||||
const mockAuthenticator = 'pki';
|
||||
|
||||
SkillsConfiguration.configure({
|
||||
authenticator: mockAuthenticator, projectId: 'proj', serviceUrl: 'http://somewhere',
|
||||
});
|
||||
await flushPromises();
|
||||
|
||||
expect(SkillsConfiguration.isPKIMode()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it('properly initializes variables', () => {
|
||||
const mockServiceUrl = Math.random().toString();;
|
||||
const mockProjectId = Math.random().toString();;
|
||||
const mockAuthenticator = Math.random().toString();;
|
||||
const mockAuthToken = Math.random().toString();;
|
||||
it('properly initializes variables', async () => {
|
||||
expect.assertions(6);
|
||||
|
||||
const mockServiceUrl = Math.random().toString();
|
||||
const mockProjectId = Math.random().toString();
|
||||
const mockAuthenticator = Math.random().toString();
|
||||
const mockAuthToken = Math.random().toString();
|
||||
|
||||
SkillsConfiguration.configure({
|
||||
projectId: mockProjectId,
|
||||
@@ -90,14 +173,17 @@ describe('SkillsConfiguration', () => {
|
||||
authenticator: mockAuthenticator,
|
||||
authToken: mockAuthToken,
|
||||
});
|
||||
await flushPromises();
|
||||
|
||||
expect(SkillsConfiguration.getProjectId()).toBe(mockProjectId);
|
||||
expect(SkillsConfiguration.getServiceUrl()).toBe(mockServiceUrl);
|
||||
expect(SkillsConfiguration.getAuthenticator()).toBe(mockAuthenticator);
|
||||
expect(SkillsConfiguration.getAuthToken()).toBe(mockAuthToken);
|
||||
expect(SkillsConfiguration.isInitialized()).toBe(true);
|
||||
expect(SkillsConfiguration.wasConfigureCalled()).toBe(true);
|
||||
});
|
||||
|
||||
it('notifies afterConfigure Promisees' ,(done) => {
|
||||
it('notifies afterConfigure Promisees', (done) => {
|
||||
let resolveCount = 0;
|
||||
SkillsConfiguration.afterConfigure().then(() => {
|
||||
resolveCount++;
|
||||
@@ -112,12 +198,15 @@ describe('SkillsConfiguration', () => {
|
||||
SkillsConfiguration.configure({ authenticator: 'pki', projectId: 'proj', serviceUrl: 'http://somewhere' });
|
||||
});
|
||||
|
||||
it('trailing slash and spaces are removed from the serviceUrl', () => {
|
||||
it('trailing slash and spaces are removed from the serviceUrl', async () => {
|
||||
const authEndpoint = 'http://auth/';
|
||||
mock.get(authEndpoint, (req, res) => res.status(200).body('{"access_token": "token"}'));
|
||||
SkillsConfiguration.configure({
|
||||
projectId: 'proj',
|
||||
serviceUrl: ' http://some/ ',
|
||||
authenticator: 'http://auth',
|
||||
authenticator: authEndpoint,
|
||||
});
|
||||
await flushPromises();
|
||||
|
||||
expect(SkillsConfiguration.getServiceUrl()).toBe('http://some');
|
||||
});
|
||||
@@ -130,6 +219,10 @@ describe('SkillsConfiguration', () => {
|
||||
serviceUrl: 'http://somewhere',
|
||||
authenticator: 'pki',
|
||||
});
|
||||
|
||||
// when an error occurs during initialization isInitialized should be false, but wasConfigureCalled should be true
|
||||
expect(SkillsConfiguration.isInitialized()).toBe(false);
|
||||
expect(SkillsConfiguration.wasConfigureCalled()).toBe(true);
|
||||
}).toThrow('SkillTree: SkillsConfiguration.configure received invalid parameter for projectId=[undefined]');
|
||||
});
|
||||
|
||||
@@ -162,7 +255,6 @@ describe('SkillsConfiguration', () => {
|
||||
}).toThrow('SkillTree: SkillsConfiguration.configure received invalid parameter for serviceUrl=[undefined]');
|
||||
});
|
||||
|
||||
|
||||
it('throws an error if authenticator is set to null', () => {
|
||||
expect(() => {
|
||||
SkillsConfiguration.configure({
|
||||
@@ -212,7 +304,7 @@ describe('SkillsConfiguration', () => {
|
||||
expect(oldPromise).not.toBe(newPromise);
|
||||
});
|
||||
|
||||
it('clears the authToken', () => {
|
||||
it('clears the authToken', async () => {
|
||||
const mockAuthToken = Math.random().toString();;
|
||||
SkillsConfiguration.configure({
|
||||
authToken: mockAuthToken,
|
||||
@@ -220,6 +312,7 @@ describe('SkillsConfiguration', () => {
|
||||
serviceUrl: 'http://some',
|
||||
authenticator: 'http://auth',
|
||||
});
|
||||
await flushPromises();
|
||||
|
||||
expect(SkillsConfiguration.getAuthToken()).toBe(mockAuthToken);
|
||||
|
||||
|
||||
@@ -202,7 +202,7 @@ describe('SkillsDisplayJS', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('rout-changed event', () => {
|
||||
describe('route-changed event', () => {
|
||||
it('does not attempt to scroll if disableAutoScroll option is set', (done) => {
|
||||
const client = new SkillsDisplayJS({
|
||||
options: {
|
||||
@@ -223,7 +223,7 @@ describe('SkillsDisplayJS', () => {
|
||||
client.attachTo(mockIframeContainer);
|
||||
|
||||
setTimeout(() => {
|
||||
mockChildFrame.emit('route-changed');
|
||||
mockChildFrame.emit('route-changed', { path: 'blah', query: {} });
|
||||
expect(mockIframeContainer.scrollIntoView).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
@@ -245,7 +245,7 @@ describe('SkillsDisplayJS', () => {
|
||||
client.attachTo(mockIframeContainer);
|
||||
|
||||
setTimeout(() => {
|
||||
mockChildFrame.emit('route-changed');
|
||||
mockChildFrame.emit('route-changed', { path: 'blah', query: {} });
|
||||
expect(mockIframeContainer.scrollIntoView).toHaveBeenCalledWith({ behavior: 'smooth' });
|
||||
done();
|
||||
});
|
||||
@@ -268,7 +268,7 @@ describe('SkillsDisplayJS', () => {
|
||||
const top = mockIframeContainer.offsetTop - 10;
|
||||
|
||||
setTimeout(() => {
|
||||
mockChildFrame.emit('route-changed');
|
||||
mockChildFrame.emit('route-changed', { path: 'blah', query: {} });
|
||||
expect(global.scroll).toHaveBeenCalledWith({ top, behavior: 'smooth' });
|
||||
done();
|
||||
});
|
||||
@@ -298,11 +298,37 @@ describe('SkillsDisplayJS', () => {
|
||||
client.attachTo(mockIframeContainer);
|
||||
|
||||
setTimeout(() => {
|
||||
mockChildFrame.emit('route-changed');
|
||||
mockChildFrame.emit('route-changed', { path: 'blah', query: {} });
|
||||
expect(mockBody.scrollIntoView).toHaveBeenCalledWith({ behavior: 'smooth' });
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('handles null params', (done) => {
|
||||
const client = new SkillsDisplayJS({
|
||||
options: {
|
||||
disableAutoScroll: true,
|
||||
}
|
||||
});
|
||||
|
||||
const mockIframeContainer = {
|
||||
setAttribute: jest.fn(),
|
||||
style: {},
|
||||
scrollIntoView: jest.fn(),
|
||||
};
|
||||
|
||||
global.document.querySelector = () => {
|
||||
return mockIframeContainer;
|
||||
};
|
||||
|
||||
client.attachTo(mockIframeContainer);
|
||||
|
||||
setTimeout(() => {
|
||||
mockChildFrame.emit('route-changed');
|
||||
expect(mockIframeContainer.scrollIntoView).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('needs-authentication event', () => {
|
||||
@@ -437,6 +463,7 @@ describe('SkillsDisplayJS', () => {
|
||||
theme: mockTheme,
|
||||
minHeight: '960px',
|
||||
isSummaryOnly: false,
|
||||
internalBackButton: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -473,6 +500,120 @@ describe('SkillsDisplayJS', () => {
|
||||
theme: mockTheme,
|
||||
minHeight: '600px',
|
||||
isSummaryOnly: true,
|
||||
internalBackButton: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
it('properly configures Postmate internalBackButton === false when set explicitly', () => {
|
||||
const client = new SkillsDisplayJS({
|
||||
options: {
|
||||
isSummaryOnly: true,
|
||||
authenticator: mockAuthenticator,
|
||||
serviceUrl: mockServiceUrl,
|
||||
projectId: mockProjectId,
|
||||
internalBackButton: false,
|
||||
},
|
||||
theme: mockTheme,
|
||||
version: mockVersion,
|
||||
});
|
||||
const mockIframeContainer = {
|
||||
setAttribute: jest.fn(),
|
||||
style: { },
|
||||
};
|
||||
|
||||
global.document.querySelector = () => { return mockIframeContainer; };
|
||||
|
||||
client.attachTo(mockIframeContainer);
|
||||
|
||||
expect(Postmate).toHaveBeenCalledWith({
|
||||
container: mockIframeContainer,
|
||||
url: `${mockServiceUrl}/static/clientPortal/index.html`,
|
||||
classListArray: [`client-display-iframe-${client.currentIframeId()}`],
|
||||
model: {
|
||||
serviceUrl: mockServiceUrl,
|
||||
projectId: mockProjectId,
|
||||
version: mockVersion,
|
||||
userId: null,
|
||||
theme: mockTheme,
|
||||
minHeight: '600px',
|
||||
isSummaryOnly: true,
|
||||
internalBackButton: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('properly configures Postmate internalBackButton === true when set explicitly', () => {
|
||||
const client = new SkillsDisplayJS({
|
||||
options: {
|
||||
isSummaryOnly: true,
|
||||
authenticator: mockAuthenticator,
|
||||
serviceUrl: mockServiceUrl,
|
||||
projectId: mockProjectId,
|
||||
internalBackButton: true,
|
||||
},
|
||||
theme: mockTheme,
|
||||
version: mockVersion,
|
||||
});
|
||||
const mockIframeContainer = {
|
||||
setAttribute: jest.fn(),
|
||||
style: { },
|
||||
};
|
||||
|
||||
global.document.querySelector = () => { return mockIframeContainer; };
|
||||
|
||||
client.attachTo(mockIframeContainer);
|
||||
|
||||
expect(Postmate).toHaveBeenCalledWith({
|
||||
container: mockIframeContainer,
|
||||
url: `${mockServiceUrl}/static/clientPortal/index.html`,
|
||||
classListArray: [`client-display-iframe-${client.currentIframeId()}`],
|
||||
model: {
|
||||
serviceUrl: mockServiceUrl,
|
||||
projectId: mockProjectId,
|
||||
version: mockVersion,
|
||||
userId: null,
|
||||
theme: mockTheme,
|
||||
minHeight: '600px',
|
||||
isSummaryOnly: true,
|
||||
internalBackButton: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('properly configures Postmate internalBackButton === false when NOT set explicitly', () => {
|
||||
const client = new SkillsDisplayJS({
|
||||
options: {
|
||||
isSummaryOnly: true,
|
||||
authenticator: mockAuthenticator,
|
||||
serviceUrl: mockServiceUrl,
|
||||
projectId: mockProjectId,
|
||||
},
|
||||
theme: mockTheme,
|
||||
version: mockVersion,
|
||||
});
|
||||
const mockIframeContainer = {
|
||||
setAttribute: jest.fn(),
|
||||
style: { },
|
||||
};
|
||||
|
||||
global.document.querySelector = () => { return mockIframeContainer; };
|
||||
|
||||
client.attachTo(mockIframeContainer);
|
||||
|
||||
expect(Postmate).toHaveBeenCalledWith({
|
||||
container: mockIframeContainer,
|
||||
url: `${mockServiceUrl}/static/clientPortal/index.html`,
|
||||
classListArray: [`client-display-iframe-${client.currentIframeId()}`],
|
||||
model: {
|
||||
serviceUrl: mockServiceUrl,
|
||||
projectId: mockProjectId,
|
||||
version: mockVersion,
|
||||
userId: null,
|
||||
theme: mockTheme,
|
||||
minHeight: '600px',
|
||||
isSummaryOnly: true,
|
||||
internalBackButton: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,307 @@
|
||||
/*
|
||||
* Copyright 2020 SkillTree
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import mock from 'xhr-mock';
|
||||
import SkillsConfiguration from '../../src/config/SkillsConfiguration';
|
||||
import { SkillsReporter } from '../../src/reporter/SkillsReporter';
|
||||
|
||||
require('@babel/polyfill');
|
||||
|
||||
describe('retryTests()', () => {
|
||||
const mockServiceUrl = 'http://some.com';
|
||||
const mockProjectId = 'proj1';
|
||||
const authEndpoint = `${mockServiceUrl}/auth/endpoint`;
|
||||
const flushPromises = () => new Promise(setImmediate);
|
||||
|
||||
// replace the real XHR object with the mock XHR object before each test
|
||||
beforeEach(() => {
|
||||
mock.setup();
|
||||
SkillsConfiguration.logout();
|
||||
SkillsReporter.configure({ retryInterval: 60 });
|
||||
const url = /.*\/api\/projects\/proj1\/skillsClientVersion/;
|
||||
mock.post(url, (req, res) => res.status(200).body('{"success":true,"explanation":null}'));
|
||||
mock.get(/.*\/public\/status/, (req, res) => res.status(200).body('{"status":"OK","clientLib":{"loggingEnabled":"false","loggingLevel":"DEBUG"}}'));
|
||||
});
|
||||
|
||||
// put the real XHR object back and clear the mocks after each test
|
||||
afterEach(() => {
|
||||
SkillsReporter.cancelRetryChecker();
|
||||
window.localStorage.removeItem('skillTreeRetryQueue');
|
||||
SkillsConfiguration.logout();
|
||||
mock.teardown();
|
||||
});
|
||||
|
||||
it('reportSkill will retry for errors', async () => {
|
||||
expect.assertions(8);
|
||||
const mockUserSkillId = 'skill1-random';
|
||||
|
||||
mock.get(authEndpoint, (req, res) => res.status(200).body('{"access_token": "token"}'));
|
||||
SkillsConfiguration.configure({
|
||||
serviceUrl: mockServiceUrl,
|
||||
projectId: mockProjectId,
|
||||
authenticator: authEndpoint,
|
||||
});
|
||||
await flushPromises();
|
||||
const handler1 = jest.fn();
|
||||
const mockSuccess = '{"data":{"id":"abc-123"}}';
|
||||
const mockError = JSON.stringify({
|
||||
explanation: 'Some random error occurred.', errorCode: 'RandomError', success: false, projectId: 'movies', skillId: 'IronMan', userId: 'user1',
|
||||
});
|
||||
let body = mockError;
|
||||
let status = 503;
|
||||
|
||||
SkillsReporter.addErrorHandler(handler1);
|
||||
|
||||
const url = `${mockServiceUrl}/api/projects/${mockProjectId}/skills/${mockUserSkillId}`;
|
||||
let count = 0;
|
||||
let timestamp = null;
|
||||
mock.post(url, (req, res) => {
|
||||
expect(req.header('Authorization')).toEqual('Bearer token');
|
||||
count++;
|
||||
if (count > 1) {
|
||||
const reqBody = JSON.parse(req.body());
|
||||
expect(reqBody.isRetry).toEqual(true); // verify that isRetry is set to true
|
||||
if (timestamp == null) {
|
||||
timestamp = reqBody.timestamp;
|
||||
} else {
|
||||
expect(timestamp).toEqual(reqBody.timestamp); // verify the timestamp remains the same
|
||||
}
|
||||
}
|
||||
|
||||
// fail the first two times, then succeed after that
|
||||
if (count > 2) {
|
||||
body = mockSuccess;
|
||||
status = 200;
|
||||
}
|
||||
return res.status(status).body(body);
|
||||
});
|
||||
|
||||
try {
|
||||
await SkillsReporter.reportSkill(mockUserSkillId);
|
||||
} catch (e) {
|
||||
}
|
||||
// sleep for 3 seconds
|
||||
await new Promise((r) => setTimeout(r, 3000));
|
||||
expect(count).toEqual(3);
|
||||
expect(handler1).toHaveBeenCalledWith(JSON.parse(mockError));
|
||||
});
|
||||
|
||||
it('reportSkill will not retry on success', async () => {
|
||||
expect.assertions(4);
|
||||
const mockUserSkillId = 'skill1-success';
|
||||
|
||||
mock.get(authEndpoint, (req, res) => res.status(200).body('{"access_token": "token"}'));
|
||||
SkillsConfiguration.configure({
|
||||
serviceUrl: mockServiceUrl,
|
||||
projectId: mockProjectId,
|
||||
authenticator: authEndpoint,
|
||||
});
|
||||
await flushPromises();
|
||||
const handler1 = jest.fn();
|
||||
|
||||
SkillsReporter.addErrorHandler(handler1);
|
||||
|
||||
const url = `${mockServiceUrl}/api/projects/${mockProjectId}/skills/${mockUserSkillId}`;
|
||||
let count = 0;
|
||||
mock.post(url, (req, res) => {
|
||||
expect(req.header('Authorization')).toEqual('Bearer token');
|
||||
count++;
|
||||
return res.status(200).body('{"data":{"id":"abc-123"}}');
|
||||
});
|
||||
|
||||
const res = await SkillsReporter.reportSkill(mockUserSkillId);
|
||||
// sleep for 2 seconds
|
||||
await new Promise((r) => setTimeout(r, 2000));
|
||||
expect(res).toEqual({ data: { id: 'abc-123' } });
|
||||
expect(count).toEqual(1);
|
||||
expect(handler1).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('do not exceed the max retry queue size', async () => {
|
||||
const maxRetryQueueSize = 2;
|
||||
SkillsReporter.configure({ maxRetryQueueSize });
|
||||
|
||||
mock.get(authEndpoint, (req, res) => res.status(200).body('{"access_token": "token"}'));
|
||||
SkillsConfiguration.configure({
|
||||
serviceUrl: mockServiceUrl,
|
||||
projectId: mockProjectId,
|
||||
authenticator: authEndpoint,
|
||||
});
|
||||
await flushPromises();
|
||||
|
||||
const mockError = JSON.stringify({
|
||||
explanation: 'Some random error occurred - maxRetryQueueSize.', errorCode: 'RandomError', success: false, projectId: 'movies', skillId: 'IronMan', userId: 'user1',
|
||||
});
|
||||
const url = /.*\/api\/projects\/proj1\/skills\/skill[1-3]/;
|
||||
mock.post(url, (req, res) => {
|
||||
expect(req.header('Authorization')).toEqual('Bearer token');
|
||||
return res.status(503).body(mockError);
|
||||
});
|
||||
try { await SkillsReporter.reportSkill('skill1'); } catch (e) { }
|
||||
try { await SkillsReporter.reportSkill('skill2'); } catch (e) { }
|
||||
try { await SkillsReporter.reportSkill('skill3'); } catch (e) { }
|
||||
|
||||
const retryQueue = JSON.parse(window.localStorage.getItem('skillTreeRetryQueue'));
|
||||
expect(retryQueue.length).toEqual(maxRetryQueueSize);
|
||||
expect(retryQueue.find((item) => item.skillId === 'skill1')).toBeTruthy();
|
||||
expect(retryQueue.find((item) => item.skillId === 'skill2')).toBeTruthy();
|
||||
expect(retryQueue.find((item) => item.skillId === 'skill3')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('do not exceed the max retry attempts size', async () => {
|
||||
const maxRetryAttempts = 2;
|
||||
SkillsReporter.configure({ retryInterval: 60, maxRetryAttempts });
|
||||
|
||||
mock.get(authEndpoint, (req, res) => res.status(200).body('{"access_token": "token"}'));
|
||||
SkillsConfiguration.configure({
|
||||
serviceUrl: mockServiceUrl,
|
||||
projectId: mockProjectId,
|
||||
authenticator: authEndpoint,
|
||||
});
|
||||
await flushPromises();
|
||||
|
||||
const handler1 = jest.fn();
|
||||
SkillsReporter.addErrorHandler(handler1);
|
||||
const mockUserSkillId = 'skill1-random';
|
||||
const mockError = JSON.stringify({
|
||||
explanation: 'Some random error occurred - maxRetryAttempts.', errorCode: 'RandomError', success: false, projectId: 'movies', skillId: 'IronMan', userId: 'user1',
|
||||
});
|
||||
const url = `${mockServiceUrl}/api/projects/${mockProjectId}/skills/${mockUserSkillId}`;
|
||||
let count = 0;
|
||||
let timestamp = null;
|
||||
mock.post(url, (req, res) => {
|
||||
expect(req.header('Authorization')).toEqual('Bearer token');
|
||||
count++;
|
||||
if (count > 1) {
|
||||
const reqBody = JSON.parse(req.body());
|
||||
expect(reqBody.isRetry).toEqual(true); // verify that isRetry is set to true
|
||||
if (timestamp == null) {
|
||||
timestamp = reqBody.timestamp;
|
||||
} else {
|
||||
expect(timestamp).toEqual(reqBody.timestamp); // verify the timestamp remains the same
|
||||
}
|
||||
}
|
||||
return res.status(503).body(mockError);
|
||||
});
|
||||
|
||||
try {
|
||||
await SkillsReporter.reportSkill(mockUserSkillId);
|
||||
} catch (e) {
|
||||
}
|
||||
// sleep for 3 seconds
|
||||
await new Promise((r) => setTimeout(r, 3000));
|
||||
expect(count).toEqual(maxRetryAttempts + 1); // initial attempt plus 2 retries
|
||||
expect(handler1).toHaveBeenCalledWith(JSON.parse(mockError));
|
||||
|
||||
const retryQueue = JSON.parse(window.localStorage.getItem('skillTreeRetryQueue'));
|
||||
expect(retryQueue).toBeNull();
|
||||
});
|
||||
|
||||
it('reportSkill will not retry when errorCode === SkillNotFound', async () => {
|
||||
SkillsConfiguration.logout();
|
||||
expect.assertions(3);
|
||||
const mockUserSkillId = 'skill1-SkillNotFound';
|
||||
|
||||
mock.get(authEndpoint, (req, res) => res.status(200).body('{"access_token": "token"}'));
|
||||
SkillsConfiguration.configure({
|
||||
serviceUrl: mockServiceUrl,
|
||||
projectId: mockProjectId,
|
||||
authenticator: authEndpoint,
|
||||
});
|
||||
await flushPromises();
|
||||
const handler1 = jest.fn();
|
||||
const mockError = JSON.stringify({
|
||||
explanation: 'Failed to report skill event because skill definition does not exist.', errorCode: 'SkillNotFound', success: false, projectId: 'movies', skillId: 'DoesNotExist', userId: 'user1',
|
||||
});
|
||||
const body = mockError;
|
||||
const status = 503;
|
||||
|
||||
SkillsReporter.addErrorHandler(handler1);
|
||||
|
||||
const url = `${mockServiceUrl}/api/projects/${mockProjectId}/skills/${mockUserSkillId}`;
|
||||
let count = 0;
|
||||
mock.post(url, (req, res) => {
|
||||
expect(req.header('Authorization')).toEqual('Bearer token');
|
||||
count++;
|
||||
return res.status(status).body(body);
|
||||
});
|
||||
|
||||
try {
|
||||
await SkillsReporter.reportSkill(mockUserSkillId);
|
||||
} catch (e) {
|
||||
}
|
||||
// sleep for 3 seconds
|
||||
await new Promise((r) => setTimeout(r, 3000));
|
||||
expect(count).toEqual(1);
|
||||
expect(handler1).toHaveBeenCalledWith(JSON.parse(mockError));
|
||||
});
|
||||
|
||||
|
||||
it('reportSkill will retry for errors when maxRetryAttempts is undefined', async () => {
|
||||
SkillsReporter.maxRetryAttempts = undefined;
|
||||
expect.assertions(8);
|
||||
const mockUserSkillId = 'skill1-random';
|
||||
|
||||
mock.get(authEndpoint, (req, res) => res.status(200).body('{"access_token": "token"}'));
|
||||
SkillsConfiguration.configure({
|
||||
serviceUrl: mockServiceUrl,
|
||||
projectId: mockProjectId,
|
||||
authenticator: authEndpoint,
|
||||
});
|
||||
await flushPromises();
|
||||
const handler1 = jest.fn();
|
||||
const mockSuccess = '{"data":{"id":"abc-123"}}';
|
||||
const mockError = JSON.stringify({
|
||||
explanation: 'Some random error occurred.', errorCode: 'RandomError', success: false, projectId: 'movies', skillId: 'IronMan', userId: 'user1',
|
||||
});
|
||||
let body = mockError;
|
||||
let status = 503;
|
||||
|
||||
SkillsReporter.addErrorHandler(handler1);
|
||||
|
||||
const url = `${mockServiceUrl}/api/projects/${mockProjectId}/skills/${mockUserSkillId}`;
|
||||
let count = 0;
|
||||
let timestamp = null;
|
||||
mock.post(url, (req, res) => {
|
||||
expect(req.header('Authorization')).toEqual('Bearer token');
|
||||
count++;
|
||||
if (count > 1) {
|
||||
const reqBody = JSON.parse(req.body());
|
||||
expect(reqBody.isRetry).toEqual(true); // verify that isRetry is set to true
|
||||
if (timestamp == null) {
|
||||
timestamp = reqBody.timestamp;
|
||||
} else {
|
||||
expect(timestamp).toEqual(reqBody.timestamp); // verify the timestamp remains the same
|
||||
}
|
||||
}
|
||||
|
||||
// fail the first two times, then succeed after that
|
||||
if (count > 2) {
|
||||
body = mockSuccess;
|
||||
status = 200;
|
||||
}
|
||||
return res.status(status).body(body);
|
||||
});
|
||||
|
||||
try {
|
||||
await SkillsReporter.reportSkill(mockUserSkillId);
|
||||
} catch (e) {
|
||||
}
|
||||
// sleep for 3 seconds
|
||||
await new Promise((r) => setTimeout(r, 3000));
|
||||
expect(count).toEqual(3);
|
||||
expect(handler1).toHaveBeenCalledWith(JSON.parse(mockError));
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "skills-client-ng-parent",
|
||||
"version": "3.1.1",
|
||||
"version": "3.3.1",
|
||||
"license": "Apache-2.0",
|
||||
"description": "SkillTree Client Lib - Angular",
|
||||
"author": "SkillTree Team",
|
||||
@@ -27,8 +27,8 @@
|
||||
"@angular/platform-browser": "10.1.5",
|
||||
"@angular/platform-browser-dynamic": "10.1.5",
|
||||
"@angular/router": "10.1.5",
|
||||
"@skilltree/skills-client-js": "3.1.1",
|
||||
"lodash-es": "4.17.15",
|
||||
"@skilltree/skills-client-js": "3.3.1",
|
||||
"lodash-es": "4.17.21",
|
||||
"rxjs": "6.6.3",
|
||||
"tslib": "2.0.3",
|
||||
"zone.js": "0.11.1"
|
||||
@@ -40,7 +40,7 @@
|
||||
"@angular/compiler-cli": "10.1.5",
|
||||
"@types/jasmine": "3.5.14",
|
||||
"@types/jasminewd2": "2.0.8",
|
||||
"@types/lodash-es": "4.17.3",
|
||||
"@types/lodash-es": "4.17.4",
|
||||
"@types/node": "14.11.8",
|
||||
"codelyzer": "6.0.1",
|
||||
"jasmine-core": "3.6.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@skilltree/skills-client-ng",
|
||||
"version": "3.1.1",
|
||||
"version": "3.3.1",
|
||||
"license": "Apache-2.0",
|
||||
"description": "SkillTree Client Lib - Angular",
|
||||
"author": "SkillTree Team",
|
||||
@@ -8,12 +8,12 @@
|
||||
"prepare": "node -e \"console.log('APP_VERSION = \\''+ process.env.npm_package_name + '-' + process.env.npm_package_version +'\\';')\" > src/version.js"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/common": "10.0.4",
|
||||
"@angular/core": "10.0.4"
|
||||
"@angular/common": "10.1.5",
|
||||
"@angular/core": "10.1.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": "2.0.0",
|
||||
"@skilltree/skills-client-js": "3.1.1"
|
||||
"@skilltree/skills-client-js": "3.3.1"
|
||||
},
|
||||
"keywords": [
|
||||
"SkillTree",
|
||||
|
||||
+8
@@ -32,6 +32,7 @@ export class SkillsDisplayComponent implements OnInit, OnDestroy {
|
||||
@Input() version: number;
|
||||
@Input() theme: string;
|
||||
@Input() userId: string;
|
||||
@Input() handleRouteChanged: Function;
|
||||
constructor() { }
|
||||
|
||||
ngOnInit(): void {
|
||||
@@ -40,10 +41,17 @@ export class SkillsDisplayComponent implements OnInit, OnDestroy {
|
||||
version: this.version,
|
||||
theme: this.theme,
|
||||
userId: this.userId,
|
||||
handleRouteChanged: this.handleRouteChanged,
|
||||
});
|
||||
this.clientDisplay.attachTo(document.getElementById('clientDisplayContainer'));
|
||||
}
|
||||
|
||||
navigate(path: string): void {
|
||||
if (this.clientDisplay) {
|
||||
this.clientDisplay.navigate(path);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (this.clientDisplay) {
|
||||
this.clientDisplay.destroy();
|
||||
|
||||
@@ -1,8 +1,20 @@
|
||||
{
|
||||
"presets": ["@babel/preset-env", "@babel/preset-react"],
|
||||
"presets": [
|
||||
"@babel/preset-env",
|
||||
"@babel/preset-react"
|
||||
],
|
||||
"env": {
|
||||
"test": {
|
||||
"presets": [["@babel/preset-env", { "targets": { "node": "current" } }]]
|
||||
"presets": [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
"targets": {
|
||||
"node": "current"
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@skilltree/skills-client-react",
|
||||
"version": "3.1.1",
|
||||
"version": "3.3.1",
|
||||
"license": "Apache-2.0",
|
||||
"description": "SkillTree Client Lib - React",
|
||||
"main": "dist/skills-client-react.umd.min.js",
|
||||
@@ -25,9 +25,9 @@
|
||||
},
|
||||
"author": "SkillTree Team",
|
||||
"peerDependencies": {
|
||||
"prop-types": "15.X",
|
||||
"react": "16.X",
|
||||
"react-dom": "16.X",
|
||||
"prop-types": "15.X"
|
||||
"react-dom": "16.X"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "7.11.6",
|
||||
@@ -38,7 +38,10 @@
|
||||
"@rollup/plugin-commonjs": "15.1.0",
|
||||
"@rollup/plugin-node-resolve": "9.0.0",
|
||||
"@rollup/plugin-replace": "2.3.3",
|
||||
"babel-eslint": "10.1.0",
|
||||
"eslint-config-airbnb-base": "14.2.0",
|
||||
"eslint-plugin-react": "7.21.4",
|
||||
"eslint-plugin-react-hooks": "4.1.2",
|
||||
"license-check-and-add": "3.0.4",
|
||||
"license-checker": "25.0.1",
|
||||
"react": "16.13.1",
|
||||
@@ -49,17 +52,13 @@
|
||||
"rollup-plugin-buble": "0.19.8",
|
||||
"rollup-plugin-eslint": "7.0.0",
|
||||
"rollup-plugin-json": "4.0.0",
|
||||
"rollup-plugin-peer-deps-external": "^2.2.0",
|
||||
"rollup-plugin-terser": "7.0.2",
|
||||
"rollup-plugin-uglify": "6.0.4",
|
||||
"eslint-plugin-react-hooks": "4.1.2",
|
||||
"babel-eslint": "10.1.0",
|
||||
"eslint-plugin-react": "7.21.4"
|
||||
"rollup-plugin-peer-deps-external": "2.2.0",
|
||||
"rollup-plugin-terser": "7.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@skilltree/skills-client-js": "3.1.1",
|
||||
"@skilltree/skills-client-js": "3.3.1",
|
||||
"axios": "0.21.1",
|
||||
"axios-auth-refresh": "3.0.0"
|
||||
"axios-auth-refresh": "3.1.0"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "react-app"
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import React, { useEffect, forwardRef, useImperativeHandle } from 'react';
|
||||
import { SkillsDisplayJS } from '@skilltree/skills-client-js';
|
||||
import PropTypes from 'prop-types';
|
||||
import './SkillsDisplay.css';
|
||||
@@ -24,14 +24,16 @@ const destroy = (cd) => {
|
||||
cd.destroy();
|
||||
}
|
||||
};
|
||||
|
||||
// eslint-disable-next-line object-curly-newline
|
||||
const SkillDisplay = ({ theme = null, version = null, userId = null, options = {} }) => {
|
||||
React.useEffect(() => {
|
||||
const SkillDisplay = forwardRef(({ theme = null, version = null, userId = null, handleRouteChanged = null, options = {} }, ref) => {
|
||||
useEffect(() => {
|
||||
clientDisplay = new SkillsDisplayJS({
|
||||
options,
|
||||
theme,
|
||||
version,
|
||||
userId,
|
||||
handleRouteChanged,
|
||||
});
|
||||
|
||||
clientDisplay.attachTo(document.getElementById('clientSkillsDisplayContainer'));
|
||||
@@ -39,21 +41,32 @@ const SkillDisplay = ({ theme = null, version = null, userId = null, options = {
|
||||
return () => {
|
||||
destroy(clientDisplay);
|
||||
};
|
||||
}, [theme, version, userId, options]);
|
||||
}, [theme, version, userId, handleRouteChanged, options]);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
navigate(path) {
|
||||
if (clientDisplay) {
|
||||
clientDisplay.navigate(path);
|
||||
}
|
||||
},
|
||||
}), []);
|
||||
|
||||
return (<div id='clientSkillsDisplayContainer'/>);
|
||||
};
|
||||
});
|
||||
|
||||
SkillDisplay.propTypes = {
|
||||
theme: PropTypes.object,
|
||||
version: PropTypes.string,
|
||||
userId: PropTypes.string,
|
||||
handleRouteChanged: PropTypes.func,
|
||||
options: PropTypes.shape({
|
||||
serviceUrl: PropTypes.string,
|
||||
projectId: PropTypes.string,
|
||||
disableAutoScroll: PropTypes.bool,
|
||||
autoScrollStrategy: PropTypes.string,
|
||||
isSummaryOnly: PropTypes.bool,
|
||||
internalBackButton: PropTypes.bool,
|
||||
updateHistory: PropTypes.bool,
|
||||
scrollTopOffset: PropTypes.number,
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -16,12 +16,12 @@
|
||||
|
||||
import { SkillsLevelJS } from '@skilltree/skills-client-js';
|
||||
|
||||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
// eslint-disable-next-line object-curly-newline
|
||||
const SkillsLevel = ({ projectId }) => {
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
const skillLevel = new SkillsLevelJS(projectId);
|
||||
skillLevel.attachTo('.skills-level-text-display');
|
||||
}, [projectId]);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@skilltree/skills-client-vue",
|
||||
"version": "3.1.1",
|
||||
"version": "3.3.1",
|
||||
"license": "Apache-2.0",
|
||||
"description": "SkillTree Client Lib - Vue.js",
|
||||
"main": "dist/skills-client-vue.umd.min.js",
|
||||
@@ -24,13 +24,13 @@
|
||||
"devDependencies": {
|
||||
"@babel/cli": "7.11.6",
|
||||
"@babel/preset-env": "7.11.5",
|
||||
"babel-eslint": "10.1.0",
|
||||
"@rollup/plugin-alias": "3.1.1",
|
||||
"@rollup/plugin-commonjs": "15.1.0",
|
||||
"@rollup/plugin-node-resolve": "9.0.0",
|
||||
"@rollup/plugin-replace": "2.3.3",
|
||||
"@vue/test-utils": "1.1.0",
|
||||
"babel-core": "7.0.0-bridge.0",
|
||||
"babel-eslint": "10.1.0",
|
||||
"babel-jest": "26.5.2",
|
||||
"eslint-config-airbnb-base": "14.2.0",
|
||||
"eslint-plugin-import": "2.22.1",
|
||||
@@ -45,18 +45,17 @@
|
||||
"rollup-plugin-json": "4.0.0",
|
||||
"rollup-plugin-peer-deps-external": "2.2.3",
|
||||
"rollup-plugin-terser": "7.0.2",
|
||||
"rollup-plugin-uglify": "6.0.4",
|
||||
"rollup-plugin-vue": "5.1.9",
|
||||
"vue": "2.6.12",
|
||||
"vue-eslint-parser": "7.1.1",
|
||||
"vue-jest": "3.0.7",
|
||||
"vue-template-compiler": "2.6.12",
|
||||
"vue-eslint-parser": "7.1.1"
|
||||
"vue-template-compiler": "2.6.12"
|
||||
},
|
||||
"dependencies": {
|
||||
"@skilltree/skills-client-js": "3.1.1",
|
||||
"@skilltree/skills-client-js": "3.3.1",
|
||||
"axios": "0.21.1",
|
||||
"axios-auth-refresh": "3.0.0",
|
||||
"lodash": "4.17.20",
|
||||
"axios-auth-refresh": "3.1.0",
|
||||
"lodash": "4.17.21",
|
||||
"postmate": "1.5.2"
|
||||
},
|
||||
"files": [
|
||||
|
||||
@@ -38,6 +38,8 @@ limitations under the License.
|
||||
'autoScrollStrategy',
|
||||
'isSummaryOnly',
|
||||
'scrollTopOffset',
|
||||
'internalBackButton',
|
||||
'updateHistory,',
|
||||
];
|
||||
const options = { ...defaults, ...value };
|
||||
const passedOptions = Object.keys(options);
|
||||
@@ -65,6 +67,11 @@ limitations under the License.
|
||||
this.clientDisplay.version = newValue;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
navigate(path) {
|
||||
this.clientDisplay.navigate(path);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
Logger.info('SkillsDisplay::mounted');
|
||||
this.clientDisplay = new SkillsDisplayJS({
|
||||
@@ -72,6 +79,7 @@ limitations under the License.
|
||||
version: this.version,
|
||||
theme: this.theme,
|
||||
userId: this.userId,
|
||||
handleRouteChanged: (newPath) => { this.$emit('route-changed', newPath); },
|
||||
});
|
||||
Logger.info(`SkillsDisplay::created SkillsDisplayJS...calling attachTo for ref [${this.$refs.clientDisplayContainer}]`);
|
||||
this.clientDisplay.attachTo(this.$refs.clientDisplayContainer);
|
||||
|
||||
Reference in new issue
Block a user