1
0
mirror of https://github.com/sasjs/adapter.git synced 2026-01-03 10:40:06 +00:00

Compare commits

...

6 Commits

Author SHA1 Message Date
Allan Bowe
8e116d81d7 Merge pull request #641 from sasjs/issue-624
fix(special-missing): fix convertToCSV format object
2022-02-17 13:05:24 +02:00
Yury Shkoda
fbca91144d test(generateFileUploadForm): add test data 2022-02-17 13:49:21 +03:00
Allan Bowe
c392390147 Merge pull request #642 from sasjs/sasjs-server-job-executor
feat: execute job on SASJS server
2022-02-17 12:48:41 +02:00
Yury Shkoda
9b239abda0 test(generateFileUploadForm): add unit test 2022-02-17 13:34:13 +03:00
Saad Jutt
bfc534da15 feat: execute job on SASJS server 2022-02-17 02:01:19 +05:00
Yury Shkoda
d3dff44918 fix(special-missings): fix convertToCSV format object 2022-02-16 17:13:00 +03:00
7 changed files with 243 additions and 37 deletions

View File

@@ -80,7 +80,7 @@ const errorAndCsrfData: any = {
}
const testTable = 'sometable'
const testTableWithNullVars: { [key: string]: any } = {
export const testTableWithNullVars: { [key: string]: any } = {
[testTable]: [
{ var1: 'string', var2: 232, nullvar: 'A' },
{ var1: 'string', var2: 232, nullvar: 'B' },

View File

@@ -27,6 +27,7 @@ import {
ComputeJobExecutor,
JesJobExecutor,
Sas9JobExecutor,
SasJsJobExecutor,
FileUploader
} from './job-execution'
import { ErrorResponse } from './types/errors'
@@ -62,6 +63,7 @@ export default class SASjs {
private computeJobExecutor: JobExecutor | null = null
private jesJobExecutor: JobExecutor | null = null
private sas9JobExecutor: JobExecutor | null = null
private sasJsJobExecutor: JobExecutor | null = null
constructor(config?: Partial<SASjsConfig>) {
this.sasjsConfig = {
@@ -76,6 +78,12 @@ export default class SASjs {
return this.requestClient?.getCsrfToken(type)
}
/**
* Executes the sas code against SAS9 server
* @param linesOfCode - lines of sas code from the file to run.
* @param username - a string representing the username.
* @param password - a string representing the password.
*/
public async executeScriptSAS9(
linesOfCode: string[],
userName: string,
@@ -90,6 +98,38 @@ export default class SASjs {
)
}
/**
* Executes the sas code against SASViya 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 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
*/
public async executeScriptSASViya(
fileName: string,
linesOfCode: string[],
contextName: string,
authConfig?: AuthConfig,
debug?: boolean
) {
this.isMethodSupported('executeScriptSASViya', [ServerType.SasViya])
if (!contextName) {
throw new Error(
'Context name is undefined. Please set a `contextName` in your SASjs or override config.'
)
}
return await this.sasViyaApiClient!.executeScript(
fileName,
linesOfCode,
contextName,
authConfig,
null,
debug ? debug : this.sasjsConfig.debug
)
}
/**
* Gets compute contexts.
* @param accessToken - an access token for an authorized user.
@@ -253,38 +293,6 @@ export default class SASjs {
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 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
*/
public async executeScriptSASViya(
fileName: string,
linesOfCode: string[],
contextName: string,
authConfig?: AuthConfig,
debug?: boolean
) {
this.isMethodSupported('executeScriptSASViya', [ServerType.SasViya])
if (!contextName) {
throw new Error(
'Context name is undefined. Please set a `contextName` in your SASjs or override config.'
)
}
return await this.sasViyaApiClient!.executeScript(
fileName,
linesOfCode,
contextName,
authConfig,
null,
debug ? debug : this.sasjsConfig.debug
)
}
/**
* Creates a folder in the logical SAS folder tree
* @param folderName - name of the folder to be created.
@@ -675,7 +683,16 @@ export default class SASjs {
const validationResult = this.validateInput(data)
if (validationResult.status) {
if (
if (config.serverType === ServerType.Sasjs) {
return await this.sasJsJobExecutor!.execute(
sasJob,
data,
config,
loginRequiredCallback,
authConfig,
extraResponseAttributes
)
} else if (
config.serverType !== ServerType.Sas9 &&
config.useComputeApi !== undefined &&
config.useComputeApi !== null
@@ -1096,6 +1113,13 @@ export default class SASjs {
this.sasViyaApiClient!
)
this.sasJsJobExecutor = new SasJsJobExecutor(
this.sasjsConfig.serverUrl,
this.sasjsConfig.serverType!,
this.jobsPath,
this.requestClient
)
this.sas9JobExecutor = new Sas9JobExecutor(
this.sasjsConfig.serverUrl,
this.sasjsConfig.serverType!,

View File

@@ -5,8 +5,11 @@ export const generateFileUploadForm = (
data: any
): FormData => {
for (const tableName in data) {
if (!Array.isArray(data[tableName])) continue
const name = tableName
const csv = convertToCSV(data[tableName])
if (csv === 'ERROR: LARGE STRING LENGTH') {
throw new Error(
'The max length of a string value in SASjs is 32765 characters.'

View File

@@ -0,0 +1,55 @@
import { generateFileUploadForm } from '../generateFileUploadForm'
describe('generateFileUploadForm', () => {
beforeAll(() => {
function FormDataMock(this: any) {
this.append = () => {}
}
const BlobMock = jest.fn()
;(global as any).FormData = FormDataMock
;(global as any).Blob = BlobMock
})
it('should generate file upload form from data', () => {
const formData = new FormData()
const testTable = 'sometable'
const testTableWithNullVars: { [key: string]: any } = {
[testTable]: [
{ var1: 'string', var2: 232, nullvar: 'A' },
{ var1: 'string', var2: 232, nullvar: 'B' },
{ var1: 'string', var2: 232, nullvar: '_' },
{ var1: 'string', var2: 232, nullvar: 0 },
{ var1: 'string', var2: 232, nullvar: 'z' },
{ var1: 'string', var2: 232, nullvar: null }
],
[`$${testTable}`]: { formats: { var1: '$char12.', nullvar: 'best.' } }
}
const tableName = Object.keys(testTableWithNullVars).filter((key: string) =>
Array.isArray(testTableWithNullVars[key])
)[0]
jest.spyOn(formData, 'append').mockImplementation(() => {})
generateFileUploadForm(formData, testTableWithNullVars)
expect(formData.append).toHaveBeenCalledOnce()
expect(formData.append).toHaveBeenCalledWith(
tableName,
{},
`${tableName}.csv`
)
})
it('should throw an error if too large string was provided', () => {
const formData = new FormData()
const data = { testTable: [{ var1: 'z'.repeat(32765 + 1) }] }
expect(() => generateFileUploadForm(formData, data)).toThrow(
new Error(
'The max length of a string value in SASjs is 32765 characters.'
)
)
})
})

View File

@@ -0,0 +1,123 @@
import {
AuthConfig,
ExtraResponseAttributes,
ServerType
} from '@sasjs/utils/types'
import {
ErrorResponse,
JobExecutionError,
LoginRequiredError
} from '../types/errors'
import { RequestClient } from '../request/RequestClient'
import {
isRelativePath,
appendExtraResponseAttributes,
getValidJson
} from '../utils'
import { BaseJobExecutor } from './JobExecutor'
import { parseWeboutResponse } from '../utils/parseWeboutResponse'
export class SasJsJobExecutor extends BaseJobExecutor {
constructor(
serverUrl: string,
serverType: ServerType,
private jobsPath: string,
private requestClient: RequestClient
) {
super(serverUrl, serverType)
}
async execute(
sasJob: string,
data: any,
config: any,
loginRequiredCallback?: any,
authConfig?: AuthConfig,
extraResponseAttributes: ExtraResponseAttributes[] = []
) {
const loginCallback = loginRequiredCallback || (() => Promise.resolve())
const program = isRelativePath(sasJob)
? config.appLoc
? config.appLoc.replace(/\/?$/, '/') + sasJob.replace(/^\//, '')
: sasJob
: sasJob
let apiUrl = `${config.serverUrl}${this.jobsPath}/?${'_program=' + program}`
const requestParams = this.getRequestParams(config)
const requestPromise = new Promise((resolve, reject) => {
this.requestClient!.post(
apiUrl,
{ ...requestParams, ...data },
authConfig?.access_token
)
.then(async (res: any) => {
const resObj = {
result: res.result._webout,
log: res.result.log
}
this.requestClient!.appendRequest(resObj, sasJob, config.debug)
let jsonResponse = res.result
if (config.debug) {
const webout = parseWeboutResponse(res.result._webout, apiUrl)
jsonResponse = getValidJson(webout)
} else {
jsonResponse = getValidJson(res.result._webout)
}
const responseObject = appendExtraResponseAttributes(
{ result: jsonResponse },
extraResponseAttributes
)
resolve(responseObject)
})
.catch(async (e: Error) => {
if (e instanceof JobExecutionError) {
this.requestClient!.appendRequest(e, sasJob, config.debug)
reject(new ErrorResponse(e?.message, e))
}
if (e instanceof LoginRequiredError) {
this.appendWaitingRequest(() => {
return this.execute(
sasJob,
data,
config,
loginRequiredCallback,
authConfig,
extraResponseAttributes
).then(
(res: any) => {
resolve(res)
},
(err: any) => {
reject(err)
}
)
})
await loginCallback()
} else {
reject(new ErrorResponse(e?.message, e))
}
})
})
return requestPromise
}
private getRequestParams(config: any): any {
const requestParams: any = {}
if (config.debug) {
requestParams['_omittextlog'] = 'false'
requestParams['_omitsessionresults'] = 'false'
requestParams['_debug'] = 131
}
return requestParams
}
}

View File

@@ -1,6 +1,7 @@
export * from './ComputeJobExecutor'
export * from './FileUploader'
export * from './JesJobExecutor'
export * from './JobExecutor'
export * from './Sas9JobExecutor'
export * from './SasJsJobExecutor'
export * from './WebJobExecutor'
export * from './FileUploader'

View File

@@ -3,7 +3,7 @@
* @param data - the array of JSON objects to convert.
*/
export const convertToCSV = (
data: any,
data: any[],
sasFormats?: { formats: { [key: string]: string } }
) => {
let formats = sasFormats?.formats
@@ -76,7 +76,7 @@ export const convertToCSV = (
return byteSize
}
})
.sort((a: number, b: number) => b - a)[0]
.sort((a: any, b: any) => b - a)[0]
if (longestValueForField && longestValueForField > 32765) {
invalidString = true