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

Compare commits

...

37 Commits

Author SHA1 Message Date
Allan Bowe
7cbc2bda08 Merge pull request #801 from sasjs/issue-680
ErrorBody interface made public
2023-04-06 12:02:15 +01:00
Allan Bowe
5bc2afba8c Merge pull request #800 from sasjs/sasjstestservices
chore: adding services in seperate (deployable) files as alternative …
2023-04-06 11:57:52 +01:00
dd3a7fe393 fix: ErrorBody interface made public 2023-04-06 12:45:22 +02:00
allan
ba1b1e6e80 Merge branch 'sasjstestservices' of github.com:sasjs/adapter into sasjstestservices 2023-04-06 11:42:04 +01:00
allan
3daa85a74a chore: updating appLoc to /Public/app/adapter-tests in sasjs-tests 2023-04-06 11:41:54 +01:00
Allan Bowe
d1139857a4 Update invalidJSON.sas 2023-04-06 11:33:31 +01:00
allan
cb106c76cb chore: adding services in seperate (deployable) files as alternative to manually running the code from the README in sasjs-test 2023-04-06 11:31:37 +01:00
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
37 changed files with 3172 additions and 2303 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

@@ -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 -->

4486
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -59,12 +59,12 @@
"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",
@@ -72,7 +72,7 @@
"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",

View File

@@ -21,3 +21,6 @@
npm-debug.log*
yarn-debug.log*
yarn-error.log*
sasjsbuild
sasjsresults

19
sasjs-tests/.sasjslint Normal file
View File

@@ -0,0 +1,19 @@
{
"lineEndings": "off",
"noTrailingSpaces": true,
"noEncodedPasswords": true,
"hasDoxygenHeader": true,
"noSpacesInFileNames": true,
"lowerCaseFileNames": true,
"maxLineLength": 80,
"maxHeaderLineLength": 80,
"maxDataLineLength": 80,
"noTabIndentation": true,
"indentationMultiple": 2,
"hasMacroNameInMend": true,
"noNestedMacros": true,
"hasMacroParentheses": true,
"strictMacroDefinition": true,
"noGremlins": true,
"defaultHeader": "/**{lineEnding} @file{lineEnding} @brief <Your brief here>{lineEnding} <h4> SAS Macros </h4>{lineEnding}**/"
}

View File

@@ -65,6 +65,7 @@ The code below will work on ALL SAS platforms (Viya, SAS 9 EBI, SASjs Server).
```sas
filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
%inc mc;
%let apploc=/Public/app/adapter-tests;
filename ft15f001 temp lrecl=1000;
parmcards4;
%webout(FETCH)
@@ -80,7 +81,7 @@ parmcards4;
%mend; %x()
%webout(CLOSE)
;;;;
%mx_createwebservice(path=/Public/app/common,name=sendObj)
%mx_createwebservice(path=&apploc/services/common,name=sendObj)
parmcards4;
%webout(FETCH)
%webout(OPEN)
@@ -95,7 +96,7 @@ parmcards4;
%mend; %x()
%webout(CLOSE)
;;;;
%mx_createwebservice(path=/Public/app/common,name=sendArr)
%mx_createwebservice(path=&apploc/services/common,name=sendArr)
parmcards4;
data work.macvars;
set sashelp.vmacro;
@@ -104,14 +105,14 @@ parmcards4;
%webout(OBJ,macvars)
%webout(CLOSE)
;;;;
%mx_createwebservice(path=/Public/app/common,name=sendMacVars)
%mx_createwebservice(path=&apploc/services/common,name=sendMacVars)
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;
;;;;
%mx_createwebservice(path=/Public/app/common,name=makeErr)
%mx_createwebservice(path=&apploc/services/common,name=makeErr)
parmcards4;
%webout(OPEN)
data _null_;
@@ -120,7 +121,7 @@ data _null_;
run;
%webout(CLOSE)
;;;;
%mx_createwebservice(path=/Public/app/common,name=invalidJSON)
%mx_createwebservice(path=&apploc/services/common,name=invalidJSON)
```
You should now be able to access the tests in your browser at the deployed path on your server.

