mirror of
https://github.com/sasjs/server.git
synced 2026-01-16 18:30:06 +00:00
feat(executor): improved api response
This commit is contained in:
@@ -1,27 +1,21 @@
|
|||||||
import {
|
import { readFile, deleteFile, fileExists, createFile } from '@sasjs/utils'
|
||||||
readFile,
|
|
||||||
generateTimestamp,
|
|
||||||
deleteFile,
|
|
||||||
fileExists,
|
|
||||||
createFile
|
|
||||||
} from '@sasjs/utils'
|
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { ExecutionResult, ExecutionQuery } from '../types'
|
import { ExecutionResult, ExecutionQuery } from '../types'
|
||||||
import {
|
import {
|
||||||
getTmpFilesFolderPath,
|
getTmpFilesFolderPath,
|
||||||
getTmpLogFolderPath,
|
getTmpLogFolderPath,
|
||||||
getTmpWeboutFolderPath
|
getTmpWeboutFolderPath,
|
||||||
|
generateUniqueFileName
|
||||||
} from '../utils'
|
} from '../utils'
|
||||||
import { configuration } from '../../package.json'
|
import { configuration } from '../../package.json'
|
||||||
import { promisify } from 'util'
|
import { promisify } from 'util'
|
||||||
import { execFile } from 'child_process'
|
import { execFile } from 'child_process'
|
||||||
const execFilePromise = promisify(execFile)
|
const execFilePromise = promisify(execFile)
|
||||||
|
|
||||||
export const processSas = async (
|
export const processSas = async (query: ExecutionQuery): Promise<any> => {
|
||||||
query: ExecutionQuery
|
const sasCodePath = path
|
||||||
): Promise<ExecutionResult> => {
|
.join(getTmpFilesFolderPath(), query._program)
|
||||||
let sasCodePath = path.join(getTmpFilesFolderPath(), query._program)
|
.replace(new RegExp('/', 'g'), path.sep)
|
||||||
sasCodePath = sasCodePath.replace(new RegExp('/', 'g'), path.sep)
|
|
||||||
|
|
||||||
if (!(await fileExists(sasCodePath))) {
|
if (!(await fileExists(sasCodePath))) {
|
||||||
return Promise.reject('SAS file does not exist.')
|
return Promise.reject('SAS file does not exist.')
|
||||||
@@ -29,71 +23,73 @@ export const processSas = async (
|
|||||||
|
|
||||||
const sasFile: string = sasCodePath.split(path.sep).pop() || 'default'
|
const sasFile: string = sasCodePath.split(path.sep).pop() || 'default'
|
||||||
|
|
||||||
const logArgs = []
|
const sasLogPath = path.join(
|
||||||
let sasLogPath
|
getTmpLogFolderPath(),
|
||||||
|
generateUniqueFileName(sasFile.replace(/\.sas/g, ''), '.log')
|
||||||
if (query._debug) {
|
)
|
||||||
sasLogPath = path.join(
|
|
||||||
getTmpLogFolderPath(),
|
|
||||||
[sasFile.replace(/\.sas/g, ''), '-', generateTimestamp(), '.log'].join('')
|
|
||||||
)
|
|
||||||
logArgs.push('-log')
|
|
||||||
logArgs.push(sasLogPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
const sasWeboutPath = path.join(
|
const sasWeboutPath = path.join(
|
||||||
getTmpWeboutFolderPath(),
|
getTmpWeboutFolderPath(),
|
||||||
[sasFile.replace(/\.sas/g, ''), '-', generateTimestamp(), '.json'].join('')
|
generateUniqueFileName(sasFile.replace(/\.sas/g, ''), '.json')
|
||||||
)
|
)
|
||||||
|
|
||||||
let sasCode = await readFile(sasCodePath)
|
let sasCode = await readFile(sasCodePath)
|
||||||
const originalSasCode = sasCode
|
|
||||||
|
|
||||||
if (query.macroVars) {
|
const vars: any = query
|
||||||
const macroVars = query.macroVars.macroVars
|
Object.keys(query).forEach(
|
||||||
|
(key: string) => (sasCode = `%let ${key}=${vars[key]};\n${sasCode}`)
|
||||||
Object.keys(macroVars).forEach(
|
)
|
||||||
(key: string) => (sasCode = `%let ${key}=${macroVars[key]};\n${sasCode}`)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
sasCode = `filename _webout "${sasWeboutPath}";\n${sasCode}`
|
sasCode = `filename _webout "${sasWeboutPath}";\n${sasCode}`
|
||||||
|
|
||||||
await createFile(sasCodePath, sasCode)
|
const tmpSasCodePath = sasCodePath.replace(
|
||||||
|
sasFile,
|
||||||
|
generateUniqueFileName(sasFile)
|
||||||
|
)
|
||||||
|
|
||||||
|
await createFile(tmpSasCodePath, sasCode)
|
||||||
|
|
||||||
const { stdout, stderr } = await execFilePromise(configuration.sasPath, [
|
const { stdout, stderr } = await execFilePromise(configuration.sasPath, [
|
||||||
'-SYSIN',
|
'-SYSIN',
|
||||||
sasCodePath,
|
tmpSasCodePath,
|
||||||
...logArgs,
|
'-log',
|
||||||
'-nosplash'
|
sasLogPath,
|
||||||
])
|
'-nosplash' // FIXME: should be configurable
|
||||||
|
]).catch((err) => ({ stderr: err, stdout: '' }))
|
||||||
|
|
||||||
if (stderr) return Promise.reject(stderr)
|
let log = ''
|
||||||
|
if (sasLogPath && (await fileExists(sasLogPath))) {
|
||||||
if (await fileExists(sasWeboutPath)) {
|
log = await readFile(sasLogPath)
|
||||||
const webout = await readFile(sasWeboutPath)
|
|
||||||
|
|
||||||
try {
|
|
||||||
const weboutJson = JSON.parse(webout)
|
|
||||||
|
|
||||||
if (sasLogPath && (await fileExists(sasLogPath))) {
|
|
||||||
return Promise.resolve({
|
|
||||||
webout: weboutJson,
|
|
||||||
log: await readFile(sasLogPath),
|
|
||||||
logPath: sasLogPath
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
return Promise.resolve({
|
|
||||||
webout: weboutJson
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
return Promise.reject(`Error while parsing Webout. Details: ${error}`)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Promise.reject(`Webout wasn't created.`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// await createFile(sasCodePath, originalSasCode)
|
await deleteFile(sasLogPath)
|
||||||
// await deleteFile(sasLogPath)
|
await deleteFile(tmpSasCodePath)
|
||||||
|
|
||||||
|
if (stderr) return Promise.reject({ error: stderr, log: log })
|
||||||
|
|
||||||
|
if (await fileExists(sasWeboutPath)) {
|
||||||
|
let webout = await readFile(sasWeboutPath)
|
||||||
|
|
||||||
|
await deleteFile(sasWeboutPath)
|
||||||
|
|
||||||
|
const debug = Object.keys(query).find(
|
||||||
|
(key: string) => key.toLowerCase() === '_debug'
|
||||||
|
)
|
||||||
|
|
||||||
|
if (debug && (query as any)[debug] >= 131) {
|
||||||
|
webout = `<html><body>
|
||||||
|
>>weboutBEGIN<< ${webout} >>weboutEND<<
|
||||||
|
<div style="text-align:left">
|
||||||
|
<hr /><h2>SAS Log</h2>
|
||||||
|
<pre>${log}</pre>
|
||||||
|
</div>
|
||||||
|
</body></html>`
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve(webout)
|
||||||
|
} else {
|
||||||
|
return Promise.resolve({
|
||||||
|
log: log
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,27 +53,13 @@ router.get('/SASjsExecutor', async (req, res) => {
|
|||||||
res.status(200).send({ status: 'success', tree: {} })
|
res.status(200).send({ status: 'success', tree: {} })
|
||||||
})
|
})
|
||||||
|
|
||||||
// SAS:
|
|
||||||
// https://sas.analytium.co.uk:8343/SASStoredProcess/do?_action=form,properties,execute,noba[…]blic%2Fapp%2Fdata-combiner%2Fservices%2Fcommon%2Fappinit
|
|
||||||
// https://sas.analytium.co.uk:8343/SASStoredProcess/
|
|
||||||
// https://sas.analytium.co.uk:8343/SASStoredProcess/do?&_program=%2FPublic%2Fapp%2Fdata-combiner%2Fservices%2Fcommon%2Fappinit&_DEBUG=131
|
|
||||||
// https://sas.analytium.co.uk:8343/SASStoredProcess/do?_program=%2FPublic%2Fapp%2Fdata-comb[…]ction=update%2Cnewwindow%2Cnobanner&_updatekey=895432774
|
|
||||||
|
|
||||||
// SASjs:
|
|
||||||
// http://localhost:5000/SASjsExecutor?_program=%2FPublic%2Fapp%2Fdata-combiner%2Fservices%2Fcommon%2Fappinit
|
|
||||||
// http://localhost:5000/SASjsExecutor
|
|
||||||
// http://localhost:5000/SASjsExecutor?_program=%2FPublic%2Fapp%2Fdata-combiner%2Fservices%2Fcommon%2Fappinit&_DEBUG=131
|
|
||||||
|
|
||||||
router.get('/SASjsExecutor/do', async (req, res) => {
|
router.get('/SASjsExecutor/do', async (req, res) => {
|
||||||
const queryEntries = Object.keys(req.query).map((entry: string) =>
|
const queryEntries = Object.keys(req.query).map((entry: string) =>
|
||||||
entry.toLowerCase()
|
entry.toLowerCase()
|
||||||
)
|
)
|
||||||
const isDebug = queryEntries.find((entry: string) => entry === '_debug')
|
|
||||||
? true
|
|
||||||
: false
|
|
||||||
|
|
||||||
if (isRequestQuery(req.query)) {
|
if (isRequestQuery(req.query)) {
|
||||||
await processSas({ ...req.query, _debug: isDebug })
|
await processSas({ ...req.query })
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
res.status(200).send(result)
|
res.status(200).send(result)
|
||||||
})
|
})
|
||||||
@@ -81,7 +67,7 @@ router.get('/SASjsExecutor/do', async (req, res) => {
|
|||||||
res.status(400).send({
|
res.status(400).send({
|
||||||
status: 'failure',
|
status: 'failure',
|
||||||
message: 'Job execution failed.',
|
message: 'Job execution failed.',
|
||||||
error: err
|
...err
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
export interface ExecutionResult {
|
export interface ExecutionResult {
|
||||||
webout: object
|
webout?: string
|
||||||
log?: string
|
log?: string
|
||||||
logPath?: string
|
logPath?: string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { getRealPath } from '@sasjs/utils'
|
import { getRealPath, generateTimestamp } from '@sasjs/utils'
|
||||||
|
|
||||||
export const getTmpFolderPath = () =>
|
export const getTmpFolderPath = () =>
|
||||||
getRealPath(path.join(__dirname, '..', '..', 'tmp'))
|
getRealPath(path.join(__dirname, '..', '..', 'tmp'))
|
||||||
@@ -11,3 +11,13 @@ export const getTmpLogFolderPath = () => path.join(getTmpFolderPath(), 'logs')
|
|||||||
|
|
||||||
export const getTmpWeboutFolderPath = () =>
|
export const getTmpWeboutFolderPath = () =>
|
||||||
path.join(getTmpFolderPath(), 'webouts')
|
path.join(getTmpFolderPath(), 'webouts')
|
||||||
|
|
||||||
|
export const generateUniqueFileName = (fileName: string, extension = '') =>
|
||||||
|
[
|
||||||
|
fileName,
|
||||||
|
'-',
|
||||||
|
Math.round(Math.random() * 100000),
|
||||||
|
'-',
|
||||||
|
generateTimestamp(),
|
||||||
|
extension
|
||||||
|
].join('')
|
||||||
|
|||||||
Reference in New Issue
Block a user