Compare commits

...
254 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
rmmayo 5236d4a386 #105 - log skill reporting errors to the console 2021-03-24 17:00:42 -04:00
Dima May 525adddfbf Merge pull request #104 from NationalSecurityAgency/t#102/ci_failures
T#102/ci failures
2021-03-19 15:06:46 -04:00
rmmayo 01cfcf68af #102 - reverting temporary enabling of workflow on push 2021-03-19 14:27:29 -04:00
rmmayo da68860c7c #102 - updated validation of no background set for non-themed specs to take into account version after css changed in skills-service/#414 2021-03-19 09:48:31 -04:00
rmmayo c7da2e3759 #102 - temporarily enable workflow on push 2021-03-19 09:10:13 -04:00
rmmayo 473aeaa902 #102 - updated validation of no background set for non-themed specs to take into account version after css changed in skills-service/#414 2021-03-19 09:09:58 -04:00
Dima May 58c919b6f9 Merge pull request #101
#100 - updated validation of no background set for non-themed specs t…
2021-03-18 15:21:26 -04:00
rmmayo fda0db88bf #100 - updated validation of no background set for non-themed specs to take into account version after css changed in skills-service/#414 2021-03-18 13:06:52 -04:00
rmmayo 07cdfb7421 #90 - enable workflow to be triggered manually 2021-03-18 09:04:50 -04:00
rmmayo a2d8a61ae9 #97 - reverting temporary enabling of workflow on push 2021-03-15 17:51:39 -04:00
Ryan Mayo 6bed3b43a2 Merge pull request #98 from NationalSecurityAgency/t#97/backwards_compat
T#97/backwards compat
2021-03-15 17:00:16 -04:00
rmmayo 61f60feb39 #97 - temporarily enable workflow on push 2021-03-15 15:51:27 -04:00
rmmayo c02439d325 #97 - updated cypress tests to be look for the proper HTTP error codes for missing skills for the version being tested (see skills-service/#271) 2021-03-15 15:49:15 -04:00
rmmayo 88cb2f4b21 #97 - updated .patch files to be look for the updated HTTP error codes for missing skills (see skills-service/#271) 2021-03-15 15:06:23 -04:00
rmmayo bdeb251c25 #97 - updated .patch files to be look for the updated HTTP error codes for missing skills (see skills-service/#271) 2021-03-15 14:49:29 -04:00
Dima May 0a04cf2d69 Merge pull request #94 from NationalSecurityAgency/t#89/default_redirect
#89 - add flag to SkillsConfiguration to indicate if when using oauth…
2021-03-02 07:25:23 -05:00
Dima May 45334d0c04 Merge pull request #96 from NationalSecurityAgency/t#95/cypress_failures
#95 - corrected HTTP error code to be 404 (vs 400) due to changes in …
2021-03-02 07:23:55 -05:00
rmmayo df6735fa76 #95 - corrected HTTP error code to be 404 (vs 400) due to changes in skills-service #272 2021-03-01 07:38:36 -05:00
rmmayo 8ac4afe087 #89 - add flag to SkillsConfiguration to indicate if when using oauth, the logic should auto redirect to the oauth authenticator when the token is unable to be retrieved due to HTTTP 401. Default value is false 2021-02-26 07:17:09 -05:00
Dima May 9c4a47fa8f Merge pull request #93 from NationalSecurityAgency/t#92/node_version
T#92/node version
2021-02-12 12:28:34 -05:00
rmmayo 5d365d695b #92 - reverting temporarily trigger backwards compat 2021-02-12 10:07:25 -05:00
rmmayo 5ac40b6339 #92 - temporarily trigger backwards compat on push 2021-02-12 09:05:27 -05:00
rmmayo 181b4096af #92 - change node version to 14.15.0 for backwards compat tests 2021-02-12 09:01:16 -05:00
rmmayo e888279fe5 #90 - add workflow_dispatch for manually triggering workflow 2021-02-08 15:05:55 -05:00
rmmayo 80d70d1532 #90 - add workflow_dispatch for manually triggering workflow 2021-02-08 15:03:53 -05:00
rmmayo 2eb4ef60a4 #90 - add workflow_dispatch for manually triggering workflow 2021-02-08 15:02:38 -05:00
rmmayo 3de3035185 #90 - changed cypress version to 6.2.0 in the patch file 2021-02-08 14:51:20 -05:00
rmmayo 2cc86d7b78 #90 - changed cypress version to 6.2.0 in the patch file 2021-02-08 14:45:43 -05:00
Dima May 71f443e840 Merge pull request #87
T#85/intercept
2021-01-04 11:53:02 -05:00
rmmayo 4abed70e49 #85 - reverted back to scheduled run 2020-12-31 13:16:00 -05:00
rmmayo 8491bfddae #85 - adding patches to fix backwards compatibility runs 2020-12-31 13:11:08 -05:00
rmmayo ce83b210dd #85 - adding patches to fix backwards compatibility runs 2020-12-31 12:24:32 -05:00
rmmayo 1ec8a224a8 #85 - adding patches to fix backwards compatibility runs 2020-12-31 11:43:36 -05:00
rmmayo df92e366b6 #85 - testing changes with backwards compatibility runs 2020-12-31 10:25:37 -05:00
rmmayo 8c5c76f1f0 #85 - testing changes with backwards compatibility runs 2020-12-31 08:10:52 -05:00
rmmayo 6e36163712 #85 - migrate cy.route() and cy.server() to cy.intercept() 2020-12-30 16:14:00 -05:00
rmmayo 6af5c1a932 #85 - migrate cy.route() and cy.server() with cy.intercept() 2020-12-30 16:12:42 -05:00
rmmayo d23be82cc3 creating_release_version_3.1.1 2020-12-29 16:58:39 -05:00
EVOFORGE\dimay acc79c5a32 #86: proper use fo request config 2020-12-29 15:49:29 -05:00
EVOFORGE\dimay ffe82fa6c9 #86: upgrade axios 2020-12-29 15:05:03 -05:00
Ryan Mayo f75c524bde Merge pull request #84 from NationalSecurityAgency/t#83/cypress_upgrade
T#83/cypress upgrade
2020-12-23 13:02:15 -05:00
rmmayo 50c62955eb #83 - revert oauth ci back to schedule 2020-12-23 13:01:43 -05:00
Dima May 503ecca40a Merge pull request #82
#81 - replace double quote constant string w/ single quotes
2020-12-23 12:45:25 -05:00
rmmayo 5267886606 #83 - added data-cy label to title for more consistent testing 2020-12-23 10:38:18 -05:00
rmmayo cbe9c40be5 #83 - upgrade cypress to latest version 2020-12-23 08:02:30 -05:00
rmmayo bd7ed55e73 #81 - replace double quote constant string w/ single quotes 2020-12-18 09:29:20 -05:00
rmmayo adbc0cff51 creating_release_version_3.1.0 2020-12-17 13:19:10 -05:00
rmmayo 5bb46a9e0d #226 - try to reduce memory footprint of hydra containers 2020-12-15 09:45:15 -05:00
Dima May aad60b5dbd Merge pull request #79
T#78/clean service url
2020-12-10 16:30:38 -05:00
rmmayo d10f2db7cd #80 - added missing 'tag' option to CliBuilder 2020-12-08 11:27:52 -05:00
rmmayo 7c8369b22a #78 - remove trailing slash and also and leading/trailing whitespace 2020-12-08 07:55:21 -05:00
rmmayo d6fe21d835 #78 - remove trailing slash and also and leading/trailing whitespace 2020-12-07 16:01:37 -05:00
Dima May 67581e228e Merge pull request #77
T#76/GitHub ci
2020-11-17 11:38:05 -05:00
rmmayo 10bdc7cb5b #76 - reverted back to schedule 2020-11-17 10:20:41 -05:00
rmmayo 04234ed81e #76 - stCarolas/setup-maven@v3 is using deprecated 'add-path' command. we may not need to use it, testing w/o it. 2020-11-17 09:10:51 -05:00
rmmayo c07ed1bbe0 #73 - Updated ProcessRunner to properly parse quoted arguments 2020-11-13 10:43:03 -05:00
rmmayo 9f277d2106 #73 - use github.workflow instead of github.job for the cypress tag 2020-11-13 07:43:49 -05:00
rmmayo 4813586eee #73 - use github.job instead of github.action for the cypress tag 2020-11-13 07:09:40 -05:00
Dima May f2a94d01d1 Merge pull request #75
T#73/cypress tags
2020-11-12 12:09:49 -05:00
rmmayo 8e3da5badd #73 - tag cypress runs with workflow action name (and version for backwards compat testing) 2020-11-12 11:44:07 -05:00
rmmayo 9489e1f000 #73 - tag cypress runs with workflow action name (and version for backwards compat testing) 2020-11-12 11:19:58 -05:00
Dima May d303ac1833 Merge pull request #74
#72 updated 2-0-1.patch and added 3-0-0.patch and 3-0-1.patch to incl…
2020-11-12 10:54:01 -05:00
rmmayo fccc44cafc #72 updated 2-0-1.patch and added 3-0-0.patch and 3-0-1.patch to includes cypress dashboard props in cypress.json 2020-11-12 10:09:19 -05:00
rmmayo d6130c3eef #71 - added legal/IP contribution documents 2020-11-10 14:41:55 -05:00
rmmayo d82385e5b9 #65 - try setting retires using env var for backwards compat tests 2020-11-10 07:51:16 -05:00
rmmayo 71365b32f5 #68 - change nodeVersion version back to v12.6.0 2020-11-09 14:17:32 -05:00
rmmayo 172c1b8324 #68 - force cleaning of node_modules in skills-int-client-react 2020-11-09 13:05:29 -05:00
rmmayo 729247898b #65 - record cypress backwards compatibility tests in cypress dashboard 2020-11-09 11:37:54 -05:00
Dima May 408370b0d7 Merge pull request #70
T#65/backwards compat dashboard
2020-11-07 15:41:37 -05:00
rmmayo 22ae2248f3 #65 - changing backwards compatibility workflows back to running on schedule vs. push/pull_request 2020-11-06 14:02:07 -05:00
rmmayo b0e49963a7 #65 - record cypress backwards compatibility tests in cypress dashboard 2020-11-06 13:15:26 -05:00
rmmayo 562fd1c5aa #65 - record cypress backwards compatibility tests in cypress dashboard 2020-11-06 12:50:31 -05:00
rmmayo 32198eff6e #65 - record cypress backwards compatibility tests in cypress dashboard 2020-11-06 12:35:47 -05:00
rmmayo ebf4599020 #68 - force cleaning of node_modules in skills-int-client-react 2020-11-06 08:11:19 -05:00
rmmayo 17dd46c50e #68 - change nodeVersion version back to v12.6.0 2020-11-05 14:28:06 -05:00
rmmayo b161e24a74 #68 - change frontend-maven-plugin version back to 1.6 2020-11-05 13:53:01 -05:00
rmmayo 608779cc9f #68 - increased node version to 14.15.0 and frontend-maven-plugin version to 10.0.2 2020-11-05 12:36:48 -05:00
rmmayo ccc8b968c3 #64 - change oauth test to run on schedule 2020-10-30 15:12:03 -04:00
Dima May ebf4aecc33 Merge pull request #67 from NationalSecurityAgency/T#64/oauth_workflow
#64 - break out OAuth CI tests into a separate workflow with it's own…
2020-10-30 14:11:14 -04:00
rmmayo 3f6bf5a9ec #66 - fix paths for 'upload result artifacts'. 2020-10-30 10:43:25 -04:00
rmmayo 2a54fcebdb #64 - break out OAuth CI tests into a separate workflow with it's own badge 2020-10-30 10:38:24 -04:00
rmmayo 8a6d7decc3 #66 - revert temporary run on push and fail on purpose 2020-10-30 10:17:51 -04:00
rmmayo 0f8d7ee427 #66 - fix paths for 'upload result artifacts'. (temporarily run on push and fail on purpose) 2020-10-30 09:37:05 -04:00
rmmayo fc21c2ccf2 #66 - fix paths for 'upload result artifacts'. (temporarily run on push and fail on purpose) 2020-10-30 09:36:17 -04:00
rmmayo 8394f5dd8f #66 - fix paths for 'upload result artifacts'. (temporarily run on push and fail on purpose) 2020-10-30 08:53:53 -04:00
Dima May bc87dca6f0 Merge pull request #59 from NationalSecurityAgency/ticket#46/upgrade_maven_dependencies
#46 upgraded maven deps to latest stable versions
2020-10-29 15:28:03 -04:00
rmmayo 5f936d0e66 #57 - pass the serviceUrl vs/ the SkillsConfiguration object to configureLogging() method 2020-10-28 14:33:42 -04:00
rmmayo 06d19f52f5 #61 - revert temporarily allowing oauth tests to run on push 2020-10-27 15:19:52 -04:00
rmmayo e8cd72ee7d #42 - upload log files as well as cypress artifacts 2020-10-27 09:24:53 -04:00
rmmayo b4f79c547e #61 - Fixed client logging messages to help troubleshoot scheduled failing oauth tests. 2020-10-27 08:51:19 -04:00
rmmayo 4216a9dfac #61 - temporarily allow oauth tests to run on push 2020-10-26 11:35:53 -04:00
rmmayo ddd1ca8254 #61 - Added client logging messages to help troubleshoot scheduled failing oauth tests. 2020-10-26 11:32:26 -04:00
Ryan Mayo 700a4cd81d Merge pull request #63 from NationalSecurityAgency/T#61/oauth_provider
#49 - changed 'Proxy Skills Display' test to use 'wrapIframe' method …
2020-10-20 07:15:39 -04:00
rmmayo 07af430261 #49 - changed 'Proxy Skills Display' test to use 'wrapIframe' method of validating iframe data which prevents race condition 2020-10-20 06:58:24 -04:00
EVOFORGE\dimay 440628895f Merge branch 'master' into T#61/oauth_provider
# Conflicts:
#	.github/workflows/build-and-test.yml
2020-10-19 13:26:53 -04:00
rmmayo 88974877d1 #57 - run cypress OAuth test on schedule, non-oauth on push/pull-request 2020-10-19 12:51:38 -04:00
rmmayo 27d54fefe3 #57 - run cypress OAuth test on schedule, non-oauth on push/pull-request 2020-10-19 12:15:33 -04:00
rmmayo a4026377c1 #61 - Allow clients to configure oauth provider id vs. url when using oauth 2020-10-19 11:57:43 -04:00
rmmayo 71af4d80b3 #57 - broke out oauth properties into separate file 2020-10-16 10:04:40 -04:00
mosefer 4801b5fdaf #46 upgrading spring boot version, adding prop-types for int-client-react, updating open source licenses 2020-10-16 07:21:58 -04:00
mosefer 4239c79325 #46 upgraded maven deps to latest stable versions 2020-10-16 07:20:23 -04:00
Ryan Mayo 315a519d38 Merge pull request #60 from NationalSecurityAgency/T#57_OAuth
T#57 o auth
2020-10-15 15:16:18 -04:00
rmmayo fccf514c95 #57 - add support for pull request branches 2020-10-15 14:29:10 -04:00
Dima May 206a1fd066 Merge pull request #58
#45 npm dependency upgrades - WIP
2020-10-15 13:14:34 -04:00
rmmayo 2c55b6b986 #57 - add support for 3rd party oauth - WIP 2020-10-15 12:36:17 -04:00
rmmayo 414cbafa3d #57 - troubleshooting failing CI tests 2020-10-15 11:34:27 -04:00
rmmayo 2ef2ea9739 #57 - troubleshooting failing CI tests 2020-10-15 11:04:04 -04:00
rmmayo bca43b417b #57 - troubleshooting failing CI tests 2020-10-15 09:38:43 -04:00
rmmayo 935e077586 #57 - troubleshooting failing CI tests 2020-10-15 09:26:50 -04:00
rmmayo 1b4686def4 #57 - run cypress tests for OAuth only, will uncomment after merge (scheduled runs only occur on master) 2020-10-15 08:41:32 -04:00
rmmayo 9e335df094 #57 - troubleshooting failing CI tests 2020-10-15 08:20:33 -04:00
rmmayo fa8a124029 #57 - troubleshooting failing CI tests 2020-10-15 08:09:53 -04:00
rmmayo aa8df4c537 #57 - add hydra support 2020-10-15 07:47:21 -04:00
rmmayo 4350468ce2 #57 - add support for 3rd party oauth - WIP 2020-10-14 14:50:18 -04:00
rmmayo a2f5a6054b #57 - add support for 3rd party oauth - WIP 2020-10-14 14:49:09 -04:00
rmmayo e5b6d53ab6 #57 - add support for 3rd party oauth - WIP 2020-10-14 14:22:31 -04:00
mosefer dcfd901c67 #45 npm dependency upgrades - WIP 2020-10-14 13:35:57 -04:00
rmmayo 40643309d7 #57 - add hydra support 2020-10-13 16:45:44 -04:00
rmmayo b02fd18f9b #57 - add hydra support 2020-10-13 15:34:20 -04:00
rmmayo a27f164a85 #57 - add hydra support 2020-10-13 14:05:45 -04:00
rmmayo 4ef1cf8e38 #57 - add hydra support 2020-10-13 13:57:10 -04:00
rmmayo 41b731f521 #57 - add hydra support 2020-10-13 13:50:54 -04:00
rmmayo 7bfa9caeeb #57 - add hydra support 2020-10-13 13:36:47 -04:00
rmmayo 46339148ec #57 - test conditional 2020-10-13 13:30:19 -04:00
rmmayo c6eb9d2115 #57 - add support for 3rd party oauth - WIP 2020-10-13 13:27:03 -04:00
rmmayo 8047b20afd #57 - test conditional 2020-10-12 13:35:47 -04:00
rmmayo 749d971976 #57 - add hydra support 2020-10-12 13:32:49 -04:00
rmmayo b417db357b #57 - test conditional 2020-10-12 12:47:42 -04:00
rmmayo 7a82c93962 #57 - add support for 3rd party oauth - WIP 2020-10-12 12:25:39 -04:00
rmmayo c63f127bff #57 - add hydra support 2020-10-12 12:23:58 -04:00
rmmayo 6981b1fda8 #57 - add hydra support 2020-10-12 12:21:40 -04:00
rmmayo 37a73243d4 #57 - add hydra support 2020-10-12 12:05:28 -04:00
rmmayo 3c72df1408 #57 - add hydra support 2020-10-12 12:03:51 -04:00
rmmayo 8c6cb5da9a #57 - add hydra support 2020-10-12 12:02:03 -04:00
rmmayo e6c792fc9a #57 - add hydra support 2020-10-12 11:59:22 -04:00
rmmayo 801e6985f5 #57 - test conditional 2020-10-12 11:50:35 -04:00
rmmayo 25698e9c61 #57 - test conditional 2020-10-12 11:09:59 -04:00
rmmayo 7f9b836d41 #57 - test conditional 2020-10-12 10:55:17 -04:00
rmmayo 2434fe4867 #57 - test conditional 2020-10-12 10:52:03 -04:00
rmmayo 4e9f2ec71b #57 - test conditional 2020-10-12 10:48:43 -04:00
rmmayo 65b476d360 #57 - test conditional 2020-10-12 10:46:47 -04:00
rmmayo 648fbc8a5a #57 - test conditional 2020-10-12 10:43:51 -04:00
rmmayo 7224558d5a #57 - test conditional 2020-10-12 10:41:13 -04:00
rmmayo 41a0fa689c #57 - test conditional 2020-10-12 10:40:13 -04:00
rmmayo 72ea56b413 #57 - add support for 3rd party oauth - WIP 2020-10-12 10:30:22 -04:00
rmmayo d2ca900a7d #57 - testing env to get event type 2020-10-12 10:28:18 -04:00
rmmayo 53b0e43a0e #57 - add support for 3rd party oauth - WIP 2020-10-07 08:18:12 -04:00
rmmayo 3dd6951125 #57 - removed dead code and added/removed comments left over from skills-client #29 2020-10-07 08:17:56 -04:00
rmmayo 7eb4522484 #57 - add support for 3rd party oauth - WIP 2020-10-05 14:20:29 -04:00
rmmayo 64c8299890 #57 - add support for 3rd party oauth - WIP 2020-10-02 12:33:18 -04:00
rmmayo 2a9bd4d4a0 #10 - test periodically failing w/ no error message. Adding std out to output to see if there is anything there that will help show to problem. 2020-09-30 07:38:00 -04:00
Ryan Mayo 73208c49ac Merge pull request #56
#55: Cypress tests retries
2020-09-25 15:57:57 -04:00
Ryan Mayo 7a323266ea Merge branch 'master' into t#55/cypress-retries 2020-09-25 15:56:18 -04:00
Ryan Mayo c5bf18c27c Merge pull request #54
#53: Record cypress test in dashboard
2020-09-25 15:49:34 -04:00
EVOFORGE\dimay fd20a3e646 #55: Cypress tests retries 2020-09-25 15:20:52 -04:00
EVOFORGE\dimay 11c8570258 #53: Record cypress test in dashboard 2020-09-25 13:01:53 -04:00
88 changed files with 3894 additions and 977 deletions

No files matched your search

