Skip to content

Commit

Permalink
Merge pull request #375 from sasjs/issue-373-stp
Browse files Browse the repository at this point in the history
Issue 373 stp
  • Loading branch information
YuryShkoda authored Oct 29, 2024
2 parents 0838b81 + deee34f commit a044176
Show file tree
Hide file tree
Showing 5 changed files with 181 additions and 4 deletions.
57 changes: 57 additions & 0 deletions api/public/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion api/src/controllers/code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
93 changes: 91 additions & 2 deletions api/src/controllers/stp.ts
Original file line number Diff line number Diff line change
@@ -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 {
/**
Expand All @@ -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')
Expand Down Expand Up @@ -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 (
Expand Down Expand Up @@ -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
}
}
}
24 changes: 23 additions & 1 deletion api/src/routes/api/stp.ts
Original file line number Diff line number Diff line change
@@ -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'

Expand Down Expand Up @@ -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
9 changes: 9 additions & 0 deletions api/src/utils/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)

0 comments on commit a044176

Please sign in to comment.