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

Compare commits

...

62 Commits

Author SHA1 Message Date
Allan Bowe
ab8da28de1 Merge pull request #798 from sasjs/redirected-login-m8
fix: sas9 m8 redirected login
2023-04-05 15:14:16 +01:00
a729d67d3e chore: remove /home leftovers 2023-04-05 15:39:31 +02:00
548a44d665 chore: auth manager test fix 2023-04-04 14:08:47 +02:00
Allan Bowe
afda43fc7f Merge pull request #799 from sasjs/issue-470
fix: regex for extracting login url fixed
2023-04-03 22:21:13 +01:00
5291e7f01c fix: regex for extracting login url fixed 2023-04-03 23:10:27 +05:00
39abdad518 fix: sas9 m8 redirected login 2023-04-03 12:48:32 +02:00
Allan Bowe
6aa12ee950 Merge pull request #797 from sasjs/all-contributors/add-saramartinelli1992
docs: add saramartinelli1992 as a contributor for userTesting, and platform
2023-04-03 11:41:34 +01:00
allcontributors[bot]
b5b5093295 docs: update .all-contributorsrc [skip ci] 2023-04-03 10:41:06 +00:00
allcontributors[bot]
114ca21c17 docs: update README.md [skip ci] 2023-04-03 10:41:05 +00:00
Allan Bowe
6aee95b21d Merge pull request #794 from sasjs/issue-303
fix: modify the regex to handle the loginForm response on latest sas9
2023-03-27 13:09:11 +01:00
3d281abbf8 chore: downgrade pem dev dependency by one minor version 2023-03-27 15:09:10 +05:00
99d783e174 fix: write a message to the log indicating that a login was not attempted as a valid session already exists 2023-03-24 15:11:10 +05:00
17a3d1b8a9 fix: modify the reqgex to handle the loginForm response on latest sas9 2023-03-22 23:33:42 +05:00
Allan Bowe
01af5eb634 Merge pull request #791 from sasjs/sas9-mini
feat: create a minified version for sas9  and web based appications
2023-03-16 13:36:08 +00:00
0c3797e2de chore: deleted package-lock.json and recreated by npm install 2023-03-16 16:22:26 +05:00
c33c509207 chore: merge master 2023-03-16 13:40:22 +05:00
af351d7375 chore: bump webpack 2023-03-16 12:39:39 +05:00
2b53406cac chore: no need for forked version of tough cookie 2023-03-16 00:28:06 +05:00
99cfb8b2af feat: created a minified version of adapter for executing web jobs on sas9 2023-03-16 00:26:08 +05:00
Yury Shkoda
22fa185715 feat: minified adapter for SAS9 2023-03-14 10:45:03 +03:00
Allan Bowe
dad99557a7 Merge pull request #788 from sasjs/quick-fix
fix: throw error as it is when its an instance of JobExecutionError
2023-03-06 10:15:36 +00:00
c7cc2e5fa4 fix: throw error as it is in sas9RequestClient when its an instance of JobExecutionError 2023-03-06 14:53:42 +05:00
Allan Bowe
2bd7544051 Merge pull request #787 from sasjs/issue-786
fix: removing .do from loginUrl mechanism
2023-02-21 18:56:12 +00:00
1fb972d88a chore: improved url match 2023-02-21 16:10:14 +01:00
64f8f8c893 fix: removing .do from loginUrl mechanism 2023-02-21 16:04:50 +01:00
Sabir Hassan
ddb4a51c55 Merge pull request #785 from sasjs/issue-783
Sasjs executor not re-sending waiting requests
2023-02-15 21:45:16 +05:00
921d6ef364 fix: sasjs executor not re-sending waiting requests 2023-02-15 17:26:23 +01:00
Allan Bowe
105675d46a Merge pull request #784 from sasjs/medjedovicm-issue-template
Create issue_template.md
2023-02-15 16:18:27 +00:00
Allan Bowe
e4addba762 Update issue_template.md 2023-02-15 16:17:02 +00:00
8203e918fd Create issue_template.md 2023-02-15 17:15:24 +01:00
Allan Bowe
2210e43880 chore: update README
Thanks @saramartinelli1992

https://github.com/sasjs/adapter/issues/781#issuecomment-1420943632
2023-02-07 15:20:10 +00:00
Allan Bowe
b04df0bc6d Merge pull request #779 from sasjs/issue-771
fix: bump axios version
2023-02-01 10:23:29 +00:00
98e851b4d8 chore: npm audit fix 2023-01-31 23:08:56 +05:00
84306bea3d fix: bump axios version to 0.27.2 2023-01-31 23:05:54 +05:00
89d32262f8 chore: form-data provides its own type definitions, so you do not need @types/form-data installed 2023-01-31 15:49:40 +05:00
257010f57d chore: axios provides its own type definitions, so you don't need @types/axios installed 2023-01-31 15:48:08 +05:00
eb9991015b chore: fix npm install 2023-01-31 15:45:49 +05:00
Allan Bowe
9d17e87a09 Merge pull request #778 from sasjs/issue-777
fix: improve error message when sasjs runner is not found
2023-01-30 19:12:48 +00:00
55f309e998 chore: quick fix 2023-01-30 23:50:40 +05:00
3d9b40398c fix: improve error message when sasjs runner is not found 2023-01-30 23:22:43 +05:00
Allan Bowe
e0badae973 fix: bumping semantic release package 2023-01-26 13:51:22 +00:00
Allan Bowe
524c561390 Merge pull request #775 from sasjs/import-node-env
fix: createReadStream import where needed
2023-01-26 13:46:39 +00:00
e7ceac1b78 fix: createReadStream import where needed
It's node specific util and othervise adapter was failing on browser environment
2023-01-09 13:07:51 +01:00
Allan Bowe
72ddd424a5 Merge pull request #772 from sasjs/add-deployZipFile
feat: add a new method deployZipFile in sasjsAPiClient class
2022-12-06 11:25:48 +00:00
85f771d1ed feat: add a new method deployZipFile in sasjsAPiClient class 2022-12-06 14:51:34 +05:00
Allan Bowe
1a781c3a56 chore: adding matrix room link in action 2022-10-24 21:25:52 +00:00
Allan Bowe
296d4efdfb Merge pull request #768 from sasjs/update-sasjs-tests-deps
fix: vulnerabilities in sasjs-tests
2022-10-24 21:48:22 +01:00
8df09d01de fix: bump deps versions
@sasjs/test-framework
node-sass
2022-10-25 01:41:09 +05:00
Allan Bowe
2d4a9d6dee chore: updating matrix room id for matrixbot 2022-10-24 10:24:46 +00:00
Allan Bowe
38c30f6342 Merge pull request #767 from sasjs/removevpn
fix: removing old vpn files
2022-10-24 11:12:01 +01:00
Allan Bowe
dd72304bc7 fix: removing old vpn files
BREAKING CHANGE: The _previous_ commit introduced some breaking changes, due to method re-organisation, namely:
* merged executableScriptSAS9, executableScriptSASViya and executableScriptSASjs methods to executableScript
* removed deployToSASjs and executeJobSASjs from the main SASjs moodule, as we can use the SASjsApiClient directly for these operations
2022-10-24 10:10:39 +00:00
Allan Bowe
296a543b2d Merge pull request #766 from sasjs/breaking-change
fix!: move methods from main sasjs class to respective api client class
2022-10-20 14:02:06 +01:00
70b31dcb8f fix: export SasjsRequestClient from main index.ts 2022-10-18 19:10:46 +05:00
b0c2a81989 fix: remove deployToSasjs and executeJobSasjs from main sasjs module 2022-10-18 19:09:56 +05:00
53e167b17d fix: merge executeScriptSAS9, executeScriptSASViya and executeScriptSASjs into single executeScript
BREAKING CHANGE
2022-10-17 23:14:51 +05:00
Allan Bowe
5159318d0d chore: final edit to matrix 2022-10-07 12:35:09 +00:00
Allan Bowe
6842ee13e4 chore: matrix curl syntax 2022-10-07 12:29:13 +00:00
Allan Bowe
2ce0395a2e chore: new gh-pages action 2022-10-07 12:24:58 +00:00
Allan Bowe
63440ddfd2 chore: matrix message fix 2022-10-07 12:20:37 +00:00
Allan Bowe
eb6729a9c7 chore: fix error if folder does not exist 2022-10-07 12:13:40 +00:00
Allan Bowe
9cd9dc83f3 chore: putting docs back in docs folder 2022-10-07 12:06:47 +00:00
Allan Bowe
7608887a0e chore: move to gh-pages 2022-10-07 12:00:55 +00:00
27 changed files with 6166 additions and 2958 deletions