View File

@@ -45,4 +45,4 @@
"devDependencies": {
"node-sass": "7.0.3"
}
}
}

View File

@@ -3,10 +3,10 @@
"password": "",
"sasJsConfig": {
"serverUrl": "",
"appLoc": "/Public/app",
"serverType": "SASVIYA",
"appLoc": "/Public/app/adapter-tests",
"serverType": "SASJS",
"debug": false,
"contextName": "sasjs adapter compute context",
"useComputeApi": true
}
}
}

View File

@@ -0,0 +1,13 @@
/**
@file
@brief Makes an invalid JSON file
<h4> SAS Macros </h4>
**/
%webout(OPEN)
data _null_;
file _webout;
put ' the discovery channel ';
run;
%webout(CLOSE)

View File

@@ -0,0 +1,11 @@
/**
@file
@brief Makes an error
<h4> SAS Macros </h4>
**/
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;

View File

@@ -0,0 +1,21 @@
/**
@file
@brief Returns JSON in Array format
<h4> SAS Macros </h4>
**/
%webout(FETCH)
%webout(OPEN)
%macro x();
%if %symexist(sasjs_tables) %then
%do i=1 %to %sysfunc(countw(&sasjs_tables));
%let table=%scan(&sasjs_tables,&i);
%webout(ARR,&table,missing=STRING,showmeta=YES)
%end;
%else %do i=1 %to &_webin_file_count;
%webout(ARR,&&_webin_name&i,missing=STRING,showmeta=YES)
%end;
%mend x;
%x()
%webout(CLOSE)

View File

@@ -0,0 +1,13 @@
/**
@file
@brief Returns Macro Variables
<h4> SAS Macros </h4>
**/
data work.macvars;
set sashelp.vmacro;
run;
%webout(OPEN)
%webout(OBJ,macvars)
%webout(CLOSE)

View File

@@ -0,0 +1,21 @@
/**
@file
@brief Returns JSON in Object format
<h4> SAS Macros </h4>
**/
%webout(FETCH)
%webout(OPEN)
%macro x();
%if %symexist(sasjs_tables) %then
%do i=1 %to %sysfunc(countw(&sasjs_tables));
%let table=%scan(&sasjs_tables,&i);
%webout(OBJ,&table,missing=STRING,showmeta=YES)
%end;
%else %do i=1 %to &_webin_file_count;
%webout(OBJ,&&_webin_name&i,missing=STRING,showmeta=YES)
%end;
%mend x;
%x()
%webout(CLOSE)

View File

@@ -0,0 +1,40 @@
ALPHABETICAL_INDEX = NO
ENABLE_PREPROCESSING = NO
EXTENSION_MAPPING = sas=Java ddl=Java
EXTRACT_LOCAL_CLASSES = NO
FILE_PATTERNS = *.sas \
*.ddl \
*.dox
GENERATE_LATEX = NO
GENERATE_TREEVIEW = YES
HIDE_FRIEND_COMPOUNDS = YES
HIDE_IN_BODY_DOCS = YES
HIDE_SCOPE_NAMES = YES
HIDE_UNDOC_CLASSES = YES
HIDE_UNDOC_MEMBERS = YES
HTML_OUTPUT = $(DOXY_HTML_OUTPUT)
HTML_HEADER = $(HTML_HEADER)
HTML_EXTRA_FILES = $(HTML_EXTRA_FILES)
HTML_FOOTER = $(HTML_FOOTER)
HTML_EXTRA_STYLESHEET = $(HTML_EXTRA_STYLESHEET)
INHERIT_DOCS = NO
INLINE_INFO = NO
INPUT = $(DOXY_INPUT)
LAYOUT_FILE = $(LAYOUT_FILE)
USE_MDFILE_AS_MAINPAGE = README.md
MAX_INITIALIZER_LINES = 0
PROJECT_NAME = $(PROJECT_NAME)
PROJECT_LOGO = $(PROJECT_LOGO)
PROJECT_BRIEF = $(PROJECT_BRIEF)
RECURSIVE = YES
REPEAT_BRIEF = NO
SHOW_NAMESPACES = NO
SHOW_USED_FILES = NO
SOURCE_BROWSER = YES
SOURCE_TOOLTIPS = NO
STRICT_PROTO_MATCHING = YES
STRIP_CODE_COMMENTS = NO
SUBGROUPING = NO
TAB_SIZE = 2
VERBATIM_HEADERS = NO

