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

feat(stp): added trigger endpoint

This commit is contained in:
Yury
2024-10-29 16:27:53 +03:00
parent e9519cb3c6
commit b0723f1444
4 changed files with 191 additions and 3 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

@@ -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,31 @@ 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,
@Query() _program?: string
): Promise<TriggerProgramResponse> {
const program = _program ?? body?._program
const vars = { ...request.query, ...request.body }
const filesNamesMap = request.files?.length
? makeFilesNamesMap(request.files as MulterFile[])
: null
const otherArgs = { filesNamesMap: filesNamesMap }
const { expiresAfterMins } = body
return triggerProgram(request, program!, vars, otherArgs, expiresAfterMins)
}
}
const execute = async (
@@ -117,3 +169,47 @@ const execute = async (
}
}
}
const triggerProgram = async (
req: express.Request,
_program: string,
vars: ExecutionVars,
otherArgs?: any,
expiresAfterMins?: number
): Promise<TriggerProgramResponse> => {
try {
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,27 @@ 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,
req.query?._program as string
)
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)