1
0
mirror of https://github.com/sasjs/adapter.git synced 2026-01-04 03:00:05 +00:00

Compare commits

...

12 Commits

Author SHA1 Message Date
Krishna Acondy
bd8012fe3e fix(*): revert to older version of isomorphic-fetch 2020-10-03 18:19:06 +01:00
Krishna Acondy
fa531b34fd Merge pull request #120 from sasjs/session-manager-debug
fix(debug): propagate debug value from SASjs config
2020-10-03 17:41:35 +01:00
Krishna Acondy
354443c98b fix(debug): propagate debug value from SASjs config 2020-10-03 16:53:00 +01:00
Krishna Acondy
ee30ab195f Merge pull request #115 from sasjs/issue-114
chore(error-message): updated error message for forbidden request
2020-10-01 09:10:35 +01:00
Yury Shkoda
02c1712d22 chore(error-message): updated error message for forbidden request 2020-10-01 09:49:24 +03:00
Krishna Acondy
37def7a956 Merge pull request #111 from sasjs/dependabot/npm_and_yarn/isomorphic-fetch-3.0.0
chore(deps): bump isomorphic-fetch from 2.2.1 to 3.0.0
2020-09-29 20:03:06 +01:00
Krishna Acondy
653e3d05e0 Merge branch 'master' into dependabot/npm_and_yarn/isomorphic-fetch-3.0.0 2020-09-29 19:55:08 +01:00
Yury Shkoda
d8467c24b1 Merge pull request #112 from sasjs/cli-issue-73
feat(folder-management): made folder related methods public
2020-09-28 15:16:33 +03:00
Yury Shkoda
fc9056c1ac chore(folder-management): made 'moveFolder' method public and fixed 'createFolder' method 2020-09-28 14:59:27 +03:00
Yury Shkoda
9b1d295b82 feat(folder): made 'deleteFolder' method public 2020-09-26 11:41:18 +03:00
dependabot-preview[bot]
e2ea3f4ddc chore(deps): bump isomorphic-fetch from 2.2.1 to 3.0.0
Bumps [isomorphic-fetch](https://github.com/matthew-andrews/isomorphic-fetch) from 2.2.1 to 3.0.0.
- [Release notes](https://github.com/matthew-andrews/isomorphic-fetch/releases)
- [Commits](https://github.com/matthew-andrews/isomorphic-fetch/compare/v2.2.1...v3.0.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-09-25 23:51:17 +00:00
Allan Bowe
99d0b01a24 Update example.html 2020-09-24 23:00:37 +02:00
6 changed files with 112 additions and 126 deletions

View File

@@ -30,8 +30,8 @@
$('#chart-container').append('<canvas id="myChart" style="display: none;"></canvas>') $('#chart-container').append('<canvas id="myChart" style="display: none;"></canvas>')
// make a request to a SAS service // make a request to a SAS service
var type = $("#cars")[0].options[$("#cars")[0].selectedIndex].value; var type = $("#cars")[0].options[$("#cars")[0].selectedIndex].value;
// request data from an endpoint under your appLoc // request data from an endpoint under your appLoc (missing opening slash implies relative path)
sasJs.request("/common/getdata", { sasJs.request("common/getdata", {
// send data as an array of objects - each object is one row // send data as an array of objects - each object is one row
fromjs: [{ type: type }] fromjs: [{ type: type }]
}).then((response) => { }).then((response) => {

25
package-lock.json generated
View File

@@ -3704,11 +3704,21 @@
"dev": true "dev": true
}, },
"encoding": { "encoding": {
"version": "0.1.12", "version": "0.1.13",
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
"integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
"requires": { "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": { "end-of-stream": {
@@ -4967,6 +4977,7 @@
"version": "0.4.24", "version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
"dev": true,
"requires": { "requires": {
"safer-buffer": ">= 2.1.2 < 3" "safer-buffer": ">= 2.1.2 < 3"
} }
@@ -16070,9 +16081,9 @@
} }
}, },
"whatwg-fetch": { "whatwg-fetch": {
"version": "3.0.0", "version": "3.4.1",
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz", "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.4.1.tgz",
"integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==" "integrity": "sha512-sofZVzE1wKwO+EYPbWfiwzaKovWiZXf4coEzjGP9b2GBVgQRLQUZ2QcuPpQExGDAW5GItpEm6Tl4OU5mywnAoQ=="
}, },
"whatwg-mimetype": { "whatwg-mimetype": {
"version": "2.3.0", "version": "2.3.0",

View File

@@ -38,14 +38,23 @@ export class SASViyaApiClient {
private csrfToken: CsrfToken | null = null private csrfToken: CsrfToken | null = null
private fileUploadCsrfToken: CsrfToken | null = null private fileUploadCsrfToken: CsrfToken | null = null
private _debug = false
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 folderMap = new Map<string, Job[]>() 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. * Returns a list of jobs in the currently set root folder.
*/ */
@@ -141,7 +150,6 @@ export class SASViyaApiClient {
linesOfCode, linesOfCode,
context.name, context.name,
accessToken, accessToken,
false,
null, null,
true true
).catch(() => null) ).catch(() => null)
@@ -405,7 +413,6 @@ export class SASViyaApiClient {
* @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.
* @param sessionId - optional session ID to reuse. * @param sessionId - optional session ID to reuse.
* @param silent - optional flag to disable logging.
* @param data - execution data. * @param data - execution data.
* @param debug - when set to true, the log will be returned. * @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). * @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[], linesOfCode: string[],
contextName: string, contextName: string,
accessToken?: string, accessToken?: string,
silent = false,
data = null, data = null,
debug = false,
expectWebout = false expectWebout = false
): Promise<any> { ): Promise<any> {
silent = !debug
try { try {
const headers: any = { const headers: any = {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
@@ -443,7 +447,7 @@ export class SASViyaApiClient {
_OMITTEXTLOG: true _OMITTEXTLOG: true
} }
if (debug) { if (this.debug) {
jobArguments['_OMITTEXTLOG'] = false jobArguments['_OMITTEXTLOG'] = false
jobArguments['_OMITSESSIONRESULTS'] = false jobArguments['_OMITSESSIONRESULTS'] = false
jobArguments['_DEBUG'] = 131 jobArguments['_DEBUG'] = 131
@@ -503,7 +507,7 @@ export class SASViyaApiClient {
postJobRequest postJobRequest
) )
if (!silent) { if (this.debug) {
console.log(`Job has been submitted for '${fileName}'.`) console.log(`Job has been submitted for '${fileName}'.`)
console.log( console.log(
`You can monitor the job progress at '${this.serverUrl}${ `You can monitor the job progress at '${this.serverUrl}${
@@ -512,12 +516,7 @@ export class SASViyaApiClient {
) )
} }
const jobStatus = await this.pollJobState( const jobStatus = await this.pollJobState(postedJob, etag, accessToken)
postedJob,
etag,
accessToken,
silent
)
const { result: currentJob } = await this.request<Job>( const { result: currentJob } = await this.request<Job>(
`${this.serverUrl}/compute/sessions/${executionSessionId}/jobs/${postedJob.id}`, `${this.serverUrl}/compute/sessions/${executionSessionId}/jobs/${postedJob.id}`,
@@ -529,7 +528,7 @@ export class SASViyaApiClient {
const logLink = currentJob.links.find((l) => l.rel === 'log') const logLink = currentJob.links.find((l) => l.rel === 'log')
if (debug && logLink) { if (this.debug && logLink) {
log = await this.request<any>( log = await this.request<any>(
`${this.serverUrl}${logLink.href}/content?limit=10000`, `${this.serverUrl}${logLink.href}/content?limit=10000`,
{ {
@@ -591,9 +590,7 @@ export class SASViyaApiClient {
linesOfCode, linesOfCode,
contextName, contextName,
accessToken, accessToken,
silent, data
data,
debug
) )
} else { } else {
throw e throw e
@@ -625,8 +622,6 @@ export class SASViyaApiClient {
if (!parentFolderUri && parentFolderPath) { if (!parentFolderUri && parentFolderPath) {
parentFolderUri = await this.getFolderUri(parentFolderPath, accessToken) parentFolderUri = await this.getFolderUri(parentFolderPath, accessToken)
if (!parentFolderUri) { if (!parentFolderUri) {
if (isForced) this.isForceDeploy = true
console.log( console.log(
`Parent folder at path '${parentFolderPath}' is not present.` `Parent folder at path '${parentFolderPath}' is not present.`
) )
@@ -652,37 +647,16 @@ export class SASViyaApiClient {
`Parent folder '${newFolderName}' has been successfully created.` `Parent folder '${newFolderName}' has been successfully created.`
) )
parentFolderUri = `/folders/folders/${parentFolder.id}` parentFolderUri = `/folders/folders/${parentFolder.id}`
} else if (isForced && accessToken && !this.isForceDeploy) { } else if (isForced && accessToken) {
this.isForceDeploy = true const folderPath = parentFolderPath + '/' + folderName
const folderUri = await this.getFolderUri(folderPath, accessToken)
await this.deleteFolder(parentFolderPath, accessToken) if (folderUri) {
await this.deleteFolder(
const newParentFolderPath = parentFolderPath.substring( parentFolderPath + '/' + folderName,
0, accessToken
parentFolderPath.lastIndexOf('/') )
)
const newFolderName = `${parentFolderPath.split('/').pop()}`
if (newParentFolderPath === '') {
throw new Error(`Root folder has to be present on the server.`)
} }
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, linesToExecute,
contextName, contextName,
accessToken, accessToken,
true,
data, data,
debug,
true true
) )
} }
@@ -1149,12 +1121,7 @@ export class SASViyaApiClient {
`${this.serverUrl}/jobExecution/jobs?_action=wait`, `${this.serverUrl}/jobExecution/jobs?_action=wait`,
postJobRequest postJobRequest
) )
const jobStatus = await this.pollJobState( const jobStatus = await this.pollJobState(postedJob, etag, accessToken)
postedJob,
etag,
accessToken,
true
)
const { result: currentJob } = await this.request<Job>( const { result: currentJob } = await this.request<Job>(
`${this.serverUrl}/jobExecution/jobs/${postedJob.id}`, `${this.serverUrl}/jobExecution/jobs/${postedJob.id}`,
{ headers } { headers }
@@ -1219,8 +1186,7 @@ export class SASViyaApiClient {
private async pollJobState( private async pollJobState(
postedJob: any, postedJob: any,
etag: string | null, etag: string | null,
accessToken?: string, accessToken?: string
silent = false
) { ) {
const MAX_POLL_COUNT = 1000 const MAX_POLL_COUNT = 1000
const POLL_INTERVAL = 100 const POLL_INTERVAL = 100
@@ -1259,7 +1225,7 @@ export class SASViyaApiClient {
postedJobState === 'pending' postedJobState === 'pending'
) { ) {
if (stateLink) { if (stateLink) {
if (!silent) { if (this.debug) {
console.log('Polling job status... \n') console.log('Polling job status... \n')
} }
const { result: jobState } = await this.request<string>( const { result: jobState } = await this.request<string>(
@@ -1271,7 +1237,7 @@ export class SASViyaApiClient {
) )
postedJobState = jobState.trim() postedJobState = jobState.trim()
if (!silent) { if (this.debug) {
console.log(`Current state: ${postedJobState}\n`) console.log(`Current state: ${postedJobState}\n`)
} }
pollCount++ 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) { private async uploadTables(data: any, accessToken?: string) {
const uploadedFiles = [] const uploadedFiles = []
const headers: any = { const headers: any = {
@@ -1508,6 +1431,16 @@ export class SASViyaApiClient {
`${this.serverUrl}${url}`, `${this.serverUrl}${url}`,
requestInfo requestInfo
).catch((err) => { ).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 throw err
}) })

View File

@@ -209,9 +209,7 @@ export default class SASjs {
fileName: string, fileName: string,
linesOfCode: string[], linesOfCode: string[],
contextName: string, contextName: string,
accessToken?: string, accessToken?: string
sessionId = '',
silent = false
) { ) {
this.isMethodSupported('executeScriptSASViya', ServerType.SASViya) this.isMethodSupported('executeScriptSASViya', ServerType.SASViya)
@@ -220,9 +218,7 @@ export default class SASjs {
linesOfCode, linesOfCode,
contextName, contextName,
accessToken, accessToken,
silent, null
null,
this.sasjsConfig.debug
) )
} }
@@ -243,8 +239,6 @@ export default class SASjs {
sasApiClient?: SASViyaApiClient, sasApiClient?: SASViyaApiClient,
isForced?: boolean isForced?: boolean
) { ) {
this.isMethodSupported('createFolder', ServerType.SASViya)
if (sasApiClient) if (sasApiClient)
return await sasApiClient.createFolder( return await sasApiClient.createFolder(
folderName, 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( public async createJobDefinition(
jobName: string, jobName: string,
code: string, code: string,
@@ -378,6 +406,9 @@ export default class SASjs {
*/ */
public setDebugState(value: boolean) { public setDebugState(value: boolean) {
this.sasjsConfig.debug = value this.sasjsConfig.debug = value
if (this.sasViyaApiClient) {
this.sasViyaApiClient.debug = value
}
} }
/** /**
@@ -603,6 +634,7 @@ export default class SASjs {
this.sasjsConfig.contextName, this.sasjsConfig.contextName,
this.setCsrfTokenApi this.setCsrfTokenApi
) )
sasApiClient.debug = this.sasjsConfig.debug
} else if (this.sasjsConfig.serverType === ServerType.SAS9) { } else if (this.sasjsConfig.serverType === ServerType.SAS9) {
sasApiClient = new SAS9ApiClient(serverUrl) sasApiClient = new SAS9ApiClient(serverUrl)
} }
@@ -1320,6 +1352,8 @@ export default class SASjs {
this.sasjsConfig.contextName, this.sasjsConfig.contextName,
this.setCsrfTokenApi this.setCsrfTokenApi
) )
this.sasViyaApiClient.debug = this.sasjsConfig.debug
} }
if (this.sasjsConfig.serverType === ServerType.SAS9) { if (this.sasjsConfig.serverType === ServerType.SAS9) {
if (this.sas9ApiClient) if (this.sas9ApiClient)

View File

@@ -15,6 +15,15 @@ export class SessionManager {
private sessions: Session[] = [] private sessions: Session[] = []
private currentContext: Context | null = null private currentContext: Context | null = null
private csrfToken: CsrfToken | 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) { async getSession(accessToken?: string) {
await this.createSessions(accessToken) await this.createSessions(accessToken)
@@ -115,8 +124,7 @@ export class SessionManager {
private async waitForSession( private async waitForSession(
session: Session, session: Session,
etag: string | null, etag: string | null,
accessToken?: string, accessToken?: string
silent = false
) { ) {
let sessionState = session.state let sessionState = session.state
const headers: any = { const headers: any = {
@@ -127,7 +135,7 @@ export class SessionManager {
return new Promise(async (resolve, _) => { return new Promise(async (resolve, _) => {
if (sessionState === 'pending') { if (sessionState === 'pending') {
if (stateLink) { if (stateLink) {
if (!silent) { if (this.debug) {
console.log('Polling session status... \n') // ? console.log('Polling session status... \n') // ?
} }
const { result: state } = await this.request<string>( const { result: state } = await this.request<string>(
@@ -139,7 +147,7 @@ export class SessionManager {
) )
sessionState = state.trim() sessionState = state.trim()
if (!silent) { if (this.debug) {
console.log(`Current state is '${sessionState}'\n`) console.log(`Current state is '${sessionState}'\n`)
} }
resolve(sessionState) resolve(sessionState)

View File

@@ -48,7 +48,7 @@ export async function makeRequest<T>(
try { try {
body = JSON.parse(body) 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 || '' body.message || ''
}` }`