+134
View File
@@ -0,0 +1,134 @@
# This is a basic workflow to help you get started with Actions
name: Continuous Integration with OAuth
on:
schedule:
- cron: '0 7 * * *'
workflow_dispatch:
#on:
# push:
# paths-ignore:
# - 'README.md'
# - .github/workflows/skills-service-backward-compat.yml
# - .github/workflows/client-libs-backward-compat.yml
# pull_request:
# paths-ignore:
# - 'README.md'
# - .github/workflows/skills-service-backward-compat.yml
# - .github/workflows/client-libs-backward-compat.yml
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "build"
build:
# The type of runner that the job will run on
runs-on: ubuntu-latest
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
- uses: actions/setup-node@v2-beta
with:
node-version: '14'
- uses: actions/setup-java@v1
with:
java-version: '11.X.X' # The JDK version to make available on the path.
- name: Print Versions
run: |
mvn --version
java -version
npm -version
- name: Cache local Maven repository
uses: actions/cache@v2
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-maven-
- name: Cache npm
uses: actions/cache@v2
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: build skill-service
run: ./.github/workflows/scripts/getSkillsService.sh
- name: setup npm links
run: ./.github/workflows/scripts/setupNpmLinks.sh
- name: build integration test apps
run: |
cd ./skills-client-integration
mvn --batch-mode clean install
cd ../
- name: run hyrda oauth service
run: ./.github/workflows/scripts/runHydra.sh
- name: Check running containers
run: docker ps -a
- name: Caputre Hydra Oauth Service logs
run: |
mkdir -p ./skills-client-integration/skills-int-e2e-test/target/logs
docker logs hydra > ./skills-client-integration/skills-int-e2e-test/target/logs/hydra.out &
docker container logs -f hydra_consent > ./skills-client-integration/skills-int-e2e-test/target/logs/hydra_consent.out &
docker container logs -f hydra_postgres > ./skills-client-integration/skills-int-e2e-test/target/logs/hydra_postgres.out &
- name: Register hydra client
run: |
cd skills-client-integration/skills-int-e2e-test/hydra
docker-compose -f quickstart.yml exec -T hydra \
hydra clients create \
--endpoint http://localhost:4445/ \
--id skilltree-test \
--secret client-secret \
--grant-types authorization_code,refresh_token \
--response-types code \
--scope openid \
--callbacks http://localhost:8080/login/oauth2/code/hydra
- name: start services for OAuth cypress tests
run: |
cd skills-client-integration/skills-int-e2e-test
npm install
npm run cyServices:start:skills-service:ci:oauth
npm run cyServices:start:integration-apps:oauth
cd ../../
- name: Run Cypress tests using OAuth
uses: cypress-io/github-action@v2
with:
working-directory: skills-client-integration/skills-int-e2e-test
record: true
parallel: false
group: 'skills-client oauth tests'
tag: "${{ github.workflow }}"
env: 'authMode=oauth'
env:
# pass the Dashboard record key as an environment variable
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
# pass GitHub token to allow accurately detecting a build vs a re-run build
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: upload result artifacts
if: ${{ always() }}
uses: actions/upload-artifact@v2
with:
name: result artifacts
path: |
./skills-service/*.jar
./skills-client-integration/skills-int-service/target/*.jar
./skills-client-integration/skills-int-e2e-test/target/logs/*.out
./skills-client-integration/skills-int-e2e-test/cypress/videos/*.mp4
./skills-client-integration/skills-int-e2e-test/cypress/screenshots/**/*.png
+23 -8
View File
@@ -13,6 +13,7 @@ on:
- 'README.md'
- .github/workflows/skills-service-backward-compat.yml
- .github/workflows/client-libs-backward-compat.yml
workflow_dispatch:
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
@@ -28,12 +29,7 @@ jobs:
- uses: actions/setup-node@v2-beta
with:
node-version: '12'
- name: Set up Maven
uses: stCarolas/setup-maven@v3
with:
maven-version: 3.6.0
node-version: '14'
- uses: actions/setup-java@v1
with:
@@ -73,8 +69,27 @@ jobs:
mvn --batch-mode clean install
cd ../
- name: run cypress tests
run: ./.github/workflows/scripts/runCypressTests.sh
- name: start services for cypress tests
run: |
cd skills-client-integration/skills-int-e2e-test
npm install
npm run cyServices:start:skills-service:ci
npm run cyServices:start:integration-apps
cd ../../
- name: Run Cypress tests
uses: cypress-io/github-action@v2
with:
working-directory: skills-client-integration/skills-int-e2e-test
record: true
parallel: false
group: 'skills-client tests'
tag: "${{ github.workflow }}"
env:
# pass the Dashboard record key as an environment variable
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
# pass GitHub token to allow accurately detecting a build vs a re-run build
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: upload result artifacts
if: ${{ always() }}
@@ -12,11 +12,12 @@ name: Client Libs Backward Compatibility
on:
schedule:
- cron: '0 5 * * *'
workflow_dispatch:
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "build"
test-backward-compat:
test-client-libs-backward-compat:
# The type of runner that the job will run on
runs-on: ubuntu-latest
@@ -27,12 +28,7 @@ jobs:
- uses: actions/setup-node@v2-beta
with:
node-version: '12'
- name: Set up Maven
uses: stCarolas/setup-maven@v3
with:
maven-version: 3.6.0
node-version: '14'
- uses: actions/setup-java@v1
with:
@@ -82,7 +78,8 @@ jobs:
cd skills-client-integration/skills-int-e2e-test
mvn --batch-mode clean package
export NO_COLOR=1
java -cp target/skills-int-e2e-test-*.jar -Dloader.main=skills.TestClientLibsBackwardsCompat org.springframework.boot.loader.PropertiesLauncher
export CYPRESS_RECORD_KEY=${{ secrets.CYPRESS_RECORD_KEY }}
java -cp target/skills-int-e2e-test-*.jar -Dloader.main=skills.TestClientLibsBackwardsCompat org.springframework.boot.loader.PropertiesLauncher -record -tag "${{ github.workflow }}"
cd ../../
- name: upload result artifacts
@@ -92,5 +89,7 @@ jobs:
name: result artifacts
path: |
./skills-client-integration/skills-int-e2e-test/target/logs/*
./skills-client-integration/skills-int-e2e-test/target/skills-client/skills-client-integration/skills-int-e2e-test/cypress/videos/*.mp4
./skills-client-integration/skills-int-e2e-test/target/skills-client/skills-client-integration/skills-int-e2e-test/cypress/screenshots/**/*.png
./skills-client-integration/skills-int-e2e-test/cypress/videos/*.mp4
./skills-client-integration/skills-int-e2e-test/cypress/screenshots/**/*.png
+10 -3
View File
@@ -18,11 +18,18 @@ echo "------- START: Build skills-service jar -------"
set -e
#set -o pipefail
git branch
git branch | grep \* | awk '{print $2}'
myGitBranch=`git branch | grep \* | awk '{print $2}'`
BRANCH_NAME=$(git branch | grep \*)
myGitBranch=${BRANCH_NAME:2}
echo "My Git Branch: [${myGitBranch}]"
if [[ "$myGitBranch" == *"HEAD detached at pull"* ]]; then
echo 'We are on a pull-request branch'
pullNumber=$(echo "$myGitBranch" | gawk -F'/' '{print $2}' | cat)
echo "Looking for branch associated with PR#${pullNumber}"
myGitBranch=$(curl -s https://api.github.com/repos/NationalSecurityAgency/skills-client/pulls/"${pullNumber}" | jq -r '.head.ref')
echo "Found new Git Branch: [${myGitBranch}]"
fi
echo "Checkout skill-service code and build it."
git clone https://github.com/NationalSecurityAgency/skills-service.git
cd ./skills-service/
+39
View File
@@ -0,0 +1,39 @@
#!/bin/bash
# exit if a command returns non-zero exit code
set -e
set -o pipefail
DB=${DB:-postgres}
TRACING=${TRACING:-false}
PROMETHEUS=${PROMETHEUS:-false}
echo "PWD: "
echo $(pwd)
cd ./skills-client-integration/skills-int-e2e-test/hydra
DC="docker-compose -f quickstart.yml"
if [[ $DB == "mysql" ]]; then
DC+=" -f quickstart-mysql.yml"
fi
if [[ $DB == "postgres" ]]; then
DC+=" -f quickstart-postgres.yml"
fi
if [[ $TRACING == true ]]; then
DC+=" -f quickstart-tracing.yml"
fi
if [[ $PROMETHEUS == true ]]; then
DC+=" -f quickstart-prometheus.yml"
fi
DC+=" up --build -d"
$DC
declare -r HOST="localhost:4445/clients"
wait-for-url() {
echo "Waiting for hydra service $1 ..."
timeout -s TERM 45 bash -c \
'while [[ "$(curl -X GET -s -o /dev/null -w ''%{http_code}'' ${0})" != "200" ]];\
do echo "Waiting for ${0}" && sleep 2;\
done' ${1}
echo "OK!"
}
wait-for-url http://${HOST}
@@ -12,11 +12,12 @@ name: Skills-service Backward Compatibility
on:
schedule:
- cron: '0 6 * * *'
workflow_dispatch:
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "build"
test-backward-compat:
test-skill-service-backward-compat:
# The type of runner that the job will run on
runs-on: ubuntu-latest
@@ -27,12 +28,7 @@ jobs:
- uses: actions/setup-node@v2-beta
with:
node-version: '12'
- name: Set up Maven
uses: stCarolas/setup-maven@v3
with:
maven-version: 3.6.0
node-version: '14'
- uses: actions/setup-java@v1
with:
@@ -68,7 +64,8 @@ jobs:
cd skills-client-integration/skills-int-e2e-test
mvn --batch-mode clean package
export NO_COLOR=1
java -cp target/skills-int-e2e-test-*.jar -Dloader.main=skills.TestDashboardBackwardCompat org.springframework.boot.loader.PropertiesLauncher
export CYPRESS_RECORD_KEY=${{ secrets.CYPRESS_RECORD_KEY }}
java -cp target/skills-int-e2e-test-*.jar -Dloader.main=skills.TestDashboardBackwardCompat org.springframework.boot.loader.PropertiesLauncher -record -tag "${{ github.workflow }}"
cd ../../
- name: upload result artifacts
@@ -77,5 +74,8 @@ jobs:
with:
name: result artifacts
path: |
./skills-client-integration/skills-int-e2e-test/target/skills-client/skills-client-integration/skills-int-e2e-test/target/logs/*.out
./skills-client-integration/skills-int-e2e-test/target/*.log
./skills-client-integration/skills-int-e2e-test/target/logs/*
./skills-client-integration/skills-int-e2e-test/target/skills-client/skills-client-integration/skills-int-e2e-test/cypress/videos/*.mp4
./skills-client-integration/skills-int-e2e-test/target/skills-client/skills-client-integration/skills-int-e2e-test/cypress/screenshots/**/*.png
./skills-client-integration/skills-int-e2e-test/cypress/videos/*.mp4
./skills-client-integration/skills-int-e2e-test/cypress/screenshots/**/*.png
+12
View File
@@ -0,0 +1,12 @@
## Legal
Consistent with Section D.6. of the GitHub Terms of Service as of 2019, and Section 5. of the Apache License, Version 2.0, the project maintainer for this project accepts contributions using the inbound=outbound model.
When you submit a pull request to this repository (inbound), you are agreeing to license your contribution under the same terms as specified in [LICENSE] (outbound).
This is an open source project.
Contributions you make to this public U.S. Government ("USG") repository are completely voluntary.
When you submit an issue, bug report, question, enhancement, pull request, etc., you are offering your contribution without expectation of payment, you expressly waive any future pay claims against the USG related to your contribution, and you acknowledge that this does not create an obligation on the part of the USG of any kind.
Furthermore, your contributing to this project does not create an employer-employee relationship between the United States ("U.S.") Government and the contributor.
[LICENSE]: LICENSE.txt
+13
View File
@@ -0,0 +1,13 @@
# Disclaimer of Warranty
This Work is provided "AS IS."
Any express or implied warranties, including but not limited to, the implied warranties of merchantability and fitness for a particular purpose are disclaimed.
In no event shall the United States Government be liable for any direct, indirect, incidental, special, exemplary or consequential damages (including, but not limited to, procurement of substitute goods or services, loss of use, data or profits, or business interruption) however caused and on any theory of liability, whether in contract, strict liability, or tort (including negligence or otherwise) arising in any way out of the use of this Work, even if advised of the possibility of such damage.
The User of this Work agrees to hold harmless and indemnify the United States Government, its agents and employees from every claim or liability (whether in tort or in contract), including attorney's fees, court costs, and expenses, arising in direct consequence of Recipient's use of the item, including, but not limited to, claims or liabilities made for injury to or death of personnel of User or third parties, damage to or destruction of property of User or third parties, and infringement or other violations of intellectual property or technical data rights.
# Disclaimer of Endorsement
Nothing in this Work is intended to constitute an endorsement, explicit or implied, by the United States Government of any particular manufacturer's product or service.
Reference herein to any specific commercial product, process, or service by trade name, trademark, manufacturer, or otherwise, in this Work does not constitute an endorsement, recommendation, or favoring by the United States Government and shall not be used for advertising or product endorsement purposes.
+19
View File
@@ -0,0 +1,19 @@
## Licensing Intent
The intent is that this software and documentation ("Project") should be treated
as if it is licensed under the license associated with the Project ("License")
in the LICENSE file. However, because we are part of the United States (U.S.)
Federal Government, it is not that simple.
The portions of this Project written by U.S. Federal Government employees within
the scope of their federal employment are ineligible for copyright protection in
the U.S.; this is generally understood to mean that these portions of the
Project are placed in the public domain.
In countries where copyright protection is available (which does not include the
U.S.), contributions made by U.S. Federal Government employees are released
under the License. Merged contributions from private contributors are released
under the License.
The SkillTree software is released under the Apache License, Version 2.0
("Apache 2.0").
+3
View File
@@ -10,9 +10,12 @@ These pages provide in-depth guidance on the installation, usage and contributio
[![CI Badge](https://github.com/NationalSecurityAgency/skills-client/workflows/Continuous%20Integration/badge.svg)](https://github.com/NationalSecurityAgency/skills-client/actions?query=workflow%3A%22Continuous+Integration%22)
[![Run integration tests using OAuth](https://github.com/NationalSecurityAgency/skills-client/workflows/Continuous%20Integration%20with%20OAuth/badge.svg)](https://github.com/NationalSecurityAgency/skills-client/actions?query=workflow%3A%22Continuous+Integration+with+OAuth%22)
[![Skills-service Backward Compatibility](https://github.com/NationalSecurityAgency/skills-client/workflows/Skills-service%20Backward%20Compatibility/badge.svg)](https://github.com/NationalSecurityAgency/skills-client/actions?query=workflow%3A%22Skills-service+Backward+Compatibility%22)
[![Client Libs Backward Compatibility](https://github.com/NationalSecurityAgency/skills-client/workflows/Client%20Libs%20Backward%20Compatibility/badge.svg)](https://github.com/NationalSecurityAgency/skills-client/actions?query=workflow%3A%22Client+Libs+Backward+Compatibility%22)
+4 -22
View File
@@ -7,13 +7,13 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<version>2.3.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>skills</groupId>
<artifactId>skills-client-integration</artifactId>
<packaging>pom</packaging>
<version>3.0.1-SNAPSHOT</version>
<version>3.3.1-SNAPSHOT</version>
<licenses>
<license>
@@ -34,26 +34,8 @@
<properties>
<!--suppress UnresolvedMavenProperty -->
<nexusServer>${nexus.url}</nexusServer>
<node.version>v12.6.0</node.version>
<frontend-maven-plugin.version>1.10.3</frontend-maven-plugin.version>
<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.0.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.0.1",
"axios": "^0.19.0",
"bootstrap": "4.3.1",
"jquery": "^3.4.1",
"js-beautify": "1.10.2"
"@skilltree/skills-client-js": "3.3.1",
"axios": "0.21.1",
"bootstrap": "4.5.2",
"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.0.1-SNAPSHOT</version>
<version>3.3.1-SNAPSHOT</version>
</parent>
<artifactId>skills-int-client-js</artifactId>
@@ -21,7 +21,7 @@
<plugin>
<groupId>com.github.eirslett</groupId>
<artifactId>frontend-maven-plugin</artifactId>
<version>1.6</version>
<version>${frontend-maven-plugin.version}</version>
<configuration>
<installDirectory>target</installDirectory>
</configuration>
@@ -1,6 +1,6 @@
{
"name": "skills-int-client-ng",
"version": "3.0.1",
"version": "3.3.1",
"license": "Apache-2.0",
"description": "SkillTree Client Lib - Angular",
"author": "SkillTree Team",
@@ -18,47 +18,47 @@
"e2e": "ng e2e"
},
"dependencies": {
"@angular/animations": "10.0.3",
"@angular/common": "10.0.3",
"@angular/compiler": "10.0.3",
"@angular/core": "10.0.3",
"@angular/forms": "10.0.3",
"@angular/localize": "10.0.4",
"@angular/platform-browser": "10.0.3",
"@angular/platform-browser-dynamic": "10.0.3",
"@angular/router": "10.0.3",
"@angular/animations": "10.1.5",
"@angular/common": "10.1.5",
"@angular/compiler": "10.1.5",
"@angular/core": "10.1.5",
"@angular/forms": "10.1.5",
"@angular/localize": "10.1.5",
"@angular/platform-browser": "10.1.5",
"@angular/platform-browser-dynamic": "10.1.5",
"@angular/router": "10.0.14",
"@ng-bootstrap/ng-bootstrap": "7.0.0",
"@skilltree/skills-client-ng": "3.0.1",
"@skilltree/skills-client-ng": "3.3.1",
"@types/js-beautify": "1.11.0",
"axios": "0.19.2",
"bootstrap": "4.5.0",
"axios": "0.21.1",
"bootstrap": "4.5.3",
"jquery": "3.5.1",
"js-beautify": "1.10.2",
"ngx-highlightjs": "4.1.0-beta",
"rxjs": "6.5.5",
"tslib": "2.0.0",
"zone.js": "0.10.3"
"js-beautify": "1.13.0",
"ngx-highlightjs": "4.1.2",
"rxjs": "6.6.3",
"tslib": "2.0.3",
"zone.js": "0.11.1"
},
"devDependencies": {
"@angular-devkit/build-angular": "0.1000.2",
"@angular/cli": "10.0.2",
"@angular/compiler-cli": "10.0.3",
"@types/jasmine": "3.5.0",
"@types/jasminewd2": "2.0.3",
"@types/node": "12.11.1",
"codelyzer": "6.0.0",
"jasmine-core": "3.5.0",
"jasmine-spec-reporter": "5.0.0",
"karma": "5.0.0",
"@angular-devkit/build-angular": "0.1001.6",
"@angular/cli": "10.1.6",
"@angular/compiler-cli": "10.1.5",
"@types/jasmine": "3.5.14",
"@types/jasminewd2": "2.0.8",
"@types/node": "14.11.8",
"codelyzer": "6.0.1",
"jasmine-core": "3.6.0",
"jasmine-spec-reporter": "6.0.0",
"karma": "5.2.3",
"karma-chrome-launcher": "3.1.0",
"karma-coverage-istanbul-reporter": "3.0.2",
"karma-jasmine": "3.3.0",
"karma-jasmine-html-reporter": "1.5.0",
"karma-coverage-istanbul-reporter": "3.0.3",
"karma-jasmine": "4.0.1",
"karma-jasmine-html-reporter": "1.5.4",
"license-check-and-add": "3.0.4",
"license-checker": "25.0.1",
"protractor": "7.0.0",
"ts-node": "8.3.0",
"tslint": "6.1.0",
"typescript": "3.9.5"
"ts-node": "9.0.0",
"tslint": "6.1.3",
"typescript": "4.0.3"
}
}
@@ -7,7 +7,7 @@
<parent>
<groupId>skills</groupId>
<artifactId>skills-client-integration</artifactId>
<version>3.0.1-SNAPSHOT</version>
<version>3.3.1-SNAPSHOT</version>
</parent>
<artifactId>skills-int-client-ng</artifactId>
@@ -21,7 +21,7 @@
<plugin>
<groupId>com.github.eirslett</groupId>
<artifactId>frontend-maven-plugin</artifactId>
<version>1.6</version>
<version>${frontend-maven-plugin.version}</version>
<configuration>
<installDirectory>target</installDirectory>
</configuration>
@@ -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,22 +1,23 @@
{
"name": "skills-int-client-react",
"version": "3.0.1",
"version": "3.3.1",
"license": "Apache-2.0",
"description": "SkillTree Client Lib Integration Tests - React",
"homepage": "/react/",
"dependencies": {
"@skilltree/skills-client-react": "3.0.1",
"axios": "0.19.0",
"bootstrap": "4.3.1",
"core-js": "3.2.1",
"highlightjs": "9.12.0",
"js-beautify": "1.10.2",
"react": "16.11.0",
"react-bootstrap": "1.0.0-beta.14",
"react-dom": "16.11.0",
"@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.1.2",
"react-router-dom": "5.1.2"
"react-router": "5.2.0",
"react-router-dom": "5.2.0"
},
"scripts": {
"serve": "PORT=8083 react-scripts start",
@@ -25,6 +26,8 @@
"licenseHeaderCheck": "license-check-and-add check -f '../license-add/license-add-config.json'",
"licenseHeaderAdd": "license-check-and-add add -f ../license-add/license-add-config.json",
"licenseCheck": "npm run licenseHeaderCheck && npm run licenseDepCheck ",
"clean": "find node_modules/ -maxdepth 1 ! -name \"@skilltree\" ! -name node_modules -exec rm -rf {} \\;",
"reinstall": "npm run clean && npm install",
"build": "npm run licenseCheck && react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
@@ -45,8 +48,8 @@
]
},
"devDependencies": {
"license-check-and-add": "^3.0.4",
"license-checker": "^25.0.1",
"react-scripts": "3.2.0"
"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.0.1-SNAPSHOT</version>
<version>3.3.1-SNAPSHOT</version>
</parent>
<artifactId>skills-int-client-react</artifactId>
@@ -21,7 +21,7 @@
<plugin>
<groupId>com.github.eirslett</groupId>
<artifactId>frontend-maven-plugin</artifactId>
<version>1.6</version>
<version>${frontend-maven-plugin.version}</version>
<configuration>
<installDirectory>target</installDirectory>
</configuration>
@@ -32,7 +32,7 @@
<goal>install-node-and-npm</goal>
</goals>
<configuration>
<nodeVersion>${node.version}</nodeVersion>
<nodeVersion>v12.6.0</nodeVersion>
</configuration>
</execution>
<execution>
@@ -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.0.1",
"version": "3.3.1",
"license": "Apache-2.0",
"description": "SkillTree Client Lib Integration Tests - Vue",
"scripts": {
@@ -14,32 +14,32 @@
"lint": "vue-cli-service lint"
},
"dependencies": {
"@skilltree/skills-client-vue": "3.0.1",
"axios": "0.19.0",
"bootstrap": "4.3.1",
"bootstrap-vue": "2.0.2",
"core-js": "^3.2.1",
"highlightjs": "9.12.0",
"js-beautify": "1.10.2",
"vue": "2.6.10",
"@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",
"highlightjs": "9.16.2",
"js-beautify": "1.13.0",
"vue": "2.6.12",
"vue-highlightjs": "1.3.3",
"vue-multiselect": "2.1.6",
"vue-router": "3.1.3",
"vue-scrollto": "2.17.1",
"vuex": "3.1.1"
"vue-router": "3.4.6",
"vue-scrollto": "2.19.1",
"vuex": "3.5.1"
},
"devDependencies": {
"@vue/cli-plugin-babel": "3.11.0",
"@vue/cli-plugin-eslint": "3.11.0",
"@vue/cli-service": "3.11.0",
"babel-eslint": "10.0.3",
"eslint": "6.5.1",
"eslint-plugin-vue": "5.2.3",
"@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": "6.2.0",
"eslint-plugin-vue": "7.0.1",
"license-check-and-add": "3.0.4",
"license-checker": "25.0.1",
"sass": "1.23.0",
"sass-loader": "8.0.0",
"vue-template-compiler": "2.6.10"
"sass": "1.27.0",
"sass-loader": "10.0.3",
"vue-template-compiler": "2.6.12"
},
"eslintConfig": {
"root": true,
@@ -7,7 +7,7 @@
<parent>
<groupId>skills</groupId>
<artifactId>skills-client-integration</artifactId>
<version>3.0.1-SNAPSHOT</version>
<version>3.3.1-SNAPSHOT</version>
</parent>
<artifactId>skills-int-client-vue</artifactId>
@@ -21,7 +21,7 @@
<plugin>
<groupId>com.github.eirslett</groupId>
<artifactId>frontend-maven-plugin</artifactId>
<version>1.6</version>
<version>${frontend-maven-plugin.version}</version>
<configuration>
<installDirectory>target</installDirectory>
</configuration>
@@ -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,13 +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(() => {
if (window.Cypress) {
window.skillsLogger = Logger;
}
new Vue({
render: h => h(App),
router,
}).$mount('#app');
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;
}
},
});
@@ -1,5 +1,10 @@
{
"baseUrl": "http://localhost:8090",
"chromeWebSecurity": false,
"defaultCommandTimeout": 10000
"defaultCommandTimeout": 10000,
"projectId": "3yas4t",
"retries": {
"runMode": 2,
"openMode": 0
}
}
@@ -19,6 +19,10 @@ const homePage = '/angular/reportSkills'
context('Angular Tests', () => {
const laterThan_1_4_0 = Utils.skillsServiceVersionLaterThan('1.4.0');
const missingSkillErrorCode = laterThan_1_4_0 ? 404 : 400;
const noThemeBackground = laterThan_1_4_0 ? 'rgba(0, 0, 0, 0)' : 'rgb(255, 255, 255)';
it('level component should be reactive', () => {
cy.createDefaultProject()
const sendEventViaDropdownId = '#PureJSReportAnySkill';
@@ -63,14 +67,14 @@ context('Angular Tests', () => {
cy.createDefaultProject()
// cy.reportSkill('subj1_skill0')
// this will increment levels for the default user = [email protected] but Level and display components display data for 'user1'
// this will increment levels for [email protected] but Level and display components display data for the proxyUser
// validate that level is still 0
cy.reportSkill('IronMan')
cy.reportSkillForUser('IronMan', '[email protected]')
cy.visitHomePage(homePage);
cy.contains('Level 0')
cy.reportSkillForUser('IronMan', 'user1')
cy.reportSkillForUser('IronMan', Cypress.env('proxyUser'))
cy.visitHomePage(homePage);
cy.contains('Level 1')
})
@@ -81,22 +85,22 @@ context('Angular Tests', () => {
cy.contains('Level 0')
cy.reportSkillForUser('IronMan', 'user1')
cy.reportSkillForUser('IronMan', Cypress.env('proxyUser'))
cy.contains('Level 1')
cy.reportSkillForUser('Thor', 'user1')
cy.reportSkillForUser('Thor', Cypress.env('proxyUser'))
cy.contains('Level 2')
cy.reportSkillForUser('subj1_skill0', 'user1')
cy.reportSkillForUser('subj1_skill0', Cypress.env('proxyUser'))
cy.contains('Level 3')
cy.reportSkillForUser('subj1_skill1', 'user1')
cy.reportSkillForUser('subj1_skill1', Cypress.env('proxyUser'))
cy.contains('Level 3')
cy.reportSkillForUser('subj2_skill0', 'user1')
cy.reportSkillForUser('subj2_skill0', Cypress.env('proxyUser'))
cy.contains('Level 4')
cy.reportSkillForUser('subj2_skill1', 'user1')
cy.reportSkillForUser('subj2_skill1', Cypress.env('proxyUser'))
cy.contains('Level 5')
})
@@ -106,7 +110,7 @@ context('Angular Tests', () => {
cy.contains('Level 0')
cy.reportSkillForUser('IronMan', 'user1')
cy.reportSkillForUser('IronMan', Cypress.env('proxyUser'))
cy.contains('Level 1')
cy.get('[data-cy=globalEventResult]').contains('"skillId": "IronMan"')
@@ -122,7 +126,7 @@ context('Angular Tests', () => {
cy.contains('Level 0')
cy.reportSkillForUser('IronMan', 'user1', 'proj2')
cy.reportSkillForUser('IronMan', Cypress.env('proxyUser'), 'proj2')
cy.contains('Level 0')
cy.get('[data-cy=globalEventResult]').should('be.empty');
@@ -130,13 +134,13 @@ context('Angular Tests', () => {
it('global event is not reported when skill is not applied', () => {
cy.createDefaultProject()
cy.reportSkillForUser('IronMan', 'user1')
cy.reportSkillForUser('IronMan', Cypress.env('proxyUser'))
cy.visitHomePage(homePage);
cy.contains('Level 1')
cy.reportSkillForUser('IronMan', 'user1')
cy.reportSkillForUser('IronMan', Cypress.env('proxyUser'))
cy.contains('Level 1')
cy.get('[data-cy=globalEventResult]').should('be.empty');
@@ -159,16 +163,15 @@ context('Angular Tests', () => {
it('skilltree directive on click', () => {
cy.createDefaultProject(1, 2, 50, 2)
cy.server().route('POST', '/api/projects/proj1/skills/IronMan').as('postSkill')
cy.intercept('POST', '/api/projects/proj1/skills/IronMan').as('postSkill')
cy.visitHomePage(homePage);
Cypress.Commands.add("clickOnDirectiveBtn", (skillApplied = true) => {
cy.get('#SkillsDirectiveClickEvent button').click()
cy.wait('@postSkill')
cy.get('@postSkill').then((xhr) => {
expect(xhr.status).to.eq(200)
expect(xhr.responseBody).to.have.property('skillApplied').to.eq(skillApplied)
cy.wait('@postSkill').then((intercept) => {
expect(intercept.response.statusCode).to.eq(200)
expect(intercept.response.body).to.have.property('skillApplied').to.eq(skillApplied)
});
})
@@ -180,16 +183,15 @@ context('Angular Tests', () => {
it('skilltree directive on input', () => {
cy.createDefaultProject(1, 2, 50, 2)
cy.server().route('POST', '/api/projects/proj1/skills/Thor').as('postSkill')
cy.intercept('POST', '/api/projects/proj1/skills/Thor').as('postSkill')
cy.visitHomePage(homePage);
Cypress.Commands.add("typeToInput", (skillApplied = true) => {
cy.get('#SkillsDirectiveInputEvent input').type('h')
cy.wait('@postSkill')
cy.get('@postSkill').then((xhr) => {
expect(xhr.status).to.eq(200)
expect(xhr.responseBody).to.have.property('skillApplied').to.eq(skillApplied)
cy.wait('@postSkill').then((intercept) => {
expect(intercept.response.statusCode).to.eq(200)
expect(intercept.response.body).to.have.property('skillApplied').to.eq(skillApplied)
});
})
@@ -200,105 +202,93 @@ context('Angular Tests', () => {
it('skilltree directive on click with error', () => {
cy.createDefaultTinyProject()
cy.server().route('POST', '/api/projects/proj1/skills/DoesNotExist').as('postSkill')
cy.intercept('POST', '/api/projects/proj1/skills/DoesNotExist').as('postSkill')
cy.visitHomePage(homePage);
cy.get('#SkillsDirectiveErrorwithButton button').click()
cy.wait('@postSkill');
cy.get('@postSkill').then((xhr) => {
expect(xhr.status).to.eq(400)
expect(xhr.responseBody).to.have.property('explanation').to.eq('Failed to report skill event because skill definition does not exist.')
cy.wait('@postSkill').then((intercept) => {
expect(intercept.response.statusCode).to.eq(missingSkillErrorCode)
expect(intercept.response.body).to.have.property('explanation').to.eq('Failed to report skill event because skill definition does not exist.')
});
})
it('skilltree directive on input with error', () => {
cy.createDefaultTinyProject()
cy.server().route('POST', '/api/projects/proj1/skills/DoesNotExist').as('postSkill')
cy.intercept('POST', '/api/projects/proj1/skills/DoesNotExist').as('postSkill')
cy.visitHomePage(homePage);
cy.get('#SkillsDirectiveErrorwithInput input').type('h')
cy.wait('@postSkill');
cy.get('@postSkill').then((xhr) => {
expect(xhr.status).to.eq(400)
expect(xhr.responseBody).to.have.property('explanation').to.eq('Failed to report skill event because skill definition does not exist.')
cy.wait('@postSkill').then((intercept) => {
expect(intercept.response.statusCode).to.eq(missingSkillErrorCode)
expect(intercept.response.body).to.have.property('explanation').to.eq('Failed to report skill event because skill definition does not exist.')
});
})
it('skill display', () => {
cy.createDefaultTinyProject()
cy.server().route('/api/users/user1/token').as('getToken')
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: 'user1', timestamp: Date.now()})
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
cy.visit('/angular/showSkills')
cy.wait('@getToken')
cy.wait('@getToken')
cy.wrapIframe().contains('My Level')
cy.wrapIframe().contains('50 Points earned Today')
cy.wrapIframe().contains('Subject 0')
cy.clientDisplay(true).contains('My Level')
cy.clientDisplay().contains('50 Points earned Today')
cy.clientDisplay().contains('Subject 0')
// verify that there is no background set
// cypress always validates against rgb
cy.wrapIframe().find('.skills-page-title-text-color')
.should('have.css', 'background-color').and('equal', 'rgb(255, 255, 255)');
cy.clientDisplay().find('.skills-page-title-text-color')
.should('have.css', 'background-color').and('equal', noThemeBackground);
})
it('skill display - summary only', () => {
cy.createDefaultTinyProject()
cy.server().route('/api/users/user1/token').as('getToken')
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: 'user1', timestamp: Date.now()})
cy.intercept(Cypress.env('tokenUrl')).as('getToken')
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
cy.visit('/angular/showSkills?isSummaryOnly=true')
cy.wait('@getToken')
cy.wait('@getToken')
cy.wrapIframe().contains('My Level')
cy.wrapIframe().contains('50 Points earned Today')
cy.wrapIframe().contains('Subject 0').should('not.exist')
cy.clientDisplay(true).contains('My Level')
cy.clientDisplay().contains('50 Points earned Today')
cy.clientDisplay().contains('Subject 0').should('not.exist')
// verify that there is no background set
// cypress always validates against rgb
cy.wrapIframe().find('.skills-page-title-text-color')
.should('have.css', 'background-color').and('equal', 'rgb(255, 255, 255)');
cy.clientDisplay().find('.skills-page-title-text-color')
.should('have.css', 'background-color').and('equal', noThemeBackground);
})
it('skill display - theme', () => {
cy.createDefaultTinyProject()
cy.server().route('/api/users/user1/token').as('getToken')
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: 'user1', timestamp: Date.now()})
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
cy.visit('/angular/showSkills?themeName=Dark Blue')
cy.wait('@getToken')
cy.wait('@getToken')
cy.wrapIframe().contains('My Level')
cy.wrapIframe().contains('50 Points earned Today')
cy.wrapIframe().contains('Subject 0')
cy.clientDisplay(true).contains('My Level')
cy.clientDisplay().contains('50 Points earned Today')
cy.clientDisplay().contains('Subject 0')
// verify dark blue background of hex #152E4d
// cypress always validates against rgb
cy.wrapIframe().find('.skills-page-title-text-color')
cy.clientDisplay().find('.skills-page-title-text-color')
.should('have.css', 'background-color').and('equal', 'rgb(21, 46, 77)');
})
it('skill display - summary only - theme', () => {
cy.createDefaultTinyProject()
cy.server().route('/api/users/user1/token').as('getToken')
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: 'user1', timestamp: Date.now()})
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
cy.visit('/angular/showSkills?themeName=Dark Blue&isSummaryOnly=true')
cy.wait('@getToken')
cy.wrapIframe().contains('My Level')
cy.wrapIframe().contains('50 Points earned Today')
cy.wrapIframe().contains('Subject 0').should('not.exist')
cy.clientDisplay(true).contains('My Level')
cy.clientDisplay().contains('50 Points earned Today')
cy.clientDisplay().contains('Subject 0').should('not.exist')
// verify dark blue background of hex #152E4d
// cypress always validates against rgb
cy.wrapIframe().find('.skills-page-title-text-color')
cy.clientDisplay().find('.skills-page-title-text-color')
.should('have.css', 'background-color').and('equal', 'rgb(21, 46, 77)');
})
it('client display should display an error if skills service is down', () => {
cy.server().route({
method: 'GET',
url: '/public/status',
status: 503, // server is down
response: {}
cy.createDefaultTinyProject()
cy.intercept('/public/status',
{
statusCode: 503, // server is down
body: {}
}).as('getStatus')
cy.visit('/angular/showSkills')
cy.wait('@getStatus')
@@ -308,39 +298,172 @@ context('Angular Tests', () => {
it('only display skills up-to the provided version', () => {
cy.createDefaultTinyProject()
cy.server().route('/api/users/user1/token').as('getToken')
cy.backendAddSkill('skillv1', 1)
cy.backendAddSkill('skillv2', 2)
cy.visit('/angular/showSkills')
cy.wait('@getToken')
cy.wait('@getToken')
cy.wrapIframe().contains('Earn up to 200 points')
cy.clientDisplay(true).contains('Earn up to 200 points')
cy.visit('/angular/reportSkills')
cy.get('#globalEventResultDiv').should('be.visible');
cy.visit('/angular/showSkills?skillsVersion=1')
cy.wait('@getToken')
cy.wrapIframe().contains('Earn up to 150 points')
cy.clientDisplay().contains('Earn up to 150 points')
cy.visit('/angular/reportSkills')
cy.get('#globalEventResultDiv').should('be.visible');
cy.visit('/angular/showSkills?skillsVersion=0')
cy.wait('@getToken')
cy.wrapIframe().contains('Earn up to 100 points')
cy.clientDisplay().contains('Earn up to 100 points')
});
it('skillsClientVersion is reported correctly', () => {
cy.createDefaultProject()
cy.intercept('POST', '/api/projects/proj1/skillsClientVersion', (req) => {
expect(req.body).to.have.property('skillsClientVersion').and.to.contain('@skilltree/skills-client-ng-')
req.reply((res) => {
expect(res.statusCode).to.eq(200)
expect(res.body).to.have.property('success').to.eq(true)
})
}).as('reportClientVersion')
cy.visit(homePage)
cy.server().route('POST', '/api/projects/proj1/skillsClientVersion').as('reportClientVersion')
cy.wait('@reportClientVersion')
cy.get('@reportClientVersion').then((xhr) => {
expect(xhr.status).to.eq(200)
expect(xhr.responseBody).to.have.property('success').to.eq(true)
});
cy.get('@reportClientVersion').should((xhr) => {
expect(xhr.request.body, 'request body').to.have.property('skillsClientVersion').and.to.contain('@skilltree/skills-client-ng-')
});
})
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]');
});
}
})
@@ -18,6 +18,10 @@ import Utils from "./Utils";
const homePage = '/native/index.html'
context("Native JS Tests", () => {
const laterThan_1_4_0 = Utils.skillsServiceVersionLaterThan('1.4.0');
const noThemeBackground = laterThan_1_4_0 ? 'rgba(0, 0, 0, 0)' : 'rgb(255, 255, 255)';
it("global event show correct results", () => {
cy.createDefaultProject();
@@ -38,8 +42,9 @@ context("Native JS Tests", () => {
cy.createDefaultProject();
cy.visitHomePage(homePage);
cy.reportSkillForUser("IronMan", "user1");
cy.reportSkillForUser("IronMan", Cypress.env('proxyUser'));
cy.get('pre[data-cy=globalEventResult]').should('be.visible')
cy.get('pre[data-cy=globalEventResult]').contains('"skillId": "IronMan"')
cy.get('pre[data-cy=globalEventResult]').contains('"pointsEarned": 50')
cy.get('pre[data-cy=globalEventResult]').contains('"skillApplied": true')
@@ -51,22 +56,22 @@ context("Native JS Tests", () => {
cy.createDefaultTinyProject('proj2')
cy.visitHomePage(homePage);
cy.reportSkillForUser('IronMan', 'user1', 'proj2')
cy.reportSkillForUser('IronMan', Cypress.env('proxyUser'), 'proj2')
cy.get('pre[data-cy=globalEventResult]').should('be.empty');
})
it('global event is not reported when skill is not applied (skill reported directly to backend endpoint)', () => {
cy.createDefaultProject()
cy.reportSkillForUser('IronMan', 'user1')
cy.reportSkillForUser('IronMan', Cypress.env('proxyUser'))
cy.visitHomePage(homePage);
cy.reportSkillForUser('IronMan', 'user1')
cy.reportSkillForUser('IronMan', Cypress.env('proxyUser'))
cy.get('[data-cy=globalEventResult]').should('be.empty');
})
it('global event is not reported when skill is not applied', () => {
cy.createDefaultProject()
cy.reportSkillForUser('IronMan', 'user1')
cy.reportSkillForUser('IronMan', Cypress.env('proxyUser'))
cy.visitHomePage(homePage);
const sendEventViaDropdownId = "#exampleDirectiveClickEvent";
@@ -80,7 +85,7 @@ context("Native JS Tests", () => {
it('global event is reported even when skill is not applied when notifyIfSkillNotApplied=true ', () => {
cy.createDefaultProject()
cy.reportSkillForUser('IronMan', 'user1')
cy.reportSkillForUser('IronMan', Cypress.env('proxyUser'))
const sendEventViaDropdownId = "#exampleDirectiveClickEvent";
Cypress.Commands.add("clickSubmit", () => {
@@ -99,11 +104,10 @@ context("Native JS Tests", () => {
it("skill display", () => {
cy.createDefaultTinyProject();
cy.server()
.route("/api/users/user1/token")
cy.intercept(Cypress.env('tokenUrl'))
.as("getToken");
cy.backendPost("/api/projects/proj1/skills/Thor", {
userId: "user1",
userId: Cypress.env('proxyUser'),
timestamp: Date.now()
});
cy.visit("/native/clientDisplay.html");
@@ -115,16 +119,15 @@ context("Native JS Tests", () => {
// verify that there is no background set
// cypress always validates against rgb
cy.wrapIframe().find('.skills-page-title-text-color')
.should('have.css', 'background-color').and('equal', 'rgb(255, 255, 255)');
.should('have.css', 'background-color').and('equal', noThemeBackground);
});
it("skill display - default options", () => {
cy.createDefaultTinyProject();
cy.server()
.route("/api/users/user1/token")
cy.intercept(Cypress.env('tokenUrl'))
.as("getToken");
cy.backendPost("/api/projects/proj1/skills/Thor", {
userId: "user1",
userId: Cypress.env('proxyUser'),
timestamp: Date.now()
});
cy.visit("/native/clientDisplayDefaultOptions.html");
@@ -137,16 +140,15 @@ context("Native JS Tests", () => {
// verify that there is no background set
// cypress always validates against rgb
cy.wrapIframe().find('.skills-page-title-text-color')
.should('have.css', 'background-color').and('equal', 'rgb(255, 255, 255)');
.should('have.css', 'background-color').and('equal', noThemeBackground);
});
it("skill display - summary only", () => {
cy.createDefaultTinyProject();
cy.server()
.route("/api/users/user1/token")
cy.intercept(Cypress.env('tokenUrl'))
.as("getToken");
cy.backendPost("/api/projects/proj1/skills/Thor", {
userId: "user1",
userId: Cypress.env('proxyUser'),
timestamp: Date.now()
});
cy.visit("/native/clientDisplay.html?isSummaryOnly=true");
@@ -159,16 +161,15 @@ context("Native JS Tests", () => {
// verify that there is no background set
// cypress always validates against rgb
cy.wrapIframe().find('.skills-page-title-text-color')
.should('have.css', 'background-color').and('equal', 'rgb(255, 255, 255)');
.should('have.css', 'background-color').and('equal', noThemeBackground);
});
it("skill display - theme", () => {
cy.createDefaultTinyProject();
cy.server()
.route("/api/users/user1/token")
cy.intercept(Cypress.env('tokenUrl'))
.as("getToken");
cy.backendPost("/api/projects/proj1/skills/Thor", {
userId: "user1",
userId: Cypress.env('proxyUser'),
timestamp: Date.now()
});
cy.visit("/native/clientDisplay.html?themeName=Dark Blue");
@@ -186,11 +187,10 @@ context("Native JS Tests", () => {
it("skill display - summary only - theme", () => {
cy.createDefaultTinyProject();
cy.server()
.route("/api/users/user1/token")
cy.intercept(Cypress.env('tokenUrl'))
.as("getToken");
cy.backendPost("/api/projects/proj1/skills/Thor", {
userId: "user1",
userId: Cypress.env('proxyUser'),
timestamp: Date.now()
});
cy.visit(
@@ -207,18 +207,15 @@ context("Native JS Tests", () => {
.should('have.css', 'background-color').and('equal', 'rgb(21, 46, 77)');
});
it("client display should display an error if skills service is down", () => {
cy.createDefaultTinyProject();
cy.server()
.route({
method: "GET",
url: "/public/status",
status: 503, // server is down
response: {}
})
.as("getStatus");
cy.visit("/native/clientDisplay.html");
cy.wait("@getStatus");
it('client display should display an error if skills service is down', () => {
cy.createDefaultTinyProject()
cy.intercept('/public/status',
{
statusCode: 503, // server is down
body: {}
}).as('getStatus')
cy.visit('/native/clientDisplay.html');
cy.wait('@getStatus')
cy.contains("Could NOT reach Skilltree Service");
});
@@ -228,8 +225,7 @@ context("Native JS Tests", () => {
const v1Points = 'Earn up to 150 points';
const v0Points = 'Earn up to 100 points';
cy.createDefaultTinyProject();
cy.server()
.route("/api/users/user1/token")
cy.intercept(Cypress.env('tokenUrl'))
.as("getToken");
cy.backendAddSkill("skillv1", 1);
cy.backendAddSkill("skillv2", 2);
@@ -238,12 +234,12 @@ context("Native JS Tests", () => {
cy.wait('@getToken')
cy.wrapIframe().contains(noVersionPoints);
cy.visit("/native/index.html");
// cy.visit("/native/index.html");
cy.visit("/native/clientDisplay.html?skillsVersion=1");
cy.wait('@getToken')
cy.wrapIframe().contains(v1Points);
cy.visit("/native/index.html");
// cy.visit("/native/index.html");
cy.visit("/native/clientDisplay.html?skillsVersion=0");
cy.wait('@getToken')
cy.wrapIframe().contains(v0Points);
@@ -251,18 +247,16 @@ context("Native JS Tests", () => {
it('skillsClientVersion is reported correctly', () => {
cy.createDefaultProject()
cy.server().route('POST', '/api/projects/proj1/skillsClientVersion').as('reportClientVersion')
cy.intercept('POST', '/api/projects/proj1/skillsClientVersion', (req) => {
expect(req.body).to.have.property('skillsClientVersion').and.to.contain('@skilltree/skills-client-js-')
req.reply((res) => {
expect(res.statusCode).to.eq(200)
expect(res.body).to.have.property('success').to.eq(true)
})
}).as('reportClientVersion')
cy.visit('/native/index.html')
cy.wait('@reportClientVersion')
cy.get('@reportClientVersion').then((xhr) => {
expect(xhr.status).to.eq(200)
expect(xhr.responseBody).to.have.property('success').to.eq(true)
});
cy.get('@reportClientVersion').should((xhr) => {
expect(xhr.request.body, 'request body').to.have.property('skillsClientVersion').and.to.contain('@skilltree/skills-client-js-')
});
})
it('level component should be reactive', () => {
@@ -282,15 +276,174 @@ context("Native JS Tests", () => {
it('level component should load initial level', () => {
cy.createDefaultProject()
// this will increment levels for the default user = [email protected] but Level and display components display data for 'user1'
// this will increment levels for [email protected] but Level and display components display data for the proxyUser
// validate that level is still 0
cy.reportSkill('subj1_skill0')
cy.reportSkillForUser('subj1_skill0', '[email protected]')
cy.visitHomePage(homePage);
cy.contains('Level 0')
cy.reportSkillForUser('subj1_skill0', 'user1')
cy.reportSkillForUser('subj1_skill0', Cypress.env('proxyUser'))
cy.visitHomePage(homePage);
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]');
});
}
});
@@ -18,28 +18,32 @@ import Utils from "./Utils";
const homePage = '/react/index.html#/'
context('React Tests', () => {
const laterThan_1_4_0 = Utils.skillsServiceVersionLaterThan('1.4.0');
const noThemeBackground = laterThan_1_4_0 ? 'rgba(0, 0, 0, 0)' : 'rgb(255, 255, 255)';
it('level component should be reactive (skills reported directly to backend endpoint)', () => {
cy.createDefaultProject()
cy.visitHomePage(homePage);
cy.contains('Level 0')
cy.reportSkillForUser('IronMan', 'user1')
cy.reportSkillForUser('IronMan', Cypress.env('proxyUser'))
cy.contains('Level 1')
cy.reportSkillForUser('Thor', 'user1')
cy.reportSkillForUser('Thor', Cypress.env('proxyUser'))
cy.contains('Level 2')
cy.reportSkillForUser('subj1_skill0', 'user1')
cy.reportSkillForUser('subj1_skill0', Cypress.env('proxyUser'))
cy.contains('Level 3')
cy.reportSkillForUser('subj1_skill1', 'user1')
cy.reportSkillForUser('subj1_skill1', Cypress.env('proxyUser'))
cy.contains('Level 3')
cy.reportSkillForUser('subj2_skill0', 'user1')
cy.reportSkillForUser('subj2_skill0', Cypress.env('proxyUser'))
cy.contains('Level 4')
cy.reportSkillForUser('subj2_skill1', 'user1')
cy.reportSkillForUser('subj2_skill1', Cypress.env('proxyUser'))
cy.contains('Level 5')
})
@@ -47,14 +51,14 @@ context('React Tests', () => {
cy.createDefaultProject()
// cy.reportSkill('subj1_skill0')
// this will increment levels for the default user = [email protected] but Level and display components display data for 'user1'
// this will increment levels for the default user = [email protected] but Level and display components display data for Cypress.env('proxyUser')
// validate that level is still 0
cy.reportSkill('IronMan')
cy.reportSkillForUser('IronMan', '[email protected]')
cy.visitHomePage(homePage);
cy.contains('Level 0')
cy.reportSkillForUser('IronMan', 'user1')
cy.reportSkillForUser('IronMan', Cypress.env('proxyUser'))
cy.visitHomePage(homePage);
cy.contains('Level 1')
})
@@ -65,7 +69,7 @@ context('React Tests', () => {
cy.contains('Level 0')
cy.reportSkillForUser('IronMan', 'user1')
cy.reportSkillForUser('IronMan', Cypress.env('proxyUser'))
cy.contains('Level 1')
cy.get('[data-cy=globalEventResult]').contains('"skillId": "IronMan"')
@@ -120,7 +124,7 @@ context('React Tests', () => {
cy.contains('Level 0')
cy.reportSkillForUser('IronMan', 'user1', 'proj2')
cy.reportSkillForUser('IronMan', Cypress.env('proxyUser'), 'proj2')
cy.contains('Level 0')
cy.get('[data-cy=globalEventResult]').should('be.empty');
@@ -138,8 +142,8 @@ context('React Tests', () => {
it('skill display', () => {
cy.createDefaultTinyProject()
cy.server().route('/api/users/user1/token').as('getToken')
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: 'user1', timestamp: Date.now()})
cy.intercept(Cypress.env('tokenUrl')).as('getToken')
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
cy.visit('/react/index.html#/showSkills')
cy.wait('@getToken')
cy.wait('@getToken')
@@ -150,13 +154,13 @@ context('React Tests', () => {
// verify that there is no background set
// cypress always validates against rgb
cy.wrapIframe().find('.skills-page-title-text-color')
.should('have.css', 'background-color').and('equal', 'rgb(255, 255, 255)');
.should('have.css', 'background-color').and('equal', noThemeBackground);
})
it('skill display - summary only', () => {
cy.createDefaultTinyProject()
cy.server().route('/api/users/user1/token').as('getToken')
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: 'user1', timestamp: Date.now()})
cy.intercept(Cypress.env('tokenUrl')).as('getToken')
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
cy.visit('/react/index.html#/showSkills?isSummaryOnly=true')
cy.wait('@getToken')
cy.wait('@getToken')
@@ -167,13 +171,13 @@ context('React Tests', () => {
// verify that there is no background set
// cypress always validates against rgb
cy.wrapIframe().find('.skills-page-title-text-color')
.should('have.css', 'background-color').and('equal', 'rgb(255, 255, 255)');
.should('have.css', 'background-color').and('equal', noThemeBackground);
})
it('skill display - theme', () => {
cy.createDefaultTinyProject()
cy.server().route('/api/users/user1/token').as('getToken')
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: 'user1', timestamp: Date.now()})
cy.intercept(Cypress.env('tokenUrl')).as('getToken')
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
cy.visit('/react/index.html#/showSkills?themeName=Dark Blue')
cy.wait('@getToken')
cy.wait('@getToken')
@@ -189,8 +193,8 @@ context('React Tests', () => {
it('skill display - summary only - theme', () => {
cy.createDefaultTinyProject()
cy.server().route('/api/users/user1/token').as('getToken')
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: 'user1', timestamp: Date.now()})
cy.intercept(Cypress.env('tokenUrl')).as('getToken')
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
cy.visit('/react/index.html#/showSkills?themeName=Dark Blue&isSummaryOnly=true')
cy.wait('@getToken')
cy.wait('@getToken')
@@ -205,13 +209,11 @@ context('React Tests', () => {
})
it('client display should display an error if skills service is down', () => {
cy.createDefaultTinyProject()
cy.server().route({
method: 'GET',
url: '/public/status',
status: 503, // server is down
response: {}
cy.createDefaultTinyProject()
cy.intercept('/public/status',
{
statusCode: 503, // server is down
body: {}
}).as('getStatus')
cy.visit('/react/index.html#/showSkills')
cy.wait('@getStatus')
@@ -221,7 +223,7 @@ context('React Tests', () => {
it('only display skills up-to the provided version', () => {
cy.createDefaultTinyProject()
cy.server().route('/api/users/user1/token').as('getToken')
cy.intercept(Cypress.env('tokenUrl')).as('getToken')
cy.backendAddSkill('skillv1', 1)
cy.backendAddSkill('skillv2', 2)
cy.visit('/react/index.html#/showSkills')
@@ -243,17 +245,150 @@ context('React Tests', () => {
it('skillsClientVersion is reported correctly', () => {
cy.createDefaultProject()
cy.intercept('POST', '/api/projects/proj1/skillsClientVersion', (req) => {
expect(req.body).to.have.property('skillsClientVersion').and.to.contain('@skilltree/skills-client-react-')
req.reply((res) => {
expect(res.statusCode).to.eq(200)
expect(res.body).to.have.property('success').to.eq(true)
})
}).as('reportClientVersion')
cy.visit('/react/index.html#/')
cy.server().route('POST', '/api/projects/proj1/skillsClientVersion').as('reportClientVersion')
cy.wait('@reportClientVersion')
cy.get('@reportClientVersion').then((xhr) => {
expect(xhr.status).to.eq(200)
expect(xhr.responseBody).to.have.property('success').to.eq(true)
});
cy.get('@reportClientVersion').should((xhr) => {
expect(xhr.request.body, 'request body').to.have.property('skillsClientVersion').and.to.contain('@skilltree/skills-client-react-')
});
})
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]');
});
}
})
@@ -19,6 +19,12 @@ 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 => {
// const skillsLogger = win.skillsLogger;
@@ -66,14 +72,14 @@ context('Vue Tests', () => {
cy.createDefaultProject()
// cy.reportSkill('subj1_skill0')
// this will increment levels for the default user = [email protected] but Level and display components display data for 'user1'
// this will increment levels for [email protected] but Level and display components display data for the proxyUser
// validate that level is still 0
cy.reportSkill('IronMan')
cy.reportSkillForUser('IronMan', '[email protected]')
cy.visitHomePage(homePage);
cy.contains('Level 0')
cy.reportSkillForUser('IronMan', 'user1')
cy.reportSkillForUser('IronMan', Cypress.env('proxyUser'))
cy.visitHomePage(homePage);
cy.contains('Level 1')
})
@@ -84,22 +90,22 @@ context('Vue Tests', () => {
cy.contains('Level 0')
cy.reportSkillForUser('IronMan', 'user1')
cy.reportSkillForUser('IronMan', Cypress.env('proxyUser'))
cy.contains('Level 1')
cy.reportSkillForUser('Thor', 'user1')
cy.reportSkillForUser('Thor', Cypress.env('proxyUser'))
cy.contains('Level 2')
cy.reportSkillForUser('subj1_skill0', 'user1')
cy.reportSkillForUser('subj1_skill0', Cypress.env('proxyUser'))
cy.contains('Level 3')
cy.reportSkillForUser('subj1_skill1', 'user1')
cy.reportSkillForUser('subj1_skill1', Cypress.env('proxyUser'))
cy.contains('Level 3')
cy.reportSkillForUser('subj2_skill0', 'user1')
cy.reportSkillForUser('subj2_skill0', Cypress.env('proxyUser'))
cy.contains('Level 4')
cy.reportSkillForUser('subj2_skill1', 'user1')
cy.reportSkillForUser('subj2_skill1', Cypress.env('proxyUser'))
cy.contains('Level 5')
})
@@ -109,7 +115,7 @@ context('Vue Tests', () => {
cy.contains('Level 0')
cy.reportSkillForUser('IronMan', 'user1')
cy.reportSkillForUser('IronMan', Cypress.env('proxyUser'))
cy.contains('Level 1')
cy.get('[data-cy=globalEventResult]').contains('"skillId": "IronMan"')
@@ -117,6 +123,7 @@ context('Vue Tests', () => {
cy.get('[data-cy=globalEventResult]').contains('"skillApplied": true')
cy.get('[data-cy=globalEventResult]').contains(/completed": [[][^]*"type": "Overall",[^]\s*"level": 1/)
})
it('global event does not update when skill reported for a different project', () => {
cy.createDefaultProject()
cy.createDefaultTinyProject('proj2')
@@ -124,24 +131,26 @@ context('Vue Tests', () => {
cy.contains('Level 0')
cy.reportSkillForUser('IronMan', 'user1', 'proj2')
cy.reportSkillForUser('IronMan', Cypress.env('proxyUser'), 'proj2')
cy.contains('Level 0')
cy.get('[data-cy=globalEventResult]').should('be.empty');
})
it('global event is not reported when skill is not applied', () => {
cy.createDefaultProject()
cy.reportSkillForUser('IronMan', 'user1')
cy.reportSkillForUser('IronMan', Cypress.env('proxyUser'))
cy.visitHomePage(homePage);
cy.contains('Level 1')
cy.reportSkillForUser('IronMan', 'user1')
cy.reportSkillForUser('IronMan', Cypress.env('proxyUser'))
cy.contains('Level 1')
cy.get('[data-cy=globalEventResult]').should('be.empty');
})
it('level component should not update when admin reports skill for other user', () => {
cy.createDefaultProject()
@@ -159,16 +168,15 @@ context('Vue Tests', () => {
it('v-skill directive on click', () => {
cy.createDefaultProject(1, 2, 50, 2)
cy.server().route('POST', '/api/projects/proj1/skills/IronMan').as('postSkill')
cy.intercept('POST', '/api/projects/proj1/skills/IronMan').as('postSkill')
cy.visit('/vuejs#/')
Cypress.Commands.add("clickOnDirectiveBtn", (skillApplied = true) => {
cy.get('#SkillsDirectiveClickEvent button').click()
cy.wait('@postSkill')
cy.get('@postSkill').then((xhr) => {
expect(xhr.status).to.eq(200)
expect(xhr.responseBody).to.have.property('skillApplied').to.eq(skillApplied)
cy.wait('@postSkill').then((intercept) => {
expect(intercept.response.statusCode).to.eq(200)
expect(intercept.response.body).to.have.property('skillApplied').to.eq(skillApplied)
});
})
@@ -177,19 +185,17 @@ context('Vue Tests', () => {
cy.clickOnDirectiveBtn(false)
})
it('v-skill directive on input', () => {
cy.createDefaultProject(1, 2, 50, 2)
cy.server().route('POST', '/api/projects/proj1/skills/Thor').as('postSkill')
cy.intercept('POST', '/api/projects/proj1/skills/Thor').as('postSkill')
cy.visitHomePage(homePage);
Cypress.Commands.add("typeToInput", (skillApplied = true) => {
cy.get('#SkillsDirectiveInputEvent input').type('h')
cy.wait('@postSkill')
cy.get('@postSkill').then((xhr) => {
expect(xhr.status).to.eq(200)
expect(xhr.responseBody).to.have.property('skillApplied').to.eq(skillApplied)
cy.wait('@postSkill').then((intercept) => {
expect(intercept.response.statusCode).to.eq(200)
expect(intercept.response.body).to.have.property('skillApplied').to.eq(skillApplied)
});
})
@@ -200,136 +206,124 @@ context('Vue Tests', () => {
it('v-skill directive on click with error', () => {
cy.createDefaultTinyProject()
cy.server().route('POST', '/api/projects/proj1/skills/DoesNotExist').as('postSkill')
cy.intercept('POST', '/api/projects/proj1/skills/DoesNotExist').as('postSkill')
cy.visitHomePage(homePage);
cy.get('#SkillsDirectiveErrorwithButton button').click()
cy.wait('@postSkill');
cy.get('@postSkill').then((xhr) => {
expect(xhr.status).to.eq(400)
expect(xhr.responseBody).to.have.property('explanation').to.eq('Failed to report skill event because skill definition does not exist.')
cy.wait('@postSkill').then((intercept) => {
expect(intercept.response.statusCode).to.eq(missingSkillErrorCode)
expect(intercept.response.body).to.have.property('explanation').to.eq('Failed to report skill event because skill definition does not exist.')
});
})
it('v-skill directive on input with error', () => {
cy.createDefaultTinyProject()
cy.server().route('POST', '/api/projects/proj1/skills/DoesNotExist').as('postSkill')
cy.intercept('POST', '/api/projects/proj1/skills/DoesNotExist').as('postSkill')
cy.visitHomePage(homePage);
cy.get('#SkillsDirectiveErrorwithInput input').type('h')
cy.wait('@postSkill');
cy.get('@postSkill').then((xhr) => {
expect(xhr.status).to.eq(400)
expect(xhr.responseBody).to.have.property('explanation').to.eq('Failed to report skill event because skill definition does not exist.')
cy.wait('@postSkill').then((intercept) => {
expect(intercept.response.statusCode).to.eq(missingSkillErrorCode)
expect(intercept.response.body).to.have.property('explanation').to.eq('Failed to report skill event because skill definition does not exist.')
});
})
it('skill display', () => {
cy.createDefaultTinyProject()
cy.server().route('/api/users/user1/token').as('getToken')
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: 'user1', timestamp: Date.now()})
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
cy.visit('/vuejs#/showSkills')
cy.wait('@getToken')
cy.wait('@getToken')
cy.wrapIframe().contains('My Level')
cy.wrapIframe().contains('50 Points earned Today')
cy.wrapIframe().contains('Subject 0')
cy.clientDisplay(true).contains('My Level')
cy.clientDisplay().contains('50 Points earned Today')
cy.clientDisplay().contains('Subject 0')
// verify that there is no background set
// cypress always validates against rgb
cy.wrapIframe().find('.skills-page-title-text-color')
.should('have.css', 'background-color').and('equal', 'rgb(255, 255, 255)');
cy.clientDisplay().find('.skills-page-title-text-color')
.should('have.css', 'background-color').and('equal', noThemeBackground);
})
it('Proxy Skills Display', () => {
cy.createDefaultTinyProject()
cy.createDefaultTinyProject('proj2')
if (!Cypress.env('oauthMode')) {
cy.createDefaultTinyProject()
cy.createDefaultTinyProject('proj2')
cy.server().route('/api/users/user1/token').as('getToken')
cy.server().route('/api/users/proj2/user2/token').as('getToken2')
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: 'user1', timestamp: Date.now()})
cy.visit('/vuejs#/showSkills')
cy.wait('@getToken')
cy.iframe((body) => {
cy.intercept(Cypress.env('tokenUrl')).as('getToken')
cy.intercept('/api/users/proj2/user2/token').as('getToken2')
cy.intercept('api/projects/proj1/rank').as('getRankUser2')
cy.intercept('api/projects/proj1/pointHistory').as('getPointsHistoryUser2')
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
cy.visit('/vuejs#/showSkills')
cy.wait('@getToken')
cy.wrap(body).contains('My Level')
cy.wrap(body).contains('50 Points earned Today')
cy.wrap(body).contains('Subject 0')
// cy.wait('@getToken')
cy.clientDisplay(true).contains('My Level')
cy.clientDisplay().contains('50 Points earned Today')
cy.clientDisplay().contains('Subject 0')
// verify that there is no background set
// cypress always validates against rgb
cy.wrap(body).find('.skills-page-title-text-color')
.should('have.css', 'background-color').and('equal', 'rgb(255, 255, 255)');
});
cy.clientDisplay().find('.skills-page-title-text-color')
.should('have.css', 'background-color').and('equal', noThemeBackground);
cy.visit('/vuejs#/proxyShowSkills')
cy.wait('@getToken2')
cy.iframe((body) => {
cy.wrap(body).contains('My Level')
cy.wrap(body).contains('0 Points earned Today')
})
cy.visit('/vuejs#/proxyShowSkills')
cy.wait('@getToken2')
cy.wait('@getRankUser2')
cy.wait('@getPointsHistoryUser2')
cy.clientDisplay().contains('My Level')
cy.clientDisplay().contains('0 Points earned Today')
}
})
it('skill display - summary only', () => {
cy.createDefaultTinyProject()
cy.server().route('/api/users/user1/token').as('getToken')
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: 'user1', timestamp: Date.now()})
cy.intercept(Cypress.env('tokenUrl')).as('getToken')
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
cy.visit('/vuejs#/showSkills?isSummaryOnly=true')
cy.wait('@getToken')
cy.wait('@getToken')
cy.wrapIframe().contains('My Level')
cy.wrapIframe().contains('50 Points earned Today')
cy.wrapIframe().contains('Subject 0').should('not.exist')
cy.clientDisplay(true).contains('My Level')
cy.clientDisplay().contains('50 Points earned Today')
cy.clientDisplay().contains('Subject 0').should('not.exist')
// verify that there is no background set
// cypress always validates against rgb
cy.wrapIframe().find('.skills-page-title-text-color')
.should('have.css', 'background-color').and('equal', 'rgb(255, 255, 255)');
cy.clientDisplay().find('.skills-page-title-text-color')
.should('have.css', 'background-color').and('equal', noThemeBackground);
})
it('skill display - theme', () => {
cy.createDefaultTinyProject()
cy.server().route('/api/users/user1/token').as('getToken')
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: 'user1', timestamp: Date.now()})
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
cy.visit('/vuejs#/showSkills?themeName=Dark Blue')
cy.wait('@getToken')
cy.wait('@getToken')
cy.wrapIframe().contains('My Level')
cy.wrapIframe().contains('50 Points earned Today')
cy.wrapIframe().contains('Subject 0')
cy.clientDisplay(true).contains('My Level')
cy.clientDisplay().contains('50 Points earned Today')
cy.clientDisplay().contains('Subject 0')
// verify dark blue background of hex #152E4d
// cypress always validates against rgb
cy.wrapIframe().find('.skills-page-title-text-color')
cy.clientDisplay().find('.skills-page-title-text-color')
.should('have.css', 'background-color').and('equal', 'rgb(21, 46, 77)');
})
it('skill display - summary only - theme', () => {
cy.createDefaultTinyProject()
cy.server().route('/api/users/user1/token').as('getToken')
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: 'user1', timestamp: Date.now()})
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
cy.visit('/vuejs#/showSkills?themeName=Dark Blue&isSummaryOnly=true')
cy.wait('@getToken')
cy.wait('@getToken')
cy.wrapIframe().contains('My Level')
cy.wrapIframe().contains('50 Points earned Today')
cy.wrapIframe().contains('Subject 0').should('not.exist')
cy.clientDisplay(true).contains('My Level')
cy.clientDisplay().contains('50 Points earned Today')
cy.clientDisplay().contains('Subject 0').should('not.exist')
// verify dark blue background of hex #152E4d
// cypress always validates against rgb
cy.wrapIframe().find('.skills-page-title-text-color')
cy.clientDisplay().find('.skills-page-title-text-color')
.should('have.css', 'background-color').and('equal', 'rgb(21, 46, 77)');
})
it('client display should display an error if skills service is down', () => {
cy.server().route({
method: 'GET',
url: '/public/status',
status: 503, // server is down
response: {}
cy.createDefaultTinyProject()
cy.intercept('/public/status',
{
statusCode: 503, // server is down
body: {}
}).as('getStatus')
cy.visit('/vuejs#/showSkills')
cy.wait('@getStatus')
@@ -339,39 +333,37 @@ context('Vue Tests', () => {
it('only display skills up-to the provided version', () => {
cy.createDefaultTinyProject()
cy.server().route('/api/users/user1/token').as('getToken')
cy.backendAddSkill('skillv1', 1)
cy.backendAddSkill('skillv2', 2)
cy.visit('/vuejs#/showSkills')
cy.wait('@getToken')
cy.wait('@getToken')
cy.wrapIframe().contains('Earn up to 200 points')
cy.clientDisplay(true).contains('Earn up to 200 points')
cy.visit('/vuejs#/')
cy.get('[data-cy=reportSkillsLink]').should('be.visible');
cy.visit('/vuejs#/showSkills?skillsVersion=1')
cy.wait('@getToken')
cy.wrapIframe().contains('Earn up to 150 points')
cy.clientDisplay().contains('Earn up to 150 points')
cy.visit('/vuejs#/')
cy.get('[data-cy=reportSkillsLink]').should('be.visible');
cy.visit('/vuejs#/showSkills?skillsVersion=0')
cy.wait('@getToken')
cy.wrapIframe().contains('Earn up to 100 points')
cy.clientDisplay().contains('Earn up to 100 points')
});
it('skillsClientVersion is reported correctly', () => {
cy.createDefaultProject()
cy.intercept('POST', '/api/projects/proj1/skillsClientVersion', (req) => {
expect(req.body).to.have.property('skillsClientVersion').and.to.contain('@skilltree/skills-client-vue-')
req.reply((res) => {
expect(res.statusCode).to.eq(200)
expect(res.body).to.have.property('success').to.eq(true)
})
}).as('reportClientVersion')
cy.visit('/vuejs#/')
cy.server().route('POST', '/api/projects/proj1/skillsClientVersion').as('reportClientVersion')
cy.wait('@reportClientVersion')
cy.get('@reportClientVersion').then((xhr) => {
expect(xhr.status).to.eq(200)
expect(xhr.responseBody).to.have.property('success').to.eq(true)
});
cy.get('@reportClientVersion').should((xhr) => {
expect(xhr.request.body, 'request body').to.have.property('skillsClientVersion').and.to.contain('@skilltree/skills-client-vue-')
});
})
/*it('Proxy User Display', () => {
@@ -394,8 +386,8 @@ context('Vue Tests', () => {
cy.clickSubmit()
cy.contains('Level 1')
cy.server().route('/api/users/user1/token').as('getToken')
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: 'user1', timestamp: Date.now()})
cy.intercept(Cypress.env('tokenUrl')).as('getToken')
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
cy.get('[data-cy=userDisplayLink]').click();
//cy.contains('User Display').click();
/!*cy.wait('@getToken')
@@ -416,20 +408,20 @@ context('Vue Tests', () => {
if (Utils.skillsServiceVersionLaterThan('1.2.0')) {
it('back button when when returning from an external page', () => {
cy.createDefaultTinyProject()
cy.server().route('/api/users/user1/token').as('getToken')
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: 'user1', timestamp: Date.now()})
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('/vuejs#/showSkills?refreshPage=false')
cy.wait('@getToken')
cy.visit('/vuejs#/showSkills'+clientDisplayWithInternalBackButtonParams)
// cy.wait('@getToken')
cy.wrapIframe().find('[data-cy=back]').should('not.exist');
cy.wrapIframe().contains('User Skills');
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.wrapIframe().find('[data-cy=myRank]').click();
cy.wrapIframe().contains('Rank Overview');
cy.wrapIframe().find('[data-cy=back]').should('exist');
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()
@@ -443,23 +435,21 @@ context('Vue Tests', () => {
if (Utils.skillsServiceVersionLaterThan('1.2.0')) {
it('back button when when returning from an external page - multiple layers deep', () => {
cy.createDefaultTinyProject()
cy.server().route('/api/users/user1/token').as('getToken')
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: 'user1', timestamp: Date.now()})
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
// visit client display
cy.visit('/vuejs#/showSkills?refreshPage=false')
cy.wait('@getToken')
cy.visit('/vuejs#/showSkills'+clientDisplayWithInternalBackButtonParams)
cy.wrapIframe().find('[data-cy=back]').should('not.exist');
cy.wrapIframe().contains('User Skills');
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.wrapIframe().find('[data-cy=myRank]').click();
cy.wrapIframe().contains('Rank Overview');
cy.wrapIframe().find('[data-cy=back]').should('exist');
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()
@@ -468,7 +458,7 @@ context('Vue Tests', () => {
// switch back to the the client display
cy.get('[data-cy=userDisplayLink]').click()
// verify we are still on the Rank Overview page
cy.wrapIframe().contains('Rank Overview');
cy.clientDisplay(true).contains('Rank Overview');
// click the back button and verify that we are still in the
// client display (Subject page)
@@ -479,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]');
});
}
})
@@ -40,160 +40,253 @@
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
const backend = 'http://localhost:8080';
const baseUrl = Cypress.config().baseUrl;
Cypress.Commands.add("backendRegister", (user, pass, grantRoot) => {
return cy.request(`${backend}/app/users/validExistingDashboardUserId/${user}`)
.then((response) => {
if (response.body !== true) {
cy.log(`Creating user [${user}]`)
cy.request('PUT', `${backend}/createAccount`, {
firstName: 'Firstname',
lastName: 'LastName',
email: user,
password: pass,
});
if (grantRoot) {
cy.request('POST', `${backend}/grantFirstRoot`);
}
cy.request('POST', `${backend}/logout`);
} else {
cy.log(`User [${user}] already exist`)
}
return cy.request(`${backend}/app/users/validExistingDashboardUserId/${user}`)
.then((response) => {
if (response.body !== true) {
cy.log(`Creating user [${user}]`)
cy.request('PUT', `${backend}/createAccount`, {
firstName: 'Firstname',
lastName: 'LastName',
email: user,
password: pass,
});
if (grantRoot) {
cy.request('POST', `${backend}/grantFirstRoot`);
}
cy.request('POST', `${backend}/logout`);
} else {
cy.log(`User [${user}] already exists`)
}
});
});
Cypress.Commands.add("backendLogin", (user, pass) => {
cy.request( {
method: 'POST',
url: `${backend}/performLogin`,
body: {
username: user,
password: pass
},
form: true,
})
cy.request({
method: 'POST',
url: `${backend}/performLogin`,
body: {
username: user,
password: pass
},
form: true,
})
});
Cypress.Commands.add("backendLogout", () => {
cy.request('POST', `${backend}/logout`);
cy.request('POST', `${backend}/logout`);
});
Cypress.Commands.add("backendPost", (url, body) => {
cy.request('POST', `${backend}${url}`, body)
cy.request('POST', `${backend}${url}`, body)
});
Cypress.Commands.add("reportSkill", (skillId, projId = 'proj1') => {
cy.backendPost(`/api/projects/${projId}/skills/${skillId}`)
cy.backendPost(`/api/projects/${projId}/skills/${skillId}`)
})
Cypress.Commands.add("reportSkillForUser", (skillId, userId, projId = 'proj1', timestamp = Date.now()) => {
cy.backendPost(`/api/projects/${projId}/skills/${skillId}`, {userId: userId, timestamp: timestamp})
cy.backendPost(`/api/projects/${projId}/skills/${skillId}`, { userId: userId, timestamp: timestamp })
})
Cypress.Commands.add("backendAddSkill", (skillId, version = 0, projId ='proj1', subjId='subj0', pointIncrement = 50, numPerformToCompletion = 1) => {
cy.backendPost(`/admin/projects/${projId}/subjects/${subjId}/skills/${skillId}`, {
projectId: projId,
subjectId: subjId,
skillId: skillId,
name: `This is ${skillId}`,
type: "Skill",
pointIncrement: pointIncrement,
numPerformToCompletion: numPerformToCompletion,
pointIncrementInterval: 0,
numMaxOccurrencesIncrementInterval: -1,
description: "This skill a skill!",
version: version,
})
Cypress.Commands.add("backendAddSkill", (skillId, version = 0, projId = 'proj1', subjId = 'subj0', pointIncrement = 50, numPerformToCompletion = 1) => {
cy.backendPost(`/admin/projects/${projId}/subjects/${subjId}/skills/${skillId}`, {
projectId: projId,
subjectId: subjId,
skillId: skillId,
name: `This is ${skillId}`,
type: "Skill",
pointIncrement: pointIncrement,
numPerformToCompletion: numPerformToCompletion,
pointIncrementInterval: 0,
numMaxOccurrencesIncrementInterval: -1,
description: "This skill a skill!",
version: version,
})
});
Cypress.Commands.add("createDefaultProject", (numSubj = 3, numSkillsPerSubj = 2, pointIncrement = 50, numPerformToCompletion = 1, projId ='proj1') => {
const skillIdsToCreate = ['IronMan', 'Thor']
cy.backendPost(`/app/projects/${projId}`, {
projectId: projId,
name: `Very Cool Project with id ${projId}`
})
for(let i=0; i < numSubj; i++){
const subjId = `subj${i}`
cy.backendPost(`/admin/projects/${projId}/subjects/${subjId}`, {
projectId: 'proj1',
subjectId: subjId,
name: `Subject ${i}`
})
for(let j=0; j < numSkillsPerSubj; j++) {
let skillId = `${subjId}_skill${j}`
if (skillIdsToCreate.length > 0){
skillId = skillIdsToCreate.pop()
}
cy.backendAddSkill(skillId, 0, projId, subjId, pointIncrement, numPerformToCompletion)
}
Cypress.Commands.add("createDefaultProject", (numSubj = 3, numSkillsPerSubj = 2, pointIncrement = 50, numPerformToCompletion = 1, projId = 'proj1') => {
const skillIdsToCreate = ['IronMan', 'Thor']
cy.backendPost(`/app/projects/${projId}`, {
projectId: projId,
name: `Very Cool Project with id ${projId}`
}).then((resp => {
if (Cypress.env('oauthMode')) {
// if we're in oauthMode, then the foo-hyrda user will own the project. so we need to give the
// defaultUser admin rights since the user will access the project from the skills-int-service app
cy.fixture('vars.json').then((vars) => {
cy.backendPost(`/admin/projects/${projId}/users/${vars.defaultUser}/roles/ROLE_PROJECT_ADMIN`)
})
}
}))
for (let i = 0; i < numSubj; i++) {
const subjId = `subj${i}`
cy.backendPost(`/admin/projects/${projId}/subjects/${subjId}`, {
projectId: 'proj1',
subjectId: subjId,
name: `Subject ${i}`
})
for (let j = 0; j < numSkillsPerSubj; j++) {
let skillId = `${subjId}_skill${j}`
if (skillIdsToCreate.length > 0) {
skillId = skillIdsToCreate.pop()
}
cy.backendAddSkill(skillId, 0, projId, subjId, pointIncrement, numPerformToCompletion)
}
}
});
Cypress.Commands.add("createDefaultTinyProject", (projId ='proj1') => {
cy.createDefaultProject(1, 2, 50, 1, projId)
Cypress.Commands.add("createDefaultTinyProject", (projId = 'proj1') => {
cy.createDefaultProject(1, 2, 50, 1, projId)
});
Cypress.Commands.add("iframe", (handleIframeBody) => {
// iframe is not support natively (as of now)
cy.get('iframe').then((iframe) => {
const body = iframe.contents().find('body');
handleIframeBody(body);
})
// iframe is not support natively (as of now)
cy.get('iframe').then((iframe) => {
const body = iframe.contents().find('body');
handleIframeBody(body);
})
});
Cypress.Commands.add('visitHomePage', (homepage) => {
let skillsWebsocketConnected = null;
cy.visit(homepage, {
onBeforeLoad(win) {
skillsWebsocketConnected = cy.spy().as('skillsWebsocketConnected')
const postMessage = win.postMessage.bind(win)
win.postMessage = (what, target) => {
if (Cypress._.isPlainObject(what) && what.skillsWebsocketConnected) {
skillsWebsocketConnected(what)
}
return postMessage(what, target)
}
cy.spy(win, 'postMessage').as('postMessage')
let skillsWebsocketConnected = null;
cy.visit(homepage, {
onBeforeLoad(win) {
skillsWebsocketConnected = cy.spy().as('skillsWebsocketConnected')
const postMessage = win.postMessage.bind(win)
win.postMessage = (what, target) => {
if (Cypress._.isPlainObject(what) && what.skillsWebsocketConnected) {
skillsWebsocketConnected(what)
}
});
// wait for web socket to connect
cy.get('@skillsWebsocketConnected').its('lastCall.args.0').its('skillsWebsocketConnected').should('eq', true);
cy.window().should('have.property', 'skillsLogger')
cy.skillsLog(`Visit Home page for test [${Cypress.mocha.getRunner().test.title}]`)
return postMessage(what, target)
}
cy.spy(win, 'postMessage').as('postMessage')
}
});
// wait for web socket to connect
cy.get('@skillsWebsocketConnected').its('lastCall.args.0').its('skillsWebsocketConnected').should('eq', true);
cy.window().should('have.property', 'skillsLogger')
cy.skillsLog(`Visit Home page for test [${Cypress.mocha.getRunner().test.title}]`)
});
Cypress.Commands.add("cdClickSubj", (subjIndex, expectedTitle) => {
cy.wrapIframe().find(`.user-skill-subject-tile:nth-child(${subjIndex+1})`).first().click();
if (expectedTitle){
cy.wrapIframe().contains(expectedTitle);
}
cy.wrapIframe().find(`.user-skill-subject-tile:nth-child(${subjIndex + 1})`).first().click();
if (expectedTitle) {
cy.wrapIframe().contains(expectedTitle);
}
});
Cypress.Commands.add("cdBack", (expectedTitle = 'User Skills') => {
cy.wrapIframe().find('[data-cy=back]').click()
cy.wrapIframe().find('h2').contains(expectedTitle);
cy.wrapIframe().find('[data-cy=back]').click()
cy.wrapIframe().contains(expectedTitle);
// back button should not exist on the home page, whose title is the default value
if (expectedTitle === 'User Skills'){
cy.wrapIframe().find('[data-cy=back]').should('not.exist');
}
// back button should not exist on the home page, whose title is the default value
if (expectedTitle === 'User Skills') {
cy.wrapIframe().find('[data-cy=back]').should('not.exist');
}
});
Cypress.Commands.add("clientDisplay", (firstVisit=false, project='proj1') => {
cy.intercept(`/api/projects/${project}/rank`).as(`getRank${project}`)
cy.intercept(`/api/projects/${project}/pointHistory`).as(`getPointsHistory${project}`)
if (firstVisit) {
cy.wait(`@getRank${project}`)
cy.wait(`@getPointsHistory${project}`)
}
return cy.wrapIframe();
});
Cypress.Commands.add('wrapIframe', () => {
return cy.get('iframe')
.its('0.contentDocument.body').should('not.be.empty')
.then(cy.wrap)
.its('0.contentDocument.body').should('not.be.empty')
.then(cy.wrap)
});
Cypress.Commands.add('skillsLog', (message) => {
cy.window().then(win => {
if (win.skillsLogger) {
win.skillsLogger.info(message)
win.skillsLogger.info(message)
} else {
console.error('no skillsLogger found in window');
console.log(message);
}
});
});
Cypress.Commands.add('loginBySingleSignOn', (projId = 'proj1') => {
Cypress.log({
name: 'loginBySingleSignOn',
})
// first try to get a skills token,
cy.request({
url: `http://localhost:8080/api/projects/${projId}/token`,
failOnStatusCode: false,
}).then((tokenResp) => {
if (tokenResp.status === 401) {
cy.skillsLog('Skills token request failed, authenticating with OAuth provider...');
cy.request({
url: 'http://localhost:8080/oauth2/authorization/hydra',
qs: { skillsRedirectUri: baseUrl, },
// qs: { skillsRedirectUri: `${baseUrl}${homePage}` },
}).then((resp) => {
expect(resp.status).to.eq(200)
// parse out the authenticity_token
const $html = Cypress.$(resp.body)
const authenticityToken = $html.find('input[name=_csrf]').val()
const challenge = $html.find('input[name=challenge]').val()
const options = {
method: 'POST',
url: 'http://localhost:3000/login',
form: true, // we are submitting a regular form body
body: {
_csrf: authenticityToken,
challenge,
email: '[email protected]',
password: 'foobar',
submit: 'Log in',
remember: '1',
},
};
cy.request(options).then((resp2) => {
expect(resp2.status).to.eq(200)
if (resp2.redirects[resp2.redirects.length-1].includes('/consent?consent_challenge')) {
cy.skillsLog('Granting consent with OAuth provider...');
const $html = Cypress.$(resp2.body)
const authenticityToken = $html.find('input[name=_csrf]').val()
const challenge = $html.find('input[name=challenge]').val()
// const consentUrl = resp2.redirects.filter(r => r.includes('/consent?consent_challenge'))[0].split(' ')[1]
const options = {
method: 'POST',
url: 'http://localhost:3000/consent',
form: true, // we are submitting a regular form body
qs: { consent_challenge: challenge },
body: {
_csrf: authenticityToken,
challenge,
grant_scope: 'openid',
// grant_scope: 'offline',
submit: 'Allow access',
remember: '1',
},
failOnStatusCode: false,
};
cy.request(options).then((resp3) => {
expect(resp3.status).to.eq(200)
})
}
})
})
} else {
cy.skillsLog('Received Skills token, already authenticated with OAuth provider.');
}
})
});
@@ -36,15 +36,27 @@ import './commands'
beforeEach(() => {
// first call to npm fails, looks like this may be the bug: https://github.com/cypress-io/cypress/issues/6081
cy.exec('npm version', {failOnNonZeroExit: false})
cy.exec('npm run backend:resetDb')
// first call to npm fails, looks like this may be the bug: https://github.com/cypress-io/cypress/issues/6081
cy.exec('npm version', { failOnNonZeroExit: false })
cy.exec('npm run backend:resetDb')
cy.fixture('vars.json').then((vars) => {
cy.backendRegister(vars.rootUser, vars.defaultPass, true);
cy.backendRegister(vars.defaultUser, vars.defaultPass);
cy.fixture('vars.json').then((vars) => {
cy.request('api/config').then(resp => {
cy.backendRegister(vars.rootUser, vars.defaultPass, true);
cy.backendRegister(vars.defaultUser, vars.defaultPass); // used by the skills-int-service app
const oauthMode = resp.body.authenticator === 'hydra' || resp.body.authenticator === 'http://localhost:8080/oauth2/authorization/hydra'
Cypress.env('oauthMode', oauthMode)
if (!oauthMode) {
cy.backendLogin(vars.defaultUser, vars.defaultPass);
Cypress.env('proxyUser', 'user1')
Cypress.env('tokenUrl', '/api/users/user1/token')
} else {
cy.loginBySingleSignOn()
Cypress.env('proxyUser', 'foo-hydra')
Cypress.env('tokenUrl', 'http://localhost:8080/api/projects/proj1/token')
}
});
})
})
afterEach(() => {
@@ -0,0 +1,36 @@
###########################################################################
####### FOR DEMONSTRATION PURPOSES ONLY #######
###########################################################################
# #
# If you have not yet read the tutorial, do so now: #
# https://www.ory.sh/docs/hydra/5min-tutorial #
# #
# This set up is only for demonstration purposes. The login #
# endpoint can only be used if you follow the steps in the tutorial. #
# #
###########################################################################
version: '3'
services:
hydra-migrate:
image: oryd/hydra:v1.8.5
environment:
- DSN=mysql://root:secret@tcp(mysqld:3306)/mysql?max_conns=20&max_idle_conns=4
command:
migrate sql -e --yes
restart: on-failure
hydra:
depends_on:
- hydra-migrate
environment:
- DSN=mysql://root:secret@tcp(mysqld:3306)/mysql?max_conns=20&max_idle_conns=4
mysqld:
image: mysql:5.7
ports:
- "3306:3306"
environment:
- MYSQL_ROOT_PASSWORD=secret
@@ -0,0 +1,42 @@
###########################################################################
####### FOR DEMONSTRATION PURPOSES ONLY #######
###########################################################################
# #
# If you have not yet read the tutorial, do so now: #
# https://www.ory.sh/docs/hydra/5min-tutorial #
# #
# This set up is only for demonstration purposes. The login #
# endpoint can only be used if you follow the steps in the tutorial. #
# #
###########################################################################
version: '2.2'
services:
hydra-migrate:
image: oryd/hydra:v1.8.5
mem_limit: 512m
container_name: hydra_migrate
environment:
- DSN=postgres://hydra:secret@postgresd:5432/hydra?sslmode=disable&max_conns=20&max_idle_conns=4
command:
migrate sql -e --yes
restart: on-failure
hydra:
depends_on:
- hydra-migrate
environment:
- DSN=postgres://hydra:secret@postgresd:5432/hydra?sslmode=disable&max_conns=20&max_idle_conns=4
postgresd:
image: postgres:9.6
mem_limit: 512m
container_name: hydra_postgres
ports:
- "5432:5432"
environment:
- POSTGRES_USER=hydra
- POSTGRES_PASSWORD=secret
- POSTGRES_DB=hydra
@@ -0,0 +1,25 @@
###########################################################################
####### FOR DEMONSTRATION PURPOSES ONLY #######
###########################################################################
# #
# If you have not yet read the tutorial, do so now: #
# https://www.ory.sh/docs/hydra/5min-tutorial #
# #
# This set up is only for demonstration purposes. The login #
# endpoint can only be used if you follow the steps in the tutorial. #
# #
###########################################################################
version: '3'
services:
prometheus:
image: prom/prometheus:v2.12.0
ports:
- "9090:9090"
depends_on:
- hydra
command:
--config.file=/etc/prometheus/prometheus.yml
volumes:
- ./quickstart-prometheus-config.yml:/etc/prometheus/prometheus.yml
@@ -0,0 +1,53 @@
###########################################################################
####### FOR DEMONSTRATION PURPOSES ONLY #######
###########################################################################
# #
# If you have not yet read the tutorial, do so now: #
# https://www.ory.sh/docs/hydra/5min-tutorial #
# #
# This set up is only for demonstration purposes. The login #
# endpoint can only be used if you follow the steps in the tutorial. #
# #
###########################################################################
version: '3'
services:
hydra:
depends_on:
- jaeger
# - zipkin
# - datadog
environment:
- TRACING_PROVIDER=jaeger
# - TRACING_PROVIDER=zipkin
# - TRACING_PROVIDER=datadog
# Jaeger
- TRACING_PROVIDERS_JAEGER_SAMPLING_SERVER_URL=http://jaeger:5778/sampling
- TRACING_PROVIDERS_JAEGER_LOCAL_AGENT_ADDRESS=jaeger:6831
- TRACING_PROVIDERS_JAEGER_SAMPLING_TYPE=const
- TRACING_PROVIDERS_JAEGER_SAMPLING_VALUE=1
# Zipkin
- TRACING_PROVIDERS_ZIPKIN_SERVER_URL=http://zipkin:9411/api/v2/spans
# DataDog (see env vars here: https://docs.datadoghq.com/tracing/setup/go/#configuration)
- DD_SERVICE=Hydra
jaeger:
image: jaegertracing/all-in-one:1.19.2
ports:
- "16686:16686" # The UI port
# zipkin:
# image: openzipkin/zipkin:2
# environment:
# - STORAGE_TYPE=mem
# ports:
# - "9411:9411" # The UI/API port
# datadog:
# image: datadog/agent:7
# environment:
# - DD_API_KEY=<YOUR_API_KEY> # Replace it with your DataDog API key
# - DD_APM_ENABLED=true
# - DD_APM_NON_LOCAL_TRAFFIC=true
@@ -0,0 +1,48 @@
###########################################################################
####### FOR DEMONSTRATION PURPOSES ONLY #######
###########################################################################
# #
# If you have not yet read the tutorial, do so now: #
# https://www.ory.sh/docs/hydra/5min-tutorial #
# #
# This set up is only for demonstration purposes. The login #
# endpoint can only be used if you follow the steps in the tutorial. #
# #
###########################################################################
version: '2.2'
services:
hydra:
image: oryd/hydra:v1.8.5
mem_limit: 512m
container_name: hydra
ports:
- "4444:4444" # Public port
- "4445:4445" # Admin port
- "5555:5555" # Port for hydra token user
command:
serve all --dangerous-force-http
environment:
- URLS_SELF_ISSUER=http://localhost:4444
- URLS_CONSENT=http://localhost:3000/consent
- URLS_LOGIN=http://localhost:3000/login
- URLS_LOGOUT=http://localhost:3000/logout
- DSN=memory
- SECRETS_SYSTEM=youReallyNeedToChangeThis
- OIDC_SUBJECT_IDENTIFIERS_SUPPORTED_TYPES=public,pairwise
- OIDC_SUBJECT_IDENTIFIERS_PAIRWISE_SALT=youReallyNeedToChangeThis
- SERVE_COOKIES_SAME_SITE_MODE=Lax
- LOG_LEAK_SENSITIVE_VALUES=true
restart: unless-stopped
consent:
environment:
- HYDRA_ADMIN_URL=http://hydra:4445
image: oryd/hydra-login-consent-node:v1.5.2
mem_limit: 512m
container_name: hydra_consent
ports:
- "3000:3000"
restart: unless-stopped
@@ -18,32 +18,38 @@
"cy:open:dev:react": "cypress open --config baseUrl=http://localhost:8083",
"cy:open:dev:ng": "cypress open --config baseUrl=http://localhost:4200",
"integration:start": "java -Dspring.config.location=classpath:/application.properties,file:serverConfigs/integration_application.properties -jar ../skills-int-service/target/skills-int-service-*.jar",
"integration:start:oauth": "java -Dskills.service.authenticator=hydra -Dspring.config.location=classpath:/application.properties,file:serverConfigs/integration_application.properties -jar ../skills-int-service/target/skills-int-service-*.jar",
"integration:start:background": "mkdir -p ./target/logs && npm run integration:start > ./target/logs/integration-apps.out &",
"integration:start:background:oauth": "mkdir -p ./target/logs && npm run integration:start:oauth > ./target/logs/integration-apps.out &",
"integration:start:release": "java -Dspring.config.location=classpath:/application.properties,file:serverConfigs/integration_application.properties -jar ./target/servicesJars/skills-int-service-*.jar",
"integration:waitToStart": "npx wait-on -t 40000 http://localhost:8090",
"backend:start": "java -Dspring.config.location=classpath:/application.yml,file:serverConfigs/backend_application.properties -jar ../../../skills-service/service/target/skills-service-*.jar",
"backend:start:background": "mkdir -p ./target/logs && npm run backend:start | awk '/ClientLoggingController/{ print > \"./target/logs/skills-client.out\" }!/ClientLoggingController/{ print > \"./target/logs/skills-service.out\" }' &",
"backend:start:ci": "java -Dspring.config.location=classpath:/application.yml,file:serverConfigs/backend_application.properties -jar ../../skills-service/skills-service-*.jar",
"backend:start:ci:oauth": "java -Dspring.config.location=classpath:/application.yml,file:serverConfigs/backend_application.properties,file:serverConfigs/backend_oauth_application.properties -jar ../../skills-service/skills-service-*.jar",
"backend:start:ci:background": "mkdir -p ./target/logs && npm run backend:start:ci | awk '/ClientLoggingController/{ print > \"./target/logs/skills-client.out\" }!/ClientLoggingController/{ print > \"./target/logs/skills-service.out\" }' &",
"backend:start:ci:background:oauth": "mkdir -p ./target/logs && npm run backend:start:ci:oauth | awk '/ClientLoggingController/{ print > \"./target/logs/skills-client.out\" }!/ClientLoggingController/{ print > \"./target/logs/skills-service.out\" }' &",
"backend:start:release": "java -Dspring.config.location=classpath:/application.yml,file:serverConfigs/backend_application.properties -jar ./target/servicesJars/skills-service-*.jar",
"backend:waitToStart": "npx wait-port -v -t 60000 localhost:8080",
"backend:resetDb": "java -cp ./db/h2-1.4.200.jar org.h2.tools.RunScript -url jdbc:h2:tcp://localhost:9090/mem:skills -user sa -script ./db/reset.sql",
"release": "release-it --dry-run",
"cyServices:start:skills-service": "npm run backend:start:background && npm run backend:waitToStart && echo 'skill-service started!!'",
"cyServices:start:skills-service:ci": "npm run backend:start:ci:background && npm run backend:waitToStart && echo 'skill-service started!!'",
"cyServices:start:skills-service:ci:oauth": "npm run backend:start:ci:background:oauth && npm run backend:waitToStart && echo 'skill-service started!!'",
"cyServices:start:integration-apps": "npm run integration:start:background && npm run integration:waitToStart && echo 'integration apps started!!'",
"cyServices:start:integration-apps:oauth": "npm run integration:start:background:oauth && npm run integration:waitToStart && echo 'integration apps started!!'",
"cyServices:start": "npm run cyServices:start:skills-service && npm run cyServices:start:integration-apps",
"cyServices:kill": "for procid in $(ps -aux | grep \"serverConfigs/.*properties\" | grep \"jar$\" | awk '{print $2}'); do echo \"killing ${procid}\"; kill -9 $procid; done",
"dev:setupNpmLinks": "mvn --batch-mode clean package && java -cp target/skills-int-e2e-test-*.jar -Dloader.main=skills.SetupNpmLinks org.springframework.boot.loader.PropertiesLauncher",
"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": "^5.1.0",
"cypress": "6.6.0",
"license-check-and-add": "3.0.4",
"license-checker": "25.0.1",
"semver": "7.1.2",
"wait-on": "4.0.1",
"wait-port": "0.2.7"
"semver": "7.3.2",
"wait-on": "5.2.0",
"wait-port": "0.2.9"
},
"dependencies": {}
}
@@ -5,7 +5,7 @@
<parent>
<groupId>skills</groupId>
<artifactId>skills-client-integration</artifactId>
<version>3.0.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>
@@ -36,12 +36,12 @@
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.7</version>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>org.zeroturnaround</groupId>
<artifactId>zt-exec</artifactId>
<version>1.11</version>
<version>1.12</version>
</dependency>
<!--<dependency>-->
<!--<groupId>profile</groupId>-->
@@ -72,6 +72,16 @@
<artifactId>groovycsv</artifactId>
<version>1.3</version>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-cli-picocli</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.9</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
@@ -86,7 +96,7 @@
<plugin>
<groupId>org.codehaus.gmavenplus</groupId>
<artifactId>gmavenplus-plugin</artifactId>
<version>1.6.3</version>
<version>1.11.0</version>
<executions>
<execution>
<goals>
@@ -134,15 +144,20 @@
<includedLicense>Eclipse Public License - Version 1.0</includedLicense>
<includedLicense>The 3-Clause BSD License</includedLicense>
<includedLicense>Common Development and Distribution License 1.1 (CDDL-1.1) + The GNU General Public License (GPL) Version 2 with the Classpath Exception</includedLicense>
<includedLicense>Eclipse Public License - Version 2.0</includedLicense>
<includedLicense>The GNU General Public License (GPL) Version 2 with the Classpath Exception</includedLicense>
</includedLicenses>
<licenseMerges>
<licenseMerge>The Apache Software License, Version 2.0|Apache License, Version 2.0</licenseMerge>
<licenseMerge>The Apache Software License, Version 2.0|Apache 2</licenseMerge>
<licenseMerge>The Apache Software License, Version 2.0|The Apache License, Version 2.0</licenseMerge>
<licenseMerge>The Apache Software License, Version 2.0|The Apache Software License, version 2.0</licenseMerge>
<licenseMerge>Eclipse Public License - Version 1.0|Eclipse Public License 1.0</licenseMerge>
<licenseMerge>The 3-Clause BSD License|New BSD License</licenseMerge>
<licenseMerge>Eclipse Public License - Version 1.0|Eclipse Public License - v 1.0</licenseMerge>
<licenseMerge>MIT License|The MIT License</licenseMerge>
<licenseMerge>Eclipse Public License - Version 2.0|EPL 2.0</licenseMerge>
<licenseMerge>The GNU General Public License (GPL) Version 2 with the Classpath Exception|GPL2 w/ CPE</licenseMerge>
</licenseMerges>
</configuration>
</execution>
@@ -29,4 +29,4 @@ logging.level.org.springframework.messaging=INFO
logging.level.skills=DEBUG
skills.config.client.loggingEnabled=true
skills.config.client.loggingLevel=TRACE
skills.config.client.loggingLevel=TRACE
@@ -0,0 +1,7 @@
spring.security.oauth2.client.registration.hydra.client-id: skilltree-test
spring.security.oauth2.client.registration.hydra.client-secret: client-secret
spring.security.oauth2.client.registration.hydra.scope: openid
spring.security.oauth2.client.registration.hydra.clientName: Hydra
spring.security.oauth2.client.registration.hydra.iconClass: fas fa-key
spring.security.oauth2.client.provider.hydra.issuer-uri: http://localhost:4444/
@@ -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
@@ -21,6 +21,8 @@ import groovy.util.logging.Slf4j
class CypressTestsHelper {
private TitlePrinter titlePrinter = new TitlePrinter()
boolean recordInDashboard = false
String tag
File e2eDir
void runCypressTests(String msg, List<String> cypressEnv = []) {
@@ -42,6 +44,12 @@ class CypressTestsHelper {
new ProcessRunner(loc: e2eDir, waitForOutput: false).run("npm run cyServices:start:integration-apps")
String env = cypressEnv ? " --env ${cypressEnv.join(",")}" : ""
if (recordInDashboard) {
env += ' --record'
if (tag) {
env += " --tag \"${tag}\""
}
}
titlePrinter.printSubTitle("Starting Cypress to tests: [${msg}], env=[$env]")
new ProcessRunner(loc: e2eDir).run("npx cypress run${env}")
} finally {
@@ -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() {
@@ -16,6 +16,8 @@ limitations under the License.
package skills
import groovy.util.logging.Slf4j
import org.apache.commons.text.StringTokenizer
import org.apache.commons.text.matcher.StringMatcherFactory
import org.zeroturnaround.exec.stream.TeeOutputStream
import org.zeroturnaround.exec.stream.slf4j.Slf4jStream
@@ -37,6 +39,12 @@ class ProcessRunner {
File loc = new File("./")
ProcessRes run(String cmd) {
StringTokenizer st = new StringTokenizer(cmd)
st.setQuoteMatcher(StringMatcherFactory.INSTANCE.quoteMatcher())
this.run(st.tokenList)
}
ProcessRes run(List<String> cmd) {
assert cmd
assert loc
@@ -73,11 +81,12 @@ class ProcessRunner {
}
String errMsg = serr ? new String(serr.toByteArray()) : ""
String outMsg = sout ? new String(sout.toByteArray()) : ""
if (waitForOutput && p.exitValue() != 0) {
throw new IllegalStateException("[$name] - [${cmd}] failed with error code [${p.exitValue()}]:\n${errMsg}")
throw new IllegalStateException("[$name] - [${cmd}] failed with error code [${p.exitValue()}]:\nserr: ${errMsg}\nsout: ${outMsg}")
}
if (failWithErrMsg && errMsg) {
throw new IllegalStateException("[$name] - [${cmd}] failed:\n$errMsg")
throw new IllegalStateException("[$name] - [${cmd}] failed:\nserr: ${errMsg}\nsout: ${outMsg}")
}
return new ProcessRes(serr: errMsg, sout: sout ? new String(sout.toByteArray()) : "")
}
@@ -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)
@@ -15,6 +15,7 @@ limitations under the License.
*/
package skills
import groovy.cli.picocli.CliBuilder
import groovy.json.JsonSlurper
import groovy.util.logging.Slf4j
import org.apache.commons.io.FileUtils
@@ -23,12 +24,23 @@ import org.apache.commons.io.FileUtils
class TestClientLibsBackwardsCompat {
static void main(String[] args) {
new TestClientLibsBackwardsCompat().init().test()
def cli = new CliBuilder()
cli.record(type: boolean, 'record tests in cypress dashboard')
cli.tag(type: String, 'tag to give cypress test')
def options = cli.parse(args)
def shouldRecord = options.record
String tag
if (options.tag && options.tag instanceof String) {
tag = options.tag
}
new TestClientLibsBackwardsCompat(recordInDashboard: shouldRecord, tag: tag).init().test()
}
private TitlePrinter titlePrinter = new TitlePrinter()
private File e2eDir
private String serviceName = "skills-service"
boolean recordInDashboard = false
String tag
TestClientLibsBackwardsCompat init() {
JsonSlurper jsonSlurper = new JsonSlurper()
@@ -46,7 +58,7 @@ class TestClientLibsBackwardsCompat {
void test() {
List<String> versions = getBackendVersionsToTest()
CypressTestsHelper cypressTestsHelper = new CypressTestsHelper(e2eDir: e2eDir)
CypressTestsHelper cypressTestsHelper = new CypressTestsHelper(e2eDir: e2eDir, recordInDashboard: recordInDashboard, tag: "'${tag}'")
versions.each { File versionFile ->
titlePrinter.printTitle("Testing against skills-service version [${versionFile.name}]")
prepSkillsServiceJar(versionFile)
@@ -15,6 +15,7 @@ limitations under the License.
*/
package skills
import groovy.cli.picocli.CliBuilder
import groovy.json.JsonOutput
import groovy.util.logging.Slf4j
import org.apache.commons.io.FileUtils
@@ -26,12 +27,23 @@ import org.springframework.core.io.support.ResourcePatternResolver
class TestDashboardBackwardCompat {
static void main(String[] args) {
new TestDashboardBackwardCompat().testNew()
def cli = new CliBuilder()
cli.record(type: boolean, 'record tests in cypress dashboard')
cli.tag(type: String, 'tag to give cypress test')
def options = cli.parse(args)
def shouldRecord = options.record
String tag
if (options.tag && options.tag instanceof String) {
tag = options.tag
}
new TestDashboardBackwardCompat(recordInDashboard: shouldRecord, tag: tag).testNew()
}
File workDir = new File("./target/skills-client")
TitlePrinter titlePrinter = new TitlePrinter()
List<String> versionsToExclude = ["2.0.0"]
boolean recordInDashboard = false
String tag
void checkoutWorkingCopy() {
if (workDir.exists()) {
@@ -73,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.")
}
@@ -114,7 +127,8 @@ class TestDashboardBackwardCompat {
titlePrinter.printSubTitle("[${version}]: Running cypress test with")
String output = "Testing version [$version]"
new CypressTestsHelper(e2eDir: e2eLoc).runCypressTests(output)
String currentTag = [tag, "version ${version}"].findAll { it }.collect { "'${it}'" }.join(',')
new CypressTestsHelper(e2eDir: e2eLoc, recordInDashboard: recordInDashboard, tag: currentTag).runCypressTests(output)
}
}
@@ -1,57 +1,121 @@
diff --git a/skills-client-integration/skills-int-client-js/package.json b/skills-client-integration/skills-int-client-js/package.json
index a844bc6..8bc7b38 100644
--- a/skills-client-integration/skills-int-client-js/package.json
+++ b/skills-client-integration/skills-int-client-js/package.json
@@ -28,4 +28,4 @@
"license-checker": "^25.0.1",
"live-server": "1.2.1"
}
-}
+}
\ No newline at end of file
diff --git a/skills-client-integration/skills-int-client-react/package.json b/skills-client-integration/skills-int-client-react/package.json
index cdf8d19..73abfc4 100644
--- a/skills-client-integration/skills-int-client-react/package.json
+++ b/skills-client-integration/skills-int-client-react/package.json
@@ -48,4 +48,4 @@
"license-checker": "^25.0.1",
"react-scripts": "3.2.0"
}
-}
+}
\ No newline at end of file
diff --git a/skills-client-integration/skills-int-client-vue/package.json b/skills-client-integration/skills-int-client-vue/package.json
index 046ba82..0ea733a 100644
--- a/skills-client-integration/skills-int-client-vue/package.json
+++ b/skills-client-integration/skills-int-client-vue/package.json
@@ -49,14 +49,18 @@
"plugin:vue/essential",
"eslint:recommended"
],
- "rules": {},
+ "rules": {
+
+ },
"parserOptions": {
"parser": "babel-eslint"
}
},
"postcss": {
"plugins": {
- "autoprefixer": {}
+ "autoprefixer": {
+
+ }
}
}
-}
+}
\ No newline at end of file
diff --git a/skills-client-integration/pom.xml b/skills-client-integration/pom.xml
index 745a168..90d231c 100755
--- a/skills-client-integration/pom.xml
+++ b/skills-client-integration/pom.xml
@@ -33,7 +33,7 @@
<properties>
<!--suppress UnresolvedMavenProperty -->
<nexusServer>${nexus.url}</nexusServer>
- <node.version>v11.6.0</node.version>
+ <node.version>v14.15.0</node.version>
</properties>
<distributionManagement>
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
+++ b/skills-client-integration/skills-int-e2e-test/cypress.json
@@ -1,5 +1,10 @@
{
"baseUrl": "http://localhost:8090",
"chromeWebSecurity": false,
- "defaultCommandTimeout": 10000
+ "defaultCommandTimeout": 10000,
+ "projectId": "3yas4t",
+ "retries": {
+ "runMode": 2,
+ "openMode": 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 2b73362..2787c53 100644
--- 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
@@ -148,7 +148,7 @@ context("Native JS Tests", () => {
cy.wrap(body)
.find(".skills-page-title-text-color")
.should("have.css", "background-color")
- .and("equal", "rgb(255, 255, 255)");
+ .and("equal", "rgba(0, 0, 0, 0)");
});
});
@@ -175,7 +175,7 @@ context("Native JS Tests", () => {
cy.wrap(body)
.find(".skills-page-title-text-color")
.should("have.css", "background-color")
- .and("equal", "rgb(255, 255, 255)");
+ .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 1cc8b29..8e4895f 100644
--- 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
@@ -167,7 +167,7 @@ context('React Tests', () => {
// verify that there is no background set
// cypress always validates against rgb
cy.wrap(body).find('.skills-page-title-text-color')
- .should('have.css', 'background-color').and('equal', 'rgb(255, 255, 255)');
+ .should('have.css', 'background-color').and('equal', 'rgba(0, 0, 0, 0)');
});
})
@@ -186,7 +186,7 @@ context('React Tests', () => {
// verify that there is no background set
// cypress always validates against rgb
cy.wrap(body).find('.skills-page-title-text-color')
- .should('have.css', 'background-color').and('equal', 'rgb(255, 255, 255)');
+ .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 2283896..665e697 100644
--- 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
@@ -219,7 +219,7 @@ context('Vue Tests', () => {
cy.get('#SkillsDirectiveErrorwithButton button').click()
cy.wait('@postSkill');
cy.get('@postSkill').then((xhr) => {
- expect(xhr.status).to.eq(400)
+ expect(xhr.status).to.eq(404)
expect(xhr.responseBody).to.have.property('explanation').to.eq('Failed to report skill event because skill definition does not exist.')
});
})
@@ -233,7 +233,7 @@ context('Vue Tests', () => {
cy.get('#SkillsDirectiveErrorwithInput input').type('h')
cy.wait('@postSkill');
cy.get('@postSkill').then((xhr) => {
- expect(xhr.status).to.eq(400)
+ expect(xhr.status).to.eq(404)
expect(xhr.responseBody).to.have.property('explanation').to.eq('Failed to report skill event because skill definition does not exist.')
});
})
@@ -255,7 +255,7 @@ context('Vue Tests', () => {
// verify that there is no background set
// cypress always validates against rgb
cy.wrap(body).find('.skills-page-title-text-color')
- .should('have.css', 'background-color').and('equal', 'rgb(255, 255, 255)');
+ .should('have.css', 'background-color').and('equal', 'rgba(0, 0, 0, 0)');
});
})
@@ -274,7 +274,7 @@ context('Vue Tests', () => {
// verify that there is no background set
// cypress always validates against rgb
cy.wrap(body).find('.skills-page-title-text-color')
- .should('have.css', 'background-color').and('equal', 'rgb(255, 255, 255)');
+ .should('have.css', 'background-color').and('equal', 'rgba(0, 0, 0, 0)');
})
})
diff --git a/skills-client-integration/skills-int-e2e-test/package.json b/skills-client-integration/skills-int-e2e-test/package.json
index 2dfe4e3..04a2791 100644
index 2dfe4e3..6d7ec41 100644
--- a/skills-client-integration/skills-int-e2e-test/package.json
+++ b/skills-client-integration/skills-int-e2e-test/package.json
@@ -18,12 +18,17 @@
@@ -18,16 +18,21 @@
"cy:open:dev:react": "cypress open --config baseUrl=http://localhost:8083",
"integration:start": "java -Dspring.config.location=classpath:/application.properties,file:serverConfigs/integration_application.properties -jar ../skills-int-service/target/skills-int-service-*.jar",
"integration:start:release": "java -Dspring.config.location=classpath:/application.properties,file:serverConfigs/integration_application.properties -jar ./target/servicesJars/skills-int-service-*.jar",
@@ -71,6 +135,11 @@ index 2dfe4e3..04a2791 100644
"release": "release-it --dry-run"
},
"devDependencies": {
- "cypress": "4.1.0",
+ "cypress": "6.2.0",
"license-check-and-add": "3.0.4",
"license-checker": "25.0.1",
"semver": "7.1.2",
diff --git a/skills-client-integration/skills-int-e2e-test/pom.xml b/skills-client-integration/skills-int-e2e-test/pom.xml
index 10d22b8..f28ba3e 100644
--- a/skills-client-integration/skills-int-e2e-test/pom.xml
@@ -113,16 +182,16 @@ index 80ed5f3..ad2889c 100644
@@ -15,7 +15,7 @@ limitations under the License.
*/
package skills
-import callStack.profiler.Profile
+//import callStack.profiler.Profile
import groovy.util.logging.Slf4j
import org.apache.commons.io.FileUtils
@@ -29,7 +29,7 @@ class DownloadServiceJars {
// internal
TitlePrinter titlePrinter = new TitlePrinter()
- @Profile
+ // @Profile
void cleanOutputDir(){
@@ -131,7 +200,7 @@ index 80ed5f3..ad2889c 100644
@@ -40,7 +40,7 @@ class DownloadServiceJars {
// outputDir.mkdirs()
}
- @Profile
+ //@Profile
void remove(String artifact, String version) {
@@ -140,7 +209,7 @@ index 80ed5f3..ad2889c 100644
@@ -49,7 +49,7 @@ class DownloadServiceJars {
}
}
- @Profile
+ //@Profile
void download(String artifact, String version) {
@@ -153,16 +222,16 @@ index 168e90d..ea13f9c 100644
@@ -15,7 +15,7 @@ limitations under the License.
*/
package skills
-import callStack.profiler.Profile
+//import callStack.profiler.Profile
import groovy.util.logging.Slf4j
@Slf4j
@@ -57,7 +57,7 @@ class NpmProjBuilder {
}
}
- @Profile
+ // @Profile
List<NpmProj> build() {
@@ -175,16 +244,16 @@ index 4b8ba60..724ca5b 100644
@@ -15,7 +15,7 @@ limitations under the License.
*/
package skills
-import callStack.profiler.Profile
+//import callStack.profiler.Profile
import groovy.util.logging.Slf4j
import org.apache.commons.io.FileUtils
@@ -39,7 +39,7 @@ class ProjectsOps {
new File(workDir, "skills-client")
}
- @Profile
+ // @Profile
private void clearWorkDir() {
@@ -193,7 +262,7 @@ index 4b8ba60..724ca5b 100644
@@ -51,7 +51,7 @@ class ProjectsOps {
workDir.mkdirs()
}
- @Profile
+ // @Profile
void checkoutLinkedNpmLibs(String switchToBranch = null) {
@@ -201,8 +270,8 @@ index 4b8ba60..724ca5b 100644
titlePrinter.printTitle("Checkout skills-client")
@@ -68,7 +68,7 @@ class ProjectsOps {
}
- @Profile
+ //@Profile
void buildClientIntApp() {
@@ -210,8 +279,8 @@ index 4b8ba60..724ca5b 100644
File loc = new File(skillsClient, "skills-client-integration")
@@ -76,7 +76,7 @@ class ProjectsOps {
}
- @Profile
+ //@Profile
void runCypressTests(File location, String msg, List<String> cypressEnv = [], String npmIntegrationNamespace = "integration") {
@@ -224,14 +293,14 @@ index 0193982..cfad14d 100644
@@ -15,8 +15,8 @@ limitations under the License.
*/
package skills
-import callStack.profiler.CProf
-import callStack.profiler.Profile
+//import callStack.profiler.CProf
+//import callStack.profiler.Profile
import groovy.util.logging.Slf4j
import org.apache.commons.io.FileUtils
@@ -29,7 +29,7 @@ class SetupNpmLinks {
static void main(String[] args) {
boolean shouldPrune = args.find({ it.equalsIgnoreCase("--prune") })
@@ -239,12 +308,12 @@ index 0193982..cfad14d 100644
- log.info("Execution Prof:\n{}", CProf.prettyPrint())
+ //log.info("Execution Prof:\n{}", CProf.prettyPrint())
}
// configure
@@ -44,7 +44,7 @@ class SetupNpmLinks {
return this
}
- @Profile
+ // @Profile
void doLink() {
@@ -253,7 +322,7 @@ index 0193982..cfad14d 100644
@@ -57,7 +57,7 @@ class SetupNpmLinks {
build()
}
- @Profile
+ //@Profile
void removeAnyExistingLinks() {
@@ -262,7 +331,7 @@ index 0193982..cfad14d 100644
@@ -78,7 +78,7 @@ class SetupNpmLinks {
}
}
- @Profile
+ //@Profile
private void build() {
@@ -271,7 +340,7 @@ index 0193982..cfad14d 100644
@@ -86,7 +86,7 @@ class SetupNpmLinks {
}
}
- @Profile
+ //@Profile
private void validateLinks() {
@@ -280,7 +349,7 @@ index 0193982..cfad14d 100644
@@ -106,7 +106,7 @@ class SetupNpmLinks {
}
}
- @Profile
+ // @Profile
private void npmLinkToSkills() {
@@ -289,7 +358,7 @@ index 0193982..cfad14d 100644
@@ -117,7 +117,7 @@ class SetupNpmLinks {
}
}
- @Profile
+ //@Profile
private void npmLinkToReact() {
@@ -298,7 +367,7 @@ index 0193982..cfad14d 100644
@@ -127,7 +127,7 @@ class SetupNpmLinks {
}
}
- @Profile
+ //@Profile
private void createLinks() {
@@ -307,7 +376,7 @@ index 0193982..cfad14d 100644
@@ -138,7 +138,7 @@ class SetupNpmLinks {
}
}
- @Profile
+ //@Profile
private void npmInstall() {
@@ -320,7 +389,7 @@ index 55c9c7c..d399b1b 100644
@@ -15,8 +15,8 @@ limitations under the License.
*/
package skills
-import callStack.profiler.CProf
-import callStack.profiler.Profile
+//import callStack.profiler.CProf
@@ -331,9 +400,9 @@ index 55c9c7c..d399b1b 100644
@@ -64,7 +64,7 @@ class TestDashboardBackwardCompat {
log.info("Profile \n{}", CProf.prettyPrint())
}
- @Profile
+ // @Profile
private void doTest() {
titlePrinter.printTitle("Release Dashboard")
@@ -0,0 +1,195 @@
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
+++ b/skills-client-integration/skills-int-e2e-test/cypress.json
@@ -1,5 +1,10 @@
{
"baseUrl": "http://localhost:8090",
"chromeWebSecurity": false,
- "defaultCommandTimeout": 10000
+ "defaultCommandTimeout": 10000,
+ "projectId": "3yas4t",
+ "retries": {
+ "runMode": 2,
+ "openMode": 0
+ }
}
diff --git a/skills-client-integration/skills-int-e2e-test/cypress/integration/angular.specs.js b/skills-client-integration/skills-int-e2e-test/cypress/integration/angular.specs.js
index cbe8938..5aea457 100644
--- a/skills-client-integration/skills-int-e2e-test/cypress/integration/angular.specs.js
+++ b/skills-client-integration/skills-int-e2e-test/cypress/integration/angular.specs.js
@@ -208,7 +208,7 @@ context('Angular Tests', () => {
cy.get('#SkillsDirectiveErrorwithButton button').click()
cy.wait('@postSkill');
cy.get('@postSkill').then((xhr) => {
- expect(xhr.status).to.eq(400)
+ expect(xhr.status).to.eq(404)
expect(xhr.responseBody).to.have.property('explanation').to.eq('Failed to report skill event because skill definition does not exist.')
});
})
@@ -222,7 +222,7 @@ context('Angular Tests', () => {
cy.get('#SkillsDirectiveErrorwithInput input').type('h')
cy.wait('@postSkill');
cy.get('@postSkill').then((xhr) => {
- expect(xhr.status).to.eq(400)
+ expect(xhr.status).to.eq(404)
expect(xhr.responseBody).to.have.property('explanation').to.eq('Failed to report skill event because skill definition does not exist.')
});
})
@@ -244,7 +244,7 @@ context('Angular Tests', () => {
// verify that there is no background set
// cypress always validates against rgb
cy.wrap(body).find('.skills-page-title-text-color')
- .should('have.css', 'background-color').and('equal', 'rgb(255, 255, 255)');
+ .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
cy.wrap(body).find('.skills-page-title-text-color')
- .should('have.css', 'background-color').and('equal', 'rgb(255, 255, 255)');
+ .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
+++ b/skills-client-integration/skills-int-e2e-test/cypress/integration/js.specs.js
@@ -118,7 +118,7 @@ context("Native JS Tests", () => {
cy.wrap(body)
.find(".skills-page-title-text-color")
.should("have.css", "background-color")
- .and("equal", "rgb(255, 255, 255)");
+ .and("equal", "rgba(0, 0, 0, 0)");
});
});
@@ -142,7 +142,7 @@ context("Native JS Tests", () => {
cy.wrap(body)
.find(".skills-page-title-text-color")
.should("have.css", "background-color")
- .and("equal", "rgb(255, 255, 255)");
+ .and("equal", "rgba(0, 0, 0, 0)");
});
});
@@ -169,7 +169,7 @@ context("Native JS Tests", () => {
cy.wrap(body)
.find(".skills-page-title-text-color")
.should("have.css", "background-color")
- .and("equal", "rgb(255, 255, 255)");
+ .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
+++ b/skills-client-integration/skills-int-e2e-test/cypress/integration/react.specs.js
@@ -153,7 +153,7 @@ context('React Tests', () => {
// verify that there is no background set
// cypress always validates against rgb
cy.wrap(body).find('.skills-page-title-text-color')
- .should('have.css', 'background-color').and('equal', 'rgb(255, 255, 255)');
+ .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
cy.wrap(body).find('.skills-page-title-text-color')
- .should('have.css', 'background-color').and('equal', 'rgb(255, 255, 255)');
+ .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
+++ b/skills-client-integration/skills-int-e2e-test/cypress/integration/vue.specs.js
@@ -204,7 +204,7 @@ context('Vue Tests', () => {
cy.get('#SkillsDirectiveErrorwithButton button').click()
cy.wait('@postSkill');
cy.get('@postSkill').then((xhr) => {
- expect(xhr.status).to.eq(400)
+ expect(xhr.status).to.eq(404)
expect(xhr.responseBody).to.have.property('explanation').to.eq('Failed to report skill event because skill definition does not exist.')
});
})
@@ -218,7 +218,7 @@ context('Vue Tests', () => {
cy.get('#SkillsDirectiveErrorwithInput input').type('h')
cy.wait('@postSkill');
cy.get('@postSkill').then((xhr) => {
- expect(xhr.status).to.eq(400)
+ expect(xhr.status).to.eq(404)
expect(xhr.responseBody).to.have.property('explanation').to.eq('Failed to report skill event because skill definition does not exist.')
});
})
@@ -240,7 +240,7 @@ context('Vue Tests', () => {
// verify that there is no background set
// cypress always validates against rgb
cy.wrap(body).find('.skills-page-title-text-color')
- .should('have.css', 'background-color').and('equal', 'rgb(255, 255, 255)');
+ .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
cy.wrap(body).find('.skills-page-title-text-color')
- .should('have.css', 'background-color').and('equal', 'rgb(255, 255, 255)');
+ .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
index 8664332..7209ea6 100644
--- a/skills-client-integration/skills-int-e2e-test/package.json
+++ b/skills-client-integration/skills-int-e2e-test/package.json
@@ -38,7 +38,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": "4.1.0",
+ "cypress": "6.2.0",
"license-check-and-add": "3.0.4",
"license-checker": "25.0.1",
"semver": "7.1.2",
@@ -0,0 +1,200 @@
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
+++ b/skills-client-integration/skills-int-e2e-test/cypress.json
@@ -1,5 +1,10 @@
{
"baseUrl": "http://localhost:8090",
"chromeWebSecurity": false,
- "defaultCommandTimeout": 10000
+ "defaultCommandTimeout": 10000,
+ "projectId": "3yas4t",
+ "retries": {
+ "runMode": 2,
+ "openMode": 0
+ }
}
diff --git a/skills-client-integration/skills-int-e2e-test/cypress/integration/angular.specs.js b/skills-client-integration/skills-int-e2e-test/cypress/integration/angular.specs.js
index 4c944c8..b17b00d 100644
--- a/skills-client-integration/skills-int-e2e-test/cypress/integration/angular.specs.js
+++ b/skills-client-integration/skills-int-e2e-test/cypress/integration/angular.specs.js
@@ -207,7 +207,7 @@ context('Angular Tests', () => {
cy.get('#SkillsDirectiveErrorwithButton button').click()
cy.wait('@postSkill');
cy.get('@postSkill').then((xhr) => {
- expect(xhr.status).to.eq(400)
+ expect(xhr.status).to.eq(404)
expect(xhr.responseBody).to.have.property('explanation').to.eq('Failed to report skill event because skill definition does not exist.')
});
})
@@ -221,7 +221,7 @@ context('Angular Tests', () => {
cy.get('#SkillsDirectiveErrorwithInput input').type('h')
cy.wait('@postSkill');
cy.get('@postSkill').then((xhr) => {
- expect(xhr.status).to.eq(400)
+ expect(xhr.status).to.eq(404)
expect(xhr.responseBody).to.have.property('explanation').to.eq('Failed to report skill event because skill definition does not exist.')
});
})
@@ -240,7 +240,7 @@ context('Angular Tests', () => {
// verify that there is no background set
// cypress always validates against rgb
cy.wrapIframe().find('.skills-page-title-text-color')
- .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
// cypress always validates against rgb
cy.wrapIframe().find('.skills-page-title-text-color')
- .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
--- 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
@@ -115,7 +115,7 @@ context("Native JS Tests", () => {
// verify that there is no background set
// cypress always validates against rgb
cy.wrapIframe().find('.skills-page-title-text-color')
- .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
// cypress always validates against rgb
cy.wrapIframe().find('.skills-page-title-text-color')
- .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
// cypress always validates against rgb
cy.wrapIframe().find('.skills-page-title-text-color')
- .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
--- 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
@@ -150,7 +150,7 @@ context('React Tests', () => {
// verify that there is no background set
// cypress always validates against rgb
cy.wrapIframe().find('.skills-page-title-text-color')
- .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
// cypress always validates against rgb
cy.wrapIframe().find('.skills-page-title-text-color')
- .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
--- 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
@@ -207,7 +207,7 @@ context('Vue Tests', () => {
cy.get('#SkillsDirectiveErrorwithButton button').click()
cy.wait('@postSkill');
cy.get('@postSkill').then((xhr) => {
- expect(xhr.status).to.eq(400)
+ expect(xhr.status).to.eq(404)
expect(xhr.responseBody).to.have.property('explanation').to.eq('Failed to report skill event because skill definition does not exist.')
});
})
@@ -221,7 +221,7 @@ context('Vue Tests', () => {
cy.get('#SkillsDirectiveErrorwithInput input').type('h')
cy.wait('@postSkill');
cy.get('@postSkill').then((xhr) => {
- expect(xhr.status).to.eq(400)
+ expect(xhr.status).to.eq(404)
expect(xhr.responseBody).to.have.property('explanation').to.eq('Failed to report skill event because skill definition does not exist.')
});
})
@@ -240,7 +240,7 @@ context('Vue Tests', () => {
// verify that there is no background set
// cypress always validates against rgb
cy.wrapIframe().find('.skills-page-title-text-color')
- .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
// cypress always validates against rgb
cy.wrap(body).find('.skills-page-title-text-color')
- .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
// cypress always validates against rgb
cy.wrapIframe().find('.skills-page-title-text-color')
- .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
// cypress always validates against rgb
cy.wrap(body).find('.skills-page-title-text-color')
- .should('have.css', 'background-color').and('equal', 'rgb(255, 255, 255)');
+ .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'){
@@ -0,0 +1,164 @@
diff --git a/skills-client-integration/skills-int-e2e-test/cypress/integration/angular.specs.js b/skills-client-integration/skills-int-e2e-test/cypress/integration/angular.specs.js
index 393ec38..ace542b 100644
--- a/skills-client-integration/skills-int-e2e-test/cypress/integration/angular.specs.js
+++ b/skills-client-integration/skills-int-e2e-test/cypress/integration/angular.specs.js
@@ -207,7 +207,7 @@ context('Angular Tests', () => {
cy.get('#SkillsDirectiveErrorwithButton button').click()
cy.wait('@postSkill');
cy.get('@postSkill').then((xhr) => {
- expect(xhr.status).to.eq(400)
+ expect(xhr.status).to.eq(404)
expect(xhr.responseBody).to.have.property('explanation').to.eq('Failed to report skill event because skill definition does not exist.')
});
})
@@ -221,7 +221,7 @@ context('Angular Tests', () => {
cy.get('#SkillsDirectiveErrorwithInput input').type('h')
cy.wait('@postSkill');
cy.get('@postSkill').then((xhr) => {
- expect(xhr.status).to.eq(400)
+ expect(xhr.status).to.eq(404)
expect(xhr.responseBody).to.have.property('explanation').to.eq('Failed to report skill event because skill definition does not exist.')
});
})
@@ -240,7 +240,7 @@ context('Angular Tests', () => {
// verify that there is no background set
// cypress always validates against rgb
cy.wrapIframe().find('.skills-page-title-text-color')
- .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
// cypress always validates against rgb
cy.wrapIframe().find('.skills-page-title-text-color')
- .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 a82b1a6..e951f9c 100644
--- 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
@@ -116,7 +116,7 @@ context("Native JS Tests", () => {
// verify that there is no background set
// cypress always validates against rgb
cy.wrapIframe().find('.skills-page-title-text-color')
- .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", () => {
@@ -138,7 +138,7 @@ context("Native JS Tests", () => {
// verify that there is no background set
// cypress always validates against rgb
cy.wrapIframe().find('.skills-page-title-text-color')
- .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", () => {
@@ -160,7 +160,7 @@ context("Native JS Tests", () => {
// verify that there is no background set
// cypress always validates against rgb
cy.wrapIframe().find('.skills-page-title-text-color')
- .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 38151c4..5a95fbe 100644
--- 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
@@ -150,7 +150,7 @@ context('React Tests', () => {
// verify that there is no background set
// cypress always validates against rgb
cy.wrapIframe().find('.skills-page-title-text-color')
- .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
// cypress always validates against rgb
cy.wrapIframe().find('.skills-page-title-text-color')
- .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 d861deb..4037779 100644
--- 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
@@ -207,7 +207,7 @@ context('Vue Tests', () => {
cy.get('#SkillsDirectiveErrorwithButton button').click()
cy.wait('@postSkill');
cy.get('@postSkill').then((xhr) => {
- expect(xhr.status).to.eq(400)
+ expect(xhr.status).to.eq(404)
expect(xhr.responseBody).to.have.property('explanation').to.eq('Failed to report skill event because skill definition does not exist.')
});
})
@@ -221,7 +221,7 @@ context('Vue Tests', () => {
cy.get('#SkillsDirectiveErrorwithInput input').type('h')
cy.wait('@postSkill');
cy.get('@postSkill').then((xhr) => {
- expect(xhr.status).to.eq(400)
+ expect(xhr.status).to.eq(404)
expect(xhr.responseBody).to.have.property('explanation').to.eq('Failed to report skill event because skill definition does not exist.')
});
})
@@ -240,7 +240,7 @@ context('Vue Tests', () => {
// verify that there is no background set
// cypress always validates against rgb
cy.wrapIframe().find('.skills-page-title-text-color')
- .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', () => {
@@ -261,7 +261,7 @@ context('Vue Tests', () => {
// verify that there is no background set
// cypress always validates against rgb
cy.wrapIframe().find('.skills-page-title-text-color')
- .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')
cy.wait('@getToken2')
@@ -284,7 +284,7 @@ context('Vue Tests', () => {
// verify that there is no background set
// cypress always validates against rgb
cy.wrapIframe().find('.skills-page-title-text-color')
- .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', () => {
@@ -407,7 +407,7 @@ context('Vue Tests', () => {
// verify that there is no background set
// cypress always validates against rgb
cy.wrap(body).find('.skills-page-title-text-color')
- .should('have.css', 'background-color').and('equal', 'rgb(255, 255, 255)');
+ .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 48da2de..d66fb30 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
@@ -182,7 +182,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') {
@@ -0,0 +1,164 @@
diff --git a/skills-client-integration/skills-int-e2e-test/cypress/integration/angular.specs.js b/skills-client-integration/skills-int-e2e-test/cypress/integration/angular.specs.js
index 393ec38..ace542b 100644
--- a/skills-client-integration/skills-int-e2e-test/cypress/integration/angular.specs.js
+++ b/skills-client-integration/skills-int-e2e-test/cypress/integration/angular.specs.js
@@ -207,7 +207,7 @@ context('Angular Tests', () => {
cy.get('#SkillsDirectiveErrorwithButton button').click()
cy.wait('@postSkill');
cy.get('@postSkill').then((xhr) => {
- expect(xhr.status).to.eq(400)
+ expect(xhr.status).to.eq(404)
expect(xhr.responseBody).to.have.property('explanation').to.eq('Failed to report skill event because skill definition does not exist.')
});
})
@@ -221,7 +221,7 @@ context('Angular Tests', () => {
cy.get('#SkillsDirectiveErrorwithInput input').type('h')
cy.wait('@postSkill');
cy.get('@postSkill').then((xhr) => {
- expect(xhr.status).to.eq(400)
+ expect(xhr.status).to.eq(404)
expect(xhr.responseBody).to.have.property('explanation').to.eq('Failed to report skill event because skill definition does not exist.')
});
})
@@ -240,7 +240,7 @@ context('Angular Tests', () => {
// verify that there is no background set
// cypress always validates against rgb
cy.wrapIframe().find('.skills-page-title-text-color')
- .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
// cypress always validates against rgb
cy.wrapIframe().find('.skills-page-title-text-color')
- .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 a82b1a6..e951f9c 100644
--- 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
@@ -116,7 +116,7 @@ context("Native JS Tests", () => {
// verify that there is no background set
// cypress always validates against rgb
cy.wrapIframe().find('.skills-page-title-text-color')
- .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", () => {
@@ -138,7 +138,7 @@ context("Native JS Tests", () => {
// verify that there is no background set
// cypress always validates against rgb
cy.wrapIframe().find('.skills-page-title-text-color')
- .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", () => {
@@ -160,7 +160,7 @@ context("Native JS Tests", () => {
// verify that there is no background set
// cypress always validates against rgb
cy.wrapIframe().find('.skills-page-title-text-color')
- .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 38151c4..5a95fbe 100644
--- 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
@@ -150,7 +150,7 @@ context('React Tests', () => {
// verify that there is no background set
// cypress always validates against rgb
cy.wrapIframe().find('.skills-page-title-text-color')
- .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
// cypress always validates against rgb
cy.wrapIframe().find('.skills-page-title-text-color')
- .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 d861deb..4037779 100644
--- 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
@@ -207,7 +207,7 @@ context('Vue Tests', () => {
cy.get('#SkillsDirectiveErrorwithButton button').click()
cy.wait('@postSkill');
cy.get('@postSkill').then((xhr) => {
- expect(xhr.status).to.eq(400)
+ expect(xhr.status).to.eq(404)
expect(xhr.responseBody).to.have.property('explanation').to.eq('Failed to report skill event because skill definition does not exist.')
});
})
@@ -221,7 +221,7 @@ context('Vue Tests', () => {
cy.get('#SkillsDirectiveErrorwithInput input').type('h')
cy.wait('@postSkill');
cy.get('@postSkill').then((xhr) => {
- expect(xhr.status).to.eq(400)
+ expect(xhr.status).to.eq(404)
expect(xhr.responseBody).to.have.property('explanation').to.eq('Failed to report skill event because skill definition does not exist.')
});
})
@@ -240,7 +240,7 @@ context('Vue Tests', () => {
// verify that there is no background set
// cypress always validates against rgb
cy.wrapIframe().find('.skills-page-title-text-color')
- .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', () => {
@@ -261,7 +261,7 @@ context('Vue Tests', () => {
// verify that there is no background set
// cypress always validates against rgb
cy.wrapIframe().find('.skills-page-title-text-color')
- .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')
cy.wait('@getToken2')
@@ -284,7 +284,7 @@ context('Vue Tests', () => {
// verify that there is no background set
// cypress always validates against rgb
cy.wrapIframe().find('.skills-page-title-text-color')
- .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', () => {
@@ -407,7 +407,7 @@ context('Vue Tests', () => {
// verify that there is no background set
// cypress always validates against rgb
cy.wrap(body).find('.skills-page-title-text-color')
- .should('have.css', 'background-color').and('equal', 'rgb(255, 255, 255)');
+ .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 d32ca41..d66fb30 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
@@ -182,7 +182,7 @@ Cypress.Commands.add("cdClickSubj", (subjIndex, expectedTitle) => {
Cypress.Commands.add("cdBack", (expectedTitle = 'User Skills') => {
cy.wrapIframe().find('[data-cy=back]').click()
- cy.wrapIframe().find('[data-cy=title]').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.0.1-SNAPSHOT</version>
<version>3.3.1-SNAPSHOT</version>
</parent>
<artifactId>skills-int-service</artifactId>
<description>SkillTree Client Lib Integration Tests</description>
@@ -25,7 +25,7 @@
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.8</version>
<version>4.5.13</version>
</dependency>
</dependencies>
@@ -38,7 +38,7 @@
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.1.0</version>
<version>3.2.0</version>
<executions>
<execution>
<id>copy Vue.js frontend content</id>
@@ -60,29 +60,7 @@
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>copy Vue.js Extended frontend content</id>
<phase>generate-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${basedir}/src/main/resources/public/vuejs-extended</outputDirectory>
<overwrite>true</overwrite>
<resources>
<resource>
<directory>${project.parent.basedir}/skills-int-client-vue-extended/dist</directory>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.1.0</version>
<version>3.2.0</version>
<executions>
<execution>
<id>copy Angular frontend content</id>
@@ -125,7 +103,7 @@
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.1.0</version>
<version>3.2.0</version>
<executions>
<execution>
<id>copy Native JS frontend content</id>
@@ -177,6 +155,8 @@
<includedLicense>Eclipse Public License - Version 1.0</includedLicense>
<includedLicense>The 3-Clause BSD License</includedLicense>
<includedLicense>Common Development and Distribution License 1.1 (CDDL-1.1) + The GNU General Public License (GPL) Version 2 with the Classpath Exception</includedLicense>
<includedLicense>Eclipse Public License - Version 2.0</includedLicense>
<includedLicense>The GNU General Public License (GPL) Version 2 with the Classpath Exception</includedLicense>
</includedLicenses>
<licenseMerges>
<licenseMerge>The Apache Software License, Version 2.0|Apache License, Version 2.0</licenseMerge>
@@ -189,6 +169,8 @@
<licenseMerge>Eclipse Public License - Version 1.0|Eclipse Public License 1.0</licenseMerge>
<licenseMerge>The 3-Clause BSD License|New BSD License</licenseMerge>
<licenseMerge>Eclipse Public License - Version 1.0|Eclipse Public License - v 1.0</licenseMerge>
<licenseMerge>Eclipse Public License - Version 2.0|EPL 2.0</licenseMerge>
<licenseMerge>The GNU General Public License (GPL) Version 2 with the Classpath Exception|GPL2 w/ CPE</licenseMerge>
</licenseMerges>
</configuration>
</execution>
@@ -17,15 +17,9 @@ package skills.example;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.http.client.HttpClient;
import org.apache.http.conn.ssl.AllowAllHostnameVerifier;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.impl.client.HttpClientBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.http.*;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.client.support.BasicAuthenticationInterceptor;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
@@ -48,6 +42,9 @@ public class Controller {
RestTemplate restTemplate;
public Controller(RestTemplateBuilder restTemplateBuilder) {
// RestTemplateBuilder will utilize the SecurityRestTemplateCustomizer, which
// will configure HttpComponentsClientHttpRequestFactory as SpringTemplate does
// not by default keeps track of session
restTemplate = restTemplateBuilder.build();
}
@@ -131,8 +128,6 @@ public class Controller {
private void authIfNecessary() {
if (!skillsConfig.getAuthMode().equalsIgnoreCase("pki")) {
// must configure HttpComponentsClientHttpRequestFactory as SpringTemplate does
// not by default keeps track of session
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
@@ -35,8 +35,10 @@ public class SecurityRestTemplateCustomizer implements RestTemplateCustomizer {
@Override
public void customize(RestTemplate restTemplate) {
// must configure HttpComponentsClientHttpRequestFactory as SpringTemplate does
// not by default keeps track of session. This method is called by the
// RestTemplateBuilder used in the skills.example.Controller constructor
HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory();
boolean pkiAuthMode = skillsConfig.getAuthMode().equalsIgnoreCase("pki");
HttpClient client = getHttpClient();
clientHttpRequestFactory.setHttpClient(client);
restTemplate.setRequestFactory(clientHttpRequestFactory);
@@ -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'],
};
+23 -21
View File
@@ -1,6 +1,6 @@
{
"name": "@skilltree/skills-client-js",
"version": "3.0.1",
"version": "3.3.1",
"license": "Apache-2.0",
"description": "SkillTree Client Lib - Native JS Lib",
"main": "dist/skills-client-js.umd.min.js",
@@ -22,33 +22,35 @@
},
"author": "SkillTree Team",
"devDependencies": {
"@babel/cli": "7.6.2",
"@babel/core": "7.6.2",
"@babel/polyfill": "7.7.0",
"@babel/preset-env": "7.6.2",
"@rollup/plugin-commonjs": "11.0.2",
"@rollup/plugin-node-resolve": "7.1.1",
"@rollup/plugin-replace": "2.3.1",
"axios-mock-adapter": "1.17.0",
"babel-jest": "24.9.0",
"eslint": "6.5.1",
"eslint-config-airbnb-base": "14.0.0",
"eslint-plugin-import": "2.18.2",
"jest": "24.9.0",
"@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",
"@rollup/plugin-node-resolve": "9.0.0",
"@rollup/plugin-replace": "2.3.3",
"axios-mock-adapter": "1.18.2",
"babel-jest": "26.5.2",
"eslint": "7.11.0",
"eslint-config-airbnb-base": "14.2.0",
"eslint-plugin-import": "2.22.1",
"jest": "26.5.3",
"license-check-and-add": "3.0.4",
"license-checker": "25.0.1",
"regenerator-runtime": "0.13.5",
"rollup": "1.31.1",
"rollup-plugin-babel": "4.3.3",
"regenerator-runtime": "0.13.7",
"rollup": "2.30.0",
"rollup-plugin-babel": "4.4.0",
"rollup-plugin-eslint": "7.0.0",
"rollup-plugin-terser": "5.2.0",
"rollup-plugin-terser": "7.0.2",
"xhr-mock": "2.5.1"
},
"dependencies": {
"axios": "0.19.0",
"axios": "0.21.1",
"history": "5.0.0",
"js-logger": "1.6.0",
"postmate": "1.5.1",
"sockjs-client": "1.4.0",
"postmate": "1.5.2",
"sockjs-client": "1.5.0",
"webstomp-client": "1.2.6"
},
"keywords": [
+1
View File
@@ -17,6 +17,7 @@ module.exports = {
plugins: [
eslint(),
babel({
runtimeHelpers: true,
exclude: 'node_modules/**',
}),
resolve({
+26 -8
View File
@@ -17,10 +17,10 @@ import log from 'js-logger';
export default {
sendLogMessage(conf, messages, context) {
sendLogMessage(serviceUrl, messages, context) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('POST', `${conf.getServiceUrl()}/public/log`);
xhr.open('POST', `${serviceUrl}/public/log`);
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status !== 200) {
@@ -36,13 +36,13 @@ export default {
});
},
configureLogging(conf, response) {
configureLogging(serviceUrl, response) {
const loggingEnabled = response.clientLib ? response.clientLib.loggingEnabled === 'true' : false;
if (loggingEnabled) {
const consoleHandler = log.createDefaultHandler();
log.setHandler((messages, context) => {
consoleHandler(messages, context);
this.sendLogMessage(conf, messages, context);
this.sendLogMessage(serviceUrl, messages, context);
});
let loggingLevel = log[response.clientLib.loggingLevel];
log.setLevel(loggingLevel);
@@ -50,7 +50,7 @@ export default {
loggingLevel = log.INFO;
log.warn(`SkillsClient::SkillService::Unknown log level [${response.clientLib.loggingLevel}], defaulting to INFO`);
}
log.info(`SkillsClient::SkillService::Logger enabled, log level set to [${loggingLevel}]`);
log.info(`SkillsClient::SkillService::Logger enabled, log level set to [${loggingLevel.name}]`);
}
},
@@ -95,20 +95,38 @@ export default {
});
},
getAuthenticationToken(authenticator) {
isOAuthMode(authenticator, serviceUrl) {
return typeof authenticator === 'string' && authenticator.startsWith(`${serviceUrl}/oauth2/authorization`);
},
getAuthenticationToken(authenticator, serviceUrl, projectId, oauthRedirect = false) {
return new Promise((resolve, reject) => {
const isOAuthMode = this.isOAuthMode(authenticator, serviceUrl);
const xhr = new XMLHttpRequest();
xhr.open('GET', authenticator);
if (!isOAuthMode) {
xhr.open('GET', authenticator);
} else {
xhr.open('GET', `${serviceUrl}/api/projects/${projectId}/token`);
xhr.withCredentials = true;
}
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status !== 200) {
reject(new Error(`SkillTree: Unable to authenticate using [${authenticator}] endpoint. Response Code=[${xhr.status}].\n
if (isOAuthMode && oauthRedirect && xhr.status === 401) {
// if we get 401 and we are using OAuth, then redirect to the OAuth Provider
const oauthAuthenticator = `${authenticator}?skillsRedirectUri=${window.location}`;
log.info(`SkillsClient::SkillService::unable to get oAuth token, navigating to [${oauthAuthenticator}]`);
window.location.assign(oauthAuthenticator);
resolve();
} else {
reject(new Error(`SkillTree: Unable to authenticate using [${authenticator}] endpoint. Response Code=[${xhr.status}].\n
Ideas to diagnose:\n
(1) verify that the authenticator property is correct.\n
(2) verify that the server providing the authenticator endpoint is responding (ex. daemon is running, network path is clear).\n
(3) check logs on the server providing authenticator endpoint.\n
Full Response=[${xhr.response}]`));
}
} else {
const response = JSON.parse(xhr.response);
if (!response.access_token) {
@@ -53,6 +53,7 @@ const setInitialized = (conf) => {
};
const setConfigureWasCalled = () => {
log.info('SkillsClient::SkillConfiguration::configured');
configureCalled = true;
};
@@ -64,7 +65,12 @@ const exportObject = {
projectId,
authenticator,
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
@@ -81,29 +87,36 @@ const exportObject = {
throw new Error(`SkillTree: SkillsConfiguration.configure received invalid parameter for authenticator=[${authenticator}]`);
}
this.projectId = projectId;
this.serviceUrl = serviceUrl;
this.serviceUrl = `${serviceUrl}`.trim().replace(/\/$/, '');
this.authenticator = authenticator;
this.authToken = authToken;
skillsService.getServiceStatus(`${this.getServiceUrl()}/public/status`).then((response) => {
this.status = response.status;
skillsService.configureLogging(this, response);
skillsService.configureLogging(this.getServiceUrl(), response);
log.info(`Returned status [${JSON.stringify(response)}]`);
if (response.oAuthProviders && response.oAuthProviders.includes(authenticator)) {
this.authenticator = `${this.getServiceUrl()}/oauth2/authorization/${authenticator}`;
log.info(`Auto configured authenticator [${this.authenticator}] for provider [${authenticator}]`);
}
if (!this.isPKIMode() && !this.getAuthToken()) {
skillsService.getAuthenticationToken(this.getAuthenticator(), this.getServiceUrl(), this.getProjectId(), oauthRedirect)
.then((token) => {
this.setAuthToken(token);
setInitialized(this);
setConfigureWasCalled();
});
} else {
setInitialized(this);
setConfigureWasCalled();
}
}).catch((error) => {
// eslint-disable-next-line no-console
console.error('Error configuring logging', error);
console.error('Error getting service status', error);
setConfigureWasCalled();
initializedResolvers.resolve();
});
if (!this.isPKIMode() && !this.getAuthToken()) {
skillsService.getAuthenticationToken(this.getAuthenticator())
.then((token) => {
this.setAuthToken(token);
setInitialized(this);
});
} else {
setInitialized(this);
}
setConfigureWasCalled();
log.info('SkillsClient::SkillConfiguration::configured');
},
afterConfigure() {
@@ -123,6 +136,10 @@ SkillsConfiguration is a singleton and you only have to do this once. Please see
return this.getAuthenticator() === 'pki' || this.getAuthToken() === 'pki';
},
isOAuthMode() {
return skillsService.isOAuthMode(this.authenticator, this.serviceUrl);
},
isInitialized() {
return initialized;
},
@@ -158,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);
},
+84 -21
View File
@@ -14,23 +14,29 @@
* limitations under the License.
*/
import Postmate from 'postmate';
import axios from 'axios';
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;
}
@@ -39,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;
@@ -66,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}`);
@@ -74,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) => {
@@ -82,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;
@@ -108,15 +147,17 @@ export default class SkillsDisplayJS {
log.debug('SkillsClient::SkillsDisplayJS::needs-authentication');
const isPkiMode = this.configuration.authenticator === 'pki';
if (!this.authenticationPromise && !isPkiMode) {
this.authenticationPromise = axios.get(this.configuration.authenticator)
this.authenticationPromise = skillsService.getAuthenticationToken(this.configuration.authenticator, this.configuration.serviceUrl, this.configuration.projectId)
.then((result) => {
child.call('updateAuthenticationToken', result.data.access_token);
child.call('updateAuthenticationToken', result);
this.navigate();
})
.finally(() => {
this.authenticationPromise = null;
});
} else if (isPkiMode) {
child.call('updateAuthenticationToken', 'pki');
this.navigate();
}
});
});
@@ -126,25 +167,36 @@ export default class SkillsDisplayJS {
}
_checkAndHandleServiceStatus(iframeContainer) {
if (!SkillsConfiguration.getServiceStatus()) {
this.status = SkillsConfiguration.getServiceStatus();
if (!this.status) {
log.info('SkillsClient::SkillsDisplayJS::SkillsConfiguration.configure() was not called, checking status endpoint.');
skillsService.getServiceStatus(`${this.configuration.serviceUrl}/public/status`)
.catch((error) => {
let errorMessage = 'Please ensure the skilltree server is running';
if (this.configuration.serviceUrl && this.configuration.serviceUrl.startsWith('https')) {
errorMessage += ' and that your browser trusts the server\'s certificate';
}
errorMessage += `. skilltree service URL: ${this.configuration.serviceUrl}`;
/* eslint-disable no-console */
console.error(errorMessage);
log.error(`SkillsClient::SkillsDisplayJS::${errorMessage}`, error);
ErrorPageUtils.removeAllChildren(iframeContainer);
iframeContainer.appendChild(ErrorPageUtils.buildErrorPage());
iframeContainer.setAttribute('style', 'border: 5px; height: 20rem; width: 100%');
});
skillsService.getServiceStatus(`${this.configuration.serviceUrl}/public/status`).then((response) => {
this.status = response.status;
skillsService.configureLogging(this, response);
}).catch((error) => {
let errorMessage = 'Please ensure the skilltree server is running';
if (this.configuration.serviceUrl && this.configuration.serviceUrl.startsWith('https')) {
errorMessage += ' and that your browser trusts the server\'s certificate';
}
errorMessage += `. skilltree service URL: ${this.configuration.serviceUrl}`;
/* eslint-disable no-console */
console.error(errorMessage);
log.error(`SkillsClient::SkillsDisplayJS::${errorMessage}`, error);
ErrorPageUtils.removeAllChildren(iframeContainer);
iframeContainer.appendChild(ErrorPageUtils.buildErrorPage());
iframeContainer.setAttribute('style', 'border: 5px; height: 20rem; width: 100%');
});
}
}
_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;
@@ -174,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',
@@ -183,6 +241,8 @@ export default class SkillsDisplayJS {
'autoScrollStrategy',
'isSummaryOnly',
'scrollTopOffset',
'internalBackButton',
'updateHistory',
];
const toTest = { ...this._options, ...options };
const passedOptions = Object.keys(toTest);
@@ -197,5 +257,8 @@ export default class SkillsDisplayJS {
if (this._childFrame) {
this._childFrame.destroy();
}
const queryParams = new URLSearchParams(window.location.search);
queryParams.delete(skillsClientDisplayPath);
unlisten();
}
}
@@ -51,8 +51,12 @@ export default class SkillsLevelJS {
this._projectId = SkillsConfiguration.getProjectId();
log.debug(`SkillsClient::SkillsLevelJS::getting projectId from SkillsConfiguration: [${this._projectId}]`);
}
const authToken = SkillsConfiguration.getAuthToken();
axios.get(`${SkillsConfiguration.getServiceUrl()}/api/projects/${this._projectId}/level`, { withCredentials: true, headers: { Authorization: `Bearer ${authToken}` } })
const requestConfig = { withCredentials: true };
if (!SkillsConfiguration.isPKIMode()) {
const authToken = SkillsConfiguration.getAuthToken();
requestConfig.headers = { Authorization: `Bearer ${authToken}` };
}
axios.get(`${SkillsConfiguration.getServiceUrl()}/api/projects/${this._projectId}/level`, requestConfig)
.then((result) => {
this.setSkillLevel(result.data);
SkillsReporter.addSuccessHandler(this.update.bind(this));
+79 -36
View File
@@ -17,6 +17,7 @@ import SockJS from 'sockjs-client';
import Stomp from 'webstomp-client';
import log from 'js-logger';
import SkillsConfiguration from '../config/SkillsConfiguration';
import skillsService from '../SkillsService';
const SUCCESS_EVENT = 'skills-report-success';
const FAILURE_EVENT = 'skills-report-error';
@@ -24,11 +25,15 @@ 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));
};
const callErrorHandlers = (event) => {
// eslint-disable-next-line no-console
console.error('Error reporting skill:', event);
errorHandlerCache.forEach((it) => it(event));
};
@@ -60,33 +65,46 @@ const connectWebsocket = (serviceUrl) => {
}
};
const getAuthenticationToken = function getAuthenticationToken() {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', SkillsConfiguration.getAuthenticator());
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);
});
}
};
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status !== 200) {
reject(new Error('Unable to authenticate'));
} else {
const response = JSON.parse(xhr.response);
if (!response.access_token) {
reject(new Error('Unable to authenticate'));
} else {
resolve(response.access_token);
}
}
}
};
xhr.send();
});
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}]`);
getAuthenticationToken()
skillsService.getAuthenticationToken(SkillsConfiguration.getAuthenticator(), SkillsConfiguration.getServiceUrl(), SkillsConfiguration.getProjectId())
.then((token) => {
SkillsConfiguration.setAuthToken(token);
this.reportSkill(userSkillId, attemptCount + 1)
@@ -104,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) {
@@ -124,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}`);
@@ -138,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);
@@ -153,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));
@@ -163,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}`);
}
});
@@ -185,6 +222,12 @@ const SkillsReporter = {
getConf() {
return SkillsConfiguration;
},
cancelRetryChecker() {
clearInterval(retryIntervalId);
this.retryEnabled = false;
},
};
export {
@@ -0,0 +1,89 @@
/*
* 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 SkillsService from '../src/SkillsService';
require('@babel/polyfill');
describe('OAuth auto redirect tests', () => {
const mockServiceUrl = 'http://some.com';
const mockProjectId = 'proj1';
const authEndpoint = `${mockServiceUrl}/oauth2/authorization/gitlab`;
const oldWindowLocation = window.location
beforeAll(() => {
delete window.location
window.location = Object.defineProperties(
{},
{
...Object.getOwnPropertyDescriptors(oldWindowLocation),
assign: {
configurable: true,
value: jest.fn(),
},
},
)
})
afterAll(() => {
// restore `window.location` to the `jsdom` `Location` object
window.location = oldWindowLocation
})
// replace the real XHR object with the mock XHR object before each test
beforeEach(() => {
mock.setup();
const url = /.*\/api\/projects\/proj1\/skillsClientVersion/;
window.location.assign.mockReset()
});
// put the real XHR object back and clear the mocks after each test
afterEach(() => {
mock.teardown();
});
it('oauth auto redirects when oauthRedirect=true', async () => {
const oauthTokenEndpoint = `${mockServiceUrl}/api/projects/${mockProjectId}/token`;
mock.get(oauthTokenEndpoint, (req, res) => res.status(401));
await SkillsService.getAuthenticationToken(authEndpoint, mockServiceUrl, mockProjectId, true);
expect(window.location.assign).toHaveBeenCalledTimes(1)
expect(window.location.assign).toHaveBeenCalledWith(`${authEndpoint}?skillsRedirectUri=http://localhost/`)
});
it('oauth does not auto redirect when oauthRedirect=false', async () => {
const oauthTokenEndpoint = `${mockServiceUrl}/api/projects/${mockProjectId}/token`;
mock.get(oauthTokenEndpoint, (req, res) => res.status(401));
try {
await SkillsService.getAuthenticationToken(authEndpoint, mockServiceUrl, mockProjectId);
} catch (e) {
expect(e.message).toMatch('Unable to authenticate');
expect(window.location.assign).toHaveBeenCalledTimes(0);
}
});
it('oauth does not auto redirect when oauthRedirect=true but token is returned', async () => {
const oauthTokenEndpoint = `${mockServiceUrl}/api/projects/${mockProjectId}/token`;
mock.get(oauthTokenEndpoint, (req, res) => res.status(200).body('{"access_token": "token1"}'));
const result = await SkillsService.getAuthenticationToken(authEndpoint, mockServiceUrl, mockProjectId, true);
expect(window.location.assign).toHaveBeenCalledTimes(0);
expect(result).toMatch('token1');
});
});
@@ -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();
const mockProjectId = Math.random();
const mockAuthenticator = Math.random();
const mockAuthToken = Math.random();
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++;
@@ -111,6 +197,19 @@ describe('SkillsConfiguration', () => {
SkillsConfiguration.configure({ authenticator: 'pki', projectId: 'proj', serviceUrl: 'http://somewhere' });
});
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: authEndpoint,
});
await flushPromises();
expect(SkillsConfiguration.getServiceUrl()).toBe('http://some');
});
});
describe('validation', () => {
@@ -120,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]');
});
@@ -152,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({
@@ -202,14 +304,15 @@ describe('SkillsConfiguration', () => {
expect(oldPromise).not.toBe(newPromise);
});
it('clears the authToken', () => {
const mockAuthToken = Math.random();
it('clears the authToken', async () => {
const mockAuthToken = Math.random().toString();;
SkillsConfiguration.configure({
authToken: mockAuthToken,
projectId: 'proj',
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', () => {
@@ -355,11 +381,13 @@ describe('SkillsDisplayJS', () => {
client.attachTo(mockIframeContainer);
const mockAccessToken = Math.random();
const mockAccessToken = `${Math.random()}`;
mockHttp.onGet(mockAuthenticator).reply(200, {
access_token: mockAccessToken
});
mock.get('http://urltonowhere.com/', (req, res) => res.status(200).body(`{"access_token":"${mockAccessToken}"}`));
setTimeout(() => {
expect(client.authenticationPromise).not.toBeDefined();
mockChildFrame.emit('needs-authentication');
@@ -435,6 +463,7 @@ describe('SkillsDisplayJS', () => {
theme: mockTheme,
minHeight: '960px',
isSummaryOnly: false,
internalBackButton: false,
},
});
});
@@ -471,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,
},
});
});
@@ -121,7 +121,7 @@ describe('authFormTests()', () => {
try {
await SkillsReporter.reportSkill('skill1');
} catch (e) {
expect(e.message).toMatch('Unable to authenticate');
expect(e.message).toMatch('Full Response=[{\"not_access_token\": \"token\"}]');
}
});
@@ -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));
});
});
@@ -100,9 +100,7 @@ describe('userSkillsService', () => {
beforeEach(() => {
MockXMLHttpRequest.status = 200;
mockResponse = JSON.stringify({
hello: 'world',
});
mockResponse = JSON.stringify({"success":true,"projectId":"movies","skillId":"IronMan","name":"Iron Man","pointsEarned":8,"skillApplied":true,"explanation":"Skill event was applied","completed":[]});
MockXMLHttpRequest.response = mockResponse;
promise = SkillsReporter.reportSkill(mockUserSkillId);
});
@@ -141,9 +139,7 @@ describe('userSkillsService', () => {
beforeEach(() => {
MockXMLHttpRequest.status = 404;
promise = SkillsReporter.reportSkill(mockUserSkillId);
mockError = JSON.stringify({
you: 'errored',
});
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"});
MockXMLHttpRequest.response = mockError;
});
@@ -159,6 +155,7 @@ describe('userSkillsService', () => {
it('calls error handlers', (done) => {
const handler1 = jest.fn();
const handler2 = jest.fn();
const consoleSpy = jest.spyOn(console, 'error');
SkillsReporter.addErrorHandler(handler1);
SkillsReporter.addErrorHandler(handler2);
@@ -168,6 +165,7 @@ describe('userSkillsService', () => {
promise.finally(() => {
expect(handler1).toHaveBeenCalledWith(JSON.parse(mockError));
expect(handler2).toHaveBeenCalledWith(JSON.parse(mockError));
expect(consoleSpy).toHaveBeenCalledWith('Error reporting skill:', JSON.parse(mockError));
done();
});
});
+33 -33
View File
@@ -1,6 +1,6 @@
{
"name": "skills-client-ng-parent",
"version": "3.0.1",
"version": "3.3.1",
"license": "Apache-2.0",
"description": "SkillTree Client Lib - Angular",
"author": "SkillTree Team",
@@ -19,44 +19,44 @@
"prepare": "cd projects/skilltree/skills-client-ng && npm run prepare && cd -"
},
"dependencies": {
"@angular/animations": "10.0.3",
"@angular/common": "10.0.3",
"@angular/compiler": "10.0.3",
"@angular/core": "10.0.3",
"@angular/forms": "10.0.3",
"@angular/platform-browser": "10.0.3",
"@angular/platform-browser-dynamic": "10.0.3",
"@angular/router": "10.0.3",
"@skilltree/skills-client-js": "3.0.1",
"lodash-es": "4.17.15",
"rxjs": "6.5.5",
"tslib": "2.0.0",
"zone.js": "0.10.3"
"@angular/animations": "10.1.5",
"@angular/common": "10.1.5",
"@angular/compiler": "10.1.5",
"@angular/core": "10.1.5",
"@angular/forms": "10.1.5",
"@angular/platform-browser": "10.1.5",
"@angular/platform-browser-dynamic": "10.1.5",
"@angular/router": "10.1.5",
"@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"
},
"devDependencies": {
"@angular-devkit/build-angular": "0.1000.3",
"@angular-devkit/build-ng-packagr": "0.1000.3",
"@angular/cli": "10.0.2",
"@angular/compiler-cli": "10.0.3",
"@types/jasmine": "3.5.0",
"@types/jasminewd2": "2.0.3",
"@types/lodash-es": "4.17.3",
"@types/node": "12.11.1",
"codelyzer": "6.0.0",
"jasmine-core": "3.5.0",
"jasmine-spec-reporter": "5.0.0",
"karma": "5.0.0",
"@angular-devkit/build-angular": "0.1001.6",
"@angular-devkit/build-ng-packagr": "0.1001.6",
"@angular/cli": "10.1.6",
"@angular/compiler-cli": "10.1.5",
"@types/jasmine": "3.5.14",
"@types/jasminewd2": "2.0.8",
"@types/lodash-es": "4.17.4",
"@types/node": "14.11.8",
"codelyzer": "6.0.1",
"jasmine-core": "3.6.0",
"jasmine-spec-reporter": "6.0.0",
"karma": "5.2.3",
"karma-chrome-launcher": "3.1.0",
"karma-coverage-istanbul-reporter": "3.0.2",
"karma-jasmine": "3.3.0",
"karma-jasmine-html-reporter": "1.5.0",
"karma-coverage-istanbul-reporter": "3.0.3",
"karma-jasmine": "4.0.1",
"karma-jasmine-html-reporter": "1.5.4",
"license-check-and-add": "3.0.4",
"license-checker": "25.0.1",
"ng-packagr": "10.0.0",
"ng-packagr": "10.1.2",
"protractor": "7.0.0",
"ts-node": "8.3.0",
"tslint": "6.1.0",
"typescript": "3.9.5"
"ts-node": "9.0.0",
"tslint": "6.1.3",
"typescript": "4.0.3"
},
"keywords": [
"SkillTree",
@@ -1,6 +1,6 @@
{
"name": "@skilltree/skills-client-ng",
"version": "3.0.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.0.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"
}
}
]
]
}
}
}
+25 -26
View File
@@ -1,6 +1,6 @@
{
"name": "@skilltree/skills-client-react",
"version": "3.0.1",
"version": "3.3.1",
"license": "Apache-2.0",
"description": "SkillTree Client Lib - React",
"main": "dist/skills-client-react.umd.min.js",
@@ -25,41 +25,40 @@
},
"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.6.4",
"@babel/core": "7.8.7",
"@babel/preset-env": "7.6.3",
"@babel/preset-react": "7.8.3",
"@rollup/plugin-alias": "2.2.0",
"@rollup/plugin-commonjs": "11.0.2",
"@rollup/plugin-node-resolve": "7.1.1",
"@rollup/plugin-replace": "2.3.1",
"eslint-config-airbnb-base": "14.0.0",
"@babel/cli": "7.11.6",
"@babel/core": "7.11.6",
"@babel/preset-env": "7.11.5",
"@babel/preset-react": "7.10.4",
"@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",
"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.11.0",
"react-dom": "16.11.0",
"react-scripts": "3.2.0",
"rollup": "1.26.3",
"rollup-plugin-babel": "4.3.3",
"react": "16.13.1",
"react-dom": "16.13.1",
"react-scripts": "3.4.3",
"rollup": "2.29.0",
"rollup-plugin-babel": "4.4.0",
"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": "5.1.2",
"rollup-plugin-uglify": "6.0.3",
"eslint-plugin-react-hooks": "4.0.8",
"babel-eslint": "10.1.0",
"eslint-plugin-react": "7.20.5"
"rollup-plugin-peer-deps-external": "2.2.0",
"rollup-plugin-terser": "7.0.2"
},
"dependencies": {
"@skilltree/skills-client-js": "3.0.1",
"axios": "0.19.0",
"axios-auth-refresh": "1.0.7"
"@skilltree/skills-client-js": "3.3.1",
"axios": "0.21.1",
"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]);
+27 -28
View File
@@ -1,6 +1,6 @@
{
"name": "@skilltree/skills-client-vue",
"version": "3.0.1",
"version": "3.3.1",
"license": "Apache-2.0",
"description": "SkillTree Client Lib - Vue.js",
"main": "dist/skills-client-vue.umd.min.js",
@@ -22,41 +22,40 @@
},
"author": "SkillTree Team",
"devDependencies": {
"@babel/cli": "7.6.4",
"@babel/preset-env": "7.6.3",
"babel-eslint": "10.1.0",
"@rollup/plugin-alias": "2.2.0",
"@rollup/plugin-commonjs": "11.0.2",
"@rollup/plugin-node-resolve": "7.1.1",
"@rollup/plugin-replace": "2.3.1",
"@vue/test-utils": "1.0.0-beta.29",
"@babel/cli": "7.11.6",
"@babel/preset-env": "7.11.5",
"@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-jest": "24.9.0",
"eslint-config-airbnb-base": "14.0.0",
"eslint-plugin-import": "2.18.2",
"eslint-plugin-vue": "5.2.3",
"jest": "24.9.0",
"babel-eslint": "10.1.0",
"babel-jest": "26.5.2",
"eslint-config-airbnb-base": "14.2.0",
"eslint-plugin-import": "2.22.1",
"eslint-plugin-vue": "7.0.1",
"jest": "26.5.3",
"license-check-and-add": "3.0.4",
"license-checker": "25.0.1",
"rollup": "1.26.3",
"rollup-plugin-babel": "4.3.3",
"rollup": "2.29.0",
"rollup-plugin-babel": "4.4.0",
"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": "5.1.2",
"rollup-plugin-uglify": "6.0.3",
"rollup-plugin-vue": "5.1.2",
"vue": "2.6.10",
"vue-jest": "3.0.5",
"vue-template-compiler": "2.6.10",
"vue-eslint-parser": "7.1.0"
"rollup-plugin-peer-deps-external": "2.2.3",
"rollup-plugin-terser": "7.0.2",
"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"
},
"dependencies": {
"@skilltree/skills-client-js": "3.0.1",
"axios": "0.19.2",
"axios-auth-refresh": "1.0.7",
"lodash": "4.17.20",
"@skilltree/skills-client-js": "3.3.1",
"axios": "0.21.1",
"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);
@@ -27,7 +27,7 @@ describe('SkillsDisplay', () => {
it('exists and is a vue component', () => {
const component = shallowMount(SkillsDisplay);
expect(component.isVueInstance()).toBeTruthy();
expect((component).vm).toBeTruthy()
});
describe('destructing', () => {
@@ -39,7 +39,7 @@ describe('SkillsDisplay', () => {
});
});
it('when version is updated, clientDisplay is updated with the new version', () => {
it('when version is updated, clientDisplay is updated with the new version', async () => {
const component = shallowMount(SkillsDisplay, {
propsData: {
version: 1,
@@ -48,7 +48,7 @@ describe('SkillsDisplay', () => {
expect(component.props().version).toBe(1);
component.setProps({ version: 5 });
await component.setProps({ version: 5 });
const mockSkillsDisplayInstance = SkillsDisplayJS.mock.instances[0];
@@ -23,14 +23,14 @@ import { shallowMount } from '@vue/test-utils';
describe('SkillsLevel', () => {
describe('rendering', () => {
it('only renders once skillsLevel is initialized', () => {
it('only renders once skillsLevel is initialized', async () => {
SkillsConfiguration.afterConfigure.mockImplementation(() => new Promise(() => { }))
const wrapper = shallowMount(SkillsLevel);
const selector = '.skills-level-text-display';
expect(wrapper.find(selector).isVisible()).toBe(false);
wrapper.setData({ skillLevel: 1 });
await wrapper.setData({ skillLevel: 1 });
expect(wrapper.find(selector).isVisible()).toBe(true);
});