mirror of
https://github.com/sasjs/adapter.git
synced 2025-12-11 09:24:35 +00:00
Compare commits
66 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8856c3b6ec | ||
|
|
b8ea618f1e | ||
|
|
10c72e6483 | ||
|
|
9d03b54fba | ||
|
|
38ad9abfbc | ||
| 29a65052dc | |||
| 63328163ab | |||
|
|
aebf4ea8d8 | ||
|
|
59e3edf4b3 | ||
| 8f309143e9 | |||
| a65d4257a5 | |||
| 5d2f1d306a | |||
| f7bd63ee7f | |||
| a4cd320272 | |||
| c243f25477 | |||
|
|
72ed5e3fab | ||
|
|
6bfd7024ce | ||
| fdc3e1cce8 | |||
| fc47222830 | |||
|
|
0a5de21386 | ||
|
|
1cbe57d512 | ||
| 936e4f8c0a | |||
|
|
4ebf949912 | ||
| c00c8007e5 | |||
| 54516665bf | |||
| ecec2e77c0 | |||
|
|
102898ac33 | ||
| 7370a2be4c | |||
| 135d019026 | |||
| e2651344d7 | |||
| 9bf3885868 | |||
| caa5aa47dc | |||
|
|
7a42bc1b88 | ||
|
|
6c02ee4cd6 | ||
| 73ee214b61 | |||
|
|
77487bfa35 | ||
|
|
9cf0165cf7 | ||
|
|
e4d4b3142f | ||
|
|
a87be39b44 | ||
|
|
8ea621ac98 | ||
| ea61119919 | |||
|
|
01235616a0 | ||
|
|
10051cb7d1 | ||
|
|
7b0ad2d60d | ||
|
|
fc0a450e94 | ||
| 6cab245cde | |||
| ed90cd8036 | |||
|
|
784bab4522 | ||
|
|
ee97e8211e | ||
| b0eb8b07a8 | |||
| 1d1ef7179e | |||
| d0eb1a7bfb | |||
|
|
256e4ef314 | ||
|
|
6a6dfc5e9d | ||
|
|
5140848039 | ||
|
|
31baf01d3e | ||
|
|
804e78cf0c | ||
| f6a621fe46 | |||
| c47d0c9789 | |||
|
|
1ddc71b017 | ||
|
|
3e507885ab | ||
| e92d0d73b5 | |||
| 00a99e752c | |||
|
|
b13f3d2fcb | ||
|
|
495e4b9069 | ||
|
|
2e843e3f36 |
12
.git-hooks/pre-commit
Executable file
12
.git-hooks/pre-commit
Executable file
@@ -0,0 +1,12 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Using `--silent` helps for showing any errs in the first line of the response
|
||||
# The first line is picked up by the VS Code GIT UI popup when rc is not 0
|
||||
|
||||
if npm run --silent lint:silent ; then
|
||||
exit 0
|
||||
else
|
||||
npm run --silent lint:fix
|
||||
echo "❌ Prettier check failed! We ran lint:fix for you. Please add & commit again."
|
||||
exit 1
|
||||
fi
|
||||
1
docs/CNAME
Normal file
1
docs/CNAME
Normal file
@@ -0,0 +1 @@
|
||||
adapter.sasjs.io
|
||||
14
package-lock.json
generated
14
package-lock.json
generated
@@ -8,7 +8,7 @@
|
||||
"hasInstallScript": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@sasjs/utils": "2.36.1",
|
||||
"@sasjs/utils": "2.44.0",
|
||||
"axios": "0.26.0",
|
||||
"axios-cookiejar-support": "1.0.1",
|
||||
"form-data": "4.0.0",
|
||||
@@ -1142,9 +1142,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@sasjs/utils": {
|
||||
"version": "2.36.1",
|
||||
"resolved": "https://registry.npmjs.org/@sasjs/utils/-/utils-2.36.1.tgz",
|
||||
"integrity": "sha512-JkGUpLOODsvkeU+S25jb9k2WnvzyD2w6cEk7YyQ/byuqKL8xawH91PPWegrVcJlDY8WmqKE4CPcA3d1mM3B3LA==",
|
||||
"version": "2.44.0",
|
||||
"resolved": "https://registry.npmjs.org/@sasjs/utils/-/utils-2.44.0.tgz",
|
||||
"integrity": "sha512-hpC4erHYA8Mcb38mzxFEP0cXehfa0iKeqSW2d9MmxZ9g2qpy0BU6xyZJohN9kOiafXo5H359ndNKsg4DOq5YgA==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@types/fs-extra": "9.0.13",
|
||||
@@ -13870,9 +13870,9 @@
|
||||
}
|
||||
},
|
||||
"@sasjs/utils": {
|
||||
"version": "2.36.1",
|
||||
"resolved": "https://registry.npmjs.org/@sasjs/utils/-/utils-2.36.1.tgz",
|
||||
"integrity": "sha512-JkGUpLOODsvkeU+S25jb9k2WnvzyD2w6cEk7YyQ/byuqKL8xawH91PPWegrVcJlDY8WmqKE4CPcA3d1mM3B3LA==",
|
||||
"version": "2.44.0",
|
||||
"resolved": "https://registry.npmjs.org/@sasjs/utils/-/utils-2.44.0.tgz",
|
||||
"integrity": "sha512-hpC4erHYA8Mcb38mzxFEP0cXehfa0iKeqSW2d9MmxZ9g2qpy0BU6xyZJohN9kOiafXo5H359ndNKsg4DOq5YgA==",
|
||||
"requires": {
|
||||
"@types/fs-extra": "9.0.13",
|
||||
"@types/prompts": "2.0.13",
|
||||
|
||||
@@ -8,8 +8,9 @@
|
||||
"build": "rimraf build && rimraf node && mkdir node && copyfiles -u 1 \"./src/**/*\" ./node && webpack && rimraf build/src && rimraf node",
|
||||
"package:lib": "npm run build && copyfiles ./package.json ./checkNodeVersion.js build && cd build && npm version \"5.0.0\" && npm pack",
|
||||
"publish:lib": "npm run build && cd build && npm publish",
|
||||
"lint:fix": "npx prettier --write \"src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}\" && npx prettier --write \"sasjs-tests/src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}\"",
|
||||
"lint:fix": "npx prettier --loglevel silent --write \"src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}\" && npx prettier --loglevel silent --write \"sasjs-tests/src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}\"",
|
||||
"lint": "npx prettier --check \"src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}\" && npx prettier --check \"sasjs-tests/src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}\"",
|
||||
"lint:silent": "npx prettier --loglevel silent --check \"src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}\" && npx prettier --loglevel silent --check \"sasjs-tests/src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}\"",
|
||||
"test": "jest --silent --coverage",
|
||||
"prepublishOnly": "cp -r ./build/* . && rm -rf ./build",
|
||||
"postpublish": "git clean -fd",
|
||||
@@ -72,7 +73,7 @@
|
||||
},
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"@sasjs/utils": "2.36.1",
|
||||
"@sasjs/utils": "2.44.0",
|
||||
"axios": "0.26.0",
|
||||
"axios-cookiejar-support": "1.0.1",
|
||||
"form-data": "4.0.0",
|
||||
|
||||
@@ -65,7 +65,7 @@ The below services need to be created on your SAS server, at the location specif
|
||||
```sas
|
||||
filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
|
||||
%inc mc;
|
||||
filename ft15f001 temp;
|
||||
filename ft15f001 temp lrecl=1000;
|
||||
parmcards4;
|
||||
%webout(FETCH)
|
||||
%webout(OPEN)
|
||||
@@ -84,6 +84,15 @@ parmcards4;
|
||||
%webout(CLOSE)
|
||||
;;;;
|
||||
%mm_createwebservice(path=/Public/app/common,name=sendArr)
|
||||
parmcards4;
|
||||
data work.macvars;
|
||||
set sashelp.vmacro;
|
||||
run;
|
||||
%webout(OPEN)
|
||||
%webout(OBJ,macvars)
|
||||
%webout(CLOSE)
|
||||
;;;;
|
||||
%mm_createwebservice(path=/Public/app/common,name=sendMacVars)
|
||||
parmcards4;
|
||||
let he who hath understanding, reckon the number of the beast
|
||||
;;;;
|
||||
@@ -104,7 +113,7 @@ data _null_;
|
||||
```sas
|
||||
filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
|
||||
%inc mc;
|
||||
filename ft15f001 temp;
|
||||
filename ft15f001 temp lrecl=1000;
|
||||
parmcards4;
|
||||
%webout(FETCH)
|
||||
%webout(OPEN)
|
||||
@@ -118,7 +127,6 @@ parmcards4;
|
||||
%webout(CLOSE)
|
||||
;;;;
|
||||
%mp_createwebservice(path=/Public/app/common,name=sendObj)
|
||||
filename ft15f001 temp;
|
||||
parmcards4;
|
||||
%webout(FETCH)
|
||||
%webout(OPEN)
|
||||
@@ -132,7 +140,15 @@ parmcards4;
|
||||
%webout(CLOSE)
|
||||
;;;;
|
||||
%mp_createwebservice(path=/Public/app/common,name=sendArr)
|
||||
filename ft15f001 temp;
|
||||
parmcards4;
|
||||
data work.macvars;
|
||||
set sashelp.vmacro;
|
||||
run;
|
||||
%webout(OPEN)
|
||||
%webout(OBJ,macvars)
|
||||
%webout(CLOSE)
|
||||
;;;;
|
||||
%mp_createwebservice(path=/Public/app/common,name=sendMacVars)
|
||||
parmcards4;
|
||||
If you can keep your head when all about you
|
||||
Are losing theirs and blaming it on you,
|
||||
|
||||
20
sasjs-tests/package-lock.json
generated
20
sasjs-tests/package-lock.json
generated
@@ -2388,11 +2388,11 @@
|
||||
"node_modules/@sasjs/adapter": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "file:../build/sasjs-adapter-5.0.0.tgz",
|
||||
"integrity": "sha512-lbDWueAEnfNlu4OGrc9hBEzT0aoLfAy7eLd2nLHArrF6zukcSGBNhUgOqxIhlz4WeBdf1gt3nk1G7p5X1mrWYQ==",
|
||||
"integrity": "sha512-5qtEs9yFuZ4v2UrFGNHeCIr/yZTp7D9He+e+N333qW9mdLJJ8fzRifuur/rFE6bNPqC2bdCjicYkO/yrHR4LQw==",
|
||||
"hasInstallScript": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@sasjs/utils": "2.36.1",
|
||||
"@sasjs/utils": "2.40.1",
|
||||
"axios": "0.26.0",
|
||||
"axios-cookiejar-support": "1.0.1",
|
||||
"form-data": "4.0.0",
|
||||
@@ -2422,9 +2422,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@sasjs/utils": {
|
||||
"version": "2.36.1",
|
||||
"resolved": "https://registry.npmjs.org/@sasjs/utils/-/utils-2.36.1.tgz",
|
||||
"integrity": "sha512-JkGUpLOODsvkeU+S25jb9k2WnvzyD2w6cEk7YyQ/byuqKL8xawH91PPWegrVcJlDY8WmqKE4CPcA3d1mM3B3LA==",
|
||||
"version": "2.40.1",
|
||||
"resolved": "https://registry.npmjs.org/@sasjs/utils/-/utils-2.40.1.tgz",
|
||||
"integrity": "sha512-wWYElDH71bSZTdZ5V38743vAnw2EPDhzH7+1s7zxINHpaQWK/qrDldI0vgVFLeGpxVU0D7WPZ/ltG6MoE2obeg==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@types/fs-extra": "9.0.13",
|
||||
@@ -20998,9 +20998,9 @@
|
||||
},
|
||||
"@sasjs/adapter": {
|
||||
"version": "file:../build/sasjs-adapter-5.0.0.tgz",
|
||||
"integrity": "sha512-lbDWueAEnfNlu4OGrc9hBEzT0aoLfAy7eLd2nLHArrF6zukcSGBNhUgOqxIhlz4WeBdf1gt3nk1G7p5X1mrWYQ==",
|
||||
"integrity": "sha512-5qtEs9yFuZ4v2UrFGNHeCIr/yZTp7D9He+e+N333qW9mdLJJ8fzRifuur/rFE6bNPqC2bdCjicYkO/yrHR4LQw==",
|
||||
"requires": {
|
||||
"@sasjs/utils": "2.36.1",
|
||||
"@sasjs/utils": "2.40.1",
|
||||
"axios": "0.26.0",
|
||||
"axios-cookiejar-support": "1.0.1",
|
||||
"form-data": "4.0.0",
|
||||
@@ -21022,9 +21022,9 @@
|
||||
}
|
||||
},
|
||||
"@sasjs/utils": {
|
||||
"version": "2.36.1",
|
||||
"resolved": "https://registry.npmjs.org/@sasjs/utils/-/utils-2.36.1.tgz",
|
||||
"integrity": "sha512-JkGUpLOODsvkeU+S25jb9k2WnvzyD2w6cEk7YyQ/byuqKL8xawH91PPWegrVcJlDY8WmqKE4CPcA3d1mM3B3LA==",
|
||||
"version": "2.40.1",
|
||||
"resolved": "https://registry.npmjs.org/@sasjs/utils/-/utils-2.40.1.tgz",
|
||||
"integrity": "sha512-wWYElDH71bSZTdZ5V38743vAnw2EPDhzH7+1s7zxINHpaQWK/qrDldI0vgVFLeGpxVU0D7WPZ/ltG6MoE2obeg==",
|
||||
"requires": {
|
||||
"@types/fs-extra": "9.0.13",
|
||||
"@types/prompts": "2.0.13",
|
||||
|
||||
@@ -6,6 +6,7 @@ import { specialCaseTests } from './testSuites/SpecialCases'
|
||||
import { sasjsRequestTests } from './testSuites/SasjsRequests'
|
||||
import '@sasjs/test-framework/dist/index.css'
|
||||
import { computeTests } from './testSuites/Compute'
|
||||
import { fileUploadTests } from './testSuites/FileUpload'
|
||||
|
||||
const App = (): ReactElement<{}> => {
|
||||
const { adapter, config } = useContext(AppContext)
|
||||
@@ -18,7 +19,8 @@ const App = (): ReactElement<{}> => {
|
||||
sendArrTests(adapter),
|
||||
sendObjTests(adapter),
|
||||
specialCaseTests(adapter),
|
||||
sasjsRequestTests(adapter)
|
||||
sasjsRequestTests(adapter),
|
||||
fileUploadTests(adapter)
|
||||
]
|
||||
|
||||
if (adapter.getSasjsConfig().serverType === 'SASVIYA') {
|
||||
|
||||
35
sasjs-tests/src/testSuites/FileUpload.ts
Normal file
35
sasjs-tests/src/testSuites/FileUpload.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import SASjs from '@sasjs/adapter'
|
||||
import { TestSuite } from '@sasjs/test-framework'
|
||||
|
||||
export const fileUploadTests = (adapter: SASjs): TestSuite => ({
|
||||
name: 'File Upload Tests',
|
||||
tests: [
|
||||
{
|
||||
title: 'Upload File',
|
||||
description: 'Should upload the file to VIYA',
|
||||
test: async () => {
|
||||
let blob: any = new Blob(['test'], { type: 'text/html' })
|
||||
blob['lastModifiedDate'] = ''
|
||||
blob['name'] = 'macvars_testfile'
|
||||
let file = blob
|
||||
|
||||
const filesToUpload = [
|
||||
{
|
||||
file: file,
|
||||
fileName: file.name
|
||||
}
|
||||
]
|
||||
|
||||
return adapter.uploadFile('common/sendMacVars', filesToUpload, null)
|
||||
},
|
||||
assertion: (response: any) =>
|
||||
(response.macvars as any[]).findIndex(
|
||||
(el: any) => el.NAME === '_WEBIN_FILE_COUNT' && el.VALUE === '1'
|
||||
) > -1 &&
|
||||
(response.macvars as any[]).findIndex(
|
||||
(el: any) =>
|
||||
el.NAME === '_WEBIN_FILENAME' && el.VALUE === 'macvars_testfile'
|
||||
) > -1
|
||||
}
|
||||
]
|
||||
})
|
||||
@@ -218,6 +218,7 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
|
||||
const invalidData: any = {
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx: [{ col1: 42 }]
|
||||
}
|
||||
|
||||
return adapter.request('common/sendObj', invalidData).catch((e) => e)
|
||||
},
|
||||
assertion: (error: any) =>
|
||||
|
||||
@@ -11,7 +11,11 @@ import {
|
||||
JobDefinition,
|
||||
PollOptions
|
||||
} from './types'
|
||||
import { JobExecutionError, RootFolderNotFoundError } from './types/errors'
|
||||
import {
|
||||
CertificateError,
|
||||
JobExecutionError,
|
||||
RootFolderNotFoundError
|
||||
} from './types/errors'
|
||||
import { SessionManager } from './SessionManager'
|
||||
import { ContextManager } from './ContextManager'
|
||||
import { SasAuthResponse, MacroVar, AuthConfig } from '@sasjs/utils/types'
|
||||
@@ -878,7 +882,8 @@ export class SASViyaApiClient {
|
||||
|
||||
const { result: folder } = await this.requestClient
|
||||
.get<Folder>(`${this.serverUrl}${url}`, accessToken)
|
||||
.catch(() => {
|
||||
.catch((err) => {
|
||||
if (err instanceof CertificateError) throw err
|
||||
return { result: null }
|
||||
})
|
||||
|
||||
@@ -899,7 +904,8 @@ export class SASViyaApiClient {
|
||||
|
||||
const { result: folder } = await this.requestClient
|
||||
.get<Folder>(`${this.serverUrl}${url}`, accessToken)
|
||||
.catch(() => {
|
||||
.catch((err) => {
|
||||
if (err instanceof CertificateError) throw err
|
||||
return { result: null }
|
||||
})
|
||||
|
||||
|
||||
80
src/SASjs.ts
80
src/SASjs.ts
@@ -5,8 +5,7 @@ import {
|
||||
EditContextInput,
|
||||
PollOptions,
|
||||
LoginMechanism,
|
||||
ExecutionQuery,
|
||||
FileTree
|
||||
ExecutionQuery
|
||||
} from './types'
|
||||
import { SASViyaApiClient } from './SASViyaApiClient'
|
||||
import { SAS9ApiClient } from './SAS9ApiClient'
|
||||
@@ -17,7 +16,8 @@ import {
|
||||
MacroVar,
|
||||
AuthConfig,
|
||||
ExtraResponseAttributes,
|
||||
SasAuthResponse
|
||||
SasAuthResponse,
|
||||
ServicePackSASjs
|
||||
} from '@sasjs/utils/types'
|
||||
import { RequestClient } from './request/RequestClient'
|
||||
import { SasjsRequestClient } from './request/SasjsRequestClient'
|
||||
@@ -77,7 +77,7 @@ export default class SASjs {
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes code against a SAS 9 server. Requires a runner to be present in
|
||||
* Executes SAS code on a SAS 9 server. Requires a runner to be present in
|
||||
* the users home directory in metadata.
|
||||
* @param linesOfCode - lines of sas code from the file to run.
|
||||
* @param username - a string representing the username.
|
||||
@@ -97,6 +97,17 @@ export default class SASjs {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes SAS code on a SASJS server
|
||||
* @param code - a string of code from the file to run.
|
||||
* @param authConfig - (optional) a valid client, secret, refresh and access tokens that are authorised to execute scripts.
|
||||
*/
|
||||
public async executeScriptSASjs(code: string, authConfig?: AuthConfig) {
|
||||
this.isMethodSupported('executeScriptSASJS', [ServerType.Sasjs])
|
||||
|
||||
return await this.sasJSApiClient?.executeScript(code, authConfig)
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes sas code in a SAS Viya compute session.
|
||||
* @param fileName - name of the file to run. It will be converted to path to the file being submitted for execution.
|
||||
@@ -134,7 +145,7 @@ export default class SASjs {
|
||||
|
||||
/**
|
||||
* Gets compute contexts.
|
||||
* @param accessToken - an access token for an authorized user.
|
||||
* @param accessToken - an access token for an authorised user.
|
||||
*/
|
||||
public async getComputeContexts(accessToken: string) {
|
||||
this.isMethodSupported('getComputeContexts', [ServerType.SasViya])
|
||||
@@ -144,7 +155,7 @@ export default class SASjs {
|
||||
|
||||
/**
|
||||
* Gets launcher contexts.
|
||||
* @param accessToken - an access token for an authorized user.
|
||||
* @param accessToken - an access token for an authorised user.
|
||||
*/
|
||||
public async getLauncherContexts(accessToken: string) {
|
||||
this.isMethodSupported('getLauncherContexts', [ServerType.SasViya])
|
||||
@@ -163,7 +174,7 @@ export default class SASjs {
|
||||
|
||||
/**
|
||||
* Gets executable compute contexts.
|
||||
* @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 authorised user.
|
||||
*/
|
||||
public async getExecutableContexts(authConfig: AuthConfig) {
|
||||
this.isMethodSupported('getExecutableContexts', [ServerType.SasViya])
|
||||
@@ -177,8 +188,8 @@ export default class SASjs {
|
||||
* @param launchContextName - the name of the launcher context used by the compute service.
|
||||
* @param sharedAccountId - the ID of the account to run the servers for this context as.
|
||||
* @param autoExecLines - the lines of code to execute during session initialization.
|
||||
* @param accessToken - an access token for an authorized user.
|
||||
* @param authorizedUsers - an optional list of authorized user IDs.
|
||||
* @param accessToken - an access token for an authorised user.
|
||||
* @param authorisedUsers - an optional list of authorised user IDs.
|
||||
*/
|
||||
public async createComputeContext(
|
||||
contextName: string,
|
||||
@@ -186,7 +197,7 @@ export default class SASjs {
|
||||
sharedAccountId: string,
|
||||
autoExecLines: string[],
|
||||
accessToken: string,
|
||||
authorizedUsers?: string[]
|
||||
authorisedUsers?: string[]
|
||||
) {
|
||||
this.isMethodSupported('createComputeContext', [ServerType.SasViya])
|
||||
|
||||
@@ -196,7 +207,7 @@ export default class SASjs {
|
||||
sharedAccountId,
|
||||
autoExecLines,
|
||||
accessToken,
|
||||
authorizedUsers
|
||||
authorisedUsers
|
||||
)
|
||||
}
|
||||
|
||||
@@ -205,7 +216,7 @@ export default class SASjs {
|
||||
* @param contextName - the name of the context to be created.
|
||||
* @param description - the description of the context to be created.
|
||||
* @param launchType - launch type of the context to be created.
|
||||
* @param accessToken - an access token for an authorized user.
|
||||
* @param accessToken - an access token for an authorised user.
|
||||
*/
|
||||
public async createLauncherContext(
|
||||
contextName: string,
|
||||
@@ -227,7 +238,7 @@ export default class SASjs {
|
||||
* Updates a compute context on the given server.
|
||||
* @param contextName - the original name of the context to be deleted.
|
||||
* @param editedContext - an object with the properties to be updated.
|
||||
* @param accessToken - an access token for an authorized user.
|
||||
* @param accessToken - an access token for an authorised user.
|
||||
*/
|
||||
public async editComputeContext(
|
||||
contextName: string,
|
||||
@@ -246,7 +257,7 @@ export default class SASjs {
|
||||
/**
|
||||
* Deletes a compute context on the given server.
|
||||
* @param contextName - the name of the context to be deleted.
|
||||
* @param accessToken - an access token for an authorized user.
|
||||
* @param accessToken - an access token for an authorised user.
|
||||
*/
|
||||
public async deleteComputeContext(contextName: string, accessToken?: string) {
|
||||
this.isMethodSupported('deleteComputeContext', [ServerType.SasViya])
|
||||
@@ -261,7 +272,7 @@ export default class SASjs {
|
||||
* Returns a JSON representation of a compute context.
|
||||
* @example: { "createdBy": "admin", "links": [...], "id": "ID", "version": 2, "name": "context1" }
|
||||
* @param contextName - the name of the context to return.
|
||||
* @param accessToken - an access token for an authorized user.
|
||||
* @param accessToken - an access token for an authorised user.
|
||||
*/
|
||||
public async getComputeContextByName(
|
||||
contextName: string,
|
||||
@@ -278,7 +289,7 @@ export default class SASjs {
|
||||
/**
|
||||
* Returns a JSON representation of a compute context.
|
||||
* @param contextId - an id of the context to return.
|
||||
* @param accessToken - an access token for an authorized user.
|
||||
* @param accessToken - an access token for an authorised user.
|
||||
*/
|
||||
public async getComputeContextById(contextId: string, accessToken?: string) {
|
||||
this.isMethodSupported('getComputeContextById', [ServerType.SasViya])
|
||||
@@ -581,15 +592,6 @@ export default class SASjs {
|
||||
'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)
|
||||
}
|
||||
|
||||
@@ -888,24 +890,25 @@ export default class SASjs {
|
||||
|
||||
/**
|
||||
* Creates the folders and services at the given location `appLoc` on the given server `serverUrl`.
|
||||
* @param members - the JSON specifying the folders and services to be created.
|
||||
* @param appLoc - the base folder in which to create the new folders and
|
||||
* services. If not provided, is taken from SASjsConfig.
|
||||
* @param authConfig - a valid client, secret, refresh and access tokens that are authorised to execute compute jobs.
|
||||
* @param dataJson - the JSON specifying the folders and files to be created, can also includes
|
||||
* appLoc, streamServiceName, streamWebFolder, streamLogo
|
||||
* @param appLoc - (optional) the base folder in which to create the new folders and
|
||||
* services. If not provided, is taken from SASjsConfig. Precedence will be of appLoc present in dataJson.
|
||||
* @param authConfig - (optional) a valid client, secret, refresh and access tokens that are authorised to execute compute jobs.
|
||||
*/
|
||||
public async deployToSASjs(
|
||||
members: FileTree,
|
||||
dataJson: ServicePackSASjs,
|
||||
appLoc?: string,
|
||||
authConfig?: AuthConfig
|
||||
) {
|
||||
if (!appLoc) {
|
||||
appLoc = this.sasjsConfig.appLoc
|
||||
}
|
||||
return await this.sasJSApiClient?.deploy(members, appLoc, authConfig)
|
||||
return await this.sasJSApiClient?.deploy(dataJson, appLoc, authConfig)
|
||||
}
|
||||
|
||||
public async executeJobSASjs(query: ExecutionQuery) {
|
||||
return await this.sasJSApiClient?.executeJob(query)
|
||||
public async executeJobSASjs(query: ExecutionQuery, authConfig?: AuthConfig) {
|
||||
return await this.sasJSApiClient?.executeJob(query, authConfig)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -972,7 +975,7 @@ export default class SASjs {
|
||||
/**
|
||||
* Fetches content of the log file
|
||||
* @param logUrl - url of the log file.
|
||||
* @param accessToken - an access token for an authorized user.
|
||||
* @param accessToken - an access token for an authorised user.
|
||||
*/
|
||||
public async fetchLogFileContent(logUrl: string, accessToken?: string) {
|
||||
return await this.requestClient!.get(logUrl, accessToken).then((res) => {
|
||||
@@ -1094,13 +1097,8 @@ export default class SASjs {
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
if (!this.sasJSApiClient) {
|
||||
this.sasJSApiClient = new SASjsApiClient(this.requestClient)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,24 +1,16 @@
|
||||
import { AuthConfig, ServerType } from '@sasjs/utils/types'
|
||||
import { FileTree, ExecutionQuery } from './types'
|
||||
import { AuthConfig, ServerType, ServicePackSASjs } from '@sasjs/utils/types'
|
||||
import { ExecutionQuery } from './types'
|
||||
import { RequestClient } from './request/RequestClient'
|
||||
import { getAccessTokenForSasjs } from './auth/getAccessTokenForSasjs'
|
||||
import { refreshTokensForSasjs } from './auth/refreshTokensForSasjs'
|
||||
import { getAuthCodeForSasjs } from './auth/getAuthCodeForSasjs'
|
||||
import { parseWeboutResponse } from './utils'
|
||||
import { getTokens } from './auth/getTokens'
|
||||
|
||||
export class SASjsApiClient {
|
||||
constructor(
|
||||
private serverUrl: string,
|
||||
private requestClient: RequestClient
|
||||
) {}
|
||||
|
||||
public setConfig(serverUrl: string) {
|
||||
if (serverUrl) this.serverUrl = serverUrl
|
||||
}
|
||||
constructor(private requestClient: RequestClient) {}
|
||||
|
||||
public async deploy(
|
||||
members: FileTree,
|
||||
dataJson: ServicePackSASjs,
|
||||
appLoc: string,
|
||||
authConfig?: AuthConfig
|
||||
) {
|
||||
@@ -30,13 +22,17 @@ export class SASjsApiClient {
|
||||
ServerType.Sasjs
|
||||
))
|
||||
}
|
||||
|
||||
dataJson.appLoc = dataJson.appLoc || appLoc
|
||||
|
||||
const { result } = await this.requestClient.post<{
|
||||
status: string
|
||||
message: string
|
||||
streamServiceName?: string
|
||||
example?: {}
|
||||
}>(
|
||||
'SASjsApi/drive/deploy',
|
||||
{ fileTree: members, appLoc: appLoc },
|
||||
dataJson,
|
||||
access_token,
|
||||
undefined,
|
||||
{},
|
||||
@@ -46,7 +42,9 @@ export class SASjsApiClient {
|
||||
return Promise.resolve(result)
|
||||
}
|
||||
|
||||
public async executeJob(query: ExecutionQuery) {
|
||||
public async executeJob(query: ExecutionQuery, authConfig?: AuthConfig) {
|
||||
const access_token = authConfig ? authConfig.access_token : undefined
|
||||
|
||||
const { result } = await this.requestClient.post<{
|
||||
status: string
|
||||
message: string
|
||||
@@ -54,7 +52,7 @@ export class SASjsApiClient {
|
||||
logPath?: string
|
||||
error?: {}
|
||||
_webout?: string
|
||||
}>('SASjsApi/stp/execute', query, undefined)
|
||||
}>('SASjsApi/stp/execute', query, access_token)
|
||||
|
||||
if (Object.keys(result).includes('_webout')) {
|
||||
result._webout = parseWeboutResponse(result._webout!)
|
||||
@@ -63,6 +61,39 @@ export class SASjsApiClient {
|
||||
return Promise.resolve(result)
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes code on a SASJS server.
|
||||
* @param code - a string of code to execute.
|
||||
* @param authConfig - an object for authentication.
|
||||
*/
|
||||
public async executeScript(code: string, authConfig?: AuthConfig) {
|
||||
let access_token = (authConfig || {}).access_token
|
||||
if (authConfig) {
|
||||
;({ access_token } = await getTokens(
|
||||
this.requestClient,
|
||||
authConfig,
|
||||
ServerType.Sasjs
|
||||
))
|
||||
}
|
||||
|
||||
let parsedSasjsServerLog = ''
|
||||
|
||||
await this.requestClient
|
||||
.post('SASjsApi/code/execute', { code }, access_token)
|
||||
.then((res: any) => {
|
||||
if (res.result?.log) {
|
||||
parsedSasjsServerLog = res.result.log
|
||||
.map((logLine: any) => logLine.line)
|
||||
.join('\n')
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
parsedSasjsServerLog = err
|
||||
})
|
||||
|
||||
return parsedSasjsServerLog
|
||||
}
|
||||
|
||||
/**
|
||||
* Exchanges the auth code for an access token for the given client.
|
||||
* @param clientId - the client ID to authenticate with.
|
||||
@@ -82,20 +113,6 @@ export class SASjsApiClient {
|
||||
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
|
||||
|
||||
@@ -2,8 +2,6 @@ import { ServerType } from '@sasjs/utils/types'
|
||||
import { RequestClient } from '../request/RequestClient'
|
||||
import { LoginOptions, LoginResult } from '../types/Login'
|
||||
import { serialize } from '../utils'
|
||||
import { getAccessTokenForSasjs } from './getAccessTokenForSasjs'
|
||||
import { getAuthCodeForSasjs } from './getAuthCodeForSasjs'
|
||||
import { openWebPage } from './openWebPage'
|
||||
import { verifySas9Login } from './verifySas9Login'
|
||||
import { verifySasViyaLogin } from './verifySasViyaLogin'
|
||||
@@ -83,39 +81,6 @@ export class AuthManager {
|
||||
return { isLoggedIn: false, userName: '' }
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @returns - a boolean `isLoggedin` and a string `username`
|
||||
*/
|
||||
public async logInSasjs(
|
||||
username: string,
|
||||
password: string,
|
||||
clientId: string
|
||||
): Promise<LoginResult> {
|
||||
const isLoggedIn = await this.sendLoginRequestSasjs(
|
||||
username,
|
||||
password,
|
||||
clientId
|
||||
)
|
||||
.then((res) => {
|
||||
this.userName = username
|
||||
this.requestClient.saveLocalStorageToken(
|
||||
res.access_token,
|
||||
res.refresh_token
|
||||
)
|
||||
return true
|
||||
})
|
||||
.catch(() => false)
|
||||
|
||||
return {
|
||||
isLoggedIn,
|
||||
userName: this.userName
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs into the SAS server with the supplied credentials.
|
||||
* @param username - a string representing the username.
|
||||
@@ -152,7 +117,7 @@ export class AuthManager {
|
||||
|
||||
let loginResponse = await this.sendLoginRequest(loginForm, loginParams)
|
||||
|
||||
let isLoggedIn = isLogInSuccess(loginResponse)
|
||||
let isLoggedIn = isLogInSuccess(this.serverType, loginResponse)
|
||||
|
||||
if (!isLoggedIn) {
|
||||
if (isCredentialsVerifyError(loginResponse)) {
|
||||
@@ -196,6 +161,17 @@ export class AuthManager {
|
||||
loginForm: { [key: string]: any },
|
||||
loginParams: { [key: string]: any }
|
||||
) {
|
||||
if (this.serverType === ServerType.Sasjs) {
|
||||
const { username, password } = loginParams
|
||||
const { result: loginResponse } = await this.requestClient.post<string>(
|
||||
this.loginUrl,
|
||||
{ username, password },
|
||||
undefined
|
||||
)
|
||||
|
||||
return loginResponse
|
||||
}
|
||||
|
||||
for (const key in loginForm) {
|
||||
loginParams[key] = loginForm[key]
|
||||
}
|
||||
@@ -215,19 +191,6 @@ export class AuthManager {
|
||||
return loginResponse
|
||||
}
|
||||
|
||||
private async sendLoginRequestSasjs(
|
||||
username: string,
|
||||
password: string,
|
||||
clientId: string
|
||||
) {
|
||||
const authCode = await getAuthCodeForSasjs(
|
||||
this.requestClient,
|
||||
username,
|
||||
password,
|
||||
clientId
|
||||
)
|
||||
return getAccessTokenForSasjs(this.requestClient, clientId, authCode)
|
||||
}
|
||||
/**
|
||||
* Checks whether a session is active, or login is required.
|
||||
* @returns - a promise which resolves with an object containing three values
|
||||
@@ -248,8 +211,7 @@ export class AuthManager {
|
||||
//Residue can happen in case of session expiration
|
||||
await this.logOut()
|
||||
|
||||
if (this.serverType !== ServerType.Sasjs)
|
||||
loginForm = await this.getNewLoginForm()
|
||||
loginForm = await this.getNewLoginForm()
|
||||
}
|
||||
|
||||
return Promise.resolve({
|
||||
@@ -260,6 +222,12 @@ export class AuthManager {
|
||||
}
|
||||
|
||||
private async getNewLoginForm() {
|
||||
if (this.serverType === ServerType.Sasjs) {
|
||||
// server will be sending CSRF cookie,
|
||||
// http client will use it automatically
|
||||
return this.requestClient.get('/', undefined)
|
||||
}
|
||||
|
||||
const { result: formResponse } = await this.requestClient.get<string>(
|
||||
this.loginUrl.replace('.do', ''),
|
||||
undefined,
|
||||
@@ -384,5 +352,8 @@ const isCredentialsVerifyError = (response: string): boolean =>
|
||||
response
|
||||
)
|
||||
|
||||
const isLogInSuccess = (response: string): boolean =>
|
||||
/You have signed in/gm.test(response)
|
||||
const isLogInSuccess = (serverType: ServerType, response: any): boolean => {
|
||||
if (serverType === ServerType.Sasjs) return response?.loggedin
|
||||
|
||||
return /You have signed in/gm.test(response)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { SasAuthResponse } from '@sasjs/utils/types'
|
||||
import { prefixMessage } from '@sasjs/utils/error'
|
||||
import { RequestClient } from '../request/RequestClient'
|
||||
import { CertificateError } from '../types/errors'
|
||||
|
||||
/**
|
||||
* Exchanges the auth code for an access token for the given client.
|
||||
@@ -36,6 +37,7 @@ export async function getAccessTokenForViya(
|
||||
.post(url, data, undefined, 'application/x-www-form-urlencoded', headers)
|
||||
.then((res) => res.result as SasAuthResponse)
|
||||
.catch((err) => {
|
||||
if (err instanceof CertificateError) throw err
|
||||
throw prefixMessage(err, 'Error while getting access token. ')
|
||||
})
|
||||
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import { prefixMessage } from '@sasjs/utils/error'
|
||||
import { RequestClient } from '../request/RequestClient'
|
||||
|
||||
/**
|
||||
* Performs a login authenticate and returns an auth code for the given client.
|
||||
* @param requestClient - the pre-configured HTTP request client
|
||||
* @param username - a string representing the username.
|
||||
* @param password - a string representing the password.
|
||||
* @param clientId - the client ID to authenticate with.
|
||||
*/
|
||||
export const getAuthCodeForSasjs = async (
|
||||
requestClient: RequestClient,
|
||||
username: string,
|
||||
password: string,
|
||||
clientId: string
|
||||
) => {
|
||||
const url = '/SASjsApi/auth/authorize'
|
||||
const data = { username, password, clientId }
|
||||
|
||||
const { code: authCode } = await requestClient
|
||||
.post<{ code: string }>(url, data, undefined)
|
||||
.then((res) => res.result)
|
||||
.catch((err) => {
|
||||
throw prefixMessage(
|
||||
err,
|
||||
'Error while authenticating with provided username, password and clientId. '
|
||||
)
|
||||
})
|
||||
|
||||
return authCode
|
||||
}
|
||||
@@ -26,11 +26,18 @@ export const generateFileUploadForm = (
|
||||
)
|
||||
}
|
||||
|
||||
const file = new Blob([csv], {
|
||||
type: 'application/csv'
|
||||
})
|
||||
if (typeof FormData === 'undefined' && formData instanceof NodeFormData) {
|
||||
formData.append(name, csv, {
|
||||
filename: `${name}.csv`,
|
||||
contentType: 'application/csv'
|
||||
})
|
||||
} else {
|
||||
const file = new Blob([csv], {
|
||||
type: 'application/csv'
|
||||
})
|
||||
|
||||
formData.append(name, file, `${name}.csv`)
|
||||
formData.append(name, file, `${name}.csv`)
|
||||
}
|
||||
}
|
||||
|
||||
return formData
|
||||
|
||||
@@ -99,7 +99,20 @@ export class FileUploader extends BaseJobExecutor {
|
||||
? parseWeboutResponse(res.result, uploadUrl)
|
||||
: res.result
|
||||
break
|
||||
case ServerType.Sasjs:
|
||||
if (typeof res.result._webout === 'object') {
|
||||
jsonResponse = res.result._webout
|
||||
} else {
|
||||
const webout = parseWeboutResponse(
|
||||
res.result._webout,
|
||||
uploadUrl
|
||||
)
|
||||
jsonResponse = getValidJson(webout)
|
||||
}
|
||||
break
|
||||
}
|
||||
} else if (this.serverType === ServerType.Sasjs) {
|
||||
jsonResponse = getValidJson(res.result._webout)
|
||||
} else {
|
||||
jsonResponse =
|
||||
typeof res.result === 'string'
|
||||
|
||||
@@ -74,38 +74,32 @@ export class Sas9JobExecutor extends BaseJobExecutor {
|
||||
? 'multipart/form-data; boundary=' + (formData as any)._boundary
|
||||
: 'text/plain'
|
||||
|
||||
return await this.sas9RequestClient!.post(
|
||||
apiUrl,
|
||||
formData,
|
||||
undefined,
|
||||
contentType,
|
||||
{
|
||||
const requestPromise = new Promise((resolve, reject) =>
|
||||
this.sas9RequestClient!.post(apiUrl, formData, undefined, contentType, {
|
||||
Accept: '*/*',
|
||||
Connection: 'Keep-Alive'
|
||||
}
|
||||
})
|
||||
.then((res: any) => {
|
||||
// appending response to requests array that will be used for requests history reference
|
||||
this.requestClient!.appendRequest(res, sasJob, config.debug)
|
||||
resolve(res)
|
||||
})
|
||||
.catch((err: any) => {
|
||||
// by default error string is equal to actual error object
|
||||
let errString = err
|
||||
|
||||
// if error object contains non empty result attribute, set result to errString
|
||||
if (err.result && err.result !== '') errString = err.result
|
||||
// if there's no result but error message, set error message to errString
|
||||
else if (err.message) errString = err.message
|
||||
|
||||
// appending error to requests array that will be used for requests history reference
|
||||
this.requestClient!.appendRequest(errString, sasJob, config.debug)
|
||||
reject(new ErrorResponse(err?.message, err))
|
||||
})
|
||||
)
|
||||
.then((res: any) => {
|
||||
let resString = res
|
||||
|
||||
if (typeof res === 'object') {
|
||||
resString = JSON.stringify(res)
|
||||
}
|
||||
|
||||
this.requestClient!.appendRequest(resString, sasJob, config.debug)
|
||||
|
||||
return res
|
||||
})
|
||||
.catch((err: any) => {
|
||||
let errString = err
|
||||
|
||||
if (typeof err === 'object') {
|
||||
errString = JSON.stringify(errString)
|
||||
}
|
||||
|
||||
this.requestClient!.appendRequest(errString, sasJob, config.debug)
|
||||
|
||||
return err
|
||||
})
|
||||
return requestPromise
|
||||
}
|
||||
|
||||
private getRequestParams(config: any): any {
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
} from '../utils'
|
||||
import { BaseJobExecutor } from './JobExecutor'
|
||||
import { parseWeboutResponse } from '../utils/parseWeboutResponse'
|
||||
import { Server } from 'https'
|
||||
|
||||
export interface WaitingRequstPromise {
|
||||
promise: Promise<any> | null
|
||||
@@ -46,7 +47,7 @@ export class WebJobExecutor extends BaseJobExecutor {
|
||||
authConfig?: AuthConfig,
|
||||
extraResponseAttributes: ExtraResponseAttributes[] = []
|
||||
) {
|
||||
const loginCallback = loginRequiredCallback || (() => Promise.resolve())
|
||||
const loginCallback = loginRequiredCallback
|
||||
const program = isRelativePath(sasJob)
|
||||
? config.appLoc
|
||||
? config.appLoc.replace(/\/?$/, '/') + sasJob.replace(/^\//, '')
|
||||
@@ -79,7 +80,7 @@ export class WebJobExecutor extends BaseJobExecutor {
|
||||
)
|
||||
})
|
||||
|
||||
await loginCallback()
|
||||
if (loginCallback) await loginCallback()
|
||||
} else {
|
||||
reject(new ErrorResponse(e?.message, e))
|
||||
}
|
||||
@@ -220,6 +221,15 @@ export class WebJobExecutor extends BaseJobExecutor {
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -238,7 +248,7 @@ export class WebJobExecutor extends BaseJobExecutor {
|
||||
)
|
||||
})
|
||||
|
||||
await loginCallback()
|
||||
if (loginCallback) await loginCallback()
|
||||
} else {
|
||||
reject(new ErrorResponse(e?.message, e))
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@ import {
|
||||
LoginRequiredError,
|
||||
NotFoundError,
|
||||
InternalServerError,
|
||||
JobExecutionError
|
||||
JobExecutionError,
|
||||
CertificateError
|
||||
} from '../types/errors'
|
||||
import { SASjsRequest } from '../types'
|
||||
import { parseWeboutResponse } from '../utils/parseWeboutResponse'
|
||||
@@ -131,6 +132,26 @@ export class RequestClient implements HttpClient {
|
||||
} else {
|
||||
sasWork = response.log
|
||||
}
|
||||
} else if (response?.result?.log) {
|
||||
//In this scenario we know we got the response from SASJS server
|
||||
//Log is array of `{ line: '' }` so we need to convert it back to text
|
||||
//To be able to parse it with current functions.
|
||||
let log: string = ''
|
||||
|
||||
if (typeof log !== 'string') {
|
||||
log = response.result.log
|
||||
.map((logLine: any) => logLine.line)
|
||||
.join('\n')
|
||||
}
|
||||
|
||||
sourceCode = parseSourceCode(log)
|
||||
generatedCode = parseGeneratedCode(log)
|
||||
|
||||
if (response?.result?._webout) {
|
||||
sasWork = response.result._webout.WORK
|
||||
} else {
|
||||
sasWork = log
|
||||
}
|
||||
} else if (response?.result) {
|
||||
sourceCode = parseSourceCode(response.result)
|
||||
generatedCode = parseGeneratedCode(response.result)
|
||||
@@ -497,6 +518,10 @@ export class RequestClient implements HttpClient {
|
||||
else return
|
||||
}
|
||||
|
||||
if (e.isAxiosError && e.code === 'UNABLE_TO_VERIFY_LEAF_SIGNATURE') {
|
||||
throw new CertificateError(e.message)
|
||||
}
|
||||
|
||||
if (e.message) throw e
|
||||
else throw prefixMessage(e, 'Error while handling error. ')
|
||||
}
|
||||
@@ -557,8 +582,18 @@ export class RequestClient implements HttpClient {
|
||||
}
|
||||
|
||||
export const throwIfError = (response: AxiosResponse) => {
|
||||
if (response.status === 401) {
|
||||
throw new LoginRequiredError()
|
||||
switch (response.status) {
|
||||
case 400:
|
||||
if (typeof response.data === 'object') {
|
||||
throw new LoginRequiredError(response.data)
|
||||
}
|
||||
break
|
||||
case 401:
|
||||
if (typeof response.data === 'object') {
|
||||
throw new LoginRequiredError(response.data)
|
||||
} else {
|
||||
throw new LoginRequiredError()
|
||||
}
|
||||
}
|
||||
|
||||
if (response.data?.entityID?.includes('login')) {
|
||||
|
||||
@@ -30,6 +30,11 @@ const ERROR_MESSAGES = {
|
||||
CCA: 'unable to verify the first certificate'
|
||||
}
|
||||
|
||||
const incorrectAuthCodeErr = {
|
||||
error: 'unauthorized',
|
||||
error_description: 'Bad credentials'
|
||||
}
|
||||
|
||||
describe('RequestClient', () => {
|
||||
let server: http.Server
|
||||
|
||||
@@ -65,7 +70,7 @@ describe('RequestClient', () => {
|
||||
adapter.getAccessToken('clientId', 'clientSecret', 'incorrect')
|
||||
).rejects.toEqual(
|
||||
prefixMessage(
|
||||
new LoginRequiredError(),
|
||||
new LoginRequiredError(incorrectAuthCodeErr),
|
||||
'Error while getting access token. '
|
||||
)
|
||||
)
|
||||
@@ -246,7 +251,7 @@ describe('RequestClient - Self Signed Server', () => {
|
||||
adapter.getAccessToken('clientId', 'clientSecret', 'incorrect')
|
||||
).rejects.toEqual(
|
||||
prefixMessage(
|
||||
new LoginRequiredError(),
|
||||
new LoginRequiredError(incorrectAuthCodeErr),
|
||||
'Error while getting access token. '
|
||||
)
|
||||
)
|
||||
|
||||
@@ -62,7 +62,7 @@ describe('formatDataForRequest', () => {
|
||||
|
||||
expect(() => formatDataForRequest(tableWithMissingValues)).toThrow(
|
||||
new Error(
|
||||
'Special missing value can only be a single character from A to Z or _'
|
||||
`A Special missing value can only be a single character from 'A' to 'Z', '_', '.[a-z]', '._'`
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
export interface FileTree {
|
||||
members: [FolderMember, ServiceMember]
|
||||
}
|
||||
|
||||
export enum MemberType {
|
||||
folder = 'folder',
|
||||
service = 'service'
|
||||
}
|
||||
|
||||
export interface FolderMember {
|
||||
name: string
|
||||
type: MemberType.folder
|
||||
members: [FolderMember, ServiceMember]
|
||||
}
|
||||
|
||||
export interface ServiceMember {
|
||||
name: string
|
||||
type: MemberType.service
|
||||
code: string
|
||||
}
|
||||
|
||||
export const isFileTree = (arg: any): arg is FileTree =>
|
||||
arg &&
|
||||
arg.members &&
|
||||
Array.isArray(arg.members) &&
|
||||
arg.members.filter(
|
||||
(member: FolderMember | ServiceMember) =>
|
||||
!isFolderMember(member) && !isServiceMember(member)
|
||||
).length === 0
|
||||
|
||||
const isFolderMember = (arg: any): arg is FolderMember =>
|
||||
arg &&
|
||||
typeof arg.name === 'string' &&
|
||||
arg.type === MemberType.folder &&
|
||||
arg.members &&
|
||||
Array.isArray(arg.members) &&
|
||||
arg.members.filter(
|
||||
(member: FolderMember | ServiceMember) =>
|
||||
!isFolderMember(member) && !isServiceMember(member)
|
||||
).length === 0
|
||||
|
||||
const isServiceMember = (arg: any): arg is ServiceMember =>
|
||||
arg &&
|
||||
typeof arg.name === 'string' &&
|
||||
arg.type === MemberType.service &&
|
||||
arg.code &&
|
||||
typeof arg.code === 'string'
|
||||
12
src/types/errors/CertificateError.ts
Normal file
12
src/types/errors/CertificateError.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
const instructionsToFix =
|
||||
'https://github.com/sasjs/cli/issues/1181#issuecomment-1090638584'
|
||||
|
||||
export class CertificateError extends Error {
|
||||
constructor(message: string) {
|
||||
super(
|
||||
`${message}\nPlease visit the link below for further information on this issue:\n- ${instructionsToFix}\n`
|
||||
)
|
||||
this.name = 'CertificateError'
|
||||
Object.setPrototypeOf(this, CertificateError.prototype)
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,10 @@
|
||||
export class LoginRequiredError extends Error {
|
||||
constructor() {
|
||||
super('Auth error: You must be logged in to access this resource')
|
||||
constructor(details?: any) {
|
||||
const message = details
|
||||
? JSON.stringify(details, null, 2)
|
||||
: 'You must be logged in to access this resource'
|
||||
|
||||
super(`Auth error: ${message}`)
|
||||
this.name = 'LoginRequiredError'
|
||||
Object.setPrototypeOf(this, LoginRequiredError.prototype)
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
export * from './AuthorizeError'
|
||||
export * from './CertificateError'
|
||||
export * from './ComputeJobExecutionError'
|
||||
export * from './ErrorResponse'
|
||||
export * from './InternalServerError'
|
||||
export * from './InvalidJsonError'
|
||||
export * from './JobExecutionError'
|
||||
export * from './JobStatePollError'
|
||||
export * from './LoginRequiredError'
|
||||
export * from './NotFoundError'
|
||||
export * from './ErrorResponse'
|
||||
export * from './NoSessionStateError'
|
||||
export * from './RootFolderNotFoundError'
|
||||
export * from './JsonParseArrayError'
|
||||
export * from './LoginRequiredError'
|
||||
export * from './NoSessionStateError'
|
||||
export * from './NotFoundError'
|
||||
export * from './RootFolderNotFoundError'
|
||||
export * from './WeboutResponseError'
|
||||
export * from './InvalidJsonError'
|
||||
|
||||
@@ -12,5 +12,4 @@ export * from './Session'
|
||||
export * from './UploadFile'
|
||||
export * from './PollOptions'
|
||||
export * from './WriteStream'
|
||||
export * from './FileTree'
|
||||
export * from './ExecuteScript'
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { isSpecialMissing } from '@sasjs/utils/input/validators'
|
||||
|
||||
/**
|
||||
* Converts the given JSON object array to a CSV string.
|
||||
* @param data - the array of JSON objects to convert.
|
||||
@@ -18,7 +20,6 @@ export const convertToCSV = (
|
||||
let headers: string[] = []
|
||||
let csvTest
|
||||
let invalidString = false
|
||||
const specialMissingValueRegExp = /^[a-z_]{1}$/i
|
||||
|
||||
if (formats) {
|
||||
headers = Object.keys(formats).map((key) => `${key}:${formats![key]}`)
|
||||
@@ -36,7 +37,7 @@ export const convertToCSV = (
|
||||
hasNullOrNumber = true
|
||||
} else if (
|
||||
typeof row[field] === 'string' &&
|
||||
specialMissingValueRegExp.test(row[field])
|
||||
isSpecialMissing(row[field])
|
||||
) {
|
||||
hasSpecialMissingString = true
|
||||
}
|
||||
@@ -130,10 +131,9 @@ export const convertToCSV = (
|
||||
value = currentCell === null ? '' : currentCell
|
||||
|
||||
if (formats && formats[fieldName] === 'best.') {
|
||||
if (value && !specialMissingValueRegExp.test(value)) {
|
||||
console.log(`🤖[value]🤖`, value)
|
||||
if (value && !isSpecialMissing(value)) {
|
||||
throw new Error(
|
||||
'Special missing value can only be a single character from A to Z or _'
|
||||
`A Special missing value can only be a single character from 'A' to 'Z', '_', '.[a-z]', '._'`
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -16,10 +16,15 @@ export const parseSasViyaDebugResponse = async (
|
||||
requestClient: RequestClient,
|
||||
serverUrl: string
|
||||
) => {
|
||||
// On viya 3.5, iframe is like <iframe style="width: 99%; height: 500px" src="..."></iframe>
|
||||
// On viya 4, iframe is like <iframe style="width: 99%; height: 500px; background-color:Canvas;" src=...></iframe>
|
||||
|
||||
const iframeStart = response.split(
|
||||
'<iframe style="width: 99%; height: 500px" src="'
|
||||
/<iframe style="width: 99%; height: 500px" src="|<iframe style="width: 99%; height: 500px; background-color:Canvas;" src=/
|
||||
)[1]
|
||||
const jsonUrl = iframeStart ? iframeStart.split('"></iframe>')[0] : null
|
||||
const jsonUrl = iframeStart
|
||||
? iframeStart.split(/"><\/iframe>|><\/iframe>/)[0]
|
||||
: null
|
||||
if (!jsonUrl) {
|
||||
throw new Error('Unable to find webout file URL.')
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user