mirror of
https://github.com/sasjs/adapter.git
synced 2026-01-09 13:30:04 +00:00
feat(job-execution): populate folder map on demand
This commit is contained in:
@@ -8,7 +8,6 @@ import {
|
|||||||
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,
|
||||||
@@ -30,14 +29,12 @@ 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 (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,
|
||||||
@@ -47,15 +44,15 @@ export class SASViyaApiClient {
|
|||||||
private folderMap = new Map<string, Job[]>()
|
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) {
|
if (this.folderMap.get(folderPath)) {
|
||||||
return this.rootFolderMap
|
return this.folderMap.get(folderPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.populateRootFolderMap()
|
await this.populateFolderMap(folderPath)
|
||||||
return this.rootFolderMap
|
return this.folderMap.get(folderPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -658,8 +655,11 @@ export class SASViyaApiClient {
|
|||||||
createFolderRequest
|
createFolderRequest
|
||||||
)
|
)
|
||||||
|
|
||||||
// update 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -897,22 +897,12 @@ export class SASViyaApiClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isRelativePath(sasJob)) {
|
if (isRelativePath(sasJob)) {
|
||||||
if (!this.rootFolder) {
|
const folderName = sasJob.split('/')[0]
|
||||||
await this.populateRootFolder(accessToken)
|
await this.populateFolderMap(`${this.rootFolderName}/${folderName}`)
|
||||||
}
|
|
||||||
if (!this.rootFolder) {
|
if (!this.folderMap.get(`${this.rootFolderName}/${folderName}`)) {
|
||||||
console.error('Root folder was not found')
|
|
||||||
throw new Error('Root folder was not found')
|
|
||||||
}
|
|
||||||
if (!this.rootFolderMap.size) {
|
|
||||||
await this.populateRootFolderMap(accessToken)
|
|
||||||
}
|
|
||||||
if (!this.rootFolderMap.size) {
|
|
||||||
console.error(
|
|
||||||
`The job ${sasJob} was not found in ${this.rootFolderName}`
|
|
||||||
)
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`The job ${sasJob} was not found in ${this.rootFolderName}`
|
`The folder '${folderName}' was not found at '${this.serverUrl}/${this.rootFolderName}'`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -931,7 +921,9 @@ export class SASViyaApiClient {
|
|||||||
if (isRelativePath(sasJob)) {
|
if (isRelativePath(sasJob)) {
|
||||||
const folderName = sasJob.split('/')[0]
|
const folderName = sasJob.split('/')[0]
|
||||||
const jobName = sasJob.split('/')[1]
|
const jobName = sasJob.split('/')[1]
|
||||||
const jobFolder = this.rootFolderMap.get(folderName)
|
const jobFolder = this.folderMap.get(
|
||||||
|
`${this.rootFolderName}/${folderName}`
|
||||||
|
)
|
||||||
jobToExecute = jobFolder?.find((item) => item.name === jobName)
|
jobToExecute = jobFolder?.find((item) => item.name === jobName)
|
||||||
} else {
|
} else {
|
||||||
const folderPathParts = sasJob.split('/')
|
const folderPathParts = sasJob.split('/')
|
||||||
@@ -983,7 +975,7 @@ 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.
|
||||||
@@ -996,34 +988,59 @@ 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}`)) {
|
||||||
}
|
|
||||||
if (!this.rootFolderMap.size) {
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`The job ${sasJob} was not found in ${this.rootFolderName}`
|
`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)
|
||||||
|
if (!this.folderMap.get(folderPath)) {
|
||||||
|
throw new Error(
|
||||||
|
`The folder '${folderPath}' was not found at '${this.serverUrl}'.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let files: any[] = []
|
let files: any[] = []
|
||||||
if (data && Object.keys(data).length) {
|
if (data && Object.keys(data).length) {
|
||||||
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('/', ''))
|
if (isRelativePath(sasJob)) {
|
||||||
|
const folderName = sasJob.split('/')[0]
|
||||||
|
jobName = sasJob.split('/')[1]
|
||||||
|
const jobFolder = this.folderMap.get(
|
||||||
|
`${this.rootFolderName}/${folderName}`
|
||||||
|
)
|
||||||
|
jobToExecute = jobFolder?.find((item) => item.name === jobName)
|
||||||
|
} else {
|
||||||
|
const folderPathParts = sasJob.split('/')
|
||||||
|
jobName = folderPathParts.pop()
|
||||||
|
const folderPath = folderPathParts.join('/')
|
||||||
|
const jobFolder = this.folderMap.get(folderPath)
|
||||||
|
jobToExecute = jobFolder?.find((item) => item.name === jobName)
|
||||||
|
}
|
||||||
|
|
||||||
if (allJobsInFolder) {
|
if (!jobToExecute) {
|
||||||
const jobSpec = allJobsInFolder.find((j: Job) => j.name === jobName)
|
throw new Error(`The job ${sasJob} was not found.`)
|
||||||
const jobDefinitionLink = jobSpec?.links.find(
|
}
|
||||||
|
const jobDefinitionLink = jobToExecute?.links.find(
|
||||||
(l) => l.rel === 'getResource'
|
(l) => l.rel === 'getResource'
|
||||||
)?.href
|
)?.href
|
||||||
const requestInfo: any = {
|
const requestInfo: any = {
|
||||||
@@ -1091,7 +1108,8 @@ export class SASViyaApiClient {
|
|||||||
{ headers }
|
{ headers }
|
||||||
)
|
)
|
||||||
|
|
||||||
let jobResult, log
|
let jobResult
|
||||||
|
let log
|
||||||
if (jobStatus === 'failed') {
|
if (jobStatus === 'failed') {
|
||||||
return Promise.reject(currentJob.error)
|
return Promise.reject(currentJob.error)
|
||||||
}
|
}
|
||||||
@@ -1110,19 +1128,16 @@ export class SASViyaApiClient {
|
|||||||
{
|
{
|
||||||
headers
|
headers
|
||||||
}
|
}
|
||||||
).then((res: any) =>
|
).then((res: any) => res.result.items.map((i: any) => i.line).join('\n'))
|
||||||
res.result.items.map((i: any) => i.line).join('\n')
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
return { result: jobResult?.result, log }
|
return { result: jobResult?.result, log }
|
||||||
} else {
|
|
||||||
throw new Error(
|
|
||||||
`The job ${sasJob} was not found at the location ${this.rootFolderName}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async populateFolderMap(folderPath: string, accessToken?: string) {
|
private async populateFolderMap(folderPath: string, accessToken?: string) {
|
||||||
|
if (this.folderMap.get(folderPath)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const url = '/folders/folders/@item?path=' + folderPath
|
const url = '/folders/folders/@item?path=' + folderPath
|
||||||
const requestInfo: any = {
|
const requestInfo: any = {
|
||||||
method: 'GET'
|
method: 'GET'
|
||||||
@@ -1148,79 +1163,6 @@ export class SASViyaApiClient {
|
|||||||
this.folderMap.set(folderPath, itemsAtRoot)
|
this.folderMap.set(folderPath, itemsAtRoot)
|
||||||
}
|
}
|
||||||
|
|
||||||
private async populateRootFolderMap(accessToken?: string) {
|
|
||||||
const allItems = new Map<string, Job[]>()
|
|
||||||
const url = '/folders/folders/@item?path=' + this.rootFolderName
|
|
||||||
const requestInfo: any = {
|
|
||||||
method: 'GET'
|
|
||||||
}
|
|
||||||
if (accessToken) {
|
|
||||||
requestInfo.headers = { Authorization: `Bearer ${accessToken}` }
|
|
||||||
}
|
|
||||||
const { result: folder } = await this.request<Folder>(
|
|
||||||
`${this.serverUrl}${url}`,
|
|
||||||
requestInfo
|
|
||||||
)
|
|
||||||
if (!folder) {
|
|
||||||
throw new Error('Cannot populate RootFolderMap unless rootFolder exists')
|
|
||||||
}
|
|
||||||
const { result: members } = await this.request<{ items: any[] }>(
|
|
||||||
`${this.serverUrl}/folders/folders/${folder.id}/members`,
|
|
||||||
requestInfo
|
|
||||||
)
|
|
||||||
|
|
||||||
const itemsAtRoot = members.items
|
|
||||||
allItems.set('', 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(
|
||||||
postedJob: any,
|
postedJob: any,
|
||||||
etag: string | null,
|
etag: string | null,
|
||||||
|
|||||||
@@ -1079,14 +1079,12 @@ 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) {
|
|
||||||
const jobKey = sasJob.split('/')[0]
|
const jobKey = sasJob.split('/')[0]
|
||||||
const jobName = sasJob.split('/')[1]
|
const jobName = sasJob.split('/')[1]
|
||||||
|
|
||||||
const locJobs = jobMap.get(jobKey)
|
const locJobs = await this.sasViyaApiClient.getJobsInFolder(jobKey)
|
||||||
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'
|
||||||
@@ -1095,7 +1093,6 @@ export default class SASjs {
|
|||||||
uri = job.uri
|
uri = job.uri
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return uri
|
return uri
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user