mirror of
https://github.com/sasjs/adapter.git
synced 2026-01-03 18:50:05 +00:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e0140a23c2 | ||
|
|
ff6698a9d1 | ||
|
|
843d498b72 | ||
|
|
f679b17cbe | ||
|
|
36a0f0e743 | ||
|
|
ad563b9bc8 | ||
|
|
893cce7f21 | ||
|
|
bf7e8fd0e6 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -3,4 +3,6 @@ build
|
|||||||
|
|
||||||
.env
|
.env
|
||||||
|
|
||||||
/coverage
|
/coverage
|
||||||
|
|
||||||
|
.DS_Store
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -21,6 +21,7 @@ import { isAuthorizeFormRequired } from './auth/isAuthorizeFormRequired'
|
|||||||
import { RequestClient } from './request/RequestClient'
|
import { RequestClient } from './request/RequestClient'
|
||||||
import { NotFoundError } from './types/NotFoundError'
|
import { NotFoundError } from './types/NotFoundError'
|
||||||
import { SasAuthResponse } from '@sasjs/utils/types'
|
import { SasAuthResponse } from '@sasjs/utils/types'
|
||||||
|
import { prefixMessage } from '@sasjs/utils/error'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A client for interfacing with the SAS Viya REST API.
|
* A client for interfacing with the SAS Viya REST API.
|
||||||
@@ -280,25 +281,24 @@ export class SASViyaApiClient {
|
|||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (accessToken) {
|
if (accessToken) headers.Authorization = `Bearer ${accessToken}`
|
||||||
headers.Authorization = `Bearer ${accessToken}`
|
|
||||||
}
|
|
||||||
|
|
||||||
let executionSessionId: string
|
let executionSessionId: string
|
||||||
|
|
||||||
const session = await this.sessionManager
|
const session = await this.sessionManager
|
||||||
.getSession(accessToken)
|
.getSession(accessToken)
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
throw err
|
throw prefixMessage(err, 'Error while getting session. ')
|
||||||
})
|
})
|
||||||
|
|
||||||
executionSessionId = session!.id
|
executionSessionId = session!.id
|
||||||
|
|
||||||
if (printPid) {
|
if (printPid) {
|
||||||
const { result: jobIdVariable } = await this.sessionManager.getVariable(
|
const { result: jobIdVariable } = await this.sessionManager
|
||||||
executionSessionId,
|
.getVariable(executionSessionId, 'SYSJOBID', accessToken)
|
||||||
'SYSJOBID',
|
.catch((err) => {
|
||||||
accessToken
|
throw prefixMessage(err, 'Error while getting session variable. ')
|
||||||
)
|
})
|
||||||
|
|
||||||
if (jobIdVariable && jobIdVariable.value) {
|
if (jobIdVariable && jobIdVariable.value) {
|
||||||
const relativeJobPath = this.rootFolderName
|
const relativeJobPath = this.rootFolderName
|
||||||
@@ -331,6 +331,7 @@ export class SASViyaApiClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let fileName
|
let fileName
|
||||||
|
|
||||||
if (isRelativePath(jobPath)) {
|
if (isRelativePath(jobPath)) {
|
||||||
fileName = `exec-${
|
fileName = `exec-${
|
||||||
jobPath.includes('/') ? jobPath.split('/')[1] : jobPath
|
jobPath.includes('/') ? jobPath.split('/')[1] : jobPath
|
||||||
@@ -352,7 +353,7 @@ export class SASViyaApiClient {
|
|||||||
if (data) {
|
if (data) {
|
||||||
if (JSON.stringify(data).includes(';')) {
|
if (JSON.stringify(data).includes(';')) {
|
||||||
files = await this.uploadTables(data, accessToken).catch((err) => {
|
files = await this.uploadTables(data, accessToken).catch((err) => {
|
||||||
throw err
|
throw prefixMessage(err, 'Error while uploading tables. ')
|
||||||
})
|
})
|
||||||
|
|
||||||
jobVariables['_webin_file_count'] = files.length
|
jobVariables['_webin_file_count'] = files.length
|
||||||
@@ -376,19 +377,18 @@ export class SASViyaApiClient {
|
|||||||
variables: jobVariables,
|
variables: jobVariables,
|
||||||
arguments: jobArguments
|
arguments: jobArguments
|
||||||
}
|
}
|
||||||
|
|
||||||
const { result: postedJob, etag } = await this.requestClient
|
const { result: postedJob, etag } = await this.requestClient
|
||||||
.post<Job>(
|
.post<Job>(
|
||||||
`/compute/sessions/${executionSessionId}/jobs`,
|
`/compute/sessions/${executionSessionId}/jobs`,
|
||||||
jobRequestBody,
|
jobRequestBody,
|
||||||
accessToken
|
accessToken
|
||||||
)
|
)
|
||||||
.catch((err: any) => {
|
.catch((err) => {
|
||||||
throw err
|
throw prefixMessage(err, 'Error while posting job. ')
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!waitForResult) {
|
if (!waitForResult) return session
|
||||||
return session
|
|
||||||
}
|
|
||||||
|
|
||||||
if (debug) {
|
if (debug) {
|
||||||
console.log(`Job has been submitted for '${fileName}'.`)
|
console.log(`Job has been submitted for '${fileName}'.`)
|
||||||
@@ -404,7 +404,9 @@ export class SASViyaApiClient {
|
|||||||
etag,
|
etag,
|
||||||
accessToken,
|
accessToken,
|
||||||
pollOptions
|
pollOptions
|
||||||
)
|
).catch((err) => {
|
||||||
|
throw prefixMessage(err, 'Error while polling job status. ')
|
||||||
|
})
|
||||||
|
|
||||||
const { result: currentJob } = await this.requestClient
|
const { result: currentJob } = await this.requestClient
|
||||||
.get<Job>(
|
.get<Job>(
|
||||||
@@ -412,7 +414,7 @@ export class SASViyaApiClient {
|
|||||||
accessToken
|
accessToken
|
||||||
)
|
)
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
throw err
|
throw prefixMessage(err, 'Error while getting job. ')
|
||||||
})
|
})
|
||||||
|
|
||||||
let jobResult
|
let jobResult
|
||||||
@@ -427,7 +429,7 @@ export class SASViyaApiClient {
|
|||||||
res.result.items.map((i: any) => i.line).join('\n')
|
res.result.items.map((i: any) => i.line).join('\n')
|
||||||
)
|
)
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
throw err
|
throw prefixMessage(err, 'Error while getting log. ')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -455,7 +457,7 @@ export class SASViyaApiClient {
|
|||||||
res.result.items.map((i: any) => i.line).join('\n')
|
res.result.items.map((i: any) => i.line).join('\n')
|
||||||
)
|
)
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
throw err
|
throw prefixMessage(err, 'Error while getting log. ')
|
||||||
})
|
})
|
||||||
|
|
||||||
return Promise.reject({
|
return Promise.reject({
|
||||||
@@ -464,6 +466,7 @@ export class SASViyaApiClient {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
result: JSON.stringify(e)
|
result: JSON.stringify(e)
|
||||||
}
|
}
|
||||||
@@ -473,7 +476,7 @@ export class SASViyaApiClient {
|
|||||||
await this.sessionManager
|
await this.sessionManager
|
||||||
.clearSession(executionSessionId, accessToken)
|
.clearSession(executionSessionId, accessToken)
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
throw err
|
throw prefixMessage(err, 'Error while clearing session. ')
|
||||||
})
|
})
|
||||||
|
|
||||||
return { result: jobResult?.result, log }
|
return { result: jobResult?.result, log }
|
||||||
@@ -490,7 +493,7 @@ export class SASViyaApiClient {
|
|||||||
true
|
true
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
throw e
|
throw prefixMessage(e, 'Error while executing script. ')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -814,9 +817,12 @@ export class SASViyaApiClient {
|
|||||||
? `${this.rootFolderName}/${folderPath}`
|
? `${this.rootFolderName}/${folderPath}`
|
||||||
: folderPath
|
: folderPath
|
||||||
|
|
||||||
await this.populateFolderMap(fullFolderPath, accessToken)
|
await this.populateFolderMap(fullFolderPath, accessToken).catch((err) => {
|
||||||
|
throw prefixMessage(err, 'Error while populating folder map. ')
|
||||||
|
})
|
||||||
|
|
||||||
const jobFolder = this.folderMap.get(fullFolderPath)
|
const jobFolder = this.folderMap.get(fullFolderPath)
|
||||||
|
|
||||||
if (!jobFolder) {
|
if (!jobFolder) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`The folder '${fullFolderPath}' was not found on '${this.serverUrl}'`
|
`The folder '${fullFolderPath}' was not found on '${this.serverUrl}'`
|
||||||
@@ -824,6 +830,7 @@ export class SASViyaApiClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const headers: any = { 'Content-Type': 'application/json' }
|
const headers: any = { 'Content-Type': 'application/json' }
|
||||||
|
|
||||||
if (!!accessToken) {
|
if (!!accessToken) {
|
||||||
headers.Authorization = `Bearer ${accessToken}`
|
headers.Authorization = `Bearer ${accessToken}`
|
||||||
}
|
}
|
||||||
@@ -847,10 +854,14 @@ export class SASViyaApiClient {
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
result: jobDefinition
|
result: jobDefinition
|
||||||
} = await this.requestClient.get<JobDefinition>(
|
} = await this.requestClient
|
||||||
`${this.serverUrl}${jobDefinitionLink.href}`,
|
.get<JobDefinition>(
|
||||||
accessToken
|
`${this.serverUrl}${jobDefinitionLink.href}`,
|
||||||
)
|
accessToken
|
||||||
|
)
|
||||||
|
.catch((err) => {
|
||||||
|
throw prefixMessage(err, 'Error while getting job definition. ')
|
||||||
|
})
|
||||||
|
|
||||||
code = jobDefinition.code
|
code = jobDefinition.code
|
||||||
|
|
||||||
@@ -861,6 +872,7 @@ export class SASViyaApiClient {
|
|||||||
if (!code) code = ''
|
if (!code) code = ''
|
||||||
|
|
||||||
const linesToExecute = code.replace(/\r\n/g, '\n').split('\n')
|
const linesToExecute = code.replace(/\r\n/g, '\n').split('\n')
|
||||||
|
|
||||||
return await this.executeScript(
|
return await this.executeScript(
|
||||||
sasJob,
|
sasJob,
|
||||||
linesToExecute,
|
linesToExecute,
|
||||||
@@ -872,7 +884,9 @@ export class SASViyaApiClient {
|
|||||||
waitForResult,
|
waitForResult,
|
||||||
pollOptions,
|
pollOptions,
|
||||||
printPid
|
printPid
|
||||||
)
|
).catch((err) => {
|
||||||
|
throw prefixMessage(err, 'Error while executing script. ')
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1007,19 +1021,27 @@ export class SASViyaApiClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const url = '/folders/folders/@item?path=' + path
|
const url = '/folders/folders/@item?path=' + path
|
||||||
const { result: folder } = await this.requestClient.get<Folder>(
|
const { result: folder } = await this.requestClient
|
||||||
`${url}`,
|
.get<Folder>(`${url}`, accessToken)
|
||||||
accessToken
|
.catch((err) => {
|
||||||
)
|
throw prefixMessage(err, 'Error while getting folder. ')
|
||||||
|
})
|
||||||
|
|
||||||
if (!folder) {
|
if (!folder) {
|
||||||
throw new Error(`The path ${path} does not exist on ${this.serverUrl}`)
|
throw new Error(`The path ${path} does not exist on ${this.serverUrl}`)
|
||||||
}
|
}
|
||||||
const { result: members } = await this.requestClient.get<{ items: any[] }>(
|
|
||||||
`/folders/folders/${folder.id}/members?limit=${folder.memberCount}`,
|
const { result: members } = await this.requestClient
|
||||||
accessToken
|
.get<{ items: any[] }>(
|
||||||
)
|
`/folders/folders/${folder.id}/members?limit=${folder.memberCount}`,
|
||||||
|
accessToken
|
||||||
|
)
|
||||||
|
.catch((err) => {
|
||||||
|
throw prefixMessage(err, 'Error while getting members. ')
|
||||||
|
})
|
||||||
|
|
||||||
const itemsAtRoot = members.items
|
const itemsAtRoot = members.items
|
||||||
|
|
||||||
this.folderMap.set(path, itemsAtRoot)
|
this.folderMap.set(path, itemsAtRoot)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1052,11 +1074,15 @@ export class SASViyaApiClient {
|
|||||||
Promise.reject(`Job state link was not found.`)
|
Promise.reject(`Job state link was not found.`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const { result: state } = await this.requestClient.get<string>(
|
const { result: state } = await this.requestClient
|
||||||
`${this.serverUrl}${stateLink.href}?_action=wait&wait=30`,
|
.get<string>(
|
||||||
accessToken,
|
`${this.serverUrl}${stateLink.href}?_action=wait&wait=30`,
|
||||||
'text/plain'
|
accessToken,
|
||||||
)
|
'text/plain'
|
||||||
|
)
|
||||||
|
.catch((err) => {
|
||||||
|
throw prefixMessage(err, 'Error while getting job state. ')
|
||||||
|
})
|
||||||
|
|
||||||
const currentState = state.trim()
|
const currentState = state.trim()
|
||||||
if (currentState === 'completed') {
|
if (currentState === 'completed') {
|
||||||
@@ -1073,11 +1099,15 @@ export class SASViyaApiClient {
|
|||||||
postedJobState === 'pending'
|
postedJobState === 'pending'
|
||||||
) {
|
) {
|
||||||
if (stateLink) {
|
if (stateLink) {
|
||||||
const { result: jobState } = await this.requestClient.get<string>(
|
const { result: jobState } = await this.requestClient
|
||||||
`${this.serverUrl}${stateLink.href}?_action=wait&wait=30`,
|
.get<string>(
|
||||||
accessToken,
|
`${this.serverUrl}${stateLink.href}?_action=wait&wait=30`,
|
||||||
'text/plain'
|
accessToken,
|
||||||
)
|
'text/plain'
|
||||||
|
)
|
||||||
|
.catch((err) => {
|
||||||
|
throw prefixMessage(err, 'Error while getting job state. ')
|
||||||
|
})
|
||||||
|
|
||||||
postedJobState = jobState.trim()
|
postedJobState = jobState.trim()
|
||||||
|
|
||||||
@@ -1119,11 +1149,11 @@ export class SASViyaApiClient {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const uploadResponse = await this.requestClient.uploadFile(
|
const uploadResponse = await this.requestClient
|
||||||
`${this.serverUrl}/files/files#rawUpload`,
|
.uploadFile(`${this.serverUrl}/files/files#rawUpload`, csv, accessToken)
|
||||||
csv,
|
.catch((err) => {
|
||||||
accessToken
|
throw prefixMessage(err, 'Error while uploading file. ')
|
||||||
)
|
})
|
||||||
|
|
||||||
uploadedFiles.push({ tableName, file: uploadResponse.result })
|
uploadedFiles.push({ tableName, file: uploadResponse.result })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ export class SessionManager {
|
|||||||
this.sessions = this.sessions.filter((s) => s.id !== id)
|
this.sessions = this.sessions.filter((s) => s.id !== id)
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
throw err
|
throw prefixMessage(err, 'Error while deleting session. ')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { LoginRequiredError } from '../types'
|
|||||||
import { AuthorizeError } from '../types/AuthorizeError'
|
import { AuthorizeError } from '../types/AuthorizeError'
|
||||||
import { NotFoundError } from '../types/NotFoundError'
|
import { NotFoundError } from '../types/NotFoundError'
|
||||||
import { parseWeboutResponse } from '../utils/parseWeboutResponse'
|
import { parseWeboutResponse } from '../utils/parseWeboutResponse'
|
||||||
|
import { prefixMessage } from '@sasjs/utils/error'
|
||||||
|
|
||||||
export interface HttpClient {
|
export interface HttpClient {
|
||||||
get<T>(
|
get<T>(
|
||||||
@@ -97,7 +98,9 @@ export class RequestClient implements HttpClient {
|
|||||||
.catch(async (e) => {
|
.catch(async (e) => {
|
||||||
return await this.handleError(e, () =>
|
return await this.handleError(e, () =>
|
||||||
this.get<T>(url, accessToken, contentType, overrideHeaders)
|
this.get<T>(url, accessToken, contentType, overrideHeaders)
|
||||||
)
|
).catch((err) => {
|
||||||
|
throw prefixMessage(err, 'Error while handling error. ')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -331,33 +334,54 @@ export class RequestClient implements HttpClient {
|
|||||||
|
|
||||||
private handleError = async (e: any, callback: any) => {
|
private handleError = async (e: any, callback: any) => {
|
||||||
const response = e.response as AxiosResponse
|
const response = e.response as AxiosResponse
|
||||||
|
|
||||||
if (e instanceof AuthorizeError) {
|
if (e instanceof AuthorizeError) {
|
||||||
const res = await this.httpClient.get(e.confirmUrl, {
|
const res = await this.httpClient
|
||||||
responseType: 'text',
|
.get(e.confirmUrl, {
|
||||||
headers: { 'Content-Type': 'text/plain', Accept: '*/*' }
|
responseType: 'text',
|
||||||
})
|
headers: { 'Content-Type': 'text/plain', Accept: '*/*' }
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
throw prefixMessage(err, 'Error while getting error confirmUrl. ')
|
||||||
|
})
|
||||||
|
|
||||||
if (isAuthorizeFormRequired(res?.data as string)) {
|
if (isAuthorizeFormRequired(res?.data as string)) {
|
||||||
await this.authorize(res.data as string)
|
await this.authorize(res.data as string)
|
||||||
}
|
}
|
||||||
return await callback()
|
|
||||||
|
return await callback().catch((err: any) => {
|
||||||
|
throw prefixMessage(
|
||||||
|
err,
|
||||||
|
'Error while executing callback in handleError. '
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e instanceof LoginRequiredError) {
|
if (e instanceof LoginRequiredError) {
|
||||||
this.clearCsrfTokens()
|
this.clearCsrfTokens()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response?.status === 403 || response?.status === 449) {
|
if (response?.status === 403 || response?.status === 449) {
|
||||||
this.parseAndSetCsrfToken(response)
|
this.parseAndSetCsrfToken(response)
|
||||||
|
|
||||||
if (this.csrfToken.headerName && this.csrfToken.value) {
|
if (this.csrfToken.headerName && this.csrfToken.value) {
|
||||||
return await callback()
|
return await callback().catch((err: any) => {
|
||||||
|
throw prefixMessage(
|
||||||
|
err,
|
||||||
|
'Error while executing callback in handleError. '
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
throw e
|
throw e
|
||||||
} else if (response?.status === 404) {
|
} else if (response?.status === 404) {
|
||||||
throw new NotFoundError(response.config.url!)
|
throw new NotFoundError(response.config.url!)
|
||||||
}
|
}
|
||||||
|
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
|
|
||||||
private async parseResponse<T>(response: AxiosResponse<any>) {
|
private parseResponse<T>(response: AxiosResponse<any>) {
|
||||||
const etag = response?.headers ? response.headers['etag'] : ''
|
const etag = response?.headers ? response.headers['etag'] : ''
|
||||||
let parsedResponse
|
let parsedResponse
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user