mirror of
https://github.com/sasjs/adapter.git
synced 2025-12-12 09:44:36 +00:00
Compare commits
1 Commits
v3.4.1
...
debug-sasj
| Author | SHA1 | Date | |
|---|---|---|---|
| 2c6198ae25 |
4
.github/dependabot.yml
vendored
4
.github/dependabot.yml
vendored
@@ -1,7 +1,7 @@
|
|||||||
version: 2
|
version: 2
|
||||||
updates:
|
updates:
|
||||||
- package-ecosystem: npm
|
- package-ecosystem: npm
|
||||||
directory: '/'
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: monthly
|
interval: daily
|
||||||
open-pull-requests-limit: 10
|
open-pull-requests-limit: 10
|
||||||
|
|||||||
3
.github/reviewer-lottery.yml
vendored
3
.github/reviewer-lottery.yml
vendored
@@ -2,8 +2,11 @@ groups:
|
|||||||
- name: SASjs Devs # name of the group
|
- name: SASjs Devs # name of the group
|
||||||
reviewers: 1 # how many reviewers do you want to assign?
|
reviewers: 1 # how many reviewers do you want to assign?
|
||||||
usernames: # github usernames of the reviewers
|
usernames: # github usernames of the reviewers
|
||||||
|
- krishna-acondy
|
||||||
- YuryShkoda
|
- YuryShkoda
|
||||||
|
- saadjutt01
|
||||||
- medjedovicm
|
- medjedovicm
|
||||||
|
- allanbowe
|
||||||
- sabhas
|
- sabhas
|
||||||
- name: SASjs QA
|
- name: SASjs QA
|
||||||
reviewers: 1
|
reviewers: 1
|
||||||
|
|||||||
7
.github/workflows/build.yml
vendored
7
.github/workflows/build.yml
vendored
@@ -13,17 +13,14 @@ jobs:
|
|||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [lts/fermium]
|
node-version: [15.x]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
cache: npm
|
|
||||||
- name: Check npm audit
|
|
||||||
run: npm audit --production --audit-level=low
|
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: npm ci
|
run: npm ci
|
||||||
- name: Check code style
|
- name: Check code style
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
tasks:
|
|
||||||
- init: npm install && npm run build
|
|
||||||
@@ -3,4 +3,3 @@ docs/
|
|||||||
.github/
|
.github/
|
||||||
*.md
|
*.md
|
||||||
*.spec.ts
|
*.spec.ts
|
||||||
.all-contributorsrc
|
|
||||||
|
|||||||
108
README.md
108
README.md
@@ -36,7 +36,7 @@ Ok ok. Deploy this [example.html](https://raw.githubusercontent.com/sasjs/adapte
|
|||||||
|
|
||||||
The backend part can be deployed as follows:
|
The backend part can be deployed as follows:
|
||||||
|
|
||||||
```sas
|
```
|
||||||
%let appLoc=/Public/app/readme; /* Metadata or Viya Folder per SASjs config */
|
%let appLoc=/Public/app/readme; /* Metadata or Viya Folder per SASjs config */
|
||||||
filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
|
filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
|
||||||
%inc mc; /* compile macros (can also be downloaded & compiled seperately) */
|
%inc mc; /* compile macros (can also be downloaded & compiled seperately) */
|
||||||
@@ -85,11 +85,11 @@ let sasJs = new SASjs.default(
|
|||||||
);
|
);
|
||||||
```
|
```
|
||||||
If you've installed it via NPM, you can import it as a default import like so:
|
If you've installed it via NPM, you can import it as a default import like so:
|
||||||
```js
|
```
|
||||||
import SASjs from '@sasjs/adapter';
|
import SASjs from '@sasjs/adapter';
|
||||||
```
|
```
|
||||||
You can then instantiate it with:
|
You can then instantiate it with:
|
||||||
```js
|
```
|
||||||
const sasJs = new SASjs({your config})
|
const sasJs = new SASjs({your config})
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -119,7 +119,6 @@ sasJs.request("/path/to/my/service", dataObject)
|
|||||||
console.log(response.tablewith2cols1row[0].COL1.value)
|
console.log(response.tablewith2cols1row[0].COL1.value)
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
We supply the path to the SAS service, and a data object. The data object can be null (for services with no input), or can contain one or more tables in the following format:
|
We supply the path to the SAS service, and a data object. The data object can be null (for services with no input), or can contain one or more tables in the following format:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
@@ -142,105 +141,27 @@ The response object will contain returned tables and columns. Table names are a
|
|||||||
|
|
||||||
The adapter will also cache the logs (if debug enabled) and even the work tables. For performance, it is best to keep debug mode off.
|
The adapter will also cache the logs (if debug enabled) and even the work tables. For performance, it is best to keep debug mode off.
|
||||||
|
|
||||||
### Variable Types
|
|
||||||
|
|
||||||
The SAS type (char/numeric) of the values is determined according to a set of rules:
|
|
||||||
|
|
||||||
* If the values are numeric, the SAS type is numeric
|
|
||||||
* If the values are all string, the SAS type is character
|
|
||||||
* If the values contain a single character (a-Z + underscore) AND a numeric, then the SAS type is numeric (with special missing values).
|
|
||||||
* `null` is set to either '.' or '' depending on the assigned or derived type per the above rules. If entire column is `null` then the type will be numeric.
|
|
||||||
|
|
||||||
The following table illustrates the formats applied to columns under various scenarios:
|
|
||||||
|
|
||||||
|JS Values |SAS Format|
|
|
||||||
|---|---|
|
|
||||||
|'a', 'a' |$char1.|
|
|
||||||
|0, '_' |best.|
|
|
||||||
|'Z', 0 |best.|
|
|
||||||
|'a', 'aaa' |$char3.|
|
|
||||||
|null, 'a', 'aaa' | $char3.|
|
|
||||||
|null, 'a', 0 | best.|
|
|
||||||
|null, null | best.|
|
|
||||||
|null, '' | $char1.|
|
|
||||||
|null, 'a' | $char1.|
|
|
||||||
|'a' | $char1.|
|
|
||||||
|'a', null | $char1.|
|
|
||||||
|'a', null, 0 | best.|
|
|
||||||
|
|
||||||
Validation is also performed on the values. The following combinations will throw errors:
|
|
||||||
|
|
||||||
|JS Values |SAS Format|
|
|
||||||
|---|---|
|
|
||||||
|null, 'aaaa', 0 | Error: mixed types. 'aaaa' is not a special missing value.|
|
|
||||||
|0, 'a', '!' | Error: mixed types. '!' is not a special missing value|
|
|
||||||
|1.1, '.', 0| Error: mixed types. For regular nulls, use `null`|
|
|
||||||
|
|
||||||
### Variable Format Override
|
|
||||||
The auto-detect functionality above is thwarted in the following scenarios:
|
|
||||||
|
|
||||||
* A character column containing only `null` values (is considered numeric)
|
|
||||||
* A numeric column containing only special missing values (is considered character)
|
|
||||||
|
|
||||||
To cater for these scenarios, an optional array of formats can be passed along with the data to ensure that SAS will read them in correctly.
|
|
||||||
|
|
||||||
To understand these formats, it should be noted that the JSON data is NOT passed directly (as JSON) to SAS. It is first converted into CSV, and the header row is actually an `infile` statement in disguise. It looks a bit like this:
|
|
||||||
|
|
||||||
```csv
|
|
||||||
CHARVAR1:$char4. CHARVAR2:$char1. NUMVAR:best.
|
|
||||||
LOAD,,0
|
|
||||||
ABCD,X,.
|
|
||||||
```
|
|
||||||
|
|
||||||
To provide overrides to this header row, the tables object can be constructed as follows (with a leading '$' in the table name):
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
let specialData={
|
|
||||||
"tablewith2cols2rows": [
|
|
||||||
{"col1": "val1","specialMissingsCol": "A"},
|
|
||||||
{"col1": "val2","specialMissingsCol": "_"}
|
|
||||||
],
|
|
||||||
"$tablewith2cols2rows":{"formats":{"specialMissingsCol":"best."}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
It is not necessary to provide formats for ALL the columns, only the ones that need to be overridden.
|
|
||||||
|
|
||||||
## SAS Inputs / Outputs
|
## SAS Inputs / Outputs
|
||||||
|
|
||||||
The SAS side is handled by a number of macros in the [macro core](https://github.com/sasjs/core) library.
|
The SAS side is handled by a number of macros in the [macro core](https://github.com/sasjs/core) library.
|
||||||
|
|
||||||
The following snippet shows the process of SAS tables arriving / leaving:
|
The following snippet shows the process of SAS tables arriving / leaving:
|
||||||
|
|
||||||
```sas
|
```sas
|
||||||
/* fetch all input tables sent from frontend - they arrive as work tables */
|
/* fetch all input tables sent from frontend - they arrive as work tables */
|
||||||
%webout(FETCH)
|
%webout(FETCH)
|
||||||
|
|
||||||
/* some sas code */
|
/* some sas code */
|
||||||
data a b c;
|
data some sas tables;
|
||||||
set from js;
|
set from js;
|
||||||
run;
|
run;
|
||||||
|
|
||||||
%webout(OPEN) /* Open the JSON to be returned */
|
%webout(OPEN) /* open the JSON to be returned */
|
||||||
%webout(OBJ,a) /* Rows in table `a` are objects (easy to use) */
|
%webout(OBJ,some) /* `some` table is sent in object format */
|
||||||
%webout(ARR,b) /* Rows in table `b` are arrays (compact) */
|
%webout(ARR,sas) /* `sas` table is sent in array format, smaller filesize */
|
||||||
%webout(OBJ,c,fmt=N) /* Table `c` is sent unformatted (raw) */
|
%webout(OBJ,tables,fmt=N) /* unformatted (raw) data */
|
||||||
%webout(OBJ,c,label=d) /* Rename as `d` on JS side */
|
%webout(OBJ,tables,label=newtable) /* rename tables on export */
|
||||||
%webout(CLOSE) /* Close the JSON and add default variables */
|
%webout(CLOSE) /* close the JSON and send some extra useful variables too */
|
||||||
```
|
|
||||||
|
|
||||||
By default, special SAS numeric missings (_a-Z) are converted to `null` in the JSON. If you'd like to preserve these, use the `missing=STRING` option as follows:
|
|
||||||
|
|
||||||
```sas
|
|
||||||
%webout(OBJ,a,missing=STRING)
|
|
||||||
```
|
|
||||||
In this case, special missings (such as `.a`, `.b`) are converted to javascript string values (`'A', 'B'`).
|
|
||||||
|
|
||||||
Where an entire column is made up of special missing numerics, there would be no way to distinguish it from a single-character column by looking at the values. To cater for this scenario, it is possible to export the variable types (and other attributes such as label and format) by adding a `showmeta` param to the `webout()` macro as follows:
|
|
||||||
|
|
||||||
```sas
|
|
||||||
%webout(OBJ,a,missing=STRING,showmeta=YES)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
@@ -248,10 +169,9 @@ Where an entire column is made up of special missing numerics, there would be no
|
|||||||
Configuration on the client side involves passing an object on startup, which can also be passed with each request. Technical documentation on the SASjsConfig class is available [here](https://adapter.sasjs.io/classes/types.sasjsconfig.html). The main config items are:
|
Configuration on the client side involves passing an object on startup, which can also be passed with each request. Technical documentation on the SASjsConfig class is available [here](https://adapter.sasjs.io/classes/types.sasjsconfig.html). The main config items are:
|
||||||
|
|
||||||
* `appLoc` - this is the folder under which the SAS services will be created.
|
* `appLoc` - this is the folder under which the SAS services will be created.
|
||||||
* `serverType` - either `SAS9`, `SASVIYA` or `SASJS`. The `SASJS` server type is for use with [sasjs/server](https://github.com/sasjs/server).
|
* `serverType` - either `SAS9` or `SASVIYA`.
|
||||||
* `serverUrl` - the location (including http protocol and port) of the SAS Server. Can be omitted, eg if serving directly from the SAS Web Server, or in streaming mode.
|
* `serverUrl` - the location (including http protocol and port) of the SAS Server. Can be omitted, eg if serving directly from the SAS Web Server, or in streaming mode.
|
||||||
* `debug` - if `true` then SAS Logs and extra debug information is returned.
|
* `debug` - if `true` then SAS Logs and extra debug information is returned.
|
||||||
* `LoginMechanism` - either `Default` or `Redirected`. If `Redirected` then authentication occurs through the injection of an additional screen, which contains the SASLogon prompt. This allows for more complex authentication flows (such as 2FA) and avoids the need to handle passwords in the application itself. The styling of the redirect flow can also be modified. If left at "Default" then the developer must capture the username and password and use these with the `.login()` method.
|
|
||||||
* `useComputeApi` - Only relevant when the serverType is `SASVIYA`. If `true` the [Compute API](#using-the-compute-api) is used. If `false` the [JES API](#using-the-jes-api) is used. If `null` or `undefined` the [Web](#using-jes-web-app) approach is used.
|
* `useComputeApi` - Only relevant when the serverType is `SASVIYA`. If `true` the [Compute API](#using-the-compute-api) is used. If `false` the [JES API](#using-the-jes-api) is used. If `null` or `undefined` the [Web](#using-jes-web-app) approach is used.
|
||||||
* `contextName` - Compute context on which the requests will be called. If missing or not provided, defaults to `Job Execution Compute context`.
|
* `contextName` - Compute context on which the requests will be called. If missing or not provided, defaults to `Job Execution Compute context`.
|
||||||
|
|
||||||
@@ -276,7 +196,7 @@ Here we are running Jobs using the Job Execution Service except this time we are
|
|||||||
|
|
||||||
This approach (`useComputeApi: false`) also ensures that jobs are displayed in Environment Manager.
|
This approach (`useComputeApi: false`) also ensures that jobs are displayed in Environment Manager.
|
||||||
|
|
||||||
```json
|
```
|
||||||
{
|
{
|
||||||
appLoc:"/Your/Path",
|
appLoc:"/Your/Path",
|
||||||
serverType:"SASVIYA",
|
serverType:"SASVIYA",
|
||||||
@@ -290,12 +210,12 @@ This approach is by far the fastest, as a result of the optimisations we have bu
|
|||||||
|
|
||||||
With this approach (`useComputeApi: true`), the requests/logs will _not_ appear in the list in Environment manager.
|
With this approach (`useComputeApi: true`), the requests/logs will _not_ appear in the list in Environment manager.
|
||||||
|
|
||||||
```json
|
```
|
||||||
{
|
{
|
||||||
appLoc:"/Your/Path",
|
appLoc:"/Your/Path",
|
||||||
serverType:"SASVIYA",
|
serverType:"SASVIYA",
|
||||||
useComputeApi: true,
|
useComputeApi: true,
|
||||||
contextName: "yourComputeContext"
|
contextName: 'yourComputeContext'
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
137
docs/index.html
137
docs/index.html
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
20034
package-lock.json
generated
20034
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
66
package.json
66
package.json
@@ -40,44 +40,40 @@
|
|||||||
},
|
},
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/axios": "0.14.0",
|
"@types/axios": "^0.14.0",
|
||||||
"@types/express": "4.17.13",
|
"@types/form-data": "^2.5.0",
|
||||||
"@types/form-data": "2.5.0",
|
"@types/jest": "^27.0.1",
|
||||||
"@types/jest": "27.0.2",
|
"@types/mime": "^2.0.3",
|
||||||
"@types/mime": "2.0.3",
|
"@types/tough-cookie": "^4.0.1",
|
||||||
"@types/pem": "1.9.6",
|
"copyfiles": "^2.4.1",
|
||||||
"@types/tough-cookie": "4.0.1",
|
"cp": "^0.2.0",
|
||||||
"copyfiles": "2.4.1",
|
"dotenv": "^10.0.0",
|
||||||
"cp": "0.2.0",
|
"jest": "^27.1.0",
|
||||||
"dotenv": "10.0.0",
|
"jest-extended": "^0.11.5",
|
||||||
"express": "4.17.1",
|
"node-polyfill-webpack-plugin": "^1.1.4",
|
||||||
"jest": "27.2.0",
|
"path": "^0.12.7",
|
||||||
"jest-extended": "0.11.5",
|
"process": "^0.11.10",
|
||||||
"node-polyfill-webpack-plugin": "1.1.4",
|
"rimraf": "^3.0.2",
|
||||||
"path": "0.12.7",
|
"semantic-release": "^17.4.7",
|
||||||
"pem": "1.14.4",
|
"terser-webpack-plugin": "^5.2.0",
|
||||||
"process": "0.11.10",
|
"ts-jest": "^27.0.3",
|
||||||
"rimraf": "3.0.2",
|
"ts-loader": "^9.2.2",
|
||||||
"semantic-release": "18.0.0",
|
"tslint": "^6.1.3",
|
||||||
"terser-webpack-plugin": "5.2.4",
|
"tslint-config-prettier": "^1.18.0",
|
||||||
"ts-jest": "27.0.3",
|
"typedoc": "^0.21.9",
|
||||||
"ts-loader": "9.2.6",
|
"typedoc-neo-theme": "^1.1.1",
|
||||||
"tslint": "6.1.3",
|
"typedoc-plugin-external-module-name": "^4.0.6",
|
||||||
"tslint-config-prettier": "1.18.0",
|
|
||||||
"typedoc": "0.19.2",
|
|
||||||
"typedoc-neo-theme": "1.1.1",
|
|
||||||
"typedoc-plugin-external-module-name": "4.0.6",
|
|
||||||
"typescript": "4.3.5",
|
"typescript": "4.3.5",
|
||||||
"webpack": "5.56.0",
|
"webpack": "^5.44.0",
|
||||||
"webpack-cli": "4.7.2"
|
"webpack-cli": "^4.7.2"
|
||||||
},
|
},
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sasjs/utils": "2.32.0",
|
"@sasjs/utils": "^2.30.0",
|
||||||
"axios": "0.25.0",
|
"axios": "^0.21.1",
|
||||||
"axios-cookiejar-support": "1.0.1",
|
"axios-cookiejar-support": "^1.0.1",
|
||||||
"form-data": "4.0.0",
|
"form-data": "^4.0.0",
|
||||||
"https": "1.0.0",
|
"https": "^1.0.0",
|
||||||
"tough-cookie": "4.0.0"
|
"tough-cookie": "^4.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ parmcards4;
|
|||||||
%webout(FETCH)
|
%webout(FETCH)
|
||||||
%webout(OPEN)
|
%webout(OPEN)
|
||||||
%macro x();
|
%macro x();
|
||||||
%do i=1 %to &_webin_file_count; %webout(OBJ,&&_webin_name&i,missing=STRING) %end;
|
%do i=1 %to &_webin_file_count; %webout(OBJ,&&_webin_name&i) %end;
|
||||||
%mend; %x()
|
%mend; %x()
|
||||||
%webout(CLOSE)
|
%webout(CLOSE)
|
||||||
;;;;
|
;;;;
|
||||||
@@ -79,7 +79,7 @@ parmcards4;
|
|||||||
%webout(FETCH)
|
%webout(FETCH)
|
||||||
%webout(OPEN)
|
%webout(OPEN)
|
||||||
%macro x();
|
%macro x();
|
||||||
%do i=1 %to &_webin_file_count; %webout(ARR,&&_webin_name&i,missing=STRING) %end;
|
%do i=1 %to &_webin_file_count; %webout(ARR,&&_webin_name&i) %end;
|
||||||
%mend; %x()
|
%mend; %x()
|
||||||
%webout(CLOSE)
|
%webout(CLOSE)
|
||||||
;;;;
|
;;;;
|
||||||
@@ -111,7 +111,7 @@ parmcards4;
|
|||||||
%macro x();
|
%macro x();
|
||||||
%do i=1 %to %sysfunc(countw(&sasjs_tables));
|
%do i=1 %to %sysfunc(countw(&sasjs_tables));
|
||||||
%let table=%scan(&sasjs_tables,&i);
|
%let table=%scan(&sasjs_tables,&i);
|
||||||
%webout(OBJ,&table,missing=STRING)
|
%webout(OBJ,&table)
|
||||||
%end;
|
%end;
|
||||||
%mend;
|
%mend;
|
||||||
%x()
|
%x()
|
||||||
@@ -125,7 +125,7 @@ parmcards4;
|
|||||||
%macro x();
|
%macro x();
|
||||||
%do i=1 %to %sysfunc(countw(&sasjs_tables));
|
%do i=1 %to %sysfunc(countw(&sasjs_tables));
|
||||||
%let table=%scan(&sasjs_tables,&i);
|
%let table=%scan(&sasjs_tables,&i);
|
||||||
%webout(ARR,&table,missing=STRING)
|
%webout(ARR,&table)
|
||||||
%end;
|
%end;
|
||||||
%mend;
|
%mend;
|
||||||
%x()
|
%x()
|
||||||
|
|||||||
25641
sasjs-tests/package-lock.json
generated
25641
sasjs-tests/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sasjs/adapter": "file:../build/sasjs-adapter-5.0.0.tgz",
|
"@sasjs/adapter": "file:../build/sasjs-adapter-5.0.0.tgz",
|
||||||
"@sasjs/test-framework": "^1.4.3",
|
"@sasjs/test-framework": "^1.4.0",
|
||||||
"@types/jest": "^26.0.20",
|
"@types/jest": "^26.0.20",
|
||||||
"@types/node": "^14.14.41",
|
"@types/node": "^14.14.41",
|
||||||
"@types/react": "^17.0.1",
|
"@types/react": "^17.0.1",
|
||||||
@@ -22,8 +22,8 @@
|
|||||||
"build": "react-scripts build",
|
"build": "react-scripts build",
|
||||||
"test": "react-scripts test",
|
"test": "react-scripts test",
|
||||||
"eject": "react-scripts eject",
|
"eject": "react-scripts eject",
|
||||||
"update:adapter": "cd .. && npm run package:lib && cd sasjs-tests && npm i ../build/sasjs-adapter-5.0.0.tgz --legacy-peer-deps",
|
"update:adapter": "cd .. && npm run package:lib && cd sasjs-tests && npm i ../build/sasjs-adapter-5.0.0.tgz",
|
||||||
"deploy:tests": "rsync -avhe ssh ./build/* --delete $SSH_ACCOUNT:$DEPLOY_PATH || npm run deploy:tests-win",
|
"deploy:tests": "rsync -avhe ssh ./build/* --delete sabhas@sas.analytium.co.uk:/var/www/html/sabhas/sasjs-test || npm run deploy:tests-win",
|
||||||
"deploy:tests-win": "scp %DEPLOY_PATH% ./build/*",
|
"deploy:tests-win": "scp %DEPLOY_PATH% ./build/*",
|
||||||
"deploy": "npm run update:adapter && npm run build && npm run deploy:tests"
|
"deploy": "npm run update:adapter && npm run build && npm run deploy:tests"
|
||||||
},
|
},
|
||||||
@@ -43,6 +43,6 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"node-sass": "^6.0.1"
|
"node-sass": "^5.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"userName": "",
|
"userName": "",
|
||||||
"password": "",
|
"password": "",
|
||||||
"sasJsConfig": {
|
"sasJsConfig": {
|
||||||
"serverUrl": "",
|
"serverUrl": "https://sas.analytium.co.uk/",
|
||||||
"appLoc": "/Public/app",
|
"appLoc": "/Public/app",
|
||||||
"serverType": "SASVIYA",
|
"serverType": "SASVIYA",
|
||||||
"debug": false,
|
"debug": false,
|
||||||
|
|||||||
@@ -14,16 +14,16 @@ const App = (): ReactElement<{}> => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (adapter) {
|
if (adapter) {
|
||||||
const testSuites = [
|
const testSuites = [
|
||||||
basicTests(adapter, config.userName, config.password),
|
// basicTests(adapter, config.userName, config.password),
|
||||||
sendArrTests(adapter),
|
// sendArrTests(adapter),
|
||||||
sendObjTests(adapter),
|
// sendObjTests(adapter),
|
||||||
specialCaseTests(adapter),
|
// specialCaseTests(adapter),
|
||||||
sasjsRequestTests(adapter)
|
sasjsRequestTests(adapter)
|
||||||
]
|
]
|
||||||
|
|
||||||
if (adapter.getSasjsConfig().serverType === 'SASVIYA') {
|
// if (adapter.getSasjsConfig().serverType === 'SASVIYA') {
|
||||||
testSuites.push(computeTests(adapter))
|
// testSuites.push(computeTests(adapter))
|
||||||
}
|
// }
|
||||||
|
|
||||||
setTestSuites(testSuites)
|
setTestSuites(testSuites)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import SASjs, { LoginMechanism, SASjsConfig } from '@sasjs/adapter'
|
import SASjs, { SASjsConfig } from '@sasjs/adapter'
|
||||||
import { TestSuite } from '@sasjs/test-framework'
|
import { TestSuite } from '@sasjs/test-framework'
|
||||||
import { ServerType } from '@sasjs/utils/types'
|
import { ServerType } from '@sasjs/utils/types'
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ const defaultConfig: SASjsConfig = {
|
|||||||
debug: false,
|
debug: false,
|
||||||
contextName: 'SAS Job Execution compute context',
|
contextName: 'SAS Job Execution compute context',
|
||||||
useComputeApi: false,
|
useComputeApi: false,
|
||||||
loginMechanism: LoginMechanism.Default
|
allowInsecureRequests: false
|
||||||
}
|
}
|
||||||
|
|
||||||
const customConfig = {
|
const customConfig = {
|
||||||
@@ -41,19 +41,6 @@ export const basicTests = (
|
|||||||
assertion: (response: any) =>
|
assertion: (response: any) =>
|
||||||
response && response.isLoggedIn && response.userName === userName
|
response && response.isLoggedIn && response.userName === userName
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: 'Fetch username for already logged in user',
|
|
||||||
description: 'Should log the user in',
|
|
||||||
test: async () => {
|
|
||||||
await adapter.logIn(userName, password)
|
|
||||||
|
|
||||||
const newAdapterIns = new SASjs(adapter.getSasjsConfig())
|
|
||||||
|
|
||||||
return await newAdapterIns.checkSession()
|
|
||||||
},
|
|
||||||
assertion: (response: any) =>
|
|
||||||
response?.isLoggedIn && response?.userName === userName
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: 'Multiple Log in attempts',
|
title: 'Multiple Log in attempts',
|
||||||
description:
|
description:
|
||||||
@@ -61,7 +48,7 @@ export const basicTests = (
|
|||||||
test: async () => {
|
test: async () => {
|
||||||
await adapter.logOut()
|
await adapter.logOut()
|
||||||
await adapter.logIn('invalid', 'invalid')
|
await adapter.logIn('invalid', 'invalid')
|
||||||
return await adapter.logIn(userName, password)
|
return adapter.logIn(userName, password)
|
||||||
},
|
},
|
||||||
assertion: (response: any) =>
|
assertion: (response: any) =>
|
||||||
response && response.isLoggedIn && response.userName === userName
|
response && response.isLoggedIn && response.userName === userName
|
||||||
@@ -77,8 +64,8 @@ export const basicTests = (
|
|||||||
'common/sendArr',
|
'common/sendArr',
|
||||||
stringData,
|
stringData,
|
||||||
undefined,
|
undefined,
|
||||||
async () => {
|
() => {
|
||||||
await adapter.logIn(userName, password)
|
adapter.logIn(userName, password)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -164,7 +151,7 @@ export const basicTests = (
|
|||||||
description:
|
description:
|
||||||
'Should complete successful request with extra attributes present in response',
|
'Should complete successful request with extra attributes present in response',
|
||||||
test: async () => {
|
test: async () => {
|
||||||
const config: Partial<SASjsConfig> = {
|
const config = {
|
||||||
useComputeApi: false
|
useComputeApi: false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -79,19 +79,6 @@ const errorAndCsrfData: any = {
|
|||||||
_csrf: [{ col1: 'q', col2: 'w', col3: 'e', col4: 'r' }]
|
_csrf: [{ col1: 'q', col2: 'w', col3: 'e', col4: 'r' }]
|
||||||
}
|
}
|
||||||
|
|
||||||
const testTable = 'sometable'
|
|
||||||
const testTableWithNullVars: { [key: string]: any } = {
|
|
||||||
[testTable]: [
|
|
||||||
{ var1: 'string', var2: 232, nullvar: 'A' },
|
|
||||||
{ var1: 'string', var2: 232, nullvar: 'B' },
|
|
||||||
{ var1: 'string', var2: 232, nullvar: '_' },
|
|
||||||
{ var1: 'string', var2: 232, nullvar: 0 },
|
|
||||||
{ var1: 'string', var2: 232, nullvar: 'z' },
|
|
||||||
{ var1: 'string', var2: 232, nullvar: null }
|
|
||||||
],
|
|
||||||
[`$${testTable}`]: { formats: { var1: '$char12.', nullvar: 'best.' } }
|
|
||||||
}
|
|
||||||
|
|
||||||
export const specialCaseTests = (adapter: SASjs): TestSuite => ({
|
export const specialCaseTests = (adapter: SASjs): TestSuite => ({
|
||||||
name: 'Special Cases',
|
name: 'Special Cases',
|
||||||
tests: [
|
tests: [
|
||||||
@@ -260,39 +247,6 @@ export const specialCaseTests = (adapter: SASjs): TestSuite => ({
|
|||||||
res._csrf[0].COL4 === errorAndCsrfData._csrf[0].col4
|
res._csrf[0].COL4 === errorAndCsrfData._csrf[0].col4
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Special missing values',
|
|
||||||
description: 'Should support special missing values',
|
|
||||||
test: () => {
|
|
||||||
return adapter.request('common/sendObj', testTableWithNullVars)
|
|
||||||
},
|
|
||||||
assertion: (res: any) => {
|
|
||||||
let assertionRes = true
|
|
||||||
|
|
||||||
testTableWithNullVars[testTable].forEach(
|
|
||||||
(row: { [key: string]: any }, i: number) =>
|
|
||||||
Object.keys(row).forEach((col: string) => {
|
|
||||||
const resValue = res[testTable][i][col.toUpperCase()]
|
|
||||||
|
|
||||||
if (
|
|
||||||
typeof row[col] === 'string' &&
|
|
||||||
testTableWithNullVars[`$${testTable}`].formats[col] ===
|
|
||||||
'best.' &&
|
|
||||||
row[col].toUpperCase() !== resValue
|
|
||||||
) {
|
|
||||||
assertionRes = false
|
|
||||||
} else if (
|
|
||||||
typeof row[col] !== 'string' &&
|
|
||||||
row[col] !== resValue
|
|
||||||
) {
|
|
||||||
assertionRes = false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
return assertionRes
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|||||||
107
src/FileUploader.ts
Normal file
107
src/FileUploader.ts
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
import { isUrl, getValidJson, parseSasViyaDebugResponse } from './utils'
|
||||||
|
import { UploadFile } from './types/UploadFile'
|
||||||
|
import { ErrorResponse, LoginRequiredError } from './types/errors'
|
||||||
|
import { RequestClient } from './request/RequestClient'
|
||||||
|
import { ServerType } from '@sasjs/utils/types'
|
||||||
|
import SASjs from './SASjs'
|
||||||
|
import { Server } from 'https'
|
||||||
|
import { SASjsConfig } from './types'
|
||||||
|
import { config } from 'process'
|
||||||
|
|
||||||
|
export class FileUploader {
|
||||||
|
constructor(
|
||||||
|
private sasjsConfig: SASjsConfig,
|
||||||
|
private jobsPath: string,
|
||||||
|
private requestClient: RequestClient
|
||||||
|
) {
|
||||||
|
if (this.sasjsConfig.serverUrl) isUrl(this.sasjsConfig.serverUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
public uploadFile(sasJob: string, files: UploadFile[], params: any) {
|
||||||
|
if (files?.length < 1)
|
||||||
|
return Promise.reject(
|
||||||
|
new ErrorResponse('At least one file must be provided.')
|
||||||
|
)
|
||||||
|
if (!sasJob || sasJob === '')
|
||||||
|
return Promise.reject(new ErrorResponse('sasJob must be provided.'))
|
||||||
|
|
||||||
|
let paramsString = ''
|
||||||
|
|
||||||
|
for (let param in params) {
|
||||||
|
if (params.hasOwnProperty(param)) {
|
||||||
|
paramsString += `&${param}=${params[param]}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const program = this.sasjsConfig.appLoc
|
||||||
|
? this.sasjsConfig.appLoc.replace(/\/?$/, '/') + sasJob.replace(/^\//, '')
|
||||||
|
: sasJob
|
||||||
|
const uploadUrl = `${this.jobsPath}/?${
|
||||||
|
'_program=' + program
|
||||||
|
}${paramsString}`
|
||||||
|
|
||||||
|
const formData = new FormData()
|
||||||
|
|
||||||
|
for (let file of files) {
|
||||||
|
formData.append('file', file.file, file.fileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
const csrfToken = this.requestClient.getCsrfToken('file')
|
||||||
|
if (csrfToken) formData.append('_csrf', csrfToken.value)
|
||||||
|
if (this.sasjsConfig.debug) formData.append('_debug', '131')
|
||||||
|
if (
|
||||||
|
this.sasjsConfig.serverType === ServerType.SasViya &&
|
||||||
|
this.sasjsConfig.contextName
|
||||||
|
)
|
||||||
|
formData.append('_contextname', this.sasjsConfig.contextName)
|
||||||
|
|
||||||
|
const headers = {
|
||||||
|
'cache-control': 'no-cache',
|
||||||
|
Accept: '*/*',
|
||||||
|
'Content-Type': 'text/plain'
|
||||||
|
}
|
||||||
|
|
||||||
|
// currently only web approach is supported for file upload
|
||||||
|
// therefore log is part of response with debug enabled and must be parsed
|
||||||
|
return this.requestClient
|
||||||
|
.post(
|
||||||
|
uploadUrl,
|
||||||
|
formData,
|
||||||
|
undefined,
|
||||||
|
'application/json',
|
||||||
|
headers,
|
||||||
|
this.sasjsConfig.debug,
|
||||||
|
true,
|
||||||
|
sasJob
|
||||||
|
)
|
||||||
|
.then(async (res) => {
|
||||||
|
if (
|
||||||
|
this.sasjsConfig.serverType === ServerType.SasViya &&
|
||||||
|
this.sasjsConfig.debug
|
||||||
|
) {
|
||||||
|
const jsonResponse = await parseSasViyaDebugResponse(
|
||||||
|
res.result as string,
|
||||||
|
this.requestClient,
|
||||||
|
this.sasjsConfig.serverUrl
|
||||||
|
)
|
||||||
|
return jsonResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
return typeof res.result === 'string'
|
||||||
|
? getValidJson(res.result)
|
||||||
|
: res.result
|
||||||
|
|
||||||
|
//TODO: append to SASjs requests
|
||||||
|
})
|
||||||
|
.catch((err: Error) => {
|
||||||
|
if (err instanceof LoginRequiredError) {
|
||||||
|
return Promise.reject(
|
||||||
|
new ErrorResponse('You must be logged in to upload a file.', err)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return Promise.reject(
|
||||||
|
new ErrorResponse('File upload request failed.', err)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import * as https from 'https'
|
|
||||||
import { generateTimestamp } from '@sasjs/utils/time'
|
import { generateTimestamp } from '@sasjs/utils/time'
|
||||||
import * as NodeFormData from 'form-data'
|
import * as NodeFormData from 'form-data'
|
||||||
import { Sas9RequestClient } from './request/Sas9RequestClient'
|
import { Sas9RequestClient } from './request/Sas9RequestClient'
|
||||||
@@ -14,10 +13,10 @@ export class SAS9ApiClient {
|
|||||||
constructor(
|
constructor(
|
||||||
private serverUrl: string,
|
private serverUrl: string,
|
||||||
private jobsPath: string,
|
private jobsPath: string,
|
||||||
httpsAgentOptions?: https.AgentOptions
|
allowInsecureRequests: boolean
|
||||||
) {
|
) {
|
||||||
if (serverUrl) isUrl(serverUrl)
|
if (serverUrl) isUrl(serverUrl)
|
||||||
this.requestClient = new Sas9RequestClient(serverUrl, httpsAgentOptions)
|
this.requestClient = new Sas9RequestClient(serverUrl, allowInsecureRequests)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ import { pollJobState } from './api/viya/pollJobState'
|
|||||||
import { getTokens } from './auth/getTokens'
|
import { getTokens } from './auth/getTokens'
|
||||||
import { uploadTables } from './api/viya/uploadTables'
|
import { uploadTables } from './api/viya/uploadTables'
|
||||||
import { executeScript } from './api/viya/executeScript'
|
import { executeScript } from './api/viya/executeScript'
|
||||||
import { getAccessTokenForViya } from './auth/getAccessTokenForViya'
|
import { getAccessToken } from './auth/getAccessToken'
|
||||||
import { refreshTokensForViya } from './auth/refreshTokensForViya'
|
import { refreshTokens } from './auth/refreshTokens'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A client for interfacing with the SAS Viya REST API.
|
* A client for interfacing with the SAS Viya REST API.
|
||||||
@@ -51,16 +51,6 @@ export class SASViyaApiClient {
|
|||||||
)
|
)
|
||||||
private folderMap = new Map<string, Job[]>()
|
private folderMap = new Map<string, Job[]>()
|
||||||
|
|
||||||
/**
|
|
||||||
* A helper method used to call appendRequest method of RequestClient
|
|
||||||
* @param response - response from sasjs request
|
|
||||||
* @param program - name of program
|
|
||||||
* @param debug - a boolean that indicates whether debug was enabled or not
|
|
||||||
*/
|
|
||||||
public appendRequest(response: any, program: string, debug: boolean) {
|
|
||||||
this.requestClient!.appendRequest(response, program, debug)
|
|
||||||
}
|
|
||||||
|
|
||||||
public get debug() {
|
public get debug() {
|
||||||
return this._debug
|
return this._debug
|
||||||
}
|
}
|
||||||
@@ -534,26 +524,21 @@ export class SASViyaApiClient {
|
|||||||
clientSecret: string,
|
clientSecret: string,
|
||||||
authCode: string
|
authCode: string
|
||||||
): Promise<SasAuthResponse> {
|
): Promise<SasAuthResponse> {
|
||||||
return getAccessTokenForViya(
|
return getAccessToken(this.requestClient, clientId, clientSecret, authCode)
|
||||||
this.requestClient,
|
|
||||||
clientId,
|
|
||||||
clientSecret,
|
|
||||||
authCode
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exchanges the refresh token for an access token for the given client.
|
* Exchanges the refresh token for an access token for the given client.
|
||||||
* @param clientId - the client ID to authenticate with.
|
* @param clientId - the client ID to authenticate with.
|
||||||
* @param clientSecret - the client secret to authenticate with.
|
* @param clientSecret - the client secret to authenticate with.
|
||||||
* @param refreshToken - the refresh token received from the server.
|
* @param authCode - the refresh token received from the server.
|
||||||
*/
|
*/
|
||||||
public async refreshTokens(
|
public async refreshTokens(
|
||||||
clientId: string,
|
clientId: string,
|
||||||
clientSecret: string,
|
clientSecret: string,
|
||||||
refreshToken: string
|
refreshToken: string
|
||||||
) {
|
) {
|
||||||
return refreshTokensForViya(
|
return refreshTokens(
|
||||||
this.requestClient,
|
this.requestClient,
|
||||||
clientId,
|
clientId,
|
||||||
clientSecret,
|
clientSecret,
|
||||||
@@ -797,12 +782,24 @@ export class SASViyaApiClient {
|
|||||||
jobResult = await this.requestClient.get<any>(
|
jobResult = await this.requestClient.get<any>(
|
||||||
`${this.serverUrl}${resultLink}/content`,
|
`${this.serverUrl}${resultLink}/content`,
|
||||||
access_token,
|
access_token,
|
||||||
'text/plain'
|
'text/plain',
|
||||||
|
{},
|
||||||
|
debug,
|
||||||
|
true,
|
||||||
|
sasJob
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (debug && logLink) {
|
if (debug && logLink) {
|
||||||
log = await this.requestClient
|
log = await this.requestClient
|
||||||
.get<any>(`${this.serverUrl}${logLink.href}/content`, access_token)
|
.get<any>(
|
||||||
|
`${this.serverUrl}${logLink.href}/content`,
|
||||||
|
access_token,
|
||||||
|
'application/json',
|
||||||
|
{},
|
||||||
|
debug,
|
||||||
|
true,
|
||||||
|
sasJob
|
||||||
|
)
|
||||||
.then((res: any) => res.result.items.map((i: any) => i.line).join('\n'))
|
.then((res: any) => res.result.items.map((i: any) => i.line).join('\n'))
|
||||||
}
|
}
|
||||||
if (jobStatus === 'failed') {
|
if (jobStatus === 'failed') {
|
||||||
|
|||||||
255
src/SASjs.ts
255
src/SASjs.ts
@@ -1,41 +1,27 @@
|
|||||||
import { compareTimestamps, asyncForEach } from './utils'
|
import { compareTimestamps, asyncForEach } from './utils'
|
||||||
import {
|
import { SASjsConfig, UploadFile, EditContextInput, PollOptions } from './types'
|
||||||
SASjsConfig,
|
|
||||||
UploadFile,
|
|
||||||
EditContextInput,
|
|
||||||
PollOptions,
|
|
||||||
LoginMechanism,
|
|
||||||
FolderMember,
|
|
||||||
ServiceMember,
|
|
||||||
ExecutionQuery
|
|
||||||
} from './types'
|
|
||||||
import { SASViyaApiClient } from './SASViyaApiClient'
|
import { SASViyaApiClient } from './SASViyaApiClient'
|
||||||
import { SAS9ApiClient } from './SAS9ApiClient'
|
import { SAS9ApiClient } from './SAS9ApiClient'
|
||||||
import { SASjsApiClient, SASjsAuthResponse } from './SASjsApiClient'
|
import { FileUploader } from './FileUploader'
|
||||||
import { AuthManager } from './auth'
|
import { AuthManager } from './auth'
|
||||||
import {
|
import {
|
||||||
ServerType,
|
ServerType,
|
||||||
MacroVar,
|
MacroVar,
|
||||||
AuthConfig,
|
AuthConfig,
|
||||||
ExtraResponseAttributes,
|
ExtraResponseAttributes
|
||||||
SasAuthResponse
|
|
||||||
} from '@sasjs/utils/types'
|
} from '@sasjs/utils/types'
|
||||||
import { RequestClient } from './request/RequestClient'
|
import { RequestClient } from './request/RequestClient'
|
||||||
import { SasjsRequestClient } from './request/SasjsRequestClient'
|
|
||||||
import {
|
import {
|
||||||
JobExecutor,
|
JobExecutor,
|
||||||
WebJobExecutor,
|
WebJobExecutor,
|
||||||
ComputeJobExecutor,
|
ComputeJobExecutor,
|
||||||
JesJobExecutor,
|
JesJobExecutor,
|
||||||
Sas9JobExecutor,
|
Sas9JobExecutor
|
||||||
FileUploader
|
|
||||||
} from './job-execution'
|
} from './job-execution'
|
||||||
import { ErrorResponse } from './types/errors'
|
import { ErrorResponse } from './types/errors'
|
||||||
import { LoginOptions, LoginResult } from './types/Login'
|
|
||||||
|
|
||||||
const defaultConfig: SASjsConfig = {
|
const defaultConfig: SASjsConfig = {
|
||||||
serverUrl: '',
|
serverUrl: '',
|
||||||
pathSASJS: '/SASjsApi/stp/execute',
|
|
||||||
pathSAS9: '/SASStoredProcess/do',
|
pathSAS9: '/SASStoredProcess/do',
|
||||||
pathSASViya: '/SASJobExecution',
|
pathSASViya: '/SASJobExecution',
|
||||||
appLoc: '/Public/seedapp',
|
appLoc: '/Public/seedapp',
|
||||||
@@ -43,7 +29,7 @@ const defaultConfig: SASjsConfig = {
|
|||||||
debug: false,
|
debug: false,
|
||||||
contextName: 'SAS Job Execution compute context',
|
contextName: 'SAS Job Execution compute context',
|
||||||
useComputeApi: null,
|
useComputeApi: null,
|
||||||
loginMechanism: LoginMechanism.Default
|
allowInsecureRequests: false
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -55,7 +41,6 @@ export default class SASjs {
|
|||||||
private jobsPath: string = ''
|
private jobsPath: string = ''
|
||||||
private sasViyaApiClient: SASViyaApiClient | null = null
|
private sasViyaApiClient: SASViyaApiClient | null = null
|
||||||
private sas9ApiClient: SAS9ApiClient | null = null
|
private sas9ApiClient: SAS9ApiClient | null = null
|
||||||
private sasJSApiClient: SASjsApiClient | null = null
|
|
||||||
private fileUploader: FileUploader | null = null
|
private fileUploader: FileUploader | null = null
|
||||||
private authManager: AuthManager | null = null
|
private authManager: AuthManager | null = null
|
||||||
private requestClient: RequestClient | null = null
|
private requestClient: RequestClient | null = null
|
||||||
@@ -64,7 +49,8 @@ export default class SASjs {
|
|||||||
private jesJobExecutor: JobExecutor | null = null
|
private jesJobExecutor: JobExecutor | null = null
|
||||||
private sas9JobExecutor: JobExecutor | null = null
|
private sas9JobExecutor: JobExecutor | null = null
|
||||||
|
|
||||||
constructor(config?: Partial<SASjsConfig>) {
|
constructor(config?: any) {
|
||||||
|
console.log('from SASjs constructor')
|
||||||
this.sasjsConfig = {
|
this.sasjsConfig = {
|
||||||
...defaultConfig,
|
...defaultConfig,
|
||||||
...config
|
...config
|
||||||
@@ -82,7 +68,7 @@ export default class SASjs {
|
|||||||
userName: string,
|
userName: string,
|
||||||
password: string
|
password: string
|
||||||
) {
|
) {
|
||||||
this.isMethodSupported('executeScriptSAS9', [ServerType.Sas9])
|
this.isMethodSupported('executeScriptSAS9', ServerType.Sas9)
|
||||||
|
|
||||||
return await this.sas9ApiClient?.executeScript(
|
return await this.sas9ApiClient?.executeScript(
|
||||||
linesOfCode,
|
linesOfCode,
|
||||||
@@ -96,7 +82,7 @@ export default class SASjs {
|
|||||||
* @param accessToken - an access token for an authorized user.
|
* @param accessToken - an access token for an authorized user.
|
||||||
*/
|
*/
|
||||||
public async getComputeContexts(accessToken: string) {
|
public async getComputeContexts(accessToken: string) {
|
||||||
this.isMethodSupported('getComputeContexts', [ServerType.SasViya])
|
this.isMethodSupported('getComputeContexts', ServerType.SasViya)
|
||||||
|
|
||||||
return await this.sasViyaApiClient!.getComputeContexts(accessToken)
|
return await this.sasViyaApiClient!.getComputeContexts(accessToken)
|
||||||
}
|
}
|
||||||
@@ -106,7 +92,7 @@ export default class SASjs {
|
|||||||
* @param accessToken - an access token for an authorized user.
|
* @param accessToken - an access token for an authorized user.
|
||||||
*/
|
*/
|
||||||
public async getLauncherContexts(accessToken: string) {
|
public async getLauncherContexts(accessToken: string) {
|
||||||
this.isMethodSupported('getLauncherContexts', [ServerType.SasViya])
|
this.isMethodSupported('getLauncherContexts', ServerType.SasViya)
|
||||||
|
|
||||||
return await this.sasViyaApiClient!.getLauncherContexts(accessToken)
|
return await this.sasViyaApiClient!.getLauncherContexts(accessToken)
|
||||||
}
|
}
|
||||||
@@ -115,7 +101,7 @@ export default class SASjs {
|
|||||||
* Gets default(system) launcher contexts.
|
* Gets default(system) launcher contexts.
|
||||||
*/
|
*/
|
||||||
public getDefaultComputeContexts() {
|
public getDefaultComputeContexts() {
|
||||||
this.isMethodSupported('getDefaultComputeContexts', [ServerType.SasViya])
|
this.isMethodSupported('getDefaultComputeContexts', ServerType.SasViya)
|
||||||
|
|
||||||
return this.sasViyaApiClient!.getDefaultComputeContexts()
|
return this.sasViyaApiClient!.getDefaultComputeContexts()
|
||||||
}
|
}
|
||||||
@@ -125,7 +111,7 @@ export default class SASjs {
|
|||||||
* @param authConfig - an access token, refresh token, client and secret for an authorized user.
|
* @param authConfig - an access token, refresh token, client and secret for an authorized user.
|
||||||
*/
|
*/
|
||||||
public async getExecutableContexts(authConfig: AuthConfig) {
|
public async getExecutableContexts(authConfig: AuthConfig) {
|
||||||
this.isMethodSupported('getExecutableContexts', [ServerType.SasViya])
|
this.isMethodSupported('getExecutableContexts', ServerType.SasViya)
|
||||||
|
|
||||||
return await this.sasViyaApiClient!.getExecutableContexts(authConfig)
|
return await this.sasViyaApiClient!.getExecutableContexts(authConfig)
|
||||||
}
|
}
|
||||||
@@ -147,7 +133,7 @@ export default class SASjs {
|
|||||||
accessToken: string,
|
accessToken: string,
|
||||||
authorizedUsers?: string[]
|
authorizedUsers?: string[]
|
||||||
) {
|
) {
|
||||||
this.isMethodSupported('createComputeContext', [ServerType.SasViya])
|
this.isMethodSupported('createComputeContext', ServerType.SasViya)
|
||||||
|
|
||||||
return await this.sasViyaApiClient!.createComputeContext(
|
return await this.sasViyaApiClient!.createComputeContext(
|
||||||
contextName,
|
contextName,
|
||||||
@@ -172,7 +158,7 @@ export default class SASjs {
|
|||||||
launchType: string,
|
launchType: string,
|
||||||
accessToken: string
|
accessToken: string
|
||||||
) {
|
) {
|
||||||
this.isMethodSupported('createLauncherContext', [ServerType.SasViya])
|
this.isMethodSupported('createLauncherContext', ServerType.SasViya)
|
||||||
|
|
||||||
return await this.sasViyaApiClient!.createLauncherContext(
|
return await this.sasViyaApiClient!.createLauncherContext(
|
||||||
contextName,
|
contextName,
|
||||||
@@ -193,7 +179,7 @@ export default class SASjs {
|
|||||||
editedContext: EditContextInput,
|
editedContext: EditContextInput,
|
||||||
accessToken?: string
|
accessToken?: string
|
||||||
) {
|
) {
|
||||||
this.isMethodSupported('editComputeContext', [ServerType.SasViya])
|
this.isMethodSupported('editComputeContext', ServerType.SasViya)
|
||||||
|
|
||||||
return await this.sasViyaApiClient!.editComputeContext(
|
return await this.sasViyaApiClient!.editComputeContext(
|
||||||
contextName,
|
contextName,
|
||||||
@@ -208,7 +194,7 @@ export default class SASjs {
|
|||||||
* @param accessToken - an access token for an authorized user.
|
* @param accessToken - an access token for an authorized user.
|
||||||
*/
|
*/
|
||||||
public async deleteComputeContext(contextName: string, accessToken?: string) {
|
public async deleteComputeContext(contextName: string, accessToken?: string) {
|
||||||
this.isMethodSupported('deleteComputeContext', [ServerType.SasViya])
|
this.isMethodSupported('deleteComputeContext', ServerType.SasViya)
|
||||||
|
|
||||||
return await this.sasViyaApiClient!.deleteComputeContext(
|
return await this.sasViyaApiClient!.deleteComputeContext(
|
||||||
contextName,
|
contextName,
|
||||||
@@ -226,7 +212,7 @@ export default class SASjs {
|
|||||||
contextName: string,
|
contextName: string,
|
||||||
accessToken?: string
|
accessToken?: string
|
||||||
) {
|
) {
|
||||||
this.isMethodSupported('getComputeContextByName', [ServerType.SasViya])
|
this.isMethodSupported('getComputeContextByName', ServerType.SasViya)
|
||||||
|
|
||||||
return await this.sasViyaApiClient!.getComputeContextByName(
|
return await this.sasViyaApiClient!.getComputeContextByName(
|
||||||
contextName,
|
contextName,
|
||||||
@@ -240,7 +226,7 @@ export default class SASjs {
|
|||||||
* @param accessToken - an access token for an authorized user.
|
* @param accessToken - an access token for an authorized user.
|
||||||
*/
|
*/
|
||||||
public async getComputeContextById(contextId: string, accessToken?: string) {
|
public async getComputeContextById(contextId: string, accessToken?: string) {
|
||||||
this.isMethodSupported('getComputeContextById', [ServerType.SasViya])
|
this.isMethodSupported('getComputeContextById', ServerType.SasViya)
|
||||||
|
|
||||||
return await this.sasViyaApiClient!.getComputeContextById(
|
return await this.sasViyaApiClient!.getComputeContextById(
|
||||||
contextId,
|
contextId,
|
||||||
@@ -249,7 +235,7 @@ export default class SASjs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async createSession(contextName: string, accessToken: string) {
|
public async createSession(contextName: string, accessToken: string) {
|
||||||
this.isMethodSupported('createSession', [ServerType.SasViya])
|
this.isMethodSupported('createSession', ServerType.SasViya)
|
||||||
|
|
||||||
return await this.sasViyaApiClient!.createSession(contextName, accessToken)
|
return await this.sasViyaApiClient!.createSession(contextName, accessToken)
|
||||||
}
|
}
|
||||||
@@ -269,7 +255,7 @@ export default class SASjs {
|
|||||||
authConfig?: AuthConfig,
|
authConfig?: AuthConfig,
|
||||||
debug?: boolean
|
debug?: boolean
|
||||||
) {
|
) {
|
||||||
this.isMethodSupported('executeScriptSASViya', [ServerType.SasViya])
|
this.isMethodSupported('executeScriptSASViya', ServerType.SasViya)
|
||||||
if (!contextName) {
|
if (!contextName) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Context name is undefined. Please set a `contextName` in your SASjs or override config.'
|
'Context name is undefined. Please set a `contextName` in your SASjs or override config.'
|
||||||
@@ -359,7 +345,7 @@ export default class SASjs {
|
|||||||
* @param accessToken - the access token to authorize the request.
|
* @param accessToken - the access token to authorize the request.
|
||||||
*/
|
*/
|
||||||
public async getFolder(folderPath: string, accessToken?: string) {
|
public async getFolder(folderPath: string, accessToken?: string) {
|
||||||
this.isMethodSupported('getFolder', [ServerType.SasViya])
|
this.isMethodSupported('getFolder', ServerType.SasViya)
|
||||||
return await this.sasViyaApiClient!.getFolder(folderPath, accessToken)
|
return await this.sasViyaApiClient!.getFolder(folderPath, accessToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -369,7 +355,7 @@ export default class SASjs {
|
|||||||
* @param accessToken - an access token for authorizing the request.
|
* @param accessToken - an access token for authorizing the request.
|
||||||
*/
|
*/
|
||||||
public async deleteFolder(folderPath: string, accessToken: string) {
|
public async deleteFolder(folderPath: string, accessToken: string) {
|
||||||
this.isMethodSupported('deleteFolder', [ServerType.SasViya])
|
this.isMethodSupported('deleteFolder', ServerType.SasViya)
|
||||||
|
|
||||||
return await this.sasViyaApiClient?.deleteFolder(folderPath, accessToken)
|
return await this.sasViyaApiClient?.deleteFolder(folderPath, accessToken)
|
||||||
}
|
}
|
||||||
@@ -384,7 +370,7 @@ export default class SASjs {
|
|||||||
accessToken?: string,
|
accessToken?: string,
|
||||||
limit?: number
|
limit?: number
|
||||||
) {
|
) {
|
||||||
this.isMethodSupported('listFolder', [ServerType.SasViya])
|
this.isMethodSupported('listFolder', ServerType.SasViya)
|
||||||
|
|
||||||
return await this.sasViyaApiClient?.listFolder(
|
return await this.sasViyaApiClient?.listFolder(
|
||||||
sourceFolder,
|
sourceFolder,
|
||||||
@@ -406,7 +392,7 @@ export default class SASjs {
|
|||||||
targetFolderName: string,
|
targetFolderName: string,
|
||||||
accessToken: string
|
accessToken: string
|
||||||
) {
|
) {
|
||||||
this.isMethodSupported('moveFolder', [ServerType.SasViya])
|
this.isMethodSupported('moveFolder', ServerType.SasViya)
|
||||||
|
|
||||||
return await this.sasViyaApiClient?.moveFolder(
|
return await this.sasViyaApiClient?.moveFolder(
|
||||||
sourceFolder,
|
sourceFolder,
|
||||||
@@ -424,7 +410,7 @@ export default class SASjs {
|
|||||||
accessToken?: string,
|
accessToken?: string,
|
||||||
sasApiClient?: SASViyaApiClient
|
sasApiClient?: SASViyaApiClient
|
||||||
) {
|
) {
|
||||||
this.isMethodSupported('createJobDefinition', [ServerType.SasViya])
|
this.isMethodSupported('createJobDefinition', ServerType.SasViya)
|
||||||
|
|
||||||
if (sasApiClient)
|
if (sasApiClient)
|
||||||
return await sasApiClient!.createJobDefinition(
|
return await sasApiClient!.createJobDefinition(
|
||||||
@@ -444,7 +430,7 @@ export default class SASjs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async getAuthCode(clientId: string) {
|
public async getAuthCode(clientId: string) {
|
||||||
this.isMethodSupported('getAuthCode', [ServerType.SasViya])
|
this.isMethodSupported('getAuthCode', ServerType.SasViya)
|
||||||
|
|
||||||
return await this.sasViyaApiClient!.getAuthCode(clientId)
|
return await this.sasViyaApiClient!.getAuthCode(clientId)
|
||||||
}
|
}
|
||||||
@@ -459,14 +445,8 @@ export default class SASjs {
|
|||||||
clientId: string,
|
clientId: string,
|
||||||
clientSecret: string,
|
clientSecret: string,
|
||||||
authCode: string
|
authCode: string
|
||||||
): Promise<SasAuthResponse | SASjsAuthResponse> {
|
) {
|
||||||
this.isMethodSupported('getAccessToken', [
|
this.isMethodSupported('getAccessToken', ServerType.SasViya)
|
||||||
ServerType.SasViya,
|
|
||||||
ServerType.Sasjs
|
|
||||||
])
|
|
||||||
|
|
||||||
if (this.sasjsConfig.serverType === ServerType.Sasjs)
|
|
||||||
return await this.sasJSApiClient!.getAccessToken(clientId, authCode)
|
|
||||||
|
|
||||||
return await this.sasViyaApiClient!.getAccessToken(
|
return await this.sasViyaApiClient!.getAccessToken(
|
||||||
clientId,
|
clientId,
|
||||||
@@ -475,24 +455,12 @@ export default class SASjs {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Exchanges the refresh token for an access token for the given client.
|
|
||||||
* @param clientId - the client ID to authenticate with.
|
|
||||||
* @param clientSecret - the client secret to authenticate with.
|
|
||||||
* @param refreshToken - the refresh token received from the server.
|
|
||||||
*/
|
|
||||||
public async refreshTokens(
|
public async refreshTokens(
|
||||||
clientId: string,
|
clientId: string,
|
||||||
clientSecret: string,
|
clientSecret: string,
|
||||||
refreshToken: string
|
refreshToken: string
|
||||||
): Promise<SasAuthResponse | SASjsAuthResponse> {
|
) {
|
||||||
this.isMethodSupported('refreshTokens', [
|
this.isMethodSupported('refreshTokens', ServerType.SasViya)
|
||||||
ServerType.SasViya,
|
|
||||||
ServerType.Sasjs
|
|
||||||
])
|
|
||||||
|
|
||||||
if (this.sasjsConfig.serverType === ServerType.Sasjs)
|
|
||||||
return await this.sasJSApiClient!.refreshTokens(refreshToken)
|
|
||||||
|
|
||||||
return await this.sasViyaApiClient!.refreshTokens(
|
return await this.sasViyaApiClient!.refreshTokens(
|
||||||
clientId,
|
clientId,
|
||||||
@@ -502,7 +470,7 @@ export default class SASjs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async deleteClient(clientId: string, accessToken: string) {
|
public async deleteClient(clientId: string, accessToken: string) {
|
||||||
this.isMethodSupported('deleteClient', [ServerType.SasViya])
|
this.isMethodSupported('deleteClient', ServerType.SasViya)
|
||||||
|
|
||||||
return await this.sasViyaApiClient!.deleteClient(clientId, accessToken)
|
return await this.sasViyaApiClient!.deleteClient(clientId, accessToken)
|
||||||
}
|
}
|
||||||
@@ -532,7 +500,7 @@ export default class SASjs {
|
|||||||
...this.sasjsConfig,
|
...this.sasjsConfig,
|
||||||
...config
|
...config
|
||||||
}
|
}
|
||||||
this.setupConfiguration()
|
await this.setupConfiguration()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -558,41 +526,11 @@ export default class SASjs {
|
|||||||
* Logs into the SAS server with the supplied credentials.
|
* Logs into the SAS server with the supplied credentials.
|
||||||
* @param username - a string representing the username.
|
* @param username - a string representing the username.
|
||||||
* @param password - a string representing the password.
|
* @param password - a string representing the password.
|
||||||
* @param clientId - a string representing the client ID.
|
|
||||||
*/
|
*/
|
||||||
public async logIn(
|
public async logIn(username: string, password: string) {
|
||||||
username?: string,
|
|
||||||
password?: string,
|
|
||||||
clientId?: string,
|
|
||||||
options: LoginOptions = {}
|
|
||||||
): Promise<LoginResult> {
|
|
||||||
if (this.sasjsConfig.loginMechanism === LoginMechanism.Default) {
|
|
||||||
if (!username || !password)
|
|
||||||
throw new Error(
|
|
||||||
'A username and password are required when using the default login mechanism.'
|
|
||||||
)
|
|
||||||
|
|
||||||
if (this.sasjsConfig.serverType === ServerType.Sasjs) {
|
|
||||||
if (!clientId)
|
|
||||||
throw new Error(
|
|
||||||
'A username, password and clientId are required when using the default login mechanism with server type SASJS.'
|
|
||||||
)
|
|
||||||
|
|
||||||
return this.authManager!.logInSasjs(username, password, clientId)
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.authManager!.logIn(username, password)
|
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.
|
* Logs out of the configured SAS server.
|
||||||
*/
|
*/
|
||||||
@@ -607,32 +545,24 @@ export default class SASjs {
|
|||||||
* Process). Is prepended at runtime with the value of `appLoc`.
|
* Process). Is prepended at runtime with the value of `appLoc`.
|
||||||
* @param files - array of files to be uploaded, including File object and file name.
|
* @param files - array of files to be uploaded, including File object and file name.
|
||||||
* @param params - request URL parameters.
|
* @param params - request URL parameters.
|
||||||
* @param config - provide any changes to the config here, for instance to
|
* @param overrideSasjsConfig - object to override existing config (optional)
|
||||||
* 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(
|
public uploadFile(
|
||||||
sasJob: string,
|
sasJob: string,
|
||||||
files: UploadFile[],
|
files: UploadFile[],
|
||||||
params: { [key: string]: any } | null,
|
params: any,
|
||||||
config: { [key: string]: any } = {},
|
overrideSasjsConfig?: any
|
||||||
loginRequiredCallback?: () => any
|
|
||||||
) {
|
) {
|
||||||
config = {
|
const fileUploader = overrideSasjsConfig
|
||||||
...this.sasjsConfig,
|
? new FileUploader(
|
||||||
...config
|
{ ...this.sasjsConfig, ...overrideSasjsConfig },
|
||||||
}
|
this.jobsPath,
|
||||||
const data = { files, params }
|
this.requestClient!
|
||||||
|
|
||||||
return await this.fileUploader!.execute(
|
|
||||||
sasJob,
|
|
||||||
data,
|
|
||||||
config,
|
|
||||||
loginRequiredCallback
|
|
||||||
)
|
)
|
||||||
|
: this.fileUploader ||
|
||||||
|
new FileUploader(this.sasjsConfig, this.jobsPath, this.requestClient!)
|
||||||
|
|
||||||
|
return fileUploader.uploadFile(sasJob, files, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -682,6 +612,7 @@ export default class SASjs {
|
|||||||
config.useComputeApi !== null
|
config.useComputeApi !== null
|
||||||
) {
|
) {
|
||||||
if (config.useComputeApi) {
|
if (config.useComputeApi) {
|
||||||
|
console.log(615)
|
||||||
return await this.computeJobExecutor!.execute(
|
return await this.computeJobExecutor!.execute(
|
||||||
sasJob,
|
sasJob,
|
||||||
data,
|
data,
|
||||||
@@ -736,19 +667,15 @@ export default class SASjs {
|
|||||||
msg: string
|
msg: string
|
||||||
} {
|
} {
|
||||||
if (data === null) return { status: true, msg: '' }
|
if (data === null) return { status: true, msg: '' }
|
||||||
|
|
||||||
const isSasFormatsTable = (key: string) =>
|
|
||||||
key.match(/^\$.*/) && Object.keys(data).includes(key.replace(/^\$/, ''))
|
|
||||||
|
|
||||||
for (const key in data) {
|
for (const key in data) {
|
||||||
if (!key.match(/^[a-zA-Z_]/) && !isSasFormatsTable(key)) {
|
if (!key.match(/^[a-zA-Z_]/)) {
|
||||||
return {
|
return {
|
||||||
status: false,
|
status: false,
|
||||||
msg: 'First letter of table should be alphabet or underscore.'
|
msg: 'First letter of table should be alphabet or underscore.'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!key.match(/^[a-zA-Z_][a-zA-Z0-9_]*$/) && !isSasFormatsTable(key)) {
|
if (!key.match(/^[a-zA-Z_][a-zA-Z0-9_]*$/)) {
|
||||||
return { status: false, msg: 'Table name should be alphanumeric.' }
|
return { status: false, msg: 'Table name should be alphanumeric.' }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -759,7 +686,7 @@ export default class SASjs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.getType(data[key]) !== 'Array' && !isSasFormatsTable(key)) {
|
if (this.getType(data[key]) !== 'Array') {
|
||||||
return {
|
return {
|
||||||
status: false,
|
status: false,
|
||||||
msg: 'Parameter data contains invalid table structure.'
|
msg: 'Parameter data contains invalid table structure.'
|
||||||
@@ -775,7 +702,6 @@ export default class SASjs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { status: true, msg: '' }
|
return { status: true, msg: '' }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -811,7 +737,7 @@ export default class SASjs {
|
|||||||
accessToken?: string,
|
accessToken?: string,
|
||||||
isForced = false
|
isForced = false
|
||||||
) {
|
) {
|
||||||
this.isMethodSupported('deployServicePack', [ServerType.SasViya])
|
this.isMethodSupported('deployServicePack', ServerType.SasViya)
|
||||||
|
|
||||||
let sasApiClient: any = null
|
let sasApiClient: any = null
|
||||||
if (serverUrl || appLoc) {
|
if (serverUrl || appLoc) {
|
||||||
@@ -833,7 +759,7 @@ export default class SASjs {
|
|||||||
sasApiClient = new SAS9ApiClient(
|
sasApiClient = new SAS9ApiClient(
|
||||||
serverUrl,
|
serverUrl,
|
||||||
this.jobsPath,
|
this.jobsPath,
|
||||||
this.sasjsConfig.httpsAgentOptions
|
this.sasjsConfig.allowInsecureRequests
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -865,14 +791,6 @@ export default class SASjs {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
public async deployToSASjs(members: [FolderMember, ServiceMember]) {
|
|
||||||
return await this.sasJSApiClient?.deploy(members, this.sasjsConfig.appLoc)
|
|
||||||
}
|
|
||||||
|
|
||||||
public async executeJobSASjs(query: ExecutionQuery) {
|
|
||||||
return await this.sasJSApiClient?.executeJob(query)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Kicks off execution of the given job via the compute API.
|
* Kicks off execution of the given job via the compute API.
|
||||||
* @returns an object representing the compute session created for the given job.
|
* @returns an object representing the compute session created for the given job.
|
||||||
@@ -906,7 +824,7 @@ export default class SASjs {
|
|||||||
...config
|
...config
|
||||||
}
|
}
|
||||||
|
|
||||||
this.isMethodSupported('startComputeJob', [ServerType.SasViya])
|
this.isMethodSupported('startComputeJob', ServerType.SasViya)
|
||||||
if (!config.contextName) {
|
if (!config.contextName) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Context name is undefined. Please set a `contextName` in your SASjs or override config.'
|
'Context name is undefined. Please set a `contextName` in your SASjs or override config.'
|
||||||
@@ -931,7 +849,6 @@ export default class SASjs {
|
|||||||
await this.webJobExecutor?.resendWaitingRequests()
|
await this.webJobExecutor?.resendWaitingRequests()
|
||||||
await this.computeJobExecutor?.resendWaitingRequests()
|
await this.computeJobExecutor?.resendWaitingRequests()
|
||||||
await this.jesJobExecutor?.resendWaitingRequests()
|
await this.jesJobExecutor?.resendWaitingRequests()
|
||||||
await this.fileUploader?.resendWaitingRequests()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -968,8 +885,10 @@ export default class SASjs {
|
|||||||
* @returns SASjsRequest[]
|
* @returns SASjsRequest[]
|
||||||
*/
|
*/
|
||||||
public getSasRequests() {
|
public getSasRequests() {
|
||||||
const requests = [...this.requestClient!.getRequests()]
|
console.log('from getSASRequests')
|
||||||
|
const requests = this.requestClient!.getRequests()
|
||||||
const sortedRequests = requests.sort(compareTimestamps)
|
const sortedRequests = requests.sort(compareTimestamps)
|
||||||
|
console.log('sortedRequests', sortedRequests)
|
||||||
return sortedRequests
|
return sortedRequests
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -997,28 +916,15 @@ export default class SASjs {
|
|||||||
this.sasjsConfig.serverUrl = this.sasjsConfig.serverUrl.slice(0, -1)
|
this.sasjsConfig.serverUrl = this.sasjsConfig.serverUrl.slice(0, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.requestClient) {
|
this.requestClient = new RequestClient(
|
||||||
const RequestClientClass =
|
|
||||||
this.sasjsConfig.serverType === ServerType.Sasjs
|
|
||||||
? SasjsRequestClient
|
|
||||||
: RequestClient
|
|
||||||
this.requestClient = new RequestClientClass(
|
|
||||||
this.sasjsConfig.serverUrl,
|
this.sasjsConfig.serverUrl,
|
||||||
this.sasjsConfig.httpsAgentOptions
|
this.sasjsConfig.allowInsecureRequests
|
||||||
)
|
)
|
||||||
} else {
|
|
||||||
this.requestClient.setConfig(
|
|
||||||
this.sasjsConfig.serverUrl,
|
|
||||||
this.sasjsConfig.httpsAgentOptions
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.jobsPath =
|
this.jobsPath =
|
||||||
this.sasjsConfig.serverType === ServerType.SasViya
|
this.sasjsConfig.serverType === ServerType.SasViya
|
||||||
? this.sasjsConfig.pathSASViya
|
? this.sasjsConfig.pathSASViya
|
||||||
: this.sasjsConfig.serverType === ServerType.Sas9
|
: this.sasjsConfig.pathSAS9
|
||||||
? this.sasjsConfig.pathSAS9
|
|
||||||
: this.sasjsConfig.pathSASJS || ''
|
|
||||||
|
|
||||||
this.authManager = new AuthManager(
|
this.authManager = new AuthManager(
|
||||||
this.sasjsConfig.serverUrl,
|
this.sasjsConfig.serverUrl,
|
||||||
@@ -1028,49 +934,34 @@ export default class SASjs {
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (this.sasjsConfig.serverType === ServerType.SasViya) {
|
if (this.sasjsConfig.serverType === ServerType.SasViya) {
|
||||||
if (this.sasViyaApiClient) {
|
if (this.sasViyaApiClient)
|
||||||
this.sasViyaApiClient!.setConfig(
|
this.sasViyaApiClient!.setConfig(
|
||||||
this.sasjsConfig.serverUrl,
|
this.sasjsConfig.serverUrl,
|
||||||
this.sasjsConfig.appLoc
|
this.sasjsConfig.appLoc
|
||||||
)
|
)
|
||||||
} else {
|
else
|
||||||
this.sasViyaApiClient = new SASViyaApiClient(
|
this.sasViyaApiClient = new SASViyaApiClient(
|
||||||
this.sasjsConfig.serverUrl,
|
this.sasjsConfig.serverUrl,
|
||||||
this.sasjsConfig.appLoc,
|
this.sasjsConfig.appLoc,
|
||||||
this.sasjsConfig.contextName,
|
this.sasjsConfig.contextName,
|
||||||
this.requestClient
|
this.requestClient
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
this.sasViyaApiClient.debug = this.sasjsConfig.debug
|
this.sasViyaApiClient.debug = this.sasjsConfig.debug
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.sasjsConfig.serverType === ServerType.Sas9) {
|
if (this.sasjsConfig.serverType === ServerType.Sas9) {
|
||||||
if (this.sas9ApiClient) {
|
if (this.sas9ApiClient)
|
||||||
this.sas9ApiClient!.setConfig(this.sasjsConfig.serverUrl)
|
this.sas9ApiClient!.setConfig(this.sasjsConfig.serverUrl)
|
||||||
} else {
|
else
|
||||||
this.sas9ApiClient = new SAS9ApiClient(
|
this.sas9ApiClient = new SAS9ApiClient(
|
||||||
this.sasjsConfig.serverUrl,
|
this.sasjsConfig.serverUrl,
|
||||||
this.jobsPath,
|
this.jobsPath,
|
||||||
this.sasjsConfig.httpsAgentOptions
|
this.sasjsConfig.allowInsecureRequests
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (this.sasjsConfig.serverType === ServerType.Sasjs) {
|
|
||||||
if (this.sasJSApiClient) {
|
|
||||||
this.sasJSApiClient.setConfig(this.sasjsConfig.serverUrl)
|
|
||||||
} else {
|
|
||||||
this.sasJSApiClient = new SASjsApiClient(
|
|
||||||
this.sasjsConfig.serverUrl,
|
|
||||||
this.requestClient
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.fileUploader = new FileUploader(
|
this.fileUploader = new FileUploader(
|
||||||
this.sasjsConfig.serverUrl,
|
this.sasjsConfig,
|
||||||
this.sasjsConfig.serverType!,
|
|
||||||
this.jobsPath,
|
this.jobsPath,
|
||||||
this.requestClient
|
this.requestClient
|
||||||
)
|
)
|
||||||
@@ -1087,8 +978,7 @@ export default class SASjs {
|
|||||||
this.sasjsConfig.serverUrl,
|
this.sasjsConfig.serverUrl,
|
||||||
this.sasjsConfig.serverType!,
|
this.sasjsConfig.serverType!,
|
||||||
this.jobsPath,
|
this.jobsPath,
|
||||||
this.requestClient,
|
this.sasjsConfig.allowInsecureRequests
|
||||||
this.sasjsConfig.httpsAgentOptions
|
|
||||||
)
|
)
|
||||||
|
|
||||||
this.computeJobExecutor = new ComputeJobExecutor(
|
this.computeJobExecutor = new ComputeJobExecutor(
|
||||||
@@ -1155,15 +1045,12 @@ export default class SASjs {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private isMethodSupported(method: string, serverTypes: ServerType[]) {
|
private isMethodSupported(method: string, serverType: string) {
|
||||||
if (
|
if (this.sasjsConfig.serverType !== serverType) {
|
||||||
!this.sasjsConfig.serverType ||
|
|
||||||
!serverTypes.includes(this.sasjsConfig.serverType)
|
|
||||||
) {
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Method '${method}' is only supported on ${serverTypes.join(
|
`Method '${method}' is only supported on ${
|
||||||
', '
|
serverType === ServerType.Sas9 ? 'SAS9' : 'SAS Viya'
|
||||||
)} servers.`
|
} servers.`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,82 +0,0 @@
|
|||||||
import { FolderMember, ServiceMember, ExecutionQuery } from './types'
|
|
||||||
import { RequestClient } from './request/RequestClient'
|
|
||||||
import { getAccessTokenForSasjs } from './auth/getAccessTokenForSasjs'
|
|
||||||
import { refreshTokensForSasjs } from './auth/refreshTokensForSasjs'
|
|
||||||
import { getAuthCodeForSasjs } from './auth/getAuthCodeForSasjs'
|
|
||||||
|
|
||||||
export class SASjsApiClient {
|
|
||||||
constructor(
|
|
||||||
private serverUrl: string,
|
|
||||||
private requestClient: RequestClient
|
|
||||||
) {}
|
|
||||||
|
|
||||||
public setConfig(serverUrl: string) {
|
|
||||||
if (serverUrl) this.serverUrl = serverUrl
|
|
||||||
}
|
|
||||||
|
|
||||||
public async deploy(members: [FolderMember, ServiceMember], appLoc: string) {
|
|
||||||
const { result } = await this.requestClient.post<{
|
|
||||||
status: string
|
|
||||||
message: string
|
|
||||||
example?: {}
|
|
||||||
}>(
|
|
||||||
'SASjsApi/drive/deploy',
|
|
||||||
{ fileTree: members, appLoc: appLoc },
|
|
||||||
undefined
|
|
||||||
)
|
|
||||||
|
|
||||||
return Promise.resolve(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
public async executeJob(query: ExecutionQuery) {
|
|
||||||
const { result } = await this.requestClient.post<{
|
|
||||||
status: string
|
|
||||||
message: string
|
|
||||||
log?: string
|
|
||||||
logPath?: string
|
|
||||||
error?: {}
|
|
||||||
}>('SASjsApi/stp/execute', query, undefined)
|
|
||||||
|
|
||||||
return Promise.resolve(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Exchanges the auth code for an access token for the given client.
|
|
||||||
* @param clientId - the client ID to authenticate with.
|
|
||||||
* @param authCode - the auth code received from the server.
|
|
||||||
*/
|
|
||||||
public async getAccessToken(
|
|
||||||
clientId: string,
|
|
||||||
authCode: string
|
|
||||||
): Promise<SASjsAuthResponse> {
|
|
||||||
return getAccessTokenForSasjs(this.requestClient, clientId, authCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Exchanges the refresh token for an access token.
|
|
||||||
* @param refreshToken - the refresh token received from the server.
|
|
||||||
*/
|
|
||||||
public async refreshTokens(refreshToken: string): Promise<SASjsAuthResponse> {
|
|
||||||
return refreshTokensForSasjs(this.requestClient, refreshToken)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Performs a login authenticate and returns an auth code for the given client.
|
|
||||||
* @param username - a string representing the username.
|
|
||||||
* @param password - a string representing the password.
|
|
||||||
* @param clientId - the client ID to authenticate with.
|
|
||||||
*/
|
|
||||||
public async getAuthCode(
|
|
||||||
username: string,
|
|
||||||
password: string,
|
|
||||||
clientId: string
|
|
||||||
) {
|
|
||||||
return getAuthCodeForSasjs(this.requestClient, username, password, clientId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo move to sasjs/utils
|
|
||||||
export interface SASjsAuthResponse {
|
|
||||||
access_token: string
|
|
||||||
refresh_token: string
|
|
||||||
}
|
|
||||||
@@ -168,7 +168,7 @@ export class SessionManager {
|
|||||||
) {
|
) {
|
||||||
if (stateLink) {
|
if (stateLink) {
|
||||||
if (this.debug && !this.printedSessionState.printed) {
|
if (this.debug && !this.printedSessionState.printed) {
|
||||||
logger.info(`Polling: ${this.serverUrl + stateLink.href}`)
|
logger.info('Polling session status...')
|
||||||
|
|
||||||
this.printedSessionState.printed = true
|
this.printedSessionState.printed = true
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user