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

Compare commits

...

34 Commits

Author SHA1 Message Date
Allan Bowe
bf35e52962 Merge pull request #713 from sasjs/critical-deps-issues
Fixed critical dependencies issues
2022-06-29 14:24:29 +02:00
Yury Shkoda
eb83101dbf fix(sasjs-test): addede appLoc to useEffect deps 2022-06-29 08:46:52 +03:00
Yury Shkoda
56d84e1940 fix(sasjs-tests): used appLoc from config 2022-06-29 08:37:59 +03:00
Yury Shkoda
283800dfa6 fix(special-missings): fixed formats table sent as part of sasjs_tables 2022-06-28 10:17:22 +03:00
Yury Shkoda
c073d72dd4 chore(deps): regenerated package-locks 2022-06-24 16:16:36 +03:00
Yury Shkoda
f5d40eaaf7 chore: Merge branch 'deps-fix' into critical-deps-issues 2022-06-24 16:13:43 +03:00
Yury Shkoda
8e9cf98985 fix(deps): semantic-release, @sasjs/test-framework 2022-06-24 15:47:34 +03:00
Allan Bowe
79ba044dea Update README.md 2022-06-24 11:49:48 +01:00
Allan Bowe
9329dc848a Update README.md 2022-06-24 11:46:09 +01:00
Allan Bowe
98c492e85e Merge pull request #729 from sasjs/update-AuthManager
fix: update logout url
2022-06-21 22:10:26 +02:00
d1fcc2ca0a fix: update logout url 2022-06-22 00:53:49 +05:00
Yury Shkoda
122f302bae Merge pull request #728 from sasjs/deps-fix
fix(workflow): added actions/setup-node@v2
2022-06-20 20:44:57 +03:00
Yury Shkoda
c3a0ad1f41 fix(workflow): added actions/setup-node@v2 2022-06-20 20:43:11 +03:00
Yury Shkoda
a28b48f815 Merge pull request #726 from sasjs/deps-fix
fix(workflows): fixed npmpublish workflow
2022-06-20 20:36:13 +03:00
Yury Shkoda
9b6a42e412 fix(workflows): fixed npmpublish workflow 2022-06-20 20:33:44 +03:00
Allan Bowe
db60962c1e Merge pull request #725 from sasjs/allanbowe-patch-1
fix: bumping with README updates
2022-06-20 19:29:15 +02:00
Allan Bowe
1eae59ad3b fix: bumping with README updates 2022-06-20 18:28:47 +01:00
Allan Bowe
d485023d65 Update dependabot.yml 2022-06-20 18:09:07 +01:00
Allan Bowe
c2f21babb4 Merge pull request #723 from sasjs/deps-fix
Regenerated package-lock and fixed linting issues
2022-06-20 19:03:17 +02:00
Yury Shkoda
dd788ae423 chore(lint): fixed linting 2022-06-20 19:48:42 +03:00
Yury Shkoda
a113c95441 chore(deps): added prettier dev dependency 2022-06-20 19:36:12 +03:00
Yury Shkoda
489947bcae chore(lint): fixed linting issues 2022-06-20 19:26:29 +03:00
Yury Shkoda
1596173dda fix(deps): regenerated package-lock 2022-06-20 19:24:09 +03:00
Allan Bowe
bb1b2ddcb2 Merge pull request #719 from sasjs/issue-718
fix: parse the logs before appending the request to request array whe…
2022-06-20 17:53:44 +02:00
a3cc274ef1 chore: no need to appendRequest from then block when there is jobExecutionError 2022-06-10 15:38:03 +05:00
451d0906fa chore: update error message 2022-06-10 15:31:09 +05:00
dd6b89b0d0 fix: parse the logs before appending the request to request array when server type is sasjs 2022-06-09 23:11:47 +05:00
Yury Shkoda
f602d5baf0 chore(deps): added prettier 2022-06-01 10:08:50 +03:00
Yury Shkoda
4744dbf196 fix(deps): fixed critical vulnerabilities 2022-06-01 09:53:22 +03:00
Allan Bowe
f0525c5796 Merge pull request #674 from sasjs/numeric-missing
fix: special missings accept - regular missing .
2022-05-23 14:11:30 +03:00
076ed1cc7a chore: added special missing test 2022-05-23 12:59:48 +02:00
0a3289b577 chore(git): Merge branch 'master' into numeric-missing 2022-05-23 12:47:13 +02:00
d2ea67e5d6 chore: docs 2022-03-08 18:32:45 +01:00
b0df4cb7ee fix: special missings accept - regular missing . 2022-03-08 18:28:48 +01:00
38 changed files with 18121 additions and 23310 deletions

