mirror of
https://github.com/sasjs/adapter.git
synced 2026-01-03 18:50:05 +00:00
Compare commits
56 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7cb2a43f95 | ||
|
|
6e85c7a588 | ||
|
|
a68f6962fd | ||
|
|
a650ba15dd | ||
|
|
6ca1b489fc | ||
|
|
a5c9f11c75 | ||
|
|
d4ebef4290 | ||
| 89590f9a37 | |||
| 5d61bebc9e | |||
| 99afa6e7e4 | |||
| b590a9f41b | |||
| 4466ee30d2 | |||
| db372950b4 | |||
| 46f5e07f11 | |||
|
|
a00cb1ebec | ||
|
|
7b1264d140 | ||
|
|
369b9fb023 | ||
|
|
76487b00e9 | ||
|
|
2d0515e25b | ||
|
|
b132b99586 | ||
|
|
5a7b4a1de4 | ||
|
|
6cac008b61 | ||
|
|
929ec6eb1c | ||
|
|
5a35237de5 | ||
|
|
5d77bbba8b | ||
|
|
eda021b6a5 | ||
|
|
259c479ef0 | ||
|
|
a962b8e7cf | ||
|
|
eb0e7247a6 | ||
| ccc77cb9d1 | |||
|
|
5cb5bbdb55 | ||
|
|
ac6cd7be82 | ||
|
|
63f5f4d03d | ||
|
|
a164fb7df9 | ||
|
|
336ba207cf | ||
|
|
3cfd45cc62 | ||
|
|
f7fb917282 | ||
|
|
a182037883 | ||
|
|
f9e79fb756 | ||
|
|
aaf0eef62b | ||
|
|
fafa0c3567 | ||
|
|
4a6845ad6a | ||
|
|
61d66c6f82 | ||
| 123fbc7235 | |||
|
|
eae8694a29 | ||
|
|
2b16be3aef | ||
|
|
d8d4da9c9a | ||
|
|
0b755b7304 | ||
|
|
0816b7b1f9 | ||
|
|
97d45e87ec | ||
|
|
57ef0647b5 | ||
|
|
2ee6c45d16 | ||
|
|
56b2ba026a | ||
|
|
8beda1ad6c | ||
|
|
b18b471549 | ||
| 93c9a34591 |
@@ -6,7 +6,7 @@ GREEN="\033[1;32m"
|
|||||||
# temporary file which holds the message).
|
# temporary file which holds the message).
|
||||||
commit_message=$(cat "$1")
|
commit_message=$(cat "$1")
|
||||||
|
|
||||||
if (echo "$commit_message" | grep -Eq "^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\([a-z0-9 \-]+\))?!?: .+$") then
|
if (echo "$commit_message" | grep -Eq "^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\([a-z0-9 \-\*]+\))?!?: .+$") then
|
||||||
echo "${GREEN} ✔ Commit message meets Conventional Commit standards"
|
echo "${GREEN} ✔ Commit message meets Conventional Commit standards"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ What code changes have been made to achieve the intent.
|
|||||||
|
|
||||||
## Checks
|
## Checks
|
||||||
|
|
||||||
|
No PR (that involves a non-trivial code change) should be merged, unless all four of the items below are confirmed! If an urgent fix is needed - use a tar file.
|
||||||
|
|
||||||
- [ ] Code is formatted correctly (`npm run lint:fix`).
|
- [ ] Code is formatted correctly (`npm run lint:fix`).
|
||||||
- [ ] All unit tests are passing (`npm test`).
|
- [ ] All unit tests are passing (`npm test`).
|
||||||
- [ ] All `sasjs-cli` unit tests are passing (`npm test`).
|
- [ ] All `sasjs-cli` unit tests are passing (`npm test`).
|
||||||
|
|||||||
@@ -189,9 +189,13 @@ In this setup, all requests are routed through the JES web app, at `YOURSERVER/S
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Note - to use the web approach, the `useComputeApi` property must be `undefined` or `null`.
|
||||||
|
|
||||||
### Using the JES API
|
### Using the JES API
|
||||||
Here we are running Jobs using the Job Execution Service except this time we are making the requests directly using the REST API instead of through the JES Web App. This is helpful when we need to call web services outside of a browser (eg with the SASjs CLI or other commandline tools). To save one network request, the adapter prefetches the JOB URIs and passes them in the `__job` parameter. Depending on your network bandwidth, it may or may not be faster than the JES Web approach.
|
Here we are running Jobs using the Job Execution Service except this time we are making the requests directly using the REST API instead of through the JES Web App. This is helpful when we need to call web services outside of a browser (eg with the SASjs CLI or other commandline tools). To save one network request, the adapter prefetches the JOB URIs and passes them in the `__job` parameter. Depending on your network bandwidth, it may or may not be faster than the JES Web approach.
|
||||||
|
|
||||||
|
This approach (`useComputeApi: false`) also ensures that jobs are displayed in Environment Manager.
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
appLoc:"/Your/Path",
|
appLoc:"/Your/Path",
|
||||||
@@ -204,6 +208,8 @@ Here we are running Jobs using the Job Execution Service except this time we are
|
|||||||
### Using the Compute API
|
### Using the Compute API
|
||||||
This approach is by far the fastest, as a result of the optimisations we have built into the adapter. With this configuration, in the first sasjs request, we take a URI map of the services in the target folder, and create a session manager. This manager will spawn a additional session every time a request is made. Subsequent requests will use the existing 'hot' session, if it exists. Sessions are always deleted after every use, which actually makes this _less_ resource intensive than a typical JES web app, in which all sessions are kept alive by default for 15 minutes.
|
This approach is by far the fastest, as a result of the optimisations we have built into the adapter. With this configuration, in the first sasjs request, we take a URI map of the services in the target folder, and create a session manager. This manager will spawn a additional session every time a request is made. Subsequent requests will use the existing 'hot' session, if it exists. Sessions are always deleted after every use, which actually makes this _less_ resource intensive than a typical JES web app, in which all sessions are kept alive by default for 15 minutes.
|
||||||
|
|
||||||
|
With this approach (`useComputeApi: true`), the requests/logs will _not_ appear in the list in Environment manager.
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
appLoc:"/Your/Path",
|
appLoc:"/Your/Path",
|
||||||
|
|||||||
2630
package-lock.json
generated
2630
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
20
package.json
20
package.json
@@ -3,8 +3,8 @@
|
|||||||
"description": "JavaScript adapter for SAS",
|
"description": "JavaScript adapter for SAS",
|
||||||
"homepage": "https://adapter.sasjs.io",
|
"homepage": "https://adapter.sasjs.io",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "rimraf build && rimraf node && mkdir node && cp -r src/* node && webpack && rimraf build/src && rimraf node",
|
"build": "rimraf build && rimraf node && mkdir node && copyfiles -u 1 \"./src/**/*\" ./node && webpack && rimraf build/src && rimraf node",
|
||||||
"package:lib": "npm run build && cp ./package.json build && cd build && npm version \"5.0.0\" && npm pack",
|
"package:lib": "npm run build && copyfiles ./package.json build && cd build && npm version \"5.0.0\" && npm pack",
|
||||||
"publish:lib": "npm run build && cd build && npm publish",
|
"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 --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": "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": "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}\"",
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
"postpublish": "git clean -fd",
|
"postpublish": "git clean -fd",
|
||||||
"semantic-release": "semantic-release",
|
"semantic-release": "semantic-release",
|
||||||
"typedoc": "typedoc",
|
"typedoc": "typedoc",
|
||||||
"postinstall": "[ -d .git ] && git config core.hooksPath ./.git-hooks || true"
|
"prepare": "git rev-parse --git-dir && git config core.hooksPath ./.git-hooks && git config core.autocrlf false || true"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
@@ -41,30 +41,32 @@
|
|||||||
"@types/jest": "^26.0.23",
|
"@types/jest": "^26.0.23",
|
||||||
"@types/mime": "^2.0.3",
|
"@types/mime": "^2.0.3",
|
||||||
"@types/tough-cookie": "^4.0.0",
|
"@types/tough-cookie": "^4.0.0",
|
||||||
|
"copyfiles": "^2.4.1",
|
||||||
"cp": "^0.2.0",
|
"cp": "^0.2.0",
|
||||||
"dotenv": "^10.0.0",
|
"dotenv": "^10.0.0",
|
||||||
"jest": "^27.0.4",
|
"jest": "^27.0.6",
|
||||||
"jest-extended": "^0.11.5",
|
"jest-extended": "^0.11.5",
|
||||||
"mime": "^2.5.2",
|
"mime": "^2.5.2",
|
||||||
|
"node-polyfill-webpack-plugin": "^1.1.4",
|
||||||
"path": "^0.12.7",
|
"path": "^0.12.7",
|
||||||
"process": "^0.11.10",
|
"process": "^0.11.10",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"semantic-release": "^17.4.4",
|
"semantic-release": "^17.4.4",
|
||||||
"terser-webpack-plugin": "^5.1.3",
|
"terser-webpack-plugin": "^5.1.4",
|
||||||
"ts-jest": "^27.0.3",
|
"ts-jest": "^27.0.3",
|
||||||
"ts-loader": "^9.2.2",
|
"ts-loader": "^9.2.2",
|
||||||
"tslint": "^6.1.3",
|
"tslint": "^6.1.3",
|
||||||
"tslint-config-prettier": "^1.18.0",
|
"tslint-config-prettier": "^1.18.0",
|
||||||
"typedoc": "^0.20.36",
|
"typedoc": "^0.21.2",
|
||||||
"typedoc-neo-theme": "^1.1.1",
|
"typedoc-neo-theme": "^1.1.1",
|
||||||
"typedoc-plugin-external-module-name": "^4.0.6",
|
"typedoc-plugin-external-module-name": "^4.0.6",
|
||||||
"typescript": "^4.3.2",
|
"typescript": "^4.3.4",
|
||||||
"webpack": "^5.38.1",
|
"webpack": "^5.41.1",
|
||||||
"webpack-cli": "^4.7.2"
|
"webpack-cli": "^4.7.2"
|
||||||
},
|
},
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sasjs/utils": "^2.20.1",
|
"@sasjs/utils": "^2.23.2",
|
||||||
"axios": "^0.21.1",
|
"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",
|
||||||
|
|||||||
22100
sasjs-tests/package-lock.json
generated
22100
sasjs-tests/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -25,7 +25,7 @@ export const computeTests = (adapter: SASjs): TestSuite => ({
|
|||||||
'/Public/app/common/sendArr',
|
'/Public/app/common/sendArr',
|
||||||
data,
|
data,
|
||||||
{},
|
{},
|
||||||
'',
|
undefined,
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Context, EditContextInput, ContextAllAttributes } from './types'
|
|||||||
import { isUrl } from './utils'
|
import { isUrl } from './utils'
|
||||||
import { prefixMessage } from '@sasjs/utils/error'
|
import { prefixMessage } from '@sasjs/utils/error'
|
||||||
import { RequestClient } from './request/RequestClient'
|
import { RequestClient } from './request/RequestClient'
|
||||||
|
import { AuthConfig } from '@sasjs/utils/types'
|
||||||
|
|
||||||
export class ContextManager {
|
export class ContextManager {
|
||||||
private defaultComputeContexts = [
|
private defaultComputeContexts = [
|
||||||
@@ -328,12 +329,12 @@ export class ContextManager {
|
|||||||
|
|
||||||
public async getExecutableContexts(
|
public async getExecutableContexts(
|
||||||
executeScript: Function,
|
executeScript: Function,
|
||||||
accessToken?: string
|
authConfig?: AuthConfig
|
||||||
) {
|
) {
|
||||||
const { result: contexts } = await this.requestClient
|
const { result: contexts } = await this.requestClient
|
||||||
.get<{ items: Context[] }>(
|
.get<{ items: Context[] }>(
|
||||||
`${this.serverUrl}/compute/contexts?limit=10000`,
|
`${this.serverUrl}/compute/contexts?limit=10000`,
|
||||||
accessToken
|
authConfig?.access_token
|
||||||
)
|
)
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
throw prefixMessage(err, 'Error while fetching compute contexts.')
|
throw prefixMessage(err, 'Error while fetching compute contexts.')
|
||||||
@@ -350,7 +351,7 @@ export class ContextManager {
|
|||||||
`test-${context.name}`,
|
`test-${context.name}`,
|
||||||
linesOfCode,
|
linesOfCode,
|
||||||
context.name,
|
context.name,
|
||||||
accessToken,
|
authConfig,
|
||||||
null,
|
null,
|
||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
|
|||||||
@@ -26,10 +26,14 @@ import { formatDataForRequest } from './utils/formatDataForRequest'
|
|||||||
import { SessionManager } from './SessionManager'
|
import { SessionManager } from './SessionManager'
|
||||||
import { ContextManager } from './ContextManager'
|
import { ContextManager } from './ContextManager'
|
||||||
import { timestampToYYYYMMDDHHMMSS } from '@sasjs/utils/time'
|
import { timestampToYYYYMMDDHHMMSS } from '@sasjs/utils/time'
|
||||||
|
import {
|
||||||
|
isAccessTokenExpiring,
|
||||||
|
isRefreshTokenExpiring
|
||||||
|
} from '@sasjs/utils/auth'
|
||||||
import { Logger, LogLevel } from '@sasjs/utils/logger'
|
import { Logger, LogLevel } from '@sasjs/utils/logger'
|
||||||
|
import { SasAuthResponse, MacroVar, AuthConfig } from '@sasjs/utils/types'
|
||||||
import { isAuthorizeFormRequired } from './auth/isAuthorizeFormRequired'
|
import { isAuthorizeFormRequired } from './auth/isAuthorizeFormRequired'
|
||||||
import { RequestClient } from './request/RequestClient'
|
import { RequestClient } from './request/RequestClient'
|
||||||
import { SasAuthResponse, MacroVar } from '@sasjs/utils/types'
|
|
||||||
import { prefixMessage } from '@sasjs/utils/error'
|
import { prefixMessage } from '@sasjs/utils/error'
|
||||||
import * as mime from 'mime'
|
import * as mime from 'mime'
|
||||||
|
|
||||||
@@ -130,14 +134,14 @@ export class SASViyaApiClient {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all compute contexts on this server that the user has access to.
|
* Returns all compute contexts on this server that the user has access to.
|
||||||
* @param accessToken - an access token for an authorized user.
|
* @param authConfig - an access token, refresh token, client and secret for an authorized user.
|
||||||
*/
|
*/
|
||||||
public async getExecutableContexts(accessToken?: string) {
|
public async getExecutableContexts(authConfig?: AuthConfig) {
|
||||||
const bindedExecuteScript = this.executeScript.bind(this)
|
const bindedExecuteScript = this.executeScript.bind(this)
|
||||||
|
|
||||||
return await this.contextManager.getExecutableContexts(
|
return await this.contextManager.getExecutableContexts(
|
||||||
bindedExecuteScript,
|
bindedExecuteScript,
|
||||||
accessToken
|
authConfig
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,7 +270,7 @@ export class SASViyaApiClient {
|
|||||||
* @param jobPath - the path to the file being submitted for execution.
|
* @param jobPath - the path to the file being submitted for execution.
|
||||||
* @param linesOfCode - an array of code lines to execute.
|
* @param linesOfCode - an array of code lines to execute.
|
||||||
* @param contextName - the context to execute the code in.
|
* @param contextName - the context to execute the code in.
|
||||||
* @param accessToken - an access token for an authorized user.
|
* @param authConfig - an object containing an access token, refresh token, client ID and secret.
|
||||||
* @param data - execution data.
|
* @param data - execution data.
|
||||||
* @param debug - when set to true, the log will be returned.
|
* @param debug - when set to true, the log will be returned.
|
||||||
* @param expectWebout - when set to true, the automatic _webout fileref will be checked for content, and that content returned. This fileref is used when the Job contains a SASjs web request (as opposed to executing arbitrary SAS code).
|
* @param expectWebout - when set to true, the automatic _webout fileref will be checked for content, and that content returned. This fileref is used when the Job contains a SASjs web request (as opposed to executing arbitrary SAS code).
|
||||||
@@ -279,7 +283,7 @@ export class SASViyaApiClient {
|
|||||||
jobPath: string,
|
jobPath: string,
|
||||||
linesOfCode: string[],
|
linesOfCode: string[],
|
||||||
contextName: string,
|
contextName: string,
|
||||||
accessToken?: string,
|
authConfig?: AuthConfig,
|
||||||
data = null,
|
data = null,
|
||||||
debug: boolean = false,
|
debug: boolean = false,
|
||||||
expectWebout = false,
|
expectWebout = false,
|
||||||
@@ -288,17 +292,18 @@ export class SASViyaApiClient {
|
|||||||
printPid = false,
|
printPid = false,
|
||||||
variables?: MacroVar
|
variables?: MacroVar
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
|
let access_token = (authConfig || {}).access_token
|
||||||
|
if (authConfig) {
|
||||||
|
;({ access_token } = await this.getTokens(authConfig))
|
||||||
|
}
|
||||||
|
|
||||||
|
const logger = process.logger || console
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const headers: any = {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (accessToken) headers.Authorization = `Bearer ${accessToken}`
|
|
||||||
|
|
||||||
let executionSessionId: string
|
let executionSessionId: string
|
||||||
|
|
||||||
const session = await this.sessionManager
|
const session = await this.sessionManager
|
||||||
.getSession(accessToken)
|
.getSession(access_token)
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
throw prefixMessage(err, 'Error while getting session. ')
|
throw prefixMessage(err, 'Error while getting session. ')
|
||||||
})
|
})
|
||||||
@@ -307,7 +312,7 @@ export class SASViyaApiClient {
|
|||||||
|
|
||||||
if (printPid) {
|
if (printPid) {
|
||||||
const { result: jobIdVariable } = await this.sessionManager
|
const { result: jobIdVariable } = await this.sessionManager
|
||||||
.getVariable(executionSessionId, 'SYSJOBID', accessToken)
|
.getVariable(executionSessionId, 'SYSJOBID', access_token)
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
throw prefixMessage(err, 'Error while getting session variable. ')
|
throw prefixMessage(err, 'Error while getting session variable. ')
|
||||||
})
|
})
|
||||||
@@ -339,7 +344,6 @@ export class SASViyaApiClient {
|
|||||||
if (debug) {
|
if (debug) {
|
||||||
jobArguments['_OMITTEXTLOG'] = false
|
jobArguments['_OMITTEXTLOG'] = false
|
||||||
jobArguments['_OMITSESSIONRESULTS'] = false
|
jobArguments['_OMITSESSIONRESULTS'] = false
|
||||||
jobArguments['_DEBUG'] = 131
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let fileName
|
let fileName
|
||||||
@@ -362,11 +366,13 @@ export class SASViyaApiClient {
|
|||||||
|
|
||||||
if (variables) jobVariables = { ...jobVariables, ...variables }
|
if (variables) jobVariables = { ...jobVariables, ...variables }
|
||||||
|
|
||||||
|
if (debug) jobVariables = { ...jobVariables, _DEBUG: 131 }
|
||||||
|
|
||||||
let files: any[] = []
|
let files: any[] = []
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
if (JSON.stringify(data).includes(';')) {
|
if (JSON.stringify(data).includes(';')) {
|
||||||
files = await this.uploadTables(data, accessToken).catch((err) => {
|
files = await this.uploadTables(data, access_token).catch((err) => {
|
||||||
throw prefixMessage(err, 'Error while uploading tables. ')
|
throw prefixMessage(err, 'Error while uploading tables. ')
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -396,7 +402,7 @@ export class SASViyaApiClient {
|
|||||||
.post<Job>(
|
.post<Job>(
|
||||||
`/compute/sessions/${executionSessionId}/jobs`,
|
`/compute/sessions/${executionSessionId}/jobs`,
|
||||||
jobRequestBody,
|
jobRequestBody,
|
||||||
accessToken
|
access_token
|
||||||
)
|
)
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
throw prefixMessage(err, 'Error while posting job. ')
|
throw prefixMessage(err, 'Error while posting job. ')
|
||||||
@@ -405,8 +411,8 @@ export class SASViyaApiClient {
|
|||||||
if (!waitForResult) return session
|
if (!waitForResult) return session
|
||||||
|
|
||||||
if (debug) {
|
if (debug) {
|
||||||
console.log(`Job has been submitted for '${fileName}'.`)
|
logger.info(`Job has been submitted for '${fileName}'.`)
|
||||||
console.log(
|
logger.info(
|
||||||
`You can monitor the job progress at '${this.serverUrl}${
|
`You can monitor the job progress at '${this.serverUrl}${
|
||||||
postedJob.links.find((l: any) => l.rel === 'state')!.href
|
postedJob.links.find((l: any) => l.rel === 'state')!.href
|
||||||
}'.`
|
}'.`
|
||||||
@@ -416,7 +422,7 @@ export class SASViyaApiClient {
|
|||||||
const jobStatus = await this.pollJobState(
|
const jobStatus = await this.pollJobState(
|
||||||
postedJob,
|
postedJob,
|
||||||
etag,
|
etag,
|
||||||
accessToken,
|
authConfig,
|
||||||
pollOptions
|
pollOptions
|
||||||
).catch(async (err) => {
|
).catch(async (err) => {
|
||||||
const error = err?.response?.data
|
const error = err?.response?.data
|
||||||
@@ -429,7 +435,7 @@ export class SASViyaApiClient {
|
|||||||
const logCount = 1000000
|
const logCount = 1000000
|
||||||
err.log = await fetchLogByChunks(
|
err.log = await fetchLogByChunks(
|
||||||
this.requestClient,
|
this.requestClient,
|
||||||
accessToken!,
|
access_token!,
|
||||||
sessionLogUrl,
|
sessionLogUrl,
|
||||||
logCount
|
logCount
|
||||||
)
|
)
|
||||||
@@ -437,10 +443,14 @@ export class SASViyaApiClient {
|
|||||||
throw prefixMessage(err, 'Error while polling job status. ')
|
throw prefixMessage(err, 'Error while polling job status. ')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (authConfig) {
|
||||||
|
;({ access_token } = await this.getTokens(authConfig))
|
||||||
|
}
|
||||||
|
|
||||||
const { result: currentJob } = await this.requestClient
|
const { result: currentJob } = await this.requestClient
|
||||||
.get<Job>(
|
.get<Job>(
|
||||||
`/compute/sessions/${executionSessionId}/jobs/${postedJob.id}`,
|
`/compute/sessions/${executionSessionId}/jobs/${postedJob.id}`,
|
||||||
accessToken
|
access_token
|
||||||
)
|
)
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
throw prefixMessage(err, 'Error while getting job. ')
|
throw prefixMessage(err, 'Error while getting job. ')
|
||||||
@@ -456,7 +466,7 @@ export class SASViyaApiClient {
|
|||||||
const logCount = currentJob.logStatistics?.lineCount ?? 1000000
|
const logCount = currentJob.logStatistics?.lineCount ?? 1000000
|
||||||
log = await fetchLogByChunks(
|
log = await fetchLogByChunks(
|
||||||
this.requestClient,
|
this.requestClient,
|
||||||
accessToken!,
|
access_token!,
|
||||||
logUrl,
|
logUrl,
|
||||||
logCount
|
logCount
|
||||||
)
|
)
|
||||||
@@ -476,7 +486,7 @@ export class SASViyaApiClient {
|
|||||||
|
|
||||||
if (resultLink) {
|
if (resultLink) {
|
||||||
jobResult = await this.requestClient
|
jobResult = await this.requestClient
|
||||||
.get<any>(resultLink, accessToken, 'text/plain')
|
.get<any>(resultLink, access_token, 'text/plain')
|
||||||
.catch(async (e) => {
|
.catch(async (e) => {
|
||||||
if (e instanceof NotFoundError) {
|
if (e instanceof NotFoundError) {
|
||||||
if (logLink) {
|
if (logLink) {
|
||||||
@@ -484,7 +494,7 @@ export class SASViyaApiClient {
|
|||||||
const logCount = currentJob.logStatistics?.lineCount ?? 1000000
|
const logCount = currentJob.logStatistics?.lineCount ?? 1000000
|
||||||
log = await fetchLogByChunks(
|
log = await fetchLogByChunks(
|
||||||
this.requestClient,
|
this.requestClient,
|
||||||
accessToken!,
|
access_token!,
|
||||||
logUrl,
|
logUrl,
|
||||||
logCount
|
logCount
|
||||||
)
|
)
|
||||||
@@ -503,7 +513,7 @@ export class SASViyaApiClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await this.sessionManager
|
await this.sessionManager
|
||||||
.clearSession(executionSessionId, accessToken)
|
.clearSession(executionSessionId, access_token)
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
throw prefixMessage(err, 'Error while clearing session. ')
|
throw prefixMessage(err, 'Error while clearing session. ')
|
||||||
})
|
})
|
||||||
@@ -515,7 +525,7 @@ export class SASViyaApiClient {
|
|||||||
jobPath,
|
jobPath,
|
||||||
linesOfCode,
|
linesOfCode,
|
||||||
contextName,
|
contextName,
|
||||||
accessToken,
|
authConfig,
|
||||||
data,
|
data,
|
||||||
debug,
|
debug,
|
||||||
false,
|
false,
|
||||||
@@ -602,6 +612,7 @@ export class SASViyaApiClient {
|
|||||||
accessToken?: string,
|
accessToken?: string,
|
||||||
isForced?: boolean
|
isForced?: boolean
|
||||||
): Promise<Folder> {
|
): Promise<Folder> {
|
||||||
|
const logger = process.logger || console
|
||||||
if (!parentFolderPath && !parentFolderUri) {
|
if (!parentFolderPath && !parentFolderUri) {
|
||||||
throw new Error('Path or URI of the parent folder is required.')
|
throw new Error('Path or URI of the parent folder is required.')
|
||||||
}
|
}
|
||||||
@@ -609,7 +620,7 @@ export class SASViyaApiClient {
|
|||||||
if (!parentFolderUri && parentFolderPath) {
|
if (!parentFolderUri && parentFolderPath) {
|
||||||
parentFolderUri = await this.getFolderUri(parentFolderPath, accessToken)
|
parentFolderUri = await this.getFolderUri(parentFolderPath, accessToken)
|
||||||
if (!parentFolderUri) {
|
if (!parentFolderUri) {
|
||||||
console.log(
|
logger.info(
|
||||||
`Parent folder at path '${parentFolderPath}' is not present.`
|
`Parent folder at path '${parentFolderPath}' is not present.`
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -621,7 +632,7 @@ export class SASViyaApiClient {
|
|||||||
if (newParentFolderPath === '') {
|
if (newParentFolderPath === '') {
|
||||||
throw new Error('Root folder has to be present on the server.')
|
throw new Error('Root folder has to be present on the server.')
|
||||||
}
|
}
|
||||||
console.log(
|
logger.info(
|
||||||
`Creating parent folder:\n'${newFolderName}' in '${newParentFolderPath}'`
|
`Creating parent folder:\n'${newFolderName}' in '${newParentFolderPath}'`
|
||||||
)
|
)
|
||||||
const parentFolder = await this.createFolder(
|
const parentFolder = await this.createFolder(
|
||||||
@@ -630,7 +641,7 @@ export class SASViyaApiClient {
|
|||||||
undefined,
|
undefined,
|
||||||
accessToken
|
accessToken
|
||||||
)
|
)
|
||||||
console.log(
|
logger.info(
|
||||||
`Parent folder '${newFolderName}' has been successfully created.`
|
`Parent folder '${newFolderName}' has been successfully created.`
|
||||||
)
|
)
|
||||||
parentFolderUri = `/folders/folders/${parentFolder.id}`
|
parentFolderUri = `/folders/folders/${parentFolder.id}`
|
||||||
@@ -872,13 +883,18 @@ export class SASViyaApiClient {
|
|||||||
contextName: string,
|
contextName: string,
|
||||||
debug?: boolean,
|
debug?: boolean,
|
||||||
data?: any,
|
data?: any,
|
||||||
accessToken?: string,
|
authConfig?: AuthConfig,
|
||||||
waitForResult = true,
|
waitForResult = true,
|
||||||
expectWebout = false,
|
expectWebout = false,
|
||||||
pollOptions?: PollOptions,
|
pollOptions?: PollOptions,
|
||||||
printPid = false,
|
printPid = false,
|
||||||
variables?: MacroVar
|
variables?: MacroVar
|
||||||
) {
|
) {
|
||||||
|
let access_token = (authConfig || {}).access_token
|
||||||
|
if (authConfig) {
|
||||||
|
;({ access_token } = await this.getTokens(authConfig))
|
||||||
|
}
|
||||||
|
|
||||||
if (isRelativePath(sasJob) && !this.rootFolderName) {
|
if (isRelativePath(sasJob) && !this.rootFolderName) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Relative paths cannot be used without specifying a root folder name'
|
'Relative paths cannot be used without specifying a root folder name'
|
||||||
@@ -892,7 +908,7 @@ export class SASViyaApiClient {
|
|||||||
? `${this.rootFolderName}/${folderPath}`
|
? `${this.rootFolderName}/${folderPath}`
|
||||||
: folderPath
|
: folderPath
|
||||||
|
|
||||||
await this.populateFolderMap(fullFolderPath, accessToken).catch((err) => {
|
await this.populateFolderMap(fullFolderPath, access_token).catch((err) => {
|
||||||
throw prefixMessage(err, 'Error while populating folder map. ')
|
throw prefixMessage(err, 'Error while populating folder map. ')
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -904,12 +920,6 @@ export class SASViyaApiClient {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const headers: any = { 'Content-Type': 'application/json' }
|
|
||||||
|
|
||||||
if (!!accessToken) {
|
|
||||||
headers.Authorization = `Bearer ${accessToken}`
|
|
||||||
}
|
|
||||||
|
|
||||||
const jobToExecute = jobFolder?.find((item) => item.name === jobName)
|
const jobToExecute = jobFolder?.find((item) => item.name === jobName)
|
||||||
|
|
||||||
if (!jobToExecute) {
|
if (!jobToExecute) {
|
||||||
@@ -930,7 +940,7 @@ export class SASViyaApiClient {
|
|||||||
const { result: jobDefinition } = await this.requestClient
|
const { result: jobDefinition } = await this.requestClient
|
||||||
.get<JobDefinition>(
|
.get<JobDefinition>(
|
||||||
`${this.serverUrl}${jobDefinitionLink.href}`,
|
`${this.serverUrl}${jobDefinitionLink.href}`,
|
||||||
accessToken
|
access_token
|
||||||
)
|
)
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
throw prefixMessage(err, 'Error while getting job definition. ')
|
throw prefixMessage(err, 'Error while getting job definition. ')
|
||||||
@@ -950,7 +960,7 @@ export class SASViyaApiClient {
|
|||||||
sasJob,
|
sasJob,
|
||||||
linesToExecute,
|
linesToExecute,
|
||||||
contextName,
|
contextName,
|
||||||
accessToken,
|
authConfig,
|
||||||
data,
|
data,
|
||||||
debug,
|
debug,
|
||||||
expectWebout,
|
expectWebout,
|
||||||
@@ -974,8 +984,12 @@ export class SASViyaApiClient {
|
|||||||
contextName: string,
|
contextName: string,
|
||||||
debug: boolean,
|
debug: boolean,
|
||||||
data?: any,
|
data?: any,
|
||||||
accessToken?: string
|
authConfig?: AuthConfig
|
||||||
) {
|
) {
|
||||||
|
let access_token = (authConfig || {}).access_token
|
||||||
|
if (authConfig) {
|
||||||
|
;({ access_token } = await this.getTokens(authConfig))
|
||||||
|
}
|
||||||
if (isRelativePath(sasJob) && !this.rootFolderName) {
|
if (isRelativePath(sasJob) && !this.rootFolderName) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Relative paths cannot be used without specifying a root folder name.'
|
'Relative paths cannot be used without specifying a root folder name.'
|
||||||
@@ -988,7 +1002,7 @@ export class SASViyaApiClient {
|
|||||||
const fullFolderPath = isRelativePath(sasJob)
|
const fullFolderPath = isRelativePath(sasJob)
|
||||||
? `${this.rootFolderName}/${folderPath}`
|
? `${this.rootFolderName}/${folderPath}`
|
||||||
: folderPath
|
: folderPath
|
||||||
await this.populateFolderMap(fullFolderPath, accessToken)
|
await this.populateFolderMap(fullFolderPath, access_token)
|
||||||
|
|
||||||
const jobFolder = this.folderMap.get(fullFolderPath)
|
const jobFolder = this.folderMap.get(fullFolderPath)
|
||||||
if (!jobFolder) {
|
if (!jobFolder) {
|
||||||
@@ -1001,7 +1015,7 @@ export class SASViyaApiClient {
|
|||||||
|
|
||||||
let files: any[] = []
|
let files: any[] = []
|
||||||
if (data && Object.keys(data).length) {
|
if (data && Object.keys(data).length) {
|
||||||
files = await this.uploadTables(data, accessToken)
|
files = await this.uploadTables(data, access_token)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!jobToExecute) {
|
if (!jobToExecute) {
|
||||||
@@ -1013,7 +1027,7 @@ export class SASViyaApiClient {
|
|||||||
|
|
||||||
const { result: jobDefinition } = await this.requestClient.get<Job>(
|
const { result: jobDefinition } = await this.requestClient.get<Job>(
|
||||||
`${this.serverUrl}${jobDefinitionLink}`,
|
`${this.serverUrl}${jobDefinitionLink}`,
|
||||||
accessToken
|
access_token
|
||||||
)
|
)
|
||||||
|
|
||||||
const jobArguments: { [key: string]: any } = {
|
const jobArguments: { [key: string]: any } = {
|
||||||
@@ -1049,18 +1063,18 @@ export class SASViyaApiClient {
|
|||||||
const { result: postedJob, etag } = await this.requestClient.post<Job>(
|
const { result: postedJob, etag } = await this.requestClient.post<Job>(
|
||||||
`${this.serverUrl}/jobExecution/jobs?_action=wait`,
|
`${this.serverUrl}/jobExecution/jobs?_action=wait`,
|
||||||
postJobRequestBody,
|
postJobRequestBody,
|
||||||
accessToken
|
access_token
|
||||||
)
|
)
|
||||||
const jobStatus = await this.pollJobState(
|
const jobStatus = await this.pollJobState(
|
||||||
postedJob,
|
postedJob,
|
||||||
etag,
|
etag,
|
||||||
accessToken
|
authConfig
|
||||||
).catch((err) => {
|
).catch((err) => {
|
||||||
throw prefixMessage(err, 'Error while polling job status. ')
|
throw prefixMessage(err, 'Error while polling job status. ')
|
||||||
})
|
})
|
||||||
const { result: currentJob } = await this.requestClient.get<Job>(
|
const { result: currentJob } = await this.requestClient.get<Job>(
|
||||||
`${this.serverUrl}/jobExecution/jobs/${postedJob.id}`,
|
`${this.serverUrl}/jobExecution/jobs/${postedJob.id}`,
|
||||||
accessToken
|
access_token
|
||||||
)
|
)
|
||||||
|
|
||||||
let jobResult
|
let jobResult
|
||||||
@@ -1071,13 +1085,13 @@ export class SASViyaApiClient {
|
|||||||
if (resultLink) {
|
if (resultLink) {
|
||||||
jobResult = await this.requestClient.get<any>(
|
jobResult = await this.requestClient.get<any>(
|
||||||
`${this.serverUrl}${resultLink}/content`,
|
`${this.serverUrl}${resultLink}/content`,
|
||||||
accessToken,
|
access_token,
|
||||||
'text/plain'
|
'text/plain'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (debug && logLink) {
|
if (debug && logLink) {
|
||||||
log = await this.requestClient
|
log = await this.requestClient
|
||||||
.get<any>(`${this.serverUrl}${logLink.href}/content`, accessToken)
|
.get<any>(`${this.serverUrl}${logLink.href}/content`, access_token)
|
||||||
.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') {
|
||||||
@@ -1127,12 +1141,18 @@ export class SASViyaApiClient {
|
|||||||
private async pollJobState(
|
private async pollJobState(
|
||||||
postedJob: any,
|
postedJob: any,
|
||||||
etag: string | null,
|
etag: string | null,
|
||||||
accessToken?: string,
|
authConfig?: AuthConfig,
|
||||||
pollOptions?: PollOptions
|
pollOptions?: PollOptions
|
||||||
) {
|
) {
|
||||||
|
const logger = process.logger || console
|
||||||
|
|
||||||
let POLL_INTERVAL = 300
|
let POLL_INTERVAL = 300
|
||||||
let MAX_POLL_COUNT = 1000
|
let MAX_POLL_COUNT = 1000
|
||||||
let MAX_ERROR_COUNT = 5
|
let MAX_ERROR_COUNT = 5
|
||||||
|
let access_token = (authConfig || {}).access_token
|
||||||
|
if (authConfig) {
|
||||||
|
;({ access_token } = await this.getTokens(authConfig))
|
||||||
|
}
|
||||||
|
|
||||||
if (pollOptions) {
|
if (pollOptions) {
|
||||||
POLL_INTERVAL = pollOptions.POLL_INTERVAL || POLL_INTERVAL
|
POLL_INTERVAL = pollOptions.POLL_INTERVAL || POLL_INTERVAL
|
||||||
@@ -1146,8 +1166,8 @@ export class SASViyaApiClient {
|
|||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'If-None-Match': etag
|
'If-None-Match': etag
|
||||||
}
|
}
|
||||||
if (accessToken) {
|
if (access_token) {
|
||||||
headers.Authorization = `Bearer ${accessToken}`
|
headers.Authorization = `Bearer ${access_token}`
|
||||||
}
|
}
|
||||||
const stateLink = postedJob.links.find((l: any) => l.rel === 'state')
|
const stateLink = postedJob.links.find((l: any) => l.rel === 'state')
|
||||||
if (!stateLink) {
|
if (!stateLink) {
|
||||||
@@ -1157,7 +1177,7 @@ export class SASViyaApiClient {
|
|||||||
const { result: state } = await this.requestClient
|
const { result: state } = await this.requestClient
|
||||||
.get<string>(
|
.get<string>(
|
||||||
`${this.serverUrl}${stateLink.href}?_action=wait&wait=300`,
|
`${this.serverUrl}${stateLink.href}?_action=wait&wait=300`,
|
||||||
accessToken,
|
access_token,
|
||||||
'text/plain',
|
'text/plain',
|
||||||
{},
|
{},
|
||||||
this.debug
|
this.debug
|
||||||
@@ -1185,11 +1205,15 @@ export class SASViyaApiClient {
|
|||||||
postedJobState === 'pending' ||
|
postedJobState === 'pending' ||
|
||||||
postedJobState === 'unavailable'
|
postedJobState === 'unavailable'
|
||||||
) {
|
) {
|
||||||
|
if (authConfig) {
|
||||||
|
;({ access_token } = await this.getTokens(authConfig))
|
||||||
|
}
|
||||||
|
|
||||||
if (stateLink) {
|
if (stateLink) {
|
||||||
const { result: jobState } = await this.requestClient
|
const { result: jobState } = await this.requestClient
|
||||||
.get<string>(
|
.get<string>(
|
||||||
`${this.serverUrl}${stateLink.href}?_action=wait&wait=300`,
|
`${this.serverUrl}${stateLink.href}?_action=wait&wait=300`,
|
||||||
accessToken,
|
access_token,
|
||||||
'text/plain',
|
'text/plain',
|
||||||
{},
|
{},
|
||||||
this.debug
|
this.debug
|
||||||
@@ -1218,8 +1242,8 @@ export class SASViyaApiClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.debug && printedState !== postedJobState) {
|
if (this.debug && printedState !== postedJobState) {
|
||||||
console.log('Polling job status...')
|
logger.info('Polling job status...')
|
||||||
console.log(`Current job state: ${postedJobState}`)
|
logger.info(`Current job state: ${postedJobState}`)
|
||||||
|
|
||||||
printedState = postedJobState
|
printedState = postedJobState
|
||||||
}
|
}
|
||||||
@@ -1409,6 +1433,9 @@ export class SASViyaApiClient {
|
|||||||
accessToken
|
accessToken
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (!sourceFolderUri) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
const sourceFolderId = sourceFolderUri?.split('/').pop()
|
const sourceFolderId = sourceFolderUri?.split('/').pop()
|
||||||
|
|
||||||
const { result: folder } = await this.requestClient
|
const { result: folder } = await this.requestClient
|
||||||
@@ -1463,4 +1490,21 @@ export class SASViyaApiClient {
|
|||||||
|
|
||||||
return movedFolder
|
return movedFolder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async getTokens(authConfig: AuthConfig): Promise<AuthConfig> {
|
||||||
|
const logger = process.logger || console
|
||||||
|
let { access_token, refresh_token, client, secret } = authConfig
|
||||||
|
if (
|
||||||
|
isAccessTokenExpiring(access_token) ||
|
||||||
|
isRefreshTokenExpiring(refresh_token)
|
||||||
|
) {
|
||||||
|
logger.info('Refreshing access and refresh tokens.')
|
||||||
|
;({ access_token, refresh_token } = await this.refreshTokens(
|
||||||
|
client,
|
||||||
|
secret,
|
||||||
|
refresh_token
|
||||||
|
))
|
||||||
|
}
|
||||||
|
return { access_token, refresh_token, client, secret }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
28
src/SASjs.ts
28
src/SASjs.ts
@@ -4,7 +4,7 @@ import { SASViyaApiClient } from './SASViyaApiClient'
|
|||||||
import { SAS9ApiClient } from './SAS9ApiClient'
|
import { SAS9ApiClient } from './SAS9ApiClient'
|
||||||
import { FileUploader } from './FileUploader'
|
import { FileUploader } from './FileUploader'
|
||||||
import { AuthManager } from './auth'
|
import { AuthManager } from './auth'
|
||||||
import { ServerType, MacroVar } from '@sasjs/utils/types'
|
import { ServerType, MacroVar, AuthConfig } from '@sasjs/utils/types'
|
||||||
import { RequestClient } from './request/RequestClient'
|
import { RequestClient } from './request/RequestClient'
|
||||||
import {
|
import {
|
||||||
JobExecutor,
|
JobExecutor,
|
||||||
@@ -103,12 +103,12 @@ export default class SASjs {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets executable compute contexts.
|
* Gets executable compute contexts.
|
||||||
* @param accessToken - an access token for an authorized user.
|
* @param authConfig - an access token, refresh token, client and secret for an authorized user.
|
||||||
*/
|
*/
|
||||||
public async getExecutableContexts(accessToken: string) {
|
public async getExecutableContexts(authConfig: AuthConfig) {
|
||||||
this.isMethodSupported('getExecutableContexts', ServerType.SasViya)
|
this.isMethodSupported('getExecutableContexts', ServerType.SasViya)
|
||||||
|
|
||||||
return await this.sasViyaApiClient!.getExecutableContexts(accessToken)
|
return await this.sasViyaApiClient!.getExecutableContexts(authConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -240,14 +240,14 @@ export default class SASjs {
|
|||||||
* @param fileName - name of the file to run. It will be converted to path to the file being submitted for execution.
|
* @param fileName - name of the file to run. It will be converted to path to the file being submitted for execution.
|
||||||
* @param linesOfCode - lines of sas code from the file to run.
|
* @param linesOfCode - lines of sas code from the file to run.
|
||||||
* @param contextName - context name on which code will be run on the server.
|
* @param contextName - context name on which code will be run on the server.
|
||||||
* @param accessToken - (optional) the access token for authorizing the request.
|
* @param authConfig - (optional) the access token, refresh token, client and secret for authorizing the request.
|
||||||
* @param debug - (optional) if true, global debug config will be overriden
|
* @param debug - (optional) if true, global debug config will be overriden
|
||||||
*/
|
*/
|
||||||
public async executeScriptSASViya(
|
public async executeScriptSASViya(
|
||||||
fileName: string,
|
fileName: string,
|
||||||
linesOfCode: string[],
|
linesOfCode: string[],
|
||||||
contextName: string,
|
contextName: string,
|
||||||
accessToken?: string,
|
authConfig?: AuthConfig,
|
||||||
debug?: boolean
|
debug?: boolean
|
||||||
) {
|
) {
|
||||||
this.isMethodSupported('executeScriptSASViya', ServerType.SasViya)
|
this.isMethodSupported('executeScriptSASViya', ServerType.SasViya)
|
||||||
@@ -261,7 +261,7 @@ export default class SASjs {
|
|||||||
fileName,
|
fileName,
|
||||||
linesOfCode,
|
linesOfCode,
|
||||||
contextName,
|
contextName,
|
||||||
accessToken,
|
authConfig,
|
||||||
null,
|
null,
|
||||||
debug ? debug : this.sasjsConfig.debug
|
debug ? debug : this.sasjsConfig.debug
|
||||||
)
|
)
|
||||||
@@ -579,7 +579,7 @@ export default class SASjs {
|
|||||||
data: { [key: string]: any } | null,
|
data: { [key: string]: any } | null,
|
||||||
config: { [key: string]: any } = {},
|
config: { [key: string]: any } = {},
|
||||||
loginRequiredCallback?: () => any,
|
loginRequiredCallback?: () => any,
|
||||||
accessToken?: string,
|
authConfig?: AuthConfig,
|
||||||
extraResponseAttributes: ExtraResponseAttributes[] = []
|
extraResponseAttributes: ExtraResponseAttributes[] = []
|
||||||
) {
|
) {
|
||||||
config = {
|
config = {
|
||||||
@@ -601,7 +601,7 @@ export default class SASjs {
|
|||||||
data,
|
data,
|
||||||
config,
|
config,
|
||||||
loginRequiredCallback,
|
loginRequiredCallback,
|
||||||
accessToken
|
authConfig
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
return await this.jesJobExecutor!.execute(
|
return await this.jesJobExecutor!.execute(
|
||||||
@@ -609,7 +609,7 @@ export default class SASjs {
|
|||||||
data,
|
data,
|
||||||
config,
|
config,
|
||||||
loginRequiredCallback,
|
loginRequiredCallback,
|
||||||
accessToken,
|
authConfig,
|
||||||
extraResponseAttributes
|
extraResponseAttributes
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -625,7 +625,7 @@ export default class SASjs {
|
|||||||
data,
|
data,
|
||||||
config,
|
config,
|
||||||
loginRequiredCallback,
|
loginRequiredCallback,
|
||||||
accessToken,
|
authConfig,
|
||||||
extraResponseAttributes
|
extraResponseAttributes
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -776,7 +776,7 @@ export default class SASjs {
|
|||||||
* @param config - provide any changes to the config here, for instance to
|
* @param config - provide any changes to the config here, for instance to
|
||||||
* enable/disable `debug`. Any change provided will override the global config,
|
* enable/disable `debug`. Any change provided will override the global config,
|
||||||
* for that particular function call.
|
* for that particular function call.
|
||||||
* @param accessToken - a valid access token that is authorised to execute compute jobs.
|
* @param authConfig - a valid client, secret, refresh and access tokens that are authorised to execute compute jobs.
|
||||||
* The access token is not required when the user is authenticated via the browser.
|
* The access token is not required when the user is authenticated via the browser.
|
||||||
* @param waitForResult - a boolean that indicates whether the function needs to wait for execution to complete.
|
* @param waitForResult - a boolean that indicates whether the function needs to wait for execution to complete.
|
||||||
* @param pollOptions - an object that represents poll interval(milliseconds) and maximum amount of attempts. Object example: { MAX_POLL_COUNT: 24 * 60 * 60, POLL_INTERVAL: 1000 }.
|
* @param pollOptions - an object that represents poll interval(milliseconds) and maximum amount of attempts. Object example: { MAX_POLL_COUNT: 24 * 60 * 60, POLL_INTERVAL: 1000 }.
|
||||||
@@ -787,7 +787,7 @@ export default class SASjs {
|
|||||||
sasJob: string,
|
sasJob: string,
|
||||||
data: any,
|
data: any,
|
||||||
config: any = {},
|
config: any = {},
|
||||||
accessToken?: string,
|
authConfig?: AuthConfig,
|
||||||
waitForResult?: boolean,
|
waitForResult?: boolean,
|
||||||
pollOptions?: PollOptions,
|
pollOptions?: PollOptions,
|
||||||
printPid = false,
|
printPid = false,
|
||||||
@@ -810,7 +810,7 @@ export default class SASjs {
|
|||||||
config.contextName,
|
config.contextName,
|
||||||
config.debug,
|
config.debug,
|
||||||
data,
|
data,
|
||||||
accessToken,
|
authConfig,
|
||||||
!!waitForResult,
|
!!waitForResult,
|
||||||
false,
|
false,
|
||||||
pollOptions,
|
pollOptions,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Session, Context, CsrfToken, SessionVariable } from './types'
|
import { Session, Context, SessionVariable } from './types'
|
||||||
|
import { NoSessionStateError } from './types/errors'
|
||||||
import { asyncForEach, isUrl } from './utils'
|
import { asyncForEach, isUrl } from './utils'
|
||||||
import { prefixMessage } from '@sasjs/utils/error'
|
import { prefixMessage } from '@sasjs/utils/error'
|
||||||
import { RequestClient } from './request/RequestClient'
|
import { RequestClient } from './request/RequestClient'
|
||||||
@@ -6,10 +7,6 @@ import { RequestClient } from './request/RequestClient'
|
|||||||
const MAX_SESSION_COUNT = 1
|
const MAX_SESSION_COUNT = 1
|
||||||
const RETRY_LIMIT: number = 3
|
const RETRY_LIMIT: number = 3
|
||||||
let RETRY_COUNT: number = 0
|
let RETRY_COUNT: number = 0
|
||||||
const INTERNAL_SAS_ERROR = {
|
|
||||||
status: 304,
|
|
||||||
message: 'Not Modified'
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SessionManager {
|
export class SessionManager {
|
||||||
constructor(
|
constructor(
|
||||||
@@ -158,11 +155,13 @@ export class SessionManager {
|
|||||||
etag: string | null,
|
etag: string | null,
|
||||||
accessToken?: string
|
accessToken?: string
|
||||||
) {
|
) {
|
||||||
|
const logger = process.logger || console
|
||||||
|
|
||||||
let sessionState = session.state
|
let sessionState = session.state
|
||||||
|
|
||||||
const stateLink = session.links.find((l: any) => l.rel === 'state')
|
const stateLink = session.links.find((l: any) => l.rel === 'state')
|
||||||
|
|
||||||
return new Promise(async (resolve, _) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
if (
|
if (
|
||||||
sessionState === 'pending' ||
|
sessionState === 'pending' ||
|
||||||
sessionState === 'running' ||
|
sessionState === 'running' ||
|
||||||
@@ -170,23 +169,24 @@ export class SessionManager {
|
|||||||
) {
|
) {
|
||||||
if (stateLink) {
|
if (stateLink) {
|
||||||
if (this.debug && !this.printedSessionState.printed) {
|
if (this.debug && !this.printedSessionState.printed) {
|
||||||
console.log('Polling session status...')
|
logger.info('Polling session status...')
|
||||||
|
|
||||||
this.printedSessionState.printed = true
|
this.printedSessionState.printed = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const state = await this.getSessionState(
|
const { result: state, responseStatus: responseStatus } =
|
||||||
`${this.serverUrl}${stateLink.href}?wait=30`,
|
await this.getSessionState(
|
||||||
etag!,
|
`${this.serverUrl}${stateLink.href}?wait=30`,
|
||||||
accessToken
|
etag!,
|
||||||
).catch((err) => {
|
accessToken
|
||||||
throw err
|
).catch((err) => {
|
||||||
})
|
throw prefixMessage(err, 'Error while getting session state.')
|
||||||
|
})
|
||||||
|
|
||||||
sessionState = state.trim()
|
sessionState = state.trim()
|
||||||
|
|
||||||
if (this.debug && this.printedSessionState.state !== sessionState) {
|
if (this.debug && this.printedSessionState.state !== sessionState) {
|
||||||
console.log(`Current session state is '${sessionState}'`)
|
logger.info(`Current session state is '${sessionState}'`)
|
||||||
|
|
||||||
this.printedSessionState.state = sessionState
|
this.printedSessionState.state = sessionState
|
||||||
this.printedSessionState.printed = false
|
this.printedSessionState.printed = false
|
||||||
@@ -194,13 +194,21 @@ export class SessionManager {
|
|||||||
|
|
||||||
// There is an internal error present in SAS Viya 3.5
|
// There is an internal error present in SAS Viya 3.5
|
||||||
// Retry to wait for a session status in such case of SAS internal error
|
// Retry to wait for a session status in such case of SAS internal error
|
||||||
if (
|
if (!sessionState) {
|
||||||
sessionState === INTERNAL_SAS_ERROR.message &&
|
if (RETRY_COUNT < RETRY_LIMIT) {
|
||||||
RETRY_COUNT < RETRY_LIMIT
|
RETRY_COUNT++
|
||||||
) {
|
|
||||||
RETRY_COUNT++
|
|
||||||
|
|
||||||
resolve(this.waitForSession(session, etag, accessToken))
|
resolve(this.waitForSession(session, etag, accessToken))
|
||||||
|
} else {
|
||||||
|
reject(
|
||||||
|
new NoSessionStateError(
|
||||||
|
responseStatus,
|
||||||
|
this.serverUrl + stateLink.href,
|
||||||
|
session.links.find((l: any) => l.rel === 'log')
|
||||||
|
?.href as string
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve(sessionState)
|
resolve(sessionState)
|
||||||
@@ -218,11 +226,11 @@ export class SessionManager {
|
|||||||
) {
|
) {
|
||||||
return await this.requestClient
|
return await this.requestClient
|
||||||
.get(url, accessToken, 'text/plain', { 'If-None-Match': etag })
|
.get(url, accessToken, 'text/plain', { 'If-None-Match': etag })
|
||||||
.then((res) => res.result as string)
|
.then((res) => ({
|
||||||
|
result: res.result as string,
|
||||||
|
responseStatus: res.status
|
||||||
|
}))
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
if (err.status === INTERNAL_SAS_ERROR.status)
|
|
||||||
return INTERNAL_SAS_ERROR.message
|
|
||||||
|
|
||||||
throw err
|
throw err
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { ServerType } from '@sasjs/utils/types'
|
import { ServerType } from '@sasjs/utils/types'
|
||||||
import { isAuthorizeFormRequired } from '.'
|
|
||||||
import { RequestClient } from '../request/RequestClient'
|
import { RequestClient } from '../request/RequestClient'
|
||||||
import { serialize } from '../utils'
|
import { serialize } from '../utils'
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ServerType } from '@sasjs/utils/types'
|
import { AuthConfig, ServerType } from '@sasjs/utils/types'
|
||||||
import { SASViyaApiClient } from '../SASViyaApiClient'
|
import { SASViyaApiClient } from '../SASViyaApiClient'
|
||||||
import {
|
import {
|
||||||
ErrorResponse,
|
ErrorResponse,
|
||||||
@@ -17,7 +17,7 @@ export class ComputeJobExecutor extends BaseJobExecutor {
|
|||||||
data: any,
|
data: any,
|
||||||
config: any,
|
config: any,
|
||||||
loginRequiredCallback?: any,
|
loginRequiredCallback?: any,
|
||||||
accessToken?: string
|
authConfig?: AuthConfig
|
||||||
) {
|
) {
|
||||||
const loginCallback = loginRequiredCallback || (() => Promise.resolve())
|
const loginCallback = loginRequiredCallback || (() => Promise.resolve())
|
||||||
const waitForResult = true
|
const waitForResult = true
|
||||||
@@ -30,7 +30,7 @@ export class ComputeJobExecutor extends BaseJobExecutor {
|
|||||||
config.contextName,
|
config.contextName,
|
||||||
config.debug,
|
config.debug,
|
||||||
data,
|
data,
|
||||||
accessToken,
|
authConfig,
|
||||||
waitForResult,
|
waitForResult,
|
||||||
expectWebout
|
expectWebout
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ServerType } from '@sasjs/utils/types'
|
import { AuthConfig, ServerType } from '@sasjs/utils/types'
|
||||||
import { SASViyaApiClient } from '../SASViyaApiClient'
|
import { SASViyaApiClient } from '../SASViyaApiClient'
|
||||||
import {
|
import {
|
||||||
ErrorResponse,
|
ErrorResponse,
|
||||||
@@ -18,20 +18,14 @@ export class JesJobExecutor extends BaseJobExecutor {
|
|||||||
data: any,
|
data: any,
|
||||||
config: any,
|
config: any,
|
||||||
loginRequiredCallback?: any,
|
loginRequiredCallback?: any,
|
||||||
accessToken?: string,
|
authConfig?: AuthConfig,
|
||||||
extraResponseAttributes: ExtraResponseAttributes[] = []
|
extraResponseAttributes: ExtraResponseAttributes[] = []
|
||||||
) {
|
) {
|
||||||
const loginCallback = loginRequiredCallback || (() => Promise.resolve())
|
const loginCallback = loginRequiredCallback || (() => Promise.resolve())
|
||||||
|
|
||||||
const requestPromise = new Promise((resolve, reject) => {
|
const requestPromise = new Promise((resolve, reject) => {
|
||||||
this.sasViyaApiClient
|
this.sasViyaApiClient
|
||||||
?.executeJob(
|
?.executeJob(sasJob, config.contextName, config.debug, data, authConfig)
|
||||||
sasJob,
|
|
||||||
config.contextName,
|
|
||||||
config.debug,
|
|
||||||
data,
|
|
||||||
accessToken
|
|
||||||
)
|
|
||||||
.then((response: any) => {
|
.then((response: any) => {
|
||||||
this.appendRequest(response, sasJob, config.debug)
|
this.appendRequest(response, sasJob, config.debug)
|
||||||
|
|
||||||
@@ -69,7 +63,7 @@ export class JesJobExecutor extends BaseJobExecutor {
|
|||||||
data,
|
data,
|
||||||
config,
|
config,
|
||||||
loginRequiredCallback,
|
loginRequiredCallback,
|
||||||
accessToken,
|
authConfig,
|
||||||
extraResponseAttributes
|
extraResponseAttributes
|
||||||
).then(
|
).then(
|
||||||
(res: any) => {
|
(res: any) => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ServerType } from '@sasjs/utils/types'
|
import { AuthConfig, ServerType } from '@sasjs/utils/types'
|
||||||
import { SASjsRequest } from '../types'
|
import { SASjsRequest } from '../types'
|
||||||
import { ExtraResponseAttributes } from '@sasjs/utils/types'
|
import { ExtraResponseAttributes } from '@sasjs/utils/types'
|
||||||
import { asyncForEach, parseGeneratedCode, parseSourceCode } from '../utils'
|
import { asyncForEach, parseGeneratedCode, parseSourceCode } from '../utils'
|
||||||
@@ -11,7 +11,7 @@ export interface JobExecutor {
|
|||||||
data: any,
|
data: any,
|
||||||
config: any,
|
config: any,
|
||||||
loginRequiredCallback?: any,
|
loginRequiredCallback?: any,
|
||||||
accessToken?: string,
|
authConfig?: AuthConfig,
|
||||||
extraResponseAttributes?: ExtraResponseAttributes[]
|
extraResponseAttributes?: ExtraResponseAttributes[]
|
||||||
) => Promise<any>
|
) => Promise<any>
|
||||||
resendWaitingRequests: () => Promise<void>
|
resendWaitingRequests: () => Promise<void>
|
||||||
@@ -30,7 +30,7 @@ export abstract class BaseJobExecutor implements JobExecutor {
|
|||||||
data: any,
|
data: any,
|
||||||
config: any,
|
config: any,
|
||||||
loginRequiredCallback?: any,
|
loginRequiredCallback?: any,
|
||||||
accessToken?: string | undefined,
|
authConfig?: AuthConfig | undefined,
|
||||||
extraResponseAttributes?: ExtraResponseAttributes[]
|
extraResponseAttributes?: ExtraResponseAttributes[]
|
||||||
): Promise<any>
|
): Promise<any>
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,9 @@ import { generateFileUploadForm } from '../file/generateFileUploadForm'
|
|||||||
import { generateTableUploadForm } from '../file/generateTableUploadForm'
|
import { generateTableUploadForm } from '../file/generateTableUploadForm'
|
||||||
import { RequestClient } from '../request/RequestClient'
|
import { RequestClient } from '../request/RequestClient'
|
||||||
import { SASViyaApiClient } from '../SASViyaApiClient'
|
import { SASViyaApiClient } from '../SASViyaApiClient'
|
||||||
import { isRelativePath } from '../utils'
|
import { isRelativePath, isValidJson } from '../utils'
|
||||||
import { BaseJobExecutor } from './JobExecutor'
|
import { BaseJobExecutor } from './JobExecutor'
|
||||||
|
import { parseWeboutResponse } from '../utils/parseWeboutResponse'
|
||||||
|
|
||||||
export interface WaitingRequstPromise {
|
export interface WaitingRequstPromise {
|
||||||
promise: Promise<any> | null
|
promise: Promise<any> | null
|
||||||
@@ -100,6 +101,19 @@ export class WebJobExecutor extends BaseJobExecutor {
|
|||||||
this.appendRequest(res, sasJob, config.debug)
|
this.appendRequest(res, sasJob, config.debug)
|
||||||
resolve(jsonResponse)
|
resolve(jsonResponse)
|
||||||
}
|
}
|
||||||
|
if (this.serverType === ServerType.Sas9 && config.debug) {
|
||||||
|
const jsonResponse = parseWeboutResponse(res.result as string)
|
||||||
|
if (jsonResponse === '') {
|
||||||
|
throw new Error(
|
||||||
|
'Valid JSON could not be extracted from response.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
isValidJson(jsonResponse)
|
||||||
|
this.appendRequest(res, sasJob, config.debug)
|
||||||
|
resolve(res.result)
|
||||||
|
}
|
||||||
|
isValidJson(res.result as string)
|
||||||
this.appendRequest(res, sasJob, config.debug)
|
this.appendRequest(res, sasJob, config.debug)
|
||||||
resolve(res.result)
|
resolve(res.result)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
import { parseWeboutResponse } from '../utils/parseWeboutResponse'
|
import { parseWeboutResponse } from '../utils/parseWeboutResponse'
|
||||||
import { prefixMessage } from '@sasjs/utils/error'
|
import { prefixMessage } from '@sasjs/utils/error'
|
||||||
import { SAS9AuthError } from '../types/errors/SAS9AuthError'
|
import { SAS9AuthError } from '../types/errors/SAS9AuthError'
|
||||||
|
import { isValidJson } from '../utils'
|
||||||
|
|
||||||
export interface HttpClient {
|
export interface HttpClient {
|
||||||
get<T>(
|
get<T>(
|
||||||
@@ -63,6 +64,9 @@ export class RequestClient implements HttpClient {
|
|||||||
baseURL: baseUrl
|
baseURL: baseUrl
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.httpClient.defaults.validateStatus = (status) =>
|
||||||
|
status >= 200 && status < 305
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCsrfToken(type: 'general' | 'file' = 'general') {
|
public getCsrfToken(type: 'general' | 'file' = 'general') {
|
||||||
@@ -80,7 +84,7 @@ export class RequestClient implements HttpClient {
|
|||||||
contentType: string = 'application/json',
|
contentType: string = 'application/json',
|
||||||
overrideHeaders: { [key: string]: string | number } = {},
|
overrideHeaders: { [key: string]: string | number } = {},
|
||||||
debug: boolean = false
|
debug: boolean = false
|
||||||
): Promise<{ result: T; etag: string }> {
|
): Promise<{ result: T; etag: string; status: number }> {
|
||||||
const headers = {
|
const headers = {
|
||||||
...this.getHeaders(accessToken, contentType),
|
...this.getHeaders(accessToken, contentType),
|
||||||
...overrideHeaders
|
...overrideHeaders
|
||||||
@@ -287,7 +291,8 @@ export class RequestClient implements HttpClient {
|
|||||||
})
|
})
|
||||||
.then((res) => res.data)
|
.then((res) => res.data)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.log(error)
|
const logger = process.logger || console
|
||||||
|
logger.error(error)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -419,7 +424,13 @@ export class RequestClient implements HttpClient {
|
|||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
try {
|
try {
|
||||||
parsedResponse = JSON.parse(parseWeboutResponse(response.data))
|
const weboutResponse = parseWeboutResponse(response.data)
|
||||||
|
if (weboutResponse === '') {
|
||||||
|
throw new Error('Valid JSON could not be extracted from response.')
|
||||||
|
}
|
||||||
|
|
||||||
|
const jsonResponse = isValidJson(weboutResponse)
|
||||||
|
parsedResponse = jsonResponse
|
||||||
} catch {
|
} catch {
|
||||||
parsedResponse = response.data
|
parsedResponse = response.data
|
||||||
}
|
}
|
||||||
@@ -427,9 +438,15 @@ export class RequestClient implements HttpClient {
|
|||||||
includeSAS9Log = true
|
includeSAS9Log = true
|
||||||
}
|
}
|
||||||
|
|
||||||
let responseToReturn: { result: T; etag: any; log?: string } = {
|
let responseToReturn: {
|
||||||
|
result: T
|
||||||
|
etag: any
|
||||||
|
log?: string
|
||||||
|
status: number
|
||||||
|
} = {
|
||||||
result: parsedResponse as T,
|
result: parsedResponse as T,
|
||||||
etag
|
etag,
|
||||||
|
status: response.status
|
||||||
}
|
}
|
||||||
|
|
||||||
if (includeSAS9Log) {
|
if (includeSAS9Log) {
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export class Sas9RequestClient extends RequestClient {
|
|||||||
contentType: string = 'application/json',
|
contentType: string = 'application/json',
|
||||||
overrideHeaders: { [key: string]: string | number } = {},
|
overrideHeaders: { [key: string]: string | number } = {},
|
||||||
debug: boolean = false
|
debug: boolean = false
|
||||||
): Promise<{ result: T; etag: string }> {
|
): Promise<{ result: T; etag: string; status: number }> {
|
||||||
const headers = {
|
const headers = {
|
||||||
...this.getHeaders(accessToken, contentType),
|
...this.getHeaders(accessToken, contentType),
|
||||||
...overrideHeaders
|
...overrideHeaders
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { SessionManager } from '../SessionManager'
|
import { SessionManager } from '../SessionManager'
|
||||||
import * as dotenv from 'dotenv'
|
|
||||||
import { RequestClient } from '../request/RequestClient'
|
import { RequestClient } from '../request/RequestClient'
|
||||||
|
import { NoSessionStateError } from '../types/errors'
|
||||||
|
import * as dotenv from 'dotenv'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
|
|
||||||
jest.mock('axios')
|
jest.mock('axios')
|
||||||
const mockedAxios = axios as jest.Mocked<typeof axios>
|
const mockedAxios = axios as jest.Mocked<typeof axios>
|
||||||
|
|
||||||
@@ -43,4 +45,38 @@ describe('SessionManager', () => {
|
|||||||
).resolves.toEqual(expectedResponse)
|
).resolves.toEqual(expectedResponse)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('waitForSession', () => {
|
||||||
|
it('should reject with NoSessionStateError if SAS server did not provide session state', async () => {
|
||||||
|
const responseStatus = 304
|
||||||
|
|
||||||
|
mockedAxios.get.mockImplementation(() =>
|
||||||
|
Promise.resolve({ data: '', status: responseStatus })
|
||||||
|
)
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
sessionManager['waitForSession'](
|
||||||
|
{
|
||||||
|
id: 'id',
|
||||||
|
state: '',
|
||||||
|
links: [
|
||||||
|
{ rel: 'state', href: '', uri: '', type: '', method: 'GET' }
|
||||||
|
],
|
||||||
|
attributes: {
|
||||||
|
sessionInactiveTimeout: 0
|
||||||
|
},
|
||||||
|
creationTimeStamp: ''
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
'access_token'
|
||||||
|
)
|
||||||
|
).rejects.toEqual(
|
||||||
|
new NoSessionStateError(
|
||||||
|
responseStatus,
|
||||||
|
process.env.SERVER_URL as string,
|
||||||
|
'logUrl'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
31
src/test/utils/isValidJson.spec.ts
Normal file
31
src/test/utils/isValidJson.spec.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { isValidJson } from '../../utils'
|
||||||
|
|
||||||
|
describe('jsonValidator', () => {
|
||||||
|
it('should not throw an error with an valid json', () => {
|
||||||
|
const json = {
|
||||||
|
test: 'test'
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(isValidJson(json)).toBe(json)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not throw an error with an valid json string', () => {
|
||||||
|
const json = {
|
||||||
|
test: 'test'
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(isValidJson(JSON.stringify(json))).toStrictEqual(json)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw an error with an invalid json', () => {
|
||||||
|
const json = `{\"test\":\"test\"\"test2\":\"test\"}`
|
||||||
|
|
||||||
|
expect(() => {
|
||||||
|
try {
|
||||||
|
isValidJson(json)
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error()
|
||||||
|
}
|
||||||
|
}).toThrowError
|
||||||
|
})
|
||||||
|
})
|
||||||
5
src/types/Process.d.ts
vendored
Normal file
5
src/types/Process.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
declare namespace NodeJS {
|
||||||
|
export interface Process {
|
||||||
|
logger?: import('@sasjs/utils/logger').Logger
|
||||||
|
}
|
||||||
|
}
|
||||||
15
src/types/errors/NoSessionStateError.ts
Normal file
15
src/types/errors/NoSessionStateError.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
export class NoSessionStateError extends Error {
|
||||||
|
constructor(
|
||||||
|
public serverResponseStatus: number,
|
||||||
|
public sessionStateUrl: string,
|
||||||
|
public logUrl: string
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
`Could not get session state. Server responded with ${serverResponseStatus} whilst checking state: ${sessionStateUrl}`
|
||||||
|
)
|
||||||
|
|
||||||
|
this.name = 'NoSessionStatus'
|
||||||
|
|
||||||
|
Object.setPrototypeOf(this, NoSessionStateError.prototype)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,3 +5,4 @@ export * from './JobExecutionError'
|
|||||||
export * from './LoginRequiredError'
|
export * from './LoginRequiredError'
|
||||||
export * from './NotFoundError'
|
export * from './NotFoundError'
|
||||||
export * from './ErrorResponse'
|
export * from './ErrorResponse'
|
||||||
|
export * from './NoSessionStateError'
|
||||||
|
|||||||
@@ -15,12 +15,14 @@ export const fetchLogByChunks = async (
|
|||||||
logUrl: string,
|
logUrl: string,
|
||||||
logCount: number
|
logCount: number
|
||||||
): Promise<string> => {
|
): Promise<string> => {
|
||||||
|
const logger = process.logger || console
|
||||||
|
|
||||||
let log: string = ''
|
let log: string = ''
|
||||||
|
|
||||||
const loglimit = logCount < 10000 ? logCount : 10000
|
const loglimit = logCount < 10000 ? logCount : 10000
|
||||||
let start = 0
|
let start = 0
|
||||||
do {
|
do {
|
||||||
console.log(
|
logger.info(
|
||||||
`Fetching logs from line no: ${start + 1} to ${
|
`Fetching logs from line no: ${start + 1} to ${
|
||||||
start + loglimit
|
start + loglimit
|
||||||
} of ${logCount}.`
|
} of ${logCount}.`
|
||||||
|
|||||||
@@ -12,3 +12,4 @@ export * from './serialize'
|
|||||||
export * from './splitChunks'
|
export * from './splitChunks'
|
||||||
export * from './parseWeboutResponse'
|
export * from './parseWeboutResponse'
|
||||||
export * from './fetchLogByChunks'
|
export * from './fetchLogByChunks'
|
||||||
|
export * from './isValidJson'
|
||||||
|
|||||||
13
src/utils/isValidJson.ts
Normal file
13
src/utils/isValidJson.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* Checks if string is in valid JSON format else throw error.
|
||||||
|
* @param str - string to check.
|
||||||
|
*/
|
||||||
|
export const isValidJson = (str: string | object) => {
|
||||||
|
try {
|
||||||
|
if (typeof str === 'object') return str
|
||||||
|
|
||||||
|
return JSON.parse(str)
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error('Invalid JSON response.')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
const path = require('path')
|
const path = require('path')
|
||||||
const webpack = require('webpack')
|
const webpack = require('webpack')
|
||||||
const terserPlugin = require('terser-webpack-plugin')
|
const terserPlugin = require('terser-webpack-plugin')
|
||||||
|
const nodePolyfillPlugin = require('node-polyfill-webpack-plugin')
|
||||||
|
|
||||||
const defaultPlugins = [
|
const defaultPlugins = [
|
||||||
new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /en/),
|
new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /en/),
|
||||||
@@ -37,7 +38,7 @@ const browserConfig = {
|
|||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: ['.ts', '.js'],
|
extensions: ['.ts', '.js'],
|
||||||
fallback: { https: false }
|
fallback: { https: false, fs: false, readline: false }
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
filename: 'index.js',
|
filename: 'index.js',
|
||||||
@@ -49,7 +50,8 @@ const browserConfig = {
|
|||||||
...defaultPlugins,
|
...defaultPlugins,
|
||||||
new webpack.ProvidePlugin({
|
new webpack.ProvidePlugin({
|
||||||
process: 'process/browser'
|
process: 'process/browser'
|
||||||
})
|
}),
|
||||||
|
new nodePolyfillPlugin()
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user