mirror of
https://github.com/sasjs/adapter.git
synced 2026-01-03 10:40:06 +00:00
Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
40de5f2e11 | ||
|
|
6056424d26 | ||
|
|
ae5110974f | ||
|
|
b2f6d4e6d1 | ||
|
|
f69a635e1d | ||
|
|
c8bc6936e8 | ||
|
|
9a24a8b962 | ||
|
|
c43c9ec211 | ||
|
|
9b0e02f5b7 | ||
|
|
3ec6ee2db9 | ||
|
|
e6ab5f918f | ||
|
|
951e119c08 | ||
|
|
d22d9e1039 | ||
|
|
6e276e2e26 | ||
|
|
eec973efa1 | ||
|
|
0f9eca7482 | ||
|
|
2c763e38ae | ||
|
|
4957bc5b05 | ||
|
|
f6b1eecb42 | ||
|
|
7ae2a4d2c6 | ||
|
|
f86d20b723 | ||
|
|
7a1cce193e | ||
|
|
05539fff11 | ||
|
|
58d69a62d6 | ||
|
|
c9c9754916 | ||
|
|
e056ca21fe | ||
|
|
0a77ebf5c5 | ||
|
|
12835893b1 | ||
|
|
df86b2e700 | ||
|
|
5fce25d58a | ||
|
|
7ee9335183 | ||
|
|
07695bdb85 | ||
|
|
26c8946fd5 | ||
|
|
fc1d54d105 | ||
|
|
a318d61f83 | ||
|
|
cc5a0cbec3 | ||
|
|
d932d9ea0a | ||
|
|
e3edace882 | ||
|
|
29d9df5792 |
File diff suppressed because one or more lines are too long
223
docs/classes/reflection-640.reflection-158.fileuploader.html
Normal file
223
docs/classes/reflection-640.reflection-158.fileuploader.html
Normal file
File diff suppressed because one or more lines are too long
296
docs/classes/reflection-640.reflection-158.sas9apiclient.html
Normal file
296
docs/classes/reflection-640.reflection-158.sas9apiclient.html
Normal file
File diff suppressed because one or more lines are too long
1038
docs/classes/reflection-640.reflection-158.sasjs.html
Normal file
1038
docs/classes/reflection-640.reflection-158.sasjs.html
Normal file
File diff suppressed because one or more lines are too long
1006
docs/classes/reflection-640.reflection-158.sasviyaapiclient.html
Normal file
1006
docs/classes/reflection-640.reflection-158.sasviyaapiclient.html
Normal file
File diff suppressed because one or more lines are too long
259
docs/classes/reflection-640.reflection-158.sessionmanager.html
Normal file
259
docs/classes/reflection-640.reflection-158.sessionmanager.html
Normal file
File diff suppressed because one or more lines are too long
223
docs/classes/reflection-641.reflection-158.fileuploader.html
Normal file
223
docs/classes/reflection-641.reflection-158.fileuploader.html
Normal file
File diff suppressed because one or more lines are too long
296
docs/classes/reflection-641.reflection-158.sas9apiclient.html
Normal file
296
docs/classes/reflection-641.reflection-158.sas9apiclient.html
Normal file
File diff suppressed because one or more lines are too long
1038
docs/classes/reflection-641.reflection-158.sasjs.html
Normal file
1038
docs/classes/reflection-641.reflection-158.sasjs.html
Normal file
File diff suppressed because one or more lines are too long
1012
docs/classes/reflection-641.reflection-158.sasviyaapiclient.html
Normal file
1012
docs/classes/reflection-641.reflection-158.sasviyaapiclient.html
Normal file
File diff suppressed because one or more lines are too long
259
docs/classes/reflection-641.reflection-158.sessionmanager.html
Normal file
259
docs/classes/reflection-641.reflection-158.sessionmanager.html
Normal file
File diff suppressed because one or more lines are too long
223
docs/classes/reflection-644.reflection-162.fileuploader.html
Normal file
223
docs/classes/reflection-644.reflection-162.fileuploader.html
Normal file
File diff suppressed because one or more lines are too long
296
docs/classes/reflection-644.reflection-162.sas9apiclient.html
Normal file
296
docs/classes/reflection-644.reflection-162.sas9apiclient.html
Normal file
File diff suppressed because one or more lines are too long
1038
docs/classes/reflection-644.reflection-162.sasjs.html
Normal file
1038
docs/classes/reflection-644.reflection-162.sasjs.html
Normal file
File diff suppressed because one or more lines are too long
1006
docs/classes/reflection-644.reflection-162.sasviyaapiclient.html
Normal file
1006
docs/classes/reflection-644.reflection-162.sasviyaapiclient.html
Normal file
File diff suppressed because one or more lines are too long
259
docs/classes/reflection-644.reflection-162.sessionmanager.html
Normal file
259
docs/classes/reflection-644.reflection-162.sessionmanager.html
Normal file
File diff suppressed because one or more lines are too long
223
docs/classes/reflection-648.reflection-166.fileuploader.html
Normal file
223
docs/classes/reflection-648.reflection-166.fileuploader.html
Normal file
File diff suppressed because one or more lines are too long
296
docs/classes/reflection-648.reflection-166.sas9apiclient.html
Normal file
296
docs/classes/reflection-648.reflection-166.sas9apiclient.html
Normal file
File diff suppressed because one or more lines are too long
1064
docs/classes/reflection-648.reflection-166.sasjs.html
Normal file
1064
docs/classes/reflection-648.reflection-166.sasjs.html
Normal file
File diff suppressed because one or more lines are too long
1006
docs/classes/reflection-648.reflection-166.sasviyaapiclient.html
Normal file
1006
docs/classes/reflection-648.reflection-166.sasviyaapiclient.html
Normal file
File diff suppressed because one or more lines are too long
259
docs/classes/reflection-648.reflection-166.sessionmanager.html
Normal file
259
docs/classes/reflection-648.reflection-166.sessionmanager.html
Normal file
File diff suppressed because one or more lines are too long
223
docs/classes/reflection-678.reflection-166.fileuploader.html
Normal file
223
docs/classes/reflection-678.reflection-166.fileuploader.html
Normal file
File diff suppressed because one or more lines are too long
296
docs/classes/reflection-678.reflection-166.sas9apiclient.html
Normal file
296
docs/classes/reflection-678.reflection-166.sas9apiclient.html
Normal file
File diff suppressed because one or more lines are too long
1170
docs/classes/reflection-678.reflection-166.sasjs.html
Normal file
1170
docs/classes/reflection-678.reflection-166.sasjs.html
Normal file
File diff suppressed because one or more lines are too long
1118
docs/classes/reflection-678.reflection-166.sasviyaapiclient.html
Normal file
1118
docs/classes/reflection-678.reflection-166.sasviyaapiclient.html
Normal file
File diff suppressed because one or more lines are too long
259
docs/classes/reflection-678.reflection-166.sessionmanager.html
Normal file
259
docs/classes/reflection-678.reflection-166.sessionmanager.html
Normal file
File diff suppressed because one or more lines are too long
224
docs/classes/reflection-706.reflection-178.fileuploader.html
Normal file
224
docs/classes/reflection-706.reflection-178.fileuploader.html
Normal file
File diff suppressed because one or more lines are too long
297
docs/classes/reflection-706.reflection-178.sas9apiclient.html
Normal file
297
docs/classes/reflection-706.reflection-178.sas9apiclient.html
Normal file
File diff suppressed because one or more lines are too long
1218
docs/classes/reflection-706.reflection-178.sasjs.html
Normal file
1218
docs/classes/reflection-706.reflection-178.sasjs.html
Normal file
File diff suppressed because one or more lines are too long
1163
docs/classes/reflection-706.reflection-178.sasviyaapiclient.html
Normal file
1163
docs/classes/reflection-706.reflection-178.sasviyaapiclient.html
Normal file
File diff suppressed because one or more lines are too long
260
docs/classes/reflection-706.reflection-178.sessionmanager.html
Normal file
260
docs/classes/reflection-706.reflection-178.sessionmanager.html
Normal file
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
252
docs/interfaces/types.editcontextinput.html
Normal file
252
docs/interfaces/types.editcontextinput.html
Normal file
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
106
docs/modules/reflection-640.html
Normal file
106
docs/modules/reflection-640.html
Normal file
File diff suppressed because one or more lines are too long
128
docs/modules/reflection-640.reflection-158.html
Normal file
128
docs/modules/reflection-640.reflection-158.html
Normal file
File diff suppressed because one or more lines are too long
106
docs/modules/reflection-641.html
Normal file
106
docs/modules/reflection-641.html
Normal file
File diff suppressed because one or more lines are too long
128
docs/modules/reflection-641.reflection-158.html
Normal file
128
docs/modules/reflection-641.reflection-158.html
Normal file
File diff suppressed because one or more lines are too long
106
docs/modules/reflection-644.html
Normal file
106
docs/modules/reflection-644.html
Normal file
File diff suppressed because one or more lines are too long
128
docs/modules/reflection-644.reflection-162.html
Normal file
128
docs/modules/reflection-644.reflection-162.html
Normal file
File diff suppressed because one or more lines are too long
106
docs/modules/reflection-648.html
Normal file
106
docs/modules/reflection-648.html
Normal file
File diff suppressed because one or more lines are too long
128
docs/modules/reflection-648.reflection-166.html
Normal file
128
docs/modules/reflection-648.reflection-166.html
Normal file
File diff suppressed because one or more lines are too long
106
docs/modules/reflection-678.html
Normal file
106
docs/modules/reflection-678.html
Normal file
File diff suppressed because one or more lines are too long
128
docs/modules/reflection-678.reflection-166.html
Normal file
128
docs/modules/reflection-678.reflection-166.html
Normal file
File diff suppressed because one or more lines are too long
107
docs/modules/reflection-706.html
Normal file
107
docs/modules/reflection-706.html
Normal file
File diff suppressed because one or more lines are too long
129
docs/modules/reflection-706.reflection-178.html
Normal file
129
docs/modules/reflection-706.reflection-178.html
Normal file
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
@@ -3,11 +3,19 @@ import {
|
||||
parseAndSubmitAuthorizeForm,
|
||||
convertToCSV,
|
||||
makeRequest,
|
||||
isUri,
|
||||
isUrl
|
||||
} from './utils'
|
||||
import * as NodeFormData from 'form-data'
|
||||
import * as path from 'path'
|
||||
import { Job, Session, Context, Folder, CsrfToken } from './types'
|
||||
import {
|
||||
Job,
|
||||
Session,
|
||||
Context,
|
||||
Folder,
|
||||
CsrfToken,
|
||||
EditContextInput
|
||||
} from './types'
|
||||
import { JobDefinition } from './types/JobDefinition'
|
||||
import { formatDataForRequest } from './utils/formatDataForRequest'
|
||||
import { SessionManager } from './SessionManager'
|
||||
@@ -38,6 +46,7 @@ export class SASViyaApiClient {
|
||||
this.contextName,
|
||||
this.setCsrfToken
|
||||
)
|
||||
private isForceDeploy: boolean = false
|
||||
|
||||
/**
|
||||
* Returns a map containing the directory structure in the currently set root folder.
|
||||
@@ -193,6 +202,169 @@ export class SASViyaApiClient {
|
||||
return createdSession
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a compute context on the given server.
|
||||
* @param contextName - the name of the context to be created.
|
||||
* @param launchContextName - the name of the launcher context used by the compute service.
|
||||
* @param sharedAccountId - the ID of the account to run the servers for this context as.
|
||||
* @param autoExecLines - the lines of code to execute during session initialization.
|
||||
* @param authorizedUsers - an optional list of authorized user IDs.
|
||||
* @param accessToken - an access token for an authorized user.
|
||||
*/
|
||||
public async createContext(
|
||||
contextName: string,
|
||||
launchContextName: string,
|
||||
sharedAccountId: string,
|
||||
autoExecLines: string[],
|
||||
authorizedUsers: string[],
|
||||
accessToken?: string
|
||||
) {
|
||||
if (!contextName) {
|
||||
throw new Error('Missing context name.')
|
||||
}
|
||||
|
||||
if (!launchContextName) {
|
||||
throw new Error('Missing launch context name.')
|
||||
}
|
||||
|
||||
if (!sharedAccountId) {
|
||||
throw new Error('Missing shared account ID.')
|
||||
}
|
||||
|
||||
const headers: any = {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
if (accessToken) {
|
||||
headers.Authorization = `Bearer ${accessToken}`
|
||||
}
|
||||
|
||||
const requestBody: any = {
|
||||
name: contextName,
|
||||
launchContext: {
|
||||
contextName: launchContextName
|
||||
},
|
||||
attributes: {
|
||||
reuseServerProcesses: true,
|
||||
runServerAs: sharedAccountId
|
||||
}
|
||||
}
|
||||
|
||||
if (authorizedUsers && authorizedUsers.length) {
|
||||
requestBody['authorizedUsers'] = authorizedUsers
|
||||
} else {
|
||||
requestBody['authorizeAllAuthenticatedUsers'] = true
|
||||
}
|
||||
|
||||
if (autoExecLines) {
|
||||
requestBody.environment = { autoExecLines }
|
||||
}
|
||||
|
||||
const createContextRequest: RequestInit = {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify(requestBody)
|
||||
}
|
||||
|
||||
const { result: context } = await this.request<Context>(
|
||||
`${this.serverUrl}/compute/contexts`,
|
||||
createContextRequest
|
||||
)
|
||||
|
||||
return context
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a compute context on the given server.
|
||||
* @param contextId - the ID of the context to be deleted.
|
||||
* @param editedContext - an object with the properties to be updated.
|
||||
* @param accessToken - an access token for an authorized user.
|
||||
*/
|
||||
public async editContext(
|
||||
contextId: string,
|
||||
updatedContext: EditContextInput,
|
||||
accessToken?: string
|
||||
) {
|
||||
if (!contextId) {
|
||||
throw new Error('Invalid context ID.')
|
||||
}
|
||||
|
||||
const headers: any = {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
if (accessToken) {
|
||||
headers.Authorization = `Bearer ${accessToken}`
|
||||
}
|
||||
|
||||
const { result: context, etag } = await this.request<Context>(
|
||||
`${this.serverUrl}/compute/contexts/${contextId}`,
|
||||
{
|
||||
headers
|
||||
}
|
||||
).catch((e) => {
|
||||
console.error(e)
|
||||
|
||||
if (e && e.status === 404) {
|
||||
throw new Error(
|
||||
`The context with ID ${contextId} was not found on this server.`
|
||||
)
|
||||
}
|
||||
throw new Error(
|
||||
`An error occurred when fetching the context with ID ${contextId}`
|
||||
)
|
||||
})
|
||||
|
||||
// An If-Match header with the value of the last ETag for the context
|
||||
// is required to be able to update it
|
||||
// https://developer.sas.com/apis/rest/Compute/#update-a-context-definition
|
||||
headers['If-Match'] = etag
|
||||
|
||||
const updateContextRequest: RequestInit = {
|
||||
method: 'PUT',
|
||||
headers,
|
||||
body: JSON.stringify({
|
||||
...context,
|
||||
...updatedContext,
|
||||
attributes: { ...context.attributes, ...updatedContext.attributes }
|
||||
})
|
||||
}
|
||||
|
||||
return await this.request<Context>(
|
||||
`${this.serverUrl}/compute/contexts/${contextId}`,
|
||||
updateContextRequest
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a compute context on the given server.
|
||||
* @param contextId - the ID of the context to be deleted.
|
||||
* @param accessToken - an access token for an authorized user.
|
||||
*/
|
||||
public async deleteContext(contextId: string, accessToken?: string) {
|
||||
if (!contextId) {
|
||||
throw new Error('Invalid context ID.')
|
||||
}
|
||||
|
||||
const headers: any = {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
if (accessToken) {
|
||||
headers.Authorization = `Bearer ${accessToken}`
|
||||
}
|
||||
|
||||
const deleteContextRequest: RequestInit = {
|
||||
method: 'DELETE',
|
||||
headers
|
||||
}
|
||||
|
||||
return await this.request<Context>(
|
||||
`${this.serverUrl}/compute/contexts/${contextId}`,
|
||||
deleteContextRequest
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes code on the current SAS Viya server.
|
||||
* @param fileName - a name for the file being submitted for execution.
|
||||
@@ -364,12 +536,15 @@ export class SASViyaApiClient {
|
||||
* provided, the parentFolderUri must be provided.
|
||||
* @param parentFolderUri - the URI (eg /folders/folders/UUID) of the parent
|
||||
* folder. If not provided, the parentFolderPath must be provided.
|
||||
* @param accessToken - an access token for authorizing the request.
|
||||
* @param isForced - flag that indicates if target folder already exists, it and all subfolders have to be deleted.
|
||||
*/
|
||||
public async createFolder(
|
||||
folderName: string,
|
||||
parentFolderPath?: string,
|
||||
parentFolderUri?: string,
|
||||
accessToken?: string
|
||||
accessToken?: string,
|
||||
isForced?: boolean
|
||||
): Promise<Folder> {
|
||||
if (!parentFolderPath && !parentFolderUri) {
|
||||
throw new Error('Parent folder path or uri is required')
|
||||
@@ -378,6 +553,8 @@ export class SASViyaApiClient {
|
||||
if (!parentFolderUri && parentFolderPath) {
|
||||
parentFolderUri = await this.getFolderUri(parentFolderPath, accessToken)
|
||||
if (!parentFolderUri) {
|
||||
if (isForced) this.isForceDeploy = true
|
||||
|
||||
console.log(`Parent folder is not present: ${parentFolderPath}`)
|
||||
|
||||
const newParentFolderPath = parentFolderPath.substring(
|
||||
@@ -398,6 +575,35 @@ export class SASViyaApiClient {
|
||||
accessToken
|
||||
)
|
||||
console.log(`Parent Folder "${newFolderName}" successfully created.`)
|
||||
parentFolderUri = `/folders/folders/${parentFolder.id}`
|
||||
} else if (isForced && accessToken && !this.isForceDeploy) {
|
||||
this.isForceDeploy = true
|
||||
|
||||
await this.deleteFolder(parentFolderPath, accessToken)
|
||||
|
||||
const newParentFolderPath = parentFolderPath.substring(
|
||||
0,
|
||||
parentFolderPath.lastIndexOf('/')
|
||||
)
|
||||
const newFolderName = `${parentFolderPath.split('/').pop()}`
|
||||
|
||||
if (newParentFolderPath === '') {
|
||||
throw new Error('Root Folder should have been present on server')
|
||||
}
|
||||
|
||||
console.log(
|
||||
`Creating Parent Folder:\n${newFolderName} in ${newParentFolderPath}`
|
||||
)
|
||||
|
||||
const parentFolder = await this.createFolder(
|
||||
newFolderName,
|
||||
newParentFolderPath,
|
||||
undefined,
|
||||
accessToken
|
||||
)
|
||||
|
||||
console.log(`Parent Folder "${newFolderName}" successfully created.`)
|
||||
|
||||
parentFolderUri = `/folders/folders/${parentFolder.id}`
|
||||
}
|
||||
}
|
||||
@@ -620,7 +826,7 @@ export class SASViyaApiClient {
|
||||
/**
|
||||
* Deletes the client representing the supplied ID.
|
||||
* @param clientId - the client ID to authenticate with.
|
||||
* @param accessToken - an access token for an authorized user.
|
||||
* @param accessToken - an access token for authorizing the request.
|
||||
*/
|
||||
public async deleteClient(clientId: string, accessToken?: string) {
|
||||
const url = this.serverUrl + `/oauth/clients/${clientId}`
|
||||
@@ -1089,6 +1295,101 @@ export class SASViyaApiClient {
|
||||
return `/folders/folders/${folder.id}`
|
||||
}
|
||||
|
||||
private async getRecycleBinUri(accessToken: string) {
|
||||
const url = '/folders/folders/@myRecycleBin'
|
||||
const requestInfo = {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: 'Bearer ' + accessToken
|
||||
}
|
||||
}
|
||||
|
||||
const { result: folder } = await this.request<Folder>(
|
||||
`${this.serverUrl}${url}`,
|
||||
requestInfo
|
||||
).catch((err) => {
|
||||
return { result: null }
|
||||
})
|
||||
|
||||
if (!folder) return undefined
|
||||
|
||||
return `/folders/folders/${folder.id}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves a Viya 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
|
||||
) {
|
||||
// checks if 'sourceFolder' is already a URI
|
||||
const sourceFolderUri = isUri(sourceFolder)
|
||||
? sourceFolder
|
||||
: await this.getFolderUri(sourceFolder, accessToken)
|
||||
|
||||
// checks if 'targetParentFolder' is already a URI
|
||||
const targetParentFolderUri = isUri(targetParentFolder)
|
||||
? targetParentFolder
|
||||
: await this.getFolderUri(targetParentFolder, accessToken)
|
||||
|
||||
const sourceFolderId = sourceFolderUri?.split('/').pop()
|
||||
const url = sourceFolderUri
|
||||
|
||||
const requestInfo = {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: 'Bearer ' + accessToken
|
||||
},
|
||||
body: JSON.stringify({
|
||||
id: sourceFolderId,
|
||||
name: targetFolderName,
|
||||
parentFolderUri: targetParentFolderUri
|
||||
})
|
||||
}
|
||||
|
||||
const { result: folder } = await this.request<Folder>(
|
||||
`${this.serverUrl}${url}`,
|
||||
requestInfo
|
||||
).catch((err) => {
|
||||
throw err
|
||||
})
|
||||
|
||||
if (!folder) return undefined
|
||||
|
||||
return folder
|
||||
}
|
||||
|
||||
/**
|
||||
* For performance (and in case of accidental error) the `deleteFolder` function does not actually delete the folder (and all it's 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) {
|
||||
const recycleBinUri = await this.getRecycleBinUri(accessToken)
|
||||
const folderName = folderPath.split('/').pop() || ''
|
||||
const date = new Date()
|
||||
const timeMark = date.toLocaleDateString() + ' ' + date.toLocaleTimeString()
|
||||
const deletedFolderName = folderName + ' ' + timeMark
|
||||
|
||||
const movedFolder = await this.moveFolder(
|
||||
folderPath,
|
||||
recycleBinUri!,
|
||||
deletedFolderName,
|
||||
accessToken
|
||||
)
|
||||
|
||||
return movedFolder
|
||||
}
|
||||
|
||||
setCsrfTokenLocal = (csrfToken: CsrfToken) => {
|
||||
this.csrfToken = csrfToken
|
||||
this.setCsrfToken(csrfToken)
|
||||
|
||||
97
src/SASjs.ts
97
src/SASjs.ts
@@ -29,7 +29,8 @@ import {
|
||||
SASjsWaitingRequest,
|
||||
ServerType,
|
||||
CsrfToken,
|
||||
UploadFile
|
||||
UploadFile,
|
||||
EditContextInput
|
||||
} from './types'
|
||||
import { SASViyaApiClient } from './SASViyaApiClient'
|
||||
import { SAS9ApiClient } from './SAS9ApiClient'
|
||||
@@ -107,6 +108,69 @@ export default class SASjs {
|
||||
return await this.sasViyaApiClient!.getExecutableContexts(accessToken)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a compute context on the given server.
|
||||
* @param contextName - the name of the context to be created.
|
||||
* @param launchContextName - the name of the launcher context used by the compute service.
|
||||
* @param sharedAccountId - the ID of the account to run the servers for this context as.
|
||||
* @param autoExecLines - the lines of code to execute during session initialization.
|
||||
* @param authorizedUsers - an optional list of authorized user IDs.
|
||||
* @param accessToken - an access token for an authorized user.
|
||||
*/
|
||||
public async createContext(
|
||||
contextName: string,
|
||||
launchContextName: string,
|
||||
sharedAccountId: string,
|
||||
autoExecLines: string[],
|
||||
authorizedUsers: string[],
|
||||
accessToken: string
|
||||
) {
|
||||
if (this.sasjsConfig.serverType !== ServerType.SASViya) {
|
||||
throw new Error('This operation is only supported on SAS Viya servers.')
|
||||
}
|
||||
return await this.sasViyaApiClient!.createContext(
|
||||
contextName,
|
||||
launchContextName,
|
||||
sharedAccountId,
|
||||
autoExecLines,
|
||||
authorizedUsers,
|
||||
accessToken
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a compute context on the given server.
|
||||
* @param contextId - the ID of the context to be deleted.
|
||||
* @param editedContext - an object with the properties to be updated.
|
||||
* @param accessToken - an access token for an authorized user.
|
||||
*/
|
||||
public async editContext(
|
||||
contextId: string,
|
||||
editedContext: EditContextInput,
|
||||
accessToken?: string
|
||||
) {
|
||||
if (this.sasjsConfig.serverType !== ServerType.SASViya) {
|
||||
throw new Error('This operation is only supported on SAS Viya servers.')
|
||||
}
|
||||
return await this.sasViyaApiClient!.editContext(
|
||||
contextId,
|
||||
editedContext,
|
||||
accessToken
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a compute context on the given server.
|
||||
* @param contextId - the ID of the context to be deleted.
|
||||
* @param accessToken - an access token for an authorized user.
|
||||
*/
|
||||
public async deleteContext(contextId: string, accessToken?: string) {
|
||||
if (this.sasjsConfig.serverType !== ServerType.SASViya) {
|
||||
throw new Error('This operation is only supported on SAS Viya servers.')
|
||||
}
|
||||
return await this.sasViyaApiClient!.deleteContext(contextId, accessToken)
|
||||
}
|
||||
|
||||
public async createSession(contextName: string, accessToken: string) {
|
||||
if (this.sasjsConfig.serverType !== ServerType.SASViya) {
|
||||
throw new Error('This operation is only supported on SAS Viya servers.')
|
||||
@@ -134,12 +198,22 @@ export default class SASjs {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a folder at SAS file system
|
||||
* @param folderName - name of the folder to be created.
|
||||
* @param parentFolderPath - the full path (eg `/Public/example/myFolder`) of the parent folder.
|
||||
* @param parentFolderUri - the URI of the parent folder.
|
||||
* @param accessToken - the access token to authorizing the request.
|
||||
* @param sasApiClient - a client for interfacing with SAS API.
|
||||
* @param isForced - flag that indicates if target folder already exists, it and all subfolders have to be deleted. Applicable for SAS VIYA only.
|
||||
*/
|
||||
public async createFolder(
|
||||
folderName: string,
|
||||
parentFolderPath: string,
|
||||
parentFolderUri?: string,
|
||||
accessToken?: string,
|
||||
sasApiClient?: SASViyaApiClient
|
||||
sasApiClient?: SASViyaApiClient,
|
||||
isForced?: boolean
|
||||
) {
|
||||
if (this.sasjsConfig.serverType !== ServerType.SASViya) {
|
||||
throw new Error('This operation is only supported on SAS Viya servers.')
|
||||
@@ -155,7 +229,8 @@ export default class SASjs {
|
||||
folderName,
|
||||
parentFolderPath,
|
||||
parentFolderUri,
|
||||
accessToken
|
||||
accessToken,
|
||||
isForced
|
||||
)
|
||||
}
|
||||
|
||||
@@ -484,12 +559,14 @@ export default class SASjs {
|
||||
* If not provided, is taken from SASjsConfig.
|
||||
* @param accessToken - an optional access token to be passed in when
|
||||
* using this function from the command line.
|
||||
* @param isForced - flag that indicates if target folder already exists, it and all subfolders have to be deleted.
|
||||
*/
|
||||
public async deployServicePack(
|
||||
serviceJson: any,
|
||||
appLoc?: string,
|
||||
serverUrl?: string,
|
||||
accessToken?: string
|
||||
accessToken?: string,
|
||||
isForced = false
|
||||
) {
|
||||
if (this.sasjsConfig.serverType !== ServerType.SASViya) {
|
||||
throw new Error('This operation is only supported on SAS Viya servers.')
|
||||
@@ -540,7 +617,8 @@ export default class SASjs {
|
||||
appLoc,
|
||||
members,
|
||||
accessToken,
|
||||
sasApiClient
|
||||
sasApiClient,
|
||||
isForced
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1247,7 +1325,8 @@ export default class SASjs {
|
||||
parentFolder: string,
|
||||
membersJson: any[],
|
||||
accessToken?: string,
|
||||
sasApiClient?: SASViyaApiClient
|
||||
sasApiClient?: SASViyaApiClient,
|
||||
isForced?: boolean
|
||||
) {
|
||||
await asyncForEach(membersJson, async (member: any) => {
|
||||
switch (member.type) {
|
||||
@@ -1257,7 +1336,8 @@ export default class SASjs {
|
||||
parentFolder,
|
||||
undefined,
|
||||
accessToken,
|
||||
sasApiClient
|
||||
sasApiClient,
|
||||
isForced
|
||||
)
|
||||
break
|
||||
case 'service':
|
||||
@@ -1278,7 +1358,8 @@ export default class SASjs {
|
||||
`${parentFolder}/${member.name}`,
|
||||
member.members,
|
||||
accessToken,
|
||||
sasApiClient
|
||||
sasApiClient,
|
||||
isForced
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ export class SessionManager {
|
||||
(new Date().getTime() - new Date(session!.creationTimeStamp).getTime()) /
|
||||
1000
|
||||
if (
|
||||
!session!.attributes ||
|
||||
secondsSinceSessionCreation >= session!.attributes.sessionInactiveTimeout
|
||||
) {
|
||||
await this.createSessions(accessToken)
|
||||
|
||||
@@ -3,4 +3,15 @@ export interface Context {
|
||||
id: string
|
||||
createdBy: string
|
||||
version: number
|
||||
attributes?: any
|
||||
}
|
||||
|
||||
export interface EditContextInput {
|
||||
name?: string
|
||||
description?: string
|
||||
launchContext?: { name: string }
|
||||
environment?: { options?: string[]; autoExecLines?: string[] }
|
||||
attributes?: any
|
||||
authorizedUsers?: string[]
|
||||
authorizeAllAuthenticatedUsers?: boolean
|
||||
}
|
||||
|
||||
@@ -13,4 +13,5 @@ export * from './parseSasViyaLog'
|
||||
export * from './serialize'
|
||||
export * from './splitChunks'
|
||||
export * from './parseWeboutResponse'
|
||||
export * from './isUri'
|
||||
export * from './isUrl'
|
||||
|
||||
5
src/utils/isUri.ts
Normal file
5
src/utils/isUri.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
* Checks if string is in URI format
|
||||
* @param str string to check
|
||||
*/
|
||||
export const isUri = (str: string): boolean => /^\/folders\/folders\//.test(str)
|
||||
@@ -1,34 +1,21 @@
|
||||
import SASjs from './index'
|
||||
|
||||
const adapter = new SASjs()
|
||||
|
||||
it('should parse SAS9 source code', async (done) => {
|
||||
expect(sampleResponse).toBeTruthy()
|
||||
const parsedSourceCode = (adapter as any).parseSAS9SourceCode(sampleResponse)
|
||||
expect(parsedSourceCode).toBeTruthy()
|
||||
const sourceCodeLines = parsedSourceCode.split('\r\n')
|
||||
expect(sourceCodeLines.length).toEqual(5)
|
||||
expect(sourceCodeLines[0].startsWith('6')).toBeTruthy()
|
||||
expect(sourceCodeLines[1].startsWith('7')).toBeTruthy()
|
||||
expect(sourceCodeLines[2].startsWith('8')).toBeTruthy()
|
||||
expect(sourceCodeLines[3].startsWith('9')).toBeTruthy()
|
||||
expect(sourceCodeLines[4].startsWith('10')).toBeTruthy()
|
||||
done()
|
||||
})
|
||||
import { parseGeneratedCode } from './index'
|
||||
|
||||
it('should parse generated code', async (done) => {
|
||||
expect(sampleResponse).toBeTruthy()
|
||||
const parsedGeneratedCode = (adapter as any).parseGeneratedCode(
|
||||
sampleResponse
|
||||
)
|
||||
|
||||
const parsedGeneratedCode = parseGeneratedCode(sampleResponse)
|
||||
|
||||
expect(parsedGeneratedCode).toBeTruthy()
|
||||
|
||||
const generatedCodeLines = parsedGeneratedCode.split('\r\n')
|
||||
|
||||
expect(generatedCodeLines.length).toEqual(5)
|
||||
expect(generatedCodeLines[0].startsWith('MPRINT(MM_WEBIN)')).toBeTruthy()
|
||||
expect(generatedCodeLines[1].startsWith('MPRINT(MM_WEBLEFT)')).toBeTruthy()
|
||||
expect(generatedCodeLines[2].startsWith('MPRINT(MM_WEBOUT)')).toBeTruthy()
|
||||
expect(generatedCodeLines[3].startsWith('MPRINT(MM_WEBRIGHT)')).toBeTruthy()
|
||||
expect(generatedCodeLines[4].startsWith('MPRINT(MM_WEBOUT)')).toBeTruthy()
|
||||
|
||||
done()
|
||||
})
|
||||
|
||||
35
src/utils/parseSourceCode.spec.ts
Normal file
35
src/utils/parseSourceCode.spec.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { parseSourceCode } from './index'
|
||||
|
||||
it('should parse SAS9 source code', async (done) => {
|
||||
expect(sampleResponse).toBeTruthy()
|
||||
|
||||
const parsedSourceCode = parseSourceCode(sampleResponse)
|
||||
|
||||
expect(parsedSourceCode).toBeTruthy()
|
||||
|
||||
const sourceCodeLines = parsedSourceCode.split('\r\n')
|
||||
|
||||
expect(sourceCodeLines.length).toEqual(5)
|
||||
expect(sourceCodeLines[0].startsWith('6')).toBeTruthy()
|
||||
expect(sourceCodeLines[1].startsWith('7')).toBeTruthy()
|
||||
expect(sourceCodeLines[2].startsWith('8')).toBeTruthy()
|
||||
expect(sourceCodeLines[3].startsWith('9')).toBeTruthy()
|
||||
expect(sourceCodeLines[4].startsWith('10')).toBeTruthy()
|
||||
|
||||
done()
|
||||
})
|
||||
|
||||
/* tslint:disable */
|
||||
const sampleResponse = `<meta http-equiv="Content-Type" content="text/html; charset=windows-1252"/>
|
||||
6 @file mm_webout.sas
|
||||
7 @brief Send data to/from SAS Stored Processes
|
||||
8 @details This macro should be added to the start of each Stored Process,
|
||||
9 **immediately** followed by a call to:
|
||||
10 %webout(OPEN)
|
||||
MPRINT(MM_WEBIN): ;
|
||||
MPRINT(MM_WEBLEFT): filename _temp temp lrecl=999999;
|
||||
MPRINT(MM_WEBOUT): data _null_;
|
||||
MPRINT(MM_WEBRIGHT): file _temp;
|
||||
MPRINT(MM_WEBOUT): if upcase(symget('_debug'))='LOG' then put '>>weboutBEGIN<<';
|
||||
`
|
||||
/* tslint:enable */
|
||||
Reference in New Issue
Block a user