1
0
mirror of https://github.com/sasjs/adapter.git synced 2026-01-15 16:10:06 +00:00

Merge pull request #94 from sasjs/sas-job-absolute-paths

feat(job-execution): support absolute paths to SAS jobs
This commit is contained in:
Krishna Acondy
2020-09-22 08:46:04 +01:00
committed by GitHub
44 changed files with 8874 additions and 846 deletions

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

1861
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -43,6 +43,7 @@
"path": "^0.12.7", "path": "^0.12.7",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"semantic-release": "^17.1.2", "semantic-release": "^17.1.2",
"terser-webpack-plugin": "^4.2.2",
"ts-jest": "^25.5.1", "ts-jest": "^25.5.1",
"ts-loader": "^8.0.4", "ts-loader": "^8.0.4",
"tslint": "^6.1.3", "tslint": "^6.1.3",
@@ -51,7 +52,6 @@
"typedoc-neo-theme": "^1.0.10", "typedoc-neo-theme": "^1.0.10",
"typedoc-plugin-external-module-name": "^4.0.3", "typedoc-plugin-external-module-name": "^4.0.3",
"typescript": "^3.9.7", "typescript": "^3.9.7",
"uglifyjs-webpack-plugin": "^2.2.0",
"webpack": "^4.44.2", "webpack": "^4.44.2",
"webpack-cli": "^3.3.12" "webpack-cli": "^3.3.12"
}, },

View File

