1
0
mirror of https://github.com/sasjs/adapter.git synced 2026-01-03 18:50:05 +00:00

Compare commits

...

52 Commits

Author SHA1 Message Date
Krishna Acondy
619833db29 Merge pull request #56 from sasjs/performance-improvements
fix(*): Performance improvements
2020-09-01 11:49:34 +01:00
Krishna Acondy
a587d9f6de chore(ci): add lint action 2020-09-01 11:20:46 +01:00
Krishna Acondy
83fb89f779 fix(*): cache job definition code after first fetch, make initial state request before poll 2020-09-01 11:13:52 +01:00
Krishna Acondy
6b98bbce7c chore(types): add code property to Job model 2020-09-01 11:12:56 +01:00
Krishna Acondy
3c2487e423 Merge pull request #49 from sasjs/dependabot/npm_and_yarn/ts-loader-8.0.3
chore(deps-dev): bump ts-loader from 8.0.2 to 8.0.3
2020-08-31 10:23:01 +01:00
dependabot-preview[bot]
0d52af5375 chore(deps-dev): bump ts-loader from 8.0.2 to 8.0.3
Bumps [ts-loader](https://github.com/TypeStrong/ts-loader) from 8.0.2 to 8.0.3.
- [Release notes](https://github.com/TypeStrong/ts-loader/releases)
- [Changelog](https://github.com/TypeStrong/ts-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/TypeStrong/ts-loader/compare/v8.0.2...v8.0.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-08-31 09:04:08 +00:00
Krishna Acondy
d0da343efc Merge pull request #53 from sasjs/dependabot/npm_and_yarn/prettier-2.1.1
chore(deps-dev): bump prettier from 2.0.5 to 2.1.1
2020-08-31 09:58:58 +01:00
dependabot-preview[bot]
54f401a319 chore(deps-dev): bump prettier from 2.0.5 to 2.1.1
Bumps [prettier](https://github.com/prettier/prettier) from 2.0.5 to 2.1.1.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/master/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/2.0.5...2.1.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-08-31 08:24:44 +00:00
Krishna Acondy
5efcb11b7d chore(*): add lint fix command 2020-08-31 09:22:21 +01:00
Krishna Acondy
929d7b993b chore(*): add .prettierrc, fix formatting 2020-08-31 09:15:02 +01:00
Allan Bowe
688221c042 Update example.html 2020-08-28 20:11:04 +02:00
Allan Bowe
57d0b30f47 Merge pull request #52 from sasjs/deploy-issue
fix: members of type folder should be processed first on service pack deploy.
2020-08-28 20:00:43 +02:00
0d5af2487d fix: members of type folder should be proccessed first on service pack deploy 2020-08-28 15:30:53 +03:00
1ea163fd03 fix: members of type folder should be proccessed first on service pack deploy 2020-08-28 14:31:35 +03:00
Allan Bowe
f27444bc52 Merge pull request #48 from sasjs/issue47
fix: login not working on non english browsers
2020-08-23 18:24:36 +02:00
Mihajlo Medjedovic
de426c9a92 fix: login not working on non english browsers 2020-08-23 18:17:35 +02:00
Allan Bowe
a006ead205 Merge pull request #46 from sasjs/issue45
fix: jobViaWeb config override
2020-08-23 17:07:47 +02:00
Mihajlo Medjedovic
422c2a1fd5 fix: jobViaWeb config override 2020-08-23 17:04:39 +02:00
Krishna Acondy
0c6409e402 Merge pull request #44 from sasjs/session-expiry-retry
fix(session-expiry-retry): retry job with new session on expiry
2020-08-18 21:45:47 +01:00
Krishna Acondy
68b864cf75 fix(session-expiry): discard and create new session if expired 2020-08-18 21:35:02 +01:00
Krishna Acondy
75a11cdff4 fix(session-expiry-retry): retry job with new session when current session has expired 2020-08-18 20:23:59 +01:00
Allan Bowe
4e2b6d32cc Merge pull request #43 from sasjs/parse-compute-log
fix(log): use compute log directly when available
2020-08-18 18:21:56 +02:00
Krishna Acondy
cd9757b383 Merge branch 'master' into parse-compute-log 2020-08-18 17:01:33 +01:00
Mihajlo Medjedovic
fb727788d0 fix: log capture if job fails, test framework update, added test for log capture 2020-08-18 17:36:25 +02:00
Allan Bowe
35eb6c4935 Merge pull request #40 from sasjs/dependabot/npm_and_yarn/types/jest-26.0.10
chore(deps-dev): bump @types/jest from 26.0.9 to 26.0.10
2020-08-18 13:26:57 +02:00
Mihajlo Medjedovic
ea0f338b90 Merge branch 'master' into parse-compute-log 2020-08-18 13:26:16 +02:00
Allan Bowe
b6a17b39b9 Merge branch 'master' into dependabot/npm_and_yarn/types/jest-26.0.10 2020-08-18 13:25:28 +02:00
Allan Bowe
9ed64e5a2c Merge pull request #42 from sasjs/issue41
fix: csrfTokenWeb setter callback
2020-08-18 13:22:33 +02:00
Mihajlo Medjedovic
0479a5d651 fix: csrfTokenWeb setter callback 2020-08-18 13:17:29 +02:00
Allan Bowe
005f10bb47 Update CONTRIBUTING.md 2020-08-18 11:24:30 +02:00
Krishna Acondy
98c9cb78ff fix(log): use compute log as-is when available 2020-08-18 10:05:34 +01:00
Krishna Acondy
8192f69f67 fix(*): do not use polyfill when running on Node.js 2020-08-18 08:25:40 +01:00
dependabot-preview[bot]
c28a8ebf15 chore(deps-dev): bump @types/jest from 26.0.9 to 26.0.10
Bumps [@types/jest](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jest) from 26.0.9 to 26.0.10.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/jest)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-08-17 13:19:21 +00:00
Krishna Acondy
a409d8cdb6 Merge pull request #38 from sasjs/brwoserCheckFix
fix: isIEorEdgeOrOldFirefox error
2020-08-14 13:10:44 +01:00
Mihajlo Medjedovic
618a20eaba fix: isIEorEdgeOrOldFirefox error 2020-08-14 14:09:28 +02:00
Krishna Acondy
c9b1273c31 Merge pull request #37 from sasjs/ie-edge-fetch
fix(*): use fetch polyfill in IE, Edge and Firefox <60
2020-08-13 21:56:11 +01:00
Krishna Acondy
59674744be fix(*): use fetch polyfill for Firefox versions older than 60 2020-08-13 21:46:08 +01:00
Krishna Acondy
870cc0055b fix(*): use fetch polyfill in Firefox 60 2020-08-13 21:07:21 +01:00
Krishna Acondy
0ffa62fab4 fix(*): use fetch polyfill in IE and Edge 2020-08-13 20:53:27 +01:00
Allan Bowe
b4c7868fb6 Merge pull request #36 from sasjs/issue34
fix: job definition debug log parse
2020-08-11 18:44:39 +02:00
Mihajlo Medjedovic
2266578013 Merge branch 'master' into issue34 2020-08-11 18:29:41 +02:00
Mihajlo Medjedovic
f2ebe1a5b0 fix: job definition debug log parse 2020-08-10 18:13:11 +02:00
Allan Bowe
6a52bbe560 Merge pull request #35 from sasjs/issue33
fix: makeRequest inconsistent response structure
2020-08-10 17:19:05 +02:00
Mihajlo Medjedovic
a5c725e677 fix: makeRequest incositent response structure 2020-08-10 17:05:10 +02:00
Krishna Acondy
f5e1907e28 feat(clear-requests): add function to clear debug requests 2020-08-08 14:27:40 +01:00
Krishna Acondy
f7a9b0cbb6 fix(compute-api): ignore 404s when requesting webout content 2020-08-08 14:26:09 +01:00
Krishna Acondy
1258a1a180 fix(login-callback): fix request failure when login is required 2020-08-08 12:56:31 +01:00
Krishna Acondy
0bb343a1de chore(deps): bump adapter and test framework 2020-08-08 11:42:16 +01:00
Krishna Acondy
929c89b70f Merge pull request #32 from sasjs/dependabot/npm_and_yarn/types/jest-26.0.9
chore(deps-dev): bump @types/jest from 26.0.8 to 26.0.9
2020-08-08 11:04:25 +01:00
dependabot-preview[bot]
169ca35238 chore(deps-dev): bump @types/jest from 26.0.8 to 26.0.9
Bumps [@types/jest](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jest) from 26.0.8 to 26.0.9.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/jest)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-08-06 12:59:52 +00:00
Allan Bowe
60be28f149 Merge pull request #31 from sasjs/session-cleanup
feat(session-cleanup): delete a session after it has been used
2020-08-05 23:17:42 +02:00
Krishna Acondy
14daa55184 feat(session-cleanup): delete a session after it has been used 2020-08-05 21:52:23 +01:00
36 changed files with 930 additions and 650 deletions

View File

@@ -21,7 +21,11 @@ jobs:
uses: actions/setup-node@v1 uses: actions/setup-node@v1
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
- run: npm ci - name: Install Dependencies
- run: npm run package:lib run: npm ci
- name: Check code style
run: npm run lint
- name: Build Package
run: npm run package:lib
env: env:
CI: true CI: true

View File

@@ -16,6 +16,8 @@ jobs:
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Install Dependencies - name: Install Dependencies
run: npm ci run: npm ci
- name: Check code style
run: npm run lint
- name: Build Project - name: Build Project
run: npm run build run: npm run build
- name: Semantic Release - name: Semantic Release

6
.prettierrc Normal file
View File

@@ -0,0 +1,6 @@
{
"trailingComma": "none",
"tabWidth": 2,
"semi": true,
"singleQuote": false
}

View File

@@ -70,6 +70,14 @@ parmcards4;
%webout(CLOSE) %webout(CLOSE)
;;;; ;;;;
%mp_createwebservice(path=/Public/app/common,name=sendArr) %mp_createwebservice(path=/Public/app/common,name=sendArr)
filename ft15f001 temp;
parmcards4;
If you can keep your head when all about you
Are losing theirs and blaming it on you,
If you can trust yourself when all men doubt you,
But make allowance for their doubting too;
;;;;
%mp_createwebservice(path=/Public/app/common,name=makeErr)
``` ```
The above services will return anything you send. To run the tests simply launch `npm run cypress`. The above services will return anything you send. To run the tests simply launch `npm run cypress`.

View File

@@ -1,7 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<script src="https://cdn.jsdelivr.net/combine/npm/chart.js@2.9.3,npm/jquery@3.5.1,npm/@sasjs/adapter@1.0.6"></script> <script src="https://cdn.jsdelivr.net/combine/npm/chart.js@2.9.3,npm/jquery@3.5.1,npm/@sasjs/adapter@1"></script>
<script> <script>
var sasJs = new SASjs.default({ var sasJs = new SASjs.default({
appLoc: "/Public/app/readme" appLoc: "/Public/app/readme"
@@ -106,4 +106,4 @@
<canvas id="myChart" style="display: none;"></canvas> <canvas id="myChart" style="display: none;"></canvas>
</div> </div>
</body> </body>
</head> </head>

441
package-lock.json generated
View File

@@ -1648,9 +1648,9 @@
} }
}, },
"@types/jest": { "@types/jest": {
"version": "26.0.8", "version": "26.0.10",
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.8.tgz", "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.10.tgz",
"integrity": "sha512-eo3VX9jGASSuv680D4VQ89UmuLZneNxv2MCZjfwlInav05zXVJTzfc//lavdV0GPwSxsXJTy2jALscB7Acqg0g==", "integrity": "sha512-i2m0oyh8w/Lum7wWK/YOZJakYF8Mx08UaKA1CtbmFeDquVhAEdA7znacsVSf2hJ1OQ/OfVMGN90pw/AtzF8s/Q==",
"dev": true, "dev": true,
"requires": { "requires": {
"jest-diff": "^25.2.1", "jest-diff": "^25.2.1",
@@ -3076,13 +3076,13 @@
"dev": true "dev": true
}, },
"compare-func": { "compare-func": {
"version": "1.3.4", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/compare-func/-/compare-func-1.3.4.tgz", "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz",
"integrity": "sha512-sq2sWtrqKPkEXAC8tEJA1+BqAH9GbFkGBtUOqrUX57VSfwp8xyktctk+uLoRy5eccTdxzDcVIztlYDpKs3Jv1Q==", "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==",
"dev": true, "dev": true,
"requires": { "requires": {
"array-ify": "^1.0.0", "array-ify": "^1.0.0",
"dot-prop": "^3.0.0" "dot-prop": "^5.1.0"
} }
}, },
"component-emitter": { "component-emitter": {
@@ -3122,22 +3122,49 @@
"dev": true "dev": true
}, },
"conventional-changelog-angular": { "conventional-changelog-angular": {
"version": "5.0.10", "version": "5.0.11",
"resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.10.tgz", "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.11.tgz",
"integrity": "sha512-k7RPPRs0vp8+BtPsM9uDxRl6KcgqtCJmzRD1wRtgqmhQ96g8ifBGo9O/TZBG23jqlXS/rg8BKRDELxfnQQGiaA==", "integrity": "sha512-nSLypht/1yEflhuTogC03i7DX7sOrXGsRn14g131Potqi6cbGbGEE9PSDEHKldabB6N76HiSyw9Ph+kLmC04Qw==",
"dev": true, "dev": true,
"requires": { "requires": {
"compare-func": "^1.3.1", "compare-func": "^2.0.0",
"q": "^1.5.1" "q": "^1.5.1"
},
"dependencies": {
"compare-func": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz",
"integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==",
"dev": true,
"requires": {
"array-ify": "^1.0.0",
"dot-prop": "^5.1.0"
}
},
"dot-prop": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz",
"integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==",
"dev": true,
"requires": {
"is-obj": "^2.0.0"
}
},
"is-obj": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz",
"integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==",
"dev": true
}
} }
}, },
"conventional-changelog-writer": { "conventional-changelog-writer": {
"version": "4.0.16", "version": "4.0.17",
"resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-4.0.16.tgz", "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-4.0.17.tgz",
"integrity": "sha512-jmU1sDJDZpm/dkuFxBeRXvyNcJQeKhGtVcFFkwTphUAzyYWcwz2j36Wcv+Mv2hU3tpvLMkysOPXJTLO55AUrYQ==", "integrity": "sha512-IKQuK3bib/n032KWaSb8YlBFds+aLmzENtnKtxJy3+HqDq5kohu3g/UdNbIHeJWygfnEbZjnCKFxAW0y7ArZAw==",
"dev": true, "dev": true,
"requires": { "requires": {
"compare-func": "^1.3.1", "compare-func": "^2.0.0",
"conventional-commits-filter": "^2.0.6", "conventional-commits-filter": "^2.0.6",
"dateformat": "^3.0.0", "dateformat": "^3.0.0",
"handlebars": "^4.7.6", "handlebars": "^4.7.6",
@@ -3580,12 +3607,12 @@
} }
}, },
"dot-prop": { "dot-prop": {
"version": "3.0.0", "version": "5.2.0",
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-3.0.0.tgz", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz",
"integrity": "sha1-G3CK8JSknJoOfbyteQq6U52sEXc=", "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==",
"dev": true, "dev": true,
"requires": { "requires": {
"is-obj": "^1.0.0" "is-obj": "^2.0.0"
} }
}, },
"duplexer2": { "duplexer2": {
@@ -5170,9 +5197,9 @@
"dev": true "dev": true
}, },
"is-obj": { "is-obj": {
"version": "1.0.1", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz",
"integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==",
"dev": true "dev": true
}, },
"is-plain-obj": { "is-plain-obj": {
@@ -8487,9 +8514,9 @@
"dev": true "dev": true
}, },
"npm": { "npm": {
"version": "6.14.6", "version": "6.14.8",
"resolved": "https://registry.npmjs.org/npm/-/npm-6.14.6.tgz", "resolved": "https://registry.npmjs.org/npm/-/npm-6.14.8.tgz",
"integrity": "sha512-axnz6iHFK6WPE0js/+mRp+4IOwpHn5tJEw5KB6FiCU764zmffrhsYHbSHi2kKqNkRBt53XasXjngZfBD3FQzrQ==", "integrity": "sha512-HBZVBMYs5blsj94GTeQZel7s9odVuuSUHy1+AlZh7rPVux1os2ashvEGLy/STNK7vUjbrCg5Kq9/GXisJgdf6A==",
"dev": true, "dev": true,
"requires": { "requires": {
"JSONStream": "^1.3.5", "JSONStream": "^1.3.5",
@@ -8498,7 +8525,7 @@
"ansistyles": "~0.1.3", "ansistyles": "~0.1.3",
"aproba": "^2.0.0", "aproba": "^2.0.0",
"archy": "~1.0.0", "archy": "~1.0.0",
"bin-links": "^1.1.7", "bin-links": "^1.1.8",
"bluebird": "^3.5.5", "bluebird": "^3.5.5",
"byte-size": "^5.0.1", "byte-size": "^5.0.1",
"cacache": "^12.0.3", "cacache": "^12.0.3",
@@ -8519,7 +8546,7 @@
"find-npm-prefix": "^1.0.2", "find-npm-prefix": "^1.0.2",
"fs-vacuum": "~1.2.10", "fs-vacuum": "~1.2.10",
"fs-write-stream-atomic": "~1.0.10", "fs-write-stream-atomic": "~1.0.10",
"gentle-fs": "^2.3.0", "gentle-fs": "^2.3.1",
"glob": "^7.1.6", "glob": "^7.1.6",
"graceful-fs": "^4.2.4", "graceful-fs": "^4.2.4",
"has-unicode": "~2.0.1", "has-unicode": "~2.0.1",
@@ -8534,14 +8561,14 @@
"is-cidr": "^3.0.0", "is-cidr": "^3.0.0",
"json-parse-better-errors": "^1.0.2", "json-parse-better-errors": "^1.0.2",
"lazy-property": "~1.0.0", "lazy-property": "~1.0.0",
"libcipm": "^4.0.7", "libcipm": "^4.0.8",
"libnpm": "^3.0.1", "libnpm": "^3.0.1",
"libnpmaccess": "^3.0.2", "libnpmaccess": "^3.0.2",
"libnpmhook": "^5.0.3", "libnpmhook": "^5.0.3",
"libnpmorg": "^1.0.1", "libnpmorg": "^1.0.1",
"libnpmsearch": "^2.0.2", "libnpmsearch": "^2.0.2",
"libnpmteam": "^1.0.2", "libnpmteam": "^1.0.2",
"libnpx": "^10.2.2", "libnpx": "^10.2.4",
"lock-verify": "^2.1.0", "lock-verify": "^2.1.0",
"lockfile": "^1.0.4", "lockfile": "^1.0.4",
"lodash._baseindexof": "*", "lodash._baseindexof": "*",
@@ -8556,22 +8583,22 @@
"lodash.uniq": "~4.5.0", "lodash.uniq": "~4.5.0",
"lodash.without": "~4.4.0", "lodash.without": "~4.4.0",
"lru-cache": "^5.1.1", "lru-cache": "^5.1.1",
"meant": "~1.0.1", "meant": "^1.0.2",
"mississippi": "^3.0.0", "mississippi": "^3.0.0",
"mkdirp": "^0.5.5", "mkdirp": "^0.5.5",
"move-concurrently": "^1.0.1", "move-concurrently": "^1.0.1",
"node-gyp": "^5.1.0", "node-gyp": "^5.1.0",
"nopt": "^4.0.3", "nopt": "^4.0.3",
"normalize-package-data": "^2.5.0", "normalize-package-data": "^2.5.0",
"npm-audit-report": "^1.3.2", "npm-audit-report": "^1.3.3",
"npm-cache-filename": "~1.0.2", "npm-cache-filename": "~1.0.2",
"npm-install-checks": "^3.0.2", "npm-install-checks": "^3.0.2",
"npm-lifecycle": "^3.1.4", "npm-lifecycle": "^3.1.5",
"npm-package-arg": "^6.1.1", "npm-package-arg": "^6.1.1",
"npm-packlist": "^1.4.8", "npm-packlist": "^1.4.8",
"npm-pick-manifest": "^3.0.2", "npm-pick-manifest": "^3.0.2",
"npm-profile": "^4.0.4", "npm-profile": "^4.0.4",
"npm-registry-fetch": "^4.0.5", "npm-registry-fetch": "^4.0.7",
"npm-user-validate": "~1.0.0", "npm-user-validate": "~1.0.0",
"npmlog": "~4.1.2", "npmlog": "~4.1.2",
"once": "~1.4.0", "once": "~1.4.0",
@@ -8780,7 +8807,7 @@
} }
}, },
"bin-links": { "bin-links": {
"version": "1.1.7", "version": "1.1.8",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"requires": { "requires": {
@@ -8935,26 +8962,41 @@
} }
}, },
"cliui": { "cliui": {
"version": "4.1.0", "version": "5.0.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"requires": { "requires": {
"string-width": "^2.1.1", "string-width": "^3.1.0",
"strip-ansi": "^4.0.0", "strip-ansi": "^5.2.0",
"wrap-ansi": "^2.0.0" "wrap-ansi": "^5.1.0"
}, },
"dependencies": { "dependencies": {
"ansi-regex": { "ansi-regex": {
"version": "3.0.0", "version": "4.1.0",
"bundled": true, "bundled": true,
"dev": true "dev": true
}, },
"strip-ansi": { "is-fullwidth-code-point": {
"version": "4.0.0", "version": "2.0.0",
"bundled": true,
"dev": true
},
"string-width": {
"version": "3.1.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"requires": { "requires": {
"ansi-regex": "^3.0.0" "emoji-regex": "^7.0.1",
"is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^5.1.0"
}
},
"strip-ansi": {
"version": "5.2.0",
"bundled": true,
"dev": true,
"requires": {
"ansi-regex": "^4.1.0"
} }
} }
} }
@@ -9069,11 +9111,11 @@
} }
}, },
"configstore": { "configstore": {
"version": "3.1.2", "version": "3.1.5",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"requires": { "requires": {
"dot-prop": "^4.1.0", "dot-prop": "^4.2.1",
"graceful-fs": "^4.1.2", "graceful-fs": "^4.1.2",
"make-dir": "^1.0.0", "make-dir": "^1.0.0",
"unique-string": "^1.0.0", "unique-string": "^1.0.0",
@@ -9249,7 +9291,7 @@
} }
}, },
"dot-prop": { "dot-prop": {
"version": "4.2.0", "version": "4.2.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"requires": { "requires": {
@@ -9316,6 +9358,11 @@
"bundled": true, "bundled": true,
"dev": true "dev": true
}, },
"emoji-regex": {
"version": "7.0.3",
"bundled": true,
"dev": true
},
"encoding": { "encoding": {
"version": "0.1.12", "version": "0.1.12",
"bundled": true, "bundled": true,
@@ -9441,14 +9488,6 @@
"bundled": true, "bundled": true,
"dev": true "dev": true
}, },
"find-up": {
"version": "2.1.0",
"bundled": true,
"dev": true,
"requires": {
"locate-path": "^2.0.0"
}
},
"flush-write-stream": { "flush-write-stream": {
"version": "1.0.3", "version": "1.0.3",
"bundled": true, "bundled": true,
@@ -9647,7 +9686,7 @@
"dev": true "dev": true
}, },
"gentle-fs": { "gentle-fs": {
"version": "2.3.0", "version": "2.3.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"requires": { "requires": {
@@ -9677,7 +9716,7 @@
} }
}, },
"get-caller-file": { "get-caller-file": {
"version": "1.0.3", "version": "2.0.5",
"bundled": true, "bundled": true,
"dev": true "dev": true
}, },
@@ -9901,11 +9940,6 @@
"validate-npm-package-name": "^3.0.0" "validate-npm-package-name": "^3.0.0"
} }
}, },
"invert-kv": {
"version": "2.0.0",
"bundled": true,
"dev": true
},
"ip": { "ip": {
"version": "1.1.5", "version": "1.1.5",
"bundled": true, "bundled": true,
@@ -10090,16 +10124,8 @@
"bundled": true, "bundled": true,
"dev": true "dev": true
}, },
"lcid": {
"version": "2.0.0",
"bundled": true,
"dev": true,
"requires": {
"invert-kv": "^2.0.0"
}
},
"libcipm": { "libcipm": {
"version": "4.0.7", "version": "4.0.8",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"requires": { "requires": {
@@ -10109,7 +10135,7 @@
"find-npm-prefix": "^1.0.2", "find-npm-prefix": "^1.0.2",
"graceful-fs": "^4.1.11", "graceful-fs": "^4.1.11",
"ini": "^1.3.5", "ini": "^1.3.5",
"lock-verify": "^2.0.2", "lock-verify": "^2.1.0",
"mkdirp": "^0.5.1", "mkdirp": "^0.5.1",
"npm-lifecycle": "^3.0.0", "npm-lifecycle": "^3.0.0",
"npm-logical-tree": "^1.2.1", "npm-logical-tree": "^1.2.1",
@@ -10268,7 +10294,7 @@
} }
}, },
"libnpx": { "libnpx": {
"version": "10.2.2", "version": "10.2.4",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"requires": { "requires": {
@@ -10279,16 +10305,7 @@
"update-notifier": "^2.3.0", "update-notifier": "^2.3.0",
"which": "^1.3.0", "which": "^1.3.0",
"y18n": "^4.0.0", "y18n": "^4.0.0",
"yargs": "^11.0.0" "yargs": "^14.2.3"
}
},
"locate-path": {
"version": "2.0.0",
"bundled": true,
"dev": true,
"requires": {
"p-locate": "^2.0.0",
"path-exists": "^3.0.0"
} }
}, },
"lock-verify": { "lock-verify": {
@@ -10419,36 +10436,11 @@
"ssri": "^6.0.0" "ssri": "^6.0.0"
} }
}, },
"map-age-cleaner": {
"version": "0.1.3",
"bundled": true,
"dev": true,
"requires": {
"p-defer": "^1.0.0"
}
},
"meant": { "meant": {
"version": "1.0.1", "version": "1.0.2",
"bundled": true, "bundled": true,
"dev": true "dev": true
}, },
"mem": {
"version": "4.3.0",
"bundled": true,
"dev": true,
"requires": {
"map-age-cleaner": "^0.1.1",
"mimic-fn": "^2.0.0",
"p-is-promise": "^2.0.0"
},
"dependencies": {
"mimic-fn": {
"version": "2.1.0",
"bundled": true,
"dev": true
}
}
},
"mime-db": { "mime-db": {
"version": "1.35.0", "version": "1.35.0",
"bundled": true, "bundled": true,
@@ -10470,6 +10462,11 @@
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"
} }
}, },
"minimist": {
"version": "1.2.5",
"bundled": true,
"dev": true
},
"minizlib": { "minizlib": {
"version": "1.3.3", "version": "1.3.3",
"bundled": true, "bundled": true,
@@ -10551,11 +10548,6 @@
"bundled": true, "bundled": true,
"dev": true "dev": true
}, },
"nice-try": {
"version": "1.0.5",
"bundled": true,
"dev": true
},
"node-fetch-npm": { "node-fetch-npm": {
"version": "2.0.2", "version": "2.0.2",
"bundled": true, "bundled": true,
@@ -10615,7 +10607,7 @@
} }
}, },
"npm-audit-report": { "npm-audit-report": {
"version": "1.3.2", "version": "1.3.3",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"requires": { "requires": {
@@ -10645,7 +10637,7 @@
} }
}, },
"npm-lifecycle": { "npm-lifecycle": {
"version": "3.1.4", "version": "3.1.5",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"requires": { "requires": {
@@ -10711,7 +10703,7 @@
} }
}, },
"npm-registry-fetch": { "npm-registry-fetch": {
"version": "4.0.5", "version": "4.0.7",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"requires": { "requires": {
@@ -10802,44 +10794,6 @@
"bundled": true, "bundled": true,
"dev": true "dev": true
}, },
"os-locale": {
"version": "3.1.0",
"bundled": true,
"dev": true,
"requires": {
"execa": "^1.0.0",
"lcid": "^2.0.0",
"mem": "^4.0.0"
},
"dependencies": {
"cross-spawn": {
"version": "6.0.5",
"bundled": true,
"dev": true,
"requires": {
"nice-try": "^1.0.4",
"path-key": "^2.0.1",
"semver": "^5.5.0",
"shebang-command": "^1.2.0",
"which": "^1.2.9"
}
},
"execa": {
"version": "1.0.0",
"bundled": true,
"dev": true,
"requires": {
"cross-spawn": "^6.0.0",
"get-stream": "^4.0.0",
"is-stream": "^1.1.0",
"npm-run-path": "^2.0.0",
"p-finally": "^1.0.0",
"signal-exit": "^3.0.0",
"strip-eof": "^1.0.0"
}
}
}
},
"os-tmpdir": { "os-tmpdir": {
"version": "1.0.2", "version": "1.0.2",
"bundled": true, "bundled": true,
@@ -10854,42 +10808,11 @@
"os-tmpdir": "^1.0.0" "os-tmpdir": "^1.0.0"
} }
}, },
"p-defer": {
"version": "1.0.0",
"bundled": true,
"dev": true
},
"p-finally": { "p-finally": {
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true,
"dev": true "dev": true
}, },
"p-is-promise": {
"version": "2.1.0",
"bundled": true,
"dev": true
},
"p-limit": {
"version": "1.2.0",
"bundled": true,
"dev": true,
"requires": {
"p-try": "^1.0.0"
}
},
"p-locate": {
"version": "2.0.0",
"bundled": true,
"dev": true,
"requires": {
"p-limit": "^1.1.0"
}
},
"p-try": {
"version": "1.0.0",
"bundled": true,
"dev": true
},
"package-json": { "package-json": {
"version": "4.0.1", "version": "4.0.1",
"bundled": true, "bundled": true,
@@ -11154,13 +11077,6 @@
"ini": "~1.3.0", "ini": "~1.3.0",
"minimist": "^1.2.0", "minimist": "^1.2.0",
"strip-json-comments": "~2.0.1" "strip-json-comments": "~2.0.1"
},
"dependencies": {
"minimist": {
"version": "1.2.5",
"bundled": true,
"dev": true
}
} }
}, },
"read": { "read": {
@@ -11286,7 +11202,7 @@
"dev": true "dev": true
}, },
"require-main-filename": { "require-main-filename": {
"version": "1.0.1", "version": "2.0.0",
"bundled": true, "bundled": true,
"dev": true "dev": true
}, },
@@ -11936,22 +11852,41 @@
} }
}, },
"wrap-ansi": { "wrap-ansi": {
"version": "2.1.0", "version": "5.1.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"requires": { "requires": {
"string-width": "^1.0.1", "ansi-styles": "^3.2.0",
"strip-ansi": "^3.0.1" "string-width": "^3.0.0",
"strip-ansi": "^5.0.0"
}, },
"dependencies": { "dependencies": {
"ansi-regex": {
"version": "4.1.0",
"bundled": true,
"dev": true
},
"is-fullwidth-code-point": {
"version": "2.0.0",
"bundled": true,
"dev": true
},
"string-width": { "string-width": {
"version": "1.0.2", "version": "3.1.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"requires": { "requires": {
"code-point-at": "^1.0.0", "emoji-regex": "^7.0.1",
"is-fullwidth-code-point": "^1.0.0", "is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^3.0.0" "strip-ansi": "^5.1.0"
}
},
"strip-ansi": {
"version": "5.2.0",
"bundled": true,
"dev": true,
"requires": {
"ansi-regex": "^4.1.0"
} }
} }
} }
@@ -11992,37 +11927,105 @@
"dev": true "dev": true
}, },
"yargs": { "yargs": {
"version": "11.1.1", "version": "14.2.3",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"requires": { "requires": {
"cliui": "^4.0.0", "cliui": "^5.0.0",
"decamelize": "^1.1.1", "decamelize": "^1.2.0",
"find-up": "^2.1.0", "find-up": "^3.0.0",
"get-caller-file": "^1.0.1", "get-caller-file": "^2.0.1",
"os-locale": "^3.1.0",
"require-directory": "^2.1.1", "require-directory": "^2.1.1",
"require-main-filename": "^1.0.1", "require-main-filename": "^2.0.0",
"set-blocking": "^2.0.0", "set-blocking": "^2.0.0",
"string-width": "^2.0.0", "string-width": "^3.0.0",
"which-module": "^2.0.0", "which-module": "^2.0.0",
"y18n": "^3.2.1", "y18n": "^4.0.0",
"yargs-parser": "^9.0.2" "yargs-parser": "^15.0.1"
}, },
"dependencies": { "dependencies": {
"y18n": { "ansi-regex": {
"version": "3.2.1", "version": "4.1.0",
"bundled": true, "bundled": true,
"dev": true "dev": true
},
"find-up": {
"version": "3.0.0",
"bundled": true,
"dev": true,
"requires": {
"locate-path": "^3.0.0"
}
},
"is-fullwidth-code-point": {
"version": "2.0.0",
"bundled": true,
"dev": true
},
"locate-path": {
"version": "3.0.0",
"bundled": true,
"dev": true,
"requires": {
"p-locate": "^3.0.0",
"path-exists": "^3.0.0"
}
},
"p-limit": {
"version": "2.3.0",
"bundled": true,
"dev": true,
"requires": {
"p-try": "^2.0.0"
}
},
"p-locate": {
"version": "3.0.0",
"bundled": true,
"dev": true,
"requires": {
"p-limit": "^2.0.0"
}
},
"p-try": {
"version": "2.2.0",
"bundled": true,
"dev": true
},
"string-width": {
"version": "3.1.0",
"bundled": true,
"dev": true,
"requires": {
"emoji-regex": "^7.0.1",
"is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^5.1.0"
}
},
"strip-ansi": {
"version": "5.2.0",
"bundled": true,
"dev": true,
"requires": {
"ansi-regex": "^4.1.0"
}
} }
} }
}, },
"yargs-parser": { "yargs-parser": {
"version": "9.0.2", "version": "15.0.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"requires": { "requires": {
"camelcase": "^4.1.0" "camelcase": "^5.0.0",
"decamelize": "^1.2.0"
},
"dependencies": {
"camelcase": {
"version": "5.3.1",
"bundled": true,
"dev": true
}
} }
} }
} }
@@ -12492,9 +12495,9 @@
"dev": true "dev": true
}, },
"prettier": { "prettier": {
"version": "2.0.5", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.5.tgz", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.1.1.tgz",
"integrity": "sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg==", "integrity": "sha512-9bY+5ZWCfqj3ghYBLxApy2zf6m+NJo5GzmLTpr9FsApsfjriNnS2dahWReHMi7qNPhhHl9SYHJs2cHZLgexNIw==",
"dev": true "dev": true
}, },
"pretty-format": { "pretty-format": {
@@ -14255,9 +14258,9 @@
} }
}, },
"ts-loader": { "ts-loader": {
"version": "8.0.2", "version": "8.0.3",
"resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.0.2.tgz", "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.0.3.tgz",
"integrity": "sha512-oYT7wOTUawYXQ8XIDsRhziyW0KUEV38jISYlE+9adP6tDtG+O5GkRe4QKQXrHVH4mJJ88DysvEtvGP65wMLlhg==", "integrity": "sha512-wsqfnVdB7xQiqhqbz2ZPLGHLPZbHVV5Qn/MNFZkCFxRU1miDyxKORucDGxKtsQJ63Rfza0udiUxWF5nHY6bpdQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"chalk": "^2.3.0", "chalk": "^2.3.0",

View File

@@ -5,8 +5,8 @@
"build": "rimraf build && webpack", "build": "rimraf build && webpack",
"package:lib": "npm run build && cp ./package.json build && cd build && npm version \"5.0.0\" && npm pack", "package:lib": "npm run build && cp ./package.json 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",
"format": "prettier --write \"src/**/*.ts\" \"src/**/*.js\"", "lint:fix": "npx prettier --write 'src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}'",
"lint": "tslint -p tsconfig.json", "lint": "npx prettier --check 'src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}'",
"test": "jest", "test": "jest",
"prepublishOnly": "cp -r ./build/* . && rm -rf ./build", "prepublishOnly": "cp -r ./build/* . && rm -rf ./build",
"postpublish": "git clean -fd", "postpublish": "git clean -fd",
@@ -37,15 +37,14 @@
"license": "ISC", "license": "ISC",
"devDependencies": { "devDependencies": {
"@types/isomorphic-fetch": "0.0.35", "@types/isomorphic-fetch": "0.0.35",
"@types/jest": "^26.0.8", "@types/jest": "^26.0.10",
"cp": "^0.2.0", "cp": "^0.2.0",
"jest": "^25.5.4", "jest": "^25.5.4",
"path": "^0.12.7", "path": "^0.12.7",
"prettier": "^2.0.5",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"semantic-release": "^17.1.1", "semantic-release": "^17.1.1",
"ts-jest": "^25.5.1", "ts-jest": "^25.5.1",
"ts-loader": "^8.0.2", "ts-loader": "^8.0.3",
"tslint": "^6.1.3", "tslint": "^6.1.3",
"tslint-config-prettier": "^1.18.0", "tslint-config-prettier": "^1.18.0",
"typedoc": "^0.17.8", "typedoc": "^0.17.8",

6
sasjs-tests/.prettierrc Normal file
View File

@@ -0,0 +1,6 @@
{
"trailingComma": "none",
"tabWidth": 2,
"semi": true,
"singleQuote": false
}

View File

@@ -1357,9 +1357,9 @@
"integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==" "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw=="
}, },
"@sasjs/adapter": { "@sasjs/adapter": {
"version": "1.0.5", "version": "1.3.6",
"resolved": "https://registry.npmjs.org/@sasjs/adapter/-/adapter-1.0.5.tgz", "resolved": "https://registry.npmjs.org/@sasjs/adapter/-/adapter-1.3.6.tgz",
"integrity": "sha512-54gQZD7QdNmQu77axOqr0vMS7hUVXO5hPbUtwXXocMIi3kRQDbROYjC3kuiFM9FrxqiZWbLRcyOqmFv3W/N36w==", "integrity": "sha512-d2B+cTII+vabKCU8mJy90mEz3tCWw2pEp4qIBGsDamJiTS0Rx69dgXGHuRUm8KtjLDHHrzwXATsqviU3dnU0QQ==",
"requires": { "requires": {
"es6-promise": "^4.2.8", "es6-promise": "^4.2.8",
"form-data": "^3.0.0", "form-data": "^3.0.0",
@@ -1379,15 +1379,23 @@
} }
}, },
"@sasjs/test-framework": { "@sasjs/test-framework": {
"version": "1.3.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/@sasjs/test-framework/-/test-framework-1.3.0.tgz", "resolved": "https://registry.npmjs.org/@sasjs/test-framework/-/test-framework-1.4.0.tgz",
"integrity": "sha512-vrbRFUhNUShLlNFZO+XwVwFLXDLApQG9zOPx00xhQ8IUA0cSDFFmf2mP/KBdFCxa1REaR6GHvMctUj+xRZo9QQ==", "integrity": "sha512-Pd8PUH5B5RO6q4w3OQXX7aWicvA/CJMXA/FCf2xp332ZTKBb/5uV+HphAOFKpCh58y+ykYYVSV0ZaDO/4t1h3A==",
"requires": { "requires": {
"@types/react-highlight.js": "^1.0.0", "@types/react-highlight.js": "^1.0.0",
"immer": "^7.0.7",
"moment": "^2.27.0", "moment": "^2.27.0",
"react-highlight.js": "^1.0.7", "react-highlight.js": "^1.0.7",
"semantic-ui-css": "^2.4.1", "semantic-ui-css": "^2.4.1",
"semantic-ui-react": "^1.0.0" "semantic-ui-react": "^1.0.0"
},
"dependencies": {
"immer": {
"version": "7.0.7",
"resolved": "https://registry.npmjs.org/immer/-/immer-7.0.7.tgz",
"integrity": "sha512-Q8yYwVADJXrNfp1ZUAh4XDHkcoE3wpdpb4mC5abDSajs2EbW8+cGdPyAnglMyLnm7EF6ojD2xBFX7L5i4TIytw=="
}
} }
}, },
"@semantic-ui-react/event-stack": { "@semantic-ui-react/event-stack": {
@@ -3747,11 +3755,6 @@
} }
} }
}, },
"classnames": {
"version": "2.2.6",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz",
"integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q=="
},
"clean-css": { "clean-css": {
"version": "4.2.3", "version": "4.2.3",
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz",
@@ -3829,6 +3832,11 @@
"shallow-clone": "^0.1.2" "shallow-clone": "^0.1.2"
} }
}, },
"clsx": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz",
"integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA=="
},
"co": { "co": {
"version": "4.6.0", "version": "4.6.0",
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
@@ -12226,21 +12234,36 @@
} }
}, },
"semantic-ui-react": { "semantic-ui-react": {
"version": "1.0.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/semantic-ui-react/-/semantic-ui-react-1.0.0.tgz", "resolved": "https://registry.npmjs.org/semantic-ui-react/-/semantic-ui-react-1.2.0.tgz",
"integrity": "sha512-85mYHYuDBNa6la1BgKwuOSD1vcIPsFQEXRxGsZ9pUtE4iHlEcylF+x46NYHIGbBjlys63SpNH3PtK6VyZj9LBw==", "integrity": "sha512-9tNL94nEy16RdupTQNiURyemWUIxtTpQgFimCbOOHRBOe1ApsFz3FWFsrGjv9zFtE7dQMslLYov9BQOelTCVwA==",
"requires": { "requires": {
"@babel/runtime": "^7.1.2", "@babel/runtime": "^7.10.5",
"@semantic-ui-react/event-stack": "^3.1.0", "@semantic-ui-react/event-stack": "^3.1.0",
"@stardust-ui/react-component-event-listener": "~0.38.0", "@stardust-ui/react-component-event-listener": "~0.38.0",
"@stardust-ui/react-component-ref": "~0.38.0", "@stardust-ui/react-component-ref": "~0.38.0",
"classnames": "^2.2.6", "clsx": "^1.1.1",
"keyboard-key": "^1.0.4", "keyboard-key": "^1.1.0",
"lodash": "^4.17.15", "lodash": "^4.17.19",
"prop-types": "^15.7.2", "prop-types": "^15.7.2",
"react-is": "^16.8.6", "react-is": "^16.8.6",
"react-popper": "^1.3.4", "react-popper": "^1.3.7",
"shallowequal": "^1.1.0" "shallowequal": "^1.1.0"
},
"dependencies": {
"@babel/runtime": {
"version": "7.11.2",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz",
"integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==",
"requires": {
"regenerator-runtime": "^0.13.4"
}
},
"lodash": {
"version": "4.17.20",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
}
} }
}, },
"semver": { "semver": {

View File

@@ -4,8 +4,8 @@
"homepage": ".", "homepage": ".",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@sasjs/adapter": "^1.0.5", "@sasjs/adapter": "^1.3.6",
"@sasjs/test-framework": "^1.3.0", "@sasjs/test-framework": "^1.4.0",
"@testing-library/jest-dom": "^4.2.4", "@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.5.0", "@testing-library/react": "^9.5.0",
"@testing-library/user-event": "^7.2.1", "@testing-library/user-event": "^7.2.1",

View File

@@ -1,8 +1,8 @@
import React from 'react'; import React from "react";
import { render } from '@testing-library/react'; import { render } from "@testing-library/react";
import App from './App'; import App from "./App";
test('renders learn react link', () => { test("renders learn react link", () => {
const { getByText } = render(<App />); const { getByText } = render(<App />);
const linkElement = getByText(/learn react/i); const linkElement = getByText(/learn react/i);
expect(linkElement).toBeInTheDocument(); expect(linkElement).toBeInTheDocument();

View File

@@ -17,7 +17,7 @@ const App = (): ReactElement<{}> => {
sendArrTests(adapter), sendArrTests(adapter),
sendObjTests(adapter), sendObjTests(adapter),
specialCaseTests(adapter), specialCaseTests(adapter),
sasjsRequestTests(adapter), sasjsRequestTests(adapter)
]); ]);
} }
}, [adapter, config]); }, [adapter, config]);

