mirror of
https://github.com/sasjs/adapter.git
synced 2025-12-11 01:14:36 +00:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd8012fe3e | ||
|
|
fa531b34fd | ||
|
|
354443c98b | ||
|
|
ee30ab195f | ||
|
|
02c1712d22 | ||
|
|
37def7a956 | ||
|
|
653e3d05e0 | ||
|
|
d8467c24b1 | ||
|
|
fc9056c1ac | ||
|
|
9b1d295b82 | ||
|
|
e2ea3f4ddc | ||
|
|
99d0b01a24 |
@@ -30,8 +30,8 @@
|
||||
$('#chart-container').append('<canvas id="myChart" style="display: none;"></canvas>')
|
||||
// make a request to a SAS service
|
||||
var type = $("#cars")[0].options[$("#cars")[0].selectedIndex].value;
|
||||
// request data from an endpoint under your appLoc
|
||||
sasJs.request("/common/getdata", {
|
||||
// request data from an endpoint under your appLoc (missing opening slash implies relative path)
|
||||
sasJs.request("common/getdata", {
|
||||
// send data as an array of objects - each object is one row
|
||||
fromjs: [{ type: type }]
|
||||
}).then((response) => {
|
||||
|
||||
25
package-lock.json
generated
25
package-lock.json
generated
@@ -3704,11 +3704,21 @@
|
||||
"dev": true
|
||||
},
|
||||
"encoding": {
|
||||
"version": "0.1.12",
|
||||
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
|
||||
"integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
|
||||
"version": "0.1.13",
|
||||
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
|
||||
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
|
||||
"requires": {
|
||||
"iconv-lite": "~0.4.13"
|
||||
"iconv-lite": "^0.6.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"iconv-lite": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz",
|
||||
"integrity": "sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==",
|
||||
"requires": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"end-of-stream": {
|
||||
@@ -4967,6 +4977,7 @@
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
}
|
||||
@@ -16070,9 +16081,9 @@
|
||||
}
|
||||
},
|
||||
"whatwg-fetch": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz",
|
||||
"integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q=="
|
||||
"version": "3.4.1",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.4.1.tgz",
|
||||
"integrity": "sha512-sofZVzE1wKwO+EYPbWfiwzaKovWiZXf4coEzjGP9b2GBVgQRLQUZ2QcuPpQExGDAW5GItpEm6Tl4OU5mywnAoQ=="
|
||||
},
|
||||
"whatwg-mimetype": {
|
||||
"version": "2.3.0",
|
||||
|
||||
@@ -38,14 +38,23 @@ export class SASViyaApiClient {
|
||||
|
||||
private csrfToken: CsrfToken | null = null
|
||||
private fileUploadCsrfToken: CsrfToken | null = null
|
||||
private _debug = false
|
||||
private sessionManager = new SessionManager(
|
||||
this.serverUrl,
|
||||
this.contextName,
|
||||
this.setCsrfToken
|
||||
)
|
||||
private isForceDeploy: boolean = false
|
||||
private folderMap = new Map<string, Job[]>()
|
||||
|
||||
public get debug() {
|
||||
return this._debug
|
||||
}
|
||||
|
||||
public set debug(value: boolean) {
|
||||
this._debug = value
|
||||
this.sessionManager.debug = value
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of jobs in the currently set root folder.
|
||||
*/
|
||||
@@ -141,7 +150,6 @@ export class SASViyaApiClient {
|
||||
linesOfCode,
|
||||
context.name,
|
||||
accessToken,
|
||||
false,
|
||||
null,
|
||||
true
|
||||
).catch(() => null)
|
||||
@@ -405,7 +413,6 @@ export class SASViyaApiClient {
|
||||
* @param contextName - the context to execute the code in.
|
||||
* @param accessToken - an access token for an authorized user.
|
||||
* @param sessionId - optional session ID to reuse.
|
||||
* @param silent - optional flag to disable logging.
|
||||
* @param data - execution data.
|
||||
* @param debug - when set to true, the log will be returned.
|
||||
* @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).
|
||||
@@ -415,12 +422,9 @@ export class SASViyaApiClient {
|
||||
linesOfCode: string[],
|
||||
contextName: string,
|
||||
accessToken?: string,
|
||||
silent = false,
|
||||
data = null,
|
||||
debug = false,
|
||||
expectWebout = false
|
||||
): Promise<any> {
|
||||
silent = !debug
|
||||
try {
|
||||
const headers: any = {
|
||||
'Content-Type': 'application/json'
|
||||
@@ -443,7 +447,7 @@ export class SASViyaApiClient {
|
||||
_OMITTEXTLOG: true
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
if (this.debug) {
|
||||
jobArguments['_OMITTEXTLOG'] = false
|
||||
jobArguments['_OMITSESSIONRESULTS'] = false
|
||||
jobArguments['_DEBUG'] = 131
|
||||
@@ -503,7 +507,7 @@ export class SASViyaApiClient {
|
||||
postJobRequest
|
||||
)
|
||||
|
||||
if (!silent) {
|
||||
if (this.debug) {
|
||||
console.log(`Job has been submitted for '${fileName}'.`)
|
||||
console.log(
|
||||
`You can monitor the job progress at '${this.serverUrl}${
|
||||
@@ -512,12 +516,7 @@ export class SASViyaApiClient {
|
||||
)
|
||||
}
|
||||
|
||||
const jobStatus = await this.pollJobState(
|
||||
postedJob,
|
||||
etag,
|
||||
accessToken,
|
||||
silent
|
||||
)
|
||||
const jobStatus = await this.pollJobState(postedJob, etag, accessToken)
|
||||
|
||||
const { result: currentJob } = await this.request<Job>(
|
||||
`${this.serverUrl}/compute/sessions/${executionSessionId}/jobs/${postedJob.id}`,
|
||||
@@ -529,7 +528,7 @@ export class SASViyaApiClient {
|
||||
|
||||
const logLink = currentJob.links.find((l) => l.rel === 'log')
|
||||
|
||||
if (debug && logLink) {
|
||||
if (this.debug && logLink) {
|
||||
log = await this.request<any>(
|
||||
`${this.serverUrl}${logLink.href}/content?limit=10000`,
|
||||
{
|
||||
@@ -591,9 +590,7 @@ export class SASViyaApiClient {
|
||||
linesOfCode,
|
||||
contextName,
|
||||
accessToken,
|
||||
silent,
|
||||
data,
|
||||
debug
|
||||
data
|
||||
)
|
||||
} else {
|
||||
throw e
|
||||
@@ -625,8 +622,6 @@ export class SASViyaApiClient {
|
||||
if (!parentFolderUri && parentFolderPath) {
|
||||
parentFolderUri = await this.getFolderUri(parentFolderPath, accessToken)
|
||||
if (!parentFolderUri) {
|
||||
if (isForced) this.isForceDeploy = true
|
||||
|
||||
console.log(
|
||||
`Parent folder at path '${parentFolderPath}' is not present.`
|
||||
)
|
||||
@@ -652,37 +647,16 @@ export class SASViyaApiClient {
|
||||
`Parent folder '${newFolderName}' has been successfully created.`
|
||||
)
|
||||
parentFolderUri = `/folders/folders/${parentFolder.id}`
|
||||
} else if (isForced && accessToken && !this.isForceDeploy) {
|
||||
this.isForceDeploy = true
|
||||
} else if (isForced && accessToken) {
|
||||
const folderPath = parentFolderPath + '/' + folderName
|
||||
const folderUri = await this.getFolderUri(folderPath, accessToken)
|
||||
|
||||
await this.deleteFolder(parentFolderPath, accessToken)
|
||||
|
||||
const newParentFolderPath = parentFolderPath.substring(
|
||||
0,
|
||||
parentFolderPath.lastIndexOf('/')
|
||||
)
|
||||
const newFolderName = `${parentFolderPath.split('/').pop()}`
|
||||
|
||||
if (newParentFolderPath === '') {
|
||||
throw new Error(`Root folder has to be present on the server.`)
|
||||
if (folderUri) {
|
||||
await this.deleteFolder(
|
||||
parentFolderPath + '/' + folderName,
|
||||
accessToken
|
||||
)
|
||||
}
|
||||
|
||||
console.log(
|
||||
`Creating parent folder:\n'${newFolderName}' in '${newParentFolderPath}'`
|
||||
)
|
||||
|
||||
const parentFolder = await this.createFolder(
|
||||
newFolderName,
|
||||
newParentFolderPath,
|
||||
undefined,
|
||||
accessToken
|
||||
)
|
||||
|
||||
console.log(
|
||||
`Parent folder '${newFolderName}' has been successfully created.`
|
||||
)
|
||||
|
||||
parentFolderUri = `/folders/folders/${parentFolder.id}`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1015,9 +989,7 @@ export class SASViyaApiClient {
|
||||
linesToExecute,
|
||||
contextName,
|
||||
accessToken,
|
||||
true,
|
||||
data,
|
||||
debug,
|
||||
true
|
||||
)
|
||||
}
|
||||
@@ -1149,12 +1121,7 @@ export class SASViyaApiClient {
|
||||
`${this.serverUrl}/jobExecution/jobs?_action=wait`,
|
||||
postJobRequest
|
||||
)
|
||||
const jobStatus = await this.pollJobState(
|
||||
postedJob,
|
||||
etag,
|
||||
accessToken,
|
||||
true
|
||||
)
|
||||
const jobStatus = await this.pollJobState(postedJob, etag, accessToken)
|
||||
const { result: currentJob } = await this.request<Job>(
|
||||
`${this.serverUrl}/jobExecution/jobs/${postedJob.id}`,
|
||||
{ headers }
|
||||
@@ -1219,8 +1186,7 @@ export class SASViyaApiClient {
|
||||
private async pollJobState(
|
||||
postedJob: any,
|
||||
etag: string | null,
|
||||
accessToken?: string,
|
||||
silent = false
|
||||
accessToken?: string
|
||||
) {
|
||||
const MAX_POLL_COUNT = 1000
|
||||
const POLL_INTERVAL = 100
|
||||
@@ -1259,7 +1225,7 @@ export class SASViyaApiClient {
|
||||
postedJobState === 'pending'
|
||||
) {
|
||||
if (stateLink) {
|
||||
if (!silent) {
|
||||
if (this.debug) {
|
||||
console.log('Polling job status... \n')
|
||||
}
|
||||
const { result: jobState } = await this.request<string>(
|
||||
@@ -1271,7 +1237,7 @@ export class SASViyaApiClient {
|
||||
)
|
||||
|
||||
postedJobState = jobState.trim()
|
||||
if (!silent) {
|
||||
if (this.debug) {
|
||||
console.log(`Current state: ${postedJobState}\n`)
|
||||
}
|
||||
pollCount++
|
||||
@@ -1287,49 +1253,6 @@ export class SASViyaApiClient {
|
||||
})
|
||||
}
|
||||
|
||||
private async waitForSession(
|
||||
session: Session,
|
||||
etag: string | null,
|
||||
accessToken?: string,
|
||||
silent = false
|
||||
) {
|
||||
let sessionState = session.state
|
||||
let pollCount = 0
|
||||
const headers: any = {
|
||||
'Content-Type': 'application/json',
|
||||
'If-None-Match': etag
|
||||
}
|
||||
if (accessToken) {
|
||||
headers.Authorization = `Bearer ${accessToken}`
|
||||
}
|
||||
const stateLink = session.links.find((l: any) => l.rel === 'state')
|
||||
return new Promise(async (resolve, _) => {
|
||||
if (sessionState === 'pending') {
|
||||
if (stateLink) {
|
||||
if (!silent) {
|
||||
console.log('Polling session status... \n')
|
||||
}
|
||||
const { result: state } = await this.request<string>(
|
||||
`${this.serverUrl}${stateLink.href}?wait=30`,
|
||||
{
|
||||
headers
|
||||
},
|
||||
'text'
|
||||
)
|
||||
|
||||
sessionState = state.trim()
|
||||
if (!silent) {
|
||||
console.log(`Current state: ${sessionState}\n`)
|
||||
}
|
||||
pollCount++
|
||||
resolve(sessionState)
|
||||
}
|
||||
} else {
|
||||
resolve(sessionState)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private async uploadTables(data: any, accessToken?: string) {
|
||||
const uploadedFiles = []
|
||||
const headers: any = {
|
||||
@@ -1508,6 +1431,16 @@ export class SASViyaApiClient {
|
||||
`${this.serverUrl}${url}`,
|
||||
requestInfo
|
||||
).catch((err) => {
|
||||
if (err.code && err.code === 'ENOTFOUND') {
|
||||
const notFoundError = {
|
||||
body: JSON.stringify({
|
||||
message: `Folder '${sourceFolder.split('/').pop()}' was not found.`
|
||||
})
|
||||
}
|
||||
|
||||
throw notFoundError
|
||||
}
|
||||
|
||||
throw err
|
||||
})
|
||||
|
||||
|
||||
50
src/SASjs.ts
50
src/SASjs.ts
@@ -209,9 +209,7 @@ export default class SASjs {
|
||||
fileName: string,
|
||||
linesOfCode: string[],
|
||||
contextName: string,
|
||||
accessToken?: string,
|
||||
sessionId = '',
|
||||
silent = false
|
||||
accessToken?: string
|
||||
) {
|
||||
this.isMethodSupported('executeScriptSASViya', ServerType.SASViya)
|
||||
|
||||
@@ -220,9 +218,7 @@ export default class SASjs {
|
||||
linesOfCode,
|
||||
contextName,
|
||||
accessToken,
|
||||
silent,
|
||||
null,
|
||||
this.sasjsConfig.debug
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
@@ -243,8 +239,6 @@ export default class SASjs {
|
||||
sasApiClient?: SASViyaApiClient,
|
||||
isForced?: boolean
|
||||
) {
|
||||
this.isMethodSupported('createFolder', ServerType.SASViya)
|
||||
|
||||
if (sasApiClient)
|
||||
return await sasApiClient.createFolder(
|
||||
folderName,
|
||||
@@ -261,6 +255,40 @@ export default class SASjs {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* For performance (and in case of accidental error) the `deleteFolder` function does not actually delete the folder (and all its content and subfolder content). Instead the folder is simply moved to the recycle bin. Deletion time will be added to the folder name.
|
||||
* @param folderPath - the full path (eg `/Public/example/deleteThis`) of the folder to be deleted.
|
||||
* @param accessToken - an access token for authorizing the request.
|
||||
*/
|
||||
public async deleteFolder(folderPath: string, accessToken: string) {
|
||||
this.isMethodSupported('deleteFolder', ServerType.SASViya)
|
||||
|
||||
return await this.sasViyaApiClient?.deleteFolder(folderPath, accessToken)
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves folder to a new location. The folder may be renamed at the same time.
|
||||
* @param sourceFolder - the full path (eg `/Public/example/myFolder`) or URI of the source folder to be moved. Providing URI instead of path will save one extra request.
|
||||
* @param targetParentFolder - the full path or URI of the _parent_ folder to which the `sourceFolder` will be moved (eg `/Public/newDestination`). To move a folder, a user has to have write permissions in targetParentFolder. Providing URI instead of path will save one extra request.
|
||||
* @param targetFolderName - the name of the "moved" folder. If left blank, the original folder name will be used (eg `myFolder` in `/Public/newDestination/myFolder` for the example above). Optional field.
|
||||
* @param accessToken - an access token for authorizing the request.
|
||||
*/
|
||||
public async moveFolder(
|
||||
sourceFolder: string,
|
||||
targetParentFolder: string,
|
||||
targetFolderName: string,
|
||||
accessToken: string
|
||||
) {
|
||||
this.isMethodSupported('moveFolder', ServerType.SASViya)
|
||||
|
||||
return await this.sasViyaApiClient?.moveFolder(
|
||||
sourceFolder,
|
||||
targetParentFolder,
|
||||
targetFolderName,
|
||||
accessToken
|
||||
)
|
||||
}
|
||||
|
||||
public async createJobDefinition(
|
||||
jobName: string,
|
||||
code: string,
|
||||
@@ -378,6 +406,9 @@ export default class SASjs {
|
||||
*/
|
||||
public setDebugState(value: boolean) {
|
||||
this.sasjsConfig.debug = value
|
||||
if (this.sasViyaApiClient) {
|
||||
this.sasViyaApiClient.debug = value
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -603,6 +634,7 @@ export default class SASjs {
|
||||
this.sasjsConfig.contextName,
|
||||
this.setCsrfTokenApi
|
||||
)
|
||||
sasApiClient.debug = this.sasjsConfig.debug
|
||||
} else if (this.sasjsConfig.serverType === ServerType.SAS9) {
|
||||
sasApiClient = new SAS9ApiClient(serverUrl)
|
||||
}
|
||||
@@ -1320,6 +1352,8 @@ export default class SASjs {
|
||||
this.sasjsConfig.contextName,
|
||||
this.setCsrfTokenApi
|
||||
)
|
||||
|
||||
this.sasViyaApiClient.debug = this.sasjsConfig.debug
|
||||
}
|
||||
if (this.sasjsConfig.serverType === ServerType.SAS9) {
|
||||
if (this.sas9ApiClient)
|
||||
|
||||
@@ -15,6 +15,15 @@ export class SessionManager {
|
||||
private sessions: Session[] = []
|
||||
private currentContext: Context | null = null
|
||||
private csrfToken: CsrfToken | null = null
|
||||
private _debug: boolean = false
|
||||
|
||||
public get debug() {
|
||||
return this._debug
|
||||
}
|
||||
|
||||
public set debug(value: boolean) {
|
||||
this._debug = value
|
||||
}
|
||||
|
||||
async getSession(accessToken?: string) {
|
||||
await this.createSessions(accessToken)
|
||||
@@ -115,8 +124,7 @@ export class SessionManager {
|
||||
private async waitForSession(
|
||||
session: Session,
|
||||
etag: string | null,
|
||||
accessToken?: string,
|
||||
silent = false
|
||||
accessToken?: string
|
||||
) {
|
||||
let sessionState = session.state
|
||||
const headers: any = {
|
||||
@@ -127,7 +135,7 @@ export class SessionManager {
|
||||
return new Promise(async (resolve, _) => {
|
||||
if (sessionState === 'pending') {
|
||||
if (stateLink) {
|
||||
if (!silent) {
|
||||
if (this.debug) {
|
||||
console.log('Polling session status... \n') // ?
|
||||
}
|
||||
const { result: state } = await this.request<string>(
|
||||
@@ -139,7 +147,7 @@ export class SessionManager {
|
||||
)
|
||||
|
||||
sessionState = state.trim()
|
||||
if (!silent) {
|
||||
if (this.debug) {
|
||||
console.log(`Current state is '${sessionState}'\n`)
|
||||
}
|
||||
resolve(sessionState)
|
||||
|
||||
@@ -48,7 +48,7 @@ export async function makeRequest<T>(
|
||||
try {
|
||||
body = JSON.parse(body)
|
||||
|
||||
body.message = `Forbidden. Check your permissions and user groups. ${
|
||||
body.message = `Forbidden. Check your permissions and user groups, and also the scopes granted when registering your CLIENT_ID. ${
|
||||
body.message || ''
|
||||
}`
|
||||
|
||||
|
||||
Reference in New Issue
Block a user