View File

@@ -106,6 +106,16 @@
"userTesting",
"doc"
]
},
{
"login": "saramartinelli1992",
"name": "Sara",
"avatar_url": "https://avatars.githubusercontent.com/u/100193908?v=4",
"profile": "https://github.com/saramartinelli1992",
"contributions": [
"userTesting",
"platform"
]
}
],
"contributorsPerLine": 7,

12
.github/issue_template.md vendored Normal file
View File

@@ -0,0 +1,12 @@
## Expected behaviour
*Describe what should be happening*
## Current behaviour
*Describe what is actually happening*
## Environment info
**Client tech stack**: *Angular, React, Vue, VanillaJS, NodeJS etc.*
**Server type**: SASJS|SASVIYA|SAS9
**Login mechanism**: Default|Redirected
**Debug**: true|false
**Use Compute Api (relevant only on VIYA)**: true|false

View File

@@ -1,30 +0,0 @@
cipher AES-256-CBC
setenv FORWARD_COMPATIBLE 1
client
server-poll-timeout 4
nobind
remote vpn.analytium.co.uk 1194 udp
remote vpn.analytium.co.uk 1194 udp
remote vpn.analytium.co.uk 443 tcp
remote vpn.analytium.co.uk 1194 udp
remote vpn.analytium.co.uk 1194 udp
remote vpn.analytium.co.uk 1194 udp
remote vpn.analytium.co.uk 1194 udp
remote vpn.analytium.co.uk 1194 udp
dev tun
dev-type tun
ns-cert-type server
setenv opt tls-version-min 1.0 or-highest
reneg-sec 604800
sndbuf 0
rcvbuf 0
# NOTE: LZO commands are pushed by the Access Server at connect time.
# NOTE: The below line doesn't disable LZO.
comp-lzo no
verb 3
setenv PUSH_PEER_INFO
ca ca.crt
cert user.crt
key user.key
tls-auth tls.key 1

View File

@@ -45,31 +45,6 @@ jobs:
key: ${{ secrets.DCGITLAB_KEY }}
known_hosts: 'placeholder'
- name: Write VPN Files
run: |
echo "$CA_CRT" > .github/vpn/ca.crt
echo "$USER_CRT" > .github/vpn/user.crt
echo "$USER_KEY" > .github/vpn/user.key
echo "$TLS_KEY" > .github/vpn/tls.key
shell: bash
env:
CA_CRT: ${{ secrets.CA_CRT}}
USER_CRT: ${{ secrets.USER_CRT }}
USER_KEY: ${{ secrets.USER_KEY }}
TLS_KEY: ${{ secrets.TLS_KEY }}
- name: Install Open VPN
run: |
sudo apt install apt-transport-https
sudo wget https://swupdate.openvpn.net/repos/openvpn-repo-pkg-key.pub
sudo apt-key add openvpn-repo-pkg-key.pub
sudo wget -O /etc/apt/sources.list.d/openvpn3.list https://swupdate.openvpn.net/community/openvpn3/repos/openvpn3-focal.list
sudo apt update
sudo apt install openvpn3=16~beta+focal
- name: Start Open VPN 3
run: openvpn3 session-start --config .github/vpn/config.ovpn
- name: Deploy sasjs-tests
run: |
npm install -g replace-in-files-cli

View File

@@ -26,17 +26,19 @@ jobs:
- name: Install Dependencies
run: npm ci
- name: Ensure docs folder exists
run: |
rm -rf docs || true # avoid error if docs folder does not exist
mkdir docs
- name: Generate Docs
run: npm run typedoc
- name: Create CNAME file in docs
run: |
touch CNAME
echo adapter.sasjs.io >> CNAME
- name: Push generated docs
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GH_TOKEN }}
publish_branch: gh-pages
publish_dir: ./docs
cname: adapter.sasjs.io
- name: Push generated docs to docs branch
uses: nicholasgriffintn/github-branch-deployment-action@0.0.1
env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
BRANCH: docs
MESSAGE: 'Docs: ({sha}) {msg}'

View File

@@ -34,10 +34,10 @@ jobs:
run: npm run build
- name: Semantic Release
uses: cycjimmy/semantic-release-action@v2
uses: cycjimmy/semantic-release-action@v3
env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Send Matrix message
run: curl -X POST --data-urlencode "payload={\"msgtype\":\"m.text\", \"body\":\"New version of @sasjs/adapter has been released! \n Please deploy and run `dctests` with new adapter to make sure everything is still in place.\"}" https://matrix.4gl.io/_matrix/client/r0/rooms/%21BDUPBPEGVvRLKLQUxY:4gl.io/send/m.room.message?access_token=${{ secrets.MATRIX_TOKEN }}
run: curl -XPOST -d "{\"msgtype\":\"m.text\", \"body\":\"New version of @sasjs/adapter has been released! \n Please deploy and run 'dctests' with new adapter to make sure everything is still in place.\"}" https://matrix.4gl.io/_matrix/client/r0/rooms/!jRebyiGmHZlpfDwYXN:4gl.io/send/m.room.message?access_token=${{ secrets.MATRIX_TOKEN }}

View File

