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

Compare commits

...

48 Commits

Author SHA1 Message Date
Allan Bowe
173e9746b1 Merge pull request #78 from sasjs/issue77
Issue77
2020-09-11 10:24:02 +02:00
Krishna Acondy
036f1203e3 Merge branch 'master' into issue77 2020-09-11 09:05:27 +01:00
Krishna Acondy
302ceb0cb1 Merge pull request #79 from sasjs/run-tests-ci
chore(ci): run unit tests as part of build
2020-09-11 09:05:09 +01:00
Krishna Acondy
e209902c1d chore(*): add code style section to doc 2020-09-11 08:28:22 +01:00
Krishna Acondy
e897037c8b chore(*): fix code formatting 2020-09-11 08:20:39 +01:00
Krishna Acondy
c6857e857c chore(ci): run unit tests as part of build 2020-09-11 08:07:37 +01:00
Krishna Acondy
49b25555fc Merge branch 'master' into issue77 2020-09-11 07:59:52 +01:00
Mihajlo Medjedovic
e642264442 fix: execute script passing debug from global config 2020-09-10 22:58:50 +02:00
Mihajlo Medjedovic
d3738ad5cd fix: waitForSession accessToken not passed 2020-09-10 20:42:14 +02:00
Yury Shkoda
40de5f2e11 Merge pull request #75 from sasjs/edit-context
feat(edit-context): add the ability to edit a given context
2020-09-10 13:53:35 +03:00
Krishna Acondy
6056424d26 Merge branch 'master' into edit-context 2020-09-10 10:08:31 +01:00
Krishna Acondy
ae5110974f Merge pull request #72 from sasjs/issue-69
chore(tests): tested 'parseSourceCode' and 'parseGeneratedCode' functions directly
2020-09-10 10:08:18 +01:00
Krishna Acondy
b2f6d4e6d1 feat(create-context): add the ability to modify attributes 2020-09-10 10:02:55 +01:00
Krishna Acondy
f69a635e1d chore(doc): update documentation 2020-09-10 09:57:04 +01:00
Krishna Acondy
c8bc6936e8 Merge branch 'master' into issue-69 2020-09-10 09:50:15 +01:00
Krishna Acondy
9a24a8b962 fix(session-timeout): assume session has expired if attributes are not present 2020-09-10 09:45:10 +01:00
Krishna Acondy
c43c9ec211 chore(edit-context): add error handling for fetch 2020-09-10 09:30:56 +01:00
Krishna Acondy
9b0e02f5b7 feat(edit-context): add the ability to edit a given context 2020-09-10 09:14:02 +01:00
Allan Bowe
3ec6ee2db9 Merge pull request #65 from sasjs/issue-62
feat: added forced deploy
2020-09-09 16:20:13 +02:00
Yury Shkoda
e6ab5f918f chore(move folder): improved error handling 2020-09-09 15:08:36 +03:00
Yury Shkoda
951e119c08 doc(forced deploy): updated documentation of 'SASViyaApiClient' 2020-09-09 13:05:06 +03:00
Yury Shkoda
d22d9e1039 Merge branch 'issue-62' of https://github.com/sasjs/adapter into issue-62 2020-09-09 12:41:35 +03:00
Yury Shkoda
6e276e2e26 Merge branch 'master' into issue-62 2020-09-09 12:39:36 +03:00
Yury Shkoda
eec973efa1 doc(forced deploy): updated documentation of the functions affected by 'issue-62' 2020-09-09 12:38:28 +03:00
Yury Shkoda
0f9eca7482 chore(tests): tested 'parseSourceCode' and 'parseGeneratedCode' functions directly 2020-09-09 11:22:58 +03:00
Krishna Acondy
2c763e38ae Merge branch 'master' into issue-62 2020-09-09 07:59:43 +01:00
Yury Shkoda
4957bc5b05 Merge pull request #66 from sasjs/create-context
feat(context): add the ability to create and delete a compute context
2020-09-09 09:46:38 +03:00
Krishna Acondy
f6b1eecb42 chore(*): remove console log, add comments 2020-09-09 07:36:27 +01:00
Yury Shkoda
7ae2a4d2c6 Merged branch 'master' into 'issue-62' 2020-09-09 09:14:24 +03:00
Krishna Acondy
f86d20b723 Merge branch 'create-context' of https://github.com/sasjs/adapter into create-context 2020-09-08 18:55:33 +01:00
Krishna Acondy
7a1cce193e chore(delete-context): add punctuation to error message 2020-09-08 18:55:30 +01:00
Krishna Acondy
05539fff11 Merge branch 'master' into create-context 2020-09-08 18:53:25 +01:00
Krishna Acondy
58d69a62d6 feat(delete-context): add the ability to delete a compute context 2020-09-08 18:29:12 +01:00
Krishna Acondy
c9c9754916 fix(create-context): change autoExecLines to an array 2020-09-08 18:25:36 +01:00
Yury Shkoda
e056ca21fe refactor: added isUri utility 2020-09-08 17:24:49 +03:00
Yury Shkoda
0a77ebf5c5 feat: added sourceFolder URI capability to moveFolder function 2020-09-08 17:01:24 +03:00
Yury Shkoda
12835893b1 refactor: refactored moveFolder and deleteFolder functions 2020-09-08 10:36:16 +03:00
Yury Shkoda
df86b2e700 docs: documented moveFolder and deleteFolder functions 2020-09-07 17:07:58 +03:00
Yury Shkoda
5fce25d58a fix: made moveFolder and deleteFolder functions public 2020-09-07 12:17:48 +03:00
Yury Shkoda
7ee9335183 feat: add deleteFolder function 2020-09-07 09:07:25 +03:00
Krishna Acondy
07695bdb85 chore(create-context): update TSDoc comment 2020-09-06 21:35:56 +01:00
Krishna Acondy
26c8946fd5 feat(create-context): add list of authorized users 2020-09-06 21:32:48 +01:00
Krishna Acondy
fc1d54d105 feat(create-context): add launch context name 2020-09-06 21:28:24 +01:00
Krishna Acondy
a318d61f83 feat(create-context): add launch context name 2020-09-06 21:27:44 +01:00
Krishna Acondy
cc5a0cbec3 feat(create-context): allow all authenticated users to use context 2020-09-06 21:10:08 +01:00
Krishna Acondy
d932d9ea0a chore(*): fix code style 2020-09-06 21:05:05 +01:00
Krishna Acondy
e3edace882 feat(create-context): add the ability to create a compute context 2020-09-06 21:01:04 +01:00
Yury Shkoda
29d9df5792 feat: added forced deploy 2020-09-06 12:19:27 +03:00
71 changed files with 19911 additions and 57 deletions

