Compare commits

...
Author SHA1 Message Date
rmmayo ee35ce9540 creating_release_version_3.3.1 2021-07-13 09:08:10 -04:00
Dima May 7639042010 Merge pull request #151 from NationalSecurityAgency/t#150/navigate
#150 - added ability to programatically "navigate" skills-display to …
2021-07-12 22:38:09 -04:00
rmmayo 61a6c3c24f #150 - removed console.log 2021-07-12 18:44:08 -04:00
rmmayo 6598bcb28d #150 - added ability to programatically "navigate" skills-display to specific route 2021-07-12 18:16:14 -04:00
Dima May 537ccdeb5d Merge pull request #147 from NationalSecurityAgency/t#146/fixed_versions
#146 - use fixed versions in package.json dependencies
2021-07-02 16:29:36 -04:00
Dima May 05b6e51f88 Merge pull request #149 from NationalSecurityAgency/t#148/remove_distro_mgmt
#148 - removed no longer used distributionManagement section from pom…
2021-07-02 16:28:20 -04:00
rmmayo 7d3f375c1a #148 - removed no longer used distributionManagement section from pom.xml 2021-07-02 08:45:46 -04:00
rmmayo 1452bdd9be #146 - use fixed versions in package.json dependencies 2021-06-29 18:05:38 -04:00
rmmayo ca3de11171 creating_release_version_3.3.0 2021-06-25 16:27:22 -04:00
Dima May cd997f6d5b Merge pull request #145 from NationalSecurityAgency/t#144/default_wo_conf
#144 - use proper default for maxRetryAttempts when it's value is und…
2021-06-25 09:47:23 -04:00
rmmayo 54a54a5b00 #144 - use proper default for maxRetryAttempts when it's value is undefined 2021-06-25 08:07:42 -04:00
Dima May c2f4df8afb Merge pull request #143 from NationalSecurityAgency/t#140/maxRetryAttempt
#140 - do not retry failures after reaching configurable maxRetryAtte…
2021-06-23 10:03:29 -04:00
rmmayo 4e4334a424 #140 - do not retry failures after reaching configurable maxRetryAttempt threshold 2021-06-23 07:04:09 -04:00
Dima May 615a8db2d1 Merge pull request #142 from NationalSecurityAgency/t#139/no_retry_unknown_skillid_2
T#139/no retry unknown skillid 2
2021-06-22 15:31:06 -04:00
rmmayo 3ba071ac00 #139 - troubleshooting CI error, passes locally 2021-06-22 14:34:06 -04:00
rmmayo 1dcc6bd29e #139 - troubleshooting CI error, passes locally 2021-06-22 13:52:28 -04:00
rmmayo 386c634a3f #139 - troubleshooting CI error, passes locally 2021-06-22 12:32:52 -04:00
rmmayo f25bfb6252 #139 - troubleshooting CI error, passes locally 2021-06-22 12:04:38 -04:00
rmmayo 1bb162bbd3 #139 - troubleshooting CI error, passes locally 2021-06-22 11:45:36 -04:00
rmmayo 7e37d927ec #139 - troubleshooting CI error, passes locally 2021-06-22 10:07:29 -04:00
rmmayo 9a488b0bc3 #139 - troubleshooting CI error, passes locally 2021-06-22 09:45:35 -04:00
rmmayo f2ca470d3f #139 - troubleshooting CI error, passes locally 2021-06-22 09:11:19 -04:00
rmmayo 2ab9b7086c #139 - troubleshooting CI error, passes locally 2021-06-22 08:47:38 -04:00
rmmayo 7b76ff01af #139 - troubleshooting CI error, passes locally 2021-06-22 08:26:31 -04:00
rmmayo 8e4e2e4948 #139 - troubleshooting CI error, passes locally 2021-06-22 08:05:44 -04:00
rmmayo 634e82d24e #139 - troubleshooting CI error, passes locally 2021-06-22 07:04:36 -04:00
rmmayo 5544723938 #139 - troubleshooting CI error, passes locally 2021-06-22 07:03:40 -04:00
rmmayo 72144d7734 #139 - troubleshooting CI error, passes locally 2021-06-22 06:29:01 -04:00
rmmayo 8af1f62bd6 #139 - troubleshooting CI error, passes locally 2021-06-21 16:45:03 -04:00
rmmayo 10c24e9bd7 #139 - troubleshooting CI error, passes locally 2021-06-21 16:44:35 -04:00
rmmayo 38b0b502c3 #139 - troubleshooting CI error, passes locally 2021-06-21 16:21:54 -04:00
rmmayo 563f9977b0 #139 - troubleshooting CI error, passes locally 2021-06-21 16:19:07 -04:00
rmmayo b78d329faa #139 - troubleshooting CI error, passes locally 2021-06-21 16:10:13 -04:00
rmmayo a777500389 #139 - troubleshooting CI error, passes locally 2021-06-21 16:02:43 -04:00
rmmayo c57c9d695f #139 - troubleshooting CI error, passes locally 2021-06-21 15:46:16 -04:00
Dima May d866ce27db Merge pull request #138 from NationalSecurityAgency/t#137/rename_route_changed
#137 - renamed skills-display-route-changed event to route-changed in…
2021-06-16 08:15:26 -04:00
Dima May edb1c05d27 Merge pull request #136 from NationalSecurityAgency/t#135/default_internal_back_false
#135 - default internalBackButton option to false in the skills-displ…
2021-06-15 16:18:45 -04:00
rmmayo 3d9e8c2571 #137 - renamed skills-display-route-changed event to route-changed in skills-client-vue 2021-06-15 13:37:36 -04:00
rmmayo 843145e436 #135 - default internalBackButton option to false in the skills-display component 2021-06-15 09:59:20 -04:00
Chris Moses 8a4fededb3 Merge pull request #134 from NationalSecurityAgency/t#133/skills-int-client-ng_runtime-error
T#133/skills int client ng runtime error
2021-06-07 09:01:28 -04:00
rmmayo 5325af1973 #133 - properly load highlight.js 2021-06-04 15:42:57 -04:00
rmmayo ed1238795c #133 - adding --force option for angular module (need to figure out a way around version mismatches when upgrading angular and using npm version >= 7.x) 2021-06-04 15:08:31 -04:00
rmmayo 410a5fc740 #133 - adding --force option for angular module (need to figure out a way around version mismatches when upgrading angular and using npm version >= 7.x) 2021-06-04 14:56:36 -04:00
rmmayo b72d51c9ac #133 - adding --force option for angular module (need to figure out a way around version mismatches when upgrading angular and using npm version >= 7.x) 2021-06-04 14:50:09 -04:00
rmmayo 1979f74084 #133 - adding --force option for angular module (need to figure out a way around version mismatches when upgrading angular and using npm version >= 7.x) 2021-06-04 14:38:31 -04:00
rmmayo 76f1481557 #133 - adding --force option for angular module (need to figure out a way around version mismatches when upgrading angular and using npm version >= 7.x) 2021-06-04 14:25:46 -04:00
rmmayo af274ee955 #133 - adding --force option for angular module (need to figure out a way around version mismatches when upgrading angular and using npm version >= 7.x) 2021-06-04 14:01:00 -04:00
rmmayo e8e63fb4ef #133 - adding --force option for angular module (need to figure out a way around version mismatches when upgrading angular and using npm version >= 7.x) 2021-06-04 12:48:23 -04:00
rmmayo 95a25b8f32 #133 - reverting skills-client-ng and skills-int-client-ng dependency changes 2021-06-04 12:46:33 -04:00
Dima May ba8194f6f9 Merge pull request #132 from NationalSecurityAgency/t#131/backwards_compat
#131 - set version of ngx-highlightjs to 4.1.3
2021-06-01 12:33:04 -04:00
rmmayo 40a6980846 #131 - set version of ngx-highlightjs to 4.1.3 2021-06-01 11:21:35 -04:00
Dima May 44cd320c20 Merge pull request #130 from NationalSecurityAgency/t#129/null_params_check
#129 - check for null params
2021-06-01 10:03:09 -04:00
rmmayo 4e1eed3858 #129 - check for null params 2021-06-01 08:36:34 -04:00
Dima May 8173a7d564 Merge pull request #128
T#127/back button
2021-05-28 10:43:23 -04:00
rmmayo 4dd5e215b0 #127 - added cypress tests. initial page load will not have skillsDisplayPath param set, so when not present use '/' 2021-05-28 07:27:19 -04:00
rmmayo 775cbff377 #127 - initial page is now loaded by skills-display on creation 2021-05-27 13:11:58 -04:00
rmmayo d58e9032a1 #127 - fixing broken jest tests 2021-05-25 18:08:32 -04:00
rmmayo 6c38e8f881 #127 - update the history manually now in the parent frame to attempt to get different browsers to behave consistently. (disabled history updates in the skills-display's vue-router settings) 2021-05-25 16:15:31 -04:00
rmmayo a67e38fa84 #125 - updating skills-int-client-ng and skills-client-ng deps to make peer dependencies happy 2021-05-17 13:53:53 -04:00
rmmayo c23212f924 #125 - updating skills-int-client-vue deps to make peer dependencies happy 2021-05-17 13:23:09 -04:00
Chris Moses 605356e400 Merge pull request #126 from NationalSecurityAgency/t#125/vue_and_reat_deps
#125 - changed  "axios-auth-refresh" version from "3.0.0" to "3.1.0" …
2021-05-17 12:22:16 -04:00
rmmayo 2ad5fe410a #125 - changed "axios-auth-refresh" version from "3.0.0" to "3.1.0" and removed "rollup-plugin-uglify" dependency 2021-05-17 11:45:43 -04:00
Dima May f729048742 Merge pull request #124 from NationalSecurityAgency/t#123/ng_deps
#123 - changed  "zone.js" version from "0.11.1" to "0.10.3"
2021-05-17 09:51:07 -04:00
rmmayo d27c5b28e2 #123 - changed "zone.js" version from "0.11.1" to "0.10.3" 2021-05-17 09:44:18 -04:00
Dima May 570e7ddfa9 Merge pull request #122 from NationalSecurityAgency/t#121/notify_when_route_changed
#121 - notify client when skills-display route has changed
2021-05-17 07:25:00 -04:00
rmmayo 831c1c4209 #121 - notify client when skills-display route has changed 2021-05-14 14:34:42 -04:00
Dima May eb23dc48d6 Merge pull request #120 from NationalSecurityAgency/t#119/backwards_compat
#119 - revert patch changes made in #111 now that the skills-display …
2021-05-12 15:55:01 -04:00
rmmayo 70372c52f9 #119 - revert patch changes made in #111 now that the skills-display default it to use the internal back button 2021-05-12 14:53:40 -04:00
Dima May e9f045a32a Merge pull request #117 from NationalSecurityAgency/t#115/upgrade_lodash
#115 - upgrading lodash
2021-05-12 11:14:32 -04:00
Dima May 8954aba225 Merge pull request #114 from NationalSecurityAgency/t#111/browser_back_button
T#111/browser back button
2021-05-11 08:55:47 -04:00
rmmayo 208af5c2ba #115 - upgrading lodash 2021-05-11 08:32:00 -04:00
Dima May 36612b526e Merge pull request #113 from NationalSecurityAgency/t#112/retry
T#112/retry
2021-05-10 14:21:20 -04:00
rmmayo ca330f78ab #111 - added cypress tests for reload/linking 2021-05-10 13:44:56 -04:00
rmmayo 4803c5181e #111 - added verbose option when apply patch 2021-05-10 11:11:03 -04:00
rmmayo 3d2184ea4e #111 - added verbose option when apply patch 2021-05-10 10:28:52 -04:00
rmmayo 9be1962510 #111 - set the proper route on the child iframe when reloading/linking 2021-05-10 09:40:15 -04:00
rmmayo 23704d3171 #111 - update patch files for backwards compat tests 2021-05-07 13:02:01 -04:00
rmmayo b214a14669 #111 - update patch files for backwards compat tests 2021-05-06 18:01:52 -04:00
rmmayo a8f28f596b #111 - update patch files for backwards compat tests 2021-05-06 16:36:14 -04:00
rmmayo f93426c6ac #111 - fixing native browser back button support in client-display. 2021-05-06 13:17:24 -04:00
rmmayo 0905b5b2b9 #112 - change default max retry queue size from 100 to 1000 2021-04-29 15:52:17 -04:00
rmmayo 692d02cb5f #112 - add property in SkillEventRequest that indicate when a reported skill is being retried and log it. Updated unit tests 2021-04-27 13:49:28 -04:00
rmmayo 58be278dd9 #112 - add to retry queue when xhr.status is 0. from https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/status: "Before the request completes, the value of status is 0. Browsers also report a status of 0 in case of XMLHttpRequest errors" 2021-04-27 10:54:17 -04:00
rmmayo 1ee43ef218 #112 - adding retry logic for failed skill reporting 2021-04-26 17:01:48 -04:00
Ryan Mayo 63af6a8bff Merge pull request #110 from evenstensberg/master
fix: linting, license dates, and import statements in react
2021-04-23 13:08:31 -04:00
Even Stensberg fc4bf72202 chore: use destructuring 2021-04-23 18:25:31 +02:00
EVOFORGE\dimay 085cf72470 creating_release_version_3.2.0 2021-04-13 07:17:17 -04:00
Dima May e770f59643 Merge pull request #109 from NationalSecurityAgency/ticket#108/proxy_user_display_config
#108 make ProxyShowSkills component configurable so that it functions…
2021-04-06 08:38:52 -04:00
mosefer 4394a28fb8 #108 make ProxyShowSkills component configurable so that it functions outside of e2e spec 2021-04-05 12:10:23 -04:00
Dima May 51bdfb4ead Merge pull request #106 from NationalSecurityAgency/t#91/single_init
T#91/single init
2021-03-30 15:03:34 -04:00
rmmayo e12308b801 #91 - moved mock responses into their own variable 2021-03-30 14:29:34 -04:00
rmmayo 18b1ba3a58 #91 - add multiple calls to SkillsConfiguration.configure() to verify only called once and no harm is done. 2021-03-30 14:23:04 -04:00
rmmayo 3fd50e096a #91 - after further discussion, to stay backwards compatible, we decided to set intialized=false but still call afterConfigure without rejection 2021-03-26 07:36:04 -04:00
rmmayo 51643e043e #91 - render example integration apps outside of SkillsConfiguration.afterConfigure() callback for error condition tests to display properly 2021-03-26 07:26:12 -04:00
rmmayo 41ad8f9214 #91 - redner example integration apps outside of SkillsConfiguration.afterConfigure() callback for error condition tests to display properly 2021-03-26 07:26:11 -04:00
rmmayo b94dec07fe #91 - added missing semicolon. configure runtimeHelpers in rollup.config.js 2021-03-26 07:26:11 -04:00
rmmayo 4219fcdc97 #91 - prevent SkillsConfiguration from attemping to initalized after successful initialization 2021-03-26 07:26:11 -04:00
Dima May e00e41651c Merge pull request #107 from NationalSecurityAgency/t#105/console_errors
#105 - log skill reporting errors to the console
2021-03-25 12:51:32 -04:00
53 changed files with 1662 additions and 192 deletions

No files matched your search

+1 -20
View File
@@ -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 { }
@@ -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>
@@ -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>
@@ -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,
@@ -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>
@@ -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
@@ -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() {
@@ -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)
@@ -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.")
}
@@ -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
@@ -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>
@@ -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; }
}
}
+4 -7
View File
@@ -1,7 +1,4 @@
const presets = [
[
"@babel/env",
],
];
module.exports = { presets };
module.exports = {
presets: ['@babel/preset-env'],
plugins: ['@babel/plugin-transform-runtime'],
};
+3 -1
View File
@@ -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",
+1
View File
@@ -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();
}
}
+78 -14
View File
@@ -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));
});
});
+4 -4
View File
@@ -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",
@@ -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();
+14 -2
View File
@@ -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"
}
}
]
]
}
}
}
+10 -11
View File
@@ -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]);
+7 -8
View File
@@ -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);