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

Compare commits

..

25 Commits

Author SHA1 Message Date
Allan Bowe
4ebf949912 Merge pull request #699 from sasjs/special-missing
Special missing function from @sasjs/utils
2022-04-26 13:54:11 +03:00
c00c8007e5 chore: utils update 2022-04-26 12:27:55 +02:00
54516665bf chore: string escaping 2022-04-22 11:54:59 +02:00
ecec2e77c0 chore: error improved 2022-04-21 15:45:59 +02:00
Allan Bowe
102898ac33 Merge pull request #700 from sasjs/parse-log-in-executeScript
fix: parse log in executeScript method on sasjs server
2022-04-18 21:31:31 +03:00
7370a2be4c fix: can not read property map of undefined 2022-04-18 23:28:12 +05:00
135d019026 chore: update tsdoc for executeScript method 2022-04-18 22:56:51 +05:00
e2651344d7 fix: parse log in executeScript method on sasjs server 2022-04-18 22:50:27 +05:00
9bf3885868 chore: test fix 2022-04-18 14:54:50 +02:00
caa5aa47dc fix: isSpecialMissing from utils 2022-04-18 14:49:57 +02:00
Allan Bowe
7a42bc1b88 Merge pull request #698 from sasjs/executeScriptSASjs
feat: add method for executing scripts on sasjs server
2022-04-13 21:49:46 +03:00
Allan Bowe
6c02ee4cd6 Update SASjs.ts 2022-04-13 19:49:16 +01:00
73ee214b61 feat: add method for executing scripts on sasjs server 2022-04-13 18:22:26 +05:00
Muhammad Saad
77487bfa35 Merge pull request #696 from sasjs/certificate-error
fix(error): throw Certificate error wherever possible
2022-04-08 14:32:02 -07:00
Saad Jutt
9cf0165cf7 chore(error): removed extra prefix of Error: 2022-04-08 14:13:41 +05:00
Saad Jutt
e4d4b3142f chore: updated error message 2022-04-08 00:01:15 +05:00
Saad Jutt
a87be39b44 fix(error): throw Certificate error wherever possible 2022-04-07 23:57:44 +05:00
Allan Bowe
8ea621ac98 Merge pull request #687 from sasjs/issue-686
fix: file upload on SASJS SERVER
2022-04-04 12:37:29 +03:00
ea61119919 chore(git): Merge branch 'master' into issue-686 2022-03-31 15:09:21 +02:00
Allan Bowe
01235616a0 Merge pull request #690 from sasjs/issue-689
fix: update code for extracting JSON URL on viya4 when debug is enabled
2022-03-31 15:54:20 +03:00
6cab245cde fix: update code for extracting json url when debug is enabled 2022-03-30 01:22:03 +05:00
ed90cd8036 chore: uploadFile test added 2022-03-28 17:32:30 +02:00
Allan Bowe
784bab4522 fix: adding README for new sendMacVars service 2022-03-28 12:30:01 +00:00
b0eb8b07a8 style: lint 2022-03-25 18:11:20 +01:00
1d1ef7179e fix: file upload on SASJS SERVER
Also fixed appendRequest when debug on while on SASJS SERVER
2022-03-25 18:05:47 +01:00
18 changed files with 202 additions and 39 deletions

14
package-lock.json generated
View File