View File

@@ -1,7 +1,7 @@
version: 2
updates:
- package-ecosystem: npm
directory: '/'
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: monthly
interval: "monthly"
open-pull-requests-limit: 2

View File

@@ -11,10 +11,19 @@ on:
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [lts/fermium]
steps:
- name: Checkout
uses: actions/checkout@v2
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
cache: npm
- name: Install Dependencies
run: npm ci
@@ -23,7 +32,7 @@ jobs:
- name: Build Project
run: npm run build
- name: Semantic Release
uses: cycjimmy/semantic-release-action@v2
env:

View File

@@ -2,7 +2,6 @@
[![npm package][npm-image]][npm-url]
[![Github Workflow][githubworkflow-image]][githubworkflow-url]
[![Dependency Status][dependency-image]][dependency-url]
[![npm](https://img.shields.io/npm/dt/@sasjs/adapter)]()
![Snyk Vulnerabilities for npm package](https://img.shields.io/snyk/vulnerabilities/npm/@sasjs/adapter)
[![License](https://img.shields.io/apm/l/atomic-design-ui.svg)](/LICENSE)
@@ -16,7 +15,6 @@
[githubworkflow-image]:https://github.com/sasjs/adapter/actions/workflows/build.yml/badge.svg
[githubworkflow-url]:https://github.com/sasjs/adapter/blob/main/.github/workflows/build.yml
[dependency-image]:https://david-dm.org/sasjs/adapter.svg
[dependency-url]:https://github.com/sasjs/adapter/blob/main/package.json
SASjs is a open-source framework for building Web Apps on SAS® platforms. You can use as much or as little of it as you like. This repository contains the JS adapter, the part that handles the to/from SAS communication on the client side. There are 3 ways to install it:
@@ -32,7 +30,7 @@ For more information on building web apps with SAS, check out [sasjs.io](https:/
## None of this makes sense. How do I build an app with it?
Ok ok. Deploy this [example.html](https://raw.githubusercontent.com/sasjs/adapter/master/example.html) file to your web server, and update `servertype` to `SAS9` or `SASVIYA` depending on your backend.
Ok ok. Deploy this [example.html](https://raw.githubusercontent.com/sasjs/adapter/master/example.html) file to your web server, and update `servertype` to `SAS9`, `SASVIYA`, or `SASJS` depending on your backend.
The backend part can be deployed as follows:
@@ -52,7 +50,7 @@ parmcards4;
%webout(OBJ,areas)
%webout(CLOSE)
;;;;
%mp_createwebservice(path=&appLoc/common,name=getdata)
%mx_createwebservice(path=&appLoc/common,name=getdata)
```
You now have a simple web app with a backend service!
@@ -96,10 +94,10 @@ const sasJs = new SASjs({your config})
More on the config later.
### SAS Logon
All authentication from the adapter is done against SASLogon. There are two approaches that can be taken, which are configured using the `LoginMechanism` attribute of the sasJs config object (above):
All authentication from the adapter is done against SASLogon. There are two approaches that can be taken, which are configured using the `loginMechanism` attribute of the sasJs config object (above):
* `LoginMechanism:'Redirected'` - this approach enables authentication through a SASLogon window, supporting complex authentication flows (such as 2FA) and avoids the need to handle passwords in the application itself. The styling of the window can be modified using CSS.
* `LoginMechanism:'Default'` - this approach requires that the username and password are captured, and used within the `.login()` method. This can be helpful for development, or automated testing.
* `loginMechanism:'Redirected'` - this approach enables authentication through a SASLogon window, supporting complex authentication flows (such as 2FA) and avoids the need to handle passwords in the application itself. The styling of the window can be modified using CSS.
* `loginMechanism:'Default'` - this approach requires that the username and password are captured, and used within the `.login()` method. This can be helpful for development, or automated testing.
Sample code for logging in with the `Default` approach:
@@ -127,7 +125,11 @@ sasJs.request("/path/to/my/service", dataObject)
})
```
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.
If the path starts with a `/` then it should be a full path to the service. If there is no leading `/` then it is relative to the `appLoc`.
The data object can be null (for services with no input), or can contain one or more "tables" in the following format:
```javascript
let dataObject={
@@ -143,7 +145,9 @@ let dataObject={
};
```
There are optional parameters such as a config object and a callback login function.
These tables (`tablewith2cols1row` and `tablewith1col2rows`) will be created in SAS WORK after running `%webout(FETCH)` in your SAS service.
The `request()` method also has optional parameters such as a config object and a callback login function.
The response object will contain returned tables and columns. Table names are always lowercase, and column names uppercase.
@@ -155,7 +159,7 @@ The SAS type (char/numeric) of the values is determined according to a set of ru
* 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).
* 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:
@@ -221,7 +225,7 @@ The SAS side is handled by a number of macros in the [macro core](https://github
The following snippet shows the process of SAS tables arriving / leaving:
```sas
/* fetch all input tables sent from frontend - they arrive as work tables */
/* convert frontend input tables from into SASWORK datasets */
%webout(FETCH)
/* some sas code */
@@ -250,6 +254,8 @@ Where an entire column is made up of special missing numerics, there would be no
%webout(OBJ,a,missing=STRING,showmeta=YES)
```
The `%webout()` macro itself is just a wrapper for the [mp_jsonout](https://core.sasjs.io/mp__jsonout_8sas.html) macro.
## Configuration
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:
@@ -258,7 +264,7 @@ Configuration on the client side involves passing an object on startup, which ca
* `serverType` - either `SAS9`, `SASVIYA` or `SASJS`. The `SASJS` server type is for use with [sasjs/server](https://github.com/sasjs/server).
* `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.
* `LoginMechanism` - either `Default` or `Redirected`. See [SAS Logon](#sas-logon) section.
* `loginMechanism` - either `Default` or `Redirected`. See [SAS Logon](#sas-logon) section.
* `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`.
* `requestHistoryLimit` - Request history limit. Increasing this limit may affect browser performance, especially with debug (logs) enabled. Default is 10.
@@ -314,7 +320,7 @@ For more information and examples specific to this adapter you can check out the
For more information on building web apps in general, check out these [resources](https://sasjs.io/training/resources/) or contact the [author](https://www.linkedin.com/in/allanbowe/) directly.
If you are a SAS 9 or SAS Viya customer you can also request a copy of [Data Controller](https://datacontroller.io) - free for up to 5 users, this tool makes use of all parts of the SASjs framework.
As a SAS customer you can also request a copy of [Data Controller](https://datacontroller.io) - free for up to 5 users, this tool makes use of all parts of the SASjs framework.
## Star Gazing

10253
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -44,18 +44,16 @@
"license": "ISC",
"devDependencies": {
"@cypress/webpack-preprocessor": "5.9.1",
"@types/form-data": "2.5.0",
"cypress": "7.7.0",
"typedoc-neo-theme": "1.1.1",
"typedoc-plugin-external-module-name": "4.0.6",
"@types/axios": "0.14.0",
"@types/express": "4.17.13",
"@types/form-data": "2.5.0",
"@types/jest": "27.4.0",
"@types/mime": "2.0.3",
"@types/pem": "1.9.6",
"@types/tough-cookie": "4.0.1",
"copyfiles": "2.4.1",
"cp": "0.2.0",
"cypress": "7.7.0",
"dotenv": "16.0.0",
"express": "4.17.3",
"jest": "27.4.7",
@@ -63,15 +61,18 @@
"node-polyfill-webpack-plugin": "1.1.4",
"path": "0.12.7",
"pem": "1.14.6",
"prettier": "2.7.1",
"process": "0.11.10",
"rimraf": "3.0.2",
"semantic-release": "18.0.0",
"semantic-release": "19.0.3",
"terser-webpack-plugin": "5.3.1",
"ts-jest": "27.1.3",
"ts-loader": "9.2.6",
"tslint": "6.1.3",
"tslint-config-prettier": "1.18.0",
"typedoc": "0.22.11",
"typedoc-neo-theme": "1.1.1",
"typedoc-plugin-external-module-name": "4.0.6",
"typedoc-plugin-rename-defaults": "0.4.0",
"typescript": "4.5.5",
"webpack": "5.69.0",

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@
"private": true,
"dependencies": {
"@sasjs/adapter": "file:../build/sasjs-adapter-5.0.0.tgz",
"@sasjs/test-framework": "^1.4.3",
"@sasjs/test-framework": "^1.5.6",
"@types/jest": "^26.0.20",
"@types/node": "^14.14.41",
"@types/react": "^17.0.1",
@@ -14,7 +14,7 @@
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-router-dom": "^5.2.0",
"react-scripts": "^4.0.2",
"react-scripts": "^5.0.1",
"typescript": "^4.1.3"
},
"scripts": {
@@ -43,6 +43,6 @@
]
},
"devDependencies": {
"node-sass": "^6.0.1"
"node-sass": "^7.0.1"
}
}

View File

@@ -11,12 +11,13 @@ import { fileUploadTests } from './testSuites/FileUpload'
const App = (): ReactElement<{}> => {
const { adapter, config } = useContext(AppContext)
const [testSuites, setTestSuites] = useState<TestSuite[]>([])
const appLoc = config.sasJsConfig.appLoc
useEffect(() => {
if (adapter) {
const testSuites = [
basicTests(adapter, config.userName, config.password),
sendArrTests(adapter),
sendArrTests(adapter, appLoc),
sendObjTests(adapter),
specialCaseTests(adapter),
sasjsRequestTests(adapter),
@@ -24,12 +25,12 @@ const App = (): ReactElement<{}> => {
]
if (adapter.getSasjsConfig().serverType === 'SASVIYA') {
testSuites.push(computeTests(adapter))
testSuites.push(computeTests(adapter, appLoc))
}
setTestSuites(testSuites)
}
}, [adapter, config])
}, [adapter, config, appLoc])
return (
<div className="app">

View File

@@ -9,7 +9,7 @@ const Login = (): ReactElement<{}> => {
const appContext = useContext(AppContext)
const handleSubmit = useCallback(
(e) => {
(e: any) => {
e.preventDefault()
appContext.adapter.logIn(username, password).then((res) => {
appContext.setIsLoggedIn(res.isLoggedIn)
@@ -28,7 +28,7 @@ const Login = (): ReactElement<{}> => {
placeholder="User Name"
value={username}
required
onChange={(e) => setUsername(e.target.value)}
onChange={(e: any) => setUsername(e.target.value)}
/>
</div>
<div className="row">
@@ -38,7 +38,7 @@ const Login = (): ReactElement<{}> => {
type="password"
value={password}
required
onChange={(e) => setPassword(e.target.value)}
onChange={(e: any) => setPassword(e.target.value)}
/>
</div>
<button type="submit" className="submit-button">

View File

@@ -3,7 +3,7 @@ import { TestSuite } from '@sasjs/test-framework'
const stringData: any = { table1: [{ col1: 'first col value' }] }
export const computeTests = (adapter: SASjs): TestSuite => ({
export const computeTests = (adapter: SASjs, appLoc: string): TestSuite => ({
name: 'Compute',
tests: [
{
@@ -35,7 +35,7 @@ export const computeTests = (adapter: SASjs): TestSuite => ({
description: 'Should start a compute job and return the session',
test: () => {
const data: any = { table1: [{ col1: 'first col value' }] }
return adapter.startComputeJob('/Public/app/common/sendArr', data)
return adapter.startComputeJob(`${appLoc}/common/sendArr`, data)
},
assertion: (res: any) => {
const expectedProperties = ['id', 'applicationName', 'attributes']

View File

@@ -45,14 +45,14 @@ const getLargeObjectData = () => {
return data
}
export const sendArrTests = (adapter: SASjs): TestSuite => ({
export const sendArrTests = (adapter: SASjs, appLoc: string): TestSuite => ({
name: 'sendArr',
tests: [
{
title: 'Absolute paths',
description: 'Should work with absolute paths to SAS jobs',
test: () => {
return adapter.request('/Public/app/common/sendArr', stringData)
return adapter.request(`${appLoc}/common/sendArr`, stringData)
},
assertion: (res: any) => {
return res.table1[0][0] === stringData.table1[0].col1
@@ -86,7 +86,7 @@ export const sendArrTests = (adapter: SASjs): TestSuite => ({
'Should error out with long string values over 32765 characters',
test: () => {
const data = getLongStringData(32767)
return adapter.request('common/sendArr', data).catch((e) => e)
return adapter.request('common/sendArr', data).catch((e: any) => e)
},
assertion: (error: any) => {
return !!error && !!error.error && !!error.error.message
@@ -182,7 +182,9 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
const invalidData: any = {
'1InvalidTable': [{ col1: 42 }]
}
return adapter.request('common/sendObj', invalidData).catch((e) => e)
return adapter
.request('common/sendObj', invalidData)
.catch((e: any) => e)
},
assertion: (error: any) =>
!!error && !!error.error && !!error.error.message
@@ -194,7 +196,9 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
const invalidData: any = {
'an invalidTable': [{ col1: 42 }]
}
return adapter.request('common/sendObj', invalidData).catch((e) => e)
return adapter
.request('common/sendObj', invalidData)
.catch((e: any) => e)
},
assertion: (error: any) =>
!!error && !!error.error && !!error.error.message
@@ -206,7 +210,9 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
const invalidData: any = {
'anInvalidTable#': [{ col1: 42 }]
}
return adapter.request('common/sendObj', invalidData).catch((e) => e)
return adapter
.request('common/sendObj', invalidData)
.catch((e: any) => e)
},
assertion: (error: any) =>
!!error && !!error.error && !!error.error.message
@@ -219,7 +225,9 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx: [{ col1: 42 }]
}
return adapter.request('common/sendObj', invalidData).catch((e) => e)
return adapter
.request('common/sendObj', invalidData)
.catch((e: any) => e)
},
assertion: (error: any) =>
!!error && !!error.error && !!error.error.message
@@ -231,7 +239,9 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
const invalidData: any = {
inData: [[{ data: 'value' }]]
}
return adapter.request('common/sendObj', invalidData).catch((e) => e)
return adapter
.request('common/sendObj', invalidData)
.catch((e: any) => e)
},
assertion: (error: any) =>
!!error && !!error.error && !!error.error.message
@@ -265,7 +275,7 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
test: () => {
return adapter
.request('common/sendObj', getLongStringData(32767))
.catch((e) => e)
.catch((e: any) => e)
},
assertion: (error: any) => {
return !!error && !!error.error && !!error.error.message

View File

@@ -9,7 +9,7 @@ export const assert = (
} else {
result = expression()
}
} catch (e) {
} catch (e: any) {
console.error(message)
throw new Error(message)
}

View File

@@ -31,7 +31,7 @@ describe('SASViyaApiClient', () => {
.mockImplementation(() => Promise.reject('Not Found'))
const error = await sasViyaApiClient
.createFolder('test', '/foo')
.catch((e) => e)
.catch((e: any) => e)
expect(error).toBeInstanceOf(RootFolderNotFoundError)
})
})

View File

@@ -240,7 +240,7 @@ export async function executeScript(
jobResult = await requestClient
.get<any>(resultLink, access_token, 'text/plain')
.catch(async (e) => {
.catch(async (e: any) => {
if (e instanceof NotFoundError) {
if (logLink) {
const logUrl = `${logLink.href}/content`
@@ -271,7 +271,7 @@ export async function executeScript(
})
return { result: jobResult?.result, log }
} catch (e) {
} catch (e: any) {
interface HttpError {
status: number
}

View File

@@ -80,7 +80,7 @@ describe('executeScript', () => {
'test',
['%put hello'],
'test context'
).catch((e) => e)
).catch((e: any) => e)
expect(error).toContain('Error while getting session.')
})
@@ -128,7 +128,7 @@ describe('executeScript', () => {
false,
defaultPollOptions,
true
).catch((e) => e)
).catch((e: any) => e)
expect(error).toContain('Error while getting session variable.')
})
@@ -297,7 +297,7 @@ describe('executeScript', () => {
false,
defaultPollOptions,
true
).catch((e) => e)
).catch((e: any) => e)
expect(error).toContain('Error while posting job')
})
@@ -367,7 +367,7 @@ describe('executeScript', () => {
true,
defaultPollOptions,
true
).catch((e) => e)
).catch((e: any) => e)
expect(error).toContain('Error while polling job status.')
})
@@ -393,7 +393,7 @@ describe('executeScript', () => {
true,
defaultPollOptions,
true
).catch((e) => e)
).catch((e: any) => e)
expect(fetchLogsModule.fetchLogByChunks).toHaveBeenCalledWith(
requestClient,
@@ -468,7 +468,7 @@ describe('executeScript', () => {
true,
defaultPollOptions,
true
).catch((e) => e)
).catch((e: any) => e)
expect(fetchLogsModule.fetchLogByChunks).toHaveBeenCalledWith(
requestClient,
@@ -501,7 +501,7 @@ describe('executeScript', () => {
true,
defaultPollOptions,
true
).catch((e) => e)
).catch((e: any) => e)
expect(fetchLogsModule.fetchLogByChunks).toHaveBeenCalledWith(
requestClient,
@@ -561,7 +561,7 @@ describe('executeScript', () => {
true,
defaultPollOptions,
true
).catch((e) => e)
).catch((e: any) => e)
expect(requestClient.get).toHaveBeenCalledWith(
`/compute/sessions/${mockSession.id}/filerefs/_webout/content`,
@@ -622,7 +622,7 @@ describe('executeScript', () => {
true,
defaultPollOptions,
true
).catch((e) => e)
).catch((e: any) => e)
expect(error).toContain('Error while clearing session.')
})

View File

@@ -59,7 +59,7 @@ describe('pollJobState', () => {
false,
undefined,
defaultPollOptions
).catch((e) => e)
).catch((e: any) => e)
expect((error as Error).message).toContain('Job state link was not found.')
})
@@ -238,7 +238,7 @@ describe('pollJobState', () => {
false,
undefined,
defaultPollOptions
).catch((e) => e)
).catch((e: any) => e)
expect(error.message).toEqual(
'Error while polling job state for job j0b: Status Error'

View File

@@ -17,7 +17,7 @@ describe('saveLog', () => {
it('should throw an error when a valid access token is not provided', async () => {
const error = await saveLog(mockJob, requestClient, 0, 100, stream).catch(
(e) => e
(e: any) => e
)
expect(error.message).toContain(
@@ -33,7 +33,7 @@ describe('saveLog', () => {
100,
stream,
't0k3n'
).catch((e) => e)
).catch((e: any) => e)
expect(error.message).toContain(
`Log URL for job ${mockJob.id} was not found.`

View File

@@ -30,7 +30,7 @@ describe('uploadTables', () => {
.mockImplementation(() => 'ERROR: LARGE STRING LENGTH')
const error = await uploadTables(requestClient, data, 't0k3n').catch(
(e) => e
(e: any) => e
)
expect(requestClient.uploadFile).not.toHaveBeenCalled()
@@ -46,7 +46,7 @@ describe('uploadTables', () => {
.mockImplementation(() => Promise.reject('Upload Error'))
const error = await uploadTables(requestClient, data, 't0k3n').catch(
(e) => e
(e: any) => e
)
expect(error).toContain('Error while uploading file.')

View File

@@ -28,7 +28,7 @@ describe('writeStream', () => {
jest
.spyOn(stream, 'write')
.mockImplementation((_, callback) => callback(new Error('Test Error')))
const error = await writeStream(stream, content).catch((e) => e)
const error = await writeStream(stream, content).catch((e: any) => e)
expect(error.message).toEqual('Test Error')
})

View File

@@ -4,7 +4,7 @@ export const writeStream = async (
stream: WriteStream,
content: string
): Promise<void> =>
stream.write(content + '\n', (e) => {
stream.write(content + '\n', (e: any) => {
if (e) return Promise.reject(e)
return Promise.resolve()

View File

@@ -23,7 +23,7 @@ export class AuthManager {
? '/SASLogon/logout?'
: this.serverType === ServerType.SasViya
? '/SASLogon/logout.do?'
: '/SASjsApi/auth/logout'
: '/SASLogon/logout'
}
/**
@@ -334,19 +334,9 @@ export class AuthManager {
/**
* Logs out of the configured SAS server.
* @param accessToken - an optional access token is required for SASjs server type.
*
*/
public async logOut() {
if (this.serverType === ServerType.Sasjs) {
return this.requestClient
.delete(this.logoutUrl)
.catch(() => true)
.finally(() => {
this.requestClient.clearLocalStorageTokens()
return true
})
}
this.requestClient.clearCsrfTokens()
return this.requestClient.get(this.logoutUrl, undefined).then(() => true)

View File

@@ -53,7 +53,7 @@ describe('getAccessTokenForSasjs', () => {
requestClient,
authConfig.client,
authConfig.refresh_token
).catch((e) => e)
).catch((e: any) => e)
expect(error).toContain('Error while getting access token')
})

View File

@@ -64,7 +64,7 @@ describe('getAccessTokenForViya', () => {
authConfig.client,
authConfig.secret,
authConfig.refresh_token
).catch((e) => e)
).catch((e: any) => e)
expect(error).toContain('Error while getting access token')
})

View File

@@ -62,7 +62,9 @@ describe('getTokens', () => {
const expectedError =
'Unable to obtain new access token. Your refresh token has expired.'
const error = await getTokens(requestClient, authConfig).catch((e) => e)
const error = await getTokens(requestClient, authConfig).catch(
(e: any) => e
)
expect(error.message).toEqual(expectedError)
})

View File

@@ -35,7 +35,7 @@ describe('refreshTokensForSasjs', () => {
const error = await refreshTokensForSasjs(
requestClient,
refresh_token
).catch((e) => e)
).catch((e: any) => e)
expect(error).toContain('Error while refreshing tokens')
})

View File

@@ -63,7 +63,7 @@ describe('refreshTokensForViya', () => {
authConfig.client,
authConfig.secret,
authConfig.refresh_token
).catch((e) => e)
).catch((e: any) => e)
expect(error).toContain('Error while refreshing tokens')
})

View File

@@ -1,5 +1,5 @@
import * as NodeFormData from 'form-data'
import { convertToCSV } from '../utils/convertToCsv'
import { convertToCSV, isFormatsTable } from '../utils/convertToCsv'
import { splitChunks } from '../utils/splitChunks'
export const generateTableUploadForm = (
@@ -13,7 +13,8 @@ export const generateTableUploadForm = (
for (const tableName in data) {
tableCounter++
sasjsTables.push(tableName)
// Formats table should not be sent as part of 'sasjs_tables'
if (!isFormatsTable(tableName)) sasjsTables.push(tableName)
const csv = convertToCSV(data, tableName)

View File

@@ -7,7 +7,6 @@ describe('generateFileUploadForm', () => {
}
const BlobMock = jest.fn()
;(global as any).FormData = FormDataMock
;(global as any).Blob = BlobMock
})

View File

@@ -169,15 +169,23 @@ export class WebJobExecutor extends BaseJobExecutor {
? res.result.log.map((logLine: any) => logLine.line).join('\n')
: res.result.log
const resObj = res
const resObj =
this.serverType === ServerType.Sasjs
? {
result: res.result._webout,
log: parsedSasjsServerLog
}
: res
if (this.serverType === ServerType.Sasjs) {
if (res.result._webout.length < 1)
throw new JobExecutionError(
0,
'Job execution failed',
parsedSasjsServerLog
)
if (
this.serverType === ServerType.Sasjs &&
res.result._webout.length < 1
) {
throw new JobExecutionError(
0,
`No webout was returned by job ${program}. Server type is SASJS and the calling function is WebJobExecutor. Please check the SAS log for more info.`,
parsedSasjsServerLog
)
}
this.requestClient!.appendRequest(resObj, sasJob, config.debug)

View File

@@ -207,7 +207,7 @@ export class RequestClient implements HttpClient {
return this.parseResponse<T>(response)
})
.catch(async (e) => {
.catch(async (e: any) => {
return await this.handleError(
e,
() =>
@@ -248,7 +248,7 @@ export class RequestClient implements HttpClient {
return this.parseResponse<T>(response)
})
.catch(async (e) => {
.catch(async (e: any) => {
return await this.handleError(e, () =>
this.post<T>(url, data, accessToken, contentType, overrideHeaders)
)
@@ -272,7 +272,7 @@ export class RequestClient implements HttpClient {
throwIfError(response)
return this.parseResponse<T>(response)
})
.catch(async (e) => {
.catch(async (e: any) => {
return await this.handleError(e, () =>
this.put<T>(url, data, accessToken, overrideHeaders)
)
@@ -291,7 +291,7 @@ export class RequestClient implements HttpClient {
throwIfError(response)
return this.parseResponse<T>(response)
})
.catch(async (e) => {
.catch(async (e: any) => {
return await this.handleError(e, () => this.delete<T>(url, accessToken))
})
}
@@ -309,7 +309,7 @@ export class RequestClient implements HttpClient {
throwIfError(response)
return this.parseResponse<T>(response)
})
.catch(async (e) => {
.catch(async (e: any) => {
return await this.handleError(e, () =>
this.patch<T>(url, data, accessToken)
)

View File

@@ -68,7 +68,7 @@ export class Sas9RequestClient extends RequestClient {
throwIfError(response)
return this.parseResponse<T>(response)
})
.catch(async (e) => {
.catch(async (e: any) => {
return await this.handleError(
e,
() =>
@@ -113,7 +113,7 @@ export class Sas9RequestClient extends RequestClient {
throwIfError(response)
return this.parseResponse<T>(response)
})
.catch(async (e) => {
.catch(async (e: any) => {
return await this.handleError(e, () =>
this.post<T>(url, data, accessToken, contentType, overrideHeaders)
)

View File

@@ -54,6 +54,17 @@ describe('formatDataForRequest', () => {
expect(formatDataForRequest(data)).toEqual(expectedOutput)
})
it('should accept . as special missing value', () => {
let tableWithMissingValues = {
[testTable]: [{ var: '.' }, { var: 0 }],
[`$${testTable}`]: { formats: { var: 'best.' } }
}
expect(() =>
formatDataForRequest(tableWithMissingValues)
).not.toThrowError()
})
it('should throw an error if special missing values is not valid', () => {
let tableWithMissingValues = {
[testTable]: [{ var: 'AA' }, { var: 0 }],

View File

@@ -1,4 +1,4 @@
import { convertToCSV } from './convertToCsv'
import { convertToCSV, isFormatsTable } from './convertToCsv'
describe('convertToCsv', () => {
const tableName = 'testTable'
@@ -216,7 +216,9 @@ describe('convertToCsv', () => {
const data = { [tableName]: [{ var1: 'string' }] }
expect(() => convertToCSV(data, 'wrongTableName')).toThrow(
new Error('No table provided to be converted to CSV')
new Error(
'Error while converting to CSV. No table provided to be converted to CSV.'
)
)
})
@@ -226,3 +228,15 @@ describe('convertToCsv', () => {
expect(convertToCSV(data, tableName)).toEqual('')
})
})
describe('isFormatsTable', () => {
const tableName = 'sometable'
it('should return true if table name match pattern of formats table', () => {
expect(isFormatsTable(`$${tableName}`)).toEqual(true)
})
it('should return false if table name does not match pattern of formats table', () => {
expect(isFormatsTable(tableName)).toEqual(false)
})
})

View File

@@ -1,4 +1,5 @@
import { isSpecialMissing } from '@sasjs/utils/input/validators'
import { prefixMessage } from '@sasjs/utils/error'
/**
* Converts the given JSON object array to a CSV string.
@@ -9,7 +10,10 @@ export const convertToCSV = (
tableName: string
) => {
if (!data[tableName]) {
throw new Error('No table provided to be converted to CSV')
throw prefixMessage(
'No table provided to be converted to CSV.',
'Error while converting to CSV. '
)
}
const table = data[tableName]
@@ -170,6 +174,12 @@ export const convertToCSV = (
return finalCSV
}
/**
* Checks if table is table of formats (table name should start from '$' character).
* @param tableName - table name.
*/
export const isFormatsTable = (tableName: string) => /^\$.*/.test(tableName)
const getByteSize = (str: string) => {
let byteSize = str.length
for (let i = str.length - 1; i >= 0; i--) {

View File

@@ -1,4 +1,4 @@
import { convertToCSV } from './convertToCsv'
import { convertToCSV, isFormatsTable } from './convertToCsv'
import { splitChunks } from './splitChunks'
export const formatDataForRequest = (data: any) => {
@@ -8,7 +8,7 @@ export const formatDataForRequest = (data: any) => {
for (const tableName in data) {
if (
tableName.match(/^\$.*/) &&
isFormatsTable(tableName) &&
Object.keys(data).includes(tableName.replace(/^\$/, ''))
) {
continue
@@ -16,7 +16,8 @@ export const formatDataForRequest = (data: any) => {
tableCounter++
sasjsTables.push(tableName)
// Formats table should not be sent as part of 'sasjs_tables'
if (!isFormatsTable(tableName)) sasjsTables.push(tableName)
const csv = convertToCSV(data, tableName)

View File

@@ -15,7 +15,7 @@ export const getValidJson = (str: string | object): object => {
if (str === '') return {}
return JSON.parse(str)
} catch (e) {
} catch (e: any) {
if (e instanceof JsonParseArrayError) throw e
throw new InvalidJsonError()
}

View File

@@ -4,7 +4,7 @@ export const parseSasViyaLog = (logResponse: { items: any[] }) => {
log = logResponse.items
? logResponse.items.map((i) => i.line).join('\n')
: JSON.stringify(logResponse)
} catch (e) {
} catch (e: any) {
console.error('An error has occurred while parsing the log response', e)
log = logResponse
}

View File

@@ -8,7 +8,7 @@ export const parseWeboutResponse = (response: string, url?: string): string => {
sasResponse = response
.split('>>weboutBEGIN<<')[1]
.split('>>weboutEND<<')[0]
} catch (e) {
} catch (e: any) {
if (url) throw new WeboutResponseError(url)
sasResponse = ''