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

feat(studio): run selected code + open in studio

This commit is contained in:
Saad Jutt
2021-12-16 12:14:32 +05:00
parent da11c03d55
commit 27129a8921
7 changed files with 204 additions and 14 deletions

View File

@@ -358,6 +358,16 @@ components:
- description
type: object
additionalProperties: false
RunSASPayload:
properties:
code:
type: string
description: 'Code of SAS program'
example: '* SAS Code HERE;'
required:
- code
type: object
additionalProperties: false
ExecuteReturnJsonResponse:
properties:
status:
@@ -1027,6 +1037,30 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/ExecuteReturnJsonPayload'
/SASjsApi/stp/run:
post:
operationId: RunSAS
responses:
'200':
description: Ok
content:
application/json:
schema:
type: string
description: 'Trigger a SAS program.'
summary: 'Run SAS Program, return raw content'
tags:
- STP
security:
-
bearerAuth: []
parameters: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/RunSASPayload'
/SASjsApi/session:
get:
operationId: Session

View File

@@ -6,7 +6,7 @@ import { PreProgramVars, TreeNode } from '../../types'
import { generateFileUploadSasCode, getTmpFilesFolderPath } from '../../utils'
export class ExecutionController {
async execute(
async executeFile(
programPath: string,
preProgramVariables: PreProgramVars,
vars: { [key: string]: string | number | undefined },
@@ -16,8 +16,23 @@ export class ExecutionController {
if (!(await fileExists(programPath)))
throw 'ExecutionController: SAS file does not exist.'
let program = await readFile(programPath)
const program = await readFile(programPath)
return this.executeProgram(
program,
preProgramVariables,
vars,
otherArgs,
returnJson
)
}
async executeProgram(
program: string,
preProgramVariables: PreProgramVars,
vars: { [key: string]: string | number | undefined },
otherArgs?: any,
returnJson?: boolean
) {
const sessionController = getSessionController()
const session = await sessionController.getSession()

View File

@@ -5,6 +5,13 @@ import { ExecutionController } from './internal'
import { PreProgramVars } from '../types'
import { getTmpFilesFolderPath, makeFilesNamesMap } from '../utils'
interface RunSASPayload {
/**
* Code of SAS program
* @example "* SAS Code HERE;"
*/
code: string
}
interface ExecuteReturnJsonPayload {
/**
* Location of SAS program
@@ -40,6 +47,19 @@ export class STPController {
): Promise<string> {
return executeReturnRaw(request, _program)
}
/**
* Trigger a SAS program.
* @summary Run SAS Program, return raw content
*/
@Post('/run')
public async runSAS(
@Request() request: express.Request,
@Body() body: RunSASPayload
): Promise<string> {
return runSAS(request, body)
}
/**
* Trigger a SAS program using it's location in the _program parameter.
* Enable debugging using the _debug parameter.
@@ -72,7 +92,7 @@ const executeReturnRaw = async (
.replace(new RegExp('/', 'g'), path.sep) + '.sas'
try {
const result = await new ExecutionController().execute(
const result = await new ExecutionController().executeFile(
sasCodePath,
getPreProgramVariables(req),
query
@@ -89,6 +109,25 @@ const executeReturnRaw = async (
}
}
const runSAS = async (req: any, { code }: RunSASPayload) => {
try {
const result = await new ExecutionController().executeProgram(
code,
getPreProgramVariables(req),
req.query
)
return result as string
} catch (err: any) {
throw {
code: 400,
status: 'failure',
message: 'Job execution failed.',
error: typeof err === 'object' ? err.toString() : err
}
}
}
const executeReturnJson = async (
req: any,
_program: string
@@ -101,7 +140,7 @@ const executeReturnJson = async (
const filesNamesMap = req.files?.length ? makeFilesNamesMap(req.files) : null
try {
const { webout, log } = (await new ExecutionController().execute(
const { webout, log } = (await new ExecutionController().executeFile(
sasCodePath,
getPreProgramVariables(req),
{ ...req.query, ...req.body },

View File

@@ -1,5 +1,5 @@
import express from 'express'
import { executeProgramRawValidation } from '../../utils'
import { executeProgramRawValidation, runSASValidation } from '../../utils'
import { STPController } from '../../controllers/'
import { FileUploadController } from '../../controllers/internal'
@@ -24,6 +24,22 @@ stpRouter.get('/execute', async (req, res) => {
}
})
stpRouter.post('/run', async (req, res) => {
const { error, value: body } = runSASValidation(req.body)
if (error) return res.status(400).send(error.details[0].message)
try {
const response = await controller.runSAS(req, body)
res.send(response)
} catch (err: any) {
const statusCode = err.code
delete err.code
res.status(statusCode).send(err)
}
})
stpRouter.post(
'/execute',
fileUploadController.preuploadMiddleware,

View File

@@ -77,6 +77,11 @@ export const updateFileDriveValidation = (data: any): Joi.ValidationResult =>
fileContent: Joi.string().required()
}).validate(data)
export const runSASValidation = (data: any): Joi.ValidationResult =>
Joi.object({
code: Joi.string().required()
}).validate(data)
export const executeProgramRawValidation = (data: any): Joi.ValidationResult =>
Joi.object({
_program: Joi.string().required()

View File

@@ -1,4 +1,5 @@
import React, { useState, useEffect } from 'react'
import { Link } from 'react-router-dom'
import axios from 'axios'
import Editor from '@monaco-editor/react'
@@ -89,10 +90,10 @@ const Main = (props: any) => {
style={{ position: 'absolute', left: '50%', top: '50%' }}
/>
)}
{!isLoading && props?.selectedFilePath !== '' && !editMode && (
{!isLoading && props?.selectedFilePath && !editMode && (
<code style={{ whiteSpace: 'break-spaces' }}>{fileContent}</code>
)}
{!isLoading && props?.selectedFilePath !== '' && editMode && (
{!isLoading && props?.selectedFilePath && editMode && (
<Editor
height="95%"
value={fileContent}
@@ -110,17 +111,26 @@ const Main = (props: any) => {
<Button
variant="contained"
onClick={handleEditSaveBtnClick}
disabled={isLoading || props?.selectedFilePath === ''}
disabled={isLoading || !props?.selectedFilePath}
>
{!editMode ? 'Edit' : 'Save'}
</Button>
<Button
variant="contained"
onClick={handleCancelExecuteBtnClick}
disabled={isLoading || props?.selectedFilePath === ''}
disabled={isLoading || !props?.selectedFilePath}
>
{editMode ? 'Cancel' : 'Execute'}
</Button>
{props?.selectedFilePath && (
<Button
variant="contained"
component={Link}
to={`/SASjsStudio?_program=${props.selectedFilePath}`}
>
Open in Studio
</Button>
)}
</Stack>
</Box>
)

View File

@@ -1,13 +1,84 @@
import React from 'react'
import React, { useEffect, useRef, useState } from 'react'
import axios from 'axios'
import CssBaseline from '@mui/material/CssBaseline'
import Box from '@mui/material/Box'
import { Button, Paper, Stack, Toolbar } from '@mui/material'
import Editor from '@monaco-editor/react'
import { useLocation } from 'react-router-dom'
const Studio = () => {
const location = useLocation()
const [fileContent, setFileContent] = useState('')
const [log, setLog] = useState('')
const editorRef = useRef(null)
const handleEditorDidMount = (editor: any) => (editorRef.current = editor)
const getSelection = () => {
const editor = editorRef.current as any
const selection = editor?.getModel().getValueInRange(editor?.getSelection())
return selection ?? ''
}
const handleRunSelectionBtnClick = () => runCode(getSelection())
const handleRunBtnClick = () => runCode(fileContent)
const runCode = (code: string) => {
axios
.post(`/SASjsApi/stp/run`, { code })
.then((res: any) => {
setLog(res.data)
document?.getElementById('sas_log')?.scrollIntoView()
})
.catch((err) => console.log(err))
}
useEffect(() => {
const params = new URLSearchParams(location.search)
const programPath = params.get('_program')
if (programPath?.length)
axios
.get(`/SASjsApi/drive/file?filePath=${programPath}`)
.then((res: any) => setFileContent(res.data.fileContent))
.catch((err) => console.log(err))
}, [location.search])
return (
<Box className="main">
<CssBaseline />
<h2>This is container for SASjs studio</h2>
<Box component="main" sx={{ flexGrow: 1, p: 3 }}>
<Toolbar />
<Paper
sx={{
height: '75vh',
padding: '10px',
overflow: 'auto',
position: 'relative'
}}
elevation={3}
>
<Editor
height="95%"
value={fileContent}
onMount={handleEditorDidMount}
onChange={(val) => {
if (val) setFileContent(val)
}}
/>
</Paper>
<Stack
spacing={3}
direction="row"
sx={{ justifyContent: 'center', marginTop: '20px' }}
>
<Button variant="contained" onClick={handleRunBtnClick}>
Run SAS Code
</Button>
<Button variant="contained" onClick={handleRunSelectionBtnClick}>
Run Selected SAS Code
</Button>
</Stack>
{log && <div id="sas_log" dangerouslySetInnerHTML={{ __html: log }} />}
</Box>
)
}