mirror of
https://github.com/sasjs/adapter.git
synced 2026-04-10 07:53:14 +00:00
Compare commits
7 Commits
v2.10.5
...
debug-sasj
| Author | SHA1 | Date | |
|---|---|---|---|
| 2c6198ae25 | |||
| 73f50c0435 | |||
| 5ee57f3d07 | |||
| 146b0715bf | |||
| dfc1d567a5 | |||
| 779200f5fc | |||
| cf4c4cfca9 |
1
package-lock.json
generated
1
package-lock.json
generated
@@ -6351,7 +6351,6 @@
|
|||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"semver": "^7.3.5",
|
"semver": "^7.3.5",
|
||||||
"ssri": "^8.0.1",
|
"ssri": "^8.0.1",
|
||||||
"tar": "^6.1.0",
|
|
||||||
"treeverse": "^1.0.4",
|
"treeverse": "^1.0.4",
|
||||||
"walk-up-path": "^1.0.0"
|
"walk-up-path": "^1.0.0"
|
||||||
}
|
}
|
||||||
|
|||||||
8247
sasjs-tests/package-lock.json
generated
8247
sasjs-tests/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -23,7 +23,7 @@
|
|||||||
"test": "react-scripts test",
|
"test": "react-scripts test",
|
||||||
"eject": "react-scripts eject",
|
"eject": "react-scripts eject",
|
||||||
"update:adapter": "cd .. && npm run package:lib && cd sasjs-tests && npm i ../build/sasjs-adapter-5.0.0.tgz",
|
"update:adapter": "cd .. && npm run package:lib && cd sasjs-tests && npm i ../build/sasjs-adapter-5.0.0.tgz",
|
||||||
"deploy:tests": "rsync -avhe ssh ./build/* --delete $SSH_ACCOUNT:$DEPLOY_PATH || npm run deploy:tests-win",
|
"deploy:tests": "rsync -avhe ssh ./build/* --delete sabhas@sas.analytium.co.uk:/var/www/html/sabhas/sasjs-test || npm run deploy:tests-win",
|
||||||
"deploy:tests-win": "scp %DEPLOY_PATH% ./build/*",
|
"deploy:tests-win": "scp %DEPLOY_PATH% ./build/*",
|
||||||
"deploy": "npm run update:adapter && npm run build && npm run deploy:tests"
|
"deploy": "npm run update:adapter && npm run build && npm run deploy:tests"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"userName": "",
|
"userName": "",
|
||||||
"password": "",
|
"password": "",
|
||||||
"sasJsConfig": {
|
"sasJsConfig": {
|
||||||
"serverUrl": "",
|
"serverUrl": "https://sas.analytium.co.uk/",
|
||||||
"appLoc": "/Public/app",
|
"appLoc": "/Public/app",
|
||||||
"serverType": "SASVIYA",
|
"serverType": "SASVIYA",
|
||||||
"debug": false,
|
"debug": false,
|
||||||
|
|||||||
@@ -14,16 +14,16 @@ const App = (): ReactElement<{}> => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (adapter) {
|
if (adapter) {
|
||||||
const testSuites = [
|
const testSuites = [
|
||||||
basicTests(adapter, config.userName, config.password),
|
// basicTests(adapter, config.userName, config.password),
|
||||||
sendArrTests(adapter),
|
// sendArrTests(adapter),
|
||||||
sendObjTests(adapter),
|
// sendObjTests(adapter),
|
||||||
specialCaseTests(adapter),
|
// specialCaseTests(adapter),
|
||||||
sasjsRequestTests(adapter)
|
sasjsRequestTests(adapter)
|
||||||
]
|
]
|
||||||
|
|
||||||
if (adapter.getSasjsConfig().serverType === 'SASVIYA') {
|
// if (adapter.getSasjsConfig().serverType === 'SASVIYA') {
|
||||||
testSuites.push(computeTests(adapter))
|
// testSuites.push(computeTests(adapter))
|
||||||
}
|
// }
|
||||||
|
|
||||||
setTestSuites(testSuites)
|
setTestSuites(testSuites)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,19 +41,6 @@ export const basicTests = (
|
|||||||
assertion: (response: any) =>
|
assertion: (response: any) =>
|
||||||
response && response.isLoggedIn && response.userName === userName
|
response && response.isLoggedIn && response.userName === userName
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: 'Fetch username for already logged in user',
|
|
||||||
description: 'Should log the user in',
|
|
||||||
test: async () => {
|
|
||||||
await adapter.logIn(userName, password)
|
|
||||||
|
|
||||||
const newAdapterIns = new SASjs(adapter.getSasjsConfig())
|
|
||||||
|
|
||||||
return await newAdapterIns.checkSession()
|
|
||||||
},
|
|
||||||
assertion: (response: any) =>
|
|
||||||
response?.isLoggedIn && response?.userName === userName
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: 'Multiple Log in attempts',
|
title: 'Multiple Log in attempts',
|
||||||
description:
|
description:
|
||||||
@@ -61,7 +48,7 @@ export const basicTests = (
|
|||||||
test: async () => {
|
test: async () => {
|
||||||
await adapter.logOut()
|
await adapter.logOut()
|
||||||
await adapter.logIn('invalid', 'invalid')
|
await adapter.logIn('invalid', 'invalid')
|
||||||
return await adapter.logIn(userName, password)
|
return adapter.logIn(userName, password)
|
||||||
},
|
},
|
||||||
assertion: (response: any) =>
|
assertion: (response: any) =>
|
||||||
response && response.isLoggedIn && response.userName === userName
|
response && response.isLoggedIn && response.userName === userName
|
||||||
@@ -164,7 +151,7 @@ export const basicTests = (
|
|||||||
description:
|
description:
|
||||||
'Should complete successful request with extra attributes present in response',
|
'Should complete successful request with extra attributes present in response',
|
||||||
test: async () => {
|
test: async () => {
|
||||||
const config: Partial<SASjsConfig> = {
|
const config = {
|
||||||
useComputeApi: false
|
useComputeApi: false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -64,7 +64,16 @@ export class FileUploader {
|
|||||||
// currently only web approach is supported for file upload
|
// currently only web approach is supported for file upload
|
||||||
// therefore log is part of response with debug enabled and must be parsed
|
// therefore log is part of response with debug enabled and must be parsed
|
||||||
return this.requestClient
|
return this.requestClient
|
||||||
.post(uploadUrl, formData, undefined, 'application/json', headers)
|
.post(
|
||||||
|
uploadUrl,
|
||||||
|
formData,
|
||||||
|
undefined,
|
||||||
|
'application/json',
|
||||||
|
headers,
|
||||||
|
this.sasjsConfig.debug,
|
||||||
|
true,
|
||||||
|
sasJob
|
||||||
|
)
|
||||||
.then(async (res) => {
|
.then(async (res) => {
|
||||||
if (
|
if (
|
||||||
this.sasjsConfig.serverType === ServerType.SasViya &&
|
this.sasjsConfig.serverType === ServerType.SasViya &&
|
||||||
|
|||||||
@@ -782,12 +782,24 @@ export class SASViyaApiClient {
|
|||||||
jobResult = await this.requestClient.get<any>(
|
jobResult = await this.requestClient.get<any>(
|
||||||
`${this.serverUrl}${resultLink}/content`,
|
`${this.serverUrl}${resultLink}/content`,
|
||||||
access_token,
|
access_token,
|
||||||
'text/plain'
|
'text/plain',
|
||||||
|
{},
|
||||||
|
debug,
|
||||||
|
true,
|
||||||
|
sasJob
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (debug && logLink) {
|
if (debug && logLink) {
|
||||||
log = await this.requestClient
|
log = await this.requestClient
|
||||||
.get<any>(`${this.serverUrl}${logLink.href}/content`, access_token)
|
.get<any>(
|
||||||
|
`${this.serverUrl}${logLink.href}/content`,
|
||||||
|
access_token,
|
||||||
|
'application/json',
|
||||||
|
{},
|
||||||
|
debug,
|
||||||
|
true,
|
||||||
|
sasJob
|
||||||
|
)
|
||||||
.then((res: any) => res.result.items.map((i: any) => i.line).join('\n'))
|
.then((res: any) => res.result.items.map((i: any) => i.line).join('\n'))
|
||||||
}
|
}
|
||||||
if (jobStatus === 'failed') {
|
if (jobStatus === 'failed') {
|
||||||
|
|||||||
18
src/SASjs.ts
18
src/SASjs.ts
@@ -50,6 +50,7 @@ export default class SASjs {
|
|||||||
private sas9JobExecutor: JobExecutor | null = null
|
private sas9JobExecutor: JobExecutor | null = null
|
||||||
|
|
||||||
constructor(config?: any) {
|
constructor(config?: any) {
|
||||||
|
console.log('from SASjs constructor')
|
||||||
this.sasjsConfig = {
|
this.sasjsConfig = {
|
||||||
...defaultConfig,
|
...defaultConfig,
|
||||||
...config
|
...config
|
||||||
@@ -611,6 +612,7 @@ export default class SASjs {
|
|||||||
config.useComputeApi !== null
|
config.useComputeApi !== null
|
||||||
) {
|
) {
|
||||||
if (config.useComputeApi) {
|
if (config.useComputeApi) {
|
||||||
|
console.log(615)
|
||||||
return await this.computeJobExecutor!.execute(
|
return await this.computeJobExecutor!.execute(
|
||||||
sasJob,
|
sasJob,
|
||||||
data,
|
data,
|
||||||
@@ -878,20 +880,20 @@ export default class SASjs {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* this method returns an array of SASjsRequest
|
||||||
|
* @returns SASjsRequest[]
|
||||||
|
*/
|
||||||
public getSasRequests() {
|
public getSasRequests() {
|
||||||
const requests = [
|
console.log('from getSASRequests')
|
||||||
...this.webJobExecutor!.getRequests(),
|
const requests = this.requestClient!.getRequests()
|
||||||
...this.computeJobExecutor!.getRequests(),
|
|
||||||
...this.jesJobExecutor!.getRequests()
|
|
||||||
]
|
|
||||||
const sortedRequests = requests.sort(compareTimestamps)
|
const sortedRequests = requests.sort(compareTimestamps)
|
||||||
|
console.log('sortedRequests', sortedRequests)
|
||||||
return sortedRequests
|
return sortedRequests
|
||||||
}
|
}
|
||||||
|
|
||||||
public clearSasRequests() {
|
public clearSasRequests() {
|
||||||
this.webJobExecutor!.clearRequests()
|
this.requestClient!.clearRequests()
|
||||||
this.computeJobExecutor!.clearRequests()
|
|
||||||
this.jesJobExecutor!.clearRequests()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private setupConfiguration() {
|
private setupConfiguration() {
|
||||||
|
|||||||
@@ -239,7 +239,15 @@ export async function executeScript(
|
|||||||
const resultLink = `/compute/sessions/${executionSessionId}/filerefs/_webout/content`
|
const resultLink = `/compute/sessions/${executionSessionId}/filerefs/_webout/content`
|
||||||
|
|
||||||
jobResult = await requestClient
|
jobResult = await requestClient
|
||||||
.get<any>(resultLink, access_token, 'text/plain')
|
.get<any>(
|
||||||
|
resultLink,
|
||||||
|
access_token,
|
||||||
|
'text/plain',
|
||||||
|
{},
|
||||||
|
debug,
|
||||||
|
true,
|
||||||
|
jobPath
|
||||||
|
)
|
||||||
.catch(async (e) => {
|
.catch(async (e) => {
|
||||||
if (e instanceof NotFoundError) {
|
if (e instanceof NotFoundError) {
|
||||||
if (logLink) {
|
if (logLink) {
|
||||||
|
|||||||
@@ -23,61 +23,43 @@ export class AuthManager {
|
|||||||
* Logs into the SAS server with the supplied credentials.
|
* Logs into the SAS server with the supplied credentials.
|
||||||
* @param username - a string representing the username.
|
* @param username - a string representing the username.
|
||||||
* @param password - a string representing the password.
|
* @param password - a string representing the password.
|
||||||
* @returns - a boolean `isLoggedin` and a string `username`
|
|
||||||
*/
|
*/
|
||||||
public async logIn(
|
public async logIn(username: string, password: string) {
|
||||||
username: string,
|
const loginParams: any = {
|
||||||
password: string
|
|
||||||
): Promise<{
|
|
||||||
isLoggedIn: boolean
|
|
||||||
userName: string
|
|
||||||
}> {
|
|
||||||
const loginParams = {
|
|
||||||
_service: 'default',
|
_service: 'default',
|
||||||
username,
|
username,
|
||||||
password
|
password
|
||||||
}
|
}
|
||||||
|
|
||||||
let {
|
this.userName = loginParams.username
|
||||||
isLoggedIn: isLoggedInAlready,
|
|
||||||
loginForm,
|
|
||||||
userName: currentSessionUsername
|
|
||||||
} = await this.checkSession()
|
|
||||||
|
|
||||||
if (isLoggedInAlready) {
|
const { isLoggedIn, loginForm } = await this.checkSession()
|
||||||
if (currentSessionUsername === loginParams.username) {
|
|
||||||
await this.loginCallback()
|
|
||||||
|
|
||||||
this.userName = currentSessionUsername!
|
if (isLoggedIn) {
|
||||||
return {
|
await this.loginCallback()
|
||||||
isLoggedIn: true,
|
|
||||||
userName: this.userName
|
return {
|
||||||
}
|
isLoggedIn,
|
||||||
} else {
|
userName: this.userName
|
||||||
this.logOut()
|
|
||||||
}
|
}
|
||||||
} else this.userName = ''
|
}
|
||||||
|
|
||||||
let loginResponse = await this.sendLoginRequest(loginForm, loginParams)
|
let loginResponse = await this.sendLoginRequest(loginForm, loginParams)
|
||||||
|
|
||||||
let isLoggedIn = isLogInSuccess(loginResponse)
|
let loggedIn = isLogInSuccess(loginResponse)
|
||||||
|
|
||||||
if (!isLoggedIn) {
|
if (!loggedIn) {
|
||||||
if (isCredentialsVerifyError(loginResponse)) {
|
if (isCredentialsVerifyError(loginResponse)) {
|
||||||
const newLoginForm = await this.getLoginForm(loginResponse)
|
const newLoginForm = await this.getLoginForm(loginResponse)
|
||||||
|
|
||||||
loginResponse = await this.sendLoginRequest(newLoginForm, loginParams)
|
loginResponse = await this.sendLoginRequest(newLoginForm, loginParams)
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await this.checkSession()
|
const currentSession = await this.checkSession()
|
||||||
isLoggedIn = res.isLoggedIn
|
loggedIn = currentSession.isLoggedIn
|
||||||
|
|
||||||
if (isLoggedIn) this.userName = res.userName!
|
|
||||||
} else {
|
|
||||||
this.userName = loginParams.username
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isLoggedIn) {
|
if (loggedIn) {
|
||||||
if (this.serverType === ServerType.Sas9) {
|
if (this.serverType === ServerType.Sas9) {
|
||||||
const casAuthenticationUrl = `${this.serverUrl}/SASStoredProcess/j_spring_cas_security_check`
|
const casAuthenticationUrl = `${this.serverUrl}/SASStoredProcess/j_spring_cas_security_check`
|
||||||
|
|
||||||
@@ -88,10 +70,10 @@ export class AuthManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.loginCallback()
|
this.loginCallback()
|
||||||
} else this.userName = ''
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isLoggedIn,
|
isLoggedIn: !!loggedIn,
|
||||||
userName: this.userName
|
userName: this.userName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -121,21 +103,14 @@ export class AuthManager {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether a session is active, or login is required.
|
* Checks whether a session is active, or login is required.
|
||||||
* @returns - a promise which resolves with an object containing three values
|
* @returns - a promise which resolves with an object containing two values - a boolean `isLoggedIn`, and a string `userName`.
|
||||||
* - a boolean `isLoggedIn`
|
|
||||||
* - a string `userName` and
|
|
||||||
* - a form `loginForm` if not loggedin.
|
|
||||||
*/
|
*/
|
||||||
public async checkSession(): Promise<{
|
public async checkSession() {
|
||||||
isLoggedIn: boolean
|
|
||||||
userName?: string
|
|
||||||
loginForm?: any
|
|
||||||
}> {
|
|
||||||
//For VIYA we will send request on API endpoint. Which is faster then pinging SASJobExecution.
|
//For VIYA we will send request on API endpoint. Which is faster then pinging SASJobExecution.
|
||||||
//For SAS9 we will send request on SASStoredProcess
|
//For SAS9 we will send request on SASStoredProcess
|
||||||
const url =
|
const url =
|
||||||
this.serverType === 'SASVIYA'
|
this.serverType === 'SASVIYA'
|
||||||
? `${this.serverUrl}/identities/users/@currentUser`
|
? `${this.serverUrl}/identities`
|
||||||
: `${this.serverUrl}/SASStoredProcess`
|
: `${this.serverUrl}/SASStoredProcess`
|
||||||
|
|
||||||
const { result: loginResponse } = await this.requestClient
|
const { result: loginResponse } = await this.requestClient
|
||||||
@@ -145,10 +120,6 @@ export class AuthManager {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const isLoggedIn = loginResponse !== 'authErr'
|
const isLoggedIn = loginResponse !== 'authErr'
|
||||||
const userName = isLoggedIn
|
|
||||||
? this.extractUserName(loginResponse)
|
|
||||||
: undefined
|
|
||||||
|
|
||||||
let loginForm = null
|
let loginForm = null
|
||||||
|
|
||||||
if (!isLoggedIn) {
|
if (!isLoggedIn) {
|
||||||
@@ -167,29 +138,11 @@ export class AuthManager {
|
|||||||
|
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
isLoggedIn,
|
isLoggedIn,
|
||||||
userName: userName?.toLowerCase(),
|
userName: this.userName,
|
||||||
loginForm
|
loginForm
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private extractUserName = (response: any): string => {
|
|
||||||
switch (this.serverType) {
|
|
||||||
case ServerType.SasViya:
|
|
||||||
return response?.id
|
|
||||||
|
|
||||||
case ServerType.Sas9:
|
|
||||||
const matched = response?.match(/"title":"Log Off [0-1a-zA-Z ]*"/)
|
|
||||||
const username = matched?.[0].slice(17, -1)
|
|
||||||
|
|
||||||
if (!username.includes(' ')) return username
|
|
||||||
|
|
||||||
return username
|
|
||||||
.split(' ')
|
|
||||||
.map((name: string) => name.slice(0, 3).toLowerCase())
|
|
||||||
.join('')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private getLoginForm(response: any) {
|
private getLoginForm(response: any) {
|
||||||
const pattern: RegExp = /<form.+action="(.*Logon[^"]*).*>/
|
const pattern: RegExp = /<form.+action="(.*Logon[^"]*).*>/
|
||||||
const matches = pattern.exec(response)
|
const matches = pattern.exec(response)
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ describe('AuthManager', () => {
|
|||||||
jest.spyOn(authManager, 'checkSession').mockImplementation(() =>
|
jest.spyOn(authManager, 'checkSession').mockImplementation(() =>
|
||||||
Promise.resolve({
|
Promise.resolve({
|
||||||
isLoggedIn: true,
|
isLoggedIn: true,
|
||||||
userName,
|
userName: 'test',
|
||||||
loginForm: 'test'
|
loginForm: 'test'
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -89,6 +89,7 @@ describe('AuthManager', () => {
|
|||||||
jest.spyOn(authManager, 'checkSession').mockImplementation(() =>
|
jest.spyOn(authManager, 'checkSession').mockImplementation(() =>
|
||||||
Promise.resolve({
|
Promise.resolve({
|
||||||
isLoggedIn: false,
|
isLoggedIn: false,
|
||||||
|
userName: 'test',
|
||||||
loginForm: { name: 'test' }
|
loginForm: { name: 'test' }
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -174,7 +175,7 @@ describe('AuthManager', () => {
|
|||||||
expect(response.isLoggedIn).toBeTruthy()
|
expect(response.isLoggedIn).toBeTruthy()
|
||||||
expect(mockedAxios.get).toHaveBeenNthCalledWith(
|
expect(mockedAxios.get).toHaveBeenNthCalledWith(
|
||||||
1,
|
1,
|
||||||
`http://test-server.com/identities/users/@currentUser`,
|
`http://test-server.com/identities`,
|
||||||
{
|
{
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
responseType: 'text',
|
responseType: 'text',
|
||||||
|
|||||||
@@ -35,14 +35,11 @@ export class ComputeJobExecutor extends BaseJobExecutor {
|
|||||||
expectWebout
|
expectWebout
|
||||||
)
|
)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
this.appendRequest(response, sasJob, config.debug)
|
console.log('then block of compute job executor')
|
||||||
|
|
||||||
resolve(response.result)
|
resolve(response.result)
|
||||||
})
|
})
|
||||||
.catch(async (e: Error) => {
|
.catch(async (e: Error) => {
|
||||||
if (e instanceof ComputeJobExecutionError) {
|
if (e instanceof ComputeJobExecutionError) {
|
||||||
this.appendRequest(e, sasJob, config.debug)
|
|
||||||
|
|
||||||
reject(new ErrorResponse(e?.message, e))
|
reject(new ErrorResponse(e?.message, e))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import {
|
|||||||
} from '../types/errors'
|
} from '../types/errors'
|
||||||
import { ExtraResponseAttributes } from '@sasjs/utils/types'
|
import { ExtraResponseAttributes } from '@sasjs/utils/types'
|
||||||
import { BaseJobExecutor } from './JobExecutor'
|
import { BaseJobExecutor } from './JobExecutor'
|
||||||
import { appendExtraResponseAttributes } from '../utils'
|
|
||||||
|
|
||||||
export class JesJobExecutor extends BaseJobExecutor {
|
export class JesJobExecutor extends BaseJobExecutor {
|
||||||
constructor(serverUrl: string, private sasViyaApiClient: SASViyaApiClient) {
|
constructor(serverUrl: string, private sasViyaApiClient: SASViyaApiClient) {
|
||||||
@@ -28,19 +27,26 @@ export class JesJobExecutor extends BaseJobExecutor {
|
|||||||
this.sasViyaApiClient
|
this.sasViyaApiClient
|
||||||
?.executeJob(sasJob, config.contextName, config.debug, data, authConfig)
|
?.executeJob(sasJob, config.contextName, config.debug, data, authConfig)
|
||||||
.then((response: any) => {
|
.then((response: any) => {
|
||||||
this.appendRequest(response, sasJob, config.debug)
|
let responseObject = {}
|
||||||
|
|
||||||
const responseObject = appendExtraResponseAttributes(
|
if (extraResponseAttributes && extraResponseAttributes.length > 0) {
|
||||||
response,
|
const extraAttributes = extraResponseAttributes.reduce(
|
||||||
extraResponseAttributes
|
(map: any, obj: any) => ((map[obj] = response[obj]), map),
|
||||||
)
|
{}
|
||||||
|
)
|
||||||
|
|
||||||
|
responseObject = {
|
||||||
|
result: response.result,
|
||||||
|
...extraAttributes
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
responseObject = response.result
|
||||||
|
}
|
||||||
|
|
||||||
resolve(responseObject)
|
resolve(responseObject)
|
||||||
})
|
})
|
||||||
.catch(async (e: Error) => {
|
.catch(async (e: Error) => {
|
||||||
if (e instanceof JobExecutionError) {
|
if (e instanceof JobExecutionError) {
|
||||||
this.appendRequest(e, sasJob, config.debug)
|
|
||||||
|
|
||||||
reject(new ErrorResponse(e?.message, e))
|
reject(new ErrorResponse(e?.message, e))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ export interface JobExecutor {
|
|||||||
extraResponseAttributes?: ExtraResponseAttributes[]
|
extraResponseAttributes?: ExtraResponseAttributes[]
|
||||||
) => Promise<any>
|
) => Promise<any>
|
||||||
resendWaitingRequests: () => Promise<void>
|
resendWaitingRequests: () => Promise<void>
|
||||||
getRequests: () => SASjsRequest[]
|
|
||||||
clearRequests: () => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class BaseJobExecutor implements JobExecutor {
|
export abstract class BaseJobExecutor implements JobExecutor {
|
||||||
@@ -46,54 +44,7 @@ export abstract class BaseJobExecutor implements JobExecutor {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
getRequests = () => this.requests
|
|
||||||
|
|
||||||
clearRequests = () => {
|
|
||||||
this.requests = []
|
|
||||||
}
|
|
||||||
|
|
||||||
protected appendWaitingRequest(request: ExecuteFunction) {
|
protected appendWaitingRequest(request: ExecuteFunction) {
|
||||||
this.waitingRequests.push(request)
|
this.waitingRequests.push(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected appendRequest(response: any, program: string, debug: boolean) {
|
|
||||||
let sourceCode = ''
|
|
||||||
let generatedCode = ''
|
|
||||||
let sasWork = null
|
|
||||||
|
|
||||||
if (debug) {
|
|
||||||
if (response?.log) {
|
|
||||||
sourceCode = parseSourceCode(response.log)
|
|
||||||
generatedCode = parseGeneratedCode(response.log)
|
|
||||||
|
|
||||||
if (response?.result) {
|
|
||||||
sasWork = response.result.WORK
|
|
||||||
} else {
|
|
||||||
sasWork = response.log
|
|
||||||
}
|
|
||||||
} else if (response?.result) {
|
|
||||||
sourceCode = parseSourceCode(response.result)
|
|
||||||
generatedCode = parseGeneratedCode(response.result)
|
|
||||||
sasWork = response.result.WORK
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const stringifiedResult =
|
|
||||||
typeof response?.result === 'string'
|
|
||||||
? response?.result
|
|
||||||
: JSON.stringify(response?.result, null, 2)
|
|
||||||
|
|
||||||
this.requests.push({
|
|
||||||
logFile: response?.log || stringifiedResult || response,
|
|
||||||
serviceLink: program,
|
|
||||||
timestamp: new Date(),
|
|
||||||
sourceCode,
|
|
||||||
generatedCode,
|
|
||||||
SASWORK: sasWork
|
|
||||||
})
|
|
||||||
|
|
||||||
if (this.requests.length > 20) {
|
|
||||||
this.requests.splice(0, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
import {
|
import { ServerType } from '@sasjs/utils/types'
|
||||||
AuthConfig,
|
|
||||||
ExtraResponseAttributes,
|
|
||||||
ServerType
|
|
||||||
} from '@sasjs/utils/types'
|
|
||||||
import {
|
import {
|
||||||
ErrorResponse,
|
ErrorResponse,
|
||||||
JobExecutionError,
|
JobExecutionError,
|
||||||
@@ -16,8 +12,7 @@ import { SASViyaApiClient } from '../SASViyaApiClient'
|
|||||||
import {
|
import {
|
||||||
isRelativePath,
|
isRelativePath,
|
||||||
getValidJson,
|
getValidJson,
|
||||||
parseSasViyaDebugResponse,
|
parseSasViyaDebugResponse
|
||||||
appendExtraResponseAttributes
|
|
||||||
} from '../utils'
|
} from '../utils'
|
||||||
import { BaseJobExecutor } from './JobExecutor'
|
import { BaseJobExecutor } from './JobExecutor'
|
||||||
import { parseWeboutResponse } from '../utils/parseWeboutResponse'
|
import { parseWeboutResponse } from '../utils/parseWeboutResponse'
|
||||||
@@ -42,9 +37,7 @@ export class WebJobExecutor extends BaseJobExecutor {
|
|||||||
sasJob: string,
|
sasJob: string,
|
||||||
data: any,
|
data: any,
|
||||||
config: any,
|
config: any,
|
||||||
loginRequiredCallback?: any,
|
loginRequiredCallback?: any
|
||||||
authConfig?: AuthConfig,
|
|
||||||
extraResponseAttributes: ExtraResponseAttributes[] = []
|
|
||||||
) {
|
) {
|
||||||
const loginCallback = loginRequiredCallback || (() => Promise.resolve())
|
const loginCallback = loginRequiredCallback || (() => Promise.resolve())
|
||||||
const program = isRelativePath(sasJob)
|
const program = isRelativePath(sasJob)
|
||||||
@@ -118,40 +111,38 @@ export class WebJobExecutor extends BaseJobExecutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const requestPromise = new Promise((resolve, reject) => {
|
const requestPromise = new Promise((resolve, reject) => {
|
||||||
this.requestClient!.post(apiUrl, formData, undefined)
|
this.requestClient!.post(
|
||||||
|
apiUrl,
|
||||||
|
formData,
|
||||||
|
undefined,
|
||||||
|
'application/json',
|
||||||
|
{},
|
||||||
|
config.debug,
|
||||||
|
true,
|
||||||
|
sasJob
|
||||||
|
)
|
||||||
.then(async (res: any) => {
|
.then(async (res: any) => {
|
||||||
let jsonResponse = res.result
|
if (this.serverType === ServerType.SasViya && config.debug) {
|
||||||
|
const jsonResponse = await parseSasViyaDebugResponse(
|
||||||
if (config.debug) {
|
res.result,
|
||||||
switch (this.serverType) {
|
this.requestClient,
|
||||||
case ServerType.SasViya:
|
this.serverUrl
|
||||||
jsonResponse = await parseSasViyaDebugResponse(
|
)
|
||||||
res.result,
|
resolve(jsonResponse)
|
||||||
this.requestClient,
|
|
||||||
this.serverUrl
|
|
||||||
)
|
|
||||||
break
|
|
||||||
case ServerType.Sas9:
|
|
||||||
jsonResponse =
|
|
||||||
typeof res.result === 'string'
|
|
||||||
? parseWeboutResponse(res.result, apiUrl)
|
|
||||||
: res.result
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (this.serverType === ServerType.Sas9 && config.debug) {
|
||||||
|
let jsonResponse = res.result
|
||||||
|
if (typeof res.result === 'string')
|
||||||
|
jsonResponse = parseWeboutResponse(res.result, apiUrl)
|
||||||
|
|
||||||
this.appendRequest(res, sasJob, config.debug)
|
getValidJson(jsonResponse)
|
||||||
|
resolve(res.result)
|
||||||
const responseObject = appendExtraResponseAttributes(
|
}
|
||||||
{ result: jsonResponse },
|
getValidJson(res.result as string)
|
||||||
extraResponseAttributes
|
resolve(res.result)
|
||||||
)
|
|
||||||
resolve(responseObject)
|
|
||||||
})
|
})
|
||||||
.catch(async (e: Error) => {
|
.catch(async (e: Error) => {
|
||||||
if (e instanceof JobExecutionError) {
|
if (e instanceof JobExecutionError) {
|
||||||
this.appendRequest(e, sasJob, config.debug)
|
|
||||||
|
|
||||||
reject(new ErrorResponse(e?.message, e))
|
reject(new ErrorResponse(e?.message, e))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,9 +154,7 @@ export class WebJobExecutor extends BaseJobExecutor {
|
|||||||
sasJob,
|
sasJob,
|
||||||
data,
|
data,
|
||||||
config,
|
config,
|
||||||
loginRequiredCallback,
|
loginRequiredCallback
|
||||||
authConfig,
|
|
||||||
extraResponseAttributes
|
|
||||||
).then(
|
).then(
|
||||||
(res: any) => {
|
(res: any) => {
|
||||||
resolve(res)
|
resolve(res)
|
||||||
|
|||||||
@@ -8,10 +8,11 @@ import {
|
|||||||
InternalServerError,
|
InternalServerError,
|
||||||
JobExecutionError
|
JobExecutionError
|
||||||
} from '../types/errors'
|
} from '../types/errors'
|
||||||
|
import { SASjsRequest } from '../types'
|
||||||
import { parseWeboutResponse } from '../utils/parseWeboutResponse'
|
import { parseWeboutResponse } from '../utils/parseWeboutResponse'
|
||||||
import { prefixMessage } from '@sasjs/utils/error'
|
import { prefixMessage } from '@sasjs/utils/error'
|
||||||
import { SAS9AuthError } from '../types/errors/SAS9AuthError'
|
import { SAS9AuthError } from '../types/errors/SAS9AuthError'
|
||||||
import { getValidJson } from '../utils'
|
import { parseGeneratedCode, parseSourceCode } from '../utils'
|
||||||
|
|
||||||
export interface HttpClient {
|
export interface HttpClient {
|
||||||
get<T>(
|
get<T>(
|
||||||
@@ -47,6 +48,8 @@ export interface HttpClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class RequestClient implements HttpClient {
|
export class RequestClient implements HttpClient {
|
||||||
|
private requests: SASjsRequest[] = []
|
||||||
|
|
||||||
protected csrfToken: CsrfToken = { headerName: '', value: '' }
|
protected csrfToken: CsrfToken = { headerName: '', value: '' }
|
||||||
protected fileUploadCsrfToken: CsrfToken | undefined
|
protected fileUploadCsrfToken: CsrfToken | undefined
|
||||||
protected httpClient: AxiosInstance
|
protected httpClient: AxiosInstance
|
||||||
@@ -83,12 +86,75 @@ export class RequestClient implements HttpClient {
|
|||||||
return this.httpClient.defaults.baseURL || ''
|
return this.httpClient.defaults.baseURL || ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* this method returns all requests, an array of SASjsRequest type
|
||||||
|
* @returns SASjsRequest[]
|
||||||
|
*/
|
||||||
|
public getRequests = () => this.requests
|
||||||
|
|
||||||
|
/**
|
||||||
|
* this method clears the requests array, i.e set to empty
|
||||||
|
*/
|
||||||
|
public clearRequests = () => {
|
||||||
|
this.requests = []
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* this method appends the response from sasjs request to requests array
|
||||||
|
* @param response - response from sasjs request
|
||||||
|
* @param program - name of program
|
||||||
|
* @param debug - a boolean that indicates whether debug was enabled or not
|
||||||
|
*/
|
||||||
|
public appendRequest = (response: any, program: string, debug: boolean) => {
|
||||||
|
console.log('from appendRequest')
|
||||||
|
let sourceCode = ''
|
||||||
|
let generatedCode = ''
|
||||||
|
let sasWork = null
|
||||||
|
|
||||||
|
if (debug) {
|
||||||
|
if (response?.log) {
|
||||||
|
sourceCode = parseSourceCode(response.log)
|
||||||
|
generatedCode = parseGeneratedCode(response.log)
|
||||||
|
|
||||||
|
if (response?.result) {
|
||||||
|
sasWork = response.result.WORK
|
||||||
|
} else {
|
||||||
|
sasWork = response.log
|
||||||
|
}
|
||||||
|
} else if (response?.result) {
|
||||||
|
sourceCode = parseSourceCode(response.result)
|
||||||
|
generatedCode = parseGeneratedCode(response.result)
|
||||||
|
sasWork = response.result.WORK
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const stringifiedResult =
|
||||||
|
typeof response?.result === 'string'
|
||||||
|
? response?.result
|
||||||
|
: JSON.stringify(response?.result, null, 2)
|
||||||
|
|
||||||
|
this.requests.push({
|
||||||
|
logFile: response?.log || stringifiedResult || response,
|
||||||
|
serviceLink: program,
|
||||||
|
timestamp: new Date(),
|
||||||
|
sourceCode,
|
||||||
|
generatedCode,
|
||||||
|
SASWORK: sasWork
|
||||||
|
})
|
||||||
|
|
||||||
|
if (this.requests.length > 20) {
|
||||||
|
this.requests.splice(0, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async get<T>(
|
public async get<T>(
|
||||||
url: string,
|
url: string,
|
||||||
accessToken: string | undefined,
|
accessToken: string | undefined,
|
||||||
contentType: string = 'application/json',
|
contentType: string = 'application/json',
|
||||||
overrideHeaders: { [key: string]: string | number } = {},
|
overrideHeaders: { [key: string]: string | number } = {},
|
||||||
debug: boolean = false
|
debug: boolean = false,
|
||||||
|
captureRequest: boolean = false,
|
||||||
|
sasJob: string = ''
|
||||||
): Promise<{ result: T; etag: string; status: number }> {
|
): Promise<{ result: T; etag: string; status: number }> {
|
||||||
const headers = {
|
const headers = {
|
||||||
...this.getHeaders(accessToken, contentType),
|
...this.getHeaders(accessToken, contentType),
|
||||||
@@ -108,8 +174,11 @@ export class RequestClient implements HttpClient {
|
|||||||
.get<T>(url, requestConfig)
|
.get<T>(url, requestConfig)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
throwIfError(response)
|
throwIfError(response)
|
||||||
|
const responseToReturn = this.parseResponse<T>(response)
|
||||||
return this.parseResponse<T>(response)
|
if (captureRequest) {
|
||||||
|
this.appendRequest(responseToReturn, sasJob, debug)
|
||||||
|
}
|
||||||
|
return responseToReturn
|
||||||
})
|
})
|
||||||
.catch(async (e) => {
|
.catch(async (e) => {
|
||||||
return await this.handleError(
|
return await this.handleError(
|
||||||
@@ -135,7 +204,10 @@ export class RequestClient implements HttpClient {
|
|||||||
data: any,
|
data: any,
|
||||||
accessToken: string | undefined,
|
accessToken: string | undefined,
|
||||||
contentType = 'application/json',
|
contentType = 'application/json',
|
||||||
overrideHeaders: { [key: string]: string | number } = {}
|
overrideHeaders: { [key: string]: string | number } = {},
|
||||||
|
debug: boolean = false,
|
||||||
|
captureRequest: boolean = false,
|
||||||
|
sasJob: string = ''
|
||||||
): Promise<{ result: T; etag: string }> {
|
): Promise<{ result: T; etag: string }> {
|
||||||
const headers = {
|
const headers = {
|
||||||
...this.getHeaders(accessToken, contentType),
|
...this.getHeaders(accessToken, contentType),
|
||||||
@@ -146,7 +218,11 @@ export class RequestClient implements HttpClient {
|
|||||||
.post<T>(url, data, { headers, withCredentials: true })
|
.post<T>(url, data, { headers, withCredentials: true })
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
throwIfError(response)
|
throwIfError(response)
|
||||||
return this.parseResponse<T>(response)
|
const responseToReturn = this.parseResponse<T>(response)
|
||||||
|
if (captureRequest) {
|
||||||
|
this.appendRequest(responseToReturn, sasJob, debug)
|
||||||
|
}
|
||||||
|
return responseToReturn
|
||||||
})
|
})
|
||||||
.catch(async (e) => {
|
.catch(async (e) => {
|
||||||
return await this.handleError(e, () =>
|
return await this.handleError(e, () =>
|
||||||
@@ -499,60 +575,46 @@ export const throwIfError = (response: AxiosResponse) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const parseError = (data: string) => {
|
const parseError = (data: string) => {
|
||||||
if (!data) return null
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const responseJson = JSON.parse(data?.replace(/[\n\r]/g, ' '))
|
const responseJson = JSON.parse(data?.replace(/[\n\r]/g, ' '))
|
||||||
if (responseJson.errorCode && responseJson.message) {
|
return responseJson.errorCode && responseJson.message
|
||||||
return new JobExecutionError(
|
? new JobExecutionError(
|
||||||
responseJson.errorCode,
|
responseJson.errorCode,
|
||||||
responseJson.message,
|
responseJson.message,
|
||||||
data?.replace(/[\n\r]/g, ' ')
|
data?.replace(/[\n\r]/g, ' ')
|
||||||
)
|
|
||||||
}
|
|
||||||
} catch (_) {}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const hasError = data?.includes('{"errorCode')
|
|
||||||
if (hasError) {
|
|
||||||
const parts = data.split('{"errorCode')
|
|
||||||
if (parts.length > 1) {
|
|
||||||
const error = '{"errorCode' + parts[1].split('"}')[0] + '"}'
|
|
||||||
const errorJson = JSON.parse(error.replace(/[\n\r]/g, ' '))
|
|
||||||
return new JobExecutionError(
|
|
||||||
errorJson.errorCode,
|
|
||||||
errorJson.message,
|
|
||||||
data?.replace(/[\n\r]/g, '\n')
|
|
||||||
)
|
)
|
||||||
|
: null
|
||||||
|
} catch (_) {
|
||||||
|
try {
|
||||||
|
const hasError = data?.includes('{"errorCode')
|
||||||
|
if (hasError) {
|
||||||
|
const parts = data.split('{"errorCode')
|
||||||
|
if (parts.length > 1) {
|
||||||
|
const error = '{"errorCode' + parts[1].split('"}')[0] + '"}'
|
||||||
|
const errorJson = JSON.parse(error.replace(/[\n\r]/g, ' '))
|
||||||
|
return new JobExecutionError(
|
||||||
|
errorJson.errorCode,
|
||||||
|
errorJson.message,
|
||||||
|
data?.replace(/[\n\r]/g, '\n')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
}
|
try {
|
||||||
} catch (_) {}
|
const hasError = !!data?.match(/stored process not found: /i)
|
||||||
|
if (hasError) {
|
||||||
try {
|
const parts = data.split(/stored process not found: /i)
|
||||||
const hasError = !!data?.match(/stored process not found: /i)
|
if (parts.length > 1) {
|
||||||
if (hasError) {
|
const storedProcessPath = parts[1].split('<i>')[1].split('</i>')[0]
|
||||||
const parts = data.split(/stored process not found: /i)
|
const message = `Stored process not found: ${storedProcessPath}`
|
||||||
if (parts.length > 1) {
|
return new JobExecutionError(404, message, '')
|
||||||
const storedProcessPath = parts[1].split('<i>')[1].split('</i>')[0]
|
}
|
||||||
const message = `Stored process not found: ${storedProcessPath}`
|
}
|
||||||
return new JobExecutionError(404, message, '')
|
} catch (_) {
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
|
} catch (_) {
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
} catch (_) {}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
const hasError =
|
|
||||||
!!data?.match(/Stored Process Error/i) &&
|
|
||||||
!!data?.match(/This request completed with errors./i)
|
|
||||||
if (hasError) {
|
|
||||||
const parts = data.split('<h2>SAS Log</h2>')
|
|
||||||
if (parts.length > 1) {
|
|
||||||
const log = parts[1].split('<pre>')[1].split('</pre>')[0]
|
|
||||||
const message = `This request completed with errors.`
|
|
||||||
return new JobExecutionError(404, message, log)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (_) {}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,8 @@ const prepareFilesAndParams = () => {
|
|||||||
describe('FileUploader', () => {
|
describe('FileUploader', () => {
|
||||||
const config: SASjsConfig = {
|
const config: SASjsConfig = {
|
||||||
...new SASjsConfig(),
|
...new SASjsConfig(),
|
||||||
appLoc: '/sample/apploc'
|
appLoc: '/sample/apploc',
|
||||||
|
debug: false
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileUploader = new FileUploader(
|
const fileUploader = new FileUploader(
|
||||||
|
|||||||
@@ -33,4 +33,18 @@ describe('jsonValidator', () => {
|
|||||||
}
|
}
|
||||||
expect(test).toThrow(JsonParseArrayError)
|
expect(test).toThrow(JsonParseArrayError)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should throw an error when null is passed', () => {
|
||||||
|
const test = () => {
|
||||||
|
getValidJson(null as any)
|
||||||
|
}
|
||||||
|
expect(test).toThrow(InvalidJsonError)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw an error when undefined is passed', () => {
|
||||||
|
const test = () => {
|
||||||
|
getValidJson(undefined as any)
|
||||||
|
}
|
||||||
|
expect(test).toThrow(InvalidJsonError)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
import { ExtraResponseAttributes } from '@sasjs/utils/types'
|
|
||||||
|
|
||||||
export async function appendExtraResponseAttributes(
|
|
||||||
response: any,
|
|
||||||
extraResponseAttributes: ExtraResponseAttributes[]
|
|
||||||
) {
|
|
||||||
let responseObject = {}
|
|
||||||
|
|
||||||
if (extraResponseAttributes?.length) {
|
|
||||||
const extraAttributes = extraResponseAttributes.reduce(
|
|
||||||
(map: any, obj: any) => ((map[obj] = response[obj]), map),
|
|
||||||
{}
|
|
||||||
)
|
|
||||||
|
|
||||||
responseObject = {
|
|
||||||
result: response.result,
|
|
||||||
...extraAttributes
|
|
||||||
}
|
|
||||||
} else responseObject = response.result
|
|
||||||
|
|
||||||
return responseObject
|
|
||||||
}
|
|
||||||
@@ -6,6 +6,8 @@ import { JsonParseArrayError, InvalidJsonError } from '../types/errors'
|
|||||||
*/
|
*/
|
||||||
export const getValidJson = (str: string | object) => {
|
export const getValidJson = (str: string | object) => {
|
||||||
try {
|
try {
|
||||||
|
if (str === null || str === undefined) throw new InvalidJsonError()
|
||||||
|
|
||||||
if (Array.isArray(str)) throw new JsonParseArrayError()
|
if (Array.isArray(str)) throw new JsonParseArrayError()
|
||||||
|
|
||||||
if (typeof str === 'object') return str
|
if (typeof str === 'object') return str
|
||||||
|
|||||||
@@ -15,4 +15,3 @@ export * from './parseWeboutResponse'
|
|||||||
export * from './fetchLogByChunks'
|
export * from './fetchLogByChunks'
|
||||||
export * from './getValidJson'
|
export * from './getValidJson'
|
||||||
export * from './parseViyaDebugResponse'
|
export * from './parseViyaDebugResponse'
|
||||||
export * from './appendExtraResponseAttributes'
|
|
||||||
|
|||||||
Reference in New Issue
Block a user