View File

@@ -0,0 +1,112 @@
<doxygenlayout version="1.0">
<!-- Generated by doxygen 1.8.14 -->
<!-- Navigation index tabs for HTML output -->
<navindex>
<tab type="mainpage" visible="yes" title="Home"/>
<tab type="pages" visible="no" title="" intro=""/>
<tab type="modules" visible="no" title="" intro=""/>
<tab type="namespaces" visible="no" title="">
<tab type="namespacelist" visible="no" title="" intro=""/>
<tab type="namespacemembers" visible="no" title="" intro=""/>
</tab>
<tab type="classes" visible="no" title="">
<tab type="classlist" visible="no" title="" intro=""/>
<tab type="classindex" visible="no" title=""/>
<tab type="hierarchy" visible="no" title="" intro=""/>
<tab type="classmembers" visible="no" title="" intro=""/>
</tab>
<tab type="filelist" visible="yes" title="" intro="List of Files"/>
<tab type="examples" visible="no" title="" intro=""/>
<tab type="user" url="data_lineage.svg" title="Lineage"/>
</navindex>
<!-- Layout definition for a file page -->
<file>
<briefdescription visible="yes"/>
<includes visible="$SHOW_INCLUDE_FILES"/>
<includegraph visible="$INCLUDE_GRAPH"/>
<includedbygraph visible="$INCLUDED_BY_GRAPH"/>
<sourcelink visible="yes"/>
<memberdecl>
<classes visible="no" title=""/>
<namespaces visible="no" title=""/>
<constantgroups visible="no" title=""/>
<defines title=""/>
<typedefs title=""/>
<enums title=""/>
<functions title=""/>
<variables title=""/>
<membergroups visible="no"/>
</memberdecl>
<detaileddescription title=""/>
<memberdef>
<inlineclasses title=""/>
<defines title=""/>
<typedefs title=""/>
<enums title=""/>
<functions title=""/>
<variables title=""/>
</memberdef>
<authorsection/>
</file>
<!-- Layout definition for a group page -->
<group>
<briefdescription visible="no"/>
<groupgraph visible="$GROUP_GRAPHS"/>
<memberdecl>
<nestedgroups visible="no" title=""/>
<dirs visible="yes" title=""/>
<files visible="yes" title=""/>
<namespaces visible="no" title=""/>
<classes visible="no" title=""/>
<defines title=""/>
<typedefs title=""/>
<enums title=""/>
<enumvalues title=""/>
<functions title=""/>
<variables title=""/>
<signals title=""/>
<publicslots title=""/>
<protectedslots title=""/>
<privateslots title=""/>
<events title=""/>
<properties title=""/>
<friends title=""/>
<membergroups visible="yes"/>
</memberdecl>
<detaileddescription title=""/>
<memberdef>
<pagedocs/>
<inlineclasses title=""/>
<defines title=""/>
<typedefs title=""/>
<enums title=""/>
<enumvalues title=""/>
<functions title=""/>
<variables title=""/>
<signals title=""/>
<publicslots title=""/>
<protectedslots title=""/>
<privateslots title=""/>
<events title=""/>
<properties title=""/>
<friends title=""/>
</memberdef>
<authorsection visible="yes"/>
</group>
<!-- Layout definition for a directory page -->
<directory>
<briefdescription visible="yes"/>
<detaileddescription visible="yes" title=""/>
<directorygraph visible="yes"/>
<memberdecl>
<dirs visible="yes"/>
<files visible="yes"/>
</memberdecl>
</directory>
</doxygenlayout>

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

