1
0
mirror of https://github.com/sasjs/adapter.git synced 2025-12-11 09:24:35 +00:00

Compare commits

...

272 Commits

Author SHA1 Message Date
Yury Shkoda
0f881fba72 chore(deps): regenerate package-lock.json 2021-12-23 11:49:42 +03:00
Yury Shkoda
4e125ce38f Merge pull request #605 from sasjs/issue-604
Improve error handling and job/session state polling
2021-12-22 19:11:37 +03:00
Yury Shkoda
4a963ffbf5 feat(polling-state): improve logging 2021-12-22 18:46:06 +03:00
Yury Shkoda
f25d9ec09d test(RequestClient): cover handleError method 2021-12-21 16:41:08 +03:00
Yury Shkoda
4197ad5aa8 test(RequestClient): fix error handling 2021-12-21 11:40:59 +03:00
Yury Shkoda
2ebd6e11ba test(RequestClient): fix error handling 2021-12-21 11:40:27 +03:00
Yury Shkoda
098e7f8590 fix(errors): fixed error handling function 2021-12-20 10:57:53 +03:00
Yury Shkoda
42aec96410 feat(pollJobState): improved loggging 2021-12-20 10:56:52 +03:00
Allan Bowe
f2905ee169 Merge pull request #602 from sasjs/hot-fix-sasjs-server
fix: response from SASJS Server with/without debug on
2021-12-15 17:42:29 +00:00
Saad Jutt
1ba9291746 fix: response from SASJS Server with/without debug on 2021-12-15 21:32:02 +05:00
Muhammad Saad
c44766ea14 Merge pull request #600 from sasjs/sasjs-server-access-refresh-tokens
feat: get access token & refresh tokens for server type SASJS
2021-12-15 12:56:57 +05:00
Saad Jutt
2c10b9c65c chore: typedoc updated 2021-12-13 17:01:59 +05:00
Saad Jutt
c56874fe00 feat: login for web with server type SASJS 2021-12-09 17:13:47 +05:00
Saad Jutt
ebe9c2ffeb feat: get access token & refresh tokens for server type SASJS 2021-12-09 11:43:50 +05:00
Allan Bowe
b645d1495b Merge pull request #586 from sasjs/handle-sasjs-server-response
fix: handle sasjs server response
2021-12-08 09:09:42 +00:00
3a4a4c3460 chore: sasjs path optional 2021-12-06 17:44:08 +01:00
Saad Jutt
182de51f9b chore: added comments 2021-12-05 19:27:07 +05:00
Saad Jutt
712d1549c7 feat(server): added sasjs server support 2021-12-05 18:31:36 +05:00
Allan Bowe
5a478c8936 Merge pull request #594 from sasjs/issue-588
fix: sas9JobExecutor issues
2021-12-03 11:13:15 +00:00
c9ecc1dde4 chore: requestClient naming 2021-12-03 11:54:52 +01:00
bdf9e2fd5b fix: sas9JobExecutor not returning response 2021-11-26 15:32:10 +01:00
Vladislav Parhomchik
0b795b26c0 Merge pull request #589 from sasjs/issue-587
Pinned typedoc version to 0.19.2
2021-11-26 12:47:21 +03:00
96aac0cfa2 fix: Sas9JobExecutor appendRequest 2021-11-24 20:26:02 +01:00
Yury Shkoda
a82e1f33e3 docs: update docs 2021-11-24 11:23:27 +03:00
Yury Shkoda
058c887cd3 chore(docs): pinned typedoc version 2021-11-24 11:19:41 +03:00
Allan Bowe
81d959c7c1 fix: parsing _webout from response regardless of debug status 2021-11-18 19:56:12 +00:00
7c5adeabb5 feat: parse response in webJobExecutor when server type is sasjs server 2021-11-19 00:35:02 +05:00
Yury Shkoda
cb88376bda Merge pull request #576 from sasjs/update-dependencies
chore(deps): update dependencies
2021-11-01 15:50:51 +03:00
Yury Shkoda
4c8ddeca25 chore(deps): regenerate package-lock with --legacy-peer-deps option 2021-11-01 15:26:31 +03:00
Yury Shkoda
d264a3f239 chore(ci/cd): used node lts/fermium for GH actions 2021-11-01 14:58:27 +03:00
Yury Shkoda
840b1aa1bf chore(deps): regenerate package-lock.json 2021-11-01 14:47:48 +03:00
Yury Shkoda
e26fd307c8 chore(git): merge branch 'master' into update-dependencies 2021-11-01 14:45:38 +03:00
Allan Bowe
62deaf9f03 Merge pull request #581 from sasjs/package-lock-and-node-version
fix: updated package-lock + updated node version for workflow
2021-10-29 09:35:50 +01:00
Saad Jutt
865bf71f7d fix: updated package-lock + updated node version for workflow 2021-10-29 13:32:59 +05:00
Allan Bowe
734c5bccaa Merge pull request #564 from sasjs/issue-555
BREAKING CHANGE: boolean allowInsecure is replaced with Https Agent Config
2021-10-29 09:12:56 +01:00
Saad Jutt
bc82cb5f5e chore: TSDoc comment updated 2021-10-29 00:29:14 +05:00
Allan Bowe
f25c76fdfd fix: merge 2021-10-28 13:50:29 +00:00
Allan Bowe
e649b41e9e Merge pull request #579 from sasjs/sasjs/server-support
chore: removed legacy code
2021-10-28 14:02:17 +01:00
Yury Shkoda
a962979765 chore: removed legacy code 2021-10-28 15:44:17 +03:00
Yury Shkoda
01d76fa66f Merge pull request #574 from sasjs/sasjs/server
Add sasjs/server support
2021-10-28 15:36:45 +03:00
Yury Shkoda
49cfde9f7d chore(sasjs/server): fix deploy endpoint 2021-10-28 15:13:14 +03:00
Yury Shkoda
ce04ffea05 fix(SASjsApiClient): change SASjs Server endpoints 2021-10-28 10:21:15 +03:00
9dc0499f66 chore: allow insecure remove 2021-10-27 13:20:59 +02:00
Allan Bowe
bc1a7dc54f chore: lint fix 2021-10-27 09:53:42 +00:00
Allan Bowe
93c267fd4e Update AuthManager.ts 2021-10-27 12:20:40 +03:00
Yury Shkoda
0457eb6663 fix: fix calls to SASjsApi endpoints 2021-10-27 11:16:35 +03:00
Yury Shkoda
519494718b chore: address PR comments 2021-10-27 11:15:28 +03:00
Vladislav Parhomchik
de5c38f0fb chore(deps): versions fix, authmanager default returns 2021-10-26 15:46:51 +03:00
Vladislav Parhomchik
208470e7d9 chore(deps): update dependencies 2021-10-26 13:06:03 +03:00
Yury Shkoda
0321f77451 chore: update dependencies 2021-10-20 15:08:58 +03:00
Yury Shkoda
6c5fdc01eb chore: merge branch 'sasjs/server' of https://github.com/sasjs/adapter into sasjs/server 2021-10-20 15:05:03 +03:00
Yury Shkoda
2aa0cd8d7a chore: remove tmp utilities 2021-10-20 15:01:21 +03:00
Yury Shkoda
397bc4524f chore: change 'SASBase' to 'SASjs' 2021-10-20 15:00:52 +03:00
Yury Shkoda
8dce9f3e48 chore(npm): update @sasjs/utils version 2021-10-20 14:59:02 +03:00
8e9f1df1ce chore: fixing the error when using in angular, added isNode check for imports 2021-10-12 16:45:39 +02:00
Saad Jutt
ff4915f7f3 chore: comment file removed 2021-10-07 13:47:28 +05:00
Saad Jutt
6ff8eece7b chore: removed httpsAgent type + clean up 2021-10-07 13:45:50 +05:00
Saad Jutt
2849e6ed07 test(RequestClient): updated 2021-10-06 14:06:00 +05:00
Yury Shkoda
90b11fe3fa feat(deploy): add appLoc 2021-10-04 17:01:25 +03:00
Yury Shkoda
147609842d Merge pull request #565 from sasjs/modifying-npmignore
chore: add .all-contributorsrc to .npmignore
2021-10-04 09:17:54 +03:00
Yury Shkoda
dd6f9cd617 chore(npm): add empty line to the end of .npmignore 2021-10-04 09:10:37 +03:00
Saad Jutt
7f590c35da test(RequestClient): fixed to use actual axios 2021-10-01 15:42:04 +05:00
Vladislav Parhomchik
a38de108e3 chore: add .all-contributorsrc to .npmignore 2021-10-01 10:31:30 +03:00
Saad Jutt
e975e7de97 test(RequestClient): specs for communicating with self-signed server 2021-10-01 12:03:44 +05:00
Yury Shkoda
d418a7e971 fix(http): extend valid responce statuses up to 400 2021-09-30 14:33:33 +03:00
Yury Shkoda
a5b5052a5f fix(baseSAS): removed sasjs/server logic 2021-09-30 14:31:54 +03:00
Yury Shkoda
7638595523 chore(git): fix .gitignore 2021-09-30 14:30:47 +03:00
Allan Bowe
70d64f6eec Merge pull request #556 from sasjs/test-framework
chore: bump up test-framework
2021-09-29 22:42:54 +01:00
Saad Jutt
f0ecfa57e5 BREAKING CHANGE: boolean allowInsecure is replaced configuration of Https Agent 2021-09-28 16:02:12 +05:00
Yury Shkoda
5f3416ecd7 chore(utils): add tmpFolder utils 2021-09-28 10:39:54 +03:00
Yury Shkoda
d8b1a72da2 chore(types): add FileTree types 2021-09-28 10:39:18 +03:00
Yury Shkoda
7e64819eb2 feat(sasjs/server): add SASBaseApiClient class 2021-09-28 10:38:29 +03:00
Yury Shkoda
2f1d403af4 chore(deps): use @sasjs/utils tarball 2021-09-28 10:33:11 +03:00
Yury Shkoda
075d410f7d chore(git): ignore tmp folder 2021-09-28 10:31:36 +03:00
Muhammad Saad
f964bcef9e Merge pull request #559 from sasjs/job-executor-bugs
fix: Job executor bugs
2021-09-22 20:08:17 +05:00
Allan Bowe
5784232d4e chore: updating readme 2021-09-20 17:50:18 +00:00
Yury Shkoda
70ecc8b50e Merge pull request #553 from sasjs/update-deps
chore(deps): update dependencies
2021-09-20 15:48:57 +03:00
Saad Jutt
369a035e8a fix(webJobExecutor): append resend request for viya's getJobUri authentication 2021-09-20 13:28:41 +05:00
Saad Jutt
e5655033c1 fix(jobExecutor): appending resend requests before login call 2021-09-20 12:52:49 +05:00
Yury Shkoda
c7af30bfa3 chore(deps): add caret to axios version 2021-09-17 16:36:22 +03:00
Yury Shkoda
c8da3a54cf chore(axios): override default request transformer 2021-09-17 16:29:15 +03:00
Yury Shkoda
100da16803 chore(deps): bump axios to 0.21.4 2021-09-17 16:28:44 +03:00
Allan Bowe
dc91679040 Merge pull request #558 from sasjs/issue-554
fix: viya debug response null due to wrong content-type
2021-09-17 15:18:58 +03:00
28c8ebfc65 fix: viya debug response null due to wrong content-type 2021-09-16 14:58:18 +02:00
Yury Shkoda
0c4d30afe3 Merge pull request #557 from sasjs/dependabot-upd
chore(dependabot): change schedule interval
2021-09-16 14:18:27 +03:00
Yury Shkoda
bc015b72b6 chore(dependabot): change schedule interval 2021-09-16 14:08:53 +03:00
085a3f84e9 chore: bump up test-framework 2021-09-16 10:37:56 +02:00
Yury Shkoda
f241d75f0a chore(deps): pin axios version to 0.21.1 2021-09-14 17:23:19 +03:00
Muhammad Saad
8a883c09f6 Merge pull request #549 from sasjs/improvements-file-uploader
fix: FileUploader extends BaseJobExecutor
2021-09-14 18:47:26 +05:00
Vladislav Parhomchik
42d01b4044 chore(deps): update dependencies 2021-09-14 11:09:47 +03:00
Saad Jutt
15ff90025a fix(fileUploader): added loginCallback 2021-09-14 05:58:40 +05:00
Saad Jutt
10cf4998f5 fix(loginPrompt): z-index added 2021-09-13 18:02:26 +05:00
Saad Jutt
f714f20f29 chore(fileUploader): support loginCallback and re-submit request 2021-09-13 17:45:35 +05:00
Saad Jutt
19adcc3115 chore: FileUploader extends BaseJobExecutor 2021-09-13 17:42:41 +05:00
Muhammad Saad
bb6b25bac7 Merge pull request #548 from sasjs/code-improvements
chore(imprvements): code changes for fileUploader
2021-09-13 17:40:00 +05:00
Saad Jutt
ec9dbd7ad6 chore(imprvements): code changes for fileUploader 2021-09-13 07:30:26 +05:00
Allan Bowe
2cfba99bda Merge pull request #540 from sasjs/issue-532
fix: move SASjsRequest array from BaseJobExecutor class to RequestClient class
2021-09-11 17:17:33 +03:00
Allan Bowe
a181914c36 Merge pull request #522 from sasjs/redirected-login
feat(auth): redirected login
2021-09-10 18:03:24 +03:00
Saad Jutt
539405e249 chore: fixed sasjs-tests build 2021-09-09 15:30:12 +05:00
Krishna Acondy
d9c27efa8d chore(auth-manager): rename variable 2021-09-09 07:51:07 +01:00
Krishna Acondy
2ccc7b5499 chore(request-client): rename method 2021-09-09 07:41:20 +01:00
Saad Jutt
4623b9665b chore: login prompt dialog bug fixed 2021-09-09 11:29:12 +05:00
9c099b899c fix: If the request client has already been instantiated, update config 2021-09-09 11:03:48 +05:00
Saad Jutt
3ae0809ee5 test(AuthManager): improved coverage 2021-09-09 04:37:30 +05:00
d52c5b26a0 chore: merge master into issue-532 2021-09-08 15:16:06 +05:00
Saad Jutt
0ea6e839ac test: added for verifySasLogin 2021-09-08 15:08:55 +05:00
46ef7b19f6 fix: before instantiating RequestClient check if its already instantiated 2021-09-08 15:02:22 +05:00
Saad Jutt
a00bf5ba67 test(AuthManager): specs added for redirected login 2021-09-08 13:04:03 +05:00
Saad Jutt
e0b09adbba chore: code refactor renamed variables/functions 2021-09-08 06:30:53 +05:00
Saad Jutt
19a57dbf6e chore: code refactor renamed variables/functions 2021-09-08 05:49:24 +05:00
Saad Jutt
cd2b32f2f4 test(checkSession): extract username from server response 2021-09-07 06:08:17 +05:00
Saad Jutt
a1f5355d6a chore: fetch username for Redirected-Login and return 2021-09-07 05:26:42 +05:00
Saad Jutt
0972c0deaa chore(merge): Merge branch 'master' into redirected-login 2021-09-07 05:05:51 +05:00
Allan Bowe
e1a5cc9e45 Merge pull request #504 from sasjs/extract-username-while-check-session
fix: while checking session extract username also
2021-09-06 16:22:04 +03:00
73f50c0435 chore: merge master into issue-532 2021-09-06 13:39:58 +05:00
Saad Jutt
351a22cb3c fix: deriving username with full name 2021-09-06 12:52:40 +05:00
Saad Jutt
3ccd35a4e2 fix: if username is not present in SAS9, derive it with full name 2021-09-06 12:42:26 +05:00
Saad Jutt
e4956cc1d4 chore: test corrected for AuthManager 2021-09-05 12:06:27 +05:00
Saad Jutt
291ba51b07 fix(request): handled error case for sas9 server 2021-09-05 11:55:17 +05:00
5ee57f3d07 chore: added jsdoc header 2021-09-03 14:54:35 +05:00
Allan Bowe
ed72c5c48c Merge pull request #536 from sasjs/update-dependencies
chore(deps): update dependencies
2021-09-03 12:18:46 +03:00
146b0715bf fix: set debug: false in config of fileUploader tests 2021-09-03 13:57:39 +05:00
dfc1d567a5 fix: append sasjs requests array from uploadFile 2021-09-03 13:55:49 +05:00
779200f5fc fix: throw error if null or undefined is passed to getValidJson 2021-09-03 13:54:02 +05:00
cf4c4cfca9 fix: move SASjsRequest array from BaseJobExecutor class to RequestClient class 2021-09-03 13:51:58 +05:00
Vladislav Parhomchik
ad4eead4ca chore(deps): update dependencies 2021-09-02 12:47:23 +03:00
Saad Jutt
f40a86f0f6 chore(redirectLogin): onLoggedOut callback should be an async 2021-09-02 13:43:07 +05:00
Muhammad Saad
aa9383a483 Merge pull request #534 from sasjs/package-script-fix
chore(deps): pin typescript version to fix packaging
2021-09-01 06:20:25 +05:00
Saad Jutt
867422f4cc fix: extraResponseAttributes for WebJobExecutor + sasjs-tests 2021-09-01 03:50:55 +05:00
Saad Jutt
2a6e29b5b8 fix: username returns from checkSession 2021-09-01 03:49:44 +05:00
Yury Shkoda
ba105f609c chore(deps): pin typescript version to fix packaging 2021-08-31 14:51:14 +03:00
Saad Jutt
e4d669f9b6 chore: typo fixed userName 2021-08-31 12:58:47 +05:00
Saad Jutt
5edf09e0a7 fix: usernames to lower case 2021-08-31 12:50:04 +05:00
Saad Jutt
5a695f495c chore: login returns username 2021-08-31 12:39:04 +05:00
Saad Jutt
f231edb4a6 chore: redirect login with onLoggedOut callback 2021-08-31 12:36:20 +05:00
Saad Jutt
389ef94cd5 feat(login): redirect mechanism - in page link to open popup 2021-08-28 10:01:20 +05:00
Saad Jutt
4c90f66dbc chore(merge): Merge branch 'master' into redirected-login 2021-08-27 23:34:52 +05:00
ab8643a89a chore: merge master into extract-username-while-check-session 2021-08-27 14:17:20 +05:00
Allan Bowe
b831b93133 Merge pull request #523 from sasjs/checkNodeVersion
chore: check node version before installing packages
2021-08-26 15:17:15 +03:00
Yury Shkoda
0c3aab673a Merge pull request #521 from sasjs/npmignore
chore: updating .npmignore to reduce bundle size (currently 7.7mb)
2021-08-26 15:12:20 +03:00
Saad Jutt
83353326fb test(AuthManager): fixed 2021-08-26 10:44:06 +05:00
Saad Jutt
db7a5d601e fix(login): code refactor + sasjs-tests updated 2021-08-26 10:33:00 +05:00
Saad Jutt
ee977f4fab chore(merge): Merge branch 'master' into extract-username-while-check-session 2021-08-26 09:02:40 +05:00
33e7564e8f chore: add cross icon at the start of error message 2021-08-25 21:26:49 +05:00
Saad Jutt
1a59f95be7 fix: split code to files + corrected usage of loginCallback 2021-08-25 07:55:04 +05:00
77c4c473c1 chore: address requested changes 2021-08-24 11:51:18 +05:00
47ff1a2293 chore: check node version before installing packages 2021-08-23 13:42:37 +05:00
Saad Jutt
97918f301b chore(redirectLogin): centered popup + verifying sas9 login + sasviya login fixes 2021-08-22 03:57:23 +05:00
Krishna Acondy
830a907bd1 feat(login): add redirected login mechanism 2021-08-21 21:36:50 +01:00
Allan Bowe
ffae344476 chore: updating .npmignore to reduce bundle size (currently 7.7mb) 2021-08-20 23:18:56 +03:00
Allan Bowe
4f62cd0148 Merge pull request #518 from sasjs/loginFix
fix: web request and sas9 login
2021-08-18 19:17:43 +03:00
bd92c1925e chore: merge main into loginFix, conflicts resolved 2021-08-18 20:37:24 +05:00
Allan Bowe
6c29d7823b Merge pull request #517 from sasjs/issue-506
fix: double parsing issue in sas9 when debug is enabled
2021-08-18 18:22:54 +03:00
3c9f133374 fix: throw error from parseWeboutResponse function if unable to find webout response 2021-08-18 16:33:26 +05:00
e72195ca5d fix: predefine jsonParseArrayError message 2021-08-18 16:09:51 +05:00
3e7ddf59b4 style: lint 2021-08-18 11:43:09 +02:00
cd67fb38dc fix: web request and login 2021-08-18 11:42:34 +02:00
78149e6c54 chore: remove console log statement 2021-08-18 00:27:10 +05:00
63e220c5be fix: double parsing issue in sas9 debug mode fixed 2021-08-18 00:05:52 +05:00
8464e506e0 fix: check for valid json while parsing sas viya debug response 2021-08-18 00:04:30 +05:00
0bc69401e5 chore: refactor code for getValidJson function 2021-08-18 00:02:48 +05:00
47fe7686cb chore: introduced new error types: InvalidJsonError, JsonParseArrayError, WeboutResponseError 2021-08-18 00:01:28 +05:00
Allan Bowe
dd2b3671fd Merge pull request #513 from sasjs/issue-508
fix: handle context name when it's undefined/null or empty string
2021-08-16 14:34:40 +03:00
bd03b2b06d fix: when contextName is falsy value, do not add it to apiUrl in web approach and fallback to default in jes approach 2021-08-15 16:11:50 +05:00
Allan Bowe
2b2b8e6429 Merge pull request #505 from sasjs/fileuploader-quick-fix
fix(fileUploader): parsing debug response for SASVIYA
2021-08-09 18:22:46 +03:00
Allan Bowe
5375d0a208 Update FileUploader.ts 2021-08-09 15:42:29 +03:00
Saad Jutt
f2da84829e fix(fileUploader): parsing debug response for SASVIYA 2021-08-09 17:28:55 +05:00
Saad Jutt
fc1c93957c fix: while checking session extract username also 2021-08-07 07:32:48 +05:00
Yury Shkoda
f172ad66bc Merge pull request #501 from sasjs/cli-issue-862
Allow self-signed certificates in requests to SAS9
2021-08-06 09:25:32 +03:00
Yury Shkoda
046c58bb80 chore(deps): restore package-lock 2021-08-05 15:57:47 +03:00
Yury Shkoda
bf825a4f65 chore(deps): discard versions bump 2021-08-05 15:55:45 +03:00
Yury Shkoda
d58cff9081 chore(deps): bump ts-jest, ts-loader, typedoc, webpack 2021-08-04 16:59:55 +03:00
Yury Shkoda
7ab1964746 feat(insecureRequests): allow self-signed certificates for SAS9 2021-08-04 16:59:03 +03:00
Yury Shkoda
b118280a77 Merge pull request #491 from sasjs/session-state-fix
fix(session): remove retry limit if could not get state
2021-07-29 10:34:50 +03:00
Yury Shkoda
5317c14d54 test(sessionManager): improve test coverage of 'waitForSession' 2021-07-29 10:24:03 +03:00
Yury Shkoda
85fed5cd76 chore(git): Merge branch 'master' of https://github.com/sasjs/adapter into session-state-fix 2021-07-28 11:54:21 +03:00
Yury Shkoda
6f9196c690 refactor(session): make loggedErrors a private property 2021-07-28 09:39:52 +03:00
Allan Bowe
2d0a73e74d Merge pull request #480 from sasjs/issue-477
fix: update error message when folder not found
2021-07-28 08:37:26 +03:00
Yury Shkoda
ac8821baec test(session): add assertion of get request quantity 2021-07-27 16:06:43 +03:00
Yury Shkoda
0b9284e481 refactor(session): improve waitForSession method 2021-07-27 16:03:41 +03:00
Krishna Acondy
7b7a80c502 chore(root-folder-not-found): add test 2021-07-27 08:20:30 +01:00
Krishna Acondy
1ace15a308 fix(root-folder-not-found): create RootFolderNotFoundError class 2021-07-27 07:52:19 +01:00
Allan Bowe
e1b3ef7c8c Merge pull request #495 from sasjs/contributors
chore: contributors
2021-07-26 20:26:15 +03:00
710056bded fix: create a utility throwError and add test case for it 2021-07-26 15:30:19 +05:00
Yury Shkoda
fb7a0f43e1 test(session): added test to cover 304 response 2021-07-26 12:17:19 +03:00
Yury Shkoda
6c901f1c21 chore(session): change log level from error to info 2021-07-26 10:40:15 +03:00
26f008d527 chore: remove console log statement 2021-07-26 11:09:31 +05:00
56ebc7be3b chore: merge master into issue-477 2021-07-26 11:06:13 +05:00
Allan Bowe
0ea66f6d37 Merge pull request #494 from sasjs/fix-browser-issue
fix(browser): only import file I/O functions when running in Node.js environments
2021-07-25 10:00:51 +03:00
Allan Bowe
cb30ed2b98 Merge branch 'master' into contributors 2021-07-24 23:14:16 +03:00
Allan Bowe
dfbe2d8f94 chore: contributors 2021-07-24 21:31:51 +03:00
Krishna Acondy
eac9da22bf chore(test): fix assertion 2021-07-24 10:27:31 +01:00
Krishna Acondy
626fc2e15f fix(path): make log file path platform-agnostic 2021-07-24 09:53:39 +01:00
Krishna Acondy
87e2edbd6c chore(test): fix long poll count 2021-07-24 00:12:11 +01:00
Krishna Acondy
7cf681bea3 chore(tests): fix tests 2021-07-23 22:24:48 +01:00
Krishna Acondy
281a145bef fix(node): only create and write file stream if running in node 2021-07-23 22:24:41 +01:00
Krishna Acondy
15d5f9ec91 chore(paths): fix import paths 2021-07-23 22:24:21 +01:00
Krishna Acondy
0a6c5a0ec4 fix(fs): replace fs imports with locally defined WriteStream interface 2021-07-23 22:24:04 +01:00
Krishna Acondy
2a9526d056 fix(node): add util to check if running in node 2021-07-23 22:23:05 +01:00
Allan Bowe
c2ff28c323 Update PULL_REQUEST_TEMPLATE.md 2021-07-23 13:04:38 +03:00
Yury Shkoda
fbaa2327c6 fix(session): remove retry limit if could not get state 2021-07-23 12:44:34 +03:00
Allan Bowe
50710ee1df Merge pull request #476 from sasjs/issue-170
fix: file upload with debug enabled
2021-07-23 11:41:06 +03:00
Krishna Acondy
062ba91c17 Merge pull request #486 from sasjs/fix-poll-logic
fix(poll): add default poll options
2021-07-22 14:53:03 +01:00
6dd1d47bb2 fix: merge main into issue-477 and fixed conflicts 2021-07-22 16:13:46 +05:00
e70a9645ef fix: remove jwtDecode import statement 2021-07-22 15:56:22 +05:00
aeabc29e55 fix: remove serverurl argument from createFolder method and move decode token to utils project 2021-07-22 15:47:37 +05:00
Krishna Acondy
9600fa2512 fix(poll): add default poll options 2021-07-22 11:31:10 +01:00
Krishna Acondy
7951817480 Merge pull request #485 from sasjs/log-file-paths
fix(stream-log): use filepath if provided
2021-07-22 09:57:31 +01:00
Krishna Acondy
405eea1d6c chore(infra): set minimum node version to 15 2021-07-22 09:41:30 +01:00
Krishna Acondy
e3f189eed4 chore(test): fix test 2021-07-22 09:31:32 +01:00
Krishna Acondy
0bb42c5e3c fix(streamlog): use filepath if provided 2021-07-22 09:25:55 +01:00
Allan Bowe
c02eac196e Merge pull request #483 from sasjs/all-contributors/add-medjedovicm
docs: add medjedovicm as a contributor for code
2021-07-21 18:55:43 +03:00
Allan Bowe
3fb0d863e9 Update README.md 2021-07-21 18:55:01 +03:00
allcontributors[bot]
6d573d3897 docs: create .all-contributorsrc [skip ci] 2021-07-21 15:53:39 +00:00
allcontributors[bot]
33280d7a5b docs: update README.md [skip ci] 2021-07-21 15:53:38 +00:00
Allan Bowe
507722da0d Merge pull request #465 from sasjs/stream-job-logs
feat(stream-logs): Save logs to file during job status poll
2021-07-21 18:49:50 +03:00
Krishna Acondy
c8e029cff4 chore(deps): bump utils 2021-07-21 08:37:45 +01:00
Krishna Acondy
7bd2e31f3b chore(cleanup): remove console logs 2021-07-21 08:13:45 +01:00
Krishna Acondy
cfa0c8b9af chore(refactor): only fetch job if streaming logs, fix tests, add JSDoc comments 2021-07-21 08:12:34 +01:00
Krishna Acondy
df9c1c643f chore(merge): pull in changes from master 2021-07-20 09:26:34 +01:00
Krishna Acondy
5c8d311ae8 chore(streamlog): optimise polling mechanism 2021-07-20 09:25:39 +01:00
e1a76bc45a fix: update error message when folder not found 2021-07-19 21:53:58 +05:00
85e5ade93a fix: handle the case when array is passed in getValidJson method 2021-07-19 13:01:18 +05:00
4a61fb8f7f chore: update variable name from config to ovverrideSasjsConfig 2021-07-19 13:00:06 +05:00
5347aeba09 fix: replace isValidJson with getValidJson 2021-07-18 23:24:22 +05:00
Sabir Hassan
7ac7c5e52b Merge branch 'master' into issue-170 2021-07-18 21:56:33 +05:00
5098342dfe fix: retrieve content from the iframe in first response when viya Web approach used with debug enabled 2021-07-18 21:39:57 +05:00
c69be8ffc3 fix: move parseSasViyaDebugResponse method to utils folder 2021-07-18 21:37:08 +05:00
69999d8e8b fix: update fileUpload method to override existing config 2021-07-18 21:34:16 +05:00
Muhammad Saad
bec4180dcf Merge pull request #467 from sasjs/removed-url-package
fix: removed url package
2021-07-16 17:02:24 +05:00
Saad Jutt
1bb7807c25 chore(merge): Merge branch 'master' into removed-url-package 2021-07-16 04:12:20 +05:00
Allan Bowe
816f1d19d4 Merge pull request #471 from sasjs/windows-tests
Windows tests
2021-07-15 16:22:49 +03:00
d38d032309 chore: readme updates 2021-07-15 13:01:12 +02:00
d2a90c77fd chore: readme update 2021-07-15 10:57:44 +02:00
8a0f14b780 chore: windows fallback 2021-07-15 10:43:30 +02:00
f6cb2c4fac chore: sasjs-tests windows 2021-07-15 10:41:10 +02:00
Krishna Acondy
1594f0c7db chore(merge): pull in changes from master 2021-07-15 07:33:44 +01:00
Allan Bowe
7cb2a43f95 Merge pull request #462 from sasjs/json-fix
fix: invalid json checking
2021-07-14 20:46:16 +03:00
Allan Bowe
6e85c7a588 Merge branch 'master' into json-fix 2021-07-14 20:44:06 +03:00
Allan Bowe
a68f6962fd Merge pull request #450 from sasjs/allanbowe-patch-1
Update README.md
2021-07-14 20:42:09 +03:00
Allan Bowe
a650ba15dd Merge branch 'master' into allanbowe-patch-1 2021-07-14 20:41:51 +03:00
Allan Bowe
6ca1b489fc Merge pull request #466 from sasjs/session-state-fix
fix(session): provide more info if could not get session state
2021-07-14 20:41:25 +03:00
Yury Shkoda
a5c9f11c75 test(session): cover case when could not get session state 2021-07-14 14:17:20 +03:00
Krishna Acondy
1ff3937d11 chore(deps): update dependencies 2021-07-14 08:03:54 +01:00
Krishna Acondy
d4725d2e54 chore(refactor): change property name in PollOptions 2021-07-14 07:50:25 +01:00
Saad Jutt
db578564ba fix: removed url package 2021-07-13 17:11:49 +05:00
Yury Shkoda
d4ebef4290 fix(session): provide more info if could not get session state 2021-07-13 14:50:46 +03:00
Krishna Acondy
b9f368193d chore(refactor): add more tests 2021-07-13 08:12:15 +01:00
Krishna Acondy
4257ec78aa chore(ci): add coverage report to build workflow 2021-07-12 20:45:09 +01:00
Krishna Acondy
a0fbe1a740 chore(ci): add coverage report action 2021-07-12 20:42:49 +01:00
Krishna Acondy
123b9fb535 chore(refactor): split up and add tests for core functionality 2021-07-12 20:31:17 +01:00
Krishna Acondy
f57c7b8f7d chore(deps): up utils version 2021-07-12 20:30:42 +01:00
89590f9a37 chore: only removed 2021-07-12 14:42:19 +02:00
5d61bebc9e fix: isValidJson function returns the JSON parsed 2021-07-12 14:29:43 +02:00
99afa6e7e4 style: lint 2021-07-12 12:58:27 +02:00
b590a9f41b chore(tests): testing the isValidJson function 2021-07-12 12:58:04 +02:00
4466ee30d2 chore: preventing double parse of invalid json check 2021-07-12 11:02:01 +02:00
db372950b4 chore: type fix 2021-07-09 15:17:33 +02:00
46f5e07f11 fix: invalid json checking 2021-07-09 13:36:12 +02:00
Krishna Acondy
1c90f4f455 chore(*): remove log 2021-07-09 09:29:57 +01:00
Krishna Acondy
0114a80e38 chore(execute): add tests for executeScript 2021-07-09 09:17:49 +01:00
Krishna Acondy
13be2f9c70 chore(*): remove unused dependencies and variables, fix imports 2021-07-09 09:17:26 +01:00
Krishna Acondy
e396091aa7 chore(merge): pull in changes from master 2021-07-08 09:04:49 +01:00
Krishna Acondy
a00cb1ebec Merge pull request #461 from sasjs/fix-utils-imports
fix(imports): change imports from main barrel into internal barrels
2021-07-08 08:58:14 +01:00
Krishna Acondy
7b1264d140 fix(imports): change imports from main barrel into internal barrels 2021-07-08 08:46:28 +01:00
Krishna Acondy
04ccbf6843 feat(log): write logs to file when polling for job status 2021-07-07 10:02:14 +01:00
Allan Bowe
369b9fb023 Merge branch 'master' into allanbowe-patch-1 2021-07-05 12:02:01 +03:00
Allan Bowe
76487b00e9 Merge pull request #453 from sasjs/support-contribute-on-windows
fix: updated 'prepare' + using copyfiles instead of cp
2021-07-05 12:01:38 +03:00
Saad Jutt
2d0515e25b chore(merge): Merged master branch 2021-07-05 13:17:08 +05:00
Saad Jutt
b132b99586 fix: globstars support on mac with copyfiles 2021-07-05 13:11:40 +05:00
Saad Jutt
6cac008b61 fix: updated 'prepare' + using copyfiles instead of cp 2021-07-05 04:06:04 +05:00
Allan Bowe
929ec6eb1c Update README.md 2021-07-02 12:50:03 +03:00
176 changed files with 36857 additions and 27362 deletions

