mirror of
https://github.com/sasjs/adapter.git
synced 2025-12-11 09:24:35 +00:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
102898ac33 | ||
| 7370a2be4c | |||
| 135d019026 | |||
| e2651344d7 | |||
|
|
7a42bc1b88 | ||
|
|
6c02ee4cd6 | ||
| 73ee214b61 | |||
|
|
77487bfa35 | ||
|
|
9cf0165cf7 | ||
|
|
e4d4b3142f | ||
|
|
a87be39b44 | ||
|
|
8ea621ac98 | ||
| ea61119919 | |||
|
|
01235616a0 | ||
| 6cab245cde | |||
| ed90cd8036 | |||
|
|
784bab4522 | ||
| b0eb8b07a8 | |||
| 1d1ef7179e |
@@ -84,6 +84,15 @@ parmcards4;
|
||||
%webout(CLOSE)
|
||||
;;;;
|
||||
%mm_createwebservice(path=/Public/app/common,name=sendArr)
|
||||
parmcards4;
|
||||
data work.macvars;
|
||||
set sashelp.vmacro;
|
||||
run;
|
||||
%webout(OPEN)
|
||||
%webout(OBJ,macvars)
|
||||
%webout(CLOSE)
|
||||
;;;;
|
||||
%mm_createwebservice(path=/Public/app/common,name=sendMacVars)
|
||||
parmcards4;
|
||||
let he who hath understanding, reckon the number of the beast
|
||||
;;;;
|
||||
@@ -118,7 +127,6 @@ parmcards4;
|
||||
%webout(CLOSE)
|
||||
;;;;
|
||||
%mp_createwebservice(path=/Public/app/common,name=sendObj)
|
||||
filename ft15f001 temp;
|
||||
parmcards4;
|
||||
%webout(FETCH)
|
||||
%webout(OPEN)
|
||||
@@ -132,7 +140,15 @@ parmcards4;
|
||||
%webout(CLOSE)
|
||||
;;;;
|
||||
%mp_createwebservice(path=/Public/app/common,name=sendArr)
|
||||
filename ft15f001 temp;
|
||||
parmcards4;
|
||||
data work.macvars;
|
||||
set sashelp.vmacro;
|
||||
run;
|
||||
%webout(OPEN)
|
||||
%webout(OBJ,macvars)
|
||||
%webout(CLOSE)
|
||||
;;;;
|
||||
%mp_createwebservice(path=/Public/app/common,name=sendMacVars)
|
||||
parmcards4;
|
||||
If you can keep your head when all about you
|
||||
Are losing theirs and blaming it on you,
|
||||
|
||||
20
sasjs-tests/package-lock.json
generated
20
sasjs-tests/package-lock.json
generated
@@ -2388,11 +2388,11 @@
|
||||
"node_modules/@sasjs/adapter": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "file:../build/sasjs-adapter-5.0.0.tgz",
|
||||
"integrity": "sha512-lbDWueAEnfNlu4OGrc9hBEzT0aoLfAy7eLd2nLHArrF6zukcSGBNhUgOqxIhlz4WeBdf1gt3nk1G7p5X1mrWYQ==",
|
||||
"integrity": "sha512-5qtEs9yFuZ4v2UrFGNHeCIr/yZTp7D9He+e+N333qW9mdLJJ8fzRifuur/rFE6bNPqC2bdCjicYkO/yrHR4LQw==",
|
||||
"hasInstallScript": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@sasjs/utils": "2.36.1",
|
||||
"@sasjs/utils": "2.40.1",
|
||||
"axios": "0.26.0",
|
||||
"axios-cookiejar-support": "1.0.1",
|
||||
"form-data": "4.0.0",
|
||||
@@ -2422,9 +2422,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@sasjs/utils": {
|
||||
"version": "2.36.1",
|
||||
"resolved": "https://registry.npmjs.org/@sasjs/utils/-/utils-2.36.1.tgz",
|
||||
"integrity": "sha512-JkGUpLOODsvkeU+S25jb9k2WnvzyD2w6cEk7YyQ/byuqKL8xawH91PPWegrVcJlDY8WmqKE4CPcA3d1mM3B3LA==",
|
||||
"version": "2.40.1",
|
||||
"resolved": "https://registry.npmjs.org/@sasjs/utils/-/utils-2.40.1.tgz",
|
||||
"integrity": "sha512-wWYElDH71bSZTdZ5V38743vAnw2EPDhzH7+1s7zxINHpaQWK/qrDldI0vgVFLeGpxVU0D7WPZ/ltG6MoE2obeg==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@types/fs-extra": "9.0.13",
|
||||
@@ -20998,9 +20998,9 @@
|
||||
},
|
||||
"@sasjs/adapter": {
|
||||
"version": "file:../build/sasjs-adapter-5.0.0.tgz",
|
||||
"integrity": "sha512-lbDWueAEnfNlu4OGrc9hBEzT0aoLfAy7eLd2nLHArrF6zukcSGBNhUgOqxIhlz4WeBdf1gt3nk1G7p5X1mrWYQ==",
|
||||
"integrity": "sha512-5qtEs9yFuZ4v2UrFGNHeCIr/yZTp7D9He+e+N333qW9mdLJJ8fzRifuur/rFE6bNPqC2bdCjicYkO/yrHR4LQw==",
|
||||
"requires": {
|
||||
"@sasjs/utils": "2.36.1",
|
||||
"@sasjs/utils": "2.40.1",
|
||||
"axios": "0.26.0",
|
||||
"axios-cookiejar-support": "1.0.1",
|
||||
"form-data": "4.0.0",
|
||||
@@ -21022,9 +21022,9 @@
|
||||
}
|
||||
},
|
||||
"@sasjs/utils": {
|
||||
"version": "2.36.1",
|
||||
"resolved": "https://registry.npmjs.org/@sasjs/utils/-/utils-2.36.1.tgz",
|
||||
"integrity": "sha512-JkGUpLOODsvkeU+S25jb9k2WnvzyD2w6cEk7YyQ/byuqKL8xawH91PPWegrVcJlDY8WmqKE4CPcA3d1mM3B3LA==",
|
||||
"version": "2.40.1",
|
||||
"resolved": "https://registry.npmjs.org/@sasjs/utils/-/utils-2.40.1.tgz",
|
||||
"integrity": "sha512-wWYElDH71bSZTdZ5V38743vAnw2EPDhzH7+1s7zxINHpaQWK/qrDldI0vgVFLeGpxVU0D7WPZ/ltG6MoE2obeg==",
|
||||
"requires": {
|
||||
"@types/fs-extra": "9.0.13",
|
||||
"@types/prompts": "2.0.13",
|
||||
|
||||
@@ -6,6 +6,7 @@ import { specialCaseTests } from './testSuites/SpecialCases'
|
||||
import { sasjsRequestTests } from './testSuites/SasjsRequests'
|
||||
import '@sasjs/test-framework/dist/index.css'
|
||||
import { computeTests } from './testSuites/Compute'
|
||||
import { fileUploadTests } from './testSuites/FileUpload'
|
||||
|
||||
const App = (): ReactElement<{}> => {
|
||||
const { adapter, config } = useContext(AppContext)
|
||||
@@ -18,7 +19,8 @@ const App = (): ReactElement<{}> => {
|
||||
sendArrTests(adapter),
|
||||
sendObjTests(adapter),
|
||||
specialCaseTests(adapter),
|
||||
sasjsRequestTests(adapter)
|
||||
sasjsRequestTests(adapter),
|
||||
fileUploadTests(adapter)
|
||||
]
|
||||
|
||||
if (adapter.getSasjsConfig().serverType === 'SASVIYA') {
|
||||
|
||||
35
sasjs-tests/src/testSuites/FileUpload.ts
Normal file
35
sasjs-tests/src/testSuites/FileUpload.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import SASjs from '@sasjs/adapter'
|
||||
import { TestSuite } from '@sasjs/test-framework'
|
||||
|
||||
export const fileUploadTests = (adapter: SASjs): TestSuite => ({
|
||||
name: 'File Upload Tests',
|
||||
tests: [
|
||||
{
|
||||
title: 'Upload File',
|
||||
description: 'Should upload the file to VIYA',
|
||||
test: async () => {
|
||||
let blob: any = new Blob(['test'], { type: 'text/html' })
|
||||
blob['lastModifiedDate'] = ''
|
||||
blob['name'] = 'macvars_testfile'
|
||||
let file = blob
|
||||
|
||||
const filesToUpload = [
|
||||
{
|
||||
file: file,
|
||||
fileName: file.name
|
||||
}
|
||||
]
|
||||
|
||||
return adapter.uploadFile('common/sendMacVars', filesToUpload, null)
|
||||
},
|
||||
assertion: (response: any) =>
|
||||
(response.macvars as any[]).findIndex(
|
||||
(el: any) => el.NAME === '_WEBIN_FILE_COUNT' && el.VALUE === '1'
|
||||
) > -1 &&
|
||||
(response.macvars as any[]).findIndex(
|
||||
(el: any) =>
|
||||
el.NAME === '_WEBIN_FILENAME' && el.VALUE === 'macvars_testfile'
|
||||
) > -1
|
||||
}
|
||||
]
|
||||
})
|
||||
@@ -218,6 +218,7 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
|
||||
const invalidData: any = {
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx: [{ col1: 42 }]
|
||||
}
|
||||
|
||||
return adapter.request('common/sendObj', invalidData).catch((e) => e)
|
||||
},
|
||||
assertion: (error: any) =>
|
||||
|
||||
@@ -11,7 +11,11 @@ import {
|
||||
JobDefinition,
|
||||
PollOptions
|
||||
} from './types'
|
||||
import { JobExecutionError, RootFolderNotFoundError } from './types/errors'
|
||||
import {
|
||||
CertificateError,
|
||||
JobExecutionError,
|
||||
RootFolderNotFoundError
|
||||
} from './types/errors'
|
||||
import { SessionManager } from './SessionManager'
|
||||
import { ContextManager } from './ContextManager'
|
||||
import { SasAuthResponse, MacroVar, AuthConfig } from '@sasjs/utils/types'
|
||||
@@ -878,7 +882,8 @@ export class SASViyaApiClient {
|
||||
|
||||
const { result: folder } = await this.requestClient
|
||||
.get<Folder>(`${this.serverUrl}${url}`, accessToken)
|
||||
.catch(() => {
|
||||
.catch((err) => {
|
||||
if (err instanceof CertificateError) throw err
|
||||
return { result: null }
|
||||
})
|
||||
|
||||
@@ -899,7 +904,8 @@ export class SASViyaApiClient {
|
||||
|
||||
const { result: folder } = await this.requestClient
|
||||
.get<Folder>(`${this.serverUrl}${url}`, accessToken)
|
||||
.catch(() => {
|
||||
.catch((err) => {
|
||||
if (err instanceof CertificateError) throw err
|
||||
return { result: null }
|
||||
})
|
||||
|
||||
|
||||
13
src/SASjs.ts
13
src/SASjs.ts
@@ -77,7 +77,7 @@ export default class SASjs {
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes code against a SAS 9 server. Requires a runner to be present in
|
||||
* Executes SAS code on a SAS 9 server. Requires a runner to be present in
|
||||
* the users home directory in metadata.
|
||||
* @param linesOfCode - lines of sas code from the file to run.
|
||||
* @param username - a string representing the username.
|
||||
@@ -97,6 +97,17 @@ export default class SASjs {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes SAS code on a SASJS server
|
||||
* @param code - a string of code from the file to run.
|
||||
* @param authConfig - (optional) a valid client, secret, refresh and access tokens that are authorised to execute scripts.
|
||||
*/
|
||||
public async executeScriptSASjs(code: string, authConfig?: AuthConfig) {
|
||||
this.isMethodSupported('executeScriptSASJS', [ServerType.Sasjs])
|
||||
|
||||
return await this.sasJSApiClient?.executeScript(code, authConfig)
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes sas code in a SAS Viya compute session.
|
||||
* @param fileName - name of the file to run. It will be converted to path to the file being submitted for execution.
|
||||
|
||||
@@ -60,6 +60,39 @@ export class SASjsApiClient {
|
||||
return Promise.resolve(result)
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes code on a SASJS server.
|
||||
* @param code - a string of code to execute.
|
||||
* @param authConfig - an object for authentication.
|
||||
*/
|
||||
public async executeScript(code: string, authConfig?: AuthConfig) {
|
||||
let access_token = (authConfig || {}).access_token
|
||||
if (authConfig) {
|
||||
;({ access_token } = await getTokens(
|
||||
this.requestClient,
|
||||
authConfig,
|
||||
ServerType.Sasjs
|
||||
))
|
||||
}
|
||||
|
||||
let parsedSasjsServerLog = ''
|
||||
|
||||
await this.requestClient
|
||||
.post('SASjsApi/code/execute', { code }, access_token)
|
||||
.then((res: any) => {
|
||||
if (res.result?.log) {
|
||||
parsedSasjsServerLog = res.result.log
|
||||
.map((logLine: any) => logLine.line)
|
||||
.join('\n')
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
parsedSasjsServerLog = err
|
||||
})
|
||||
|
||||
return parsedSasjsServerLog
|
||||
}
|
||||
|
||||
/**
|
||||
* Exchanges the auth code for an access token for the given client.
|
||||
* @param clientId - the client ID to authenticate with.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { SasAuthResponse } from '@sasjs/utils/types'
|
||||
import { prefixMessage } from '@sasjs/utils/error'
|
||||
import { RequestClient } from '../request/RequestClient'
|
||||
import { CertificateError } from '../types/errors'
|
||||
|
||||
/**
|
||||
* Exchanges the auth code for an access token for the given client.
|
||||
@@ -36,6 +37,7 @@ export async function getAccessTokenForViya(
|
||||
.post(url, data, undefined, 'application/x-www-form-urlencoded', headers)
|
||||
.then((res) => res.result as SasAuthResponse)
|
||||
.catch((err) => {
|
||||
if (err instanceof CertificateError) throw err
|
||||
throw prefixMessage(err, 'Error while getting access token. ')
|
||||
})
|
||||
|
||||
|
||||
@@ -99,7 +99,20 @@ export class FileUploader extends BaseJobExecutor {
|
||||
? parseWeboutResponse(res.result, uploadUrl)
|
||||
: res.result
|
||||
break
|
||||
case ServerType.Sasjs:
|
||||
if (typeof res.result._webout === 'object') {
|
||||
jsonResponse = res.result._webout
|
||||
} else {
|
||||
const webout = parseWeboutResponse(
|
||||
res.result._webout,
|
||||
uploadUrl
|
||||
)
|
||||
jsonResponse = getValidJson(webout)
|
||||
}
|
||||
break
|
||||
}
|
||||
} else if (this.serverType === ServerType.Sasjs) {
|
||||
jsonResponse = getValidJson(res.result._webout)
|
||||
} else {
|
||||
jsonResponse =
|
||||
typeof res.result === 'string'
|
||||
|
||||
@@ -7,7 +7,8 @@ import {
|
||||
LoginRequiredError,
|
||||
NotFoundError,
|
||||
InternalServerError,
|
||||
JobExecutionError
|
||||
JobExecutionError,
|
||||
CertificateError
|
||||
} from '../types/errors'
|
||||
import { SASjsRequest } from '../types'
|
||||
import { parseWeboutResponse } from '../utils/parseWeboutResponse'
|
||||
@@ -131,6 +132,26 @@ export class RequestClient implements HttpClient {
|
||||
} else {
|
||||
sasWork = response.log
|
||||
}
|
||||
} else if (response?.result?.log) {
|
||||
//In this scenario we know we got the response from SASJS server
|
||||
//Log is array of `{ line: '' }` so we need to convert it back to text
|
||||
//To be able to parse it with current functions.
|
||||
let log: string = ''
|
||||
|
||||
if (typeof log !== 'string') {
|
||||
log = response.result.log
|
||||
.map((logLine: any) => logLine.line)
|
||||
.join('\n')
|
||||
}
|
||||
|
||||
sourceCode = parseSourceCode(log)
|
||||
generatedCode = parseGeneratedCode(log)
|
||||
|
||||
if (response?.result?._webout) {
|
||||
sasWork = response.result._webout.WORK
|
||||
} else {
|
||||
sasWork = log
|
||||
}
|
||||
} else if (response?.result) {
|
||||
sourceCode = parseSourceCode(response.result)
|
||||
generatedCode = parseGeneratedCode(response.result)
|
||||
@@ -497,6 +518,10 @@ export class RequestClient implements HttpClient {
|
||||
else return
|
||||
}
|
||||
|
||||
if (e.isAxiosError && e.code === 'UNABLE_TO_VERIFY_LEAF_SIGNATURE') {
|
||||
throw new CertificateError(e.message)
|
||||
}
|
||||
|
||||
if (e.message) throw e
|
||||
else throw prefixMessage(e, 'Error while handling error. ')
|
||||
}
|
||||
|
||||
12
src/types/errors/CertificateError.ts
Normal file
12
src/types/errors/CertificateError.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
const instructionsToFix =
|
||||
'https://github.com/sasjs/cli/issues/1181#issuecomment-1090638584'
|
||||
|
||||
export class CertificateError extends Error {
|
||||
constructor(message: string) {
|
||||
super(
|
||||
`${message}\nPlease visit the link below for further information on this issue:\n- ${instructionsToFix}\n`
|
||||
)
|
||||
this.name = 'CertificateError'
|
||||
Object.setPrototypeOf(this, CertificateError.prototype)
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
export * from './AuthorizeError'
|
||||
export * from './CertificateError'
|
||||
export * from './ComputeJobExecutionError'
|
||||
export * from './ErrorResponse'
|
||||
export * from './InternalServerError'
|
||||
export * from './InvalidJsonError'
|
||||
export * from './JobExecutionError'
|
||||
export * from './JobStatePollError'
|
||||
export * from './LoginRequiredError'
|
||||
export * from './NotFoundError'
|
||||
export * from './ErrorResponse'
|
||||
export * from './NoSessionStateError'
|
||||
export * from './RootFolderNotFoundError'
|
||||
export * from './JsonParseArrayError'
|
||||
export * from './LoginRequiredError'
|
||||
export * from './NoSessionStateError'
|
||||
export * from './NotFoundError'
|
||||
export * from './RootFolderNotFoundError'
|
||||
export * from './WeboutResponseError'
|
||||
export * from './InvalidJsonError'
|
||||
|
||||
@@ -16,10 +16,15 @@ export const parseSasViyaDebugResponse = async (
|
||||
requestClient: RequestClient,
|
||||
serverUrl: string
|
||||
) => {
|
||||
// On viya 3.5, iframe is like <iframe style="width: 99%; height: 500px" src="..."></iframe>
|
||||
// On viya 4, iframe is like <iframe style="width: 99%; height: 500px; background-color:Canvas;" src=...></iframe>
|
||||
|
||||
const iframeStart = response.split(
|
||||
'<iframe style="width: 99%; height: 500px" src="'
|
||||
/<iframe style="width: 99%; height: 500px" src="|<iframe style="width: 99%; height: 500px; background-color:Canvas;" src=/
|
||||
)[1]
|
||||
const jsonUrl = iframeStart ? iframeStart.split('"></iframe>')[0] : null
|
||||
const jsonUrl = iframeStart
|
||||
? iframeStart.split(/"><\/iframe>|><\/iframe>/)[0]
|
||||
: null
|
||||
if (!jsonUrl) {
|
||||
throw new Error('Unable to find webout file URL.')
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user