View File

@@ -12,7 +12,7 @@ const Login = (): ReactElement<{}> => {
(e) => { (e) => {
e.preventDefault(); e.preventDefault();
appContext.adapter.logIn(username, password).then((res) => { appContext.adapter.logIn(username, password).then((res) => {
appContext.setIsLoggedIn(true); appContext.setIsLoggedIn(res.isLoggedIn);
}); });
}, },
[username, password, appContext] [username, password, appContext]

View File

@@ -1,8 +1,7 @@
body { body {
margin: 0; margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans",
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", "Droid Sans", "Helvetica Neue", sans-serif;
sans-serif;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
background-color: #1f2027; background-color: #1f2027;
@@ -10,8 +9,7 @@ body {
} }
* { * {
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace;
monospace;
} }
input { input {

View File

@@ -11,9 +11,9 @@
// opt-in, read https://bit.ly/CRA-PWA // opt-in, read https://bit.ly/CRA-PWA
const isLocalhost = Boolean( const isLocalhost = Boolean(
window.location.hostname === 'localhost' || window.location.hostname === "localhost" ||
// [::1] is the IPv6 localhost address. // [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' || window.location.hostname === "[::1]" ||
// 127.0.0.0/8 are considered localhost for IPv4. // 127.0.0.0/8 are considered localhost for IPv4.
window.location.hostname.match( window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
@@ -21,7 +21,7 @@ const isLocalhost = Boolean(
); );
export function register(config) { export function register(config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) {
// The URL constructor is available in all browsers that support SW. // The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
if (publicUrl.origin !== window.location.origin) { if (publicUrl.origin !== window.location.origin) {
@@ -31,7 +31,7 @@ export function register(config) {
return; return;
} }
window.addEventListener('load', () => { window.addEventListener("load", () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) { if (isLocalhost) {
@@ -42,8 +42,8 @@ export function register(config) {
// service worker/PWA documentation. // service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => { navigator.serviceWorker.ready.then(() => {
console.log( console.log(
'This web app is being served cache-first by a service ' + "This web app is being served cache-first by a service " +
'worker. To learn more, visit https://bit.ly/CRA-PWA' "worker. To learn more, visit https://bit.ly/CRA-PWA"
); );
}); });
} else { } else {
@@ -57,21 +57,21 @@ export function register(config) {
function registerValidSW(swUrl, config) { function registerValidSW(swUrl, config) {
navigator.serviceWorker navigator.serviceWorker
.register(swUrl) .register(swUrl)
.then(registration => { .then((registration) => {
registration.onupdatefound = () => { registration.onupdatefound = () => {
const installingWorker = registration.installing; const installingWorker = registration.installing;
if (installingWorker == null) { if (installingWorker == null) {
return; return;
} }
installingWorker.onstatechange = () => { installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') { if (installingWorker.state === "installed") {
if (navigator.serviceWorker.controller) { if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched, // At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older // but the previous service worker will still serve the older
// content until all client tabs are closed. // content until all client tabs are closed.
console.log( console.log(
'New content is available and will be used when all ' + "New content is available and will be used when all " +
'tabs for this page are closed. See https://bit.ly/CRA-PWA.' "tabs for this page are closed. See https://bit.ly/CRA-PWA."
); );
// Execute callback // Execute callback
@@ -82,7 +82,7 @@ function registerValidSW(swUrl, config) {
// At this point, everything has been precached. // At this point, everything has been precached.
// It's the perfect time to display a // It's the perfect time to display a
// "Content is cached for offline use." message. // "Content is cached for offline use." message.
console.log('Content is cached for offline use.'); console.log("Content is cached for offline use.");
// Execute callback // Execute callback
if (config && config.onSuccess) { if (config && config.onSuccess) {
@@ -93,25 +93,25 @@ function registerValidSW(swUrl, config) {
}; };
}; };
}) })
.catch(error => { .catch((error) => {
console.error('Error during service worker registration:', error); console.error("Error during service worker registration:", error);
}); });
} }
function checkValidServiceWorker(swUrl, config) { function checkValidServiceWorker(swUrl, config) {
// Check if the service worker can be found. If it can't reload the page. // Check if the service worker can be found. If it can't reload the page.
fetch(swUrl, { fetch(swUrl, {
headers: { 'Service-Worker': 'script' }, headers: { "Service-Worker": "script" }
}) })
.then(response => { .then((response) => {
// Ensure service worker exists, and that we really are getting a JS file. // Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type'); const contentType = response.headers.get("content-type");
if ( if (
response.status === 404 || response.status === 404 ||
(contentType != null && contentType.indexOf('javascript') === -1) (contentType != null && contentType.indexOf("javascript") === -1)
) { ) {
// No service worker found. Probably a different app. Reload the page. // No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => { navigator.serviceWorker.ready.then((registration) => {
registration.unregister().then(() => { registration.unregister().then(() => {
window.location.reload(); window.location.reload();
}); });
@@ -123,18 +123,18 @@ function checkValidServiceWorker(swUrl, config) {
}) })
.catch(() => { .catch(() => {
console.log( console.log(
'No internet connection found. App is running in offline mode.' "No internet connection found. App is running in offline mode."
); );
}); });
} }
export function unregister() { export function unregister() {
if ('serviceWorker' in navigator) { if ("serviceWorker" in navigator) {
navigator.serviceWorker.ready navigator.serviceWorker.ready
.then(registration => { .then((registration) => {
registration.unregister(); registration.unregister();
}) })
.catch(error => { .catch((error) => {
console.error(error.message); console.error(error.message);
}); });
} }

View File

@@ -2,4 +2,4 @@
// allows you to do things like: // allows you to do things like:
// expect(element).toHaveTextContent(/react/i) // expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom // learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom/extend-expect'; import "@testing-library/jest-dom/extend-expect";

View File

@@ -9,7 +9,7 @@ const defaultConfig: SASjsConfig = {
serverType: ServerType.SASViya, serverType: ServerType.SASViya,
debug: true, debug: true,
contextName: "SAS Job Execution compute context", contextName: "SAS Job Execution compute context",
useComputeApi: false, useComputeApi: false
}; };
const customConfig = { const customConfig = {
@@ -18,7 +18,7 @@ const customConfig = {
pathSASViya: "viya", pathSASViya: "viya",
appLoc: "/Public/seedapp", appLoc: "/Public/seedapp",
serverType: ServerType.SAS9, serverType: ServerType.SAS9,
debug: false, debug: false
}; };
export const basicTests = ( export const basicTests = (
@@ -35,7 +35,7 @@ export const basicTests = (
return adapter.logIn(userName, password); return adapter.logIn(userName, password);
}, },
assertion: (response: any) => assertion: (response: any) =>
response && response.isLoggedIn && response.userName === userName, response && response.isLoggedIn && response.userName === userName
}, },
{ {
title: "Default config", title: "Default config",
@@ -54,7 +54,7 @@ export const basicTests = (
sasjsConfig.serverType === defaultConfig.serverType && sasjsConfig.serverType === defaultConfig.serverType &&
sasjsConfig.debug === defaultConfig.debug sasjsConfig.debug === defaultConfig.debug
); );
}, }
}, },
{ {
title: "Custom config", title: "Custom config",
@@ -72,7 +72,7 @@ export const basicTests = (
sasjsConfig.serverType === customConfig.serverType && sasjsConfig.serverType === customConfig.serverType &&
sasjsConfig.debug === customConfig.debug sasjsConfig.debug === customConfig.debug
); );
}, }
}, },
{ {
title: "Config overrides", title: "Config overrides",
@@ -92,7 +92,7 @@ export const basicTests = (
sasjsConfig.serverType === defaultConfig.serverType && sasjsConfig.serverType === defaultConfig.serverType &&
sasjsConfig.debug === false sasjsConfig.debug === false
); );
}, }
}, }
], ]
}); });

View File

@@ -4,7 +4,7 @@ import { TestSuite } from "@sasjs/test-framework";
const stringData: any = { table1: [{ col1: "first col value" }] }; const stringData: any = { table1: [{ col1: "first col value" }] };
const numericData: any = { table1: [{ col1: 3.14159265 }] }; const numericData: any = { table1: [{ col1: 3.14159265 }] };
const multiColumnData: any = { const multiColumnData: any = {
table1: [{ col1: 42, col2: 1.618, col3: "x", col4: "x" }], table1: [{ col1: 42, col2: 1.618, col3: "x", col4: "x" }]
}; };
const multipleRowsWithNulls: any = { const multipleRowsWithNulls: any = {
table1: [ table1: [
@@ -12,8 +12,8 @@ const multipleRowsWithNulls: any = {
{ col1: 42, col2: null, col3: "x", col4: "" }, { col1: 42, col2: null, col3: "x", col4: "" },
{ col1: 42, col2: null, col3: "x", col4: "" }, { col1: 42, col2: null, col3: "x", col4: "" },
{ col1: 42, col2: 1.62, col3: "x", col4: "x" }, { col1: 42, col2: 1.62, col3: "x", col4: "x" },
{ col1: 42, col2: 1.62, col3: "x", col4: "x" }, { col1: 42, col2: 1.62, col3: "x", col4: "x" }
], ]
}; };
const multipleColumnsWithNulls: any = { const multipleColumnsWithNulls: any = {
table1: [ table1: [
@@ -21,8 +21,8 @@ const multipleColumnsWithNulls: any = {
{ col1: 42, col2: null, col3: "x", col4: null }, { col1: 42, col2: null, col3: "x", col4: null },
{ col1: 42, col2: null, col3: "x", col4: null }, { col1: 42, col2: null, col3: "x", col4: null },
{ col1: 42, col2: null, col3: "x", col4: "" }, { col1: 42, col2: null, col3: "x", col4: "" },
{ col1: 42, col2: null, col3: "x", col4: "" }, { col1: 42, col2: null, col3: "x", col4: "" }
], ]
}; };
const getLongStringData = (length = 32764) => { const getLongStringData = (length = 32764) => {
@@ -55,7 +55,7 @@ export const sendArrTests = (adapter: SASjs): TestSuite => ({
}, },
assertion: (res: any) => { assertion: (res: any) => {
return res.table1[0][0] === stringData.table1[0].col1; return res.table1[0][0] === stringData.table1[0].col1;
}, }
}, },
{ {
title: "Long string value", title: "Long string value",
@@ -67,7 +67,7 @@ export const sendArrTests = (adapter: SASjs): TestSuite => ({
assertion: (res: any) => { assertion: (res: any) => {
const longStringData = getLongStringData(); const longStringData = getLongStringData();
return res.table1[0][0] === longStringData.table1[0].col1; return res.table1[0][0] === longStringData.table1[0].col1;
}, }
}, },
{ {
title: "Overly long string value", title: "Overly long string value",
@@ -79,7 +79,7 @@ export const sendArrTests = (adapter: SASjs): TestSuite => ({
}, },
assertion: (error: any) => { assertion: (error: any) => {
return !!error && !!error.MESSAGE; return !!error && !!error.MESSAGE;
}, }
}, },
{ {
title: "Single numeric value", title: "Single numeric value",
@@ -89,7 +89,7 @@ export const sendArrTests = (adapter: SASjs): TestSuite => ({
}, },
assertion: (res: any) => { assertion: (res: any) => {
return res.table1[0][0] === numericData.table1[0].col1; return res.table1[0][0] === numericData.table1[0].col1;
}, }
}, },
{ {
title: "Multiple columns", title: "Multiple columns",
@@ -104,7 +104,7 @@ export const sendArrTests = (adapter: SASjs): TestSuite => ({
res.table1[0][2] === multiColumnData.table1[0].col3 && res.table1[0][2] === multiColumnData.table1[0].col3 &&
res.table1[0][3] === multiColumnData.table1[0].col4 res.table1[0][3] === multiColumnData.table1[0].col4
); );
}, }
}, },
{ {
title: "Multiple rows with nulls", title: "Multiple rows with nulls",
@@ -129,7 +129,7 @@ export const sendArrTests = (adapter: SASjs): TestSuite => ({
res.table1[index][3] === multipleRowsWithNulls.table1[index].col4; res.table1[index][3] === multipleRowsWithNulls.table1[index].col4;
}); });
return result; return result;
}, }
}, },
{ {
title: "Multiple columns with nulls", title: "Multiple columns with nulls",
@@ -158,9 +158,9 @@ export const sendArrTests = (adapter: SASjs): TestSuite => ({
(multipleColumnsWithNulls.table1[index].col4 || ""); (multipleColumnsWithNulls.table1[index].col4 || "");
}); });
return result; return result;
}, }
}, }
], ]
}); });
export const sendObjTests = (adapter: SASjs): TestSuite => ({ export const sendObjTests = (adapter: SASjs): TestSuite => ({
@@ -171,11 +171,11 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
description: "Should throw an error", description: "Should throw an error",
test: async () => { test: async () => {
const invalidData: any = { const invalidData: any = {
"1 invalid table": [{ col1: 42 }], "1 invalid table": [{ col1: 42 }]
}; };
return adapter.request("common/sendObj", invalidData).catch((e) => e); return adapter.request("common/sendObj", invalidData).catch((e) => e);
}, },
assertion: (error: any) => !!error && !!error.MESSAGE, assertion: (error: any) => !!error && !!error.MESSAGE
}, },
{ {
title: "Single string value", title: "Single string value",
@@ -185,7 +185,7 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
}, },
assertion: (res: any) => { assertion: (res: any) => {
return res.table1[0].COL1 === stringData.table1[0].col1; return res.table1[0].COL1 === stringData.table1[0].col1;
}, }
}, },
{ {
title: "Long string value", title: "Long string value",
@@ -197,7 +197,7 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
assertion: (res: any) => { assertion: (res: any) => {
const longStringData = getLongStringData(); const longStringData = getLongStringData();
return res.table1[0].COL1 === longStringData.table1[0].col1; return res.table1[0].COL1 === longStringData.table1[0].col1;
}, }
}, },
{ {
title: "Overly long string value", title: "Overly long string value",
@@ -210,7 +210,7 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
}, },
assertion: (error: any) => { assertion: (error: any) => {
return !!error && !!error.MESSAGE; return !!error && !!error.MESSAGE;
}, }
}, },
{ {
title: "Single numeric value", title: "Single numeric value",
@@ -220,7 +220,7 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
}, },
assertion: (res: any) => { assertion: (res: any) => {
return res.table1[0].COL1 === numericData.table1[0].col1; return res.table1[0].COL1 === numericData.table1[0].col1;
}, }
}, },
{ {
@@ -232,7 +232,7 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
assertion: (res: any) => { assertion: (res: any) => {
const data = getLargeObjectData(); const data = getLargeObjectData();
return res.table1[9000].BIG === data.table1[9000].big; return res.table1[9000].BIG === data.table1[9000].big;
}, }
}, },
{ {
title: "Multiple columns", title: "Multiple columns",
@@ -247,7 +247,7 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
res.table1[0].COL3 === multiColumnData.table1[0].col3 && res.table1[0].COL3 === multiColumnData.table1[0].col3 &&
res.table1[0].COL4 === multiColumnData.table1[0].col4 res.table1[0].COL4 === multiColumnData.table1[0].col4
); );
}, }
}, },
{ {
title: "Multiple rows with nulls", title: "Multiple rows with nulls",
@@ -272,7 +272,7 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
res.table1[index].COL4 === multipleRowsWithNulls.table1[index].col4; res.table1[index].COL4 === multipleRowsWithNulls.table1[index].col4;
}); });
return result; return result;
}, }
}, },
{ {
title: "Multiple columns with nulls", title: "Multiple columns with nulls",
@@ -301,7 +301,7 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
(multipleColumnsWithNulls.table1[index].col4 || ""); (multipleColumnsWithNulls.table1[index].col4 || "");
}); });
return result; return result;
}, }
}, }
], ]
}); });