103
.all-contributorsrc Normal file
View File

@@ -0,0 +1,103 @@
{
"projectName": "adapter",
"projectOwner": "sasjs",
"repoType": "github",
"repoHost": "https://github.com",
"files": [
"README.md"
],
"imageSize": 100,
"commit": false,
"commitConvention": "angular",
"contributors": [
{
"login": "krishna-acondy",
"name": "Krishna Acondy",
"avatar_url": "https://avatars.githubusercontent.com/u/2980428?v=4",
"profile": "https://krishna-acondy.io/",
"contributions": [
"code",
"infra",
"blog",
"content",
"ideas",
"video"
]
},
{
"login": "YuryShkoda",
"name": "Yury Shkoda",
"avatar_url": "https://avatars.githubusercontent.com/u/25773492?v=4",
"profile": "https://www.erudicat.com/",
"contributions": [
"code",
"infra",
"ideas",
"test",
"video"
]
},
{
"login": "medjedovicm",
"name": "Mihajlo Medjedovic",
"avatar_url": "https://avatars.githubusercontent.com/u/18329105?v=4",
"profile": "https://github.com/medjedovicm",
"contributions": [
"code",
"infra",
"test",
"review"
]
},
{
"login": "allanbowe",
"name": "Allan Bowe",
"avatar_url": "https://avatars.githubusercontent.com/u/4420615?v=4",
"profile": "https://github.com/allanbowe",
"contributions": [
"code",
"review",
"test",
"mentoring",
"maintenance"
]
},
{
"login": "saadjutt01",
"name": "Muhammad Saad ",
"avatar_url": "https://avatars.githubusercontent.com/u/8914650?v=4",
"profile": "https://github.com/saadjutt01",
"contributions": [
"code",
"review",
"test",
"mentoring",
"infra"
]
},
{
"login": "sabhas",
"name": "Sabir Hassan",
"avatar_url": "https://avatars.githubusercontent.com/u/82647447?v=4",
"profile": "https://github.com/sabhas",
"contributions": [
"code",
"review",
"test",
"ideas"
]
},
{
"login": "VladislavParhomchik",
"name": "VladislavParhomchik",
"avatar_url": "https://avatars.githubusercontent.com/u/83717836?v=4",
"profile": "https://github.com/VladislavParhomchik",
"contributions": [
"test",
"review"
]
}
],
"contributorsPerLine": 7,
"skipCi": true
}

