mirror of
https://github.com/sasjs/adapter.git
synced 2026-01-04 03:00:05 +00:00
Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a7b4a1de4 | ||
|
|
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,6 +189,8 @@ 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.
|
||||||
|
|
||||||
|
|||||||
17198
package-lock.json
generated
17198
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
15
package.json
15
package.json
@@ -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": "[ -d .git ] && git config core.hooksPath ./.git-hooks || true"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
@@ -43,28 +43,29 @@
|
|||||||
"@types/tough-cookie": "^4.0.0",
|
"@types/tough-cookie": "^4.0.0",
|
||||||
"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.22.0",
|
||||||
"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,
|
||||||
|
|||||||
@@ -25,11 +25,18 @@ import {
|
|||||||
import { formatDataForRequest } from './utils/formatDataForRequest'
|
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 {
|
||||||
import { Logger, LogLevel } from '@sasjs/utils/logger'
|
timestampToYYYYMMDDHHMMSS,
|
||||||
|
isAccessTokenExpiring,
|
||||||
|
isRefreshTokenExpiring,
|
||||||
|
Logger,
|
||||||
|
LogLevel,
|
||||||
|
SasAuthResponse,
|
||||||
|
MacroVar,
|
||||||
|
AuthConfig
|
||||||
|
} from '@sasjs/utils'
|
||||||
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 +137,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 +273,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 +286,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 +295,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 +315,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 +347,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 +369,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 +405,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 +414,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 +425,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 +438,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 +446,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 +469,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 +489,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 +497,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 +516,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 +528,7 @@ export class SASViyaApiClient {
|
|||||||
jobPath,
|
jobPath,
|
||||||
linesOfCode,
|
linesOfCode,
|
||||||
contextName,
|
contextName,
|
||||||
accessToken,
|
authConfig,
|
||||||
data,
|
data,
|
||||||
debug,
|
debug,
|
||||||
false,
|
false,
|
||||||
@@ -602,6 +615,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 +623,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 +635,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 +644,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 +886,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 +911,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 +923,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 +943,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 +963,7 @@ export class SASViyaApiClient {
|
|||||||
sasJob,
|
sasJob,
|
||||||
linesToExecute,
|
linesToExecute,
|
||||||
contextName,
|
contextName,
|
||||||
accessToken,
|
authConfig,
|
||||||
data,
|
data,
|
||||||
debug,
|
debug,
|
||||||
expectWebout,
|
expectWebout,
|
||||||
@@ -974,8 +987,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 +1005,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 +1018,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 +1030,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 +1066,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 +1088,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 +1144,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 +1169,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 +1180,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 +1208,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 +1245,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 +1436,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 +1493,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,
|
||||||
|
|||||||
@@ -6,10 +6,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 +154,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,7 +168,7 @@ 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
|
||||||
}
|
}
|
||||||
@@ -180,13 +178,13 @@ export class SessionManager {
|
|||||||
etag!,
|
etag!,
|
||||||
accessToken
|
accessToken
|
||||||
).catch((err) => {
|
).catch((err) => {
|
||||||
throw 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 +192,14 @@ 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('Could not get session state.')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve(sessionState)
|
resolve(sessionState)
|
||||||
@@ -220,9 +219,6 @@ export class SessionManager {
|
|||||||
.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) => res.result as string)
|
||||||
.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') {
|
||||||
@@ -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.')
|
||||||
|
}
|
||||||
|
|
||||||
|
isValidJson(weboutResponse)
|
||||||
|
parsedResponse = JSON.parse(weboutResponse)
|
||||||
} catch {
|
} catch {
|
||||||
parsedResponse = response.data
|
parsedResponse = response.data
|
||||||
}
|
}
|
||||||
|
|||||||
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,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'
|
||||||
|
|||||||
11
src/utils/isValidJson.ts
Normal file
11
src/utils/isValidJson.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* Checks if string is in valid JSON format else throw error.
|
||||||
|
* @param str - string to check.
|
||||||
|
*/
|
||||||
|
export const isValidJson = (str: string) => {
|
||||||
|
try {
|
||||||
|
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