View File

@@ -19,7 +19,32 @@ export const sasjsRequestTests = (adapter: SASjs): TestSuite => ({
} else { } else {
return requests[0].SASWORK === null; return requests[0].SASWORK === null;
} }
}, }
}, },
], {
title: "Make error and capture log",
description: "Should make an error and capture log",
test: async () => {
return new Promise(async (resolve, reject) => {
adapter
.request("common/makeErr", data)
.then((res) => {
//no action here, this request must throw error
})
.catch((err) => {
let sasRequests = adapter.getSasRequests();
let makeErrRequest =
sasRequests.find((req) =>
req.serviceLink.includes("makeErr")
) || null;
resolve(!!makeErrRequest);
});
});
},
assertion: (response) => {
return response;
}
}
]
}); });

View File

@@ -13,9 +13,9 @@ const specialCharData: any = {
doubleQuote: '"', doubleQuote: '"',
crlf: "\r\n", crlf: "\r\n",
euro: "€euro", euro: "€euro",
banghash: "!#banghash", banghash: "!#banghash"
}, }
], ]
}; };
const moreSpecialCharData: any = { const moreSpecialCharData: any = {
@@ -31,9 +31,9 @@ const moreSpecialCharData: any = {
sigma: "Σsigma", sigma: "Σsigma",
at: "@at", at: "@at",
serbian: "Српски", serbian: "Српски",
dollar: "$", dollar: "$"
}, }
], ]
}; };
const getWideData = () => { const getWideData = () => {
@@ -43,7 +43,7 @@ const getWideData = () => {
} }
const data: any = { const data: any = {
table1: [cols], table1: [cols]
}; };
return data; return data;
@@ -67,7 +67,7 @@ const getLargeDataset = () => {
} }
const data: any = { const data: any = {
table1: rows, table1: rows
}; };
return data; return data;
@@ -75,7 +75,7 @@ const getLargeDataset = () => {
const errorAndCsrfData: any = { const errorAndCsrfData: any = {
error: [{ col1: "q", col2: "w", col3: "e", col4: "r" }], error: [{ col1: "q", col2: "w", col3: "e", col4: "r" }],
_csrf: [{ col1: "q", col2: "w", col3: "e", col4: "r" }], _csrf: [{ col1: "q", col2: "w", col3: "e", col4: "r" }]
}; };
export const specialCaseTests = (adapter: SASjs): TestSuite => ({ export const specialCaseTests = (adapter: SASjs): TestSuite => ({
@@ -100,7 +100,7 @@ export const specialCaseTests = (adapter: SASjs): TestSuite => ({
res.table1[0][8] === specialCharData.table1[0].euro && res.table1[0][8] === specialCharData.table1[0].euro &&
res.table1[0][9] === specialCharData.table1[0].banghash res.table1[0][9] === specialCharData.table1[0].banghash
); );
}, }
}, },
{ {
title: "Other special characters", title: "Other special characters",
@@ -122,7 +122,7 @@ export const specialCaseTests = (adapter: SASjs): TestSuite => ({
res.table1[0][9] === moreSpecialCharData.table1[0].serbian && res.table1[0][9] === moreSpecialCharData.table1[0].serbian &&
res.table1[0][10] === moreSpecialCharData.table1[0].dollar res.table1[0][10] === moreSpecialCharData.table1[0].dollar
); );
}, }
}, },
{ {
title: "Wide table with sendArr", title: "Wide table with sendArr",
@@ -138,7 +138,7 @@ export const specialCaseTests = (adapter: SASjs): TestSuite => ({
result && res.table1[0][i] === data.table1[0]["col" + (i + 1)]; result && res.table1[0][i] === data.table1[0]["col" + (i + 1)];
} }
return result; return result;
}, }
}, },
{ {
title: "Wide table with sendObj", title: "Wide table with sendObj",
@@ -155,7 +155,7 @@ export const specialCaseTests = (adapter: SASjs): TestSuite => ({
res.table1[0]["COL" + (i + 1)] === data.table1[0]["col" + (i + 1)]; res.table1[0]["COL" + (i + 1)] === data.table1[0]["col" + (i + 1)];
} }
return result; return result;
}, }
}, },
{ {
title: "Multiple tables", title: "Multiple tables",
@@ -175,7 +175,7 @@ export const specialCaseTests = (adapter: SASjs): TestSuite => ({
res.table50[0][2] === data.table50[0].col3 && res.table50[0][2] === data.table50[0].col3 &&
res.table50[0][3] === data.table50[0].col4 res.table50[0][3] === data.table50[0].col4
); );
}, }
}, },
{ {
title: "Large dataset with sendObj", title: "Large dataset with sendObj",
@@ -190,7 +190,7 @@ export const specialCaseTests = (adapter: SASjs): TestSuite => ({
result = result && res.table1[i][0] === data.table1[i][0]; result = result && res.table1[i][0] === data.table1[i][0];
} }
return result; return result;
}, }
}, },
{ {
title: "Large dataset with sendArr", title: "Large dataset with sendArr",
@@ -206,7 +206,7 @@ export const specialCaseTests = (adapter: SASjs): TestSuite => ({
result && res.table1[i][0] === Object.values(data.table1[i])[0]; result && res.table1[i][0] === Object.values(data.table1[i])[0];
} }
return result; return result;
}, }
}, },
{ {
title: "Error and _csrf tables with sendArr", title: "Error and _csrf tables with sendArr",
@@ -225,7 +225,7 @@ export const specialCaseTests = (adapter: SASjs): TestSuite => ({
res._csrf[0][2] === errorAndCsrfData._csrf[0].col3 && res._csrf[0][2] === errorAndCsrfData._csrf[0].col3 &&
res._csrf[0][3] === errorAndCsrfData._csrf[0].col4 res._csrf[0][3] === errorAndCsrfData._csrf[0].col4
); );
}, }
}, },
{ {
title: "Error and _csrf tables with sendObj", title: "Error and _csrf tables with sendObj",
@@ -244,7 +244,7 @@ export const specialCaseTests = (adapter: SASjs): TestSuite => ({
res._csrf[0].COL3 === errorAndCsrfData._csrf[0].col3 && res._csrf[0].COL3 === errorAndCsrfData._csrf[0].col3 &&
res._csrf[0].COL4 === errorAndCsrfData._csrf[0].col4 res._csrf[0].COL4 === errorAndCsrfData._csrf[0].col4
); );
}, }
}, }
], ]
}); });