@@ -0,0 +1,33 @@
<!-- HTML footer for doxygen 1.8.17-->
<!-- start footer part -->
<!--BEGIN GENERATE_TREEVIEW-->
<div id="nav-path" class="navpath">
<!-- id is needed for treeview function! -->
<ul>
$navpath
<li class="footer">
$generatedby
<a href="https://www.doxygen.org/index.html">
<img class="footer" src="$relpath^doxygen.svg" alt="doxygen"
/></a>
$doxygenversion
</li>
<li>
<i> For more information visit the </i>
<a href="https://cli.sasjs.io">SASjs cli</a> documentation.
</li>
</ul>
</div>
<!--END GENERATE_TREEVIEW-->
<!--BEGIN !GENERATE_TREEVIEW-->
<hr class="footer" />
<address class="footer">
<small>
$generatedby &#160;<a href="http://www.doxygen.org/index.html">
<img class="footer" src="$relpath^doxygen.svg" alt="doxygen" />
</a>
$doxygenversion
</small>
</address>
<!--END !GENERATE_TREEVIEW-->

View File

@@ -0,0 +1,57 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<!-- HTML header for doxygen 1.8.17-->
<html xmlns="https://www.w3.org/1999/xhtml" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=9" />
<meta name="generator" content="Doxygen $doxygenversion" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<!--BEGIN PROJECT_NAME-->
<title>$projectname: $title</title>
<meta name="description" content="$projectbrief" />
<!--END PROJECT_NAME-->
<!--BEGIN !PROJECT_NAME-->
<title>$title</title>
<!--END !PROJECT_NAME-->
<link href="$relpath^tabs.css" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="$relpath^jquery.js"></script>
<script type="text/javascript" src="$relpath^dynsections.js"></script>
$treeview $search $mathjax
<link href="$relpath^$stylesheet" rel="stylesheet" type="text/css" />
<link rel="shortcut icon" href="$relpath^favicon.ico" type="image/x-icon" />
$extrastylesheet
</head>
<body>
<div id="top">
<!-- do not remove this div, it is closed by doxygen! -->
<!--BEGIN TITLEAREA-->
<div id="titlearea">
<table cellspacing="0" cellpadding="0">
<tbody>
<tr style="height: 56px">
<!--BEGIN PROJECT_LOGO-->
<td id="projectlogo">
<a href="$relpath^"
><img alt="Logo" src="$relpath^$projectlogo"
/></a>
</td>
<!--END PROJECT_LOGO-->
<td id="projectalign" style="padding-left: 0.5em">
<div id="projectname">$projectname</div>
<div id="projectbrief">$projectbrief</div>
</td>
<!--BEGIN DISABLE_INDEX-->
<!--BEGIN SEARCHENGINE-->
<td>$searchbox</td>
<!--END SEARCHENGINE-->
<!--END DISABLE_INDEX-->
</tr>
</tbody>
</table>
</div>
<!--END TITLEAREA-->
<!-- end header part -->
</div>
</body>
</html>

View File

@@ -0,0 +1,4 @@
#projectlogo img {
border: 0px none;
max-height: 70px;
}

View File

@@ -0,0 +1,17 @@
{
"$schema": "https://cli.sasjs.io/sasjsconfig-schema.json",
"serviceConfig": {
"serviceFolders": [
"sasjs/common"
]
},
"defaultTarget": "4gl",
"targets": [
{
"name": "4gl",
"serverType": "SASJS",
"serverUrl": "https://sas9.4gl.io",
"appLoc": "/Public/app/adapter-tests"
}
]
}

View File

@@ -48,7 +48,7 @@ export const computeTests = (adapter: SASjs, appLoc: string): TestSuite => ({
test: () => {
const data: any = { table1: [{ col1: 'first col value' }] }
return adapter.startComputeJob(
'/Public/app/common/sendArr',
'/Public/app/adapter-tests/services/common/sendArr',
data,
{},
undefined,

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,6 @@ import {
AuthConfig,
ExtraResponseAttributes,
SasAuthResponse,
ServicePackSASjs,
AuthConfigSas9
} from '@sasjs/utils/types'
import { RequestClient } from './request/RequestClient'
@@ -895,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,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

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

@@ -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

@@ -21,8 +21,8 @@ export class ErrorResponse {
}
}
interface ErrorBody {
export interface ErrorBody {
message: string
details: string
details: any
raw: any
}

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]