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

Compare commits

..

8 Commits

Author SHA1 Message Date
Muhammad Saad
92434e48ad Merge pull request #655 from sasjs/node-form-data-fix
fix: not to use NodeFormData function on web
2022-02-22 17:20:00 +05:00
Saad Jutt
d3c91e143a fix: parse empty string in res to empty {} 2022-02-22 17:09:47 +05:00
Saad Jutt
872e73b5f0 fix: not to use NodeFormData function on web 2022-02-22 17:06:27 +05:00
Allan Bowe
af4ad3a7af Merge pull request #654 from sasjs/axios-setting-for-larger-deploy-to-sasjs-server
fix: added additional params to axios POST while deploy to SASJS server
2022-02-22 13:21:12 +02:00
Saad Jutt
1ff67ed93c fix: added additional params to axios POST while deploy to SASJS server 2022-02-22 16:09:15 +05:00
Allan Bowe
d2739d1791 Merge pull request #653 from sasjs/form-data-fix
fix: reverted sasjsJobExecutor no need for that
2022-02-22 11:15:16 +02:00
Allan Bowe
487cb489f3 fix: comments and logic tidy up 2022-02-22 08:38:25 +00:00
Saad Jutt
d9cb2db61f fix: reverted sasjsJobExecutor no need for that 2022-02-22 06:53:29 +05:00
9 changed files with 57 additions and 180 deletions

View File

@@ -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 = {
@@ -688,35 +686,14 @@ export default class SASjs {
// status is true if the data passes validation checks above // 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.webJobExecutor!.execute(
* When sending the JSON data object to SAS, it is first converted to sasJob,
* a set of specially formatted CSVs. These are passed as multi-part data,
* form data (converted to input files in the backend SAS session by the config,
* API). When running outside of a browser, the FormData object is not loginRequiredCallback,
* available. So in this case, a slightly different executor is invoked, authConfig,
* which is similar to the sas9JobExecutor. extraResponseAttributes
*/ )
if (typeof FormData === 'undefined') {
// cli invocation
return await this.sasJsJobExecutor!.execute(
sasJob,
data,
config,
loginRequiredCallback,
authConfig,
extraResponseAttributes
)
} else {
// web invocation
return await this.webJobExecutor!.execute(
sasJob,
data,
config,
loginRequiredCallback,
authConfig,
extraResponseAttributes
)
}
} else if ( } else if (
config.serverType === ServerType.SasViya && config.serverType === ServerType.SasViya &&
config.useComputeApi !== undefined && config.useComputeApi !== undefined &&
@@ -1139,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!,

View File

@@ -37,7 +37,10 @@ export class SASjsApiClient {
}>( }>(
'SASjsApi/drive/deploy', 'SASjsApi/drive/deploy',
{ fileTree: members, appLoc: appLoc }, { fileTree: members, appLoc: appLoc },
access_token access_token,
undefined,
{},
{ maxContentLength: Infinity, maxBodyLength: Infinity }
) )
return Promise.resolve(result) return Promise.resolve(result)

View File

@@ -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

View File

@@ -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

View File

@@ -1,131 +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 parsedSasjsServerLog = res.result.log
.map((logLine: any) => logLine.line)
.join('\n')
const resObj = {
result: res.result._webout,
log: parsedSasjsServerLog
}
this.requestClient!.appendRequest(resObj, sasJob, config.debug)
let jsonResponse = res.result
if (config.debug) {
if (typeof res.result._webout === 'object') {
jsonResponse = res.result._webout
} else {
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,3 +1,4 @@
import * as NodeFormData from 'form-data'
import { import {
AuthConfig, AuthConfig,
ExtraResponseAttributes, ExtraResponseAttributes,
@@ -108,9 +109,12 @@ export class WebJobExecutor extends BaseJobExecutor {
...this.getRequestParams(config) ...this.getRequestParams(config)
} }
// FormData is only valid in browser /**
// FormData is a part of JS web API (not included in native NodeJS). * Use the available form data object (FormData in Browser, NodeFormData in
let formData = new FormData() * Node)
*/
let formData =
typeof FormData === 'undefined' ? new NodeFormData() : new FormData()
if (data) { if (data) {
const stringifiedData = JSON.stringify(data) const stringifiedData = JSON.stringify(data)
@@ -145,8 +149,19 @@ export class WebJobExecutor extends BaseJobExecutor {
} }
} }
/* The NodeFormData object does not set the request header - so, set it */
const contentType =
formData instanceof NodeFormData && typeof FormData === 'undefined'
? `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 = const parsedSasjsServerLog =
this.serverType === ServerType.Sasjs this.serverType === ServerType.Sasjs

View File

@@ -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'

View File

@@ -207,7 +207,8 @@ export class RequestClient implements HttpClient {
data: any, data: any,
accessToken: string | undefined, accessToken: string | undefined,
contentType = 'application/json', contentType = 'application/json',
overrideHeaders: { [key: string]: string | number } = {} overrideHeaders: { [key: string]: string | number } = {},
additionalSettings: { [key: string]: string | number } = {}
): Promise<{ result: T; etag: string }> { ): Promise<{ result: T; etag: string }> {
const headers = { const headers = {
...this.getHeaders(accessToken, contentType), ...this.getHeaders(accessToken, contentType),
@@ -215,7 +216,11 @@ export class RequestClient implements HttpClient {
} }
return this.httpClient return this.httpClient
.post<T>(url, data, { headers, withCredentials: true }) .post<T>(url, data, {
headers,
withCredentials: true,
...additionalSettings
})
.then((response) => { .then((response) => {
throwIfError(response) throwIfError(response)

View File

@@ -12,6 +12,8 @@ export const getValidJson = (str: string | object): object => {
if (typeof str === 'object') return str if (typeof str === 'object') return str
if (str === '') return {}
return JSON.parse(str) return JSON.parse(str)
} catch (e) { } catch (e) {
if (e instanceof JsonParseArrayError) throw e if (e instanceof JsonParseArrayError) throw e