View File

@@ -6,7 +6,7 @@ GREEN="\033[1;32m"
# temporary file which holds the message). # temporary file which holds the message).
commit_message=$(cat "$1") commit_message=$(cat "$1")
if (echo "$commit_message" | grep -Eq "^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\([a-z0-9 \-\*]+\))?!?: .+$") then if (echo "$commit_message" | grep -Eq "^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\([a-z0-9 -\*]+\))?!?: .+$") then
echo "${GREEN} ✔ Commit message meets Conventional Commit standards" echo "${GREEN} ✔ Commit message meets Conventional Commit standards"
exit 0 exit 0
fi fi

View File

@@ -1,7 +1,7 @@
version: 2 version: 2
updates: updates:
- package-ecosystem: npm - package-ecosystem: npm
directory: "/" directory: '/'
schedule: schedule:
interval: daily interval: monthly
open-pull-requests-limit: 10 open-pull-requests-limit: 10

View File

@@ -13,20 +13,25 @@ jobs:
strategy: strategy:
matrix: matrix:
node-version: [12.x] node-version: [lts/fermium]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }} - name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1 uses: actions/setup-node@v2
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
cache: npm
- name: Install Dependencies - name: Install Dependencies
run: npm ci run: npm ci
- name: Check code style - name: Check code style
run: npm run lint run: npm run lint
- name: Run unit tests - name: Run unit tests
run: npm test run: npm test
- name: Generate coverage report
uses: artiomtr/jest-coverage-report-action@v2.0-rc.2
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Build Package - name: Build Package
run: npm run package:lib run: npm run package:lib
env: env:

2
.gitignore vendored
View File

@@ -5,4 +5,4 @@ build
/coverage /coverage
.DS_Store .DS_Store

View File

@@ -1,4 +1,6 @@
sasjs-tests/ sasjs-tests/
docs/ docs/
.github/ .github/
CONTRIBUTING.md *.md
*.spec.ts
.all-contributorsrc

View File