View File

@@ -9,6 +9,7 @@ export class FileUploader {
private appLoc: string, private appLoc: string,
private serverUrl: string, private serverUrl: string,
private jobsPath: string, private jobsPath: string,
private setCsrfTokenWeb: any,
private csrfToken: CsrfToken | null = null private csrfToken: CsrfToken | null = null
) {} ) {}
private retryCount = 0; private retryCount = 0;
@@ -32,7 +33,7 @@ export class FileUploader {
}${paramsString}`; }${paramsString}`;
const headers = { const headers = {
"cache-control": "no-cache", "cache-control": "no-cache"
}; };
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@@ -48,7 +49,7 @@ export class FileUploader {
method: "POST", method: "POST",
body: formData, body: formData,
referrerPolicy: "same-origin", referrerPolicy: "same-origin",
headers, headers
}) })
.then(async (response) => { .then(async (response) => {
if (!response.ok) { if (!response.ok) {
@@ -59,8 +60,10 @@ export class FileUploader {
const token = response.headers.get(tokenHeader); const token = response.headers.get(tokenHeader);
this.csrfToken = { this.csrfToken = {
headerName: tokenHeader, headerName: tokenHeader,
value: token || "", value: token || ""
}; };
this.setCsrfTokenWeb(this.csrfToken);
} }
} }
} }

View File

@@ -10,7 +10,7 @@ export class SAS9ApiClient {
*/ */
public getConfig() { public getConfig() {
return { return {
serverUrl: this.serverUrl, serverUrl: this.serverUrl
}; };
} }
@@ -37,9 +37,9 @@ export class SAS9ApiClient {
const executeScriptRequest = { const executeScriptRequest = {
method: "PUT", method: "PUT",
headers: { headers: {
Accept: "application/json", Accept: "application/json"
}, },
body: `command=${requestPayload}`, body: `command=${requestPayload}`
}; };
const executeScriptResponse = await fetch( const executeScriptResponse = await fetch(
`${this.serverUrl}/sas/servers/${serverName}/cmd?repositoryName=${repositoryName}`, `${this.serverUrl}/sas/servers/${serverName}/cmd?repositoryName=${repositoryName}`,

View File

@@ -2,7 +2,7 @@ import {
isAuthorizeFormRequired, isAuthorizeFormRequired,
parseAndSubmitAuthorizeForm, parseAndSubmitAuthorizeForm,
convertToCSV, convertToCSV,
makeRequest, makeRequest
} from "./utils"; } from "./utils";
import * as NodeFormData from "form-data"; import * as NodeFormData from "form-data";
import * as path from "path"; import * as path from "path";
@@ -29,7 +29,11 @@ export class SASViyaApiClient {
} }
private csrfToken: CsrfToken | null = null; private csrfToken: CsrfToken | null = null;
private rootFolder: Folder | null = null; private rootFolder: Folder | null = null;
private sessionManager = new SessionManager(this.serverUrl, this.contextName, this.setCsrfToken); private sessionManager = new SessionManager(
this.serverUrl,
this.contextName,
this.setCsrfToken
);
/** /**
* Returns a map containing the directory structure in the currently set root folder. * Returns a map containing the directory structure in the currently set root folder.
@@ -49,7 +53,7 @@ export class SASViyaApiClient {
public getConfig() { public getConfig() {
return { return {
serverUrl: this.serverUrl, serverUrl: this.serverUrl,
rootFolderName: this.rootFolderName, rootFolderName: this.rootFolderName
}; };
} }
@@ -69,7 +73,7 @@ export class SASViyaApiClient {
*/ */
public async getAllContexts(accessToken?: string) { public async getAllContexts(accessToken?: string) {
const headers: any = { const headers: any = {
"Content-Type": "application/json", "Content-Type": "application/json"
}; };
if (accessToken) { if (accessToken) {
headers.Authorization = `Bearer ${accessToken}`; headers.Authorization = `Bearer ${accessToken}`;
@@ -84,7 +88,7 @@ export class SASViyaApiClient {
id: context.id, id: context.id,
name: context.name, name: context.name,
version: context.version, version: context.version,
attributes: {}, attributes: {}
})); }));
} }
@@ -94,7 +98,7 @@ export class SASViyaApiClient {
*/ */
public async getExecutableContexts(accessToken?: string) { public async getExecutableContexts(accessToken?: string) {
const headers: any = { const headers: any = {
"Content-Type": "application/json", "Content-Type": "application/json"
}; };
if (accessToken) { if (accessToken) {
headers.Authorization = `Bearer ${accessToken}`; headers.Authorization = `Bearer ${accessToken}`;
@@ -113,9 +117,7 @@ export class SASViyaApiClient {
`test-${context.name}`, `test-${context.name}`,
linesOfCode, linesOfCode,
context.name, context.name,
accessToken, accessToken
undefined,
true
).catch(() => null); ).catch(() => null);
}); });
const results = await Promise.all(promises); const results = await Promise.all(promises);
@@ -137,8 +139,8 @@ export class SASViyaApiClient {
name: contextsList[index].name, name: contextsList[index].name,
version: contextsList[index].version, version: contextsList[index].version,
attributes: { attributes: {
sysUserId, sysUserId
}, }
}); });
} }
}); });
@@ -153,7 +155,7 @@ export class SASViyaApiClient {
*/ */
public async createSession(contextName: string, accessToken?: string) { public async createSession(contextName: string, accessToken?: string) {
const headers: any = { const headers: any = {
"Content-Type": "application/json", "Content-Type": "application/json"
}; };
if (accessToken) { if (accessToken) {
@@ -176,8 +178,8 @@ export class SASViyaApiClient {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${accessToken}`, Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json", "Content-Type": "application/json"
}, }
}; };
const { result: createdSession } = await this.request<Session>( const { result: createdSession } = await this.request<Session>(
`${this.serverUrl}/compute/contexts/${executionContext.id}/sessions`, `${this.serverUrl}/compute/contexts/${executionContext.id}/sessions`,
@@ -201,126 +203,152 @@ export class SASViyaApiClient {
linesOfCode: string[], linesOfCode: string[],
contextName: string, contextName: string,
accessToken?: string, accessToken?: string,
sessionId = "",
silent = false, silent = false,
data = null, data = null,
debug = false debug = false
) { ): Promise<any> {
const headers: any = { silent = !debug;
"Content-Type": "application/json", try {
}; const headers: any = {
if (accessToken) { "Content-Type": "application/json"
headers.Authorization = `Bearer ${accessToken}`; };
}
let executionSessionId: string;
const session = await this.sessionManager.getSession(accessToken);
executionSessionId = session!.id;
const jobArguments: { [key: string]: any } = { if (accessToken) {
_contextName: contextName, headers.Authorization = `Bearer ${accessToken}`;
_OMITJSONLISTING: true, }
_OMITJSONLOG: true,
_OMITSESSIONRESULTS: true,
_OMITTEXTLISTING: true,
_OMITTEXTLOG: true,
};
if (debug) { let executionSessionId: string;
jobArguments["_OMITTEXTLOG"] = false; const session = await this.sessionManager.getSession(accessToken);
jobArguments["_OMITSESSIONRESULTS"] = false; executionSessionId = session!.id;
jobArguments["_DEBUG"] = 131;
}
const fileName = `exec-${ const jobArguments: { [key: string]: any } = {
jobName.includes("/") ? jobName.split("/")[1] : jobName _contextName: contextName,
}`; _OMITJSONLISTING: true,
_OMITJSONLOG: true,
_OMITSESSIONRESULTS: true,
_OMITTEXTLISTING: true,
_OMITTEXTLOG: true
};
let jobVariables: any = { if (debug) {
SYS_JES_JOB_URI: "", jobArguments["_OMITTEXTLOG"] = false;
_program: this.rootFolderName + "/" + jobName, jobArguments["_OMITSESSIONRESULTS"] = false;
}; jobArguments["_DEBUG"] = 131;
let files: any[] = []; }
if (data) {
if (JSON.stringify(data).includes(";")) { const fileName = `exec-${
files = await this.uploadTables(data, accessToken); jobName.includes("/") ? jobName.split("/")[1] : jobName
jobVariables["_webin_file_count"] = files.length; }`;
files.forEach((fileInfo, index) => {
jobVariables[ let jobVariables: any = {
`_webin_fileuri${index + 1}` SYS_JES_JOB_URI: "",
] = `/files/files/${fileInfo.file.id}`; _program: this.rootFolderName + "/" + jobName
jobVariables[`_webin_name${index + 1}`] = fileInfo.tableName; };
});
let files: any[] = [];
if (data) {
if (JSON.stringify(data).includes(";")) {
files = await this.uploadTables(data, accessToken);
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 postJobRequest = {
method: "POST",
headers,
body: JSON.stringify({
name: fileName,
description: "Powered by SASjs",
code: linesOfCode,
variables: jobVariables,
arguments: jobArguments
})
};
const { result: postedJob, etag } = await this.request<Job>(
`${this.serverUrl}/compute/sessions/${executionSessionId}/jobs`,
postJobRequest
);
if (!silent) {
console.log(`Job has been submitted for ${fileName}`);
console.log(
`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,
accessToken,
silent
);
const { result: currentJob } = await this.request<Job>(
`${this.serverUrl}/compute/sessions/${executionSessionId}/jobs/${postedJob.id}`,
{ headers }
);
let jobResult, log;
const logLink = currentJob.links.find((l) => l.rel === "log");
if (true && logLink) {
log = await this.request<any>(
`${this.serverUrl}${logLink.href}/content?limit=10000`,
{
headers
}
).then((res: any) =>
res.result.items.map((i: any) => i.line).join("\n")
);
}
if (jobStatus === "failed" || jobStatus === "error") {
return Promise.reject({ error: currentJob.error, log: log });
}
const resultLink = `/compute/sessions/${executionSessionId}/filerefs/_webout/content`;
if (resultLink) {
jobResult = await this.request<any>(
`${this.serverUrl}${resultLink}`,
{ headers },
"text"
).catch((e) => ({
result: JSON.stringify(e)
}));
}
await this.sessionManager.clearSession(executionSessionId, accessToken);
return { result: jobResult?.result, log };
} catch (e) {
if (e && e.status === 404) {
return this.executeScript(
jobName,
linesOfCode,
contextName,
accessToken,
silent,
data,
debug
);
} else { } else {
jobVariables = { ...jobVariables, ...formatDataForRequest(data) }; throw e;
} }
} }
// Execute job in session
const postJobRequest = {
method: "POST",
headers,
body: JSON.stringify({
name: fileName,
description: "Powered by SASjs",
code: linesOfCode,
variables: jobVariables,
arguments: jobArguments,
}),
};
const { result: postedJob, etag } = await this.request<Job>(
`${this.serverUrl}/compute/sessions/${executionSessionId}/jobs`,
postJobRequest
);
if (!silent) {
console.log(`Job has been submitted for ${fileName}`);
console.log(
`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,
accessToken,
silent
);
const { result: currentJob } = await this.request<Job>(
`${this.serverUrl}/compute/sessions/${executionSessionId}/jobs/${postedJob.id}`,
{ headers }
);
let jobResult, log;
if (jobStatus === "failed" || jobStatus === "error") {
return Promise.reject(currentJob.error);
}
const resultLink = `/compute/sessions/${executionSessionId}/filerefs/_webout/content`;
const logLink = currentJob.links.find((l) => l.rel === "log");
if (resultLink) {
jobResult = await this.request<any>(
`${this.serverUrl}${resultLink}`,
{ headers },
"text"
);
}
if (true && logLink) {
log = await this.request<any>(
`${this.serverUrl}${logLink.href}/content`,
{
headers,
}
).then((res: any) => res.result.items.map((i: any) => i.line).join("\n"));
}
return { result: jobResult?.result, log };
// } else {
// console.error(
// `Unable to find execution context ${contextName}.\nPlease check the contextName in the tgtDeployVars and try again.`
// );
// console.error("Response from server: ", JSON.stringify(this.contexts));
// }
} }
/** /**
@@ -373,8 +401,8 @@ export class SASViyaApiClient {
method: "POST", method: "POST",
body: JSON.stringify({ body: JSON.stringify({
name: folderName, name: folderName,
type: "folder", type: "folder"
}), })
}; };
createFolderRequest.headers = { "Content-Type": "application/json" }; createFolderRequest.headers = { "Content-Type": "application/json" };
@@ -421,7 +449,7 @@ export class SASViyaApiClient {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/vnd.sas.job.definition+json", "Content-Type": "application/vnd.sas.job.definition+json",
Accept: "application/vnd.sas.job.definition+json", Accept: "application/vnd.sas.job.definition+json"
}, },
body: JSON.stringify({ body: JSON.stringify({
name: jobName, name: jobName,
@@ -429,18 +457,18 @@ export class SASViyaApiClient {
{ {
name: "_addjesbeginendmacros", name: "_addjesbeginendmacros",
type: "CHARACTER", type: "CHARACTER",
defaultValue: "false", defaultValue: "false"
}, }
], ],
type: "Compute", type: "Compute",
code, code
}), })
}; };
if (accessToken) { if (accessToken) {
createJobDefinitionRequest!.headers = { createJobDefinitionRequest!.headers = {
...createJobDefinitionRequest.headers, ...createJobDefinitionRequest.headers,
Authorization: `Bearer ${accessToken}`, Authorization: `Bearer ${accessToken}`
}; };
} }
@@ -459,7 +487,7 @@ export class SASViyaApiClient {
const authCode = await fetch(authUrl, { const authCode = await fetch(authUrl, {
referrerPolicy: "same-origin", referrerPolicy: "same-origin",
credentials: "include", credentials: "include"
}) })
.then((response) => response.text()) .then((response) => response.text())
.then(async (response) => { .then(async (response) => {
@@ -515,7 +543,7 @@ export class SASViyaApiClient {
token = Buffer.from(clientId + ":" + clientSecret).toString("base64"); token = Buffer.from(clientId + ":" + clientSecret).toString("base64");
} }
const headers = { const headers = {
Authorization: "Basic " + token, Authorization: "Basic " + token
}; };
let formData; let formData;
@@ -534,7 +562,7 @@ export class SASViyaApiClient {
credentials: "include", credentials: "include",
headers, headers,
body: formData as any, body: formData as any,
referrerPolicy: "same-origin", referrerPolicy: "same-origin"
}).then((res) => res.json()); }).then((res) => res.json());
return authResponse; return authResponse;
@@ -559,7 +587,7 @@ export class SASViyaApiClient {
token = Buffer.from(clientId + ":" + clientSecret).toString("base64"); token = Buffer.from(clientId + ":" + clientSecret).toString("base64");
} }
const headers = { const headers = {
Authorization: "Basic " + token, Authorization: "Basic " + token
}; };
let formData; let formData;
@@ -578,7 +606,7 @@ export class SASViyaApiClient {
credentials: "include", credentials: "include",
headers, headers,
body: formData as any, body: formData as any,
referrerPolicy: "same-origin", referrerPolicy: "same-origin"
}).then((res) => res.json()); }).then((res) => res.json());
return authResponse; return authResponse;
@@ -598,7 +626,7 @@ export class SASViyaApiClient {
const deleteResponse = await this.request(url, { const deleteResponse = await this.request(url, {
method: "DELETE", method: "DELETE",
credentials: "include", credentials: "include",
headers, headers
}); });
return deleteResponse; return deleteResponse;
@@ -623,12 +651,16 @@ export class SASViyaApiClient {
await this.populateRootFolder(accessToken); await this.populateRootFolder(accessToken);
} }
if (!this.rootFolder) { if (!this.rootFolder) {
console.error("Root folder was not found");
throw new Error("Root folder was not found"); throw new Error("Root folder was not found");
} }
if (!this.rootFolderMap.size) { if (!this.rootFolderMap.size) {
await this.populateRootFolderMap(accessToken); await this.populateRootFolderMap(accessToken);
} }
if (!this.rootFolderMap.size) { if (!this.rootFolderMap.size) {
console.error(
`The job ${sasJob} was not found in ${this.rootFolderName}`
);
throw new Error( throw new Error(
`The job ${sasJob} was not found in ${this.rootFolderName}` `The job ${sasJob} was not found in ${this.rootFolderName}`
); );
@@ -643,25 +675,35 @@ export class SASViyaApiClient {
const jobName = sasJob.split("/")[1]; const jobName = sasJob.split("/")[1];
const jobFolder = this.rootFolderMap.get(folderName); const jobFolder = this.rootFolderMap.get(folderName);
const jobToExecute = jobFolder?.find((item) => item.name === jobName); const jobToExecute = jobFolder?.find((item) => item.name === jobName);
const jobDefinitionLink = jobToExecute?.links.find( if (!jobToExecute) {
(l) => l.rel === "getResource" throw new Error("Job was not found.");
);
if (!jobDefinitionLink) {
throw new Error("Job definition URI was not found.");
} }
const { result: jobDefinition } = await this.request<JobDefinition>(
`${this.serverUrl}${jobDefinitionLink.href}`, let code = jobToExecute?.code;
headers if (!code) {
); const jobDefinitionLink = jobToExecute?.links.find(
const linesToExecute = jobDefinition.code (l) => l.rel === "getResource"
.replace(/\r\n/g, "\n") );
.split("\n"); if (!jobDefinitionLink) {
console.error("Job definition URI was not found.");
throw new Error("Job definition URI was not found.");
}
const { result: jobDefinition } = await this.request<JobDefinition>(
`${this.serverUrl}${jobDefinitionLink.href}`,
headers
);
code = jobDefinition.code;
// Add code to existing job definition
jobToExecute.code = code;
}
const linesToExecute = code.replace(/\r\n/g, "\n").split("\n");
return await this.executeScript( return await this.executeScript(
sasJob, sasJob,
linesToExecute, linesToExecute,
contextName, contextName,
accessToken, accessToken,
"",
true, true,
data, data,
debug debug
@@ -703,6 +745,7 @@ export class SASViyaApiClient {
if (data && Object.keys(data).length) { if (data && Object.keys(data).length) {
files = await this.uploadTables(data, accessToken); files = await this.uploadTables(data, accessToken);
} }
const jobName = path.basename(sasJob); const jobName = path.basename(sasJob);
const jobFolder = sasJob.replace(`/${jobName}`, ""); const jobFolder = sasJob.replace(`/${jobName}`, "");
const allJobsInFolder = this.rootFolderMap.get(jobFolder.replace("/", "")); const allJobsInFolder = this.rootFolderMap.get(jobFolder.replace("/", ""));
@@ -712,7 +755,7 @@ export class SASViyaApiClient {
(l) => l.rel === "getResource" (l) => l.rel === "getResource"
)?.href; )?.href;
const requestInfo: any = { const requestInfo: any = {
method: "GET", method: "GET"
}; };
const headers: any = { "Content-Type": "application/json" }; const headers: any = { "Content-Type": "application/json" };
if (!!accessToken) { if (!!accessToken) {
@@ -732,7 +775,7 @@ export class SASViyaApiClient {
_OMITJSONLOG: true, _OMITJSONLOG: true,
_OMITSESSIONRESULTS: true, _OMITSESSIONRESULTS: true,
_OMITTEXTLISTING: true, _OMITTEXTLISTING: true,
_OMITTEXTLOG: true, _OMITTEXTLOG: true
}; };
if (debug) { if (debug) {
@@ -755,8 +798,8 @@ export class SASViyaApiClient {
name: `exec-${jobName}`, name: `exec-${jobName}`,
description: "Powered by SASjs", description: "Powered by SASjs",
jobDefinition, jobDefinition,
arguments: jobArguments, arguments: jobArguments
}), })
}; };
const { result: postedJob, etag } = await this.request<Job>( const { result: postedJob, etag } = await this.request<Job>(
`${this.serverUrl}/jobExecution/jobs?_action=wait`, `${this.serverUrl}/jobExecution/jobs?_action=wait`,
@@ -790,7 +833,7 @@ export class SASViyaApiClient {
log = await this.request<any>( log = await this.request<any>(
`${this.serverUrl}${logLink.href}/content`, `${this.serverUrl}${logLink.href}/content`,
{ {
headers, headers
} }
).then((res: any) => ).then((res: any) =>
res.result.items.map((i: any) => i.line).join("\n") res.result.items.map((i: any) => i.line).join("\n")
@@ -808,7 +851,7 @@ export class SASViyaApiClient {
const allItems = new Map<string, Job[]>(); const allItems = new Map<string, Job[]>();
const url = "/folders/folders/@item?path=" + this.rootFolderName; const url = "/folders/folders/@item?path=" + this.rootFolderName;
const requestInfo: any = { const requestInfo: any = {
method: "GET", method: "GET"
}; };
if (accessToken) { if (accessToken) {
requestInfo.headers = { Authorization: `Bearer ${accessToken}` }; requestInfo.headers = { Authorization: `Bearer ${accessToken}` };
@@ -860,7 +903,7 @@ export class SASViyaApiClient {
private async populateRootFolder(accessToken?: string) { private async populateRootFolder(accessToken?: string) {
const url = "/folders/folders/@item?path=" + this.rootFolderName; const url = "/folders/folders/@item?path=" + this.rootFolderName;
const requestInfo: RequestInit = { const requestInfo: RequestInit = {
method: "GET", method: "GET"
}; };
if (accessToken) { if (accessToken) {
requestInfo.headers = { Authorization: `Bearer ${accessToken}` }; requestInfo.headers = { Authorization: `Bearer ${accessToken}` };
@@ -889,12 +932,29 @@ export class SASViyaApiClient {
let pollCount = 0; let pollCount = 0;
const headers: any = { const headers: any = {
"Content-Type": "application/json", "Content-Type": "application/json",
"If-None-Match": etag, "If-None-Match": etag
}; };
if (accessToken) { if (accessToken) {
headers.Authorization = `Bearer ${accessToken}`; headers.Authorization = `Bearer ${accessToken}`;
} }
const stateLink = postedJob.links.find((l: any) => l.rel === "state"); 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.request<string>(
`${this.serverUrl}${stateLink.href}?_action=wait&wait=30`,
{
headers
},
"text"
);
const currentState = state.trim();
if (currentState === "completed") {
return Promise.resolve(currentState);
}
return new Promise(async (resolve, _) => { return new Promise(async (resolve, _) => {
const interval = setInterval(async () => { const interval = setInterval(async () => {
if ( if (
@@ -909,7 +969,7 @@ export class SASViyaApiClient {
const { result: jobState } = await this.request<string>( const { result: jobState } = await this.request<string>(
`${this.serverUrl}${stateLink.href}?_action=wait&wait=30`, `${this.serverUrl}${stateLink.href}?_action=wait&wait=30`,
{ {
headers, headers
}, },
"text" "text"
); );
@@ -941,7 +1001,7 @@ export class SASViyaApiClient {
let pollCount = 0; let pollCount = 0;
const headers: any = { const headers: any = {
"Content-Type": "application/json", "Content-Type": "application/json",
"If-None-Match": etag, "If-None-Match": etag
}; };
if (accessToken) { if (accessToken) {
headers.Authorization = `Bearer ${accessToken}`; headers.Authorization = `Bearer ${accessToken}`;
@@ -956,7 +1016,7 @@ export class SASViyaApiClient {
const { result: state } = await this.request<string>( const { result: state } = await this.request<string>(
`${this.serverUrl}${stateLink.href}?wait=30`, `${this.serverUrl}${stateLink.href}?wait=30`,
{ {
headers, headers
}, },
"text" "text"
); );
@@ -977,7 +1037,7 @@ export class SASViyaApiClient {
private async uploadTables(data: any, accessToken?: string) { private async uploadTables(data: any, accessToken?: string) {
const uploadedFiles = []; const uploadedFiles = [];
const headers: any = { const headers: any = {
"Content-Type": "application/json", "Content-Type": "application/json"
}; };
if (accessToken) { if (accessToken) {
headers.Authorization = `Bearer ${accessToken}`; headers.Authorization = `Bearer ${accessToken}`;
@@ -994,15 +1054,15 @@ export class SASViyaApiClient {
const createFileRequest = { const createFileRequest = {
method: "POST", method: "POST",
body: csv, body: csv,
headers, headers
}; };
const { result: file } = await this.request<any>( const uploadResponse = await this.request<any>(
`${this.serverUrl}/files/files#rawUpload`, `${this.serverUrl}/files/files#rawUpload`,
createFileRequest createFileRequest
); );
uploadedFiles.push({ tableName, file }); uploadedFiles.push({ tableName, file: uploadResponse.result });
} }
return uploadedFiles; return uploadedFiles;
} }
@@ -1010,7 +1070,7 @@ export class SASViyaApiClient {
private async getFolderUri(folderPath: string, accessToken?: string) { private async getFolderUri(folderPath: string, accessToken?: string) {
const url = "/folders/folders/@item?path=" + folderPath; const url = "/folders/folders/@item?path=" + folderPath;
const requestInfo: any = { const requestInfo: any = {
method: "GET", method: "GET"
}; };
if (accessToken) { if (accessToken) {
requestInfo.headers = { Authorization: `Bearer ${accessToken}` }; requestInfo.headers = { Authorization: `Bearer ${accessToken}` };
@@ -1019,9 +1079,9 @@ export class SASViyaApiClient {
`${this.serverUrl}${url}`, `${this.serverUrl}${url}`,
requestInfo requestInfo
).catch((err) => { ).catch((err) => {
return {result: null}; return { result: null };
}) });
if (!folder) return undefined; if (!folder) return undefined;
return `/folders/folders/${folder.id}`; return `/folders/folders/${folder.id}`;
} }
@@ -1039,9 +1099,14 @@ export class SASViyaApiClient {
if (this.csrfToken) { if (this.csrfToken) {
options.headers = { options.headers = {
...options.headers, ...options.headers,
[this.csrfToken.headerName]: this.csrfToken.value, [this.csrfToken.headerName]: this.csrfToken.value
}; };
} }
return await makeRequest<T>(url, options, this.setCsrfTokenLocal, contentType); return await makeRequest<T>(
url,
options,
this.setCsrfTokenLocal,
contentType
);
} }
} }