@@ -20,7 +20,7 @@ SASjs is a open-source framework for building Web Apps on SAS® platforms. You c
1 - `npm install @sasjs/adapter` - for use in a nodeJS project (recommended)
2 - [Download](https://cdn.jsdelivr.net/npm/@sasjs/adapter@3/index.min.js) and use a copy of the latest JS file
2 - [Download](https://cdn.jsdelivr.net/npm/@sasjs/adapter@4/index.min.js) and use a copy of the latest JS file
3 - Reference directly from the CDN - in which case click [here](https://www.jsdelivr.com/package/npm/@sasjs/adapter?tab=collection) and select "SRI" to get the script tag with the integrity hash.
@@ -332,7 +332,7 @@ If you find this library useful, help us grow our star graph!
## Contributors ✨
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-8-orange.svg?style=flat-square)](#contributors-)
[![All Contributors](https://img.shields.io/badge/all_contributors-9-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END -->
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
@@ -341,18 +341,21 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<table>
<tr>
<td align="center"><a href="https://krishna-acondy.io/"><img src="https://avatars.githubusercontent.com/u/2980428?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Krishna Acondy</b></sub></a><br /><a href="https://github.com/sasjs/adapter/commits?author=krishna-acondy" title="Code">💻</a> <a href="#infra-krishna-acondy" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#blog-krishna-acondy" title="Blogposts">📝</a> <a href="#content-krishna-acondy" title="Content">🖋</a> <a href="#ideas-krishna-acondy" title="Ideas, Planning, & Feedback">🤔</a> <a href="#video-krishna-acondy" title="Videos">📹</a></td>
<td align="center"><a href="https://www.erudicat.com/"><img src="https://avatars.githubusercontent.com/u/25773492?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Yury Shkoda</b></sub></a><br /><a href="https://github.com/sasjs/adapter/commits?author=YuryShkoda" title="Code">💻</a> <a href="#infra-YuryShkoda" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#ideas-YuryShkoda" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/sasjs/adapter/commits?author=YuryShkoda" title="Tests">⚠️</a> <a href="#video-YuryShkoda" title="Videos">📹</a></td>
<td align="center"><a href="https://github.com/medjedovicm"><img src="https://avatars.githubusercontent.com/u/18329105?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mihajlo Medjedovic</b></sub></a><br /><a href="https://github.com/sasjs/adapter/commits?author=medjedovicm" title="Code">💻</a> <a href="#infra-medjedovicm" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="https://github.com/sasjs/adapter/commits?author=medjedovicm" title="Tests">⚠️</a> <a href="https://github.com/sasjs/adapter/pulls?q=is%3Apr+reviewed-by%3Amedjedovicm" title="Reviewed Pull Requests">👀</a></td>
<td align="center"><a href="https://github.com/allanbowe"><img src="https://avatars.githubusercontent.com/u/4420615?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Allan Bowe</b></sub></a><br /><a href="https://github.com/sasjs/adapter/commits?author=allanbowe" title="Code">💻</a> <a href="https://github.com/sasjs/adapter/pulls?q=is%3Apr+reviewed-by%3Aallanbowe" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/sasjs/adapter/commits?author=allanbowe" title="Tests">⚠️</a> <a href="#mentoring-allanbowe" title="Mentoring">🧑‍🏫</a> <a href="#maintenance-allanbowe" title="Maintenance">🚧</a></td>
<td align="center"><a href="https://github.com/saadjutt01"><img src="https://avatars.githubusercontent.com/u/8914650?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Muhammad Saad </b></sub></a><br /><a href="https://github.com/sasjs/adapter/commits?author=saadjutt01" title="Code">💻</a> <a href="https://github.com/sasjs/adapter/pulls?q=is%3Apr+reviewed-by%3Asaadjutt01" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/sasjs/adapter/commits?author=saadjutt01" title="Tests">⚠️</a> <a href="#mentoring-saadjutt01" title="Mentoring">🧑‍🏫</a> <a href="#infra-saadjutt01" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
<td align="center"><a href="https://github.com/sabhas"><img src="https://avatars.githubusercontent.com/u/82647447?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sabir Hassan</b></sub></a><br /><a href="https://github.com/sasjs/adapter/commits?author=sabhas" title="Code">💻</a> <a href="https://github.com/sasjs/adapter/pulls?q=is%3Apr+reviewed-by%3Asabhas" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/sasjs/adapter/commits?author=sabhas" title="Tests">⚠️</a> <a href="#ideas-sabhas" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="https://github.com/VladislavParhomchik"><img src="https://avatars.githubusercontent.com/u/83717836?v=4?s=100" width="100px;" alt=""/><br /><sub><b>VladislavParhomchik</b></sub></a><br /><a href="https://github.com/sasjs/adapter/commits?author=VladislavParhomchik" title="Tests">⚠️</a> <a href="https://github.com/sasjs/adapter/pulls?q=is%3Apr+reviewed-by%3AVladislavParhomchik" title="Reviewed Pull Requests">👀</a></td>
</tr>
<tr>
<td align="center"><a href="http://rudvfaden.github.io/"><img src="https://avatars.githubusercontent.com/u/2445577?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Rud Faden</b></sub></a><br /><a href="#userTesting-rudvfaden" title="User Testing">📓</a> <a href="https://github.com/sasjs/adapter/commits?author=rudvfaden" title="Documentation">📖</a></td>
</tr>
<tbody>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://krishna-acondy.io/"><img src="https://avatars.githubusercontent.com/u/2980428?v=4?s=100" width="100px;" alt="Krishna Acondy"/><br /><sub><b>Krishna Acondy</b></sub></a><br /><a href="https://github.com/sasjs/adapter/commits?author=krishna-acondy" title="Code">💻</a> <a href="#infra-krishna-acondy" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#blog-krishna-acondy" title="Blogposts">📝</a> <a href="#content-krishna-acondy" title="Content">🖋</a> <a href="#ideas-krishna-acondy" title="Ideas, Planning, & Feedback">🤔</a> <a href="#video-krishna-acondy" title="Videos">📹</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.erudicat.com/"><img src="https://avatars.githubusercontent.com/u/25773492?v=4?s=100" width="100px;" alt="Yury Shkoda"/><br /><sub><b>Yury Shkoda</b></sub></a><br /><a href="https://github.com/sasjs/adapter/commits?author=YuryShkoda" title="Code">💻</a> <a href="#infra-YuryShkoda" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#ideas-YuryShkoda" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/sasjs/adapter/commits?author=YuryShkoda" title="Tests">⚠️</a> <a href="#video-YuryShkoda" title="Videos">📹</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/medjedovicm"><img src="https://avatars.githubusercontent.com/u/18329105?v=4?s=100" width="100px;" alt="Mihajlo Medjedovic"/><br /><sub><b>Mihajlo Medjedovic</b></sub></a><br /><a href="https://github.com/sasjs/adapter/commits?author=medjedovicm" title="Code">💻</a> <a href="#infra-medjedovicm" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="https://github.com/sasjs/adapter/commits?author=medjedovicm" title="Tests">⚠️</a> <a href="https://github.com/sasjs/adapter/pulls?q=is%3Apr+reviewed-by%3Amedjedovicm" title="Reviewed Pull Requests">👀</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/allanbowe"><img src="https://avatars.githubusercontent.com/u/4420615?v=4?s=100" width="100px;" alt="Allan Bowe"/><br /><sub><b>Allan Bowe</b></sub></a><br /><a href="https://github.com/sasjs/adapter/commits?author=allanbowe" title="Code">💻</a> <a href="https://github.com/sasjs/adapter/pulls?q=is%3Apr+reviewed-by%3Aallanbowe" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/sasjs/adapter/commits?author=allanbowe" title="Tests">⚠️</a> <a href="#mentoring-allanbowe" title="Mentoring">🧑‍🏫</a> <a href="#maintenance-allanbowe" title="Maintenance">🚧</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/saadjutt01"><img src="https://avatars.githubusercontent.com/u/8914650?v=4?s=100" width="100px;" alt="Muhammad Saad "/><br /><sub><b>Muhammad Saad </b></sub></a><br /><a href="https://github.com/sasjs/adapter/commits?author=saadjutt01" title="Code">💻</a> <a href="https://github.com/sasjs/adapter/pulls?q=is%3Apr+reviewed-by%3Asaadjutt01" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/sasjs/adapter/commits?author=saadjutt01" title="Tests">⚠️</a> <a href="#mentoring-saadjutt01" title="Mentoring">🧑‍🏫</a> <a href="#infra-saadjutt01" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/sabhas"><img src="https://avatars.githubusercontent.com/u/82647447?v=4?s=100" width="100px;" alt="Sabir Hassan"/><br /><sub><b>Sabir Hassan</b></sub></a><br /><a href="https://github.com/sasjs/adapter/commits?author=sabhas" title="Code">💻</a> <a href="https://github.com/sasjs/adapter/pulls?q=is%3Apr+reviewed-by%3Asabhas" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/sasjs/adapter/commits?author=sabhas" title="Tests">⚠️</a> <a href="#ideas-sabhas" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/VladislavParhomchik"><img src="https://avatars.githubusercontent.com/u/83717836?v=4?s=100" width="100px;" alt="VladislavParhomchik"/><br /><sub><b>VladislavParhomchik</b></sub></a><br /><a href="https://github.com/sasjs/adapter/commits?author=VladislavParhomchik" title="Tests">⚠️</a> <a href="https://github.com/sasjs/adapter/pulls?q=is%3Apr+reviewed-by%3AVladislavParhomchik" title="Reviewed Pull Requests">👀</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="http://rudvfaden.github.io/"><img src="https://avatars.githubusercontent.com/u/2445577?v=4?s=100" width="100px;" alt="Rud Faden"/><br /><sub><b>Rud Faden</b></sub></a><br /><a href="#userTesting-rudvfaden" title="User Testing">📓</a> <a href="https://github.com/sasjs/adapter/commits?author=rudvfaden" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/saramartinelli1992"><img src="https://avatars.githubusercontent.com/u/100193908?v=4?s=100" width="100px;" alt="Sara"/><br /><sub><b>Sara</b></sub></a><br /><a href="#userTesting-saramartinelli1992" title="User Testing">📓</a> <a href="#platform-saramartinelli1992" title="Packaging/porting to new platform">📦</a></td>
</tr>
</tbody>
</table>
<!-- markdownlint-restore -->

7326
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -45,9 +45,7 @@
"license": "ISC",
"devDependencies": {
"@cypress/webpack-preprocessor": "5.9.1",
"@types/axios": "0.14.0",
"@types/express": "4.17.13",
"@types/form-data": "2.5.0",
"@types/jest": "27.4.0",
"@types/mime": "2.0.3",
"@types/pem": "1.9.6",
@@ -61,26 +59,26 @@
"jest-extended": "2.0.0",
"node-polyfill-webpack-plugin": "1.1.4",
"path": "0.12.7",
"pem": "1.14.6",
"pem": "1.14.5",
"prettier": "2.7.1",
"process": "0.11.10",
"rimraf": "3.0.2",
"semantic-release": "19.0.3",
"terser-webpack-plugin": "5.3.1",
"terser-webpack-plugin": "5.3.6",
"ts-jest": "27.1.3",
"ts-loader": "9.4.0",
"tslint": "6.1.3",
"tslint-config-prettier": "1.18.0",
"typedoc": "0.23.15",
"typedoc-plugin-rename-defaults": "0.4.0",
"typedoc": "0.23.24",
"typedoc-plugin-rename-defaults": "0.6.4",
"typescript": "4.8.3",
"webpack": "5.69.0",
"webpack": "5.76.2",
"webpack-cli": "4.9.2"
},
"main": "index.js",
"dependencies": {
"@sasjs/utils": "2.48.2",
"axios": "0.26.0",
"@sasjs/utils": "2.52.0",
"axios": "0.27.2",
"axios-cookiejar-support": "1.0.1",
"form-data": "4.0.0",
"https": "1.0.0",

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@
"private": true,
"dependencies": {
"@sasjs/adapter": "file:../build/sasjs-adapter-5.0.0.tgz",
"@sasjs/test-framework": "^1.5.6",
"@sasjs/test-framework": "1.5.7",
"@types/jest": "^26.0.20",
"@types/node": "^14.14.41",
"@types/react": "^17.0.1",
@@ -43,6 +43,6 @@
]
},
"devDependencies": {
"node-sass": "^7.0.1"
"node-sass": "7.0.3"
}
}

View File

@@ -4,7 +4,7 @@ if npm run cy:run -- --spec "cypress/integration/sasjs.tests.ts" ; then
echo "Cypress sasjs testing passed!"
else
echo '{"msgtype":"m.text", "body":"Automated sasjs-tests failed on the @sasjs/adapter PR: '$2'"}'
curl -XPOST -d '{"msgtype":"m.text", "body":"Automated sasjs-tests failed on the @sasjs/adapter PR: '$2'"}' https://matrix.4gl.io/_matrix/client/r0/rooms/%21BDUPBPEGVvRLKLQUxY:4gl.io/send/m.room.message?access_token=$1
curl -XPOST -d '{"msgtype":"m.text", "body":"Automated sasjs-tests failed on the @sasjs/adapter PR: '$2'"}' https://matrix.4gl.io/_matrix/client/r0/rooms/%21jRebyiGmHZlpfDwYXN:4gl.io:4gl.io/send/m.room.message?access_token=$1
echo "Cypress sasjs testing failed!"
exit 1
fi

View File

@@ -71,13 +71,12 @@ export const computeTests = (adapter: SASjs, appLoc: string): TestSuite => ({
test: () => {
const fileLines = [`data;`, `do x=1 to 100;`, `output;`, `end;`, `run;`]
return adapter.executeScriptSASViya(
'sasCode.sas',
fileLines,
'SAS Studio compute context',
undefined,
true
)
return adapter.executeScript({
fileName: 'sasCode.sas',
linesOfCode: fileLines,
contextName: 'SAS Studio compute context',
debug: true
})
},
assertion: (res: any) => {
const expectedLogContent = `1 data;\\n2 do x=1 to 100;\\n3 output;\\n4 end;\\n5 run;\\n\\n`
@@ -92,13 +91,12 @@ export const computeTests = (adapter: SASjs, appLoc: string): TestSuite => ({
const fileLines = [`%abort;`]
return adapter
.executeScriptSASViya(
'sasCode.sas',
fileLines,
'SAS Studio compute context',
undefined,
true
)
.executeScript({
fileName: 'sasCode.sas',
linesOfCode: fileLines,
contextName: 'SAS Studio compute context',
debug: true
})
.catch((err: any) => err)
},
assertion: (res: any) => {

View File

@@ -4,8 +4,7 @@ import {
UploadFile,
EditContextInput,
PollOptions,
LoginMechanism,
ExecutionQuery
LoginMechanism
} from './types'
import { SASViyaApiClient } from './SASViyaApiClient'
import { SAS9ApiClient } from './SAS9ApiClient'
@@ -17,7 +16,7 @@ import {
AuthConfig,
ExtraResponseAttributes,
SasAuthResponse,
ServicePackSASjs
AuthConfigSas9
} from '@sasjs/utils/types'
import { RequestClient } from './request/RequestClient'
import { SasjsRequestClient } from './request/SasjsRequestClient'
@@ -33,6 +32,16 @@ import {
import { ErrorResponse } from './types/errors'
import { LoginOptions, LoginResult } from './types/Login'
interface ExecuteScriptParams {
linesOfCode: string[]
fileName?: string
contextName?: string
runTime?: string
authConfig?: AuthConfig
authConfigSas9?: AuthConfigSas9
debug?: boolean
}
const defaultConfig: SASjsConfig = {
serverUrl: '',
pathSASJS: '/SASjsApi/stp/execute',
@@ -79,74 +88,73 @@ export default class SASjs {
}
/**
* Executes SAS code on a SAS 9 server. Requires a runner to be present in
* the users home directory in metadata.
* @param linesOfCode - lines of sas code from the file to run.
* @param username - a string representing the username.
* @param password - a string representing the password.
*/
public async executeScriptSAS9(
linesOfCode: string[],
userName: string,
password: string
) {
this.isMethodSupported('executeScriptSAS9', [ServerType.Sas9])
return await this.sas9ApiClient?.executeScript(
linesOfCode,
userName,
password
)
}
/**
* Executes SAS code on a SASJS server
* @param code - a string of code from the file to run.
* Executes code on a SAS server.
* @param linesOfCode - lines of code to run.
* @param fileName - (required for server type sas viya) name of the file to run. It will be converted to path to the file being submitted for execution.
* @param contextName - (required for server type sas viya) context name on which code will be run on the server.
* @param runTime - (required for server type sasjs) a string to represent runTime for code execution.
* @param authConfig - (optional) a valid client, secret, refresh and access tokens that are authorised to execute scripts.
*/
public async executeScriptSASjs(
code: string,
runTime?: string,
authConfig?: AuthConfig
) {
this.isMethodSupported('executeScriptSASJS', [ServerType.Sasjs])
return await this.sasJSApiClient?.executeScript(code, runTime, authConfig)
}
/**
* Executes sas code in a SAS Viya compute session.
* @param fileName - name of the file to run. It will be converted to path to the file being submitted for execution.
* @param linesOfCode - lines of sas code from the file to run.
* @param contextName - context name on which code will be run on the server.
* @param authConfig - (optional) the access token, refresh token, client and secret for authorizing the request.
* @param authConfigSas9 - (required for server type sas9) a valid username and password that are authorised to execute scripts.
* @param debug - (optional) if true, global debug config will be overriden
*/
public async executeScriptSASViya(
fileName: string,
linesOfCode: string[],
contextName: string,
authConfig?: AuthConfig,
debug?: boolean
) {
this.isMethodSupported('executeScriptSASViya', [ServerType.SasViya])
public async executeScript({
linesOfCode,
fileName,
contextName,
runTime,
authConfig,
authConfigSas9,
debug
}: ExecuteScriptParams) {
this.isMethodSupported('executeScript', [
ServerType.Sas9,
ServerType.Sasjs,
ServerType.SasViya
])
contextName = contextName || this.sasjsConfig.contextName
if (this.sasjsConfig.serverType === ServerType.Sas9) {
if (!authConfigSas9)
throw new Error('Auth config for sas9 is not provided')
if (!contextName) {
throw new Error(
'Context name is undefined. Please set a `contextName` in your SASjs or override config.'
return await this.sas9ApiClient?.executeScript(
linesOfCode,
authConfigSas9.userName,
authConfigSas9.password
)
}
return await this.sasViyaApiClient!.executeScript(
fileName,
linesOfCode,
contextName,
authConfig,
null,
debug ? debug : this.sasjsConfig.debug
)
if (this.sasjsConfig.serverType === ServerType.Sasjs) {
return await this.sasJSApiClient?.executeScript(
linesOfCode.join('\n'),
runTime,
authConfig
)
}
if (this.sasjsConfig.serverType === ServerType.SasViya) {
contextName = contextName || this.sasjsConfig.contextName
if (!contextName) {
throw new Error(
'Context name is undefined. Please set a `contextName` in your SASjs or override config.'
)
}
if (!fileName) {
throw new Error(
'File name is required in case of SAS VIYA. Please provide a `fileName`.'
)
}
return await this.sasViyaApiClient!.executeScript(
fileName,
linesOfCode,
contextName,
authConfig,
null,
debug ? debug : this.sasjsConfig.debug
)
}
}
/**
@@ -826,33 +834,6 @@ export default class SASjs {
)
}
/**
* Creates the folders and services at the given location `appLoc` on the given server `serverUrl`.
* @param dataJson - the JSON specifying the folders and files to be created, can also includes
* appLoc, streamServiceName, streamWebFolder, streamLogo
* @param appLoc - (optional) the base folder in which to create the new folders and
* services. If not provided, is taken from SASjsConfig. Precedence will be of appLoc present in dataJson.
* @param authConfig - (optional) a valid client, secret, refresh and access tokens that are authorised to execute compute jobs.
*/
public async deployToSASjs(
dataJson: ServicePackSASjs,
appLoc?: string,
authConfig?: AuthConfig
) {
if (!appLoc) {
appLoc = this.sasjsConfig.appLoc
}
return await this.sasJSApiClient?.deploy(dataJson, appLoc, authConfig)
}
public async executeJobSASjs(query: ExecutionQuery, authConfig?: AuthConfig) {
return await this.sasJSApiClient?.executeJob(
query,
this.sasjsConfig.appLoc,
authConfig
)
}
/**
* Kicks off execution of the given job via the compute API.
* @returns an object representing the compute session created for the given job.
@@ -912,6 +893,7 @@ export default class SASjs {
await this.computeJobExecutor?.resendWaitingRequests()
await this.jesJobExecutor?.resendWaitingRequests()
await this.fileUploader?.resendWaitingRequests()
await this.sasjsJobExecutor?.resendWaitingRequests()
}
/**

View File

@@ -1,3 +1,4 @@
import * as NodeFormData from 'form-data'
import { AuthConfig, ServerType, ServicePackSASjs } from '@sasjs/utils/types'
import { ExecutionQuery } from './types'
import { RequestClient } from './request/RequestClient'
@@ -8,20 +9,31 @@ import { getTokens } from './auth/getTokens'
export class SASjsApiClient {
constructor(private requestClient: RequestClient) {}
private async getAccessTokenForRequest(authConfig?: AuthConfig) {
if (authConfig) {
const { access_token } = await getTokens(
this.requestClient,
authConfig,
ServerType.Sasjs
)
return access_token
}
}
/**
* Creates the folders and services at the given location `appLoc` on the given server `serverUrl`.
* @param dataJson - the JSON specifying the folders and files to be created, can also includes
* appLoc, streamServiceName, streamWebFolder, streamLogo
* @param appLoc - the base folder in which to create the new folders and services.
* @param authConfig - (optional) a valid client, secret, refresh and access tokens that are authorised to execute compute jobs.
*/
public async deploy(
dataJson: ServicePackSASjs,
appLoc: string,
authConfig?: AuthConfig
) {
let access_token = (authConfig || {}).access_token
if (authConfig) {
;({ access_token } = await getTokens(
this.requestClient,
authConfig,
ServerType.Sasjs
))
}
const access_token = await this.getAccessTokenForRequest(authConfig)
dataJson.appLoc = dataJson.appLoc || appLoc
const { result } = await this.requestClient.post<{
@@ -41,6 +53,40 @@ export class SASjsApiClient {
return Promise.resolve(result)
}
/**
* Creates/updates files within SASjs drive using uploaded json compressed file.
* @param zipFilePath - Compressed file path; file should only contain one JSON file and
* should have same name as of compressed file e.g. deploy.JSON should be compressed to deploy.JSON.zip
* Any other file or JSON file in zipped will be ignored!
* @param authConfig - (optional) a valid client, secret, refresh and access tokens that are authorised to execute compute jobs.
*/
public async deployZipFile(zipFilePath: string, authConfig?: AuthConfig) {
const { createReadStream } = require('@sasjs/utils/file')
const access_token = await this.getAccessTokenForRequest(authConfig)
const file = await createReadStream(zipFilePath)
const formData = new NodeFormData()
formData.append('file', file)
const contentType = `multipart/form-data; boundary=${formData.getBoundary()}`
const { result } = await this.requestClient.post<{
status: string
message: string
streamServiceName?: string
example?: {}
}>(
'SASjsApi/drive/deploy/upload',
formData,
access_token,
contentType,
{},
{ maxContentLength: Infinity, maxBodyLength: Infinity }
)
return Promise.resolve(result)
}
public async executeJob(
query: ExecutionQuery,
appLoc: string,
@@ -73,14 +119,7 @@ export class SASjsApiClient {
runTime: string = 'sas',
authConfig?: AuthConfig
) {
let access_token = (authConfig || {}).access_token
if (authConfig) {
;({ access_token } = await getTokens(
this.requestClient,
authConfig,
ServerType.Sasjs
))
}
const access_token = await this.getAccessTokenForRequest(authConfig)
let parsedSasjsServerLog = ''

View File

@@ -1,5 +1,6 @@
import { ServerType } from '@sasjs/utils/types'
import { RequestClient } from '../request/RequestClient'
import { NotFoundError } from '../types/errors'
import { LoginOptions, LoginResult, LoginResultInternal } from '../types/Login'
import { serialize } from '../utils'
import { extractUserLongNameSas9 } from '../utils/sas9/extractUserLongNameSas9'
@@ -12,7 +13,7 @@ export class AuthManager {
public userLongName = ''
private loginUrl: string
private logoutUrl: string
private redirectedLoginUrl = `/SASLogon/home`
private redirectedLoginUrl = `/SASLogon` //SAS 9 M8 no longer redirects from `/SASLogon/home` to the login page. `/SASLogon` seems to be stable enough across SAS versions
constructor(
private serverUrl: string,
private serverType: ServerType,
@@ -42,6 +43,9 @@ export class AuthManager {
} = await this.fetchUserName()
if (isLoggedInAlready) {
const logger = process.logger || console
logger.log('login was not attempted as a valid session already exists')
await this.loginCallback()
return {
@@ -109,6 +113,9 @@ export class AuthManager {
} = await this.checkSession()
if (isLoggedInAlready) {
const logger = process.logger || console
logger.log('login was not attempted as a valid session already exists')
await this.loginCallback()
this.userName = loginParams.username
@@ -131,6 +138,10 @@ export class AuthManager {
loginResponse = await this.sendLoginRequest(newLoginForm, loginParams)
}
// Sometimes due to redirection on SAS9 and SASViya we don't get the login response that says
// You have signed in. Therefore, we have to make an extra request for checking session to
// ensure either user is logged in or not.
const res = await this.checkSession()
isLoggedIn = res.isLoggedIn
this.userLongName = res.userLongName
@@ -155,10 +166,12 @@ export class AuthManager {
private async performCASSecurityCheck() {
const casAuthenticationUrl = `${this.serverUrl}/SASStoredProcess/j_spring_cas_security_check`
await this.requestClient.get<string>(
`/SASLogon/login?service=${casAuthenticationUrl}`,
undefined
)
await this.requestClient
.get<string>(`/SASLogon/login?service=${casAuthenticationUrl}`, undefined)
.catch((err) => {
// ignore if resource not found error
if (!(err instanceof NotFoundError)) throw err
})
}
private async sendLoginRequest(
@@ -239,7 +252,7 @@ export class AuthManager {
}
const { result: formResponse } = await this.requestClient.get<string>(
this.loginUrl.replace('.do', ''),
this.loginUrl.replace('/SASLogon/login.do', '/SASLogon/login'),
undefined,
'text/plain'
)
@@ -312,12 +325,13 @@ export class AuthManager {
}
private getLoginForm(response: any) {
const pattern: RegExp = /<form.+action="(.*Logon[^"]*).*>/
const pattern: RegExp = /<form.+action="(.*(Logon|login)[^"]*).*>/
const matches = pattern.exec(response)
const formInputs: any = {}
if (matches && matches.length) {
this.setLoginUrl(matches)
response = response.replace(/<input/g, '\n<input')
const inputs = response.match(/<input.*"hidden"[^>]*>/g)
if (inputs) {
@@ -348,7 +362,7 @@ export class AuthManager {
this.loginUrl =
this.serverType === ServerType.SasViya
? tempLoginLink
: loginUrl.replace('.do', '')
: loginUrl.replace('/SASLogon/login.do', '/SASLogon/login')
}
}

View File

@@ -1,5 +1,5 @@
export const isLogInRequired = (response: string): boolean => {
const pattern: RegExp = /<form.+action="(.*Logon[^"]*).*>/gm
const pattern: RegExp = /<form.+action="(.*(Logon)|(login)[^"]*).*>/gm
const matches = pattern.test(response)
return matches
}

View File

@@ -365,7 +365,7 @@ describe('AuthManager', () => {
expect(loginResponse.userName).toEqual(userName)
expect(openWebPageModule.openWebPage).toHaveBeenCalledWith(
`/SASLogon/home`,
`/SASLogon`,
'SASLogon',
{
width: 500,
@@ -409,7 +409,7 @@ describe('AuthManager', () => {
expect(loginResponse.userName).toEqual(userName)
expect(openWebPageModule.openWebPage).toHaveBeenCalledWith(
`/SASLogon/home`,
`/SASLogon`,
'SASLogon',
{
width: 500,
@@ -453,7 +453,7 @@ describe('AuthManager', () => {
expect(loginResponse.userName).toEqual('')
expect(openWebPageModule.openWebPage).toHaveBeenCalledWith(
`/SASLogon/home`,
`/SASLogon`,
'SASLogon',
{
width: 500,
@@ -497,7 +497,7 @@ describe('AuthManager', () => {
expect(loginResponse.userName).toEqual('')
expect(openWebPageModule.openWebPage).toHaveBeenCalledWith(
`/SASLogon/home`,
`/SASLogon`,
'SASLogon',
{
width: 500,

View File

@@ -17,7 +17,7 @@ describe('verifySas9Login', () => {
it('should return isLoggedIn true by checking state of popup', async () => {
const popup = {
window: {
location: { href: serverUrl + `/SASLogon/home` },
location: { href: serverUrl + `/SASLogon` },
document: { body: { innerText: '<h3>You have signed in.</h3>' } }
}
} as unknown as Window

View File

@@ -18,7 +18,7 @@ describe('verifySasViyaLogin', () => {
it('should return isLoggedIn true by checking state of popup', async () => {
const popup = {
window: {
location: { href: serverUrl + `/SASLogon/home` },
location: { href: serverUrl + `/SASLogon` },
document: { body: { innerText: '<h3>You have signed in.</h3>' } }
}
} as unknown as Window

View File

@@ -3,4 +3,6 @@ export * from './types'
export * from './types/errors'
export * from './SASViyaApiClient'
export * from './SAS9ApiClient'
export * from './SASjsApiClient'
export * from './request/SasjsRequestClient'
export default SASjs

273
src/minified/sas9/SASjs.ts Normal file
View File

@@ -0,0 +1,273 @@
import { validateInput, compareTimestamps } from '../../utils'
import { SASjsConfig, UploadFile, LoginMechanism } from '../../types'
import { AuthManager } from '../../auth'
import {
ServerType,
AuthConfig,
ExtraResponseAttributes
} from '@sasjs/utils/types'
import { RequestClient } from '../../request/RequestClient'
import { FileUploader } from '../../job-execution/FileUploader'
import { WebJobExecutor } from './WebJobExecutor'
import { ErrorResponse } from '../../types/errors/ErrorResponse'
import { LoginOptions, LoginResult } from '../../types/Login'
const defaultConfig: SASjsConfig = {
serverUrl: '',
pathSASJS: '/SASjsApi/stp/execute',
pathSAS9: '/SASStoredProcess/do',
pathSASViya: '/SASJobExecution',
appLoc: '/Public/seedapp',
serverType: ServerType.Sas9,
debug: false,
contextName: 'SAS Job Execution compute context',
useComputeApi: null,
loginMechanism: LoginMechanism.Default
}
/**
* SASjs is a JavaScript adapter for SAS.
*
*/
export default class SASjs {
private sasjsConfig: SASjsConfig = new SASjsConfig()
private jobsPath: string = ''
private fileUploader: FileUploader | null = null
private authManager: AuthManager | null = null
private requestClient: RequestClient | null = null
private webJobExecutor: WebJobExecutor | null = null
constructor(config?: Partial<SASjsConfig>) {
this.sasjsConfig = {
...defaultConfig,
...config
}
this.setupConfiguration()
}
/**
* Logs into the SAS server with the supplied credentials.
* @param username - a string representing the username.
* @param password - a string representing the password.
* @param clientId - a string representing the client ID.
*/
public async logIn(
username?: string,
password?: string,
clientId?: string,
options: LoginOptions = {}
): Promise<LoginResult> {
if (this.sasjsConfig.loginMechanism === LoginMechanism.Default) {
if (!username || !password)
throw new Error(
'A username and password are required when using the default login mechanism.'
)
return this.authManager!.logIn(username, password)
}
if (typeof window === typeof undefined) {
throw new Error(
'The redirected login mechanism is only available for use in the browser.'
)
}
return this.authManager!.redirectedLogIn(options)
}
/**
* Logs out of the configured SAS server.
*/
public logOut() {
return this.authManager!.logOut()
}
/**
* Returns the current SASjs configuration.
*
*/
public getSasjsConfig() {
return this.sasjsConfig
}
/**
* this method returns an array of SASjsRequest
* @returns SASjsRequest[]
*/
public getSasRequests() {
const requests = [...this.requestClient!.getRequests()]
const sortedRequests = requests.sort(compareTimestamps)
return sortedRequests
}
/**
* Sets the debug state. Turning this on will enable additional logging in the adapter.
* @param value - boolean indicating debug state (on/off).
*/
public setDebugState(value: boolean) {
this.sasjsConfig.debug = value
}
/**
* Uploads a file to the given service.
* @param sasJob - the path to the SAS program (ultimately resolves to
* the SAS `_program` parameter to run a Job Definition or SAS 9 Stored
* Process). Is prepended at runtime with the value of `appLoc`.
* @param files - array of files to be uploaded, including File object and file name.
* @param params - request URL parameters.
* @param config - provide any changes to the config here, for instance to
* enable/disable `debug`. Any change provided will override the global config,
* for that particular function call.
* @param loginRequiredCallback - a function that is called if the
* user is not logged in (eg to display a login form). The request will be
* resubmitted after successful login.
*/
public async uploadFile(
sasJob: string,
files: UploadFile[],
params: { [key: string]: any } | null,
config: { [key: string]: any } = {},
loginRequiredCallback?: () => any
) {
config = {
...this.sasjsConfig,
...config
}
const data = { files, params }
return await this.fileUploader!.execute(
sasJob,
data,
config,
loginRequiredCallback
)
}
/**
* Makes a request to program specified in `SASjob` (could be a Viya Job, a
* SAS 9 Stored Process, or a SASjs Server Stored Program). The response
* object will always contain table names in lowercase, and column names in
* uppercase. Values are returned formatted by default, unformatted
* values can be configured as an option in the `%webout` macro.
*
* @param sasJob - the path to the SAS program (ultimately resolves to
* the SAS `_program` parameter to run a Job Definition or SAS 9 Stored
* Process). Is prepended at runtime with the value of `appLoc`.
* @param data - a JSON object containing one or more tables to be sent to
* SAS. For an example of the table structure, see the project README. This
* value can be `null` if no inputs are required.
* @param config - provide any changes to the config here, for instance to
* enable/disable `debug`. Any change provided will override the global config,
* for that particular function call.
* @param loginRequiredCallback - a function that is called if the
* user is not logged in (eg to display a login form). The request will be
* resubmitted after successful login.
* When using a `loginRequiredCallback`, the call to the request will look, for example, like so:
* `await request(sasJobPath, data, config, () => setIsLoggedIn(false))`
* If you are not passing in any data and configuration, it will look like so:
* `await request(sasJobPath, {}, {}, () => setIsLoggedIn(false))`
* @param extraResponseAttributes - a array of predefined values that are used
* to provide extra attributes (same names as those values) to be added in response
* Supported values are declared in ExtraResponseAttributes type.
*/
public async request(
sasJob: string,
data: { [key: string]: any } | null,
config: { [key: string]: any } = {},
loginRequiredCallback?: () => any,
authConfig?: AuthConfig,
extraResponseAttributes: ExtraResponseAttributes[] = []
) {
config = {
...this.sasjsConfig,
...config
}
const validationResult = validateInput(data)
// status is true if the data passes validation checks above
if (validationResult.status) {
return await this.webJobExecutor!.execute(
sasJob,
data,
config,
loginRequiredCallback,
authConfig,
extraResponseAttributes
)
} else {
return Promise.reject(new ErrorResponse(validationResult.msg))
}
}
/**
* Checks whether a session is active, or login is required.
* @returns - a promise which resolves with an object containing two values - a boolean `isLoggedIn`, and a string `userName`.
*/
public async checkSession() {
return this.authManager!.checkSession()
}
private setupConfiguration() {
if (
this.sasjsConfig.serverUrl === undefined ||
this.sasjsConfig.serverUrl === ''
) {
if (typeof location !== 'undefined') {
let url = `${location.protocol}//${location.hostname}`
if (location.port) url = `${url}:${location.port}`
this.sasjsConfig.serverUrl = url
} else {
this.sasjsConfig.serverUrl = ''
}
}
if (this.sasjsConfig.serverUrl.slice(-1) === '/') {
this.sasjsConfig.serverUrl = this.sasjsConfig.serverUrl.slice(0, -1)
}
if (!this.requestClient) {
this.requestClient = new RequestClient(
this.sasjsConfig.serverUrl,
this.sasjsConfig.httpsAgentOptions,
this.sasjsConfig.requestHistoryLimit
)
} else {
this.requestClient.setConfig(
this.sasjsConfig.serverUrl,
this.sasjsConfig.httpsAgentOptions
)
}
this.jobsPath = this.sasjsConfig.pathSAS9
this.authManager = new AuthManager(
this.sasjsConfig.serverUrl,
this.sasjsConfig.serverType!,
this.requestClient,
this.resendWaitingRequests
)
this.fileUploader = new FileUploader(
this.sasjsConfig.serverUrl,
this.sasjsConfig.serverType!,
this.jobsPath,
this.requestClient
)
this.webJobExecutor = new WebJobExecutor(
this.sasjsConfig.serverUrl,
this.sasjsConfig.serverType!,
this.jobsPath,
this.requestClient
)
}
private resendWaitingRequests = async () => {
await this.webJobExecutor?.resendWaitingRequests()
await this.fileUploader?.resendWaitingRequests()
}
}

View File

@@ -0,0 +1,157 @@
import {
AuthConfig,
ExtraResponseAttributes,
ServerType
} from '@sasjs/utils/types'
import {
ErrorResponse,
JobExecutionError,
LoginRequiredError
} from '../../types/errors'
import { RequestClient } from '../../request/RequestClient'
import {
isRelativePath,
parseSasViyaDebugResponse,
appendExtraResponseAttributes,
convertToCSV
} from '../../utils'
import { BaseJobExecutor } from '../../job-execution/JobExecutor'
import { parseWeboutResponse } from '../../utils/parseWeboutResponse'
export interface WaitingRequstPromise {
promise: Promise<any> | null
resolve: any
reject: any
}
export class WebJobExecutor extends BaseJobExecutor {
constructor(
serverUrl: string,
serverType: ServerType,
private jobsPath: string,
private requestClient: RequestClient
) {
super(serverUrl, serverType)
}
async execute(
sasJob: string,
data: any,
config: any,
loginRequiredCallback?: any,
authConfig?: AuthConfig,
extraResponseAttributes: ExtraResponseAttributes[] = []
) {
const loginCallback = loginRequiredCallback
const program = isRelativePath(sasJob)
? config.appLoc
? config.appLoc.replace(/\/?$/, '/') + sasJob.replace(/^\//, '')
: sasJob
: sasJob
let apiUrl = `${config.serverUrl}${this.jobsPath}/?${'_program=' + program}`
let requestParams = {
...this.getRequestParams(config)
}
let formData = new FormData()
if (data) {
try {
formData = generateFileUploadForm(formData, data)
} catch (e: any) {
return Promise.reject(new ErrorResponse(e?.message, e))
}
}
for (const key in requestParams) {
if (requestParams.hasOwnProperty(key)) {
formData.append(key, requestParams[key])
}
}
const requestPromise = new Promise((resolve, reject) => {
this.requestClient!.post(apiUrl, formData, authConfig?.access_token)
.then(async (res: any) => {
this.requestClient!.appendRequest(res, sasJob, config.debug)
const jsonResponse =
config.debug && typeof res.result === 'string'
? parseWeboutResponse(res.result, apiUrl)
: res.result
const responseObject = appendExtraResponseAttributes(
{ result: jsonResponse, log: res.log },
extraResponseAttributes
)
resolve(responseObject)
})
.catch(async (e: Error) => {
if (e instanceof JobExecutionError) {
this.requestClient!.appendRequest(e, sasJob, config.debug)
reject(new ErrorResponse(e?.message, e))
}
if (e instanceof LoginRequiredError) {
if (!loginRequiredCallback) {
reject(
new ErrorResponse(
'Request is not authenticated. Make sure .env file exists with valid credentials.',
e
)
)
}
this.appendWaitingRequest(() => {
return this.execute(
sasJob,
data,
config,
loginRequiredCallback,
authConfig,
extraResponseAttributes
).then(
(res: any) => {
resolve(res)
},
(err: any) => {
reject(err)
}
)
})
if (loginCallback) await loginCallback()
} else reject(new ErrorResponse(e?.message, e))
})
})
return requestPromise
}
}
/**
* One of the approaches SASjs takes to send tables-formatted JSON (see README)
* to SAS is as multipart form data, where each table is provided as a specially
* formatted CSV file.
*/
const generateFileUploadForm = (formData: FormData, data: any): FormData => {
for (const tableName in data) {
if (!Array.isArray(data[tableName])) continue
const name = tableName
const csv = convertToCSV(data, tableName)
if (csv === 'ERROR: LARGE STRING LENGTH') {
throw new Error(
'The max length of a string value in SASjs is 32765 characters.'
)
}
const file = new Blob([csv], {
type: 'application/csv'
})
formData.append(name, file, `${name}.csv`)
}
return formData
}

View File

@@ -0,0 +1,3 @@
import SASjs from './SASjs'
export * from '../../types'
export default SASjs

View File

@@ -691,7 +691,9 @@ const parseError = (data: string) => {
const parts = data.split(/stored process not found: /i)
if (parts.length > 1) {
const storedProcessPath = parts[1].split('<i>')[1].split('</i>')[0]
const message = `Stored process not found: ${storedProcessPath}`
const message = storedProcessPath.endsWith('runner')
? `SASJS runner not found. Here's the link (https://cli.sasjs.io/auth/#sasjs-runner) to the SAS code for registering the SASjs runner`
: `Stored process not found: ${storedProcessPath}`
return new JobExecutionError(500, message, '')
}
}

View File

@@ -4,6 +4,7 @@ import axiosCookieJarSupport from 'axios-cookiejar-support'
import * as tough from 'tough-cookie'
import { prefixMessage } from '@sasjs/utils/error'
import { RequestClient, throwIfError } from './RequestClient'
import { JobExecutionError } from '../types/errors'
/**
* Specific request client for SAS9 in Node.js environments.
@@ -69,6 +70,8 @@ export class Sas9RequestClient extends RequestClient {
return this.parseResponse<T>(response)
})
.catch(async (e: any) => {
if (e instanceof JobExecutionError) throw e
return await this.handleError(
e,
() =>

View File

@@ -23,8 +23,16 @@ const optimization = {
}
const browserConfig = {
entry: './src/index.ts',
devtool: 'inline-source-map',
entry: {
index: './src/index.ts',
minified_sas9: './src/minified/sas9/index.ts'
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'build'),
libraryTarget: 'umd',
library: 'SASjs'
},
mode: 'production',
optimization: optimization,
module: {
@@ -40,12 +48,6 @@ const browserConfig = {
extensions: ['.ts', '.js'],
fallback: { https: false, fs: false, readline: false }
},
output: {
filename: 'index.js',
path: path.resolve(__dirname, 'build'),
libraryTarget: 'umd',
library: 'SASjs'
},
plugins: [
...defaultPlugins,
new webpack.ProvidePlugin({
@@ -55,6 +57,18 @@ const browserConfig = {
]
}
const browserConfigWithDevTool = {
...browserConfig,
entry: './src/index.ts',
output: {
filename: 'index-dev.js',
path: path.resolve(__dirname, 'build'),
libraryTarget: 'umd',
library: 'SASjs'
},
devtool: 'inline-source-map'
}
const browserConfigWithoutProcessPlugin = {
entry: browserConfig.entry,
devtool: browserConfig.devtool,
@@ -76,4 +90,4 @@ const nodeConfig = {
}
}
module.exports = [browserConfig, nodeConfig]
module.exports = [browserConfig, browserConfigWithDevTool, nodeConfig]