mirror of
https://github.com/sasjs/adapter.git
synced 2026-01-07 12:30:06 +00:00
chore: FileUploader extends BaseJobExecutor
This commit is contained in:
@@ -1,89 +0,0 @@
|
|||||||
import { isUrl, getValidJson, parseSasViyaDebugResponse } from './utils'
|
|
||||||
import { UploadFile } from './types/UploadFile'
|
|
||||||
import { ErrorResponse, LoginRequiredError } from './types/errors'
|
|
||||||
import { RequestClient } from './request/RequestClient'
|
|
||||||
import { ServerType } from '@sasjs/utils/types'
|
|
||||||
import { SASjsConfig } from './types'
|
|
||||||
|
|
||||||
export class FileUploader {
|
|
||||||
constructor(private jobsPath: string, private requestClient: RequestClient) {}
|
|
||||||
|
|
||||||
public async uploadFile(
|
|
||||||
sasJob: string,
|
|
||||||
files: UploadFile[],
|
|
||||||
params: any,
|
|
||||||
config: SASjsConfig
|
|
||||||
) {
|
|
||||||
if (files?.length < 1)
|
|
||||||
return Promise.reject(
|
|
||||||
new ErrorResponse('At least one file must be provided.')
|
|
||||||
)
|
|
||||||
if (!sasJob || sasJob === '')
|
|
||||||
return Promise.reject(new ErrorResponse('sasJob must be provided.'))
|
|
||||||
|
|
||||||
let paramsString = ''
|
|
||||||
|
|
||||||
for (let param in params) {
|
|
||||||
if (params.hasOwnProperty(param)) {
|
|
||||||
paramsString += `&${param}=${params[param]}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const program = config.appLoc
|
|
||||||
? config.appLoc.replace(/\/?$/, '/') + sasJob.replace(/^\//, '')
|
|
||||||
: sasJob
|
|
||||||
const uploadUrl = `${this.jobsPath}/?${
|
|
||||||
'_program=' + program
|
|
||||||
}${paramsString}`
|
|
||||||
|
|
||||||
const formData = new FormData()
|
|
||||||
|
|
||||||
for (let file of files) {
|
|
||||||
formData.append('file', file.file, file.fileName)
|
|
||||||
}
|
|
||||||
|
|
||||||
const csrfToken = this.requestClient.getCsrfToken('file')
|
|
||||||
if (csrfToken) formData.append('_csrf', csrfToken.value)
|
|
||||||
if (config.debug) formData.append('_debug', '131')
|
|
||||||
if (config.serverType === ServerType.SasViya && config.contextName)
|
|
||||||
formData.append('_contextname', config.contextName)
|
|
||||||
|
|
||||||
const headers = {
|
|
||||||
'cache-control': 'no-cache',
|
|
||||||
Accept: '*/*',
|
|
||||||
'Content-Type': 'text/plain'
|
|
||||||
}
|
|
||||||
|
|
||||||
// currently only web approach is supported for file upload
|
|
||||||
// therefore log is part of response with debug enabled and must be parsed
|
|
||||||
return this.requestClient
|
|
||||||
.post(uploadUrl, formData, undefined, 'application/json', headers)
|
|
||||||
.then(async (res) => {
|
|
||||||
this.requestClient.appendRequest(res, sasJob, config.debug)
|
|
||||||
if (config.serverType === ServerType.SasViya && config.debug) {
|
|
||||||
const jsonResponse = await parseSasViyaDebugResponse(
|
|
||||||
res.result as string,
|
|
||||||
this.requestClient,
|
|
||||||
config.serverUrl
|
|
||||||
)
|
|
||||||
return jsonResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
return typeof res.result === 'string'
|
|
||||||
? getValidJson(res.result)
|
|
||||||
: res.result
|
|
||||||
|
|
||||||
//TODO: append to SASjs requests
|
|
||||||
})
|
|
||||||
.catch((err: Error) => {
|
|
||||||
if (err instanceof LoginRequiredError) {
|
|
||||||
return Promise.reject(
|
|
||||||
new ErrorResponse('You must be logged in to upload a file.', err)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return Promise.reject(
|
|
||||||
new ErrorResponse('File upload request failed.', err)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
16
src/SASjs.ts
16
src/SASjs.ts
@@ -8,7 +8,6 @@ import {
|
|||||||
} from './types'
|
} from './types'
|
||||||
import { SASViyaApiClient } from './SASViyaApiClient'
|
import { SASViyaApiClient } from './SASViyaApiClient'
|
||||||
import { SAS9ApiClient } from './SAS9ApiClient'
|
import { SAS9ApiClient } from './SAS9ApiClient'
|
||||||
import { FileUploader } from './FileUploader'
|
|
||||||
import { AuthManager } from './auth'
|
import { AuthManager } from './auth'
|
||||||
import {
|
import {
|
||||||
ServerType,
|
ServerType,
|
||||||
@@ -22,7 +21,8 @@ import {
|
|||||||
WebJobExecutor,
|
WebJobExecutor,
|
||||||
ComputeJobExecutor,
|
ComputeJobExecutor,
|
||||||
JesJobExecutor,
|
JesJobExecutor,
|
||||||
Sas9JobExecutor
|
Sas9JobExecutor,
|
||||||
|
FileUploader
|
||||||
} from './job-execution'
|
} from './job-execution'
|
||||||
import { ErrorResponse } from './types/errors'
|
import { ErrorResponse } from './types/errors'
|
||||||
import { LoginOptions, LoginResult } from './types/Login'
|
import { LoginOptions, LoginResult } from './types/Login'
|
||||||
@@ -579,10 +579,11 @@ export default class SASjs {
|
|||||||
params: any,
|
params: any,
|
||||||
overrideSasjsConfig?: any
|
overrideSasjsConfig?: any
|
||||||
) {
|
) {
|
||||||
return await this.fileUploader!.uploadFile(sasJob, files, params, {
|
const config = {
|
||||||
...this.sasjsConfig,
|
...this.sasjsConfig,
|
||||||
...overrideSasjsConfig
|
...overrideSasjsConfig
|
||||||
})
|
}
|
||||||
|
return await this.fileUploader!.execute(sasJob, files, params, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -984,7 +985,12 @@ export default class SASjs {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.fileUploader = new FileUploader(this.jobsPath, this.requestClient)
|
this.fileUploader = new FileUploader(
|
||||||
|
this.sasjsConfig.serverUrl,
|
||||||
|
this.sasjsConfig.serverType!,
|
||||||
|
this.jobsPath,
|
||||||
|
this.requestClient
|
||||||
|
)
|
||||||
|
|
||||||
this.webJobExecutor = new WebJobExecutor(
|
this.webJobExecutor = new WebJobExecutor(
|
||||||
this.sasjsConfig.serverUrl,
|
this.sasjsConfig.serverUrl,
|
||||||
|
|||||||
143
src/job-execution/FileUploader.ts
Normal file
143
src/job-execution/FileUploader.ts
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
import {
|
||||||
|
getValidJson,
|
||||||
|
parseSasViyaDebugResponse,
|
||||||
|
parseWeboutResponse
|
||||||
|
} from '../utils'
|
||||||
|
import { UploadFile } from '../types/UploadFile'
|
||||||
|
import {
|
||||||
|
ErrorResponse,
|
||||||
|
JobExecutionError,
|
||||||
|
LoginRequiredError
|
||||||
|
} from '../types/errors'
|
||||||
|
import { RequestClient } from '../request/RequestClient'
|
||||||
|
import { ServerType } from '@sasjs/utils/types'
|
||||||
|
import { BaseJobExecutor } from './JobExecutor'
|
||||||
|
|
||||||
|
interface dataFileUpload {
|
||||||
|
files: UploadFile[]
|
||||||
|
params: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export class FileUploader extends BaseJobExecutor {
|
||||||
|
constructor(
|
||||||
|
serverUrl: string,
|
||||||
|
serverType: ServerType,
|
||||||
|
private jobsPath: string,
|
||||||
|
private requestClient: RequestClient
|
||||||
|
) {
|
||||||
|
super(serverUrl, serverType)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async execute(
|
||||||
|
sasJob: string,
|
||||||
|
data: any,
|
||||||
|
config: any,
|
||||||
|
loginRequiredCallback?: any
|
||||||
|
) {
|
||||||
|
const { files, params }: dataFileUpload = data
|
||||||
|
const loginCallback = loginRequiredCallback || (() => Promise.resolve())
|
||||||
|
|
||||||
|
if (!files?.length)
|
||||||
|
throw new ErrorResponse('At least one file must be provided.')
|
||||||
|
|
||||||
|
if (!sasJob || sasJob === '')
|
||||||
|
throw new ErrorResponse('sasJob must be provided.')
|
||||||
|
|
||||||
|
let paramsString = ''
|
||||||
|
|
||||||
|
for (let param in params)
|
||||||
|
if (params.hasOwnProperty(param))
|
||||||
|
paramsString += `&${param}=${params[param]}`
|
||||||
|
|
||||||
|
const program = config.appLoc
|
||||||
|
? config.appLoc.replace(/\/?$/, '/') + sasJob.replace(/^\//, '')
|
||||||
|
: sasJob
|
||||||
|
const uploadUrl = `${this.jobsPath}/?${
|
||||||
|
'_program=' + program
|
||||||
|
}${paramsString}`
|
||||||
|
|
||||||
|
const formData = new FormData()
|
||||||
|
|
||||||
|
for (let file of files) {
|
||||||
|
formData.append('file', file.file, file.fileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
const csrfToken = this.requestClient.getCsrfToken('file')
|
||||||
|
if (csrfToken) formData.append('_csrf', csrfToken.value)
|
||||||
|
if (config.debug) formData.append('_debug', '131')
|
||||||
|
if (config.serverType === ServerType.SasViya && config.contextName)
|
||||||
|
formData.append('_contextname', config.contextName)
|
||||||
|
|
||||||
|
const headers = {
|
||||||
|
'cache-control': 'no-cache',
|
||||||
|
Accept: '*/*',
|
||||||
|
'Content-Type': 'text/plain'
|
||||||
|
}
|
||||||
|
|
||||||
|
// currently only web approach is supported for file upload
|
||||||
|
// therefore log is part of response with debug enabled and must be parsed
|
||||||
|
const requestPromise = new Promise((resolve, reject) => {
|
||||||
|
this.requestClient
|
||||||
|
.post(uploadUrl, formData, undefined, 'application/json', headers)
|
||||||
|
.then(async (res: any) => {
|
||||||
|
this.requestClient.appendRequest(res, sasJob, config.debug)
|
||||||
|
|
||||||
|
let jsonResponse = res.result
|
||||||
|
|
||||||
|
if (config.debug) {
|
||||||
|
switch (this.serverType) {
|
||||||
|
case ServerType.SasViya:
|
||||||
|
jsonResponse = await parseSasViyaDebugResponse(
|
||||||
|
res.result,
|
||||||
|
this.requestClient,
|
||||||
|
config.serverUrl
|
||||||
|
)
|
||||||
|
break
|
||||||
|
case ServerType.Sas9:
|
||||||
|
jsonResponse =
|
||||||
|
typeof res.result === 'string'
|
||||||
|
? parseWeboutResponse(res.result, uploadUrl)
|
||||||
|
: res.result
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
jsonResponse =
|
||||||
|
typeof res.result === 'string'
|
||||||
|
? getValidJson(res.result)
|
||||||
|
: res.result
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(jsonResponse)
|
||||||
|
})
|
||||||
|
.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
|
||||||
|
).then(
|
||||||
|
(res: any) => {
|
||||||
|
resolve(res)
|
||||||
|
},
|
||||||
|
(err: any) => {
|
||||||
|
reject(err)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
await loginCallback()
|
||||||
|
} else {
|
||||||
|
reject(new ErrorResponse('File upload request failed.', e))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return requestPromise
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -45,7 +45,7 @@ export class Sas9JobExecutor extends BaseJobExecutor {
|
|||||||
if (data) {
|
if (data) {
|
||||||
try {
|
try {
|
||||||
formData = generateFileUploadForm(formData, data)
|
formData = generateFileUploadForm(formData, data)
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
return Promise.reject(new ErrorResponse(e?.message, e))
|
return Promise.reject(new ErrorResponse(e?.message, e))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,3 +3,4 @@ export * from './JesJobExecutor'
|
|||||||
export * from './JobExecutor'
|
export * from './JobExecutor'
|
||||||
export * from './Sas9JobExecutor'
|
export * from './Sas9JobExecutor'
|
||||||
export * from './WebJobExecutor'
|
export * from './WebJobExecutor'
|
||||||
|
export * from './FileUploader'
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* @jest-environment jsdom
|
* @jest-environment jsdom
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { FileUploader } from '../FileUploader'
|
import { FileUploader } from '../job-execution/FileUploader'
|
||||||
import { SASjsConfig, UploadFile } from '../types'
|
import { SASjsConfig, UploadFile } from '../types'
|
||||||
import { RequestClient } from '../request/RequestClient'
|
import { RequestClient } from '../request/RequestClient'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
@@ -39,55 +39,66 @@ describe('FileUploader', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const fileUploader = new FileUploader(
|
const fileUploader = new FileUploader(
|
||||||
|
config.serverUrl,
|
||||||
|
config.serverType!,
|
||||||
'/jobs/path',
|
'/jobs/path',
|
||||||
new RequestClient('https://sample.server.com')
|
new RequestClient('https://sample.server.com')
|
||||||
)
|
)
|
||||||
|
|
||||||
it('should upload successfully', async () => {
|
it('should upload successfully', async () => {
|
||||||
const sasJob = 'test/upload'
|
const sasJob = 'test/upload'
|
||||||
const { files, params } = prepareFilesAndParams()
|
const data = prepareFilesAndParams()
|
||||||
mockedAxios.post.mockImplementation(() =>
|
mockedAxios.post.mockImplementation(() =>
|
||||||
Promise.resolve({ data: sampleResponse })
|
Promise.resolve({ data: sampleResponse })
|
||||||
)
|
)
|
||||||
|
|
||||||
const res = await fileUploader.uploadFile(sasJob, files, params, config)
|
const res = await fileUploader.execute(sasJob, data, config)
|
||||||
|
|
||||||
expect(res).toEqual(JSON.parse(sampleResponse))
|
expect(res).toEqual(JSON.parse(sampleResponse))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should upload successfully when login is required', async () => {
|
||||||
|
mockedAxios.post
|
||||||
|
.mockImplementationOnce(() =>
|
||||||
|
Promise.resolve({ data: '<form action="Logon">' })
|
||||||
|
)
|
||||||
|
.mockImplementationOnce(() => Promise.resolve({ data: sampleResponse }))
|
||||||
|
|
||||||
|
const loginCallback = jest.fn().mockImplementation(async () => {
|
||||||
|
await fileUploader.resendWaitingRequests()
|
||||||
|
Promise.resolve()
|
||||||
|
})
|
||||||
|
|
||||||
|
const sasJob = 'test'
|
||||||
|
const data = prepareFilesAndParams()
|
||||||
|
|
||||||
|
const res = await fileUploader.execute(sasJob, data, config, loginCallback)
|
||||||
|
|
||||||
|
expect(res).toEqual(JSON.parse(sampleResponse))
|
||||||
|
|
||||||
|
expect(mockedAxios.post).toHaveBeenCalledTimes(2)
|
||||||
|
expect(loginCallback).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
it('should an error when no files are provided', async () => {
|
it('should an error when no files are provided', async () => {
|
||||||
const sasJob = 'test/upload'
|
const sasJob = 'test/upload'
|
||||||
const files: UploadFile[] = []
|
const files: UploadFile[] = []
|
||||||
const params = { table: 'libtable' }
|
const params = { table: 'libtable' }
|
||||||
|
|
||||||
const err = await fileUploader
|
const res: any = await fileUploader
|
||||||
.uploadFile(sasJob, files, params, config)
|
.execute(sasJob, files, params, config)
|
||||||
.catch((err: any) => err)
|
.catch((err: any) => err)
|
||||||
expect(err.error.message).toEqual('At least one file must be provided.')
|
expect(res.error.message).toEqual('At least one file must be provided.')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should throw an error when no sasJob is provided', async () => {
|
it('should throw an error when no sasJob is provided', async () => {
|
||||||
const sasJob = ''
|
const sasJob = ''
|
||||||
const { files, params } = prepareFilesAndParams()
|
const data = prepareFilesAndParams()
|
||||||
|
|
||||||
const err = await fileUploader
|
const res: any = await fileUploader
|
||||||
.uploadFile(sasJob, files, params, config)
|
.execute(sasJob, data, config)
|
||||||
.catch((err: any) => err)
|
.catch((err: any) => err)
|
||||||
expect(err.error.message).toEqual('sasJob must be provided.')
|
expect(res.error.message).toEqual('sasJob must be provided.')
|
||||||
})
|
|
||||||
|
|
||||||
it('should throw an error when login is required', async () => {
|
|
||||||
mockedAxios.post.mockImplementation(() =>
|
|
||||||
Promise.resolve({ data: '<form action="Logon">' })
|
|
||||||
)
|
|
||||||
|
|
||||||
const sasJob = 'test'
|
|
||||||
const { files, params } = prepareFilesAndParams()
|
|
||||||
|
|
||||||
const err = await fileUploader
|
|
||||||
.uploadFile(sasJob, files, params, config)
|
|
||||||
.catch((err: any) => err)
|
|
||||||
expect(err.error.message).toEqual('You must be logged in to upload a file.')
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should throw an error when invalid JSON is returned by the server', async () => {
|
it('should throw an error when invalid JSON is returned by the server', async () => {
|
||||||
@@ -96,12 +107,13 @@ describe('FileUploader', () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const sasJob = 'test'
|
const sasJob = 'test'
|
||||||
const { files, params } = prepareFilesAndParams()
|
const data = prepareFilesAndParams()
|
||||||
|
|
||||||
const err = await fileUploader
|
const res: any = await fileUploader
|
||||||
.uploadFile(sasJob, files, params, config)
|
.execute(sasJob, data, config)
|
||||||
.catch((err: any) => err)
|
.catch((err: any) => err)
|
||||||
expect(err.error.message).toEqual('File upload request failed.')
|
|
||||||
|
expect(res.error.message).toEqual('File upload request failed.')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should throw an error when the server request fails', async () => {
|
it('should throw an error when the server request fails', async () => {
|
||||||
@@ -110,11 +122,11 @@ describe('FileUploader', () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const sasJob = 'test'
|
const sasJob = 'test'
|
||||||
const { files, params } = prepareFilesAndParams()
|
const data = prepareFilesAndParams()
|
||||||
|
|
||||||
const err = await fileUploader
|
const res: any = await fileUploader
|
||||||
.uploadFile(sasJob, files, params, config)
|
.execute(sasJob, data, config)
|
||||||
.catch((err: any) => err)
|
.catch((err: any) => err)
|
||||||
expect(err.error.message).toEqual('File upload request failed.')
|
expect(res.error.message).toEqual('File upload request failed.')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user