View File

@@ -2,7 +2,7 @@ import SASjs from "./index";
const adapter = new SASjs(); const adapter = new SASjs();
it("should parse SAS9 source code", async done => { it("should parse SAS9 source code", async (done) => {
expect(sampleResponse).toBeTruthy(); expect(sampleResponse).toBeTruthy();
const parsedSourceCode = (adapter as any).parseSAS9SourceCode(sampleResponse); const parsedSourceCode = (adapter as any).parseSAS9SourceCode(sampleResponse);
expect(parsedSourceCode).toBeTruthy(); expect(parsedSourceCode).toBeTruthy();
@@ -16,7 +16,7 @@ it("should parse SAS9 source code", async done => {
done(); done();
}); });
it("should parse generated code", async done => { it("should parse generated code", async (done) => {
expect(sampleResponse).toBeTruthy(); expect(sampleResponse).toBeTruthy();
const parsedGeneratedCode = (adapter as any).parseGeneratedCode( const parsedGeneratedCode = (adapter as any).parseGeneratedCode(
sampleResponse sampleResponse

View File

@@ -1,6 +1,13 @@
import "isomorphic-fetch"; import { isIEorEdgeOrOldFirefox } from "./utils/isIeOrEdge";
import * as e6p from "es6-promise"; import * as e6p from "es6-promise";
(e6p as any).polyfill(); (e6p as any).polyfill();
if (isIEorEdgeOrOldFirefox()) {
if (window) {
window.fetch = undefined as any; // ensure the polyfill runs
}
}
// tslint:disable-next-line
require("isomorphic-fetch");
import { import {
convertToCSV, convertToCSV,
compareTimestamps, compareTimestamps,
@@ -12,8 +19,9 @@ import {
isLogInSuccess, isLogInSuccess,
parseSourceCode, parseSourceCode,
parseGeneratedCode, parseGeneratedCode,
parseWeboutResponse,
needsRetry, needsRetry,
asyncForEach, asyncForEach
} from "./utils"; } from "./utils";
import { import {
SASjsConfig, SASjsConfig,
@@ -35,7 +43,7 @@ const defaultConfig: SASjsConfig = {
serverType: ServerType.SASViya, serverType: ServerType.SASViya,
debug: true, debug: true,
contextName: "SAS Job Execution compute context", contextName: "SAS Job Execution compute context",
useComputeApi: false, useComputeApi: false
}; };
const requestRetryLimit = 5; const requestRetryLimit = 5;
@@ -64,7 +72,7 @@ export default class SASjs {
constructor(config?: any) { constructor(config?: any) {
this.sasjsConfig = { this.sasjsConfig = {
...defaultConfig, ...defaultConfig,
...config, ...config
}; };
this.setupConfiguration(); this.setupConfiguration();
@@ -122,7 +130,6 @@ export default class SASjs {
linesOfCode, linesOfCode,
contextName, contextName,
accessToken, accessToken,
sessionId,
silent silent
); );
} }
@@ -263,7 +270,7 @@ export default class SASjs {
public async setSASjsConfig(config: SASjsConfig) { public async setSASjsConfig(config: SASjsConfig) {
this.sasjsConfig = { this.sasjsConfig = {
...this.sasjsConfig, ...this.sasjsConfig,
...config, ...config
}; };
await this.setupConfiguration(); await this.setupConfiguration();
} }
@@ -284,11 +291,11 @@ export default class SASjs {
public async checkSession() { public async checkSession() {
const loginResponse = await fetch(this.loginUrl.replace(".do", "")); const loginResponse = await fetch(this.loginUrl.replace(".do", ""));
const responseText = await loginResponse.text(); const responseText = await loginResponse.text();
const isLoggedIn = /You have signed in./gm.test(responseText); const isLoggedIn = /<button.+onClick.+logout/gm.test(responseText);
return Promise.resolve({ return Promise.resolve({
isLoggedIn, isLoggedIn,
userName: this.userName, userName: this.userName
}); });
} }
@@ -301,7 +308,7 @@ export default class SASjs {
const loginParams: any = { const loginParams: any = {
_service: "default", _service: "default",
username, username,
password, password
}; };
this.userName = loginParams.username; this.userName = loginParams.username;
@@ -312,7 +319,7 @@ export default class SASjs {
return Promise.resolve({ return Promise.resolve({
isLoggedIn, isLoggedIn,
userName: this.userName, userName: this.userName
}); });
} }
@@ -329,8 +336,8 @@ export default class SASjs {
referrerPolicy: "same-origin", referrerPolicy: "same-origin",
body: loginParamsStr, body: loginParamsStr,
headers: new Headers({ headers: new Headers({
"Content-Type": "application/x-www-form-urlencoded", "Content-Type": "application/x-www-form-urlencoded"
}), })
}) })
.then((response) => response.text()) .then((response) => response.text())
.then(async (responseText) => { .then(async (responseText) => {
@@ -357,7 +364,7 @@ export default class SASjs {
return { return {
isLoggedIn: loggedIn, isLoggedIn: loggedIn,
userName: this.userName, userName: this.userName
}; };
}) })
.catch((e) => Promise.reject(e)); .catch((e) => Promise.reject(e));
@@ -392,8 +399,10 @@ export default class SASjs {
this.sasjsConfig.appLoc, this.sasjsConfig.appLoc,
this.sasjsConfig.serverUrl, this.sasjsConfig.serverUrl,
this.jobsPath, this.jobsPath,
this.setCsrfTokenWeb,
this.csrfTokenWeb this.csrfTokenWeb
); );
return fileUploader.uploadFile(sasJob, files, params); return fileUploader.uploadFile(sasJob, files, params);
} }
@@ -426,7 +435,7 @@ export default class SASjs {
config = { config = {
...this.sasjsConfig, ...this.sasjsConfig,
...config, ...config
}; };
sasJob = sasJob.startsWith("/") ? sasJob.replace("/", "") : sasJob; sasJob = sasJob.startsWith("/") ? sasJob.replace("/", "") : sasJob;
@@ -514,10 +523,19 @@ export default class SASjs {
serverUrl = sasClientConfig.serverUrl; serverUrl = sasClientConfig.serverUrl;
appLoc = sasClientConfig.rootFolderName as string; appLoc = sasClientConfig.rootFolderName as string;
} }
// members of type 'folder' should be processed first
if (serviceJson.members[0].members) {
serviceJson.members[0].members.sort((member: { type: string }) =>
member.type === "folder" ? -1 : 1
);
}
const members = const members =
serviceJson.members[0].name === "services" serviceJson.members[0].name === "services"
? serviceJson.members[0].members ? serviceJson.members[0].members
: serviceJson.members; : serviceJson.members;
await this.createFoldersAndServices( await this.createFoldersAndServices(
appLoc, appLoc,
members, members,
@@ -537,61 +555,73 @@ export default class SASjs {
requestPromise: { requestPromise: {
promise: null, promise: null,
resolve: null, resolve: null,
reject: null, reject: null
}, },
SASjob: sasJob, SASjob: sasJob,
data, data
}; };
sasjsWaitingRequest.requestPromise.promise = new Promise( sasjsWaitingRequest.requestPromise.promise = new Promise(
async (resolve, reject) => { async (resolve, reject) => {
this.sasViyaApiClient this.sasViyaApiClient
?.executeComputeJob( ?.executeComputeJob(
sasJob, sasJob,
config.contextName, config.contextName,
config.debug, config.debug,
data, data,
accessToken accessToken
) )
.then((response) => { .then((response) => {
if (!config.debug) { if (!config.debug) {
this.appendSasjsRequest(null, sasJob, null); this.appendSasjsRequest(null, sasJob, null);
} else {
this.appendSasjsRequest(response, sasJob, null);
}
let responseJson;
try {
responseJson = JSON.parse(response!.result);
} catch {
responseJson = JSON.parse(parseWeboutResponse(response!.result));
}
resolve(responseJson);
})
.catch(async (response) => {
let error = response.error || response;
if (needsRetry(JSON.stringify(error))) {
if (this.retryCountComputeApi < requestRetryLimit) {
let retryResponse = await this.executeJobViaComputeApi(
sasJob,
data,
config,
loginRequiredCallback,
accessToken
);
this.retryCountComputeApi++;
resolve(retryResponse);
} else { } else {
this.appendSasjsRequest(response, sasJob, null); this.retryCountComputeApi = 0;
reject({ MESSAGE: "Compute API retry requests limit reached" });
} }
}
resolve(JSON.parse(response!.result)); if (error && error.status === 401) {
}) if (loginRequiredCallback) loginRequiredCallback(true);
.catch(async (e) => { sasjsWaitingRequest.requestPromise.resolve = resolve;
if (needsRetry(JSON.stringify(e))) { sasjsWaitingRequest.requestPromise.reject = reject;
if (this.retryCountComputeApi < requestRetryLimit) { sasjsWaitingRequest.config = config;
let retryResponse = await this.executeJobViaComputeApi( this.sasjsWaitingRequests.push(sasjsWaitingRequest);
sasJob, } else {
data, reject({ MESSAGE: error || "Job execution failed" });
config, }
loginRequiredCallback,
accessToken
);
this.retryCountComputeApi++; this.appendSasjsRequest(response.log, sasJob, null);
});
resolve(retryResponse);
} else {
this.retryCountComputeApi = 0;
reject({ MESSAGE: "Compute API retry requests limit reached" });
}
}
if (e && e.status === 401) {
if (loginRequiredCallback) loginRequiredCallback(true);
sasjsWaitingRequest.requestPromise.resolve = resolve;
sasjsWaitingRequest.requestPromise.reject = reject;
sasjsWaitingRequest.config = config;
this.sasjsWaitingRequests.push(sasjsWaitingRequest);
} else {
reject({ MESSAGE: e || "Job execution failed" });
}
})
} }
); );
return sasjsWaitingRequest.requestPromise.promise; return sasjsWaitingRequest.requestPromise.promise;
@@ -608,10 +638,10 @@ export default class SASjs {
requestPromise: { requestPromise: {
promise: null, promise: null,
resolve: null, resolve: null,
reject: null, reject: null
}, },
SASjob: sasJob, SASjob: sasJob,
data, data
}; };
sasjsWaitingRequest.requestPromise.promise = new Promise( sasjsWaitingRequest.requestPromise.promise = new Promise(
@@ -640,7 +670,18 @@ export default class SASjs {
} else { } else {
this.appendSasjsRequest(response, sasJob, null); this.appendSasjsRequest(response, sasJob, null);
} }
return JSON.parse(response!.result);
let responseJson;
try {
responseJson = JSON.parse(response!.result);
} catch {
responseJson = JSON.parse(
parseWeboutResponse(response!.result)
);
}
return responseJson;
}) })
.catch(async (e) => { .catch(async (e) => {
if (needsRetry(JSON.stringify(e))) { if (needsRetry(JSON.stringify(e))) {
@@ -652,9 +693,9 @@ export default class SASjs {
loginRequiredCallback, loginRequiredCallback,
accessToken accessToken
); );
this.retryCountJeseApi++; this.retryCountJeseApi++;
resolve(retryResponse); resolve(retryResponse);
} else { } else {
this.retryCountJeseApi = 0; this.retryCountJeseApi = 0;
@@ -662,7 +703,7 @@ export default class SASjs {
} }
} }
reject({ MESSAGE: (e && e.message) || "Job execution failed" }) reject({ MESSAGE: (e && e.message) || "Job execution failed" });
}) })
); );
} }
@@ -681,10 +722,10 @@ export default class SASjs {
requestPromise: { requestPromise: {
promise: null, promise: null,
resolve: null, resolve: null,
reject: null, reject: null
}, },
SASjob: sasJob, SASjob: sasJob,
data, data
}; };
const program = config.appLoc const program = config.appLoc
? config.appLoc.replace(/\/?$/, "/") + sasJob.replace(/^\//, "") ? config.appLoc.replace(/\/?$/, "/") + sasJob.replace(/^\//, "")
@@ -698,7 +739,7 @@ export default class SASjs {
}`; }`;
const requestParams = { const requestParams = {
...this.getRequestParamsWeb(), ...this.getRequestParamsWeb(config)
}; };
const formData = new FormData(); const formData = new FormData();
@@ -727,7 +768,7 @@ export default class SASjs {
} }
const file = new Blob([csv], { const file = new Blob([csv], {
type: "application/csv", type: "application/csv"
}); });
formData.append(name, file, `${name}.csv`); formData.append(name, file, `${name}.csv`);
@@ -784,7 +825,7 @@ export default class SASjs {
method: "POST", method: "POST",
body: formData, body: formData,
referrerPolicy: "same-origin", referrerPolicy: "same-origin",
headers, headers
}) })
.then(async (response) => { .then(async (response) => {
if (!response.ok) { if (!response.ok) {
@@ -795,7 +836,7 @@ export default class SASjs {
const token = response.headers.get(tokenHeader); const token = response.headers.get(tokenHeader);
this.csrfTokenWeb = { this.csrfTokenWeb = {
headerName: tokenHeader, headerName: tokenHeader,
value: token || "", value: token || ""
}; };
} }
} }
@@ -835,13 +876,13 @@ export default class SASjs {
} else { } else {
if (config.serverType === ServerType.SAS9 && config.debug) { if (config.serverType === ServerType.SAS9 && config.debug) {
this.updateUsername(responseText); this.updateUsername(responseText);
const jsonResponseText = this.parseSAS9Response(responseText); const jsonResponseText = parseWeboutResponse(responseText);
if (jsonResponseText !== "") { if (jsonResponseText !== "") {
resolve(JSON.parse(jsonResponseText)); resolve(JSON.parse(jsonResponseText));
} else { } else {
reject({ reject({
MESSAGE: this.parseSAS9ErrorResponse(responseText), MESSAGE: this.parseSAS9ErrorResponse(responseText)
}); });
} }
} else if ( } else if (
@@ -886,6 +927,10 @@ export default class SASjs {
return sasjsWaitingRequest.requestPromise.promise; return sasjsWaitingRequest.requestPromise.promise;
} }
private setCsrfTokenWeb = (csrfToken: CsrfToken) => {
this.csrfTokenWeb = csrfToken;
};
private setCsrfTokenApi = (csrfToken: CsrfToken) => { private setCsrfTokenApi = (csrfToken: CsrfToken) => {
this.csrfTokenApi = csrfToken; this.csrfTokenApi = csrfToken;
}; };
@@ -905,14 +950,14 @@ export default class SASjs {
this.sasjsWaitingRequests = []; this.sasjsWaitingRequests = [];
} }
private getRequestParamsWeb(): any { private getRequestParamsWeb(config: any): any {
const requestParams: any = {}; const requestParams: any = {};
if (this.csrfTokenWeb) { if (this.csrfTokenWeb) {
requestParams["_csrf"] = this.csrfTokenWeb.value; requestParams["_csrf"] = this.csrfTokenWeb.value;
} }
if (this.sasjsConfig.debug) { if (config.debug) {
requestParams["_omittextlog"] = "false"; requestParams["_omittextlog"] = "false";
requestParams["_omitsessionresults"] = "false"; requestParams["_omitsessionresults"] = "false";
@@ -976,23 +1021,6 @@ export default class SASjs {
return uri; return uri;
} }
private parseSAS9Response(response: string) {
let sas9Response = "";
if (response.includes(">>weboutBEGIN<<")) {
try {
sas9Response = response
.split(">>weboutBEGIN<<")[1]
.split(">>weboutEND<<")[0];
} catch (e) {
sas9Response = "";
console.error(e);
}
}
return sas9Response;
}
private parseSAS9ErrorResponse(response: string) { private parseSAS9ErrorResponse(response: string) {
const logLines = response.split("\n"); const logLines = response.split("\n");
const parsedLines: string[] = []; const parsedLines: string[] = [];
@@ -1030,7 +1058,7 @@ export default class SASjs {
private fetchLogFileContent(logLink: string) { private fetchLogFileContent(logLink: string) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
fetch(logLink, { fetch(logLink, {
method: "GET", method: "GET"
}) })
.then((response: any) => response.text()) .then((response: any) => response.text())
.then((response: any) => resolve(response)) .then((response: any) => resolve(response))
@@ -1050,7 +1078,16 @@ export default class SASjs {
if (response && response.result && response.log) { if (response && response.result && response.log) {
sourceCode = parseSourceCode(response.log); sourceCode = parseSourceCode(response.log);
generatedCode = parseGeneratedCode(response.log); generatedCode = parseGeneratedCode(response.log);
sasWork = JSON.parse(response.result).WORK;
if (this.sasjsConfig.debug) {
if (response.log) {
sasWork = response.log;
} else {
sasWork = JSON.parse(parseWeboutResponse(response.result)).WORK;
}
} else {
sasWork = JSON.parse(response.result).WORK;
}
} else { } else {
if (response) { if (response) {
sourceCode = parseSourceCode(response); sourceCode = parseSourceCode(response);
@@ -1065,7 +1102,7 @@ export default class SASjs {
timestamp: new Date(), timestamp: new Date(),
sourceCode, sourceCode,
generatedCode, generatedCode,
SASWORK: sasWork, SASWORK: sasWork
}); });
if (this.sasjsRequests.length > 20) { if (this.sasjsRequests.length > 20) {
@@ -1079,7 +1116,7 @@ export default class SASjs {
if (this.sasjsConfig.serverType === ServerType.SAS9) { if (this.sasjsConfig.serverType === ServerType.SAS9) {
try { try {
jsonResponse = JSON.parse(this.parseSAS9Response(response)); jsonResponse = JSON.parse(parseWeboutResponse(response));
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }
@@ -1110,6 +1147,10 @@ export default class SASjs {
return sortedRequests; return sortedRequests;
} }
public clearSasRequests() {
this.sasjsRequests = [];
}
private setupConfiguration() { private setupConfiguration() {
if ( if (
this.sasjsConfig.serverUrl === undefined || this.sasjsConfig.serverUrl === undefined ||
@@ -1159,7 +1200,8 @@ export default class SASjs {
this.fileUploader = new FileUploader( this.fileUploader = new FileUploader(
this.sasjsConfig.appLoc, this.sasjsConfig.appLoc,
this.sasjsConfig.serverUrl, this.sasjsConfig.serverUrl,
this.jobsPath this.jobsPath,
this.setCsrfTokenWeb
); );
} }

View File

@@ -16,7 +16,31 @@ export class SessionManager {
async getSession(accessToken?: string) { async getSession(accessToken?: string) {
await this.createSessions(accessToken); await this.createSessions(accessToken);
this.createAndWaitForSession(accessToken); this.createAndWaitForSession(accessToken);
return this.sessions.pop(); const session = this.sessions.pop();
const secondsSinceSessionCreation =
(new Date().getTime() - new Date(session!.creationTimeStamp).getTime()) /
1000;
if (
secondsSinceSessionCreation >= session!.attributes.sessionInactiveTimeout
) {
await this.createSessions(accessToken);
const freshSession = this.sessions.pop();
return freshSession;
}
return session;
}
async clearSession(id: string, accessToken?: string) {
const deleteSessionRequest = {
method: "DELETE",
headers: this.getHeaders(accessToken)
};
return await this.request<Session>(
`${this.serverUrl}/compute/sessions/${id}`,
deleteSessionRequest
).then(() => {
this.sessions = this.sessions.filter((s) => s.id !== id);
});
} }
private async createSessions(accessToken?: string) { private async createSessions(accessToken?: string) {
@@ -34,7 +58,7 @@ export class SessionManager {
private async createAndWaitForSession(accessToken?: string) { private async createAndWaitForSession(accessToken?: string) {
const createSessionRequest = { const createSessionRequest = {
method: "POST", method: "POST",
headers: this.getHeaders(accessToken), headers: this.getHeaders(accessToken)
}; };
const { result: createdSession, etag } = await this.request<Session>( const { result: createdSession, etag } = await this.request<Session>(
`${this.serverUrl}/compute/contexts/${this.currentContext!.id}/sessions`, `${this.serverUrl}/compute/contexts/${this.currentContext!.id}/sessions`,
@@ -51,7 +75,7 @@ export class SessionManager {
const { result: contexts } = await this.request<{ const { result: contexts } = await this.request<{
items: Context[]; items: Context[];
}>(`${this.serverUrl}/compute/contexts`, { }>(`${this.serverUrl}/compute/contexts`, {
headers: this.getHeaders(accessToken), headers: this.getHeaders(accessToken)
}); });
const contextsList = const contextsList =
@@ -75,7 +99,7 @@ export class SessionManager {
private getHeaders(accessToken?: string) { private getHeaders(accessToken?: string) {
const headers: any = { const headers: any = {
"Content-Type": "application/json", "Content-Type": "application/json"
}; };
if (accessToken) { if (accessToken) {
headers.Authorization = `Bearer ${accessToken}`; headers.Authorization = `Bearer ${accessToken}`;
@@ -93,7 +117,7 @@ export class SessionManager {
let sessionState = session.state; let sessionState = session.state;
const headers: any = { const headers: any = {
...this.getHeaders(accessToken), ...this.getHeaders(accessToken),
"If-None-Match": etag, "If-None-Match": etag
}; };
const stateLink = session.links.find((l: any) => l.rel === "state"); const stateLink = session.links.find((l: any) => l.rel === "state");
return new Promise(async (resolve, _) => { return new Promise(async (resolve, _) => {
@@ -105,7 +129,7 @@ export class SessionManager {
const { result: state } = await this.request<string>( const { result: state } = await this.request<string>(
`${this.serverUrl}${stateLink.href}?wait=30`, `${this.serverUrl}${stateLink.href}?wait=30`,
{ {
headers, headers
}, },
"text" "text"
); );
@@ -130,7 +154,7 @@ export class SessionManager {
if (this.csrfToken) { if (this.csrfToken) {
options.headers = { options.headers = {
...options.headers, ...options.headers,
[this.csrfToken.headerName]: this.csrfToken.value, [this.csrfToken.headerName]: this.csrfToken.value
}; };
} }
return await makeRequest<T>( return await makeRequest<T>(

View File

@@ -6,6 +6,7 @@ export interface Job {
name: string; name: string;
uri: string; uri: string;
createdBy: string; createdBy: string;
code?: string;
links: Link[]; links: Link[];
results: JobResult; results: JobResult;
error?: any; error?: any;

View File

@@ -4,5 +4,5 @@
*/ */
export enum ServerType { export enum ServerType {
SASViya = "SASVIYA", SASViya = "SASVIYA",
SAS9 = "SAS9", SAS9 = "SAS9"
} }

View File

@@ -4,4 +4,8 @@ export interface Session {
id: string; id: string;
state: string; state: string;
links: Link[]; links: Link[];
attributes: {
sessionInactiveTimeout: number;
};
creationTimeStamp: string;
} }

View File

@@ -3,7 +3,6 @@
* *
*/ */
export interface UploadFile { export interface UploadFile {
file: File; file: File;
fileName: string; fileName: string;
} }

View File

@@ -12,3 +12,4 @@ export * from "./parseSourceCode";
export * from "./parseSasViyaLog"; export * from "./parseSasViyaLog";
export * from "./serialize"; export * from "./serialize";
export * from "./splitChunks"; export * from "./splitChunks";
export * from "./parseWeboutResponse";

34
src/utils/isIeOrEdge.ts Normal file
View File

@@ -0,0 +1,34 @@
export function isIEorEdgeOrOldFirefox() {
if (typeof window === "undefined") {
return false;
}
const ua = window.navigator.userAgent;
if (ua.indexOf("Firefox") > 0) {
const version = parseInt(
ua.substring(ua.lastIndexOf("Firefox/") + 8, ua.length),
10
);
return version <= 60;
}
const msie = ua.indexOf("MSIE ");
if (msie > 0) {
// IE 10 or older => return version number
return true;
}
const trident = ua.indexOf("Trident/");
if (trident > 0) {
return true;
}
const edge = ua.indexOf("Edge/");
if (edge > 0) {
// Edge (IE 12+) => return version number
return true;
}
// other browser
return false;
}

View File

@@ -18,6 +18,9 @@ export async function makeRequest<T>(
: (res: Response) => res.text(); : (res: Response) => res.text();
let etag = null; let etag = null;
const result = await fetch(url, request).then(async (response) => { const result = await fetch(url, request).then(async (response) => {
if (response.redirected && response.url.includes("SASLogon/login")) {
return Promise.reject({ status: 401 });
}
if (!response.ok) { if (!response.ok) {
if (response.status === 403) { if (response.status === 403) {
const tokenHeader = response.headers.get("X-CSRF-HEADER"); const tokenHeader = response.headers.get("X-CSRF-HEADER");
@@ -26,12 +29,12 @@ export async function makeRequest<T>(
const token = response.headers.get(tokenHeader); const token = response.headers.get(tokenHeader);
callback({ callback({
headerName: tokenHeader, headerName: tokenHeader,
value: token || "", value: token || ""
}); });
retryRequest = { retryRequest = {
...request, ...request,
headers: { ...request.headers, [tokenHeader]: token }, headers: { ...request.headers, [tokenHeader]: token }
}; };
return fetch(url, retryRequest).then((res) => { return fetch(url, retryRequest).then((res) => {
etag = res.headers.get("ETag"); etag = res.headers.get("ETag");
@@ -44,44 +47,55 @@ export async function makeRequest<T>(
if (needsRetry(body)) { if (needsRetry(body)) {
if (retryCount < retryLimit) { if (retryCount < retryLimit) {
retryCount++; retryCount++;
let retryResponse = await makeRequest(url, retryRequest || request, callback, contentType); let retryResponse = await makeRequest(
url,
retryRequest || request,
callback,
contentType
);
retryCount = 0; retryCount = 0;
return retryResponse; etag = retryResponse.etag;
return retryResponse.result;
} else { } else {
retryCount = 0; retryCount = 0;
throw new Error('Request retry limit exceeded'); throw new Error("Request retry limit exceeded");
} }
} }
return Promise.reject({ status: response.status, body }); return Promise.reject({ status: response.status, body });
} }
} else { } else {
if (response.status === 204) {
return Promise.resolve();
}
const responseTransformed = await responseTransform(response); const responseTransformed = await responseTransform(response);
let responseText = ''; let responseText = "";
if (typeof responseTransformed === 'string') { if (typeof responseTransformed === "string") {
responseText = responseTransformed; responseText = responseTransformed;
} else { } else {
responseText = JSON.stringify(responseTransformed); responseText = JSON.stringify(responseTransformed);
} }
if (response.redirected && response.url.includes("SASLogon/login")) {
return Promise.reject({ status: 401, responseTransformed });
}
if (needsRetry(responseText)) { if (needsRetry(responseText)) {
if (retryCount < retryLimit) { if (retryCount < retryLimit) {
retryCount++; retryCount++;
let retryResponse = await makeRequest(url, retryRequest || request, callback, contentType); const retryResponse = await makeRequest(
url,
retryRequest || request,
callback,
contentType
);
retryCount = 0; retryCount = 0;
return retryResponse; etag = retryResponse.etag;
return retryResponse.result;
} else { } else {
retryCount = 0; retryCount = 0;
throw new Error('Request retry limit exceeded'); throw new Error("Request retry limit exceeded");
} }
} }

View File

@@ -1,11 +1,14 @@
export const needsRetry = (responseText: string): boolean => { export const needsRetry = (responseText: string): boolean => {
return ( return (
(responseText.includes('"errorCode":403') && !!responseText &&
((responseText.includes('"errorCode":403') &&
responseText.includes("_csrf") && responseText.includes("_csrf") &&
responseText.includes("X-CSRF-TOKEN")) || responseText.includes("X-CSRF-TOKEN")) ||
(responseText.includes('"status":403') && (responseText.includes('"status":403') &&
responseText.includes('"error":"Forbidden"')) || responseText.includes('"error":"Forbidden"')) ||
(responseText.includes('"status":449') && (responseText.includes('"status":449') &&
responseText.includes("Authentication success, retry original request")) responseText.includes(
"Authentication success, retry original request"
)))
); );
}; };

View File

@@ -36,7 +36,7 @@ export const parseAndSubmitAuthorizeForm = async (
method: "POST", method: "POST",
credentials: "include", credentials: "include",
body: formData, body: formData,
referrerPolicy: "same-origin", referrerPolicy: "same-origin"
}) })
.then((res) => res.text()) .then((res) => res.text())
.then((res) => { .then((res) => {

View File

@@ -0,0 +1,16 @@
export const parseWeboutResponse = (response: string) => {
let sasResponse = "";
if (response.includes(">>weboutBEGIN<<")) {
try {
sasResponse = response
.split(">>weboutBEGIN<<")[1]
.split(">>weboutEND<<")[0];
} catch (e) {
sasResponse = "";
console.error(e);
}
}
return sasResponse;
};