mirror of
https://github.com/sasjs/adapter.git
synced 2026-01-05 03:30:05 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2739d1791 | ||
|
|
487cb489f3 | ||
|
|
d9cb2db61f | ||
|
|
35f37ac796 | ||
|
|
d7ad0288b9 | ||
|
|
9c98cabe6c | ||
|
|
a6f6897543 |
25
src/SASjs.ts
25
src/SASjs.ts
@@ -27,7 +27,6 @@ import {
|
|||||||
ComputeJobExecutor,
|
ComputeJobExecutor,
|
||||||
JesJobExecutor,
|
JesJobExecutor,
|
||||||
Sas9JobExecutor,
|
Sas9JobExecutor,
|
||||||
SasJsJobExecutor,
|
|
||||||
FileUploader
|
FileUploader
|
||||||
} from './job-execution'
|
} from './job-execution'
|
||||||
import { ErrorResponse } from './types/errors'
|
import { ErrorResponse } from './types/errors'
|
||||||
@@ -63,7 +62,6 @@ export default class SASjs {
|
|||||||
private computeJobExecutor: JobExecutor | null = null
|
private computeJobExecutor: JobExecutor | null = null
|
||||||
private jesJobExecutor: JobExecutor | null = null
|
private jesJobExecutor: JobExecutor | null = null
|
||||||
private sas9JobExecutor: JobExecutor | null = null
|
private sas9JobExecutor: JobExecutor | null = null
|
||||||
private sasJsJobExecutor: JobExecutor | null = null
|
|
||||||
|
|
||||||
constructor(config?: Partial<SASjsConfig>) {
|
constructor(config?: Partial<SASjsConfig>) {
|
||||||
this.sasjsConfig = {
|
this.sasjsConfig = {
|
||||||
@@ -79,7 +77,8 @@ export default class SASjs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes the sas code against SAS9 server
|
* Executes code against a SAS 9 server. Requires a runner to be present in
|
||||||
|
* the users home directory in metadata.
|
||||||
* @param linesOfCode - lines of sas code from the file to run.
|
* @param linesOfCode - lines of sas code from the file to run.
|
||||||
* @param username - a string representing the username.
|
* @param username - a string representing the username.
|
||||||
* @param password - a string representing the password.
|
* @param password - a string representing the password.
|
||||||
@@ -99,7 +98,7 @@ export default class SASjs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes the sas code against SASViya server
|
* Executes sas code in a SAS Viya compute session.
|
||||||
* @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.
|
||||||
@@ -643,7 +642,8 @@ export default class SASjs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Makes a request to the SAS Service specified in `SASjob`. The response
|
* Makes a request to program specified in `SASjob` (could be a Viya Job, a
|
||||||
|
* SAS 9 Stored Process, or a SASjs Server Stored Program). The response
|
||||||
* object will always contain table names in lowercase, and column names in
|
* object will always contain table names in lowercase, and column names in
|
||||||
* uppercase. Values are returned formatted by default, unformatted
|
* uppercase. Values are returned formatted by default, unformatted
|
||||||
* values can be configured as an option in the `%webout` macro.
|
* values can be configured as an option in the `%webout` macro.
|
||||||
@@ -652,7 +652,8 @@ export default class SASjs {
|
|||||||
* the SAS `_program` parameter to run a Job Definition or SAS 9 Stored
|
* the SAS `_program` parameter to run a Job Definition or SAS 9 Stored
|
||||||
* Process). Is prepended at runtime with the value of `appLoc`.
|
* Process). Is prepended at runtime with the value of `appLoc`.
|
||||||
* @param data - a JSON object containing one or more tables to be sent to
|
* @param data - a JSON object containing one or more tables to be sent to
|
||||||
* SAS. Can be `null` if no inputs required.
|
* SAS. For an example of the table structure, see the project README. This
|
||||||
|
* value can be `null` if no inputs are required.
|
||||||
* @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.
|
||||||
@@ -682,9 +683,10 @@ export default class SASjs {
|
|||||||
|
|
||||||
const validationResult = this.validateInput(data)
|
const validationResult = this.validateInput(data)
|
||||||
|
|
||||||
|
// status is true if the data passes validation checks above
|
||||||
if (validationResult.status) {
|
if (validationResult.status) {
|
||||||
if (config.serverType === ServerType.Sasjs) {
|
if (config.serverType === ServerType.Sasjs) {
|
||||||
return await this.sasJsJobExecutor!.execute(
|
return await this.webJobExecutor!.execute(
|
||||||
sasJob,
|
sasJob,
|
||||||
data,
|
data,
|
||||||
config,
|
config,
|
||||||
@@ -693,7 +695,7 @@ export default class SASjs {
|
|||||||
extraResponseAttributes
|
extraResponseAttributes
|
||||||
)
|
)
|
||||||
} else if (
|
} else if (
|
||||||
config.serverType !== ServerType.Sas9 &&
|
config.serverType === ServerType.SasViya &&
|
||||||
config.useComputeApi !== undefined &&
|
config.useComputeApi !== undefined &&
|
||||||
config.useComputeApi !== null
|
config.useComputeApi !== null
|
||||||
) {
|
) {
|
||||||
@@ -1114,13 +1116,6 @@ export default class SASjs {
|
|||||||
this.sasViyaApiClient!
|
this.sasViyaApiClient!
|
||||||
)
|
)
|
||||||
|
|
||||||
this.sasJsJobExecutor = new SasJsJobExecutor(
|
|
||||||
this.sasjsConfig.serverUrl,
|
|
||||||
this.sasjsConfig.serverType!,
|
|
||||||
this.jobsPath,
|
|
||||||
this.requestClient
|
|
||||||
)
|
|
||||||
|
|
||||||
this.sas9JobExecutor = new Sas9JobExecutor(
|
this.sas9JobExecutor = new Sas9JobExecutor(
|
||||||
this.sasjsConfig.serverUrl,
|
this.sasjsConfig.serverUrl,
|
||||||
this.sasjsConfig.serverType!,
|
this.sasjsConfig.serverType!,
|
||||||
|
|||||||
@@ -1,9 +1,19 @@
|
|||||||
|
import * as NodeFormData from 'form-data'
|
||||||
import { convertToCSV } from '../utils/convertToCsv'
|
import { convertToCSV } from '../utils/convertToCsv'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One of the approaches SASjs takes to send tables-formatted JSON (see README)
|
||||||
|
* to SAS is as multipart form data, where each table is provided as a specially
|
||||||
|
* formatted CSV file.
|
||||||
|
* @param formData Different objects are used depending on whether the adapter is
|
||||||
|
* running in the browser, or in the CLI
|
||||||
|
* @param data Special, tables-formatted JSON (see README)
|
||||||
|
* @returns Populated formData
|
||||||
|
*/
|
||||||
export const generateFileUploadForm = (
|
export const generateFileUploadForm = (
|
||||||
formData: FormData,
|
formData: FormData | NodeFormData,
|
||||||
data: any
|
data: any
|
||||||
): FormData => {
|
): FormData | NodeFormData => {
|
||||||
for (const tableName in data) {
|
for (const tableName in data) {
|
||||||
if (!Array.isArray(data[tableName])) continue
|
if (!Array.isArray(data[tableName])) continue
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
|
import * as NodeFormData from 'form-data'
|
||||||
import { convertToCSV } from '../utils/convertToCsv'
|
import { convertToCSV } from '../utils/convertToCsv'
|
||||||
import { splitChunks } from '../utils/splitChunks'
|
import { splitChunks } from '../utils/splitChunks'
|
||||||
|
|
||||||
export const generateTableUploadForm = (formData: FormData, data: any) => {
|
export const generateTableUploadForm = (
|
||||||
|
formData: FormData | NodeFormData,
|
||||||
|
data: any
|
||||||
|
) => {
|
||||||
const sasjsTables = []
|
const sasjsTables = []
|
||||||
const requestParams: any = {}
|
const requestParams: any = {}
|
||||||
let tableCounter = 0
|
let tableCounter = 0
|
||||||
|
|||||||
@@ -1,123 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import * as NodeFormData from 'form-data'
|
||||||
import {
|
import {
|
||||||
AuthConfig,
|
AuthConfig,
|
||||||
ExtraResponseAttributes,
|
ExtraResponseAttributes,
|
||||||
@@ -108,7 +109,12 @@ export class WebJobExecutor extends BaseJobExecutor {
|
|||||||
...this.getRequestParams(config)
|
...this.getRequestParams(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
let formData = new FormData()
|
/**
|
||||||
|
* Use the available form data object (FormData in Browser, NodeFormData in
|
||||||
|
* Node)
|
||||||
|
*/
|
||||||
|
let formData =
|
||||||
|
typeof FormData === 'undefined' ? new NodeFormData() : new FormData()
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
const stringifiedData = JSON.stringify(data)
|
const stringifiedData = JSON.stringify(data)
|
||||||
@@ -143,14 +149,30 @@ export class WebJobExecutor extends BaseJobExecutor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The NodeFormData object does not set the request header - so, set it */
|
||||||
|
const contentType =
|
||||||
|
formData instanceof NodeFormData
|
||||||
|
? `multipart/form-data; boundary=${formData.getBoundary()}`
|
||||||
|
: undefined
|
||||||
|
|
||||||
const requestPromise = new Promise((resolve, reject) => {
|
const requestPromise = new Promise((resolve, reject) => {
|
||||||
this.requestClient!.post(apiUrl, formData, authConfig?.access_token)
|
this.requestClient!.post(
|
||||||
|
apiUrl,
|
||||||
|
formData,
|
||||||
|
authConfig?.access_token,
|
||||||
|
contentType
|
||||||
|
)
|
||||||
.then(async (res: any) => {
|
.then(async (res: any) => {
|
||||||
|
const parsedSasjsServerLog =
|
||||||
|
this.serverType === ServerType.Sasjs
|
||||||
|
? res.result.log.map((logLine: any) => logLine.line).join('\n')
|
||||||
|
: res.result.log
|
||||||
|
|
||||||
const resObj =
|
const resObj =
|
||||||
this.serverType === ServerType.Sasjs
|
this.serverType === ServerType.Sasjs
|
||||||
? {
|
? {
|
||||||
result: res.result._webout,
|
result: res.result._webout,
|
||||||
log: res.result.log
|
log: parsedSasjsServerLog
|
||||||
}
|
}
|
||||||
: res
|
: res
|
||||||
this.requestClient!.appendRequest(resObj, sasJob, config.debug)
|
this.requestClient!.appendRequest(resObj, sasJob, config.debug)
|
||||||
@@ -173,8 +195,12 @@ export class WebJobExecutor extends BaseJobExecutor {
|
|||||||
: res.result
|
: res.result
|
||||||
break
|
break
|
||||||
case ServerType.Sasjs:
|
case ServerType.Sasjs:
|
||||||
const webout = parseWeboutResponse(res.result._webout, apiUrl)
|
if (typeof res.result._webout === 'object') {
|
||||||
jsonResponse = getValidJson(webout)
|
jsonResponse = res.result._webout
|
||||||
|
} else {
|
||||||
|
const webout = parseWeboutResponse(res.result._webout, apiUrl)
|
||||||
|
jsonResponse = getValidJson(webout)
|
||||||
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
} else if (this.serverType === ServerType.Sasjs) {
|
} else if (this.serverType === ServerType.Sasjs) {
|
||||||
|
|||||||
@@ -3,5 +3,4 @@ export * from './FileUploader'
|
|||||||
export * from './JesJobExecutor'
|
export * from './JesJobExecutor'
|
||||||
export * from './JobExecutor'
|
export * from './JobExecutor'
|
||||||
export * from './Sas9JobExecutor'
|
export * from './Sas9JobExecutor'
|
||||||
export * from './SasJsJobExecutor'
|
|
||||||
export * from './WebJobExecutor'
|
export * from './WebJobExecutor'
|
||||||
|
|||||||
Reference in New Issue
Block a user