1
0
mirror of https://github.com/sasjs/adapter.git synced 2025-12-11 01:14:36 +00:00

Compare commits

...

165 Commits

Author SHA1 Message Date
Krishna Acondy
d30a1890a1 chore(*): update typedoc docs 2021-03-30 07:57:27 +01:00
Krishna Acondy
f1c2569de3 fix(request): update typings and documentation for request method 2021-03-30 07:57:11 +01:00
Muhammad Saad
4826388cdd Merge pull request #310 from sasjs/issue-309
Issue 309
2021-03-29 18:07:51 +05:00
e88736056a test: fix 2021-03-29 13:43:51 +02:00
9da2a29a72 chore: for VIYA calling API endpoint 2021-03-28 21:55:47 +02:00
dce8a08a86 lint: fix 2021-03-28 19:27:57 +02:00
1fabb9e610 test: fix 2021-03-28 19:04:10 +02:00
23db0ac80d style: lint 2021-03-28 18:40:22 +02:00
28370341d8 fix: login checkSession improved mechanism 2021-03-28 18:40:04 +02:00
Muhammad Saad
a023ffe850 Merge pull request #300 from sasjs/fix-log-by-chunks
fix: after job executing get complete log
2021-03-23 14:33:57 +05:00
Muhammad Saad
a4e77ecf6e Merge branch 'master' into fix-log-by-chunks 2021-03-23 13:04:39 +05:00
Allan Bowe
7efc0a1fb2 Merge pull request #275 from sasjs/allanbowe-patch-1
Update README.md
2021-03-22 22:37:16 +01:00
Allan Bowe
c3e2b2ce70 chore: updates to README 2021-03-22 20:56:18 +00:00
Saad Jutt
dde1228b1d fix: function renamed fetchLogByChunks 2021-03-23 00:50:33 +05:00
Saad Jutt
b3474b6dfb fix: after job executing get complete log 2021-03-22 19:21:01 +05:00
Allan Bowe
179a04ae31 Merge branch 'master' into allanbowe-patch-1 2021-03-16 17:02:47 +01:00
Yury Shkoda
2bdcbda54c Merge pull request #285 from sasjs/error-handling
fix(error-handling): added InternalServerError
2021-03-12 08:57:01 +03:00
Yury Shkoda
a123392c56 Merge branch 'master' into error-handling 2021-03-10 14:50:51 +03:00
Yury Shkoda
719135e366 fix(error-handling): added InternalServerError 2021-03-10 14:43:47 +03:00
Allan Bowe
ba619554b7 Merge branch 'master' into allanbowe-patch-1 2021-03-09 23:47:18 +01:00
Yury Shkoda
6a3ab7032f Merge pull request #284 from sasjs/error-handling
fix(error-handling): fixed console.log
2021-03-09 18:07:53 +03:00
Yury Shkoda
d818d14cb4 Merge branch 'master' into error-handling 2021-03-09 18:07:46 +03:00
Yury Shkoda
599c130395 fix(error-handling): fixed console.log 2021-03-09 18:06:27 +03:00
Yury Shkoda
9ef2759e27 Merge pull request #283 from sasjs/error-handling
fix(error-handling): catching unhandled promise rejection
2021-03-09 17:15:10 +03:00
Yury Shkoda
43355c88d4 Merge branch 'master' into error-handling 2021-03-09 17:12:45 +03:00
Yury Shkoda
15e1acaf4f fix(error-handling): catching unhandled promise rejection 2021-03-09 17:11:29 +03:00
Yury Shkoda
ec77ffdd88 Merge pull request #282 from sasjs/error-handling
fix(error-handling): console logged error
2021-03-09 15:55:39 +03:00
Yury Shkoda
9797c1ca84 Merge branch 'master' into error-handling 2021-03-09 15:53:40 +03:00
Yury Shkoda
bbe9633dc8 fix(error-handling): console logged error 2021-03-09 15:52:00 +03:00
Muhammad Saad
6f60ac5cc7 Merge branch 'master' into allanbowe-patch-1 2021-03-09 15:33:41 +05:00
Muhammad Saad
e7ba09793c Merge pull request #272 from sasjs/dependabot/npm_and_yarn/semantic-release-17.4.1
chore(deps-dev): bump semantic-release from 17.3.9 to 17.4.1
2021-03-09 15:32:57 +05:00
Muhammad Saad
c0c0800e61 Merge branch 'master' into dependabot/npm_and_yarn/semantic-release-17.4.1 2021-03-09 15:31:46 +05:00
Muhammad Saad
0bd9d8f93f Merge pull request #278 from sasjs/dependabot/npm_and_yarn/webpack-5.24.4
chore(deps-dev): bump webpack from 5.21.2 to 5.24.4
2021-03-09 15:31:37 +05:00
dependabot-preview[bot]
214fc2d5cd chore(deps-dev): bump semantic-release from 17.3.9 to 17.4.1
Bumps [semantic-release](https://github.com/semantic-release/semantic-release) from 17.3.9 to 17.4.1.
- [Release notes](https://github.com/semantic-release/semantic-release/releases)
- [Commits](https://github.com/semantic-release/semantic-release/compare/v17.3.9...v17.4.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2021-03-09 10:30:56 +00:00
Muhammad Saad
55b0e2f934 Merge branch 'master' into dependabot/npm_and_yarn/webpack-5.24.4 2021-03-09 15:30:18 +05:00
Muhammad Saad
609cd4ed6d Merge pull request #280 from sasjs/dependabot/npm_and_yarn/typedoc-0.20.30
chore(deps-dev): bump typedoc from 0.19.2 to 0.20.30
2021-03-09 15:29:36 +05:00
dependabot-preview[bot]
2b20bbdcc8 chore(deps-dev): bump typedoc from 0.19.2 to 0.20.30
Bumps [typedoc](https://github.com/TypeStrong/TypeDoc) from 0.19.2 to 0.20.30.
- [Release notes](https://github.com/TypeStrong/TypeDoc/releases)
- [Commits](https://github.com/TypeStrong/TypeDoc/compare/v0.19.2...v0.20.30)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2021-03-09 10:27:54 +00:00
dependabot-preview[bot]
946a95bea1 chore(deps-dev): bump webpack from 5.21.2 to 5.24.4
Bumps [webpack](https://github.com/webpack/webpack) from 5.21.2 to 5.24.4.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.21.2...v5.24.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2021-03-09 10:27:51 +00:00
Muhammad Saad
7ec1c152e3 Merge pull request #279 from sasjs/dependabot/npm_and_yarn/sasjs/utils-2.6.3
chore(deps): bump @sasjs/utils from 2.5.0 to 2.6.3
2021-03-09 15:26:27 +05:00
dependabot-preview[bot]
0cf1110018 chore(deps): bump @sasjs/utils from 2.5.0 to 2.6.3
Bumps [@sasjs/utils](https://github.com/sasjs/utils) from 2.5.0 to 2.6.3.
- [Release notes](https://github.com/sasjs/utils/releases)
- [Commits](https://github.com/sasjs/utils/compare/v2.5.0...v2.6.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2021-03-09 07:52:39 +00:00
Yury Shkoda
9c5ada6fa1 Merge pull request #277 from sasjs/error-handling
fix(error-handling): added catch block to poll job state
2021-03-09 10:24:14 +03:00
Yury Shkoda
0f5702e21b fix(lint): used latest version of prettier 2021-03-09 10:21:52 +03:00
Yury Shkoda
f10d6ec80a fix(lint): updated dependencies 2021-03-09 09:55:43 +03:00
Yury Shkoda
490215df90 fix(lint): fixed code style 2021-03-09 09:53:35 +03:00
Yury Shkoda
9d27451813 fix(lint): fixed code style 2021-03-09 09:44:10 +03:00
Yury Shkoda
a977f59675 fix(error-handling): added catch block to poll job state 2021-03-09 09:35:01 +03:00
Allan Bowe
51a09d049c Update README.md 2021-03-08 11:27:05 +01:00
Krishna Acondy
47be5e77e5 Merge pull request #269 from sasjs/tests-258
test: issue 258
2021-03-02 12:24:46 +00:00
Mihajlo Medjedovic
d517e79ec0 chore: cleanup 2021-03-02 13:13:03 +01:00
Mihajlo Medjedovic
8714ecdde8 test: issue 258 2021-02-26 14:40:07 +01:00
Muhammad Saad
2cffc57209 Merge pull request #259 from sasjs/issue-258
Issue 258
2021-02-25 18:41:21 +05:00
Muhammad Saad
4e43687de2 Merge branch 'master' into issue-258 2021-02-25 18:37:35 +05:00
Muhammad Saad
f8dab83e37 Merge pull request #267 from sasjs/error-handling
chore(error-handling): removed redundant catch block
2021-02-25 18:36:19 +05:00
Yury Shkoda
655af03cf3 chore(error-handling): removed redundant catch block 2021-02-25 15:20:35 +03:00
Yury Shkoda
0a4dd00edb Merge pull request #266 from sasjs/error-handling
fix(RequestClient): add catch block to authorize method
2021-02-25 12:59:47 +03:00
Yury Shkoda
55b4929c54 Merge branch 'master' into error-handling 2021-02-25 12:58:25 +03:00
Yury Shkoda
8eb73a6b3c fix(RequestClient): add catch block to authorize method 2021-02-25 12:55:34 +03:00
Krishna Acondy
0aeb201625 Merge branch 'master' into issue-258 2021-02-25 07:59:43 +00:00
Yury Shkoda
e0140a23c2 Merge pull request #264 from sasjs/error-handling
fix(RequestClient): improved handleError method
2021-02-25 09:52:40 +03:00
Yury Shkoda
ff6698a9d1 chore: removed async from parseResponse method 2021-02-25 09:51:00 +03:00
Yury Shkoda
843d498b72 fix(RequestClient): improved handleError method 2021-02-25 09:14:49 +03:00
Mihajlo Medjedovic
349612a065 Merge branch 'issue-258' of github.com:sasjs/adapter into issue-258 2021-02-24 14:11:12 +01:00
Mihajlo Medjedovic
e48b22128d chore: small fix, error was not beign returned 2021-02-24 14:11:00 +01:00
Krishna Acondy
14dfe4ec51 Merge branch 'master' into issue-258 2021-02-24 12:47:16 +00:00
Yury Shkoda
f679b17cbe Merge pull request #263 from sasjs/error-handling
fix(docs): updated docs
2021-02-24 15:44:11 +03:00
Yury Shkoda
36a0f0e743 Merge branch 'master' into error-handling 2021-02-24 15:39:40 +03:00
Yury Shkoda
ad563b9bc8 fix(docs): updated docs 2021-02-24 15:37:31 +03:00
Mihajlo Medjedovic
e0051bf276 chore: test fixing 2021-02-24 13:15:14 +01:00
Mihajlo Medjedovic
7b72998e1c Merge branches 'issue-258' and 'master' of github.com:sasjs/adapter 2021-02-24 12:57:24 +01:00
Yury Shkoda
893cce7f21 Merge pull request #262 from sasjs/error-handling
fix(startComputeJob): improved error handling
2021-02-24 14:50:15 +03:00
Yury Shkoda
bf7e8fd0e6 chore(startComputeJob): improved error handling 2021-02-24 12:49:41 +03:00
Mihajlo Medjedovic
66d02cf1d1 style: lint 2021-02-23 23:01:08 +01:00
Mihajlo Medjedovic
f2c8e40430 chore: cleanup, parseResponse optimization 2021-02-23 22:58:48 +01:00
Mihajlo Medjedovic
4b28ee8e73 style: lint 2021-02-23 22:36:32 +01:00
Mihajlo Medjedovic
c7e54cfe9f fix: web approach login callback and debug issue 2021-02-23 22:36:11 +01:00
Yury Shkoda
50be3acc78 Merge pull request #190 from sasjs/issue-186
feat: listFolder and improvements
2021-02-22 16:40:09 +03:00
Mihajlo Medjedovic
66c12a69bb typedoc: generated 2021-02-22 14:25:35 +01:00
Mihajlo Medjedovic
e2058f0c71 Merge branch 'issue-186' of github.com:sasjs/adapter into issue-186 2021-02-22 14:25:09 +01:00
Mihajlo Medjedovic
75a74152a5 chore: grammar fixing 2021-02-22 14:24:25 +01:00
Yury Shkoda
9344c4fd35 fixed grammar 2021-02-22 16:16:18 +03:00
Yury Shkoda
be0fe00076 fixed grammar 2021-02-22 16:12:56 +03:00
Yury Shkoda
a2069ee48b fixed grammar 2021-02-22 16:11:56 +03:00
Mihajlo Medjedovic
907885cf85 style: lint 2021-02-22 13:56:06 +01:00
Mihajlo Medjedovic
f10ed3236e test: fixing 2021-02-22 13:46:31 +01:00
Mihajlo Medjedovic
a852a0af7c chore: typo fix 2021-02-22 11:53:16 +01:00
Mihajlo Medjedovic
03f3550774 docs: generated 2021-02-22 11:40:08 +01:00
Mihajlo Medjedovic
2e8d06f9e1 Merge branch 'master' into issue-186 2021-02-22 11:39:44 +01:00
Krishna Acondy
1d31ec4dcc Merge pull request #218 from sasjs/axios-fetch
fix(*): replace native fetch calls with axios
2021-02-15 12:50:13 +00:00
Krishna Acondy
4407ed68ae Merge branch 'master' of https://github.com/sasjs/adapter into axios-fetch 2021-02-15 12:46:11 +00:00
Krishna Acondy
b3359f2138 Merge pull request #241 from sasjs/dependabot/npm_and_yarn/webpack-5.21.2
chore(deps-dev): bump webpack from 5.13.0 to 5.21.2
2021-02-15 08:43:03 +00:00
dependabot-preview[bot]
cff9104adf chore(deps-dev): bump webpack from 5.13.0 to 5.21.2
Bumps [webpack](https://github.com/webpack/webpack) from 5.13.0 to 5.21.2.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.13.0...v5.21.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2021-02-15 08:41:19 +00:00
Krishna Acondy
b73f821c19 Merge pull request #250 from sasjs/dependabot/npm_and_yarn/sasjs/utils-2.5.0
chore(deps): bump @sasjs/utils from 2.0.2 to 2.5.0
2021-02-15 08:39:09 +00:00
dependabot-preview[bot]
dd3c1a7375 chore(deps): bump @sasjs/utils from 2.0.2 to 2.5.0
Bumps [@sasjs/utils](https://github.com/sasjs/utils) from 2.0.2 to 2.5.0.
- [Release notes](https://github.com/sasjs/utils/releases)
- [Commits](https://github.com/sasjs/utils/compare/v2.0.2...v2.5.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2021-02-15 08:37:17 +00:00
Krishna Acondy
c10b5368af Merge pull request #251 from sasjs/dependabot/npm_and_yarn/semantic-release-17.3.9
chore(deps-dev): bump semantic-release from 17.3.1 to 17.3.9
2021-02-15 08:35:10 +00:00
dependabot-preview[bot]
2df66765c0 chore(deps-dev): bump semantic-release from 17.3.1 to 17.3.9
Bumps [semantic-release](https://github.com/semantic-release/semantic-release) from 17.3.1 to 17.3.9.
- [Release notes](https://github.com/semantic-release/semantic-release/releases)
- [Commits](https://github.com/semantic-release/semantic-release/compare/v17.3.1...v17.3.9)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2021-02-15 08:29:58 +00:00
Krishna Acondy
ed65545571 Merge pull request #248 from sasjs/dependabot/npm_and_yarn/ts-loader-8.0.17
chore(deps-dev): bump ts-loader from 8.0.14 to 8.0.17
2021-02-15 08:27:46 +00:00
dependabot-preview[bot]
b4ae486520 chore(deps-dev): bump ts-loader from 8.0.14 to 8.0.17
Bumps [ts-loader](https://github.com/TypeStrong/ts-loader) from 8.0.14 to 8.0.17.
- [Release notes](https://github.com/TypeStrong/ts-loader/releases)
- [Changelog](https://github.com/TypeStrong/ts-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/TypeStrong/ts-loader/compare/v8.0.14...v8.0.17)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2021-02-15 08:23:51 +00:00
Krishna Acondy
bb7b3d0b84 Merge pull request #249 from sasjs/dependabot/npm_and_yarn/typescript-3.9.9
chore(deps-dev): bump typescript from 3.9.7 to 3.9.9
2021-02-15 08:21:44 +00:00
dependabot-preview[bot]
65b34aa015 chore(deps-dev): bump typescript from 3.9.7 to 3.9.9
Bumps [typescript](https://github.com/Microsoft/TypeScript) from 3.9.7 to 3.9.9.
- [Release notes](https://github.com/Microsoft/TypeScript/releases)
- [Commits](https://github.com/Microsoft/TypeScript/compare/v3.9.7...v3.9.9)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2021-02-15 08:12:05 +00:00
Krishna Acondy
b3c9303def Merge pull request #235 from sasjs/dependabot/npm_and_yarn/webpack-cli-4.5.0
chore(deps-dev): bump webpack-cli from 4.3.1 to 4.5.0
2021-02-15 08:09:42 +00:00
dependabot-preview[bot]
19dfda9b6a chore(deps-dev): bump webpack-cli from 4.3.1 to 4.5.0
Bumps [webpack-cli](https://github.com/webpack/webpack-cli) from 4.3.1 to 4.5.0.
- [Release notes](https://github.com/webpack/webpack-cli/releases)
- [Changelog](https://github.com/webpack/webpack-cli/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack/webpack-cli/compare/webpack-cli@4.3.1...webpack-cli@4.5.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2021-02-15 08:01:11 +00:00
Krishna Acondy
ce2abb448f Merge pull request #217 from sasjs/dependabot/npm_and_yarn/typedoc-neo-theme-1.1.0
chore(deps-dev): bump typedoc-neo-theme from 1.0.10 to 1.1.0
2021-02-15 07:59:06 +00:00
Krishna Acondy
413c3b8098 Merge branch 'axios-fetch' of https://github.com/sasjs/adapter into axios-fetch 2021-02-10 08:19:51 +00:00
Krishna Acondy
269514a44f fix(*): expose CSRF token, add getFolder API 2021-02-10 08:19:48 +00:00
Saad Jutt
5fc334dd8b chore: param 'insecure' removed 2021-02-09 17:57:01 +05:00
Krishna Acondy
1b251f1cea fix(auth): auto submit auth form if needed 2021-02-08 19:07:37 +00:00
Krishna Acondy
70d3e25c7f Merge branch 'axios-fetch' of https://github.com/sasjs/adapter into axios-fetch 2021-02-08 08:31:55 +00:00
Krishna Acondy
5e9b33e346 chore(*): fix sasjs tests 2021-02-08 08:31:42 +00:00
Krishna Acondy
eb52ec7532 Merge branch 'master' into axios-fetch 2021-02-06 13:23:30 +00:00
Krishna Acondy
f46c4bf3ca Merge branch 'axios-fetch' of https://github.com/sasjs/adapter into axios-fetch 2021-02-06 13:00:31 +00:00
Krishna Acondy
d8176912cf fix(requests): only allow insecure requests if https module is available 2021-02-06 13:00:27 +00:00
dependabot-preview[bot]
4c54ade2d3 chore(deps-dev): bump typedoc-neo-theme from 1.0.10 to 1.1.0
Bumps [typedoc-neo-theme](https://github.com/google/typedoc-neo-theme) from 1.0.10 to 1.1.0.
- [Release notes](https://github.com/google/typedoc-neo-theme/releases)
- [Commits](https://github.com/google/typedoc-neo-theme/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2021-02-06 06:54:47 +00:00
Yury Shkoda
52c41dfb3a Merge pull request #196 from sasjs/releaseScript
Release script
2021-02-06 09:52:44 +03:00
Yury Shkoda
36f0aa7411 Merge branch 'master' into releaseScript 2021-02-06 09:31:21 +03:00
Saad Jutt
a10e4ec264 fix(*): fix invalid typing 2021-02-05 19:03:20 +05:00
Krishna Acondy
851b6bc273 chore(*): change import to require 2021-02-05 12:21:33 +00:00
Krishna Acondy
23f8d31b1b fix(*): support multipart form data post 2021-02-05 11:10:57 +00:00
Krishna Acondy
a5683bcd07 fix(insecure-requests): add flag to config 2021-02-05 09:33:42 +00:00
Krishna Acondy
c7be71c781 fix(*): add the ability to ignore SSL errors 2021-02-05 08:56:40 +00:00
Krishna Acondy
594f274323 chore(*): bump dependencies for sasjs-tests 2021-02-05 08:24:13 +00:00
Krishna Acondy
ba1ed5e732 fix(job-execution): throw error if job name is empty 2021-02-05 08:23:25 +00:00
Krishna Acondy
613cc6b9ef chore(*): clean up, handle debug responses 2021-02-04 09:42:39 +00:00
Krishna Acondy
8edb00f869 fix(request-client): handle SAS9 error scenarios, clear CSRF tokens on log out 2021-02-03 09:34:34 +00:00
Krishna Acondy
60a1f84604 chore(*): clean up, address review comments 2021-02-02 08:42:01 +00:00
Krishna Acondy
36cfaee5db chore(*): fix package.json format 2021-02-02 08:21:25 +00:00
Krishna Acondy
cb607c93ca Merge branch 'master' into axios-fetch 2021-02-02 08:19:53 +00:00
Krishna Acondy
03b5e1d824 chore(*): refactor common functionality into JobExecutor, handle all auth-related scenarios 2021-02-02 08:18:48 +00:00
Krishna Acondy
2ea49a425f fix(*): post request bodies as JSON 2021-01-28 20:36:41 +00:00
Krishna Acondy
3c894c4147 fix(*): handled 404s, set correct accept headers 2021-01-28 19:25:23 +00:00
Krishna Acondy
23d151c919 chore(*): commit missed file 2021-01-28 17:02:19 +00:00
Krishna Acondy
6d1c4ff81a fix(*): fix compute job execution 2021-01-28 17:01:59 +00:00
Mihajlo Medjedovic
7a6e6e8333 chore: fix const 2021-01-28 11:25:21 +01:00
Krishna Acondy
0eba6bdcf4 chore(*): fix tests 2021-01-27 22:03:40 +00:00
Krishna Acondy
d7ecaf5932 fix(*): separate job execution code from main SASjs class 2021-01-27 20:30:13 +00:00
Krishna Acondy
e0d85f458b fix(*): store CSRF tokens in Request Client 2021-01-24 18:23:18 +00:00
Krishna Acondy
3a9cd46e6e chore(*): use server type from utils types 2021-01-24 16:02:34 +00:00
Krishna Acondy
301edab8ad chore(*): refactor and add tests 2021-01-24 15:08:35 +00:00
Krishna Acondy
aed39c2ec4 chore(*): refactor, move all auth-related code to auth folder 2021-01-24 13:07:15 +00:00
Yury Shkoda
cc594eca52 chore(SASViyaApiClient): fixed typo 2021-01-23 09:45:42 +03:00
Mihajlo Medjedovic
3fbca2835e chore: test fix 2021-01-22 15:45:44 +01:00
Mihajlo Medjedovic
61ed5c4fa7 Merge branch 'master' into issue-186 2021-01-22 14:46:46 +01:00
Krishna Acondy
e31774ae9d fix(*): fix login issue 2021-01-19 09:29:29 +00:00
Krishna Acondy
00f09179a8 chore(*): remove unnecessary imports 2021-01-19 09:07:40 +00:00
Krishna Acondy
4196901e01 chore(*): remove comment 2021-01-19 08:17:42 +00:00
Krishna Acondy
bf35dd072a chore(*): fix failing tests 2021-01-19 08:16:08 +00:00
Krishna Acondy
75e3fd018d chore(*): replace fetch calls with axios 2021-01-18 09:22:10 +00:00
Krishna Acondy
965dfff7c6 chore(*): use axios instead of fetch 2021-01-18 09:07:36 +00:00
Krishna Acondy
ff64dd22ad chore(*): use local axios instance 2021-01-18 09:01:07 +00:00
Krishna Acondy
e7cceab065 chore(*): add axios 2021-01-18 09:00:16 +00:00
Krishna Acondy
f789b8f7a2 fix(*): extracted auth logic into separate class, used axios instead of fetch 2021-01-18 08:59:58 +00:00
Mihajlo Medjedovic
9b6ba3548f test: added tests for folder move function 2021-01-11 16:35:23 +01:00
Mihajlo Medjedovic
efa4c71b8a style: lint 2021-01-06 16:33:47 +01:00
Mihajlo Medjedovic
ecfc1a4bf8 fix: move function, test: added test for folder operations 2021-01-06 16:32:57 +01:00
Mihajlo Medjedovic
246b855c76 Merge branch 'master' into issue-186 2021-01-06 13:51:10 +01:00
Mihajlo Medjedovic
80aa848617 style: lint 2021-01-05 16:21:46 +01:00
Mihajlo Medjedovic
acf045965e fix: move command improved 2021-01-05 16:21:18 +01:00
Mihajlo Medjedovic
5a7f64dc35 docs: generate 2021-01-04 13:37:14 +01:00
Mihajlo Medjedovic
de447b0727 Merge branch 'master' into issue-186 2021-01-04 13:36:44 +01:00
Mihajlo Medjedovic
a2906abf71 Merge branch 'master' into releaseScript 2020-12-30 13:40:22 +01:00
Mihajlo Medjedovic
ace16efd93 docs: generated 2020-12-23 14:23:44 +01:00
Mihajlo Medjedovic
13ae5ae756 Merge branch 'master' into issue-186 2020-12-23 14:23:26 +01:00
Mihajlo Medjedovic
f1b035032f feat: listFolder and improvements 2020-12-22 19:24:33 +01:00
Mihajlo Medjedovic
1555afe771 ci: slack webhook secret update 2020-11-26 10:06:47 +01:00
Mihajlo Medjedovic
a2832f1e1a ci: slack webhook secret 2020-11-25 17:09:05 +01:00
Mihajlo Medjedovic
56f34508fa ci: after release ci will send slack message 2020-11-25 16:40:04 +01:00
133 changed files with 30260 additions and 9852 deletions

View File

@@ -25,3 +25,5 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Send Slack message
run: curl -X POST --data-urlencode "payload={\"channel\":\"#sasjs\", \"username\":\"GitHub CI\", \"text\":\"New version of @sasjs/adapter has been released! \n Please deploy and run `dctests` with new adapter to make sure everything is still in place.\", \"icon_emoji\":\":rocket:\"}" ${{ secrets.SLACK_WEBHOOK }}

4
.gitignore vendored
View File

@@ -3,4 +3,6 @@ build
.env
/coverage
/coverage
.DS_Store

161
README.md
View File

@@ -6,7 +6,7 @@ SASjs is a open-source framework for building Web Apps on SAS® platforms. You c
1 - `npm install @sasjs/adapter` - for use in a node project
2 - [Download](https://cdn.jsdelivr.net/npm/@sasjs/adapter@1/index.js) and use a copy of the latest JS file
2 - [Download](https://cdn.jsdelivr.net/npm/@sasjs/adapter@2/index.js) and use a copy of the latest JS file
3 - Reference directly from the CDN - in which case click [here](https://www.jsdelivr.com/package/npm/@sasjs/adapter?tab=collection) and select "SRI" to get the script tag with the integrity hash.
@@ -41,8 +41,165 @@ parmcards4;
You now have a simple web app with a backend service!
## Detailed Overview
The SASjs adapter is a JS library and a set of SAS Macros that handle the communication between the frontend app and backend SAS services.
There are three parts to consider:
1. JS request / response
2. SAS inputs / outputs
3. Configuration
### JS Request / Response
To install the library you can simply run `npm i @sasjs/adapter` or include a `<script>` tag with a reference to our [CDN](https://www.jsdelivr.com/package/npm/@sasjs/adapter).
Full technical documentation is available [here](https://adapter.sasjs.io). The main parts are:
### Instantiation
The following code will instantiate an instance of the adapter:
```javascript
let sasJs = new SASjs.default(
{
appLoc: "/Your/SAS/Folder",
serverType:"SAS9"
}
);
```
If you've installed it via NPM, you can import it as a default import like so:
```
import SASjs from '@sasjs/adapter';
```
You can then instantiate it with:
```
const sasJs = new SASjs({your config})
```
More on the config later.
### SAS Logon
The login process can be handled directly, as below, or as a callback function to a SAS request.
```javascript
sasJs.logIn('USERNAME','PASSWORD'
).then((response) => {
if (response.isLoggedIn === true) {
console.log('do stuff')
} else {
console.log('do other stuff')
}
}
```
### Request / Response
A simple request can be sent to SAS in the following fashion:
```javascript
sasJs.request("/path/to/my/service", dataObject)
.then((response) => {
// all tables are in the response object, eg:
console.log(response.tablewith2cols1row[0].COL1.value)
})
```
We supply the path to the SAS service, and a data object. The data object can be null (for services with no input), or can contain one or more tables in the following format:
```javascript
let dataObject={
"tablewith2cols1row": [{
"col1": "val1",
"col2": 42
}],
"tablewith1col2rows": [{
"col": "row1"
}, {
"col": "row2"
}]
};
```
There are optional parameters such as a config object and a callback login function.
The response object will contain returned tables and columns. Table names are always lowercase, and column names uppercase.
The adapter will also cache the logs (if debug enabled) and even the work tables. For performance, it is best to keep debug mode off.
## SAS Inputs / Outputs
The SAS side is handled by a number of macros in the [macro core](https://github.com/sasjs/core) library.
The following snippet shows the process of SAS tables arriving / leaving:
```sas
/* fetch all input tables sent from frontend - they arrive as work tables */
%webout(FETCH)
/* some sas code */
data some sas tables;
set from js;
run;
%webout(OPEN) /* open the JSON to be returned */
%webout(OBJ,some) /* `some` table is sent in object format */
%webout(ARR,sas) /* `sas` table is sent in array format, smaller filesize */
%webout(OBJ,tables,fmt=N) /* unformatted (raw) data */
%webout(OBJ,tables,label=newtable) /* rename tables on export */
%webout(CLOSE) /* close the JSON and send some extra useful variables too */
```
## Configuration
Configuration on the client side involves passing an object on startup, which can also be passed with each request. Technical documentation on the SASjsConfig class is available [here](https://adapter.sasjs.io/classes/types.sasjsconfig.html). The main config items are:
* `appLoc` - this is the folder under which the SAS services will be created.
* `serverType` - either `SAS9` or `SASVIYA`.
* `serverUrl` - the location (including http protocol and port) of the SAS Server. Can be omitted, eg if serving directly from the SAS Web Server, or in streaming mode.
* `debug` - if `true` then SAS Logs and extra debug information is returned.
* `useComputeApi` - if `true` and the serverType is `SASVIYA` then the REST APIs will be called directly (rather than using the JES web service).
* `contextName` - if missing or blank, and `useComputeApi` is `true` and `serverType` is `SASVIYA` then the JES API will be used.
The adapter supports a number of approaches for interfacing with Viya (`serverType` is `SASVIYA`). For maximum performance, be sure to [configure your compute context](https://sasjs.io/guide-viya/#shared-account-and-server-re-use) with `reuseServerProcesses` as `true` and a system account in `runServerAs`. This functionality is available since Viya 3.5. This configuration is supported when [creating contexts using the CLI](https://sasjs.io/sasjs-cli-context/#sasjs-context-create).
### Using JES Web App
In this setup, all requests are routed through the JES web app, at `YOURSERVER/SASJobExecution`. This is the most reliable method, and also the slowest. One request is made to the JES app, and remaining requests (getting job uri, session spawning, passing parameters, running the program, fetching the log) are made on the SAS server by the JES app.
```
{
appLoc:"/Your/Path",
serverType:"SASVIYA"
}
```
### Using the JES API
Here we are running Jobs using the Job Execution Service except this time we are making the requests directly using the REST API instead of through the JES Web App. This is helpful when we need to call web services outside of a browser (eg with the SASjs CLI or other commandline tools). To save one network request, the adapter prefetches the JOB URIs and passes them in the `__job` parameter.
```
{
appLoc:"/Your/Path",
serverType:"SASVIYA",
useComputeApi: true
}
```
### Using the Compute API
This approach is by far the fastest, as a result of the optimisations we have built into the adapter. With this configuration, in the first sasjs request, we take a URI map of the services in the target folder, and create a session manager - which spawns an extra session. The next time a request is made, the adapter will use the 'hot' session. Sessions are deleted after every use, which actually makes this _less_ resource intensive than a typical JES web app, in which all sessions are kept alive by default for 15 minutes.
```
{
appLoc:"/Your/Path",
serverType:"SASVIYA",
useComputeApi: true,
contextName: 'yourComputeContext'
}
```
# More resources
For more information and examples specific to this adapter you can check out the [user guide](https://sasjs.io/sasjs-adapter/) or the [technical](http://adapter.sasjs.io/) documentation.
For more information and examples specific to this adapter you can check out the [user guide](https://sasjs.io/sasjs-adapter/) or the [technical](http://adapter.sasjs.io/) documentation.
For more information on building web apps in general, check out these [resources](https://sasjs.io/training/resources/) or contact the [author](https://www.linkedin.com/in/allanbowe/) directly.
If you are a SAS 9 or SAS Viya customer you can also request a copy of [Data Controller](https://datacontroller.io) - free for up to 5 users, this tool makes use of all parts of the SASjs framework.

View File

@@ -1,13 +0,0 @@
{
"defaultCommandTimeout": 10000,
"chromeWebSecurity": false,
"screenshotOnRunFailure": false,
"env": {
"serverUrl": "",
"appLoc": "/Public/app",
"serverType": "SAS9",
"debug": false,
"username": "",
"password": ""
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

179
docs/modules/auth.html Normal file

File diff suppressed because one or more lines are too long

144
docs/modules/auth_spec.html Normal file

File diff suppressed because one or more lines are too long

184
docs/modules/file.html Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

118
docs/modules/request.html Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,10 +1,184 @@
// For a detailed explanation regarding each configuration property, visit:
// https://jestjs.io/docs/en/configuration.html
module.exports = {
roots: ["<rootDir>/src"],
testMatch: [
"**/__tests__/**/*.+(ts|tsx|js)",
"**/?(*.)+(spec|test).+(ts|tsx|js)"
],
testTimeout: 90000,
// All imported modules in your tests should be mocked automatically
// automock: false,
// Stop running tests after `n` failures
// bail: 1,
// Respect "browser" field in package.json when resolving modules
// browser: false,
// The directory where Jest should store its cached dependency information
// cacheDirectory: "/private/var/folders/7y/nmqg1srj29q6210rs9dfsdzc0000gn/T/jest_dx",
// Automatically clear mock calls and instances between every test
clearMocks: true,
// Indicates whether the coverage information should be collected while executing the test
// collectCoverage: false,
// An array of glob patterns indicating a set of files for which coverage information should be collected
// collectCoverageFrom: undefined,
// The directory where Jest should output its coverage files
coverageDirectory: 'coverage',
// An array of regexp pattern strings used to skip coverage collection
// coveragePathIgnorePatterns: [
// "/node_modules/"
// ],
// A list of reporter names that Jest uses when writing coverage reports
// coverageReporters: [
// "json",
// "text",
// "lcov",
// "clover"
// ],
// An object that configures minimum threshold enforcement for coverage results
// coverageThreshold: undefined,
// A path to a custom dependency extractor
// dependencyExtractor: undefined,
// Make calling deprecated APIs throw helpful error messages
// errorOnDeprecated: false,
// Force coverage collection from ignored files using an array of glob patterns
// forceCoverageMatch: [],
// A path to a module which exports an async function that is triggered once before all test suites
// globalSetup: undefined,
// A path to a module which exports an async function that is triggered once after all test suites
// globalTeardown: undefined,
// A set of global variables that need to be available in all test environments
// globals: {},
// The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
// maxWorkers: "50%",
// An array of directory names to be searched recursively up from the requiring module's location
// moduleDirectories: [
// "node_modules"
// ],
// An array of file extensions your modules use
// moduleFileExtensions: [
// "js",
// "json",
// "jsx",
// "ts",
// "tsx",
// "node"
// ],
// A map from regular expressions to module names that allow to stub out resources with a single module
moduleNameMapper: {},
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
// modulePathIgnorePatterns: [],
// Activates notifications for test results
// notify: false,
// An enum that specifies notification mode. Requires { notify: true }
// notifyMode: "failure-change",
// A preset that is used as a base for Jest's configuration
preset: 'ts-jest/presets/js-with-ts',
// Run tests from one or more projects
// projects: undefined,
// Use this configuration option to add custom reporters to Jest
// reporters: undefined,
// Automatically reset mock state between every test
// resetMocks: false,
// Reset the module registry before running each individual test
// resetModules: false,
// A path to a custom resolver
// resolver: undefined,
// Automatically restore mock state between every test
// restoreMocks: false,
// The root directory that Jest should scan for tests and modules within
// rootDir: undefined,
// A list of paths to directories that Jest should use to search for files in
// roots: [
// "<rootDir>"
// ],
// Allows you to use a custom runner instead of Jest's default test runner
// runner: "jest-runner",
// The paths to modules that run some code to configure or set up the testing environment before each test
setupFiles: [],
// A list of paths to modules that run some code to configure or set up the testing framework before each test
setupFilesAfterEnv: ['jest-extended'],
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
// snapshotSerializers: [],
// Options that will be passed to the testEnvironment
// testEnvironmentOptions: {},
// Adds a location field to test results
// testLocationInResults: false,
// The glob patterns Jest uses to detect test files
testMatch: ['**/*spec.[j|t]s?(x)'],
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
testPathIgnorePatterns: ['/node_modules/', '/build'],
// The regexp pattern or array of patterns that Jest uses to detect test files
// testRegex: [],
// This option allows the use of a custom results processor
// testResultsProcessor: undefined,
// This option allows use of a custom test runner
// testRunner: "jasmine2",
// This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
// testURL: "http://localhost",
// Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout"
// timers: "real",
// A map from regular expressions to paths to transformers
transform: {
"^.+\\.(ts|tsx)$": "ts-jest"
'^.+\\.ts?$': 'ts-jest'
}
};
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
// transformIgnorePatterns: [
// '**/test/**/*.ts?(x)',
// '**/?(*.)+(spec|test).ts?(x)'
// ]
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
// unmockedModulePathPatterns: undefined,
// Indicates whether each individual test should be reported during the run
// verbose: undefined,
// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
// watchPathIgnorePatterns: [],
// Whether to use watchman for file crawling
// watchman: true,
}

3416
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@
"publish:lib": "npm run build && cd build && npm publish",
"lint:fix": "npx prettier --write 'src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}'",
"lint": "npx prettier --check 'src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}'",
"test": "jest --coverage",
"test": "jest --silent --coverage",
"prepublishOnly": "cp -r ./build/* . && rm -rf ./build",
"postpublish": "git clean -fd",
"semantic-release": "semantic-release",
@@ -36,32 +36,31 @@
},
"license": "ISC",
"devDependencies": {
"@types/isomorphic-fetch": "0.0.35",
"@types/jest": "^26.0.20",
"cp": "^0.2.0",
"dotenv": "^8.2.0",
"jest": "^25.5.4",
"jest": "^26.6.3",
"jest-extended": "^0.11.5",
"path": "^0.12.7",
"rimraf": "^3.0.2",
"semantic-release": "^17.3.1",
"semantic-release": "^17.4.1",
"terser-webpack-plugin": "^4.2.3",
"ts-jest": "^25.5.1",
"ts-loader": "^8.0.14",
"ts-loader": "^8.0.17",
"tslint": "^6.1.3",
"tslint-config-prettier": "^1.18.0",
"typedoc": "^0.19.2",
"typedoc-neo-theme": "^1.0.10",
"typedoc": "^0.20.30",
"typedoc-neo-theme": "^1.1.0",
"typedoc-plugin-external-module-name": "^4.0.6",
"typescript": "^3.9.7",
"webpack": "^5.13.0",
"webpack-cli": "^4.3.1"
"typescript": "^3.9.9",
"webpack": "^5.24.4",
"webpack-cli": "^4.5.0"
},
"main": "index.js",
"dependencies": {
"@sasjs/utils": "^2.0.2",
"es6-promise": "^4.2.8",
"@sasjs/utils": "^2.6.3",
"axios": "^0.21.1",
"form-data": "^3.0.0",
"https": "^1.0.0",
"isomorphic-fetch": "^2.2.1"
"https": "^1.0.0"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -4,21 +4,18 @@
"homepage": ".",
"private": true,
"dependencies": {
"@sasjs/adapter": "^1.18.2",
"@sasjs/adapter": "^2.2.4",
"@sasjs/test-framework": "^1.4.0",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.5.0",
"@testing-library/user-event": "^7.2.1",
"@types/jest": "^26.0.3",
"@types/node": "^14.0.14",
"@types/react": "^16.9.41",
"@types/react-dom": "^16.9.8",
"@types/react-router-dom": "^5.1.5",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"@types/jest": "^26.0.20",
"@types/node": "^14.14.25",
"@types/react": "^17.0.1",
"@types/react-dom": "^17.0.0",
"@types/react-router-dom": "^5.1.7",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-router-dom": "^5.2.0",
"react-scripts": "3.4.1",
"typescript": "^3.9.6"
"react-scripts": "^4.0.2",
"typescript": "^4.1.3"
},
"scripts": {
"start": "react-scripts start",
@@ -45,6 +42,6 @@
]
},
"devDependencies": {
"node-sass": "^4.14.1"
"node-sass": "^5.0.0"
}
}

View File

@@ -6,7 +6,7 @@
"appLoc": "/Public/app",
"serverType": "SASVIYA",
"debug": false,
"contextName": "SharedCompute",
"contextName": "sasjs adapter compute context",
"useComputeApi": true
}
}

View File

@@ -1,15 +1,19 @@
import SASjs, { ServerType, SASjsConfig } from "@sasjs/adapter";
import SASjs, { SASjsConfig } from "@sasjs/adapter";
import { TestSuite } from "@sasjs/test-framework";
import { ServerType } from "@sasjs/utils/types";
const stringData: any = { table1: [{ col1: "first col value" }] };
const defaultConfig: SASjsConfig = {
serverUrl: window.location.origin,
pathSAS9: '/SASStoredProcess/do',
pathSASViya: '/SASJobExecution',
appLoc: '/Public/seedapp',
serverType: ServerType.SASViya,
pathSAS9: "/SASStoredProcess/do",
pathSASViya: "/SASJobExecution",
appLoc: "/Public/seedapp",
serverType: ServerType.SasViya,
debug: false,
contextName: 'SAS Job Execution compute context',
useComputeApi: false
contextName: "SAS Job Execution compute context",
useComputeApi: false,
allowInsecureRequests: false
};
const customConfig = {
@@ -17,7 +21,7 @@ const customConfig = {
pathSAS9: "sas9",
pathSASViya: "viya",
appLoc: "/Public/seedapp",
serverType: ServerType.SAS9,
serverType: ServerType.Sas9,
debug: false
};
@@ -39,15 +43,46 @@ export const basicTests = (
},
{
title: "Multiple Log in attempts",
description: "Should fail on first attempt and should log the user in on second attempt",
description:
"Should fail on first attempt and should log the user in on second attempt",
test: async () => {
await adapter.logOut()
await adapter.logIn('invalid', 'invalid')
return adapter.logIn(userName, password)
await adapter.logOut();
await adapter.logIn("invalid", "invalid");
return adapter.logIn(userName, password);
},
assertion: (response: any) =>
response && response.isLoggedIn && response.userName === userName
},
{
title: "Trigger login callback",
description:
"Should trigger required login callback and after successful login, it should finish the request",
test: async () => {
await adapter.logOut();
return await adapter.request("common/sendArr", stringData, null, () => {
adapter.logIn(userName, password);
});
},
assertion: (response: any) => {
return response.table1[0][0] === stringData.table1[0].col1;
}
},
{
title: "Request with debug on",
description:
"Should complete successful request with debugging switched on",
test: async () => {
const config = {
debug: true
}
return await adapter.request("common/sendArr", stringData, config)
},
assertion: (response: any) => {
return response.table1[0][0] === stringData.table1[0].col1;
}
},
{
title: "Default config",
description:

View File

@@ -12,7 +12,7 @@ export const computeTests = (adapter: SASjs): TestSuite => ({
return adapter.startComputeJob("/Public/app/common/sendArr", data);
},
assertion: (res: any) => {
const expectedProperties = ["id", "applicationName", "attributes"]
const expectedProperties = ["id", "applicationName", "attributes"];
return validate(expectedProperties, res);
}
},
@@ -21,11 +21,22 @@ export const computeTests = (adapter: SASjs): TestSuite => ({
description: "Should start a compute job and return the job",
test: () => {
const data: any = { table1: [{ col1: "first col value" }] };
return adapter.startComputeJob("/Public/app/common/sendArr", data, {}, "", true);
return adapter.startComputeJob(
"/Public/app/common/sendArr",
data,
{},
"",
true
);
},
assertion: (res: any) => {
const expectedProperties = ["id", "state", "creationTimeStamp", "jobConditionCode"]
return validate(expectedProperties, res.result);
const expectedProperties = [
"id",
"state",
"creationTimeStamp",
"jobConditionCode"
];
return validate(expectedProperties, res.job);
}
},
{
@@ -38,19 +49,19 @@ export const computeTests = (adapter: SASjs): TestSuite => ({
`output;`,
`end;`,
`run;`
]
];
return adapter.executeScriptSASViya(
'sasCode.sas',
"sasCode.sas",
fileLines,
'SAS Studio compute context',
"SAS Studio compute context",
undefined,
true
)
);
},
assertion: (res: any) => {
const expectedLogContent = `1 data;\\n2 do x=1 to 100;\\n3 output;\\n4 end;\\n5 run;\\n\\n`
const expectedLogContent = `1 data;\\n2 do x=1 to 100;\\n3 output;\\n4 end;\\n5 run;\\n\\n`;
return validateLog(expectedLogContent, res.log);
}
},
@@ -58,21 +69,21 @@ export const computeTests = (adapter: SASjs): TestSuite => ({
title: "Execute Script Viya - failed job",
description: "Should execute sas file and return log",
test: () => {
const fileLines = [
`%abort;`
]
return adapter.executeScriptSASViya(
'sasCode.sas',
fileLines,
'SAS Studio compute context',
undefined,
true
).catch((err: any) => err )
const fileLines = [`%abort;`];
return adapter
.executeScriptSASViya(
"sasCode.sas",
fileLines,
"SAS Studio compute context",
undefined,
true
)
.catch((err: any) => err);
},
assertion: (res: any) => {
const expectedLogContent = `1 %abort;\\nERROR: The %ABORT statement is not valid in open code.\\n`
const expectedLogContent = `1 %abort;\\nERROR: The %ABORT statement is not valid in open code.\\n`;
return validateLog(expectedLogContent, res.log);
}
}
@@ -80,16 +91,16 @@ export const computeTests = (adapter: SASjs): TestSuite => ({
});
const validateLog = (text: string, log: string): boolean => {
const isValid = JSON.stringify(log).includes(text)
const isValid = JSON.stringify(log).includes(text);
return isValid
}
return isValid;
};
const validate = (expectedProperties: string[], data: any): boolean => {
const actualProperties = Object.keys(data);
const isValid = expectedProperties.every(
(property) => actualProperties.includes(property)
const isValid = expectedProperties.every((property) =>
actualProperties.includes(property)
);
return isValid
}
return isValid;
};

View File

@@ -185,7 +185,8 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
};
return adapter.request("common/sendObj", invalidData).catch((e) => e);
},
assertion: (error: any) => !!error && !!error.error && !!error.error.message
assertion: (error: any) =>
!!error && !!error.error && !!error.error.message
},
{
title: "Single string value",

View File

@@ -23,26 +23,23 @@ export const sasjsRequestTests = (adapter: SASjs): TestSuite => ({
},
{
title: "Make error and capture log",
description: "Should make an error and capture log, in the same time it is testing if debug override is working",
description:
"Should make an error and capture log, in the same time it is testing if debug override is working",
test: async () => {
return new Promise(async (resolve, reject) => {
adapter
.request("common/makeErr", data, {debug: true})
.then((res) => {
//no action here, this request must throw error
})
.catch((err) => {
let sasRequests = adapter.getSasRequests();
let makeErrRequest: any =
sasRequests.find((req) =>
req.serviceLink.includes("makeErr")
) || null;
return adapter
.request("common/makeErr", data, { debug: true })
.catch(() => {
const sasRequests = adapter.getSasRequests();
const makeErrRequest: any =
sasRequests.find((req) => req.serviceLink.includes("makeErr")) ||
null;
if (!makeErrRequest) resolve(false)
if (!makeErrRequest) return false;
resolve(!!(makeErrRequest.logFile && makeErrRequest.logFile.length > 0));
});
});
return !!(
makeErrRequest.logFile && makeErrRequest.logFile.length > 0
);
});
},
assertion: (response) => {
return response;

View File

@@ -17,7 +17,8 @@
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react"
"jsx": "react-jsx",
"noFallthroughCasesInSwitch": true
},
"include": [
"src"

View File

@@ -1,12 +1,7 @@
import {
Context,
CsrfToken,
EditContextInput,
ContextAllAttributes
} from './types'
import { makeRequest, isUrl } from './utils'
import { SASViyaApiClient } from './SASViyaApiClient'
import { Context, EditContextInput, ContextAllAttributes } from './types'
import { isUrl } from './utils'
import { prefixMessage } from '@sasjs/utils/error'
import { RequestClient } from './request/RequestClient'
export class ContextManager {
private defaultComputeContexts = [
@@ -29,8 +24,6 @@ export class ContextManager {
'SAS Visual Forecasting launcher context'
]
private csrfToken: CsrfToken | null = null
get getDefaultComputeContexts() {
return this.defaultComputeContexts
}
@@ -38,28 +31,19 @@ export class ContextManager {
return this.defaultLauncherContexts
}
constructor(
private serverUrl: string,
private setCsrfToken: (csrfToken: CsrfToken) => void
) {
constructor(private serverUrl: string, private requestClient: RequestClient) {
if (serverUrl) isUrl(serverUrl)
}
public async getComputeContexts(accessToken?: string) {
const headers: any = {
'Content-Type': 'application/json'
}
if (accessToken) {
headers.Authorization = `Bearer ${accessToken}`
}
const { result: contexts } = await this.request<{ items: Context[] }>(
`${this.serverUrl}/compute/contexts?limit=10000`,
{ headers }
).catch((err) => {
throw prefixMessage(err, 'Error while getting compute contexts. ')
})
const { result: contexts } = await this.requestClient
.get<{ items: Context[] }>(
`${this.serverUrl}/compute/contexts?limit=10000`,
accessToken
)
.catch((err) => {
throw prefixMessage(err, 'Error while getting compute contexts. ')
})
const contextsList = contexts && contexts.items ? contexts.items : []
@@ -73,20 +57,14 @@ export class ContextManager {
}
public async getLauncherContexts(accessToken?: string) {
const headers: any = {
'Content-Type': 'application/json'
}
if (accessToken) {
headers.Authorization = `Bearer ${accessToken}`
}
const { result: contexts } = await this.request<{ items: Context[] }>(
`${this.serverUrl}/launcher/contexts?limit=10000`,
{ headers }
).catch((err) => {
throw prefixMessage(err, 'Error while getting launcher contexts. ')
})
const { result: contexts } = await this.requestClient
.get<{ items: Context[] }>(
`${this.serverUrl}/launcher/contexts?limit=10000`,
accessToken
)
.catch((err) => {
throw prefixMessage(err, 'Error while getting launcher contexts. ')
})
const contextsList = contexts && contexts.items ? contexts.items : []
@@ -184,18 +162,15 @@ export class ContextManager {
requestBody.environment = { autoExecLines }
}
const createContextRequest: RequestInit = {
method: 'POST',
headers,
body: JSON.stringify(requestBody)
}
const { result: context } = await this.request<Context>(
`${this.serverUrl}/compute/contexts`,
createContextRequest
).catch((err) => {
throw prefixMessage(err, 'Error while creating compute context. ')
})
const { result: context } = await this.requestClient
.post<Context>(
`${this.serverUrl}/compute/contexts`,
requestBody,
accessToken
)
.catch((err) => {
throw prefixMessage(err, 'Error while creating compute context. ')
})
return context
}
@@ -238,18 +213,15 @@ export class ContextManager {
launchType
}
const createContextRequest: RequestInit = {
method: 'POST',
headers,
body: JSON.stringify(requestBody)
}
const { result: context } = await this.request<Context>(
`${this.serverUrl}/launcher/contexts`,
createContextRequest
).catch((err) => {
throw prefixMessage(err, 'Error while creating launcher context. ')
})
const { result: context } = await this.requestClient
.post<Context>(
`${this.serverUrl}/launcher/contexts`,
requestBody,
accessToken
)
.catch((err) => {
throw prefixMessage(err, 'Error while creating launcher context. ')
})
return context
}
@@ -268,14 +240,6 @@ export class ContextManager {
true
)
const headers: any = {
'Content-Type': 'application/json'
}
if (accessToken) {
headers.Authorization = `Bearer ${accessToken}`
}
let originalContext
originalContext = await this.getComputeContextByName(
@@ -291,39 +255,33 @@ export class ContextManager {
)
}
const { result: context, etag } = await this.request<Context>(
`${this.serverUrl}/compute/contexts/${originalContext.id}`,
{
headers
}
).catch((err) => {
if (err && err.status === 404) {
throw new Error(
`The context '${contextName}' was not found on this server.`
)
}
const { result: context, etag } = await this.requestClient
.get<Context>(
`${this.serverUrl}/compute/contexts/${originalContext.id}`,
accessToken
)
.catch((err) => {
if (err && err.status === 404) {
throw new Error(
`The context '${contextName}' was not found on this server.`
)
}
throw err
})
throw err
})
// An If-Match header with the value of the last ETag for the context
// is required to be able to update it
// https://developer.sas.com/apis/rest/Compute/#update-a-context-definition
headers['If-Match'] = etag
const updateContextRequest: RequestInit = {
method: 'PUT',
headers,
body: JSON.stringify({
return await this.requestClient.put<Context>(
`/compute/contexts/${context.id}`,
{
...context,
...editedContext,
attributes: { ...context.attributes, ...editedContext.attributes }
})
}
return await this.request<Context>(
`${this.serverUrl}/compute/contexts/${context.id}`,
updateContextRequest
},
accessToken,
{ 'If-Match': etag }
)
}
@@ -331,20 +289,17 @@ export class ContextManager {
contextName: string,
accessToken?: string
): Promise<Context> {
const headers: any = {
'Content-Type': 'application/json'
}
if (accessToken) {
headers.Authorization = `Bearer ${accessToken}`
}
const { result: contexts } = await this.request<{ items: Context[] }>(
`${this.serverUrl}/compute/contexts?filter=eq(name, "${contextName}")`,
{ headers }
).catch((err) => {
throw prefixMessage(err, 'Error while getting compute context by name. ')
})
const { result: contexts } = await this.requestClient
.get<{ items: Context[] }>(
`${this.serverUrl}/compute/contexts?filter=eq(name, "${contextName}")`,
accessToken
)
.catch((err) => {
throw prefixMessage(
err,
'Error while getting compute context by name. '
)
})
if (!contexts || !(contexts.items && contexts.items.length)) {
throw new Error(
@@ -359,20 +314,16 @@ export class ContextManager {
contextId: string,
accessToken?: string
): Promise<ContextAllAttributes> {
const headers: any = {
'Content-Type': 'application/json'
}
if (accessToken) {
headers.Authorization = `Bearer ${accessToken}`
}
const { result: context } = await this.request<ContextAllAttributes>(
`${this.serverUrl}/compute/contexts/${contextId}`,
{ headers }
).catch((err) => {
throw prefixMessage(err, 'Error while getting compute context by id. ')
})
const {
result: context
} = await this.requestClient
.get<ContextAllAttributes>(
`${this.serverUrl}/compute/contexts/${contextId}`,
accessToken
)
.catch((err) => {
throw prefixMessage(err, 'Error while getting compute context by id. ')
})
return context
}
@@ -381,20 +332,14 @@ export class ContextManager {
executeScript: Function,
accessToken?: string
) {
const headers: any = {
'Content-Type': 'application/json'
}
if (accessToken) {
headers.Authorization = `Bearer ${accessToken}`
}
const { result: contexts } = await this.request<{ items: Context[] }>(
`${this.serverUrl}/compute/contexts?limit=10000`,
{ headers }
).catch((err) => {
throw prefixMessage(err, 'Error while fetching compute contexts.')
})
const { result: contexts } = await this.requestClient
.get<{ items: Context[] }>(
`${this.serverUrl}/compute/contexts?limit=10000`,
accessToken
)
.catch((err) => {
throw prefixMessage(err, 'Error while fetching compute contexts.')
})
const contextsList = contexts.items || []
const executableContexts: any[] = []
@@ -471,14 +416,9 @@ export class ContextManager {
const context = await this.getComputeContextByName(contextName, accessToken)
const deleteContextRequest: RequestInit = {
method: 'DELETE',
headers
}
return await this.request<Context>(
return await this.requestClient.delete<Context>(
`${this.serverUrl}/compute/contexts/${context.id}`,
deleteContextRequest
accessToken
)
}
@@ -486,34 +426,6 @@ export class ContextManager {
// TODO: implement deleteLauncherContext method
private async request<T>(
url: string,
options: RequestInit,
contentType: 'text' | 'json' = 'json'
) {
if (this.csrfToken) {
options.headers = {
...options.headers,
[this.csrfToken.headerName]: this.csrfToken.value
}
}
return await makeRequest<T>(
url,
options,
(token) => {
this.csrfToken = token
this.setCsrfToken(token)
},
contentType
).catch((err) => {
throw prefixMessage(
err,
'Error while making request in Context Manager. '
)
})
}
private validateContextName(name: string) {
if (!name) throw new Error('Context name is required.')
}

View File

@@ -1,115 +1,70 @@
import { isLogInRequired, needsRetry, isUrl } from './utils'
import { CsrfToken } from './types/CsrfToken'
import { isUrl } from './utils'
import { UploadFile } from './types/UploadFile'
import { ErrorResponse } from './types'
const requestRetryLimit = 5
import { ErrorResponse, LoginRequiredError } from './types/errors'
import { RequestClient } from './request/RequestClient'
export class FileUploader {
constructor(
private appLoc: string,
private serverUrl: string,
serverUrl: string,
private jobsPath: string,
private setCsrfTokenWeb: any,
private csrfToken: CsrfToken | null = null
private requestClient: RequestClient
) {
if (serverUrl) isUrl(serverUrl)
}
private retryCount = 0
public uploadFile(sasJob: string, files: UploadFile[], params: any) {
return new Promise((resolve, reject) => {
if (files?.length < 1)
reject(new ErrorResponse('At least one file must be provided.'))
if (!sasJob || sasJob === '')
reject(new ErrorResponse('sasJob must be provided.'))
if (files?.length < 1)
return Promise.reject(
new ErrorResponse('At least one file must be provided.')
)
if (!sasJob || sasJob === '')
return Promise.reject(new ErrorResponse('sasJob must be provided.'))
let paramsString = ''
let paramsString = ''
for (let param in params) {
if (params.hasOwnProperty(param)) {
paramsString += `&${param}=${params[param]}`
for (let param in params) {
if (params.hasOwnProperty(param)) {
paramsString += `&${param}=${params[param]}`
}
}
const program = this.appLoc
? this.appLoc.replace(/\/?$/, '/') + sasJob.replace(/^\//, '')
: sasJob
const uploadUrl = `${this.jobsPath}/?${
'_program=' + program
}${paramsString}`
const formData = new FormData()
for (let file of files) {
formData.append('file', file.file, file.fileName)
}
const csrfToken = this.requestClient.getCsrfToken('file')
if (csrfToken) formData.append('_csrf', csrfToken.value)
const headers = {
'cache-control': 'no-cache',
Accept: '*/*',
'Content-Type': 'text/plain'
}
return this.requestClient
.post(uploadUrl, formData, undefined, 'application/json', headers)
.then((res) =>
typeof res.result === 'string' ? JSON.parse(res.result) : res.result
)
.catch((err: Error) => {
if (err instanceof LoginRequiredError) {
return Promise.reject(
new ErrorResponse('You must be logged in to upload a file.', err)
)
}
}
const program = this.appLoc
? this.appLoc.replace(/\/?$/, '/') + sasJob.replace(/^\//, '')
: sasJob
const uploadUrl = `${this.serverUrl}${this.jobsPath}/?${
'_program=' + program
}${paramsString}`
const headers = {
'cache-control': 'no-cache'
}
const formData = new FormData()
for (let file of files) {
formData.append('file', file.file, file.fileName)
}
if (this.csrfToken) formData.append('_csrf', this.csrfToken.value)
fetch(uploadUrl, {
method: 'POST',
body: formData,
referrerPolicy: 'same-origin',
headers
return Promise.reject(
new ErrorResponse('File upload request failed.', err)
)
})
.then(async (response) => {
if (!response.ok) {
if (response.status === 403) {
const tokenHeader = response.headers.get('X-CSRF-HEADER')
if (tokenHeader) {
const token = response.headers.get(tokenHeader)
this.csrfToken = {
headerName: tokenHeader,
value: token || ''
}
this.setCsrfTokenWeb(this.csrfToken)
}
}
}
return response.text()
})
.then((responseText) => {
if (isLogInRequired(responseText))
reject(new ErrorResponse('You must be logged in to upload a file.'))
if (needsRetry(responseText)) {
if (this.retryCount < requestRetryLimit) {
this.retryCount++
this.uploadFile(sasJob, files, params).then(
(res: any) => resolve(res),
(err: any) => reject(err)
)
} else {
this.retryCount = 0
reject(responseText)
}
} else {
this.retryCount = 0
try {
resolve(JSON.parse(responseText))
} catch (e) {
reject(
new ErrorResponse(
'Error while parsing json from upload response.',
e
)
)
}
}
})
.catch((err: any) => {
reject(new ErrorResponse('Upload request failed.', err))
})
})
}
}

View File

@@ -1,3 +1,4 @@
import axios, { AxiosInstance } from 'axios'
import { isUrl } from './utils'
/**
@@ -5,8 +6,11 @@ import { isUrl } from './utils'
*
*/
export class SAS9ApiClient {
private httpClient: AxiosInstance
constructor(private serverUrl: string) {
if (serverUrl) isUrl(serverUrl)
this.httpClient = axios.create({ baseURL: this.serverUrl })
}
/**
@@ -38,18 +42,18 @@ export class SAS9ApiClient {
repositoryName: string
) {
const requestPayload = linesOfCode.join('\n')
const executeScriptRequest = {
method: 'PUT',
headers: {
Accept: 'application/json'
},
body: `command=${requestPayload}`
}
const executeScriptResponse = await fetch(
`${this.serverUrl}/sas/servers/${serverName}/cmd?repositoryName=${repositoryName}`,
executeScriptRequest
).then((res) => res.text())
return executeScriptResponse
const executeScriptResponse = await this.httpClient.put(
`/sas/servers/${serverName}/cmd?repositoryName=${repositoryName}`,
`command=${requestPayload}`,
{
headers: {
Accept: 'application/json'
},
responseType: 'text'
}
)
return executeScriptResponse.data
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,7 @@
import { Session, Context, CsrfToken, SessionVariable } from './types'
import { asyncForEach, makeRequest, isUrl } from './utils'
import { asyncForEach, isUrl } from './utils'
import { prefixMessage } from '@sasjs/utils/error'
import { RequestClient } from './request/RequestClient'
const MAX_SESSION_COUNT = 1
const RETRY_LIMIT: number = 3
@@ -14,14 +15,13 @@ export class SessionManager {
constructor(
private serverUrl: string,
private contextName: string,
private setCsrfToken: (csrfToken: CsrfToken) => void
private requestClient: RequestClient
) {
if (serverUrl) isUrl(serverUrl)
}
private sessions: Session[] = []
private currentContext: Context | null = null
private csrfToken: CsrfToken | null = null
private _debug: boolean = false
private printedSessionState = {
printed: false,
@@ -58,20 +58,13 @@ export class SessionManager {
}
async clearSession(id: string, accessToken?: string) {
const deleteSessionRequest = {
method: 'DELETE',
headers: this.getHeaders(accessToken)
}
return await this.request<Session>(
`${this.serverUrl}/compute/sessions/${id}`,
deleteSessionRequest
)
return await this.requestClient
.delete<Session>(`/compute/sessions/${id}`, accessToken)
.then(() => {
this.sessions = this.sessions.filter((s) => s.id !== id)
})
.catch((err) => {
throw err
throw prefixMessage(err, 'Error while deleting session. ')
})
}
@@ -98,17 +91,20 @@ export class SessionManager {
}
private async createAndWaitForSession(accessToken?: string) {
const createSessionRequest = {
method: 'POST',
headers: this.getHeaders(accessToken)
}
const { result: createdSession, etag } = await this.request<Session>(
`${this.serverUrl}/compute/contexts/${this.currentContext!.id}/sessions`,
createSessionRequest
).catch((err) => {
throw err
})
const {
result: createdSession,
etag
} = await this.requestClient
.post<Session>(
`${this.serverUrl}/compute/contexts/${
this.currentContext!.id
}/sessions`,
{},
accessToken
)
.catch((err) => {
throw err
})
await this.waitForSession(createdSession, etag, accessToken)
@@ -119,13 +115,13 @@ export class SessionManager {
private async setCurrentContext(accessToken?: string) {
if (!this.currentContext) {
const { result: contexts } = await this.request<{
items: Context[]
}>(`${this.serverUrl}/compute/contexts?limit=10000`, {
headers: this.getHeaders(accessToken)
}).catch((err) => {
throw err
})
const { result: contexts } = await this.requestClient
.get<{
items: Context[]
}>(`${this.serverUrl}/compute/contexts?limit=10000`, accessToken)
.catch((err) => {
throw err
})
const contextsList =
contexts && contexts.items && contexts.items.length
@@ -166,10 +162,7 @@ export class SessionManager {
accessToken?: string
) {
let sessionState = session.state
const headers: any = {
...this.getHeaders(accessToken),
'If-None-Match': etag
}
const stateLink = session.links.find((l: any) => l.rel === 'state')
return new Promise(async (resolve, _) => {
@@ -185,12 +178,10 @@ export class SessionManager {
this.printedSessionState.printed = true
}
const { result: state } = await this.requestSessionStatus<string>(
const state = await this.getSessionState(
`${this.serverUrl}${stateLink.href}?wait=30`,
{
headers
},
'text'
etag!,
accessToken
).catch((err) => {
throw err
})
@@ -223,73 +214,33 @@ export class SessionManager {
})
}
private async request<T>(
private async getSessionState(
url: string,
options: RequestInit,
contentType: 'text' | 'json' = 'json'
etag: string,
accessToken?: string
) {
if (this.csrfToken) {
options.headers = {
...options.headers,
[this.csrfToken.headerName]: this.csrfToken.value
}
}
return await this.requestClient
.get(url, accessToken, 'text/plain', { 'If-None-Match': etag })
.then((res) => res.result as string)
.catch((err) => {
if (err.status === INTERNAL_SAS_ERROR.status)
return INTERNAL_SAS_ERROR.message
return await makeRequest<T>(
url,
options,
(token) => {
this.csrfToken = token
this.setCsrfToken(token)
},
contentType
).catch((err) => {
throw err
})
}
private async requestSessionStatus<T>(
url: string,
options: RequestInit,
contentType: 'text' | 'json' = 'json'
) {
if (this.csrfToken) {
options.headers = {
...options.headers,
[this.csrfToken.headerName]: this.csrfToken.value
}
}
return await makeRequest<T>(
url,
options,
(token) => {
this.csrfToken = token
this.setCsrfToken(token)
},
contentType
).catch((err) => {
if (err.status === INTERNAL_SAS_ERROR.status)
return { result: INTERNAL_SAS_ERROR.message }
throw err
})
throw err
})
}
async getVariable(sessionId: string, variable: string, accessToken?: string) {
const getSessionVariable = {
method: 'GET',
headers: this.getHeaders(accessToken)
}
return await this.request<SessionVariable>(
`${this.serverUrl}/compute/sessions/${sessionId}/variables/${variable}`,
getSessionVariable
).catch((err) => {
throw prefixMessage(
err,
`Error while fetching session variable '${variable}'.`
return await this.requestClient
.get<SessionVariable>(
`${this.serverUrl}/compute/sessions/${sessionId}/variables/${variable}`,
accessToken
)
})
.catch((err) => {
throw prefixMessage(
err,
`Error while fetching session variable '${variable}'.`
)
})
}
}

7
src/__mocks__/axios.ts Normal file
View File

@@ -0,0 +1,7 @@
import { AxiosStatic } from 'axios'
const mockAxios = jest.genMockFromModule('axios') as AxiosStatic
mockAxios.create = jest.fn(() => mockAxios)
export default mockAxios

172
src/auth/AuthManager.ts Normal file
View File

@@ -0,0 +1,172 @@
import { ServerType } from '@sasjs/utils/types'
import { isAuthorizeFormRequired } from '.'
import { RequestClient } from '../request/RequestClient'
import { serialize } from '../utils'
export class AuthManager {
public userName = ''
private loginUrl: string
private logoutUrl: string
constructor(
private serverUrl: string,
private serverType: ServerType,
private requestClient: RequestClient,
private loginCallback: () => Promise<void>
) {
this.loginUrl = `/SASLogon/login`
this.logoutUrl =
this.serverType === ServerType.Sas9
? '/SASLogon/logout?'
: '/SASLogon/logout.do?'
}
/**
* Logs into the SAS server with the supplied credentials.
* @param username - a string representing the username.
* @param password - a string representing the password.
*/
public async logIn(username: string, password: string) {
const loginParams: any = {
_service: 'default',
username,
password
}
this.userName = loginParams.username
const { isLoggedIn, loginForm } = await this.checkSession()
if (isLoggedIn) {
await this.loginCallback()
return {
isLoggedIn,
userName: this.userName
}
}
for (const key in loginForm) {
loginParams[key] = loginForm[key]
}
const loginParamsStr = serialize(loginParams)
const { result: loginResponse } = await this.requestClient.post<string>(
this.loginUrl,
loginParamsStr,
undefined,
'text/plain',
{
'Content-Type': 'application/x-www-form-urlencoded',
Accept: '*/*'
}
)
let loggedIn = isLogInSuccess(loginResponse)
if (!loggedIn) {
const currentSession = await this.checkSession()
loggedIn = currentSession.isLoggedIn
}
if (loggedIn) {
this.loginCallback()
}
return {
isLoggedIn: !!loggedIn,
userName: this.userName
}
}
/**
* Checks whether a session is active, or login is required.
* @returns - a promise which resolves with an object containing two values - a boolean `isLoggedIn`, and a string `userName`.
*/
public async checkSession() {
//For VIYA we will send request on API endpoint. Which is faster then pinging SASJobExecution.
//For SAS9 we will send request on SASStoredProcess
const url =
this.serverType === 'SASVIYA'
? `${this.serverUrl}/identities`
: `${this.serverUrl}/SASStoredProcess`
const { result: loginResponse } = await this.requestClient
.get<string>(url, undefined, 'text/plain')
.catch((err: any) => {
return { result: 'authErr' }
})
const isLoggedIn = loginResponse !== 'authErr'
let loginForm = null
if (!isLoggedIn) {
//We will logout to make sure cookies are removed and login form is presented
this.logOut()
const { result: formResponse } = await this.requestClient.get<string>(
this.loginUrl.replace('.do', ''),
undefined,
'text/plain'
)
loginForm = await this.getLoginForm(formResponse)
}
return Promise.resolve({
isLoggedIn,
userName: this.userName,
loginForm
})
}
private getLoginForm(response: any) {
const pattern: RegExp = /<form.+action="(.*Logon[^"]*).*>/
const matches = pattern.exec(response)
const formInputs: any = {}
if (matches && matches.length) {
this.setLoginUrl(matches)
const inputs = response.match(/<input.*"hidden"[^>]*>/g)
if (inputs) {
inputs.forEach((inputStr: string) => {
const valueMatch = inputStr.match(/name="([^"]*)"\svalue="([^"]*)/)
if (valueMatch && valueMatch.length) {
formInputs[valueMatch[1]] = valueMatch[2]
}
})
}
}
return Object.keys(formInputs).length ? formInputs : null
}
private setLoginUrl = (matches: RegExpExecArray) => {
let parsedURL = matches[1].replace(/\?.*/, '')
if (parsedURL[0] === '/') {
parsedURL = parsedURL.substr(1)
const tempLoginLink = this.serverUrl
? `${this.serverUrl}/${parsedURL}`
: `${parsedURL}`
const loginUrl = tempLoginLink
this.loginUrl =
this.serverType === ServerType.SasViya
? tempLoginLink
: loginUrl.replace('.do', '')
}
}
/**
* Logs out of the configured SAS server.
*/
public logOut() {
this.requestClient.clearCsrfTokens()
return this.requestClient.get(this.logoutUrl, undefined).then(() => true)
}
}
const isLogInSuccess = (response: string): boolean =>
/You have signed in/gm.test(response)

3
src/auth/index.ts Normal file
View File

@@ -0,0 +1,3 @@
export * from './AuthManager'
export * from './isAuthorizeFormRequired'
export * from './isLoginRequired'

View File

@@ -0,0 +1,195 @@
import { AuthManager } from '../AuthManager'
import * as dotenv from 'dotenv'
import { ServerType } from '@sasjs/utils/types'
import axios from 'axios'
import {
mockLoginAuthoriseRequiredResponse,
mockLoginSuccessResponse
} from './mockResponses'
import { serialize } from '../../utils'
import { RequestClient } from '../../request/RequestClient'
jest.mock('axios')
const mockedAxios = axios as jest.Mocked<typeof axios>
describe('AuthManager', () => {
const authCallback = jest.fn().mockImplementation(() => Promise.resolve())
const serverUrl = 'http://test-server.com'
const serverType = ServerType.SasViya
const userName = 'test-username'
const password = 'test-password'
const requestClient = new RequestClient(serverUrl)
beforeAll(() => {
dotenv.config()
jest.restoreAllMocks()
})
it('should instantiate and set the correct URLs for a Viya server', () => {
const authManager = new AuthManager(
serverUrl,
serverType,
requestClient,
authCallback
)
expect(authManager).toBeTruthy()
expect((authManager as any).serverUrl).toEqual(serverUrl)
expect((authManager as any).serverType).toEqual(serverType)
expect((authManager as any).loginUrl).toEqual(`/SASLogon/login`)
expect((authManager as any).logoutUrl).toEqual('/SASLogon/logout.do?')
})
it('should instantiate and set the correct URLs for a SAS9 server', () => {
const authCallback = () => Promise.resolve()
const serverType = ServerType.Sas9
const authManager = new AuthManager(
serverUrl,
serverType,
requestClient,
authCallback
)
expect(authManager).toBeTruthy()
expect((authManager as any).serverUrl).toEqual(serverUrl)
expect((authManager as any).serverType).toEqual(serverType)
expect((authManager as any).loginUrl).toEqual(`/SASLogon/login`)
expect((authManager as any).logoutUrl).toEqual('/SASLogon/logout?')
})
it('should call the auth callback and return when already logged in', async (done) => {
const authManager = new AuthManager(
serverUrl,
serverType,
requestClient,
authCallback
)
jest.spyOn(authManager, 'checkSession').mockImplementation(() =>
Promise.resolve({
isLoggedIn: true,
userName: 'test',
loginForm: 'test'
})
)
const loginResponse = await authManager.logIn(userName, password)
expect(loginResponse.isLoggedIn).toBeTruthy()
expect(loginResponse.userName).toEqual(userName)
expect(authCallback).toHaveBeenCalledTimes(1)
done()
})
it('should post a login request to the server if not logged in', async (done) => {
const authManager = new AuthManager(
serverUrl,
serverType,
requestClient,
authCallback
)
jest.spyOn(authManager, 'checkSession').mockImplementation(() =>
Promise.resolve({
isLoggedIn: false,
userName: 'test',
loginForm: { name: 'test' }
})
)
mockedAxios.post.mockImplementation(() =>
Promise.resolve({ data: mockLoginSuccessResponse })
)
const loginResponse = await authManager.logIn(userName, password)
expect(loginResponse.isLoggedIn).toBeTruthy()
expect(loginResponse.userName).toEqual(userName)
const loginParams = serialize({
_service: 'default',
username: userName,
password,
name: 'test'
})
expect(mockedAxios.post).toHaveBeenCalledWith(
`/SASLogon/login`,
loginParams,
{
withCredentials: true,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Accept: '*/*'
}
}
)
expect(authCallback).toHaveBeenCalledTimes(1)
done()
})
it('should parse and submit the authorisation form when necessary', async (done) => {
const authManager = new AuthManager(
serverUrl,
serverType,
requestClient,
authCallback
)
jest
.spyOn(requestClient, 'authorize')
.mockImplementation(() => Promise.resolve())
jest.spyOn(authManager, 'checkSession').mockImplementation(() =>
Promise.resolve({
isLoggedIn: false,
userName: 'test',
loginForm: { name: 'test' }
})
)
mockedAxios.post.mockImplementationOnce(() =>
Promise.resolve({
data: mockLoginAuthoriseRequiredResponse,
config: { url: 'https://test.com/SASLogon/login' },
request: { responseURL: 'https://test.com/OAuth/authorize' }
})
)
mockedAxios.get.mockImplementationOnce(() =>
Promise.resolve({
data: mockLoginAuthoriseRequiredResponse
})
)
await authManager.logIn(userName, password)
expect(requestClient.authorize).toHaveBeenCalledWith(
mockLoginAuthoriseRequiredResponse
)
done()
})
it('should check and return session information if logged in', async (done) => {
const authManager = new AuthManager(
serverUrl,
serverType,
requestClient,
authCallback
)
mockedAxios.get.mockImplementation(() =>
Promise.resolve({ data: '<button onClick="logout">' })
)
const response = await authManager.checkSession()
expect(response.isLoggedIn).toBeTruthy()
expect(mockedAxios.get).toHaveBeenNthCalledWith(
1,
`http://test-server.com/identities`,
{
withCredentials: true,
responseType: 'text',
transformResponse: undefined,
headers: {
Accept: '*/*',
'Content-Type': 'text/plain'
}
}
)
done()
})
})

Some files were not shown because too many files have changed in this diff Show More