@@ -12,9 +12,9 @@ What code changes have been made to achieve the intent.
## Checks ## Checks
No PR (that involves a non-trivial code change) should be merged, unless all four of the items below are confirmed! If an urgent fix is needed - use a tar file. No PR (that involves a non-trivial code change) should be merged, unless all items below are confirmed! If an urgent fix is needed - use a tar file.
- [ ] Code is formatted correctly (`npm run lint:fix`).
- [ ] All unit tests are passing (`npm test`).
- [ ] All `sasjs-cli` unit tests are passing (`npm test`). - [ ] All `sasjs-cli` unit tests are passing (`npm test`).
- [ ] All `sasjs-tests` are passing (instructions available [here](https://github.com/sasjs/adapter/blob/master/sasjs-tests/README.md)). - [ ] All `sasjs-tests` are passing (instructions available [here](https://github.com/sasjs/adapter/blob/master/sasjs-tests/README.md)).
- [ ] [Data Controller](https://datacontroller.io) builds and is functional on both SAS 9 and Viya

View File

@@ -36,7 +36,7 @@ Ok ok. Deploy this [example.html](https://raw.githubusercontent.com/sasjs/adapte
The backend part can be deployed as follows: The backend part can be deployed as follows:
``` ```sas
%let appLoc=/Public/app/readme; /* Metadata or Viya Folder per SASjs config */ %let appLoc=/Public/app/readme; /* Metadata or Viya Folder per SASjs config */
filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas"; filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
%inc mc; /* compile macros (can also be downloaded & compiled seperately) */ %inc mc; /* compile macros (can also be downloaded & compiled seperately) */
@@ -85,11 +85,11 @@ let sasJs = new SASjs.default(
); );
``` ```
If you've installed it via NPM, you can import it as a default import like so: If you've installed it via NPM, you can import it as a default import like so:
``` ```js
import SASjs from '@sasjs/adapter'; import SASjs from '@sasjs/adapter';
``` ```
You can then instantiate it with: You can then instantiate it with:
``` ```js
const sasJs = new SASjs({your config}) const sasJs = new SASjs({your config})
``` ```
@@ -119,6 +119,7 @@ sasJs.request("/path/to/my/service", dataObject)
console.log(response.tablewith2cols1row[0].COL1.value) 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: 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 ```javascript
@@ -146,6 +147,7 @@ The adapter will also cache the logs (if debug enabled) and even the work tables
The SAS side is handled by a number of macros in the [macro core](https://github.com/sasjs/core) library. 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: The following snippet shows the process of SAS tables arriving / leaving:
```sas ```sas
/* fetch all input tables sent from frontend - they arrive as work tables */ /* fetch all input tables sent from frontend - they arrive as work tables */
%webout(FETCH) %webout(FETCH)
@@ -161,7 +163,6 @@ run;
%webout(OBJ,tables,fmt=N) /* unformatted (raw) data */ %webout(OBJ,tables,fmt=N) /* unformatted (raw) data */
%webout(OBJ,tables,label=newtable) /* rename tables on export */ %webout(OBJ,tables,label=newtable) /* rename tables on export */
%webout(CLOSE) /* close the JSON and send some extra useful variables too */ %webout(CLOSE) /* close the JSON and send some extra useful variables too */
``` ```
## Configuration ## Configuration
@@ -172,7 +173,8 @@ Configuration on the client side involves passing an object on startup, which ca
* `serverType` - either `SAS9` or `SASVIYA`. * `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. * `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. * `debug` - if `true` then SAS Logs and extra debug information is returned.
* `useComputeApi` - Only relevant when the serverType is `SASVIYA`. If `true` the [Compute API](#using-the-compute-api) is used. If `false` the [JES API](#using-the-jes-api) is used. If `null` or `undefined` the [Web](#using-jes-web-app) approach is used. * `LoginMechanism` - either `Default` or `Redirected`. If `Redirected` then authentication occurs through the injection of an additional screen, which contains the SASLogon prompt. This allows for more complex authentication flows (such as 2FA) and avoids the need to handle passwords in the application itself. The styling of the redirect flow can also be modified. If left at "Default" then the developer must capture the username and password and use these with the `.login()` method.
* `useComputeApi` - Only relevant when the serverType is `SASVIYA`. If `true` the [Compute API](#using-the-compute-api) is used. If `false` the [JES API](#using-the-jes-api) is used. If `null` or `undefined` the [Web](#using-jes-web-app) approach is used.
* `contextName` - Compute context on which the requests will be called. If missing or not provided, defaults to `Job Execution Compute context`. * `contextName` - Compute context on which the requests will be called. If missing or not provided, defaults to `Job Execution Compute context`.
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). 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).
@@ -194,7 +196,9 @@ Note - to use the web approach, the `useComputeApi` property must be `undefined`
### Using the JES API ### 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. Depending on your network bandwidth, it may or may not be faster than the JES Web approach. 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. Depending on your network bandwidth, it may or may not be faster than the JES Web approach.
``` This approach (`useComputeApi: false`) also ensures that jobs are displayed in Environment Manager.
```json
{ {
appLoc:"/Your/Path", appLoc:"/Your/Path",
serverType:"SASVIYA", serverType:"SASVIYA",
@@ -206,12 +210,14 @@ Here we are running Jobs using the Job Execution Service except this time we are
### Using the Compute API ### 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. This manager will spawn a additional session every time a request is made. Subsequent requests will use the existing 'hot' session, if it exists. Sessions are always 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. 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. This manager will spawn a additional session every time a request is made. Subsequent requests will use the existing 'hot' session, if it exists. Sessions are always 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.
``` With this approach (`useComputeApi: true`), the requests/logs will _not_ appear in the list in Environment manager.
```json
{ {
appLoc:"/Your/Path", appLoc:"/Your/Path",
serverType:"SASVIYA", serverType:"SASVIYA",
useComputeApi: true, useComputeApi: true,
contextName: 'yourComputeContext' contextName: "yourComputeContext"
} }
``` ```
@@ -230,3 +236,32 @@ If you are a SAS 9 or SAS Viya customer you can also request a copy of [Data Con
If you find this library useful, help us grow our star graph! If you find this library useful, help us grow our star graph!
![](https://starchart.cc/sasjs/adapter.svg) ![](https://starchart.cc/sasjs/adapter.svg)
## Contributors ✨
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-7-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END -->
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<table>
<tr>
<td align="center"><a href="https://krishna-acondy.io/"><img src="https://avatars.githubusercontent.com/u/2980428?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Krishna Acondy</b></sub></a><br /><a href="https://github.com/sasjs/adapter/commits?author=krishna-acondy" title="Code">💻</a> <a href="#infra-krishna-acondy" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#blog-krishna-acondy" title="Blogposts">📝</a> <a href="#content-krishna-acondy" title="Content">🖋</a> <a href="#ideas-krishna-acondy" title="Ideas, Planning, & Feedback">🤔</a> <a href="#video-krishna-acondy" title="Videos">📹</a></td>
<td align="center"><a href="https://www.erudicat.com/"><img src="https://avatars.githubusercontent.com/u/25773492?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Yury Shkoda</b></sub></a><br /><a href="https://github.com/sasjs/adapter/commits?author=YuryShkoda" title="Code">💻</a> <a href="#infra-YuryShkoda" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#ideas-YuryShkoda" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/sasjs/adapter/commits?author=YuryShkoda" title="Tests">⚠️</a> <a href="#video-YuryShkoda" title="Videos">📹</a></td>
<td align="center"><a href="https://github.com/medjedovicm"><img src="https://avatars.githubusercontent.com/u/18329105?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mihajlo Medjedovic</b></sub></a><br /><a href="https://github.com/sasjs/adapter/commits?author=medjedovicm" title="Code">💻</a> <a href="#infra-medjedovicm" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="https://github.com/sasjs/adapter/commits?author=medjedovicm" title="Tests">⚠️</a> <a href="https://github.com/sasjs/adapter/pulls?q=is%3Apr+reviewed-by%3Amedjedovicm" title="Reviewed Pull Requests">👀</a></td>
<td align="center"><a href="https://github.com/allanbowe"><img src="https://avatars.githubusercontent.com/u/4420615?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Allan Bowe</b></sub></a><br /><a href="https://github.com/sasjs/adapter/commits?author=allanbowe" title="Code">💻</a> <a href="https://github.com/sasjs/adapter/pulls?q=is%3Apr+reviewed-by%3Aallanbowe" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/sasjs/adapter/commits?author=allanbowe" title="Tests">⚠️</a> <a href="#mentoring-allanbowe" title="Mentoring">🧑‍🏫</a> <a href="#maintenance-allanbowe" title="Maintenance">🚧</a></td>
<td align="center"><a href="https://github.com/saadjutt01"><img src="https://avatars.githubusercontent.com/u/8914650?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Muhammad Saad </b></sub></a><br /><a href="https://github.com/sasjs/adapter/commits?author=saadjutt01" title="Code">💻</a> <a href="https://github.com/sasjs/adapter/pulls?q=is%3Apr+reviewed-by%3Asaadjutt01" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/sasjs/adapter/commits?author=saadjutt01" title="Tests">⚠️</a> <a href="#mentoring-saadjutt01" title="Mentoring">🧑‍🏫</a> <a href="#infra-saadjutt01" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
<td align="center"><a href="https://github.com/sabhas"><img src="https://avatars.githubusercontent.com/u/82647447?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sabir Hassan</b></sub></a><br /><a href="https://github.com/sasjs/adapter/commits?author=sabhas" title="Code">💻</a> <a href="https://github.com/sasjs/adapter/pulls?q=is%3Apr+reviewed-by%3Asabhas" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/sasjs/adapter/commits?author=sabhas" title="Tests">⚠️</a> <a href="#ideas-sabhas" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="https://github.com/VladislavParhomchik"><img src="https://avatars.githubusercontent.com/u/83717836?v=4?s=100" width="100px;" alt=""/><br /><sub><b>VladislavParhomchik</b></sub></a><br /><a href="https://github.com/sasjs/adapter/commits?author=VladislavParhomchik" title="Tests">⚠️</a> <a href="https://github.com/sasjs/adapter/pulls?q=is%3Apr+reviewed-by%3AVladislavParhomchik" title="Reviewed Pull Requests">👀</a></td>
</tr>
</table>
<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!

16
checkNodeVersion.js Normal file
View File

@@ -0,0 +1,16 @@
const result = process.versions
if (result && result.node) {
if (parseInt(result.node) < 14) {
console.log(
'\x1b[31m%s\x1b[0m',
`❌ Process failed due to Node Version,\nPlease install and use Node Version >= 14\nYour current Node Version is: ${result.node}`
)
process.exit(1)
}
} else {
console.log(
'\x1b[31m%s\x1b[0m',
'Something went wrong while checking Node version'
)
process.exit(1)
}

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

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

455
docs/modules/api_viya.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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

251
docs/modules/test.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

15420
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,8 +3,10 @@
"description": "JavaScript adapter for SAS", "description": "JavaScript adapter for SAS",
"homepage": "https://adapter.sasjs.io", "homepage": "https://adapter.sasjs.io",
"scripts": { "scripts": {
"build": "rimraf build && rimraf node && mkdir node && cp -r src/* node && webpack && rimraf build/src && rimraf node", "preinstall": "node checkNodeVersion",
"package:lib": "npm run build && cp ./package.json build && cd build && npm version \"5.0.0\" && npm pack", "prebuild": "node checkNodeVersion",
"build": "rimraf build && rimraf node && mkdir node && copyfiles -u 1 \"./src/**/*\" ./node && webpack && rimraf build/src && rimraf node",
"package:lib": "npm run build && copyfiles ./package.json ./checkNodeVersion.js build && cd build && npm version \"5.0.0\" && npm pack",
"publish:lib": "npm run build && cd build && npm publish", "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}\" && npx prettier --write \"sasjs-tests/src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}\"", "lint:fix": "npx prettier --write \"src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}\" && npx prettier --write \"sasjs-tests/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}\" && npx prettier --check \"sasjs-tests/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}\" && npx prettier --check \"sasjs-tests/src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}\"",
@@ -13,7 +15,7 @@
"postpublish": "git clean -fd", "postpublish": "git clean -fd",
"semantic-release": "semantic-release", "semantic-release": "semantic-release",
"typedoc": "typedoc", "typedoc": "typedoc",
"prepare": "[ -d .git ] && git config core.hooksPath ./.git-hooks || true" "prepare": "git rev-parse --git-dir && git config core.hooksPath ./.git-hooks && git config core.autocrlf false || true"
}, },
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
@@ -38,39 +40,44 @@
}, },
"license": "ISC", "license": "ISC",
"devDependencies": { "devDependencies": {
"@types/jest": "^26.0.23", "@types/axios": "^0.14.0",
"@types/express": "^4.17.13",
"@types/form-data": "^2.5.0",
"@types/jest": "^27.0.2",
"@types/mime": "^2.0.3", "@types/mime": "^2.0.3",
"@types/tough-cookie": "^4.0.0", "@types/pem": "^1.9.6",
"@types/tough-cookie": "^4.0.1",
"copyfiles": "^2.4.1",
"cp": "^0.2.0", "cp": "^0.2.0",
"dotenv": "^10.0.0", "dotenv": "^10.0.0",
"jest": "^27.0.6", "express": "^4.17.1",
"jest": "^27.2.0",
"jest-extended": "^0.11.5", "jest-extended": "^0.11.5",
"mime": "^2.5.2",
"node-polyfill-webpack-plugin": "^1.1.4", "node-polyfill-webpack-plugin": "^1.1.4",
"path": "^0.12.7", "path": "^0.12.7",
"pem": "^1.14.4",
"process": "^0.11.10", "process": "^0.11.10",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"semantic-release": "^17.4.4", "semantic-release": "^18.0.0",
"terser-webpack-plugin": "^5.1.4", "terser-webpack-plugin": "^5.2.4",
"ts-jest": "^27.0.3", "ts-jest": "^27.0.3",
"ts-loader": "^9.2.2", "ts-loader": "^9.2.6",
"tslint": "^6.1.3", "tslint": "^6.1.3",
"tslint-config-prettier": "^1.18.0", "tslint-config-prettier": "^1.18.0",
"typedoc": "^0.21.2", "typedoc": "0.19.2",
"typedoc-neo-theme": "^1.1.1", "typedoc-neo-theme": "^1.1.1",
"typedoc-plugin-external-module-name": "^4.0.6", "typedoc-plugin-external-module-name": "^4.0.6",
"typescript": "^4.3.4", "typescript": "4.3.5",
"webpack": "^5.41.1", "webpack": "^5.56.0",
"webpack-cli": "^4.7.2" "webpack-cli": "^4.7.2"
}, },
"main": "index.js", "main": "index.js",
"dependencies": { "dependencies": {
"@sasjs/utils": "^2.22.0", "@sasjs/utils": "^2.32.0",
"axios": "^0.21.1", "axios": "^0.21.4",
"axios-cookiejar-support": "^1.0.1", "axios-cookiejar-support": "^1.0.1",
"form-data": "^4.0.0", "form-data": "^4.0.0",
"https": "^1.0.0", "https": "^1.0.0",
"tough-cookie": "^4.0.0", "tough-cookie": "^4.0.0"
"url": "^0.11.0"
} }
} }

View File

@@ -41,6 +41,19 @@ So you can run the script like so:
SSH_ACCOUNT=me@my-sas-server.com DEPLOY_PATH=/var/www/html/my-folder/sasjs-tests npm run deploy SSH_ACCOUNT=me@my-sas-server.com DEPLOY_PATH=/var/www/html/my-folder/sasjs-tests npm run deploy
``` ```
If you are on `WINDOWS`, you will first need to install one dependency:
```bash
npm i -g copyfiles
```
and then run to build:
```bash
npm run update:adapter && npm run build
```
when it finishes run to deploy:
```bash
scp -rp ./build/* me@my-sas-server.com:/var/www/html/my-folder/sasjs-tests
```
If you'd like to deploy just `sasjs-tests` without changing the adapter version, you can use the `deploy:tests` script, while also setting the same environment variables as above. If you'd like to deploy just `sasjs-tests` without changing the adapter version, you can use the `deploy:tests` script, while also setting the same environment variables as above.
## 3. Creating the required SAS services ## 3. Creating the required SAS services

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@
"private": true, "private": true,
"dependencies": { "dependencies": {
"@sasjs/adapter": "file:../build/sasjs-adapter-5.0.0.tgz", "@sasjs/adapter": "file:../build/sasjs-adapter-5.0.0.tgz",
"@sasjs/test-framework": "^1.4.0", "@sasjs/test-framework": "^1.4.2",
"@types/jest": "^26.0.20", "@types/jest": "^26.0.20",
"@types/node": "^14.14.41", "@types/node": "^14.14.41",
"@types/react": "^17.0.1", "@types/react": "^17.0.1",
@@ -23,7 +23,8 @@
"test": "react-scripts test", "test": "react-scripts test",
"eject": "react-scripts eject", "eject": "react-scripts eject",
"update:adapter": "cd .. && npm run package:lib && cd sasjs-tests && npm i ../build/sasjs-adapter-5.0.0.tgz", "update:adapter": "cd .. && npm run package:lib && cd sasjs-tests && npm i ../build/sasjs-adapter-5.0.0.tgz",
"deploy:tests": "rsync -avhe ssh ./build/* --delete $SSH_ACCOUNT:$DEPLOY_PATH", "deploy:tests": "rsync -avhe ssh ./build/* --delete $SSH_ACCOUNT:$DEPLOY_PATH || npm run deploy:tests-win",
"deploy:tests-win": "scp %DEPLOY_PATH% ./build/*",
"deploy": "npm run update:adapter && npm run build && npm run deploy:tests" "deploy": "npm run update:adapter && npm run build && npm run deploy:tests"
}, },
"eslintConfig": { "eslintConfig": {

View File

@@ -1,4 +1,4 @@
import SASjs, { SASjsConfig } from '@sasjs/adapter' import SASjs, { LoginMechanism, SASjsConfig } from '@sasjs/adapter'
import { TestSuite } from '@sasjs/test-framework' import { TestSuite } from '@sasjs/test-framework'
import { ServerType } from '@sasjs/utils/types' import { ServerType } from '@sasjs/utils/types'
@@ -13,7 +13,7 @@ const defaultConfig: SASjsConfig = {
debug: false, debug: false,
contextName: 'SAS Job Execution compute context', contextName: 'SAS Job Execution compute context',
useComputeApi: false, useComputeApi: false,
allowInsecureRequests: false loginMechanism: LoginMechanism.Default
} }
const customConfig = { const customConfig = {
@@ -41,6 +41,19 @@ export const basicTests = (
assertion: (response: any) => assertion: (response: any) =>
response && response.isLoggedIn && response.userName === userName response && response.isLoggedIn && response.userName === userName
}, },
{
title: 'Fetch username for already logged in user',
description: 'Should log the user in',
test: async () => {
await adapter.logIn(userName, password)
const newAdapterIns = new SASjs(adapter.getSasjsConfig())
return await newAdapterIns.checkSession()
},
assertion: (response: any) =>
response?.isLoggedIn && response?.userName === userName
},
{ {
title: 'Multiple Log in attempts', title: 'Multiple Log in attempts',
description: description:
@@ -48,7 +61,7 @@ export const basicTests = (
test: async () => { test: async () => {
await adapter.logOut() await adapter.logOut()
await adapter.logIn('invalid', 'invalid') await adapter.logIn('invalid', 'invalid')
return adapter.logIn(userName, password) return await adapter.logIn(userName, password)
}, },
assertion: (response: any) => assertion: (response: any) =>
response && response.isLoggedIn && response.userName === userName response && response.isLoggedIn && response.userName === userName
@@ -64,8 +77,8 @@ export const basicTests = (
'common/sendArr', 'common/sendArr',
stringData, stringData,
undefined, undefined,
() => { async () => {
adapter.logIn(userName, password) await adapter.logIn(userName, password)
} }
) )
}, },
@@ -151,7 +164,7 @@ export const basicTests = (
description: description:
'Should complete successful request with extra attributes present in response', 'Should complete successful request with extra attributes present in response',
test: async () => { test: async () => {
const config = { const config: Partial<SASjsConfig> = {
useComputeApi: false useComputeApi: false
} }

View File

@@ -1,86 +0,0 @@
import { isUrl } from './utils'
import { UploadFile } from './types/UploadFile'
import { ErrorResponse, LoginRequiredError } from './types/errors'
import { RequestClient } from './request/RequestClient'
import { ServerType } from '@sasjs/utils/types'
import SASjs from './SASjs'
import { Server } from 'https'
import { SASjsConfig } from './types'
import { config } from 'process'
export class FileUploader {
constructor(
private sasjsConfig: SASjsConfig,
private jobsPath: string,
private requestClient: RequestClient
) {
if (this.sasjsConfig.serverUrl) isUrl(this.sasjsConfig.serverUrl)
}
public uploadFile(sasJob: string, files: UploadFile[], params: any) {
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 = ''
for (let param in params) {
if (params.hasOwnProperty(param)) {
paramsString += `&${param}=${params[param]}`
}
}
const program = this.sasjsConfig.appLoc
? this.sasjsConfig.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)
if (this.sasjsConfig.debug) formData.append('_debug', '131')
if (
this.sasjsConfig.serverType === ServerType.SasViya &&
this.sasjsConfig.contextName
)
formData.append('_contextname', this.sasjsConfig.contextName)
const headers = {
'cache-control': 'no-cache',
Accept: '*/*',
'Content-Type': 'text/plain'
}
return this.requestClient
.post(uploadUrl, formData, undefined, 'application/json', headers)
.then((res) => {
let result
result =
typeof res.result === 'string' ? JSON.parse(res.result) : res.result
return result
//TODO: append to SASjs requests
})
.catch((err: Error) => {
if (err instanceof LoginRequiredError) {
return Promise.reject(
new ErrorResponse('You must be logged in to upload a file.', err)
)
}
return Promise.reject(
new ErrorResponse('File upload request failed.', err)
)
})
}
}

