mirror of
https://github.com/sasjs/adapter.git
synced 2025-12-11 01:14:36 +00:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
35f37ac796 | ||
|
|
d7ad0288b9 | ||
|
|
9c98cabe6c | ||
|
|
a6f6897543 | ||
|
|
2ea3925977 | ||
| 489df78391 | |||
|
|
842c7f9b23 | ||
| fe3f6d6287 | |||
|
|
9728ebd98d | ||
|
|
8e116d81d7 | ||
|
|
fbca91144d | ||
|
|
c392390147 | ||
|
|
9b239abda0 | ||
|
|
78945d9f45 | ||
|
|
1e0365de1c | ||
|
|
c8d71b0267 | ||
|
|
bfc534da15 | ||
|
|
d3dff44918 | ||
|
|
4026b03005 | ||
|
|
eab19a0e6e | ||
|
|
1d7b7d654d | ||
| 2c4152a593 | |||
|
|
30a99f9cc5 | ||
|
|
b1979f63ef |
2
.github/dependabot.yml
vendored
2
.github/dependabot.yml
vendored
@@ -4,4 +4,4 @@ updates:
|
||||
directory: '/'
|
||||
schedule:
|
||||
interval: monthly
|
||||
open-pull-requests-limit: 10
|
||||
open-pull-requests-limit: 2
|
||||
|
||||
@@ -254,6 +254,7 @@ Configuration on the client side involves passing an object on startup, which ca
|
||||
* `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`.
|
||||
* `requestHistoryLimit` - Request history limit. Increasing this limit may affect browser performance, especially with debug (logs) enabled. Default is 10.
|
||||
|
||||
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).
|
||||
|
||||
|
||||
124
package-lock.json
generated
124
package-lock.json
generated
@@ -9,8 +9,8 @@
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@sasjs/utils": "2.35.0",
|
||||
"axios": "0.25.0",
|
||||
"axios-cookiejar-support": "2.0.3",
|
||||
"axios": "0.26.0",
|
||||
"axios-cookiejar-support": "1.0.1",
|
||||
"form-data": "4.0.0",
|
||||
"https": "1.0.0",
|
||||
"tough-cookie": "4.0.0"
|
||||
@@ -1694,8 +1694,7 @@
|
||||
"node_modules/@types/tough-cookie": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.1.tgz",
|
||||
"integrity": "sha512-Y0K95ThC3esLEYD6ZuqNek29lNX2EM1qxV8y2FTLUB0ff5wWrk7az+mLrnNFUnaXcgKye22+sFBRXOgpPILZNg==",
|
||||
"dev": true
|
||||
"integrity": "sha512-Y0K95ThC3esLEYD6ZuqNek29lNX2EM1qxV8y2FTLUB0ff5wWrk7az+mLrnNFUnaXcgKye22+sFBRXOgpPILZNg=="
|
||||
},
|
||||
"node_modules/@types/yargs": {
|
||||
"version": "16.0.4",
|
||||
@@ -1981,6 +1980,7 @@
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
||||
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"debug": "4"
|
||||
},
|
||||
@@ -1992,6 +1992,7 @@
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz",
|
||||
"integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ms": "2.1.2"
|
||||
},
|
||||
@@ -2007,7 +2008,8 @@
|
||||
"node_modules/agent-base/node_modules/ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/aggregate-error": {
|
||||
"version": "3.1.0",
|
||||
@@ -2205,26 +2207,28 @@
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.25.0.tgz",
|
||||
"integrity": "sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==",
|
||||
"version": "0.26.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.26.0.tgz",
|
||||
"integrity": "sha512-lKoGLMYtHvFrPVt3r+RBMp9nh34N0M8zEfCWqdWZx6phynIEhQqAdydpyBAAG211zlhX9Rgu08cOamy6XjE5Og==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.14.7"
|
||||
"follow-redirects": "^1.14.8"
|
||||
}
|
||||
},
|
||||
"node_modules/axios-cookiejar-support": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/axios-cookiejar-support/-/axios-cookiejar-support-2.0.3.tgz",
|
||||
"integrity": "sha512-tvMB+0JhxXLjjvePsXzqXhBI4DMlW4ImR4pKKNl+xclwF0IviNV+CkuhubQCCFjPzOXv7PIzOq3z7WFiF9pMpw==",
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/axios-cookiejar-support/-/axios-cookiejar-support-1.0.1.tgz",
|
||||
"integrity": "sha512-IZJxnAJ99XxiLqNeMOqrPbfR7fRyIfaoSLdPUf4AMQEGkH8URs0ghJK/xtqBsD+KsSr3pKl4DEQjCn834pHMig==",
|
||||
"dependencies": {
|
||||
"http-cookie-agent": "^1.0.2"
|
||||
"is-redirect": "^1.0.0",
|
||||
"pify": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.19.0 <13.0.0 || >=14.5.0"
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"axios": ">=0.20.0",
|
||||
"tough-cookie": ">=4.0.0"
|
||||
"@types/tough-cookie": ">=2.3.3",
|
||||
"axios": ">=0.16.2",
|
||||
"tough-cookie": ">=2.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/babel-jest": {
|
||||
@@ -4097,9 +4101,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.14.7",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz",
|
||||
"integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==",
|
||||
"version": "1.14.8",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz",
|
||||
"integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
@@ -4587,20 +4591,6 @@
|
||||
"integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/http-cookie-agent": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/http-cookie-agent/-/http-cookie-agent-1.0.4.tgz",
|
||||
"integrity": "sha512-2gSYSE3m38/QMKPPbbS/6lEIm8OTbGA5sY3aBLtr/BUTNOFNeL4WqkdNQM6WmIBT7AWKC20aYfU3uq+cgR+Byg==",
|
||||
"dependencies": {
|
||||
"agent-base": "^6.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.19.0 <13.0.0 || >=14.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tough-cookie": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/http-errors": {
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
|
||||
@@ -5148,6 +5138,14 @@
|
||||
"integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/is-redirect": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz",
|
||||
"integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-regex": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
|
||||
@@ -10366,6 +10364,17 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/pify": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz",
|
||||
"integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/pirates": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz",
|
||||
@@ -14501,8 +14510,7 @@
|
||||
"@types/tough-cookie": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.1.tgz",
|
||||
"integrity": "sha512-Y0K95ThC3esLEYD6ZuqNek29lNX2EM1qxV8y2FTLUB0ff5wWrk7az+mLrnNFUnaXcgKye22+sFBRXOgpPILZNg==",
|
||||
"dev": true
|
||||
"integrity": "sha512-Y0K95ThC3esLEYD6ZuqNek29lNX2EM1qxV8y2FTLUB0ff5wWrk7az+mLrnNFUnaXcgKye22+sFBRXOgpPILZNg=="
|
||||
},
|
||||
"@types/yargs": {
|
||||
"version": "16.0.4",
|
||||
@@ -14757,6 +14765,7 @@
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
||||
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"debug": "4"
|
||||
},
|
||||
@@ -14765,6 +14774,7 @@
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz",
|
||||
"integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "2.1.2"
|
||||
}
|
||||
@@ -14772,7 +14782,8 @@
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -14932,19 +14943,20 @@
|
||||
"dev": true
|
||||
},
|
||||
"axios": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.25.0.tgz",
|
||||
"integrity": "sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==",
|
||||
"version": "0.26.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.26.0.tgz",
|
||||
"integrity": "sha512-lKoGLMYtHvFrPVt3r+RBMp9nh34N0M8zEfCWqdWZx6phynIEhQqAdydpyBAAG211zlhX9Rgu08cOamy6XjE5Og==",
|
||||
"requires": {
|
||||
"follow-redirects": "^1.14.7"
|
||||
"follow-redirects": "^1.14.8"
|
||||
}
|
||||
},
|
||||
"axios-cookiejar-support": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/axios-cookiejar-support/-/axios-cookiejar-support-2.0.3.tgz",
|
||||
"integrity": "sha512-tvMB+0JhxXLjjvePsXzqXhBI4DMlW4ImR4pKKNl+xclwF0IviNV+CkuhubQCCFjPzOXv7PIzOq3z7WFiF9pMpw==",
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/axios-cookiejar-support/-/axios-cookiejar-support-1.0.1.tgz",
|
||||
"integrity": "sha512-IZJxnAJ99XxiLqNeMOqrPbfR7fRyIfaoSLdPUf4AMQEGkH8URs0ghJK/xtqBsD+KsSr3pKl4DEQjCn834pHMig==",
|
||||
"requires": {
|
||||
"http-cookie-agent": "^1.0.2"
|
||||
"is-redirect": "^1.0.0",
|
||||
"pify": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"babel-jest": {
|
||||
@@ -16454,9 +16466,9 @@
|
||||
}
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.14.7",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz",
|
||||
"integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ=="
|
||||
"version": "1.14.8",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz",
|
||||
"integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA=="
|
||||
},
|
||||
"foreach": {
|
||||
"version": "2.0.5",
|
||||
@@ -16803,14 +16815,6 @@
|
||||
"integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
|
||||
"dev": true
|
||||
},
|
||||
"http-cookie-agent": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/http-cookie-agent/-/http-cookie-agent-1.0.4.tgz",
|
||||
"integrity": "sha512-2gSYSE3m38/QMKPPbbS/6lEIm8OTbGA5sY3aBLtr/BUTNOFNeL4WqkdNQM6WmIBT7AWKC20aYfU3uq+cgR+Byg==",
|
||||
"requires": {
|
||||
"agent-base": "^6.0.2"
|
||||
}
|
||||
},
|
||||
"http-errors": {
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
|
||||
@@ -17192,6 +17196,11 @@
|
||||
"integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
|
||||
"dev": true
|
||||
},
|
||||
"is-redirect": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz",
|
||||
"integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ="
|
||||
},
|
||||
"is-regex": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
|
||||
@@ -21096,6 +21105,11 @@
|
||||
"integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==",
|
||||
"dev": true
|
||||
},
|
||||
"pify": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz",
|
||||
"integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA=="
|
||||
},
|
||||
"pirates": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz",
|
||||
|
||||
@@ -74,8 +74,8 @@
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"@sasjs/utils": "2.35.0",
|
||||
"axios": "0.25.0",
|
||||
"axios-cookiejar-support": "2.0.3",
|
||||
"axios": "0.26.0",
|
||||
"axios-cookiejar-support": "1.0.1",
|
||||
"form-data": "4.0.0",
|
||||
"https": "1.0.0",
|
||||
"tough-cookie": "4.0.0"
|
||||
|
||||
@@ -6,6 +6,7 @@ const stringData: any = { table1: [{ col1: 'first col value' }] }
|
||||
|
||||
const defaultConfig: SASjsConfig = {
|
||||
serverUrl: window.location.origin,
|
||||
pathSASJS: '/SASjsApi/stp/execute',
|
||||
pathSAS9: '/SASStoredProcess/do',
|
||||
pathSASViya: '/SASJobExecution',
|
||||
appLoc: '/Public/seedapp',
|
||||
|
||||
@@ -80,7 +80,7 @@ const errorAndCsrfData: any = {
|
||||
}
|
||||
|
||||
const testTable = 'sometable'
|
||||
const testTableWithNullVars: { [key: string]: any } = {
|
||||
export const testTableWithNullVars: { [key: string]: any } = {
|
||||
[testTable]: [
|
||||
{ var1: 'string', var2: 232, nullvar: 'A' },
|
||||
{ var1: 'string', var2: 232, nullvar: 'B' },
|
||||
|
||||
95
src/SASjs.ts
95
src/SASjs.ts
@@ -27,6 +27,7 @@ import {
|
||||
ComputeJobExecutor,
|
||||
JesJobExecutor,
|
||||
Sas9JobExecutor,
|
||||
SasJsJobExecutor,
|
||||
FileUploader
|
||||
} from './job-execution'
|
||||
import { ErrorResponse } from './types/errors'
|
||||
@@ -62,6 +63,7 @@ export default class SASjs {
|
||||
private computeJobExecutor: JobExecutor | null = null
|
||||
private jesJobExecutor: JobExecutor | null = null
|
||||
private sas9JobExecutor: JobExecutor | null = null
|
||||
private sasJsJobExecutor: JobExecutor | null = null
|
||||
|
||||
constructor(config?: Partial<SASjsConfig>) {
|
||||
this.sasjsConfig = {
|
||||
@@ -76,6 +78,12 @@ export default class SASjs {
|
||||
return this.requestClient?.getCsrfToken(type)
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the sas code against SAS9 server
|
||||
* @param linesOfCode - lines of sas code from the file to run.
|
||||
* @param username - a string representing the username.
|
||||
* @param password - a string representing the password.
|
||||
*/
|
||||
public async executeScriptSAS9(
|
||||
linesOfCode: string[],
|
||||
userName: string,
|
||||
@@ -90,6 +98,38 @@ export default class SASjs {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the sas code against SASViya server
|
||||
* @param fileName - name of the file to run. It will be converted to path to the file being submitted for execution.
|
||||
* @param linesOfCode - lines of sas code from the file to run.
|
||||
* @param contextName - context name on which code will be run on the server.
|
||||
* @param authConfig - (optional) the access token, refresh token, client and secret for authorizing the request.
|
||||
* @param debug - (optional) if true, global debug config will be overriden
|
||||
*/
|
||||
public async executeScriptSASViya(
|
||||
fileName: string,
|
||||
linesOfCode: string[],
|
||||
contextName: string,
|
||||
authConfig?: AuthConfig,
|
||||
debug?: boolean
|
||||
) {
|
||||
this.isMethodSupported('executeScriptSASViya', [ServerType.SasViya])
|
||||
if (!contextName) {
|
||||
throw new Error(
|
||||
'Context name is undefined. Please set a `contextName` in your SASjs or override config.'
|
||||
)
|
||||
}
|
||||
|
||||
return await this.sasViyaApiClient!.executeScript(
|
||||
fileName,
|
||||
linesOfCode,
|
||||
contextName,
|
||||
authConfig,
|
||||
null,
|
||||
debug ? debug : this.sasjsConfig.debug
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets compute contexts.
|
||||
* @param accessToken - an access token for an authorized user.
|
||||
@@ -253,38 +293,6 @@ export default class SASjs {
|
||||
return await this.sasViyaApiClient!.createSession(contextName, accessToken)
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the sas code against given sas server
|
||||
* @param fileName - name of the file to run. It will be converted to path to the file being submitted for execution.
|
||||
* @param linesOfCode - lines of sas code from the file to run.
|
||||
* @param contextName - context name on which code will be run on the server.
|
||||
* @param authConfig - (optional) the access token, refresh token, client and secret for authorizing the request.
|
||||
* @param debug - (optional) if true, global debug config will be overriden
|
||||
*/
|
||||
public async executeScriptSASViya(
|
||||
fileName: string,
|
||||
linesOfCode: string[],
|
||||
contextName: string,
|
||||
authConfig?: AuthConfig,
|
||||
debug?: boolean
|
||||
) {
|
||||
this.isMethodSupported('executeScriptSASViya', [ServerType.SasViya])
|
||||
if (!contextName) {
|
||||
throw new Error(
|
||||
'Context name is undefined. Please set a `contextName` in your SASjs or override config.'
|
||||
)
|
||||
}
|
||||
|
||||
return await this.sasViyaApiClient!.executeScript(
|
||||
fileName,
|
||||
linesOfCode,
|
||||
contextName,
|
||||
authConfig,
|
||||
null,
|
||||
debug ? debug : this.sasjsConfig.debug
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a folder in the logical SAS folder tree
|
||||
* @param folderName - name of the folder to be created.
|
||||
@@ -675,7 +683,16 @@ export default class SASjs {
|
||||
const validationResult = this.validateInput(data)
|
||||
|
||||
if (validationResult.status) {
|
||||
if (
|
||||
if (config.serverType === ServerType.Sasjs) {
|
||||
return await this.sasJsJobExecutor!.execute(
|
||||
sasJob,
|
||||
data,
|
||||
config,
|
||||
loginRequiredCallback,
|
||||
authConfig,
|
||||
extraResponseAttributes
|
||||
)
|
||||
} else if (
|
||||
config.serverType !== ServerType.Sas9 &&
|
||||
config.useComputeApi !== undefined &&
|
||||
config.useComputeApi !== null
|
||||
@@ -1017,7 +1034,8 @@ export default class SASjs {
|
||||
: RequestClient
|
||||
this.requestClient = new RequestClientClass(
|
||||
this.sasjsConfig.serverUrl,
|
||||
this.sasjsConfig.httpsAgentOptions
|
||||
this.sasjsConfig.httpsAgentOptions,
|
||||
this.sasjsConfig.requestHistoryLimit
|
||||
)
|
||||
} else {
|
||||
this.requestClient.setConfig(
|
||||
@@ -1031,7 +1049,7 @@ export default class SASjs {
|
||||
? this.sasjsConfig.pathSASViya
|
||||
: this.sasjsConfig.serverType === ServerType.Sas9
|
||||
? this.sasjsConfig.pathSAS9
|
||||
: this.sasjsConfig.pathSASJS || ''
|
||||
: this.sasjsConfig.pathSASJS
|
||||
|
||||
this.authManager = new AuthManager(
|
||||
this.sasjsConfig.serverUrl,
|
||||
@@ -1096,6 +1114,13 @@ export default class SASjs {
|
||||
this.sasViyaApiClient!
|
||||
)
|
||||
|
||||
this.sasJsJobExecutor = new SasJsJobExecutor(
|
||||
this.sasjsConfig.serverUrl,
|
||||
this.sasjsConfig.serverType!,
|
||||
this.jobsPath,
|
||||
this.requestClient
|
||||
)
|
||||
|
||||
this.sas9JobExecutor = new Sas9JobExecutor(
|
||||
this.sasjsConfig.serverUrl,
|
||||
this.sasjsConfig.serverType!,
|
||||
|
||||
@@ -4,6 +4,7 @@ import { RequestClient } from './request/RequestClient'
|
||||
import { getAccessTokenForSasjs } from './auth/getAccessTokenForSasjs'
|
||||
import { refreshTokensForSasjs } from './auth/refreshTokensForSasjs'
|
||||
import { getAuthCodeForSasjs } from './auth/getAuthCodeForSasjs'
|
||||
import { parseWeboutResponse } from './utils'
|
||||
import { getTokens } from './auth/getTokens'
|
||||
|
||||
export class SASjsApiClient {
|
||||
@@ -49,8 +50,13 @@ export class SASjsApiClient {
|
||||
log?: string
|
||||
logPath?: string
|
||||
error?: {}
|
||||
_webout?: string
|
||||
}>('SASjsApi/stp/execute', query, undefined)
|
||||
|
||||
if (Object.keys(result).includes('_webout')) {
|
||||
result._webout = parseWeboutResponse(result._webout!)
|
||||
}
|
||||
|
||||
return Promise.resolve(result)
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,11 @@ export const generateFileUploadForm = (
|
||||
data: any
|
||||
): FormData => {
|
||||
for (const tableName in data) {
|
||||
if (!Array.isArray(data[tableName])) continue
|
||||
|
||||
const name = tableName
|
||||
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.'
|
||||
|
||||
55
src/file/spec/generateFileUploadForm.spec.ts
Normal file
55
src/file/spec/generateFileUploadForm.spec.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { generateFileUploadForm } from '../generateFileUploadForm'
|
||||
|
||||
describe('generateFileUploadForm', () => {
|
||||
beforeAll(() => {
|
||||
function FormDataMock(this: any) {
|
||||
this.append = () => {}
|
||||
}
|
||||
|
||||
const BlobMock = jest.fn()
|
||||
|
||||
;(global as any).FormData = FormDataMock
|
||||
;(global as any).Blob = BlobMock
|
||||
})
|
||||
|
||||
it('should generate file upload form from data', () => {
|
||||
const formData = new FormData()
|
||||
const testTable = 'sometable'
|
||||
const testTableWithNullVars: { [key: string]: any } = {
|
||||
[testTable]: [
|
||||
{ var1: 'string', var2: 232, nullvar: 'A' },
|
||||
{ var1: 'string', var2: 232, nullvar: 'B' },
|
||||
{ var1: 'string', var2: 232, nullvar: '_' },
|
||||
{ var1: 'string', var2: 232, nullvar: 0 },
|
||||
{ var1: 'string', var2: 232, nullvar: 'z' },
|
||||
{ var1: 'string', var2: 232, nullvar: null }
|
||||
],
|
||||
[`$${testTable}`]: { formats: { var1: '$char12.', nullvar: 'best.' } }
|
||||
}
|
||||
const tableName = Object.keys(testTableWithNullVars).filter((key: string) =>
|
||||
Array.isArray(testTableWithNullVars[key])
|
||||
)[0]
|
||||
|
||||
jest.spyOn(formData, 'append').mockImplementation(() => {})
|
||||
|
||||
generateFileUploadForm(formData, testTableWithNullVars)
|
||||
|
||||
expect(formData.append).toHaveBeenCalledOnce()
|
||||
expect(formData.append).toHaveBeenCalledWith(
|
||||
tableName,
|
||||
{},
|
||||
`${tableName}.csv`
|
||||
)
|
||||
})
|
||||
|
||||
it('should throw an error if too large string was provided', () => {
|
||||
const formData = new FormData()
|
||||
const data = { testTable: [{ var1: 'z'.repeat(32765 + 1) }] }
|
||||
|
||||
expect(() => generateFileUploadForm(formData, data)).toThrow(
|
||||
new Error(
|
||||
'The max length of a string value in SASjs is 32765 characters.'
|
||||
)
|
||||
)
|
||||
})
|
||||
})
|
||||
131
src/job-execution/SasJsJobExecutor.ts
Normal file
131
src/job-execution/SasJsJobExecutor.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
import {
|
||||
AuthConfig,
|
||||
ExtraResponseAttributes,
|
||||
ServerType
|
||||
} from '@sasjs/utils/types'
|
||||
import {
|
||||
ErrorResponse,
|
||||
JobExecutionError,
|
||||
LoginRequiredError
|
||||
} from '../types/errors'
|
||||
import { RequestClient } from '../request/RequestClient'
|
||||
import {
|
||||
isRelativePath,
|
||||
appendExtraResponseAttributes,
|
||||
getValidJson
|
||||
} from '../utils'
|
||||
import { BaseJobExecutor } from './JobExecutor'
|
||||
import { parseWeboutResponse } from '../utils/parseWeboutResponse'
|
||||
|
||||
export class SasJsJobExecutor extends BaseJobExecutor {
|
||||
constructor(
|
||||
serverUrl: string,
|
||||
serverType: ServerType,
|
||||
private jobsPath: string,
|
||||
private requestClient: RequestClient
|
||||
) {
|
||||
super(serverUrl, serverType)
|
||||
}
|
||||
|
||||
async execute(
|
||||
sasJob: string,
|
||||
data: any,
|
||||
config: any,
|
||||
loginRequiredCallback?: any,
|
||||
authConfig?: AuthConfig,
|
||||
extraResponseAttributes: ExtraResponseAttributes[] = []
|
||||
) {
|
||||
const loginCallback = loginRequiredCallback || (() => Promise.resolve())
|
||||
const program = isRelativePath(sasJob)
|
||||
? config.appLoc
|
||||
? config.appLoc.replace(/\/?$/, '/') + sasJob.replace(/^\//, '')
|
||||
: sasJob
|
||||
: sasJob
|
||||
let apiUrl = `${config.serverUrl}${this.jobsPath}/?${'_program=' + program}`
|
||||
|
||||
const requestParams = this.getRequestParams(config)
|
||||
|
||||
const requestPromise = new Promise((resolve, reject) => {
|
||||
this.requestClient!.post(
|
||||
apiUrl,
|
||||
{ ...requestParams, ...data },
|
||||
authConfig?.access_token
|
||||
)
|
||||
.then(async (res: any) => {
|
||||
const parsedSasjsServerLog = res.result.log
|
||||
.map((logLine: any) => logLine.line)
|
||||
.join('\n')
|
||||
|
||||
const resObj = {
|
||||
result: res.result._webout,
|
||||
log: parsedSasjsServerLog
|
||||
}
|
||||
this.requestClient!.appendRequest(resObj, sasJob, config.debug)
|
||||
|
||||
let jsonResponse = res.result
|
||||
|
||||
if (config.debug) {
|
||||
if (typeof res.result._webout === 'object') {
|
||||
jsonResponse = res.result._webout
|
||||
} else {
|
||||
const webout = parseWeboutResponse(res.result._webout, apiUrl)
|
||||
jsonResponse = getValidJson(webout)
|
||||
}
|
||||
} else {
|
||||
jsonResponse = getValidJson(res.result._webout)
|
||||
}
|
||||
|
||||
const responseObject = appendExtraResponseAttributes(
|
||||
{ result: jsonResponse },
|
||||
extraResponseAttributes
|
||||
)
|
||||
resolve(responseObject)
|
||||
})
|
||||
.catch(async (e: Error) => {
|
||||
if (e instanceof JobExecutionError) {
|
||||
this.requestClient!.appendRequest(e, sasJob, config.debug)
|
||||
reject(new ErrorResponse(e?.message, e))
|
||||
}
|
||||
|
||||
if (e instanceof LoginRequiredError) {
|
||||
this.appendWaitingRequest(() => {
|
||||
return this.execute(
|
||||
sasJob,
|
||||
data,
|
||||
config,
|
||||
loginRequiredCallback,
|
||||
authConfig,
|
||||
extraResponseAttributes
|
||||
).then(
|
||||
(res: any) => {
|
||||
resolve(res)
|
||||
},
|
||||
(err: any) => {
|
||||
reject(err)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
await loginCallback()
|
||||
} else {
|
||||
reject(new ErrorResponse(e?.message, e))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return requestPromise
|
||||
}
|
||||
|
||||
private getRequestParams(config: any): any {
|
||||
const requestParams: any = {}
|
||||
|
||||
if (config.debug) {
|
||||
requestParams['_omittextlog'] = 'false'
|
||||
requestParams['_omitsessionresults'] = 'false'
|
||||
|
||||
requestParams['_debug'] = 131
|
||||
}
|
||||
|
||||
return requestParams
|
||||
}
|
||||
}
|
||||
@@ -146,11 +146,16 @@ export class WebJobExecutor extends BaseJobExecutor {
|
||||
const requestPromise = new Promise((resolve, reject) => {
|
||||
this.requestClient!.post(apiUrl, formData, authConfig?.access_token)
|
||||
.then(async (res: any) => {
|
||||
const parsedSasjsServerLog =
|
||||
this.serverType === ServerType.Sasjs
|
||||
? res.result.log.map((logLine: any) => logLine.line).join('\n')
|
||||
: res.result.log
|
||||
|
||||
const resObj =
|
||||
this.serverType === ServerType.Sasjs
|
||||
? {
|
||||
result: res.result._webout,
|
||||
log: res.result.log
|
||||
log: parsedSasjsServerLog
|
||||
}
|
||||
: res
|
||||
this.requestClient!.appendRequest(resObj, sasJob, config.debug)
|
||||
@@ -173,8 +178,12 @@ export class WebJobExecutor extends BaseJobExecutor {
|
||||
: res.result
|
||||
break
|
||||
case ServerType.Sasjs:
|
||||
const webout = parseWeboutResponse(res.result._webout, apiUrl)
|
||||
jsonResponse = getValidJson(webout)
|
||||
if (typeof res.result._webout === 'object') {
|
||||
jsonResponse = res.result._webout
|
||||
} else {
|
||||
const webout = parseWeboutResponse(res.result._webout, apiUrl)
|
||||
jsonResponse = getValidJson(webout)
|
||||
}
|
||||
break
|
||||
}
|
||||
} else if (this.serverType === ServerType.Sasjs) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export * from './ComputeJobExecutor'
|
||||
export * from './FileUploader'
|
||||
export * from './JesJobExecutor'
|
||||
export * from './JobExecutor'
|
||||
export * from './Sas9JobExecutor'
|
||||
export * from './SasJsJobExecutor'
|
||||
export * from './WebJobExecutor'
|
||||
export * from './FileUploader'
|
||||
|
||||
@@ -56,6 +56,7 @@ export interface HttpClient {
|
||||
|
||||
export class RequestClient implements HttpClient {
|
||||
private requests: SASjsRequest[] = []
|
||||
private requestsLimit: number = 10
|
||||
|
||||
protected csrfToken: CsrfToken = { headerName: '', value: '' }
|
||||
protected fileUploadCsrfToken: CsrfToken | undefined
|
||||
@@ -63,9 +64,11 @@ export class RequestClient implements HttpClient {
|
||||
|
||||
constructor(
|
||||
protected baseUrl: string,
|
||||
httpsAgentOptions?: https.AgentOptions
|
||||
httpsAgentOptions?: https.AgentOptions,
|
||||
requestsLimit?: number
|
||||
) {
|
||||
this.createHttpClient(baseUrl, httpsAgentOptions)
|
||||
if (requestsLimit) this.requestsLimit = requestsLimit
|
||||
}
|
||||
|
||||
public setConfig(baseUrl: string, httpsAgentOptions?: https.AgentOptions) {
|
||||
@@ -149,7 +152,7 @@ export class RequestClient implements HttpClient {
|
||||
SASWORK: sasWork
|
||||
})
|
||||
|
||||
if (this.requests.length > 20) {
|
||||
if (this.requests.length > this.requestsLimit) {
|
||||
this.requests.splice(0, 1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as https from 'https'
|
||||
import { AxiosRequestConfig } from 'axios'
|
||||
import * as axiosCookieJarSupport from 'axios-cookiejar-support'
|
||||
import axiosCookieJarSupport from 'axios-cookiejar-support'
|
||||
import * as tough from 'tough-cookie'
|
||||
import { prefixMessage } from '@sasjs/utils/error'
|
||||
import { RequestClient, throwIfError } from './RequestClient'
|
||||
@@ -17,7 +17,7 @@ export class Sas9RequestClient extends RequestClient {
|
||||
status >= 200 && status < 303
|
||||
|
||||
if (axiosCookieJarSupport) {
|
||||
axiosCookieJarSupport.wrapper(this.httpClient)
|
||||
axiosCookieJarSupport(this.httpClient)
|
||||
this.httpClient.defaults.jar = new tough.CookieJar()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ export class SASjsConfig {
|
||||
* The location of the STP Process Web Application. By default the adapter
|
||||
* will use '/SASjsApi/stp/execute' on SAS JS.
|
||||
*/
|
||||
pathSASJS?: string = ''
|
||||
pathSASJS: string = ''
|
||||
/**
|
||||
* The location of the Stored Process Web Application. By default the adapter
|
||||
* will use '/SASStoredProcess/do' on SAS 9.
|
||||
@@ -60,7 +60,7 @@ export class SASjsConfig {
|
||||
*/
|
||||
useComputeApi: boolean | null = null
|
||||
/**
|
||||
* Optional settings to configure HTTPS Agent.
|
||||
* Optional setting to configure HTTPS Agent.
|
||||
* By providing `key`, `cert`, `ca` to connect with server
|
||||
* Other options can be set `rejectUnauthorized` and `requestCert`
|
||||
*/
|
||||
@@ -69,6 +69,11 @@ export class SASjsConfig {
|
||||
* Supported login mechanisms are - Redirected and Default
|
||||
*/
|
||||
loginMechanism: LoginMechanism = LoginMechanism.Default
|
||||
/**
|
||||
* Optional setting to configure request history limit. Increasing this limit
|
||||
* may affect browser performance, especially with debug (logs) enabled.
|
||||
*/
|
||||
requestHistoryLimit?: number = 10
|
||||
}
|
||||
|
||||
export enum LoginMechanism {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* @param data - the array of JSON objects to convert.
|
||||
*/
|
||||
export const convertToCSV = (
|
||||
data: any,
|
||||
data: any[],
|
||||
sasFormats?: { formats: { [key: string]: string } }
|
||||
) => {
|
||||
let formats = sasFormats?.formats
|
||||
@@ -76,7 +76,7 @@ export const convertToCSV = (
|
||||
return byteSize
|
||||
}
|
||||
})
|
||||
.sort((a: number, b: number) => b - a)[0]
|
||||
.sort((a: any, b: any) => b - a)[0]
|
||||
|
||||
if (longestValueForField && longestValueForField > 32765) {
|
||||
invalidString = true
|
||||
|
||||
@@ -10,6 +10,7 @@ export const parseWeboutResponse = (response: string, url?: string): string => {
|
||||
.split('>>weboutEND<<')[0]
|
||||
} catch (e) {
|
||||
if (url) throw new WeboutResponseError(url)
|
||||
|
||||
sasResponse = ''
|
||||
console.error(e)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user