@@ -8,7 +8,7 @@
"hasInstallScript": true,
"license": "ISC",
"dependencies": {
"@sasjs/utils": "2.42.0",
"@sasjs/utils": "2.44.0",
"axios": "0.26.0",
"axios-cookiejar-support": "1.0.1",
"form-data": "4.0.0",
@@ -1142,9 +1142,9 @@
}
},
"node_modules/@sasjs/utils": {
"version": "2.42.0",
"resolved": "https://registry.npmjs.org/@sasjs/utils/-/utils-2.42.0.tgz",
"integrity": "sha512-Y69l89PYNF/h9xvVH72om/39xA+cY80bhiVLzp/fJb3BlvzCf4RNswBBanUOv2I5JNa7gPpJuE7mEiXOlhD3eg==",
"version": "2.44.0",
"resolved": "https://registry.npmjs.org/@sasjs/utils/-/utils-2.44.0.tgz",
"integrity": "sha512-hpC4erHYA8Mcb38mzxFEP0cXehfa0iKeqSW2d9MmxZ9g2qpy0BU6xyZJohN9kOiafXo5H359ndNKsg4DOq5YgA==",
"hasInstallScript": true,
"dependencies": {
"@types/fs-extra": "9.0.13",
@@ -13870,9 +13870,9 @@
}
},
"@sasjs/utils": {
"version": "2.42.0",
"resolved": "https://registry.npmjs.org/@sasjs/utils/-/utils-2.42.0.tgz",
"integrity": "sha512-Y69l89PYNF/h9xvVH72om/39xA+cY80bhiVLzp/fJb3BlvzCf4RNswBBanUOv2I5JNa7gPpJuE7mEiXOlhD3eg==",
"version": "2.44.0",
"resolved": "https://registry.npmjs.org/@sasjs/utils/-/utils-2.44.0.tgz",
"integrity": "sha512-hpC4erHYA8Mcb38mzxFEP0cXehfa0iKeqSW2d9MmxZ9g2qpy0BU6xyZJohN9kOiafXo5H359ndNKsg4DOq5YgA==",
"requires": {
"@types/fs-extra": "9.0.13",
"@types/prompts": "2.0.13",

View File

@@ -72,7 +72,7 @@
},
"main": "index.js",
"dependencies": {
"@sasjs/utils": "2.42.0",
"@sasjs/utils": "2.44.0",
"axios": "0.26.0",
"axios-cookiejar-support": "1.0.1",
"form-data": "4.0.0",

View File

@@ -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,

View File

@@ -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",

View File

@@ -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') {

View 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
}
]
})

View File

@@ -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) =>

View File

@@ -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 }
})

View File

@@ -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.

View File

@@ -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.

View File

@@ -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. ')
})

View File

@@ -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'

View File

@@ -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. ')
}

View File

@@ -62,7 +62,7 @@ describe('formatDataForRequest', () => {
expect(() => formatDataForRequest(tableWithMissingValues)).toThrow(
new Error(
'Special missing value can only be a single character from A to Z or _'
`A Special missing value can only be a single character from 'A' to 'Z', '_', '.[a-z]', '._'`
)
)
})

View 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)
}
}

View File

@@ -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'

View File

@@ -1,3 +1,5 @@
import { isSpecialMissing } from '@sasjs/utils'
/**
* Converts the given JSON object array to a CSV string.
* @param data - the array of JSON objects to convert.
@@ -18,7 +20,6 @@ export const convertToCSV = (
let headers: string[] = []
let csvTest
let invalidString = false
const specialMissingValueRegExp = /^[a-z_]{1}$/i
if (formats) {
headers = Object.keys(formats).map((key) => `${key}:${formats![key]}`)
@@ -36,7 +37,7 @@ export const convertToCSV = (
hasNullOrNumber = true
} else if (
typeof row[field] === 'string' &&
specialMissingValueRegExp.test(row[field])
isSpecialMissing(row[field])
) {
hasSpecialMissingString = true
}
@@ -130,10 +131,10 @@ export const convertToCSV = (
value = currentCell === null ? '' : currentCell
if (formats && formats[fieldName] === 'best.') {
if (value && !specialMissingValueRegExp.test(value)) {
if (value && !isSpecialMissing(value)) {
console.log(`🤖[value]🤖`, value)
throw new Error(
'Special missing value can only be a single character from A to Z or _'
`A Special missing value can only be a single character from 'A' to 'Z', '_', '.[a-z]', '._'`
)
}

View File

@@ -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.')
}