View File

@@ -1,3 +1,4 @@
import * as https from 'https'
import { generateTimestamp } from '@sasjs/utils/time' import { generateTimestamp } from '@sasjs/utils/time'
import * as NodeFormData from 'form-data' import * as NodeFormData from 'form-data'
import { Sas9RequestClient } from './request/Sas9RequestClient' import { Sas9RequestClient } from './request/Sas9RequestClient'
@@ -10,9 +11,13 @@ import { isUrl } from './utils'
export class SAS9ApiClient { export class SAS9ApiClient {
private requestClient: Sas9RequestClient private requestClient: Sas9RequestClient
constructor(private serverUrl: string, private jobsPath: string) { constructor(
private serverUrl: string,
private jobsPath: string,
httpsAgentOptions?: https.AgentOptions
) {
if (serverUrl) isUrl(serverUrl) if (serverUrl) isUrl(serverUrl)
this.requestClient = new Sas9RequestClient(serverUrl, false) this.requestClient = new Sas9RequestClient(serverUrl, httpsAgentOptions)
} }
/** /**

View File

@@ -0,0 +1,51 @@
import { Logger, LogLevel } from '@sasjs/utils/logger'
import { RequestClient } from './request/RequestClient'
import { SASViyaApiClient } from './SASViyaApiClient'
import { Folder } from './types'
import { RootFolderNotFoundError } from './types/errors'
const mockFolder: Folder = {
id: '1',
uri: '/folder',
links: [],
memberCount: 1
}
const requestClient = new (<jest.Mock<RequestClient>>RequestClient)()
const sasViyaApiClient = new SASViyaApiClient(
'https://test.com',
'/test',
'test context',
requestClient
)
describe('SASViyaApiClient', () => {
beforeEach(() => {
;(process as any).logger = new Logger(LogLevel.Off)
setupMocks()
})
it('should throw an error when the root folder is not found on the server', async () => {
jest
.spyOn(requestClient, 'get')
.mockImplementation(() => Promise.reject('Not Found'))
const error = await sasViyaApiClient
.createFolder('test', '/foo')
.catch((e) => e)
expect(error).toBeInstanceOf(RootFolderNotFoundError)
})
})
const setupMocks = () => {
jest
.spyOn(requestClient, 'get')
.mockImplementation(() =>
Promise.resolve({ result: mockFolder, etag: '', status: 200 })
)
jest
.spyOn(requestClient, 'post')
.mockImplementation(() =>
Promise.resolve({ result: mockFolder, etag: '', status: 200 })
)
}

View File

@@ -1,10 +1,4 @@
import { import { isRelativePath, isUri, isUrl } from './utils'
convertToCSV,
isRelativePath,
isUri,
isUrl,
fetchLogByChunks
} from './utils'
import * as NodeFormData from 'form-data' import * as NodeFormData from 'form-data'
import { import {
Job, Job,
@@ -17,28 +11,19 @@ import {
JobDefinition, JobDefinition,
PollOptions PollOptions
} from './types' } from './types'
import { import { JobExecutionError, RootFolderNotFoundError } from './types/errors'
ComputeJobExecutionError,
JobExecutionError,
NotFoundError
} from './types/errors'
import { formatDataForRequest } from './utils/formatDataForRequest'
import { SessionManager } from './SessionManager' import { SessionManager } from './SessionManager'
import { ContextManager } from './ContextManager' import { ContextManager } from './ContextManager'
import { import { SasAuthResponse, MacroVar, AuthConfig } from '@sasjs/utils/types'
timestampToYYYYMMDDHHMMSS,
isAccessTokenExpiring,
isRefreshTokenExpiring,
Logger,
LogLevel,
SasAuthResponse,
MacroVar,
AuthConfig
} from '@sasjs/utils'
import { isAuthorizeFormRequired } from './auth/isAuthorizeFormRequired' import { isAuthorizeFormRequired } from './auth/isAuthorizeFormRequired'
import { RequestClient } from './request/RequestClient' import { RequestClient } from './request/RequestClient'
import { prefixMessage } from '@sasjs/utils/error' import { prefixMessage } from '@sasjs/utils/error'
import * as mime from 'mime' import { pollJobState } from './api/viya/pollJobState'
import { getTokens } from './auth/getTokens'
import { uploadTables } from './api/viya/uploadTables'
import { executeScript } from './api/viya/executeScript'
import { getAccessTokenForViya } from './auth/getAccessTokenForViya'
import { refreshTokensForViya } from './auth/refreshTokensForViya'
/** /**
* A client for interfacing with the SAS Viya REST API. * A client for interfacing with the SAS Viya REST API.
@@ -66,6 +51,16 @@ export class SASViyaApiClient {
) )
private folderMap = new Map<string, Job[]>() private folderMap = new Map<string, Job[]>()
/**
* A helper method used to call appendRequest method of RequestClient
* @param response - response from sasjs request
* @param program - name of program
* @param debug - a boolean that indicates whether debug was enabled or not
*/
public appendRequest(response: any, program: string, debug: boolean) {
this.requestClient!.appendRequest(response, program, debug)
}
public get debug() { public get debug() {
return this._debug return this._debug
} }
@@ -174,13 +169,6 @@ export class SASViyaApiClient {
throw new Error(`Execution context ${contextName} not found.`) throw new Error(`Execution context ${contextName} not found.`)
} }
const createSessionRequest = {
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json'
}
}
const { result: createdSession } = await this.requestClient.post<Session>( const { result: createdSession } = await this.requestClient.post<Session>(
`/compute/contexts/${executionContext.id}/sessions`, `/compute/contexts/${executionContext.id}/sessions`,
{}, {},
@@ -295,249 +283,22 @@ export class SASViyaApiClient {
printPid = false, printPid = false,
variables?: MacroVar variables?: MacroVar
): Promise<any> { ): Promise<any> {
let access_token = (authConfig || {}).access_token return executeScript(
if (authConfig) { this.requestClient,
;({ access_token } = await this.getTokens(authConfig)) this.sessionManager,
} this.rootFolderName,
jobPath,
const logger = process.logger || console linesOfCode,
contextName,
try { authConfig,
let executionSessionId: string data,
debug,
const session = await this.sessionManager expectWebout,
.getSession(access_token) waitForResult,
.catch((err) => { pollOptions,
throw prefixMessage(err, 'Error while getting session. ') printPid,
}) variables
)
executionSessionId = session!.id
if (printPid) {
const { result: jobIdVariable } = await this.sessionManager
.getVariable(executionSessionId, 'SYSJOBID', access_token)
.catch((err) => {
throw prefixMessage(err, 'Error while getting session variable. ')
})
if (jobIdVariable && jobIdVariable.value) {
const relativeJobPath = this.rootFolderName
? jobPath.split(this.rootFolderName).join('').replace(/^\//, '')
: jobPath
const logger = new Logger(debug ? LogLevel.Debug : LogLevel.Info)
logger.info(
`Triggered '${relativeJobPath}' with PID ${
jobIdVariable.value
} at ${timestampToYYYYMMDDHHMMSS()}`
)
}
}
const jobArguments: { [key: string]: any } = {
_contextName: contextName,
_OMITJSONLISTING: true,
_OMITJSONLOG: true,
_OMITSESSIONRESULTS: true,
_OMITTEXTLISTING: true,
_OMITTEXTLOG: true
}
if (debug) {
jobArguments['_OMITTEXTLOG'] = false
jobArguments['_OMITSESSIONRESULTS'] = false
}
let fileName
if (isRelativePath(jobPath)) {
fileName = `exec-${
jobPath.includes('/') ? jobPath.split('/')[1] : jobPath
}`
} else {
const jobPathParts = jobPath.split('/')
fileName = jobPathParts.pop()
}
let jobVariables: any = {
SYS_JES_JOB_URI: '',
_program: isRelativePath(jobPath)
? this.rootFolderName + '/' + jobPath
: jobPath
}
if (variables) jobVariables = { ...jobVariables, ...variables }
if (debug) jobVariables = { ...jobVariables, _DEBUG: 131 }
let files: any[] = []
if (data) {
if (JSON.stringify(data).includes(';')) {
files = await this.uploadTables(data, access_token).catch((err) => {
throw prefixMessage(err, 'Error while uploading tables. ')
})
jobVariables['_webin_file_count'] = files.length
files.forEach((fileInfo, index) => {
jobVariables[
`_webin_fileuri${index + 1}`
] = `/files/files/${fileInfo.file.id}`
jobVariables[`_webin_name${index + 1}`] = fileInfo.tableName
})
} else {
jobVariables = { ...jobVariables, ...formatDataForRequest(data) }
}
}
// Execute job in session
const jobRequestBody = {
name: fileName,
description: 'Powered by SASjs',
code: linesOfCode,
variables: jobVariables,
arguments: jobArguments
}
const { result: postedJob, etag } = await this.requestClient
.post<Job>(
`/compute/sessions/${executionSessionId}/jobs`,
jobRequestBody,
access_token
)
.catch((err) => {
throw prefixMessage(err, 'Error while posting job. ')
})
if (!waitForResult) return session
if (debug) {
logger.info(`Job has been submitted for '${fileName}'.`)
logger.info(
`You can monitor the job progress at '${this.serverUrl}${
postedJob.links.find((l: any) => l.rel === 'state')!.href
}'.`
)
}
const jobStatus = await this.pollJobState(
postedJob,
etag,
authConfig,
pollOptions
).catch(async (err) => {
const error = err?.response?.data
const result = /err=[0-9]*,/.exec(error)
const errorCode = '5113'
if (result?.[0]?.slice(4, -1) === errorCode) {
const sessionLogUrl =
postedJob.links.find((l: any) => l.rel === 'up')!.href + '/log'
const logCount = 1000000
err.log = await fetchLogByChunks(
this.requestClient,
access_token!,
sessionLogUrl,
logCount
)
}
throw prefixMessage(err, 'Error while polling job status. ')
})
if (authConfig) {
;({ access_token } = await this.getTokens(authConfig))
}
const { result: currentJob } = await this.requestClient
.get<Job>(
`/compute/sessions/${executionSessionId}/jobs/${postedJob.id}`,
access_token
)
.catch((err) => {
throw prefixMessage(err, 'Error while getting job. ')
})
let jobResult
let log = ''
const logLink = currentJob.links.find((l) => l.rel === 'log')
if (debug && logLink) {
const logUrl = `${logLink.href}/content`
const logCount = currentJob.logStatistics?.lineCount ?? 1000000
log = await fetchLogByChunks(
this.requestClient,
access_token!,
logUrl,
logCount
)
}
if (jobStatus === 'failed' || jobStatus === 'error') {
return Promise.reject(new ComputeJobExecutionError(currentJob, log))
}
let resultLink
if (expectWebout) {
resultLink = `/compute/sessions/${executionSessionId}/filerefs/_webout/content`
} else {
return { job: currentJob, log }
}
if (resultLink) {
jobResult = await this.requestClient
.get<any>(resultLink, access_token, 'text/plain')
.catch(async (e) => {
if (e instanceof NotFoundError) {
if (logLink) {
const logUrl = `${logLink.href}/content`
const logCount = currentJob.logStatistics?.lineCount ?? 1000000
log = await fetchLogByChunks(
this.requestClient,
access_token!,
logUrl,
logCount
)
return Promise.reject({
status: 500,
log
})
}
}
return {
result: JSON.stringify(e)
}
})
}
await this.sessionManager
.clearSession(executionSessionId, access_token)
.catch((err) => {
throw prefixMessage(err, 'Error while clearing session. ')
})
return { result: jobResult?.result, log }
} catch (e) {
if (e && e.status === 404) {
return this.executeScript(
jobPath,
linesOfCode,
contextName,
authConfig,
data,
debug,
false,
true
)
} else {
throw prefixMessage(e, 'Error while executing script. ')
}
}
} }
/** /**
@@ -584,9 +345,6 @@ export class SASViyaApiClient {
const formData = new NodeFormData() const formData = new NodeFormData()
formData.append('file', contentBuffer, fileName) formData.append('file', contentBuffer, fileName)
const mimeType =
mime.getType(fileName.match(/\.[0-9a-z]+$/i)?.[0] || '') ?? 'text/plain'
return ( return (
await this.requestClient.post<File>( await this.requestClient.post<File>(
`/files/files?parentFolderUri=${parentFolderUri}&typeDefName=file#rawUpload`, `/files/files?parentFolderUri=${parentFolderUri}&typeDefName=file#rawUpload`,
@@ -633,7 +391,11 @@ export class SASViyaApiClient {
) )
const newFolderName = `${parentFolderPath.split('/').pop()}` const newFolderName = `${parentFolderPath.split('/').pop()}`
if (newParentFolderPath === '') { if (newParentFolderPath === '') {
throw new Error('Root folder has to be present on the server.') throw new RootFolderNotFoundError(
parentFolderPath,
this.serverUrl,
accessToken
)
} }
logger.info( logger.info(
`Creating parent folder:\n'${newFolderName}' in '${newParentFolderPath}'` `Creating parent folder:\n'${newFolderName}' in '${newParentFolderPath}'`
@@ -772,83 +534,31 @@ export class SASViyaApiClient {
clientSecret: string, clientSecret: string,
authCode: string authCode: string
): Promise<SasAuthResponse> { ): Promise<SasAuthResponse> {
const url = this.serverUrl + '/SASLogon/oauth/token' return getAccessTokenForViya(
let token this.requestClient,
if (typeof Buffer === 'undefined') { clientId,
token = btoa(clientId + ':' + clientSecret) clientSecret,
} else { authCode
token = Buffer.from(clientId + ':' + clientSecret).toString('base64') )
}
const headers = {
Authorization: 'Basic ' + token
}
let formData
if (typeof FormData === 'undefined') {
formData = new NodeFormData()
} else {
formData = new FormData()
}
formData.append('grant_type', 'authorization_code')
formData.append('code', authCode)
const authResponse = await this.requestClient
.post(
url,
formData,
undefined,
'multipart/form-data; boundary=' + (formData as any)._boundary,
headers
)
.then((res) => res.result as SasAuthResponse)
return authResponse
} }
/** /**
* Exchanges the refresh token for an access token for the given client. * Exchanges the refresh token for an access token for the given client.
* @param clientId - the client ID to authenticate with. * @param clientId - the client ID to authenticate with.
* @param clientSecret - the client secret to authenticate with. * @param clientSecret - the client secret to authenticate with.
* @param authCode - the refresh token received from the server. * @param refreshToken - the refresh token received from the server.
*/ */
public async refreshTokens( public async refreshTokens(
clientId: string, clientId: string,
clientSecret: string, clientSecret: string,
refreshToken: string refreshToken: string
) { ) {
const url = this.serverUrl + '/SASLogon/oauth/token' return refreshTokensForViya(
let token this.requestClient,
if (typeof Buffer === 'undefined') { clientId,
token = btoa(clientId + ':' + clientSecret) clientSecret,
} else { refreshToken
token = Buffer.from(clientId + ':' + clientSecret).toString('base64') )
}
const headers = {
Authorization: 'Basic ' + token
}
let formData
if (typeof FormData === 'undefined') {
formData = new NodeFormData()
formData.append('grant_type', 'refresh_token')
formData.append('refresh_token', refreshToken)
} else {
formData = new FormData()
formData.append('grant_type', 'refresh_token')
formData.append('refresh_token', refreshToken)
}
const authResponse = await this.requestClient
.post<SasAuthResponse>(
url,
formData,
undefined,
'multipart/form-data; boundary=' + (formData as any)._boundary,
headers
)
.then((res) => res.result)
return authResponse
} }
/** /**
@@ -895,7 +605,7 @@ export class SASViyaApiClient {
) { ) {
let access_token = (authConfig || {}).access_token let access_token = (authConfig || {}).access_token
if (authConfig) { if (authConfig) {
;({ access_token } = await this.getTokens(authConfig)) ;({ access_token } = await getTokens(this.requestClient, authConfig))
} }
if (isRelativePath(sasJob) && !this.rootFolderName) { if (isRelativePath(sasJob) && !this.rootFolderName) {
@@ -991,7 +701,7 @@ export class SASViyaApiClient {
) { ) {
let access_token = (authConfig || {}).access_token let access_token = (authConfig || {}).access_token
if (authConfig) { if (authConfig) {
;({ access_token } = await this.getTokens(authConfig)) ;({ access_token } = await getTokens(this.requestClient, authConfig))
} }
if (isRelativePath(sasJob) && !this.rootFolderName) { if (isRelativePath(sasJob) && !this.rootFolderName) {
throw new Error( throw new Error(
@@ -1063,18 +773,16 @@ export class SASViyaApiClient {
jobDefinition, jobDefinition,
arguments: jobArguments arguments: jobArguments
} }
const { result: postedJob, etag } = await this.requestClient.post<Job>( const { result: postedJob } = await this.requestClient.post<Job>(
`${this.serverUrl}/jobExecution/jobs?_action=wait`, `${this.serverUrl}/jobExecution/jobs?_action=wait`,
postJobRequestBody, postJobRequestBody,
access_token access_token
) )
const jobStatus = await this.pollJobState( const jobStatus = await this.pollJobState(postedJob, authConfig).catch(
postedJob, (err) => {
etag, throw prefixMessage(err, 'Error while polling job status. ')
authConfig }
).catch((err) => { )
throw prefixMessage(err, 'Error while polling job status. ')
})
const { result: currentJob } = await this.requestClient.get<Job>( const { result: currentJob } = await this.requestClient.get<Job>(
`${this.serverUrl}/jobExecution/jobs/${postedJob.id}`, `${this.serverUrl}/jobExecution/jobs/${postedJob.id}`,
access_token access_token
@@ -1140,157 +848,22 @@ export class SASViyaApiClient {
this.folderMap.set(path, itemsAtRoot) this.folderMap.set(path, itemsAtRoot)
} }
// REFACTOR: set default value for 'pollOptions' attribute
private async pollJobState( private async pollJobState(
postedJob: any, postedJob: Job,
etag: string | null,
authConfig?: AuthConfig, authConfig?: AuthConfig,
pollOptions?: PollOptions pollOptions?: PollOptions
) { ) {
const logger = process.logger || console return pollJobState(
this.requestClient,
let POLL_INTERVAL = 300 postedJob,
let MAX_POLL_COUNT = 1000 this.debug,
let MAX_ERROR_COUNT = 5 authConfig,
let access_token = (authConfig || {}).access_token pollOptions
if (authConfig) { )
;({ access_token } = await this.getTokens(authConfig))
}
if (pollOptions) {
POLL_INTERVAL = pollOptions.POLL_INTERVAL || POLL_INTERVAL
MAX_POLL_COUNT = pollOptions.MAX_POLL_COUNT || MAX_POLL_COUNT
}
let postedJobState = ''
let pollCount = 0
let errorCount = 0
const headers: any = {
'Content-Type': 'application/json',
'If-None-Match': etag
}
if (access_token) {
headers.Authorization = `Bearer ${access_token}`
}
const stateLink = postedJob.links.find((l: any) => l.rel === 'state')
if (!stateLink) {
Promise.reject(`Job state link was not found.`)
}
const { result: state } = await this.requestClient
.get<string>(
`${this.serverUrl}${stateLink.href}?_action=wait&wait=300`,
access_token,
'text/plain',
{},
this.debug
)
.catch((err) => {
console.error(
`Error fetching job state from ${this.serverUrl}${stateLink.href}. Starting poll, assuming job to be running.`,
err
)
return { result: 'unavailable' }
})
const currentState = state.trim()
if (currentState === 'completed') {
return Promise.resolve(currentState)
}
return new Promise(async (resolve, _) => {
let printedState = ''
const interval = setInterval(async () => {
if (
postedJobState === 'running' ||
postedJobState === '' ||
postedJobState === 'pending' ||
postedJobState === 'unavailable'
) {
if (authConfig) {
;({ access_token } = await this.getTokens(authConfig))
}
if (stateLink) {
const { result: jobState } = await this.requestClient
.get<string>(
`${this.serverUrl}${stateLink.href}?_action=wait&wait=300`,
access_token,
'text/plain',
{},
this.debug
)
.catch((err) => {
errorCount++
if (
pollCount >= MAX_POLL_COUNT ||
errorCount >= MAX_ERROR_COUNT
) {
throw prefixMessage(
err,
'Error while getting job state after interval. '
)
}
console.error(
`Error fetching job state from ${this.serverUrl}${stateLink.href}. Resuming poll, assuming job to be running.`,
err
)
return { result: 'unavailable' }
})
postedJobState = jobState.trim()
if (postedJobState != 'unavailable' && errorCount > 0) {
errorCount = 0
}
if (this.debug && printedState !== postedJobState) {
logger.info('Polling job status...')
logger.info(`Current job state: ${postedJobState}`)
printedState = postedJobState
}
pollCount++
if (pollCount >= MAX_POLL_COUNT) {
resolve(postedJobState)
}
}
} else {
clearInterval(interval)
resolve(postedJobState)
}
}, POLL_INTERVAL)
})
} }
private async uploadTables(data: any, accessToken?: string) { private async uploadTables(data: any, accessToken?: string) {
const uploadedFiles = [] return uploadTables(this.requestClient, data, accessToken)
const headers: any = {
'Content-Type': 'application/json'
}
if (accessToken) {
headers.Authorization = `Bearer ${accessToken}`
}
for (const tableName in data) {
const csv = convertToCSV(data[tableName])
if (csv === 'ERROR: LARGE STRING LENGTH') {
throw new Error(
'The max length of a string value in SASjs is 32765 characters.'
)
}
const uploadResponse = await this.requestClient
.uploadFile(`${this.serverUrl}/files/files#rawUpload`, csv, accessToken)
.catch((err) => {
throw prefixMessage(err, 'Error while uploading file. ')
})
uploadedFiles.push({ tableName, file: uploadResponse.result })
}
return uploadedFiles
} }
private async getFolderDetails( private async getFolderDetails(
@@ -1379,14 +952,6 @@ export class SASViyaApiClient {
? sourceFolder ? sourceFolder
: await this.getFolderUri(sourceFolder, accessToken) : await this.getFolderUri(sourceFolder, accessToken)
const requestInfo = {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer ' + accessToken
}
}
const { result: members } = await this.requestClient.get<{ items: any[] }>( const { result: members } = await this.requestClient.get<{ items: any[] }>(
`${this.serverUrl}${sourceFolderUri}/members?limit=${limit}`, `${this.serverUrl}${sourceFolderUri}/members?limit=${limit}`,
accessToken accessToken
@@ -1493,21 +1058,4 @@ export class SASViyaApiClient {
return movedFolder return movedFolder
} }
private async getTokens(authConfig: AuthConfig): Promise<AuthConfig> {
const logger = process.logger || console
let { access_token, refresh_token, client, secret } = authConfig
if (
isAccessTokenExpiring(access_token) ||
isRefreshTokenExpiring(refresh_token)
) {
logger.info('Refreshing access and refresh tokens.')
;({ access_token, refresh_token } = await this.refreshTokens(
client,
secret,
refresh_token
))
}
return { access_token, refresh_token, client, secret }
}
} }

View File

@@ -1,23 +1,41 @@
import { compareTimestamps, asyncForEach } from './utils' import { compareTimestamps, asyncForEach } from './utils'
import { SASjsConfig, UploadFile, EditContextInput, PollOptions } from './types' import {
SASjsConfig,
UploadFile,
EditContextInput,
PollOptions,
LoginMechanism,
FolderMember,
ServiceMember,
ExecutionQuery
} from './types'
import { SASViyaApiClient } from './SASViyaApiClient' import { SASViyaApiClient } from './SASViyaApiClient'
import { SAS9ApiClient } from './SAS9ApiClient' import { SAS9ApiClient } from './SAS9ApiClient'
import { FileUploader } from './FileUploader' import { SASjsApiClient, SASjsAuthResponse } from './SASjsApiClient'
import { AuthManager } from './auth' import { AuthManager } from './auth'
import { ServerType, MacroVar, AuthConfig } from '@sasjs/utils/types' import {
ServerType,
MacroVar,
AuthConfig,
ExtraResponseAttributes,
SasAuthResponse
} from '@sasjs/utils/types'
import { RequestClient } from './request/RequestClient' import { RequestClient } from './request/RequestClient'
import { SasjsRequestClient } from './request/SasjsRequestClient'
import { import {
JobExecutor, JobExecutor,
WebJobExecutor, WebJobExecutor,
ComputeJobExecutor, ComputeJobExecutor,
JesJobExecutor, JesJobExecutor,
Sas9JobExecutor Sas9JobExecutor,
FileUploader
} from './job-execution' } from './job-execution'
import { ErrorResponse } from './types/errors' import { ErrorResponse } from './types/errors'
import { ExtraResponseAttributes } from '@sasjs/utils/types' import { LoginOptions, LoginResult } from './types/Login'
const defaultConfig: SASjsConfig = { const defaultConfig: SASjsConfig = {
serverUrl: '', serverUrl: '',
pathSASJS: '/SASjsApi/stp/execute',
pathSAS9: '/SASStoredProcess/do', pathSAS9: '/SASStoredProcess/do',
pathSASViya: '/SASJobExecution', pathSASViya: '/SASJobExecution',
appLoc: '/Public/seedapp', appLoc: '/Public/seedapp',
@@ -25,7 +43,7 @@ const defaultConfig: SASjsConfig = {
debug: false, debug: false,
contextName: 'SAS Job Execution compute context', contextName: 'SAS Job Execution compute context',
useComputeApi: null, useComputeApi: null,
allowInsecureRequests: false loginMechanism: LoginMechanism.Default
} }
/** /**
@@ -37,6 +55,7 @@ export default class SASjs {
private jobsPath: string = '' private jobsPath: string = ''
private sasViyaApiClient: SASViyaApiClient | null = null private sasViyaApiClient: SASViyaApiClient | null = null
private sas9ApiClient: SAS9ApiClient | null = null private sas9ApiClient: SAS9ApiClient | null = null
private sasJSApiClient: SASjsApiClient | null = null
private fileUploader: FileUploader | null = null private fileUploader: FileUploader | null = null
private authManager: AuthManager | null = null private authManager: AuthManager | null = null
private requestClient: RequestClient | null = null private requestClient: RequestClient | null = null
@@ -45,7 +64,7 @@ export default class SASjs {
private jesJobExecutor: JobExecutor | null = null private jesJobExecutor: JobExecutor | null = null
private sas9JobExecutor: JobExecutor | null = null private sas9JobExecutor: JobExecutor | null = null
constructor(config?: any) { constructor(config?: Partial<SASjsConfig>) {
this.sasjsConfig = { this.sasjsConfig = {
...defaultConfig, ...defaultConfig,
...config ...config
@@ -63,7 +82,7 @@ export default class SASjs {
userName: string, userName: string,
password: string password: string
) { ) {
this.isMethodSupported('executeScriptSAS9', ServerType.Sas9) this.isMethodSupported('executeScriptSAS9', [ServerType.Sas9])
return await this.sas9ApiClient?.executeScript( return await this.sas9ApiClient?.executeScript(
linesOfCode, linesOfCode,
@@ -77,7 +96,7 @@ export default class SASjs {
* @param accessToken - an access token for an authorized user. * @param accessToken - an access token for an authorized user.
*/ */
public async getComputeContexts(accessToken: string) { public async getComputeContexts(accessToken: string) {
this.isMethodSupported('getComputeContexts', ServerType.SasViya) this.isMethodSupported('getComputeContexts', [ServerType.SasViya])
return await this.sasViyaApiClient!.getComputeContexts(accessToken) return await this.sasViyaApiClient!.getComputeContexts(accessToken)
} }
@@ -87,7 +106,7 @@ export default class SASjs {
* @param accessToken - an access token for an authorized user. * @param accessToken - an access token for an authorized user.
*/ */
public async getLauncherContexts(accessToken: string) { public async getLauncherContexts(accessToken: string) {
this.isMethodSupported('getLauncherContexts', ServerType.SasViya) this.isMethodSupported('getLauncherContexts', [ServerType.SasViya])
return await this.sasViyaApiClient!.getLauncherContexts(accessToken) return await this.sasViyaApiClient!.getLauncherContexts(accessToken)
} }
@@ -96,7 +115,7 @@ export default class SASjs {
* Gets default(system) launcher contexts. * Gets default(system) launcher contexts.
*/ */
public getDefaultComputeContexts() { public getDefaultComputeContexts() {
this.isMethodSupported('getDefaultComputeContexts', ServerType.SasViya) this.isMethodSupported('getDefaultComputeContexts', [ServerType.SasViya])
return this.sasViyaApiClient!.getDefaultComputeContexts() return this.sasViyaApiClient!.getDefaultComputeContexts()
} }
@@ -106,7 +125,7 @@ export default class SASjs {
* @param authConfig - an access token, refresh token, client and secret for an authorized user. * @param authConfig - an access token, refresh token, client and secret for an authorized user.
*/ */
public async getExecutableContexts(authConfig: AuthConfig) { public async getExecutableContexts(authConfig: AuthConfig) {
this.isMethodSupported('getExecutableContexts', ServerType.SasViya) this.isMethodSupported('getExecutableContexts', [ServerType.SasViya])
return await this.sasViyaApiClient!.getExecutableContexts(authConfig) return await this.sasViyaApiClient!.getExecutableContexts(authConfig)
} }
@@ -128,7 +147,7 @@ export default class SASjs {
accessToken: string, accessToken: string,
authorizedUsers?: string[] authorizedUsers?: string[]
) { ) {
this.isMethodSupported('createComputeContext', ServerType.SasViya) this.isMethodSupported('createComputeContext', [ServerType.SasViya])
return await this.sasViyaApiClient!.createComputeContext( return await this.sasViyaApiClient!.createComputeContext(
contextName, contextName,
@@ -153,7 +172,7 @@ export default class SASjs {
launchType: string, launchType: string,
accessToken: string accessToken: string
) { ) {
this.isMethodSupported('createLauncherContext', ServerType.SasViya) this.isMethodSupported('createLauncherContext', [ServerType.SasViya])
return await this.sasViyaApiClient!.createLauncherContext( return await this.sasViyaApiClient!.createLauncherContext(
contextName, contextName,
@@ -174,7 +193,7 @@ export default class SASjs {
editedContext: EditContextInput, editedContext: EditContextInput,
accessToken?: string accessToken?: string
) { ) {
this.isMethodSupported('editComputeContext', ServerType.SasViya) this.isMethodSupported('editComputeContext', [ServerType.SasViya])
return await this.sasViyaApiClient!.editComputeContext( return await this.sasViyaApiClient!.editComputeContext(
contextName, contextName,
@@ -189,7 +208,7 @@ export default class SASjs {
* @param accessToken - an access token for an authorized user. * @param accessToken - an access token for an authorized user.
*/ */
public async deleteComputeContext(contextName: string, accessToken?: string) { public async deleteComputeContext(contextName: string, accessToken?: string) {
this.isMethodSupported('deleteComputeContext', ServerType.SasViya) this.isMethodSupported('deleteComputeContext', [ServerType.SasViya])
return await this.sasViyaApiClient!.deleteComputeContext( return await this.sasViyaApiClient!.deleteComputeContext(
contextName, contextName,
@@ -207,7 +226,7 @@ export default class SASjs {
contextName: string, contextName: string,
accessToken?: string accessToken?: string
) { ) {
this.isMethodSupported('getComputeContextByName', ServerType.SasViya) this.isMethodSupported('getComputeContextByName', [ServerType.SasViya])
return await this.sasViyaApiClient!.getComputeContextByName( return await this.sasViyaApiClient!.getComputeContextByName(
contextName, contextName,
@@ -221,7 +240,7 @@ export default class SASjs {
* @param accessToken - an access token for an authorized user. * @param accessToken - an access token for an authorized user.
*/ */
public async getComputeContextById(contextId: string, accessToken?: string) { public async getComputeContextById(contextId: string, accessToken?: string) {
this.isMethodSupported('getComputeContextById', ServerType.SasViya) this.isMethodSupported('getComputeContextById', [ServerType.SasViya])
return await this.sasViyaApiClient!.getComputeContextById( return await this.sasViyaApiClient!.getComputeContextById(
contextId, contextId,
@@ -230,7 +249,7 @@ export default class SASjs {
} }
public async createSession(contextName: string, accessToken: string) { public async createSession(contextName: string, accessToken: string) {
this.isMethodSupported('createSession', ServerType.SasViya) this.isMethodSupported('createSession', [ServerType.SasViya])
return await this.sasViyaApiClient!.createSession(contextName, accessToken) return await this.sasViyaApiClient!.createSession(contextName, accessToken)
} }
@@ -250,7 +269,7 @@ export default class SASjs {
authConfig?: AuthConfig, authConfig?: AuthConfig,
debug?: boolean debug?: boolean
) { ) {
this.isMethodSupported('executeScriptSASViya', ServerType.SasViya) this.isMethodSupported('executeScriptSASViya', [ServerType.SasViya])
if (!contextName) { if (!contextName) {
throw new Error( throw new Error(
'Context name is undefined. Please set a `contextName` in your SASjs or override config.' 'Context name is undefined. Please set a `contextName` in your SASjs or override config.'
@@ -340,7 +359,7 @@ export default class SASjs {
* @param accessToken - the access token to authorize the request. * @param accessToken - the access token to authorize the request.
*/ */
public async getFolder(folderPath: string, accessToken?: string) { public async getFolder(folderPath: string, accessToken?: string) {
this.isMethodSupported('getFolder', ServerType.SasViya) this.isMethodSupported('getFolder', [ServerType.SasViya])
return await this.sasViyaApiClient!.getFolder(folderPath, accessToken) return await this.sasViyaApiClient!.getFolder(folderPath, accessToken)
} }
@@ -350,7 +369,7 @@ export default class SASjs {
* @param accessToken - an access token for authorizing the request. * @param accessToken - an access token for authorizing the request.
*/ */
public async deleteFolder(folderPath: string, accessToken: string) { public async deleteFolder(folderPath: string, accessToken: string) {
this.isMethodSupported('deleteFolder', ServerType.SasViya) this.isMethodSupported('deleteFolder', [ServerType.SasViya])
return await this.sasViyaApiClient?.deleteFolder(folderPath, accessToken) return await this.sasViyaApiClient?.deleteFolder(folderPath, accessToken)
} }
@@ -365,7 +384,7 @@ export default class SASjs {
accessToken?: string, accessToken?: string,
limit?: number limit?: number
) { ) {
this.isMethodSupported('listFolder', ServerType.SasViya) this.isMethodSupported('listFolder', [ServerType.SasViya])
return await this.sasViyaApiClient?.listFolder( return await this.sasViyaApiClient?.listFolder(
sourceFolder, sourceFolder,
@@ -387,7 +406,7 @@ export default class SASjs {
targetFolderName: string, targetFolderName: string,
accessToken: string accessToken: string
) { ) {
this.isMethodSupported('moveFolder', ServerType.SasViya) this.isMethodSupported('moveFolder', [ServerType.SasViya])
return await this.sasViyaApiClient?.moveFolder( return await this.sasViyaApiClient?.moveFolder(
sourceFolder, sourceFolder,
@@ -405,7 +424,7 @@ export default class SASjs {
accessToken?: string, accessToken?: string,
sasApiClient?: SASViyaApiClient sasApiClient?: SASViyaApiClient
) { ) {
this.isMethodSupported('createJobDefinition', ServerType.SasViya) this.isMethodSupported('createJobDefinition', [ServerType.SasViya])
if (sasApiClient) if (sasApiClient)
return await sasApiClient!.createJobDefinition( return await sasApiClient!.createJobDefinition(
@@ -425,7 +444,7 @@ export default class SASjs {
} }
public async getAuthCode(clientId: string) { public async getAuthCode(clientId: string) {
this.isMethodSupported('getAuthCode', ServerType.SasViya) this.isMethodSupported('getAuthCode', [ServerType.SasViya])
return await this.sasViyaApiClient!.getAuthCode(clientId) return await this.sasViyaApiClient!.getAuthCode(clientId)
} }
@@ -440,8 +459,14 @@ export default class SASjs {
clientId: string, clientId: string,
clientSecret: string, clientSecret: string,
authCode: string authCode: string
) { ): Promise<SasAuthResponse | SASjsAuthResponse> {
this.isMethodSupported('getAccessToken', ServerType.SasViya) this.isMethodSupported('getAccessToken', [
ServerType.SasViya,
ServerType.Sasjs
])
if (this.sasjsConfig.serverType === ServerType.Sasjs)
return await this.sasJSApiClient!.getAccessToken(clientId, authCode)
return await this.sasViyaApiClient!.getAccessToken( return await this.sasViyaApiClient!.getAccessToken(
clientId, clientId,
@@ -450,12 +475,24 @@ export default class SASjs {
) )
} }
/**
* Exchanges the refresh token for an access token for the given client.
* @param clientId - the client ID to authenticate with.
* @param clientSecret - the client secret to authenticate with.
* @param refreshToken - the refresh token received from the server.
*/
public async refreshTokens( public async refreshTokens(
clientId: string, clientId: string,
clientSecret: string, clientSecret: string,
refreshToken: string refreshToken: string
) { ): Promise<SasAuthResponse | SASjsAuthResponse> {
this.isMethodSupported('refreshTokens', ServerType.SasViya) this.isMethodSupported('refreshTokens', [
ServerType.SasViya,
ServerType.Sasjs
])
if (this.sasjsConfig.serverType === ServerType.Sasjs)
return await this.sasJSApiClient!.refreshTokens(refreshToken)
return await this.sasViyaApiClient!.refreshTokens( return await this.sasViyaApiClient!.refreshTokens(
clientId, clientId,
@@ -465,7 +502,7 @@ export default class SASjs {
} }
public async deleteClient(clientId: string, accessToken: string) { public async deleteClient(clientId: string, accessToken: string) {
this.isMethodSupported('deleteClient', ServerType.SasViya) this.isMethodSupported('deleteClient', [ServerType.SasViya])
return await this.sasViyaApiClient!.deleteClient(clientId, accessToken) return await this.sasViyaApiClient!.deleteClient(clientId, accessToken)
} }
@@ -495,7 +532,7 @@ export default class SASjs {
...this.sasjsConfig, ...this.sasjsConfig,
...config ...config
} }
await this.setupConfiguration() this.setupConfiguration()
} }
/** /**
@@ -521,9 +558,39 @@ export default class SASjs {
* Logs into the SAS server with the supplied credentials. * Logs into the SAS server with the supplied credentials.
* @param username - a string representing the username. * @param username - a string representing the username.
* @param password - a string representing the password. * @param password - a string representing the password.
* @param clientId - a string representing the client ID.
*/ */
public async logIn(username: string, password: string) { public async logIn(
return this.authManager!.logIn(username, password) username?: string,
password?: string,
clientId?: string,
options: LoginOptions = {}
): Promise<LoginResult> {
if (this.sasjsConfig.loginMechanism === LoginMechanism.Default) {
if (!username || !password)
throw new Error(
'A username and password are required when using the default login mechanism.'
)
if (this.sasjsConfig.serverType === ServerType.Sasjs) {
if (!clientId)
throw new Error(
'A username, password and clientId are required when using the default login mechanism with server type SASJS.'
)
return this.authManager!.logInSasjs(username, password, clientId)
}
return this.authManager!.logIn(username, password)
}
if (typeof window === typeof undefined) {
throw new Error(
'The redirected login mechanism is only available for use in the browser.'
)
}
return this.authManager!.redirectedLogIn(options)
} }
/** /**
@@ -540,13 +607,32 @@ export default class SASjs {
* Process). Is prepended at runtime with the value of `appLoc`. * Process). Is prepended at runtime with the value of `appLoc`.
* @param files - array of files to be uploaded, including File object and file name. * @param files - array of files to be uploaded, including File object and file name.
* @param params - request URL parameters. * @param params - request URL parameters.
* @param config - provide any changes to the config here, for instance to
* enable/disable `debug`. Any change provided will override the global config,
* for that particular function call.
* @param loginRequiredCallback - a function that is called if the
* user is not logged in (eg to display a login form). The request will be
* resubmitted after successful login.
*/ */
public uploadFile(sasJob: string, files: UploadFile[], params: any) { public async uploadFile(
const fileUploader = sasJob: string,
this.fileUploader || files: UploadFile[],
new FileUploader(this.sasjsConfig, this.jobsPath, this.requestClient!) params: { [key: string]: any } | null,
config: { [key: string]: any } = {},
loginRequiredCallback?: () => any
) {
config = {
...this.sasjsConfig,
...config
}
const data = { files, params }
return fileUploader.uploadFile(sasJob, files, params) return await this.fileUploader!.execute(
sasJob,
data,
config,
loginRequiredCallback
)
} }
/** /**
@@ -604,6 +690,11 @@ export default class SASjs {
authConfig authConfig
) )
} else { } else {
if (!config.contextName)
config = {
...config,
contextName: 'SAS Job Execution compute context'
}
return await this.jesJobExecutor!.execute( return await this.jesJobExecutor!.execute(
sasJob, sasJob,
data, data,
@@ -715,7 +806,7 @@ export default class SASjs {
accessToken?: string, accessToken?: string,
isForced = false isForced = false
) { ) {
this.isMethodSupported('deployServicePack', ServerType.SasViya) this.isMethodSupported('deployServicePack', [ServerType.SasViya])
let sasApiClient: any = null let sasApiClient: any = null
if (serverUrl || appLoc) { if (serverUrl || appLoc) {
@@ -734,7 +825,11 @@ export default class SASjs {
) )
sasApiClient.debug = this.sasjsConfig.debug sasApiClient.debug = this.sasjsConfig.debug
} else if (this.sasjsConfig.serverType === ServerType.Sas9) { } else if (this.sasjsConfig.serverType === ServerType.Sas9) {
sasApiClient = new SAS9ApiClient(serverUrl, this.jobsPath) sasApiClient = new SAS9ApiClient(
serverUrl,
this.jobsPath,
this.sasjsConfig.httpsAgentOptions
)
} }
} else { } else {
let sasClientConfig: any = null let sasClientConfig: any = null
@@ -765,6 +860,14 @@ export default class SASjs {
) )
} }
public async deployToSASjs(members: [FolderMember, ServiceMember]) {
return await this.sasJSApiClient?.deploy(members, this.sasjsConfig.appLoc)
}
public async executeJobSASjs(query: ExecutionQuery) {
return await this.sasJSApiClient?.executeJob(query)
}
/** /**
* Kicks off execution of the given job via the compute API. * Kicks off execution of the given job via the compute API.
* @returns an object representing the compute session created for the given job. * @returns an object representing the compute session created for the given job.
@@ -798,7 +901,7 @@ export default class SASjs {
...config ...config
} }
this.isMethodSupported('startComputeJob', ServerType.SasViya) this.isMethodSupported('startComputeJob', [ServerType.SasViya])
if (!config.contextName) { if (!config.contextName) {
throw new Error( throw new Error(
'Context name is undefined. Please set a `contextName` in your SASjs or override config.' 'Context name is undefined. Please set a `contextName` in your SASjs or override config.'
@@ -823,6 +926,7 @@ export default class SASjs {
await this.webJobExecutor?.resendWaitingRequests() await this.webJobExecutor?.resendWaitingRequests()
await this.computeJobExecutor?.resendWaitingRequests() await this.computeJobExecutor?.resendWaitingRequests()
await this.jesJobExecutor?.resendWaitingRequests() await this.jesJobExecutor?.resendWaitingRequests()
await this.fileUploader?.resendWaitingRequests()
} }
/** /**
@@ -854,20 +958,18 @@ export default class SASjs {
}) })
} }
/**
* this method returns an array of SASjsRequest
* @returns SASjsRequest[]
*/
public getSasRequests() { public getSasRequests() {
const requests = [ const requests = [...this.requestClient!.getRequests()]
...this.webJobExecutor!.getRequests(),
...this.computeJobExecutor!.getRequests(),
...this.jesJobExecutor!.getRequests()
]
const sortedRequests = requests.sort(compareTimestamps) const sortedRequests = requests.sort(compareTimestamps)
return sortedRequests return sortedRequests
} }
public clearSasRequests() { public clearSasRequests() {
this.webJobExecutor!.clearRequests() this.requestClient!.clearRequests()
this.computeJobExecutor!.clearRequests()
this.jesJobExecutor!.clearRequests()
} }
private setupConfiguration() { private setupConfiguration() {
@@ -890,15 +992,28 @@ export default class SASjs {
this.sasjsConfig.serverUrl = this.sasjsConfig.serverUrl.slice(0, -1) this.sasjsConfig.serverUrl = this.sasjsConfig.serverUrl.slice(0, -1)
} }
this.requestClient = new RequestClient( if (!this.requestClient) {
this.sasjsConfig.serverUrl, const RequestClientClass =
this.sasjsConfig.allowInsecureRequests this.sasjsConfig.serverType === ServerType.Sasjs
) ? SasjsRequestClient
: RequestClient
this.requestClient = new RequestClientClass(
this.sasjsConfig.serverUrl,
this.sasjsConfig.httpsAgentOptions
)
} else {
this.requestClient.setConfig(
this.sasjsConfig.serverUrl,
this.sasjsConfig.httpsAgentOptions
)
}
this.jobsPath = this.jobsPath =
this.sasjsConfig.serverType === ServerType.SasViya this.sasjsConfig.serverType === ServerType.SasViya
? this.sasjsConfig.pathSASViya ? this.sasjsConfig.pathSASViya
: this.sasjsConfig.pathSAS9 : this.sasjsConfig.serverType === ServerType.Sas9
? this.sasjsConfig.pathSAS9
: this.sasjsConfig.pathSASJS || ''
this.authManager = new AuthManager( this.authManager = new AuthManager(
this.sasjsConfig.serverUrl, this.sasjsConfig.serverUrl,
@@ -908,33 +1023,49 @@ export default class SASjs {
) )
if (this.sasjsConfig.serverType === ServerType.SasViya) { if (this.sasjsConfig.serverType === ServerType.SasViya) {
if (this.sasViyaApiClient) if (this.sasViyaApiClient) {
this.sasViyaApiClient!.setConfig( this.sasViyaApiClient!.setConfig(
this.sasjsConfig.serverUrl, this.sasjsConfig.serverUrl,
this.sasjsConfig.appLoc this.sasjsConfig.appLoc
) )
else } else {
this.sasViyaApiClient = new SASViyaApiClient( this.sasViyaApiClient = new SASViyaApiClient(
this.sasjsConfig.serverUrl, this.sasjsConfig.serverUrl,
this.sasjsConfig.appLoc, this.sasjsConfig.appLoc,
this.sasjsConfig.contextName, this.sasjsConfig.contextName,
this.requestClient this.requestClient
) )
}
this.sasViyaApiClient.debug = this.sasjsConfig.debug this.sasViyaApiClient.debug = this.sasjsConfig.debug
} }
if (this.sasjsConfig.serverType === ServerType.Sas9) { if (this.sasjsConfig.serverType === ServerType.Sas9) {
if (this.sas9ApiClient) if (this.sas9ApiClient) {
this.sas9ApiClient!.setConfig(this.sasjsConfig.serverUrl) this.sas9ApiClient!.setConfig(this.sasjsConfig.serverUrl)
else } else {
this.sas9ApiClient = new SAS9ApiClient( this.sas9ApiClient = new SAS9ApiClient(
this.sasjsConfig.serverUrl, this.sasjsConfig.serverUrl,
this.jobsPath this.jobsPath,
this.sasjsConfig.httpsAgentOptions
) )
}
}
if (this.sasjsConfig.serverType === ServerType.Sasjs) {
if (this.sasJSApiClient) {
this.sasJSApiClient.setConfig(this.sasjsConfig.serverUrl)
} else {
this.sasJSApiClient = new SASjsApiClient(
this.sasjsConfig.serverUrl,
this.requestClient
)
}
} }
this.fileUploader = new FileUploader( this.fileUploader = new FileUploader(
this.sasjsConfig, this.sasjsConfig.serverUrl,
this.sasjsConfig.serverType!,
this.jobsPath, this.jobsPath,
this.requestClient this.requestClient
) )
@@ -950,7 +1081,9 @@ export default class SASjs {
this.sas9JobExecutor = new Sas9JobExecutor( this.sas9JobExecutor = new Sas9JobExecutor(
this.sasjsConfig.serverUrl, this.sasjsConfig.serverUrl,
this.sasjsConfig.serverType!, this.sasjsConfig.serverType!,
this.jobsPath this.jobsPath,
this.requestClient,
this.sasjsConfig.httpsAgentOptions
) )
this.computeJobExecutor = new ComputeJobExecutor( this.computeJobExecutor = new ComputeJobExecutor(
@@ -1017,12 +1150,15 @@ export default class SASjs {
}) })
} }
private isMethodSupported(method: string, serverType: string) { private isMethodSupported(method: string, serverTypes: ServerType[]) {
if (this.sasjsConfig.serverType !== serverType) { if (
!this.sasjsConfig.serverType ||
!serverTypes.includes(this.sasjsConfig.serverType)
) {
throw new Error( throw new Error(
`Method '${method}' is only supported on ${ `Method '${method}' is only supported on ${serverTypes.join(
serverType === ServerType.Sas9 ? 'SAS9' : 'SAS Viya' ', '
} servers.` )} servers.`
) )
} }
} }

82
src/SASjsApiClient.ts Normal file
View File

@@ -0,0 +1,82 @@
import { FolderMember, ServiceMember, ExecutionQuery } from './types'
import { RequestClient } from './request/RequestClient'
import { getAccessTokenForSasjs } from './auth/getAccessTokenForSasjs'
import { refreshTokensForSasjs } from './auth/refreshTokensForSasjs'
import { getAuthCodeForSasjs } from './auth/getAuthCodeForSasjs'
export class SASjsApiClient {
constructor(
private serverUrl: string,
private requestClient: RequestClient
) {}
public setConfig(serverUrl: string) {
if (serverUrl) this.serverUrl = serverUrl
}
public async deploy(members: [FolderMember, ServiceMember], appLoc: string) {
const { result } = await this.requestClient.post<{
status: string
message: string
example?: {}
}>(
'SASjsApi/drive/deploy',
{ fileTree: members, appLoc: appLoc },
undefined
)
return Promise.resolve(result)
}
public async executeJob(query: ExecutionQuery) {
const { result } = await this.requestClient.post<{
status: string
message: string
log?: string
logPath?: string
error?: {}
}>('SASjsApi/stp/execute', query, undefined)
return Promise.resolve(result)
}
/**
* Exchanges the auth code for an access token for the given client.
* @param clientId - the client ID to authenticate with.
* @param authCode - the auth code received from the server.
*/
public async getAccessToken(
clientId: string,
authCode: string
): Promise<SASjsAuthResponse> {
return getAccessTokenForSasjs(this.requestClient, clientId, authCode)
}
/**
* Exchanges the refresh token for an access token.
* @param refreshToken - the refresh token received from the server.
*/
public async refreshTokens(refreshToken: string): Promise<SASjsAuthResponse> {
return refreshTokensForSasjs(this.requestClient, refreshToken)
}
/**
* Performs a login authenticate and returns an auth code for the given client.
* @param username - a string representing the username.
* @param password - a string representing the password.
* @param clientId - the client ID to authenticate with.
*/
public async getAuthCode(
username: string,
password: string,
clientId: string
) {
return getAuthCodeForSasjs(this.requestClient, username, password, clientId)
}
}
// todo move to sasjs/utils
export interface SASjsAuthResponse {
access_token: string
refresh_token: string
}

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