@@ -13,7 +13,7 @@ const defaultConfig: SASjsConfig = {
}; };
const customConfig = { const customConfig = {
serverUrl: "url", serverUrl: "http://url.com",
pathSAS9: "sas9", pathSAS9: "sas9",
pathSASViya: "viya", pathSASViya: "viya",
appLoc: "/Public/seedapp", appLoc: "/Public/seedapp",

View File

@@ -47,6 +47,16 @@ const getLargeObjectData = () => {
export const sendArrTests = (adapter: SASjs): TestSuite => ({ export const sendArrTests = (adapter: SASjs): TestSuite => ({
name: "sendArr", name: "sendArr",
tests: [ tests: [
{
title: "Absolute paths",
description: "Should work with absolute paths to SAS jobs",
test: () => {
return adapter.request("/Public/app/common/sendArr", stringData);
},
assertion: (res: any) => {
return res.table1[0][0] === stringData.table1[0].col1;
}
},
{ {
title: "Single string value", title: "Single string value",
description: "Should send an array with a single string value", description: "Should send an array with a single string value",

View File

@@ -3,20 +3,21 @@ import {
parseAndSubmitAuthorizeForm, parseAndSubmitAuthorizeForm,
convertToCSV, convertToCSV,
makeRequest, makeRequest,
isRelativePath,
isUri, isUri,
isUrl isUrl
} from './utils' } from './utils'
import * as NodeFormData from 'form-data' import * as NodeFormData from 'form-data'
import * as path from 'path'
import { import {
Job, Job,
Session, Session,
Context, Context,
Folder, Folder,
CsrfToken, CsrfToken,
EditContextInput EditContextInput,
ErrorResponse,
JobDefinition
} from './types' } from './types'
import { JobDefinition } from './types/JobDefinition'
import { formatDataForRequest } from './utils/formatDataForRequest' import { formatDataForRequest } from './utils/formatDataForRequest'
import { SessionManager } from './SessionManager' import { SessionManager } from './SessionManager'
@@ -29,35 +30,33 @@ export class SASViyaApiClient {
private serverUrl: string, private serverUrl: string,
private rootFolderName: string, private rootFolderName: string,
private contextName: string, private contextName: string,
private setCsrfToken: (csrfToken: CsrfToken) => void, private setCsrfToken: (csrfToken: CsrfToken) => void
private rootFolderMap = new Map<string, Job[]>()
) { ) {
if (!rootFolderName) {
throw new Error('Root folder must be provided.')
}
if (serverUrl) isUrl(serverUrl) if (serverUrl) isUrl(serverUrl)
} }
private csrfToken: CsrfToken | null = null private csrfToken: CsrfToken | null = null
private rootFolder: Folder | null = null
private sessionManager = new SessionManager( private sessionManager = new SessionManager(
this.serverUrl, this.serverUrl,
this.contextName, this.contextName,
this.setCsrfToken this.setCsrfToken
) )
private isForceDeploy: boolean = false private isForceDeploy: boolean = false
private folderMap = new Map<string, Job[]>()
/** /**
* Returns a map containing the directory structure in the currently set root folder. * Returns a list of jobs in the currently set root folder.
*/ */
public async getAppLocMap() { public async getJobsInFolder(folderPath: string) {
if (this.rootFolderMap.size) { const path = isRelativePath(folderPath)
return this.rootFolderMap ? `${this.rootFolderName}/${folderPath}`
: folderPath
if (this.folderMap.get(path)) {
return this.folderMap.get(path)
} }
this.populateRootFolderMap() await this.populateFolderMap(path)
return this.rootFolderMap return this.folderMap.get(path)
} }
/** /**
@@ -192,7 +191,7 @@ export class SASViyaApiClient {
} }
const { result: contexts } = await this.request<{ items: Context[] }>( const { result: contexts } = await this.request<{ items: Context[] }>(
`${this.serverUrl}/compute/contexts`, `${this.serverUrl}/compute/contexts?limit=10000`,
{ headers } { headers }
) )
const executionContext = const executionContext =
@@ -387,7 +386,7 @@ export class SASViyaApiClient {
/** /**
* Executes code on the current SAS Viya server. * Executes code on the current SAS Viya server.
* @param fileName - a name for the file being submitted for execution. * @param jobPath - the path to the file being submitted for execution.
* @param linesOfCode - an array of code lines to execute. * @param linesOfCode - an array of code lines to execute.
* @param contextName - the context to execute the code in. * @param contextName - the context to execute the code in.
* @param accessToken - an access token for an authorized user. * @param accessToken - an access token for an authorized user.
@@ -398,7 +397,7 @@ export class SASViyaApiClient {
* @param expectWebout - when set to true, the automatic _webout fileref will be checked for content, and that content returned. This fileref is used when the Job contains a SASjs web request (as opposed to executing arbitrary SAS code). * @param expectWebout - when set to true, the automatic _webout fileref will be checked for content, and that content returned. This fileref is used when the Job contains a SASjs web request (as opposed to executing arbitrary SAS code).
*/ */
public async executeScript( public async executeScript(
jobName: string, jobPath: string,
linesOfCode: string[], linesOfCode: string[],
contextName: string, contextName: string,
accessToken?: string, accessToken?: string,
@@ -436,13 +435,21 @@ export class SASViyaApiClient {
jobArguments['_DEBUG'] = 131 jobArguments['_DEBUG'] = 131
} }
const fileName = `exec-${ let fileName
jobName.includes('/') ? jobName.split('/')[1] : jobName if (isRelativePath(jobPath)) {
}` fileName = `exec-${
jobPath.includes('/') ? jobPath.split('/')[1] : jobPath
}`
} else {
const jobPathParts = jobPath.split('/')
fileName = jobPathParts.pop()
}
let jobVariables: any = { let jobVariables: any = {
SYS_JES_JOB_URI: '', SYS_JES_JOB_URI: '',
_program: this.rootFolderName + '/' + jobName _program: isRelativePath(jobPath)
? this.rootFolderName + '/' + jobPath
: jobPath
} }
let files: any[] = [] let files: any[] = []
@@ -534,9 +541,30 @@ export class SASViyaApiClient {
`${this.serverUrl}${resultLink}`, `${this.serverUrl}${resultLink}`,
{ headers }, { headers },
'text' 'text'
).catch((e) => ({ ).catch(async (e) => {
result: JSON.stringify(e) if (e && e.status === 404) {
})) if (logLink) {
log = await this.request<any>(
`${this.serverUrl}${logLink.href}/content?limit=10000`,
{
headers
}
).then((res: any) =>
res.result.items.map((i: any) => i.line).join('\n')
)
return Promise.reject(
new ErrorResponse('Job execution failed', {
status: 500,
body: log
})
)
}
}
return {
result: JSON.stringify(e)
}
})
} }
await this.sessionManager.clearSession(executionSessionId, accessToken) await this.sessionManager.clearSession(executionSessionId, accessToken)
@@ -545,7 +573,7 @@ export class SASViyaApiClient {
} catch (e) { } catch (e) {
if (e && e.status === 404) { if (e && e.status === 404) {
return this.executeScript( return this.executeScript(
jobName, jobPath,
linesOfCode, linesOfCode,
contextName, contextName,
accessToken, accessToken,
@@ -662,8 +690,11 @@ export class SASViyaApiClient {
createFolderRequest createFolderRequest
) )
// updates rootFolderMap with newly created folder. // update folder map with newly created folder.
await this.populateRootFolderMap(accessToken) await this.populateFolderMap(
`${parentFolderPath}/${folderName}`,
accessToken
)
return createFolderResponse return createFolderResponse
} }
@@ -892,30 +923,48 @@ export class SASViyaApiClient {
data?: any, data?: any,
accessToken?: string accessToken?: string
) { ) {
if (!this.rootFolder) { if (isRelativePath(sasJob) && !this.rootFolderName) {
await this.populateRootFolder(accessToken)
}
if (!this.rootFolder) {
throw new Error(`Root folder was not found.`)
}
if (!this.rootFolderMap.size) {
await this.populateRootFolderMap(accessToken)
}
if (!this.rootFolderMap.size) {
throw new Error( throw new Error(
`The job '${sasJob}' was not found in '${this.rootFolderName}'.` 'Relative paths cannot be used without specifying a root folder name'
) )
} }
if (isRelativePath(sasJob)) {
const folderName = sasJob.split('/')[0]
await this.populateFolderMap(`${this.rootFolderName}/${folderName}`)
if (!this.folderMap.get(`${this.rootFolderName}/${folderName}`)) {
throw new Error(
`The folder '${folderName}' was not found at '${this.serverUrl}/${this.rootFolderName}'`
)
}
} else {
const folderPathParts = sasJob.split('/')
folderPathParts.pop()
const folderPath = folderPathParts.join('/')
await this.populateFolderMap(folderPath, accessToken)
}
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}`
} }
const folderName = sasJob.split('/')[0] let jobToExecute
const jobName = sasJob.split('/')[1] if (isRelativePath(sasJob)) {
const jobFolder = this.rootFolderMap.get(folderName) const folderName = sasJob.split('/')[0]
const jobToExecute = jobFolder?.find((item) => item.name === jobName) const jobName = sasJob.split('/')[1]
const jobFolder = this.folderMap.get(
`${this.rootFolderName}/${folderName}`
)
jobToExecute = jobFolder?.find((item) => item.name === jobName)
} else {
const folderPathParts = sasJob.split('/')
const jobName = folderPathParts.pop()
const folderPath = folderPathParts.join('/')
const jobFolder = this.folderMap.get(folderPath)
jobToExecute = jobFolder?.find((item) => item.name === jobName)
}
if (!jobToExecute) { if (!jobToExecute) {
throw new Error(`Job was not found.`) throw new Error(`Job was not found.`)
@@ -957,8 +1006,8 @@ export class SASViyaApiClient {
} }
/** /**
* Executes a job via the SAS Viya Job Execution API. * Executes a job via the SAS Viya Job Execution API
* @param sasJob - the relative path to the job. * @param sasJob - the relative or absolute path to the job.
* @param contextName - the name of the context where the job is to be executed. * @param contextName - the name of the context where the job is to be executed.
* @param debug - sets the _debug flag in the job arguments. * @param debug - sets the _debug flag in the job arguments.
* @param data - any data to be passed in as input to the job. * @param data - any data to be passed in as input to the job.
@@ -971,20 +1020,31 @@ export class SASViyaApiClient {
data?: any, data?: any,
accessToken?: string accessToken?: string
) { ) {
if (!this.rootFolder) { if (isRelativePath(sasJob) && !this.rootFolderName) {
await this.populateRootFolder(accessToken) throw new Error(
'Relative paths cannot be used without specifying a root folder name.'
)
} }
if (!this.rootFolder) { if (isRelativePath(sasJob)) {
throw new Error(`Root folder was not found.`) const folderName = sasJob.split('/')[0]
} await this.populateFolderMap(`${this.rootFolderName}/${folderName}`)
if (!this.rootFolderMap.size) {
await this.populateRootFolderMap(accessToken) if (!this.folderMap.get(`${this.rootFolderName}/${folderName}`)) {
} throw new Error(
if (!this.rootFolderMap.size) { `The folder '${folderName}' was not found at '${this.serverUrl}/${this.rootFolderName}'.`
throw new Error( )
`The job '${sasJob}' was not found in folder '${this.rootFolderName}'.` }
) } else {
const folderPathParts = sasJob.split('/')
folderPathParts.pop()
const folderPath = folderPathParts.join('/')
await this.populateFolderMap(folderPath, accessToken)
if (!this.folderMap.get(folderPath)) {
throw new Error(
`The folder '${folderPath}' was not found at '${this.serverUrl}'.`
)
}
} }
let files: any[] = [] let files: any[] = []
@@ -992,124 +1052,128 @@ export class SASViyaApiClient {
files = await this.uploadTables(data, accessToken) files = await this.uploadTables(data, accessToken)
} }
const jobName = path.basename(sasJob) let jobToExecute: Job | undefined
const jobFolder = sasJob.replace(`/${jobName}`, '') let jobName: string | undefined
const allJobsInFolder = this.rootFolderMap.get(jobFolder.replace('/', '')) let jobPath: string | undefined
if (isRelativePath(sasJob)) {
if (allJobsInFolder) { const folderName = sasJob.split('/')[0]
const jobSpec = allJobsInFolder.find((j: Job) => j.name === jobName) jobName = sasJob.split('/')[1]
jobPath = `${this.rootFolderName}/${folderName}`
if (!jobSpec) { const jobFolder = this.folderMap.get(jobPath)
throw new Error('Job was not found.') jobToExecute = jobFolder?.find((item) => item.name === jobName)
}
const jobDefinitionLink = jobSpec?.links.find(
(l) => l.rel === 'getResource'
)?.href
if (!jobDefinitionLink) {
throw new Error('Job definition URI was not found.')
}
const requestInfo: any = {
method: 'GET'
}
const headers: any = { 'Content-Type': 'application/json' }
if (!!accessToken) {
headers.Authorization = `Bearer ${accessToken}`
}
requestInfo.headers = headers
const { result: jobDefinition } = await this.request<Job>(
`${this.serverUrl}${jobDefinitionLink}`,
requestInfo
)
const jobArguments: { [key: string]: any } = {
_contextName: contextName,
_program: `${this.rootFolderName}/${sasJob}`,
_webin_file_count: files.length,
_OMITJSONLISTING: true,
_OMITJSONLOG: true,
_OMITSESSIONRESULTS: true,
_OMITTEXTLISTING: true,
_OMITTEXTLOG: true
}
if (debug) {
jobArguments['_OMITTEXTLOG'] = 'false'
jobArguments['_OMITSESSIONRESULTS'] = 'false'
jobArguments['_DEBUG'] = 131
}
files.forEach((fileInfo, index) => {
jobArguments[
`_webin_fileuri${index + 1}`
] = `/files/files/${fileInfo.file.id}`
jobArguments[`_webin_name${index + 1}`] = fileInfo.tableName
})
const postJobRequest = {
method: 'POST',
headers,
body: JSON.stringify({
name: `exec-${jobName}`,
description: 'Powered by SASjs',
jobDefinition,
arguments: jobArguments
})
}
const { result: postedJob, etag } = await this.request<Job>(
`${this.serverUrl}/jobExecution/jobs?_action=wait`,
postJobRequest
)
const jobStatus = await this.pollJobState(
postedJob,
etag,
accessToken,
true
)
const { result: currentJob } = await this.request<Job>(
`${this.serverUrl}/jobExecution/jobs/${postedJob.id}`,
{ headers }
)
let jobResult, log
if (jobStatus === 'failed') {
return Promise.reject(currentJob.error)
}
const resultLink = currentJob.results['_webout.json']
const logLink = currentJob.links.find((l) => l.rel === 'log')
if (resultLink) {
jobResult = await this.request<any>(
`${this.serverUrl}${resultLink}/content`,
{ headers },
'text'
)
}
if (debug && logLink) {
log = await this.request<any>(
`${this.serverUrl}${logLink.href}/content`,
{
headers
}
).then((res: any) =>
res.result.items.map((i: any) => i.line).join('\n')
)
}
return { result: jobResult?.result, log }
} else { } else {
throw new Error( const folderPathParts = sasJob.split('/')
`The job '${sasJob}' was not found in folder '${this.rootFolderName}'.` jobName = folderPathParts.pop()
jobPath = folderPathParts.join('/')
const jobFolder = this.folderMap.get(jobPath)
jobToExecute = jobFolder?.find((item) => item.name === jobName)
}
if (!jobToExecute) {
throw new Error(`The job ${sasJob} was not found.`)
}
const jobDefinitionLink = jobToExecute?.links.find(
(l) => l.rel === 'getResource'
)?.href
const requestInfo: any = {
method: 'GET'
}
const headers: any = { 'Content-Type': 'application/json' }
if (!!accessToken) {
headers.Authorization = `Bearer ${accessToken}`
}
requestInfo.headers = headers
const { result: jobDefinition } = await this.request<Job>(
`${this.serverUrl}${jobDefinitionLink}`,
requestInfo
)
const jobArguments: { [key: string]: any } = {
_contextName: contextName,
_program: `${jobPath}/${jobName}`,
_webin_file_count: files.length,
_OMITJSONLISTING: true,
_OMITJSONLOG: true,
_OMITSESSIONRESULTS: true,
_OMITTEXTLISTING: true,
_OMITTEXTLOG: true
}
if (debug) {
jobArguments['_OMITTEXTLOG'] = 'false'
jobArguments['_OMITSESSIONRESULTS'] = 'false'
jobArguments['_DEBUG'] = 131
}
files.forEach((fileInfo, index) => {
jobArguments[
`_webin_fileuri${index + 1}`
] = `/files/files/${fileInfo.file.id}`
jobArguments[`_webin_name${index + 1}`] = fileInfo.tableName
})
const postJobRequest = {
method: 'POST',
headers,
body: JSON.stringify({
name: `exec-${jobName}`,
description: 'Powered by SASjs',
jobDefinition,
arguments: jobArguments
})
}
const { result: postedJob, etag } = await this.request<Job>(
`${this.serverUrl}/jobExecution/jobs?_action=wait`,
postJobRequest
)
const jobStatus = await this.pollJobState(
postedJob,
etag,
accessToken,
true
)
const { result: currentJob } = await this.request<Job>(
`${this.serverUrl}/jobExecution/jobs/${postedJob.id}`,
{ headers }
)
let jobResult
let log
if (jobStatus === 'failed') {
return Promise.reject(currentJob.error)
}
const resultLink = currentJob.results['_webout.json']
const logLink = currentJob.links.find((l) => l.rel === 'log')
if (resultLink) {
jobResult = await this.request<any>(
`${this.serverUrl}${resultLink}/content`,
{ headers },
'text'
) )
} }
if (debug && logLink) {
log = await this.request<any>(
`${this.serverUrl}${logLink.href}/content`,
{
headers
}
).then((res: any) => res.result.items.map((i: any) => i.line).join('\n'))
}
return { result: jobResult?.result, log }
} }
private async populateRootFolderMap(accessToken?: string) { private async populateFolderMap(folderPath: string, accessToken?: string) {
const allItems = new Map<string, Job[]>() const path = isRelativePath(folderPath)
const url = '/folders/folders/@item?path=' + this.rootFolderName ? `${this.rootFolderName}/${folderPath}`
: folderPath
if (this.folderMap.get(path)) {
return
}
const url = '/folders/folders/@item?path=' + path
const requestInfo: any = { const requestInfo: any = {
method: 'GET' method: 'GET'
} }
@@ -1121,9 +1185,7 @@ export class SASViyaApiClient {
requestInfo requestInfo
) )
if (!folder) { if (!folder) {
throw new Error( throw new Error(`The path ${path} does not exist on ${this.serverUrl}`)
`Not able to populate root folder map, because folder '${this.rootFolderName}' does not exist.`
)
} }
const { result: members } = await this.request<{ items: any[] }>( const { result: members } = await this.request<{ items: any[] }>(
`${this.serverUrl}/folders/folders/${folder.id}/members`, `${this.serverUrl}/folders/folders/${folder.id}/members`,
@@ -1131,55 +1193,7 @@ export class SASViyaApiClient {
) )
const itemsAtRoot = members.items const itemsAtRoot = members.items
allItems.set('', itemsAtRoot) this.folderMap.set(path, itemsAtRoot)
const subfolderRequests = members.items
.filter((i: any) => i.contentType === 'folder')
.map(async (member: any) => {
const subFolderUrl =
'/folders/folders/@item?path=' +
this.rootFolderName +
'/' +
member.name
const { result: memberDetail } = await this.request<Folder>(
`${this.serverUrl}${subFolderUrl}`,
requestInfo
)
const membersLink = memberDetail.links.find(
(l: any) => l.rel === 'members'
)
const { result: memberContents } = await this.request<{ items: any[] }>(
`${this.serverUrl}${membersLink!.href}`,
requestInfo
)
const itemsInFolder = memberContents.items as any[]
allItems.set(member.name, itemsInFolder)
return itemsInFolder
})
await Promise.all(subfolderRequests)
this.rootFolderMap = allItems
}
private async populateRootFolder(accessToken?: string) {
const url = '/folders/folders/@item?path=' + this.rootFolderName
const requestInfo: RequestInit = {
method: 'GET'
}
if (accessToken) {
requestInfo.headers = { Authorization: `Bearer ${accessToken}` }
}
let error
const rootFolder = await this.request<Folder>(
`${this.serverUrl}${url}`,
requestInfo
)
this.rootFolder = rootFolder?.result || null
if (error) {
throw new Error(JSON.stringify(error))
}
} }
private async pollJobState( private async pollJobState(

View File

@@ -21,7 +21,8 @@ import {
parseGeneratedCode, parseGeneratedCode,
parseWeboutResponse, parseWeboutResponse,
needsRetry, needsRetry,
asyncForEach asyncForEach,
isRelativePath
} from './utils' } from './utils'
import { import {
SASjsConfig, SASjsConfig,
@@ -501,8 +502,6 @@ export default class SASjs {
...config ...config
} }
sasJob = sasJob.startsWith('/') ? sasJob.replace('/', '') : sasJob
if (config.serverType === ServerType.SASViya && config.contextName) { if (config.serverType === ServerType.SASViya && config.contextName) {
if (config.useComputeApi) { if (config.useComputeApi) {
requestResponse = await this.executeJobViaComputeApi( requestResponse = await this.executeJobViaComputeApi(
@@ -794,11 +793,15 @@ export default class SASjs {
SASjob: sasJob, SASjob: sasJob,
data data
} }
const program = config.appLoc const program = isRelativePath(sasJob)
? config.appLoc.replace(/\/?$/, '/') + sasJob.replace(/^\//, '') ? config.appLoc
? config.appLoc.replace(/\/?$/, '/') + sasJob.replace(/^\//, '')
: sasJob
: sasJob : sasJob
const jobUri = const jobUri =
config.serverType === 'SASVIYA' ? await this.getJobUri(sasJob) : '' config.serverType === ServerType.SASViya
? await this.getJobUri(sasJob)
: ''
const apiUrl = `${config.serverUrl}${this.jobsPath}/?${ const apiUrl = `${config.serverUrl}${this.jobsPath}/?${
jobUri.length > 0 jobUri.length > 0
? '__program=' + program + '&_job=' + jobUri ? '__program=' + program + '&_job=' + jobUri
@@ -1091,21 +1094,26 @@ export default class SASjs {
private async getJobUri(sasJob: string) { private async getJobUri(sasJob: string) {
if (!this.sasViyaApiClient) return '' if (!this.sasViyaApiClient) return ''
const jobMap: any = await this.sasViyaApiClient.getAppLocMap()
let uri = '' let uri = ''
if (jobMap.size) { let folderPath
const jobKey = sasJob.split('/')[0] let jobName: string
const jobName = sasJob.split('/')[1] if (isRelativePath(sasJob)) {
folderPath = sasJob.split('/')[0]
jobName = sasJob.split('/')[1]
} else {
const folderPathParts = sasJob.split('/')
jobName = folderPathParts.pop() || ''
folderPath = folderPathParts.join('/')
}
const locJobs = jobMap.get(jobKey) const locJobs = await this.sasViyaApiClient.getJobsInFolder(folderPath)
if (locJobs) { if (locJobs) {
const job = locJobs.find( const job = locJobs.find(
(el: any) => el.name === jobName && el.contentType === 'jobDefinition' (el: any) => el.name === jobName && el.contentType === 'jobDefinition'
) )
if (job) { if (job) {
uri = job.uri uri = job.uri
}
} }
} }
return uri return uri

View File

@@ -78,7 +78,7 @@ export class SessionManager {
if (!this.currentContext) { if (!this.currentContext) {
const { result: contexts } = await this.request<{ const { result: contexts } = await this.request<{
items: Context[] items: Context[]
}>(`${this.serverUrl}/compute/contexts`, { }>(`${this.serverUrl}/compute/contexts?limit=10000`, {
headers: this.getHeaders(accessToken) headers: this.getHeaders(accessToken)
}) })

View File

@@ -1,7 +1,10 @@
export * from './Context' export * from './Context'
export * from './CsrfToken' export * from './CsrfToken'
export * from './ErrorResponse'
export * from './Folder' export * from './Folder'
export * from './Job' export * from './Job'
export * from './JobDefinition'
export * from './JobResult'
export * from './Link' export * from './Link'
export * from './SASjsConfig' export * from './SASjsConfig'
export * from './SASjsRequest' export * from './SASjsRequest'
@@ -9,4 +12,3 @@ export * from './SASjsWaitingRequest'
export * from './ServerType' export * from './ServerType'
export * from './Session' export * from './Session'
export * from './UploadFile' export * from './UploadFile'
export * from './ErrorResponse'

View File

@@ -4,6 +4,9 @@ export * from './convertToCsv'
export * from './isAuthorizeFormRequired' export * from './isAuthorizeFormRequired'
export * from './isLoginRequired' export * from './isLoginRequired'
export * from './isLoginSuccess' export * from './isLoginSuccess'
export * from './isRelativePath'
export * from './isUri'
export * from './isUrl'
export * from './makeRequest' export * from './makeRequest'
export * from './needsRetry' export * from './needsRetry'
export * from './parseAndSubmitAuthorizeForm' export * from './parseAndSubmitAuthorizeForm'
@@ -13,5 +16,3 @@ export * from './parseSasViyaLog'
export * from './serialize' export * from './serialize'
export * from './splitChunks' export * from './splitChunks'
export * from './parseWeboutResponse' export * from './parseWeboutResponse'
export * from './isUri'
export * from './isUrl'

View File

@@ -0,0 +1,2 @@
export const isRelativePath = (uri: string): boolean =>
!!uri && !uri.startsWith('/')

View File

@@ -1,49 +1,57 @@
const path = require("path"); const path = require('path')
const webpack = require("webpack"); const webpack = require('webpack')
const UglifyJsPlugin = require("uglifyjs-webpack-plugin"); const terserPlugin = require('terser-webpack-plugin')
const browserConfig = { const browserConfig = {
entry: "./src/index.ts", entry: './src/index.ts',
devtool: "inline-source-map", devtool: 'inline-source-map',
mode: "development", mode: 'production',
optimization: { optimization: {
minimize: false, minimizer: [
new terserPlugin({
cache: true,
parallel: true,
sourceMap: true,
terserOptions: {}
})
]
}, },
module: { module: {
rules: [ rules: [
{ {
test: /\.ts?$/, test: /\.ts?$/,
use: "ts-loader", use: 'ts-loader',
exclude: /node_modules/, exclude: /node_modules/
}, }
], ]
}, },
resolve: { resolve: {
extensions: [".ts", ".js"], extensions: ['.ts', '.js']
}, },
output: { output: {
filename: "index.js", filename: 'index.js',
path: path.resolve(__dirname, "build"), path: path.resolve(__dirname, 'build'),
libraryTarget: "umd", libraryTarget: 'umd',
library: "SASjs", library: 'SASjs'
}, },
plugins: [ plugins: [
new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /en/), new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /en/),
new webpack.SourceMapDevToolPlugin({ new webpack.SourceMapDevToolPlugin({
filename: null, filename: null,
exclude: [/node_modules/], exclude: [/node_modules/],
test: /\.ts($|\?)/i, test: /\.ts($|\?)/i
}), }),
], new webpack.IgnorePlugin(/\/iconv-loader$/)
}; ]
}
const nodeConfig = { const nodeConfig = {
...browserConfig, ...browserConfig,
target: "node", target: 'node',
output: { output: {
...browserConfig.output, ...browserConfig.output,
path: path.resolve(__dirname, "build", "node"), path: path.resolve(__dirname, 'build', 'node')
}, }
}; }
module.exports = [browserConfig, nodeConfig]; module.exports = [browserConfig, nodeConfig]