From bfc534da15c470e853542ca9352c59acb17c0c70 Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Thu, 17 Feb 2022 02:01:19 +0500 Subject: [PATCH] feat: execute job on SASJS server --- src/SASjs.ts | 90 ++++++++++++------- src/job-execution/SasJsJobExecutor.ts | 123 ++++++++++++++++++++++++++ src/job-execution/index.ts | 3 +- 3 files changed, 182 insertions(+), 34 deletions(-) create mode 100644 src/job-execution/SasJsJobExecutor.ts diff --git a/src/SASjs.ts b/src/SASjs.ts index 54f05b7..f151764 100644 --- a/src/SASjs.ts +++ b/src/SASjs.ts @@ -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) { 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!, diff --git a/src/job-execution/SasJsJobExecutor.ts b/src/job-execution/SasJsJobExecutor.ts new file mode 100644 index 0000000..5a884ae --- /dev/null +++ b/src/job-execution/SasJsJobExecutor.ts @@ -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 + } +} diff --git a/src/job-execution/index.ts b/src/job-execution/index.ts index 46237a6..0fd44f7 100644 --- a/src/job-execution/index.ts +++ b/src/job-execution/index.ts @@ -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'