View File

@@ -25,6 +25,8 @@ jobs:
run: npm ci
- name: Check code style
run: npm run lint
- name: Run unit tests
run: npm test
- name: Build Package
run: npm run package:lib
env:

View File

@@ -2,6 +2,23 @@
Contributions to SASjs are very welcome! When making a PR, test cases should be included.
## Code Style
This repository uses `Prettier` to ensure a uniform code style.
If you are using VS Code for development, you can automatically fix your code to match the style as follows:
- Install the `Prettier` extension for VS Code.
- Open your `settings.json` file by choosing 'Preferences: Open Settings (JSON)' from the command palette.
- Add the following items to the JSON.
```
"editor.formatOnSave": true,
"editor.formatOnPaste": true,
```
If you are using another editor, or are unable to install the extension, you can run `npm run lint:fix` to fix the formatting after you've made your changes.
## Testing
This repository contains a suite of tests built using [@sasjs/test-framework](https://github.com/sasjs/test-framework).
Detailed instructions for creating and running the tests can be found [here](https://github.com/sasjs/adapter/blob/master/sasjs-tests/README.md).

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

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

View File

@@ -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)

View File

@@ -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.')
@@ -130,16 +194,28 @@ export default class SASjs {
linesOfCode,
contextName,
accessToken,
silent
silent,
null,
this.sasjsConfig.debug
)
}
/**
* 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 +231,8 @@ export default class SASjs {
folderName,
parentFolderPath,
parentFolderUri,
accessToken
accessToken,
isForced
)
}
@@ -484,12 +561,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 +619,8 @@ export default class SASjs {
appLoc,
members,
accessToken,
sasApiClient
sasApiClient,
isForced
)
}
@@ -1247,7 +1327,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 +1338,8 @@ export default class SASjs {
parentFolder,
undefined,
accessToken,
sasApiClient
sasApiClient,
isForced
)
break
case 'service':
@@ -1278,7 +1360,8 @@ export default class SASjs {
`${parentFolder}/${member.name}`,
member.members,
accessToken,
sasApiClient
sasApiClient,
isForced
)
})
}

View File

@@ -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)
@@ -68,7 +69,7 @@ export class SessionManager {
createSessionRequest
)
await this.waitForSession(createdSession, etag)
await this.waitForSession(createdSession, etag, accessToken)
this.sessions.push(createdSession)
return createdSession
}

View File

@@ -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
}

View File

@@ -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
View 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)

View File

@@ -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()
})

View 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 '&gt;&gt;weboutBEGIN&lt;&lt;';
`
/* tslint:enable */