1
0
mirror of https://github.com/sasjs/server.git synced 2025-12-10 11:24:35 +00:00

Merge pull request #375 from sasjs/issue-373-stp

Issue 373 stp
This commit is contained in:
Yury Shkoda
2024-10-29 17:08:38 +03:00
committed by GitHub
5 changed files with 181 additions and 4 deletions

View File

@@ -593,6 +593,31 @@ components:
example: /Public/somefolder/some.file
type: object
additionalProperties: false
TriggerProgramResponse:
properties:
sessionId:
type: string
description: "The SessionId is the name of the temporary folder used to store the outputs.\nFor SAS, this would be the SASWORK folder. Can be used to poll program status.\nThis session ID should be used to poll program status."
example: '{ sessionId: ''20241028074744-54132-1730101664824'' }'
required:
- sessionId
type: object
additionalProperties: false
TriggerProgramPayload:
properties:
_program:
type: string
description: 'Location of SAS program'
example: /Public/somefolder/some.file
expiresAfterMins:
type: number
format: double
description: "Amount of minutes after the completion of the program when the session must be\ndestroyed."
example: 15
required:
- _program
type: object
additionalProperties: false
LoginPayload:
properties:
username:
@@ -1901,6 +1926,38 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/ExecutePostRequestPayload'
/SASjsApi/stp/trigger:
post:
operationId: TriggerProgram
responses:
'200':
description: Ok
content:
application/json:
schema:
$ref: '#/components/schemas/TriggerProgramResponse'
description: 'Trigger Program on the Specified Runtime'
summary: 'Triggers program and returns SessionId immediately - does not wait for program completion'
tags:
- STP
security:
-
bearerAuth: []
parameters:
-
description: 'Location of code in SASjs Drive'
in: query
name: _program
required: false
schema:
type: string
example: /Projects/myApp/some/program
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/TriggerProgramPayload'
/:
get:
operationId: Home

View File

@@ -120,7 +120,7 @@ const executeCode = async (
const triggerCode = async (
req: express.Request,
{ code, runTime, expiresAfterMins }: TriggerCodePayload
): Promise<{ sessionId: string }> => {
): Promise<TriggerCodeResponse> => {
const { user } = req
const userAutoExec =
process.env.MODE === ModeType.Server

View File

@@ -1,13 +1,16 @@
import express from 'express'
import { Request, Security, Route, Tags, Post, Body, Get, Query } from 'tsoa'
import { ExecutionController, ExecutionVars } from './internal'
import {
ExecutionController,
ExecutionVars,
getSessionController
} from './internal'
import {
getPreProgramVariables,
makeFilesNamesMap,
getRunTimeAndFilePath
} from '../utils'
import { MulterFile } from '../types/Upload'
import { debug } from 'console'
interface ExecutePostRequestPayload {
/**
@@ -17,6 +20,30 @@ interface ExecutePostRequestPayload {
_program?: string
}
interface TriggerProgramPayload {
/**
* Location of SAS program
* @example "/Public/somefolder/some.file"
*/
_program: string
/**
* Amount of minutes after the completion of the program when the session must be
* destroyed.
* @example 15
*/
expiresAfterMins?: number
}
interface TriggerProgramResponse {
/**
* The SessionId is the name of the temporary folder used to store the outputs.
* For SAS, this would be the SASWORK folder. Can be used to poll program status.
* This session ID should be used to poll program status.
* @example "{ sessionId: '20241028074744-54132-1730101664824' }"
*/
sessionId: string
}
@Security('bearerAuth')
@Route('SASjsApi/stp')
@Tags('STP')
@@ -79,6 +106,22 @@ export class STPController {
return execute(request, program!, vars, otherArgs)
}
/**
* Trigger Program on the Specified Runtime
* @summary Triggers program and returns SessionId immediately - does not wait for program completion
* @param _program Location of code in SASjs Drive
* @example _program "/Projects/myApp/some/program"
* @param expiresAfterMins Amount of minutes after the completion of the program when the session must be destroyed
* @example expiresAfterMins 15
*/
@Post('/trigger')
public async triggerProgram(
@Request() request: express.Request,
@Body() body: TriggerProgramPayload
): Promise<TriggerProgramResponse> {
return triggerProgram(request, body)
}
}
const execute = async (
@@ -117,3 +160,49 @@ const execute = async (
}
}
}
const triggerProgram = async (
req: express.Request,
{ _program, expiresAfterMins }: TriggerProgramPayload
): Promise<TriggerProgramResponse> => {
try {
const vars = { ...req.body }
const filesNamesMap = req.files?.length
? makeFilesNamesMap(req.files as MulterFile[])
: null
const otherArgs = { filesNamesMap: filesNamesMap }
const { codePath, runTime } = await getRunTimeAndFilePath(_program)
// get session controller based on runTime
const sessionController = getSessionController(runTime)
// get session
const session = await sessionController.getSession()
// add expiresAfterMins to session if provided
if (expiresAfterMins) {
// expiresAfterMins.used is set initially to false
session.expiresAfterMins = { mins: expiresAfterMins, used: false }
}
// call executeFile method of ExecutionController without awaiting
new ExecutionController().executeFile({
programPath: codePath,
runTime,
preProgramVariables: getPreProgramVariables(req),
vars,
otherArgs,
session
})
// return session id
return { sessionId: session.id }
} catch (err: any) {
throw {
code: 400,
status: 'failure',
message: 'Job execution failed.',
error: typeof err === 'object' ? err.toString() : err
}
}
}

View File

@@ -1,5 +1,8 @@
import express from 'express'
import { executeProgramRawValidation } from '../../utils'
import {
executeProgramRawValidation,
triggerProgramValidation
} from '../../utils'
import { STPController } from '../../controllers/'
import { FileUploadController } from '../../controllers/internal'
@@ -69,4 +72,23 @@ stpRouter.post(
}
)
stpRouter.post('/trigger', async (req, res) => {
const { error, value: body } = triggerProgramValidation(req.body)
if (error) return res.status(400).send(error.details[0].message)
try {
const response = await controller.triggerProgram(req, body)
res.status(200)
res.send(response)
} catch (err: any) {
const statusCode = err.code
delete err.code
res.status(statusCode).send(err)
}
})
export default stpRouter

View File

@@ -192,3 +192,12 @@ export const executeProgramRawValidation = (data: any): Joi.ValidationResult =>
})
.pattern(/^/, Joi.alternatives(Joi.string(), Joi.number()))
.validate(data)
export const triggerProgramValidation = (data: any): Joi.ValidationResult =>
Joi.object({
_program: Joi.string().required(),
_debug: Joi.number(),
expiresAfterMins: Joi.number().greater(0)
})
.pattern(/^/, Joi.alternatives(Joi.string(), Joi.number()))
.validate(data)