1
0
mirror of https://github.com/sasjs/adapter.git synced 2026-01-05 19:50:06 +00:00

Compare commits

..

1 Commits

Author SHA1 Message Date
Krishna Acondy
232f4ec3fb chore(*): add tests for SessionManager 2020-11-24 07:42:18 +00:00
60 changed files with 3168 additions and 10992 deletions

View File

@@ -1,2 +0,0 @@
SERVER_URL=https://server.com
DEFAULT_COMPUTE_CONTEXT=SAS Job Execution compute context

View File

@@ -1,9 +0,0 @@
groups:
- name: SASjs Devs # name of the group
reviewers: 1 # how many reviewers do you want to assign?
usernames: # github usernames of the reviewers
- krishna-acondy
- YuryShkoda
- saadjutt01
- medjedovicm
- allanbowe

View File

@@ -1,13 +0,0 @@
name: 'Assign Reviewer'
on:
pull_request:
types: [opened]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: uesteibar/reviewer-lottery@v1
with:
repo-token: ${{ secrets.GH_TOKEN }}

View File

@@ -27,6 +27,16 @@ jobs:
run: npm run lint run: npm run lint
- name: Run unit tests - name: Run unit tests
run: npm test run: npm test
env:
CI: true
CLIENT: ${{secrets.CLIENT}}
SECRET: ${{secrets.SECRET}}
SAS_USERNAME: ${{secrets.SAS_USERNAME}}
SAS_PASSWORD: ${{secrets.SAS_PASSWORD}}
SERVER_URL: ${{secrets.SERVER_URL}}
SERVER_TYPE: ${{secrets.SERVER_TYPE}}
ACCESS_TOKEN: ${{secrets.ACCESS_TOKEN}}
REFRESH_TOKEN: ${{secrets.REFRESH_TOKEN}}
- name: Build Package - name: Build Package
run: npm run package:lib run: npm run package:lib
env: env:

2
.gitignore vendored
View File

@@ -1,4 +1,2 @@
node_modules node_modules
build build
.env

View File

@@ -14,5 +14,5 @@ What code changes have been made to achieve the intent.
- [ ] 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-tests` unit tests are passing (`npm test`).
- [ ] All `sasjs-tests` are passing (instructions available [here](https://github.com/sasjs/adapter/blob/master/sasjs-tests/README.md)). - [ ] All `sasjs-tests` are passing (instructions available [here](https://github.com/sasjs/adapter/blob/master/sasjs-tests/README.md)).

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

4729
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
"name": "@sasjs/adapter", "name": "@sasjs/adapter",
"description": "JavaScript adapter for SAS", "description": "JavaScript adapter for SAS",
"scripts": { "scripts": {
"build": "rimraf build && rimraf node && mkdir node && cp -r src/* node && webpack && rimraf build/src && rimraf node", "build": "rimraf build && webpack",
"package:lib": "npm run build && cp ./package.json build && cd build && npm version \"5.0.0\" && npm pack", "package:lib": "npm run build && cp ./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}'", "lint:fix": "npx prettier --write 'src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}'",
@@ -43,7 +43,7 @@
"jest": "^25.5.4", "jest": "^25.5.4",
"path": "^0.12.7", "path": "^0.12.7",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"semantic-release": "^17.3.0", "semantic-release": "^17.2.3",
"terser-webpack-plugin": "^4.2.3", "terser-webpack-plugin": "^4.2.3",
"ts-jest": "^25.5.1", "ts-jest": "^25.5.1",
"ts-loader": "^8.0.11", "ts-loader": "^8.0.11",
@@ -58,7 +58,6 @@
}, },
"main": "index.js", "main": "index.js",
"dependencies": { "dependencies": {
"@sasjs/utils": "^1.5.0",
"es6-promise": "^4.2.8", "es6-promise": "^4.2.8",
"form-data": "^3.0.0", "form-data": "^3.0.0",
"isomorphic-fetch": "^2.2.1" "isomorphic-fetch": "^2.2.1"

View File

@@ -1357,9 +1357,9 @@
"integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==" "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw=="
}, },
"@sasjs/adapter": { "@sasjs/adapter": {
"version": "1.18.3", "version": "1.12.0",
"resolved": "https://registry.npmjs.org/@sasjs/adapter/-/adapter-1.18.3.tgz", "resolved": "https://registry.npmjs.org/@sasjs/adapter/-/adapter-1.12.0.tgz",
"integrity": "sha512-wzDFJRyt2dXFeQP+JzqRGunYUbujrAbU/Jc4IWg5URsCkGAzwsNl/4G0xJVbqOTy1MvoZ431rfCnvhkUlg7D3Q==", "integrity": "sha512-0uGQH9ynomWzdBaEujEtcR38q6V7LCgG0mrb1Wellv6cC/IHD3j6WfeZZAgtiMPeOSJjbCDBOlVnzC2TlBqJFw==",
"requires": { "requires": {
"es6-promise": "^4.2.8", "es6-promise": "^4.2.8",
"form-data": "^3.0.0", "form-data": "^3.0.0",

View File

@@ -4,7 +4,7 @@
"homepage": ".", "homepage": ".",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@sasjs/adapter": "^1.18.2", "@sasjs/adapter": "^1.12.0",
"@sasjs/test-framework": "^1.4.0", "@sasjs/test-framework": "^1.4.0",
"@testing-library/jest-dom": "^4.2.4", "@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.5.0", "@testing-library/react": "^9.5.0",

View File

@@ -0,0 +1,9 @@
import React from "react";
import { render } from "@testing-library/react";
import App from "./App";
test("renders learn react link", () => {
const { getByText } = render(<App />);
const linkElement = getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});

View File

@@ -3,12 +3,12 @@ import { TestSuite } from "@sasjs/test-framework";
const defaultConfig: SASjsConfig = { const defaultConfig: SASjsConfig = {
serverUrl: window.location.origin, serverUrl: window.location.origin,
pathSAS9: '/SASStoredProcess/do', pathSAS9: "/SASStoredProcess/do",
pathSASViya: '/SASJobExecution', pathSASViya: "/SASJobExecution",
appLoc: '/Public/seedapp', appLoc: "/Public/seedapp",
serverType: ServerType.SASViya, serverType: ServerType.SASViya,
debug: false, debug: true,
contextName: 'SAS Job Execution compute context', contextName: "SAS Job Execution compute context",
useComputeApi: false useComputeApi: false
}; };
@@ -57,7 +57,6 @@ export const basicTests = (
}, },
assertion: (sasjsInstance: SASjs) => { assertion: (sasjsInstance: SASjs) => {
const sasjsConfig = sasjsInstance.getSasjsConfig(); const sasjsConfig = sasjsInstance.getSasjsConfig();
return ( return (
sasjsConfig.serverUrl === defaultConfig.serverUrl && sasjsConfig.serverUrl === defaultConfig.serverUrl &&
sasjsConfig.pathSAS9 === defaultConfig.pathSAS9 && sasjsConfig.pathSAS9 === defaultConfig.pathSAS9 &&

View File

@@ -25,71 +25,17 @@ export const computeTests = (adapter: SASjs): TestSuite => ({
}, },
assertion: (res: any) => { assertion: (res: any) => {
const expectedProperties = ["id", "state", "creationTimeStamp", "jobConditionCode"] const expectedProperties = ["id", "state", "creationTimeStamp", "jobConditionCode"]
return validate(expectedProperties, res.result); return validate(expectedProperties, res);
}
},
{
title: "Execute Script Viya - complete job",
description: "Should execute sas file and return log",
test: () => {
const fileLines = [
`data;`,
`do x=1 to 100;`,
`output;`,
`end;`,
`run;`
]
return adapter.executeScriptSASViya(
'sasCode.sas',
fileLines,
'SAS Studio compute context',
undefined,
true
)
},
assertion: (res: any) => {
const expectedLogContent = `1 data;\\n2 do x=1 to 100;\\n3 output;\\n4 end;\\n5 run;\\n\\n`
return validateLog(expectedLogContent, res.log);
}
},
{
title: "Execute Script Viya - failed job",
description: "Should execute sas file and return log",
test: () => {
const fileLines = [
`%abort;`
]
return adapter.executeScriptSASViya(
'sasCode.sas',
fileLines,
'SAS Studio compute context',
undefined,
true
).catch((err: any) => err )
},
assertion: (res: any) => {
const expectedLogContent = `1 %abort;\\nERROR: The %ABORT statement is not valid in open code.\\n`
return validateLog(expectedLogContent, res.log);
} }
} }
] ]
}); });
const validateLog = (text: string, log: string): boolean => {
const isValid = JSON.stringify(log).includes(text)
return isValid
}
const validate = (expectedProperties: string[], data: any): boolean => { const validate = (expectedProperties: string[], data: any): boolean => {
const actualProperties = Object.keys(data); const actualProperties = Object.keys(data);
const isValid = expectedProperties.every( const isValid = expectedProperties.every(
(property) => actualProperties.includes(property) (property) => actualProperties.includes(property)
); );
return isValid return isValid
} }

View File

@@ -88,7 +88,7 @@ export const sendArrTests = (adapter: SASjs): TestSuite => ({
return adapter.request("common/sendArr", data).catch((e) => e); return adapter.request("common/sendArr", data).catch((e) => e);
}, },
assertion: (error: any) => { assertion: (error: any) => {
return !!error && !!error.error && !!error.error.message; return !!error && !!error.body && !!error.body.message;
} }
}, },
{ {
@@ -185,7 +185,7 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
}; };
return adapter.request("common/sendObj", invalidData).catch((e) => e); return adapter.request("common/sendObj", invalidData).catch((e) => e);
}, },
assertion: (error: any) => !!error && !!error.error && !!error.error.message assertion: (error: any) => !!error && !!error.body && !!error.body.message
}, },
{ {
title: "Single string value", title: "Single string value",
@@ -219,7 +219,7 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
.catch((e) => e); .catch((e) => e);
}, },
assertion: (error: any) => { assertion: (error: any) => {
return !!error && !!error.error && !!error.error.message; return !!error && !!error.body && !!error.body.message;
} }
}, },
{ {

View File

@@ -23,24 +23,22 @@ export const sasjsRequestTests = (adapter: SASjs): TestSuite => ({
}, },
{ {
title: "Make error and capture log", title: "Make error and capture log",
description: "Should make an error and capture log, in the same time it is testing if debug override is working", description: "Should make an error and capture log",
test: async () => { test: async () => {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
adapter adapter
.request("common/makeErr", data, {debug: true}) .request("common/makeErr", data)
.then((res) => { .then((res) => {
//no action here, this request must throw error //no action here, this request must throw error
}) })
.catch((err) => { .catch((err) => {
let sasRequests = adapter.getSasRequests(); let sasRequests = adapter.getSasRequests();
let makeErrRequest: any = let makeErrRequest =
sasRequests.find((req) => sasRequests.find((req) =>
req.serviceLink.includes("makeErr") req.serviceLink.includes("makeErr")
) || null; ) || null;
if (!makeErrRequest) resolve(false) resolve(!!makeErrRequest);
resolve(!!(makeErrRequest.logFile && makeErrRequest.logFile.length > 0));
}); });
}); });
}, },

View File

@@ -16,13 +16,11 @@ import {
Folder, Folder,
CsrfToken, CsrfToken,
EditContextInput, EditContextInput,
JobDefinition, ErrorResponse,
PollOptions JobDefinition
} from './types' } from './types'
import { formatDataForRequest } from './utils/formatDataForRequest' import { formatDataForRequest } from './utils/formatDataForRequest'
import { SessionManager } from './SessionManager' import { SessionManager } from './SessionManager'
import { timestampToYYYYMMDDHHMMSS } from '@sasjs/utils/time'
import { Logger, LogLevel } from '@sasjs/utils/logger'
/** /**
* A client for interfacing with the SAS Viya REST API. * A client for interfacing with the SAS Viya REST API.
@@ -156,7 +154,6 @@ export class SASViyaApiClient {
context.name, context.name,
accessToken, accessToken,
null, null,
false,
true, true,
true true
).catch((err) => err) ).catch((err) => err)
@@ -167,27 +164,30 @@ export class SASViyaApiClient {
for (const promise of promises) results.push(await promise()) for (const promise of promises) results.push(await promise())
results.forEach((result: any, index: number) => { results.forEach((result: any, index: number) => {
if (result && result.log) { if (result && result.error && result.error.details) {
try { try {
const resultParsed = result.log const resultParsed = result.error.details
let sysUserId = ''
const sysUserIdLog = resultParsed if (resultParsed && resultParsed.body) {
.split('\n') let sysUserId = ''
.find((line: string) => line.startsWith('SYSUSERID='))
if (sysUserIdLog) { const sysUserIdLog = resultParsed.body
sysUserId = sysUserIdLog.replace('SYSUSERID=', '') .split('\n')
.find((line: string) => line.startsWith('SYSUSERID='))
executableContexts.push({ if (sysUserIdLog) {
createdBy: contextsList[index].createdBy, sysUserId = sysUserIdLog.replace('SYSUSERID=', '')
id: contextsList[index].id,
name: contextsList[index].name, executableContexts.push({
version: contextsList[index].version, createdBy: contextsList[index].createdBy,
attributes: { id: contextsList[index].id,
sysUserId name: contextsList[index].name,
} version: contextsList[index].version,
}) attributes: {
sysUserId
}
})
}
} }
} catch (error) { } catch (error) {
throw error throw error
@@ -426,12 +426,10 @@ export class SASViyaApiClient {
* @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 accessToken - an access token for an authorized user.
* @param sessionId - optional session ID to reuse.
* @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).
* @param waitForResult - when set to true, function will return the session
* @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 printPid - a boolean that indicates whether the function should print (PID) of the started job.
*/ */
public async executeScript( public async executeScript(
jobPath: string, jobPath: string,
@@ -439,11 +437,8 @@ export class SASViyaApiClient {
contextName: string, contextName: string,
accessToken?: string, accessToken?: string,
data = null, data = null,
debug: boolean = false,
expectWebout = false, expectWebout = false,
waitForResult = true, waitForResult = true
pollOptions?: PollOptions,
printPid = false
): Promise<any> { ): Promise<any> {
try { try {
const headers: any = { const headers: any = {
@@ -463,28 +458,6 @@ export class SASViyaApiClient {
executionSessionId = session!.id executionSessionId = session!.id
if (printPid) {
const { result: jobIdVariable } = await this.sessionManager.getVariable(
executionSessionId,
'SYSJOBID',
accessToken
)
if (jobIdVariable && jobIdVariable.value) {
const relativeJobPath = this.rootFolderName
? jobPath.split(this.rootFolderName).join('').replace(/^\//, '')
: jobPath
const logger = new Logger(debug ? LogLevel.Debug : LogLevel.Info)
logger.info(
`Triggered '${relativeJobPath}' with PID ${
jobIdVariable.value
} at ${timestampToYYYYMMDDHHMMSS()}`
)
}
}
const jobArguments: { [key: string]: any } = { const jobArguments: { [key: string]: any } = {
_contextName: contextName, _contextName: contextName,
_OMITJSONLISTING: true, _OMITJSONLISTING: true,
@@ -494,7 +467,7 @@ export class SASViyaApiClient {
_OMITTEXTLOG: true _OMITTEXTLOG: true
} }
if (debug) { if (this.debug) {
jobArguments['_OMITTEXTLOG'] = false jobArguments['_OMITTEXTLOG'] = false
jobArguments['_OMITSESSIONRESULTS'] = false jobArguments['_OMITSESSIONRESULTS'] = false
jobArguments['_DEBUG'] = 131 jobArguments['_DEBUG'] = 131
@@ -562,7 +535,7 @@ export class SASViyaApiClient {
return session return session
} }
if (debug) { if (this.debug) {
console.log(`Job has been submitted for '${fileName}'.`) console.log(`Job has been submitted for '${fileName}'.`)
console.log( console.log(
`You can monitor the job progress at '${this.serverUrl}${ `You can monitor the job progress at '${this.serverUrl}${
@@ -571,12 +544,7 @@ export class SASViyaApiClient {
) )
} }
const jobStatus = await this.pollJobState( const jobStatus = await this.pollJobState(postedJob, etag, accessToken)
postedJob,
etag,
accessToken,
pollOptions
)
const { result: currentJob } = await this.request<Job>( const { result: currentJob } = await this.request<Job>(
`${this.serverUrl}/compute/sessions/${executionSessionId}/jobs/${postedJob.id}`, `${this.serverUrl}/compute/sessions/${executionSessionId}/jobs/${postedJob.id}`,
@@ -590,7 +558,7 @@ export class SASViyaApiClient {
const logLink = currentJob.links.find((l) => l.rel === 'log') const logLink = currentJob.links.find((l) => l.rel === 'log')
if (debug && logLink) { if (this.debug && logLink) {
log = await this.request<any>( log = await this.request<any>(
`${this.serverUrl}${logLink.href}/content?limit=10000`, `${this.serverUrl}${logLink.href}/content?limit=10000`,
{ {
@@ -606,7 +574,7 @@ export class SASViyaApiClient {
} }
if (jobStatus === 'failed' || jobStatus === 'error') { if (jobStatus === 'failed' || jobStatus === 'error') {
return Promise.reject({ job: currentJob, log }) return Promise.reject({ error: currentJob.error, log })
} }
let resultLink let resultLink
@@ -614,7 +582,7 @@ export class SASViyaApiClient {
if (expectWebout) { if (expectWebout) {
resultLink = `/compute/sessions/${executionSessionId}/filerefs/_webout/content` resultLink = `/compute/sessions/${executionSessionId}/filerefs/_webout/content`
} else { } else {
return { job: currentJob, log } return currentJob
} }
if (resultLink) { if (resultLink) {
@@ -638,10 +606,12 @@ export class SASViyaApiClient {
throw err throw err
}) })
return Promise.reject({ return Promise.reject(
status: 500, new ErrorResponse('Job execution failed.', {
log: log status: 500,
}) body: log
})
)
} }
} }
return { return {
@@ -665,7 +635,6 @@ export class SASViyaApiClient {
contextName, contextName,
accessToken, accessToken,
data, data,
debug,
false, false,
true true
) )
@@ -982,19 +951,14 @@ export class SASViyaApiClient {
* @param accessToken - an optional access token for an authorized user. * @param accessToken - an optional access token for an authorized user.
* @param waitForResult - a boolean indicating if the function should wait for a result. * @param waitForResult - a boolean indicating if the function should wait for a result.
* @param expectWebout - a boolean indicating whether to expect a _webout response. * @param expectWebout - a boolean indicating whether to expect a _webout response.
* @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 printPid - a boolean that indicates whether the function should print (PID) of the started job.
*/ */
public async executeComputeJob( public async executeComputeJob(
sasJob: string, sasJob: string,
contextName: string, contextName: string,
debug?: boolean,
data?: any, data?: any,
accessToken?: string, accessToken?: string,
waitForResult = true, waitForResult = true,
expectWebout = false, expectWebout = false
pollOptions?: PollOptions,
printPid = false
) { ) {
if (isRelativePath(sasJob) && !this.rootFolderName) { if (isRelativePath(sasJob) && !this.rootFolderName) {
throw new Error( throw new Error(
@@ -1077,11 +1041,8 @@ export class SASViyaApiClient {
contextName, contextName,
accessToken, accessToken,
data, data,
debug,
expectWebout, expectWebout,
waitForResult, waitForResult
pollOptions,
printPid
) )
} }
@@ -1266,7 +1227,7 @@ export class SASViyaApiClient {
throw new Error(`The path ${path} does not exist on ${this.serverUrl}`) throw new Error(`The path ${path} does not exist on ${this.serverUrl}`)
} }
const { result: members } = await this.request<{ items: any[] }>( const { result: members } = await this.request<{ items: any[] }>(
`${this.serverUrl}/folders/folders/${folder.id}/members?limit=${folder.memberCount}`, `${this.serverUrl}/folders/folders/${folder.id}/members`,
requestInfo requestInfo
) )
@@ -1274,21 +1235,13 @@ export class SASViyaApiClient {
this.folderMap.set(path, itemsAtRoot) this.folderMap.set(path, itemsAtRoot)
} }
// REFACTOR: set default value for 'pollOptions' attribute
private async pollJobState( private async pollJobState(
postedJob: any, postedJob: any,
etag: string | null, etag: string | null,
accessToken?: string, accessToken?: string
pollOptions?: PollOptions
) { ) {
let POLL_INTERVAL = 100 const MAX_POLL_COUNT = 1000
let MAX_POLL_COUNT = 1000 const POLL_INTERVAL = 100
if (pollOptions) {
POLL_INTERVAL = pollOptions.POLL_INTERVAL || POLL_INTERVAL
MAX_POLL_COUNT = pollOptions.MAX_POLL_COUNT || MAX_POLL_COUNT
}
let postedJobState = '' let postedJobState = ''
let pollCount = 0 let pollCount = 0
const headers: any = { const headers: any = {

View File

@@ -32,8 +32,7 @@ import {
CsrfToken, CsrfToken,
UploadFile, UploadFile,
EditContextInput, EditContextInput,
ErrorResponse, ErrorResponse
PollOptions
} from './types' } from './types'
import { SASViyaApiClient } from './SASViyaApiClient' import { SASViyaApiClient } from './SASViyaApiClient'
import { SAS9ApiClient } from './SAS9ApiClient' import { SAS9ApiClient } from './SAS9ApiClient'
@@ -206,20 +205,11 @@ export default class SASjs {
return await this.sasViyaApiClient!.createSession(contextName, accessToken) return await this.sasViyaApiClient!.createSession(contextName, accessToken)
} }
/**
* Executes the sas code against given sas server
* @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 contextName - context name on which code will be run on the server.
* @param accessToken - (optional) the access token for authorizing the request.
* @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, accessToken?: string
debug?: boolean
) { ) {
this.isMethodSupported('executeScriptSASViya', ServerType.SASViya) this.isMethodSupported('executeScriptSASViya', ServerType.SASViya)
@@ -228,8 +218,7 @@ export default class SASjs {
linesOfCode, linesOfCode,
contextName, contextName,
accessToken, accessToken,
null, null
debug ? debug : this.sasjsConfig.debug
) )
} }
@@ -722,17 +711,13 @@ export default class SASjs {
* @param accessToken - a valid access token that is authorised to execute compute jobs. * @param accessToken - a valid access token that is 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 printPid - a boolean that indicates whether the function should print (PID) of the started job.
*/ */
public async startComputeJob( public async startComputeJob(
sasJob: string, sasJob: string,
data: any, data: any,
config: any = {}, config: any = {},
accessToken?: string, accessToken?: string,
waitForResult?: boolean, waitForResult?: boolean
pollOptions?: PollOptions,
printPid = false
) { ) {
config = { config = {
...this.sasjsConfig, ...this.sasjsConfig,
@@ -749,13 +734,10 @@ export default class SASjs {
return this.sasViyaApiClient?.executeComputeJob( return this.sasViyaApiClient?.executeComputeJob(
sasJob, sasJob,
config.contextName, config.contextName,
config.debug,
data, data,
accessToken, accessToken,
!!waitForResult, !!waitForResult,
false, false
pollOptions,
printPid
) )
} }
@@ -784,7 +766,6 @@ export default class SASjs {
?.executeComputeJob( ?.executeComputeJob(
sasJob, sasJob,
config.contextName, config.contextName,
config.debug,
data, data,
accessToken, accessToken,
waitForResult, waitForResult,
@@ -914,8 +895,8 @@ export default class SASjs {
return responseJson return responseJson
}) })
.catch(async (response) => { .catch(async (e) => {
if (needsRetry(JSON.stringify(response))) { if (needsRetry(JSON.stringify(e))) {
if (this.retryCountJeseApi < requestRetryLimit) { if (this.retryCountJeseApi < requestRetryLimit) {
let retryResponse = await this.executeJobViaJesApi( let retryResponse = await this.executeJobViaJesApi(
sasJob, sasJob,
@@ -936,11 +917,11 @@ export default class SASjs {
} }
} }
if (response?.log) { if (e?.log) {
this.appendSasjsRequest(response.log, sasJob, null) this.appendSasjsRequest(e.log, sasJob, null)
} }
if (response.toString().includes('Job was not found')) { if (e.toString().includes('Job was not found')) {
reject( reject(
new ErrorResponse('Service not found on the server.', { new ErrorResponse('Service not found on the server.', {
sasJob: sasJob sasJob: sasJob
@@ -948,7 +929,7 @@ export default class SASjs {
) )
} }
reject(new ErrorResponse('Job execution failed.', response)) reject(new ErrorResponse('Job execution failed.', e))
}) })
) )
} }

View File

@@ -1,6 +1,5 @@
import { Session, Context, CsrfToken, SessionVariable } from './types' import { Session, Context, CsrfToken } from './types'
import { asyncForEach, makeRequest, isUrl } from './utils' import { asyncForEach, makeRequest, isUrl } from './utils'
import { prefixMessage } from '@sasjs/utils/error'
const MAX_SESSION_COUNT = 1 const MAX_SESSION_COUNT = 1
const RETRY_LIMIT: number = 3 const RETRY_LIMIT: number = 3
@@ -169,11 +168,7 @@ export class SessionManager {
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, _) => {
if ( if (sessionState === 'pending') {
sessionState === 'pending' ||
sessionState === 'running' ||
sessionState === ''
) {
if (stateLink) { if (stateLink) {
if (this.debug) { if (this.debug) {
console.log('Polling session status... \n') // ? console.log('Polling session status... \n') // ?
@@ -266,21 +261,4 @@ export class SessionManager {
throw err throw err
}) })
} }
async getVariable(sessionId: string, variable: string, accessToken?: string) {
const getSessionVariable = {
method: 'GET',
headers: this.getHeaders(accessToken)
}
return await this.request<SessionVariable>(
`${this.serverUrl}/compute/sessions/${sessionId}/variables/${variable}`,
getSessionVariable
).catch((err) => {
throw prefixMessage(
err,
`Error while fetching session variable '${variable}'.`
)
})
}
} }

View File

@@ -1,53 +1,38 @@
import dotenv from 'dotenv'
import { SessionManager } from '../SessionManager' import { SessionManager } from '../SessionManager'
import * as dotenv from 'dotenv' import { CsrfToken } from '../types'
describe('SessionManager', () => { describe('SessionManager', () => {
dotenv.config() const setCsrfToken = jest
.fn()
let originalFetch: any .mockImplementation((csrfToken: CsrfToken) => console.log(csrfToken))
const sessionManager = new SessionManager(
process.env.SERVER_URL as string,
process.env.DEFAULT_COMPUTE_CONTEXT as string,
() => {}
)
beforeAll(() => { beforeAll(() => {
originalFetch = (global as any).fetch dotenv.config()
}) })
afterEach(() => { it('should instantiate', () => {
;(global as any).fetch = originalFetch const sessionManager = new SessionManager(
'http://test-server.com',
'test context',
setCsrfToken
)
expect(sessionManager).toBeInstanceOf(SessionManager)
expect(sessionManager.debug).toBeFalsy()
expect((sessionManager as any).serverUrl).toEqual('http://test-server.com')
expect((sessionManager as any).contextName).toEqual('test context')
}) })
describe('getVariable', () => { it('should set the debug flag', () => {
it('should fetch session variable', async () => { const sessionManager = new SessionManager(
const sampleResponse = { 'http://test-server.com',
ok: true, 'test context',
links: [], setCsrfToken
name: 'SYSJOBID', )
scope: 'GLOBAL',
value: '25218',
version: 1
}
;(global as any).fetch = jest.fn().mockImplementation(() => sessionManager.debug = true
Promise.resolve({
ok: true,
headers: { get: () => '' },
json: () => Promise.resolve(sampleResponse)
})
)
const expectedResponse = { etag: '', result: sampleResponse } expect(sessionManager.debug).toBeTruthy()
await expect(
sessionManager.getVariable(
'fakeSessionId',
'SYSJOBID',
'fakeAccessToken'
)
).resolves.toEqual(expectedResponse)
})
}) })
}) })

View File

@@ -4,5 +4,4 @@ export interface Folder {
id: string id: string
uri: string uri: string
links: Link[] links: Link[]
memberCount: number
} }

View File

@@ -1,4 +0,0 @@
export interface PollOptions {
MAX_POLL_COUNT?: number
POLL_INTERVAL?: number
}

View File

@@ -9,7 +9,3 @@ export interface Session {
} }
creationTimeStamp: string creationTimeStamp: string
} }
export interface SessionVariable {
value: string
}

View File

@@ -12,4 +12,3 @@ export * from './SASjsWaitingRequest'
export * from './ServerType' export * from './ServerType'
export * from './Session' export * from './Session'
export * from './UploadFile' export * from './UploadFile'
export * from './PollOptions'

View File

@@ -47,7 +47,6 @@ const browserConfig = {
const nodeConfig = { const nodeConfig = {
...browserConfig, ...browserConfig,
target: 'node', target: 'node',
entry: './node/index.ts',
output: { output: {
...browserConfig.output, ...browserConfig.output,
path: path.resolve(__dirname, 'build', 'node') path: path.resolve(__dirname, 'build', 'node')