mirror of
https://github.com/sasjs/adapter.git
synced 2025-12-13 02:04:36 +00:00
Compare commits
1 Commits
v2.0.3
...
session-ma
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
232f4ec3fb |
@@ -1,2 +0,0 @@
|
|||||||
SERVER_URL=https://server.com
|
|
||||||
DEFAULT_COMPUTE_CONTEXT=SAS Job Execution compute context
|
|
||||||
9
.github/reviewer-lottery.yml
vendored
9
.github/reviewer-lottery.yml
vendored
@@ -1,9 +0,0 @@
|
|||||||
groups:
|
|
||||||
- name: SASjs Devs # name of the group
|
|
||||||
reviewers: 1 # how many reviewers do you want to assign?
|
|
||||||
usernames: # github usernames of the reviewers
|
|
||||||
- krishna-acondy
|
|
||||||
- YuryShkoda
|
|
||||||
- saadjutt01
|
|
||||||
- medjedovicm
|
|
||||||
- allanbowe
|
|
||||||
13
.github/workflows/assign-reviewer.yml
vendored
13
.github/workflows/assign-reviewer.yml
vendored
@@ -1,13 +0,0 @@
|
|||||||
name: 'Assign Reviewer'
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
types: [opened]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
test:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- uses: uesteibar/reviewer-lottery@v1
|
|
||||||
with:
|
|
||||||
repo-token: ${{ secrets.GH_TOKEN }}
|
|
||||||
10
.github/workflows/build.yml
vendored
10
.github/workflows/build.yml
vendored
@@ -27,6 +27,16 @@ jobs:
|
|||||||
run: npm run lint
|
run: npm run lint
|
||||||
- name: Run unit tests
|
- name: Run unit tests
|
||||||
run: npm test
|
run: npm test
|
||||||
|
env:
|
||||||
|
CI: true
|
||||||
|
CLIENT: ${{secrets.CLIENT}}
|
||||||
|
SECRET: ${{secrets.SECRET}}
|
||||||
|
SAS_USERNAME: ${{secrets.SAS_USERNAME}}
|
||||||
|
SAS_PASSWORD: ${{secrets.SAS_PASSWORD}}
|
||||||
|
SERVER_URL: ${{secrets.SERVER_URL}}
|
||||||
|
SERVER_TYPE: ${{secrets.SERVER_TYPE}}
|
||||||
|
ACCESS_TOKEN: ${{secrets.ACCESS_TOKEN}}
|
||||||
|
REFRESH_TOKEN: ${{secrets.REFRESH_TOKEN}}
|
||||||
- name: Build Package
|
- name: Build Package
|
||||||
run: npm run package:lib
|
run: npm run package:lib
|
||||||
env:
|
env:
|
||||||
|
|||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,6 +1,2 @@
|
|||||||
node_modules
|
node_modules
|
||||||
build
|
build
|
||||||
|
|
||||||
.env
|
|
||||||
|
|
||||||
/coverage
|
|
||||||
@@ -14,5 +14,5 @@ What code changes have been made to achieve the intent.
|
|||||||
|
|
||||||
- [ ] Code is formatted correctly (`npm run lint:fix`).
|
- [ ] Code is formatted correctly (`npm run lint:fix`).
|
||||||
- [ ] All unit tests are passing (`npm test`).
|
- [ ] All unit tests are passing (`npm test`).
|
||||||
- [ ] All `sasjs-cli` unit tests are passing (`npm test`).
|
- [ ] All `sasjs-tests` unit tests are passing (`npm test`).
|
||||||
- [ ] All `sasjs-tests` are passing (instructions available [here](https://github.com/sasjs/adapter/blob/master/sasjs-tests/README.md)).
|
- [ ] All `sasjs-tests` are passing (instructions available [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
6016
package-lock.json
generated
6016
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -2,12 +2,12 @@
|
|||||||
"name": "@sasjs/adapter",
|
"name": "@sasjs/adapter",
|
||||||
"description": "JavaScript adapter for SAS",
|
"description": "JavaScript adapter for SAS",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "rimraf build && rimraf node && mkdir node && cp -r src/* node && webpack && rimraf build/src && rimraf node",
|
"build": "rimraf build && webpack",
|
||||||
"package:lib": "npm run build && cp ./package.json build && cd build && npm version \"5.0.0\" && npm pack",
|
"package:lib": "npm run build && cp ./package.json build && cd build && npm version \"5.0.0\" && npm pack",
|
||||||
"publish:lib": "npm run build && cd build && npm publish",
|
"publish:lib": "npm run build && cd build && npm publish",
|
||||||
"lint:fix": "npx prettier --write 'src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}'",
|
"lint:fix": "npx prettier --write 'src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}'",
|
||||||
"lint": "npx prettier --check 'src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}'",
|
"lint": "npx prettier --check 'src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}'",
|
||||||
"test": "jest --coverage",
|
"test": "jest",
|
||||||
"prepublishOnly": "cp -r ./build/* . && rm -rf ./build",
|
"prepublishOnly": "cp -r ./build/* . && rm -rf ./build",
|
||||||
"postpublish": "git clean -fd",
|
"postpublish": "git clean -fd",
|
||||||
"semantic-release": "semantic-release",
|
"semantic-release": "semantic-release",
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
"jest": "^25.5.4",
|
"jest": "^25.5.4",
|
||||||
"path": "^0.12.7",
|
"path": "^0.12.7",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"semantic-release": "^17.3.0",
|
"semantic-release": "^17.2.3",
|
||||||
"terser-webpack-plugin": "^4.2.3",
|
"terser-webpack-plugin": "^4.2.3",
|
||||||
"ts-jest": "^25.5.1",
|
"ts-jest": "^25.5.1",
|
||||||
"ts-loader": "^8.0.11",
|
"ts-loader": "^8.0.11",
|
||||||
@@ -58,7 +58,6 @@
|
|||||||
},
|
},
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sasjs/utils": "^1.5.0",
|
|
||||||
"es6-promise": "^4.2.8",
|
"es6-promise": "^4.2.8",
|
||||||
"form-data": "^3.0.0",
|
"form-data": "^3.0.0",
|
||||||
"isomorphic-fetch": "^2.2.1"
|
"isomorphic-fetch": "^2.2.1"
|
||||||
|
|||||||
6
sasjs-tests/package-lock.json
generated
6
sasjs-tests/package-lock.json
generated
@@ -1357,9 +1357,9 @@
|
|||||||
"integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw=="
|
"integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw=="
|
||||||
},
|
},
|
||||||
"@sasjs/adapter": {
|
"@sasjs/adapter": {
|
||||||
"version": "1.18.3",
|
"version": "1.12.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sasjs/adapter/-/adapter-1.18.3.tgz",
|
"resolved": "https://registry.npmjs.org/@sasjs/adapter/-/adapter-1.12.0.tgz",
|
||||||
"integrity": "sha512-wzDFJRyt2dXFeQP+JzqRGunYUbujrAbU/Jc4IWg5URsCkGAzwsNl/4G0xJVbqOTy1MvoZ431rfCnvhkUlg7D3Q==",
|
"integrity": "sha512-0uGQH9ynomWzdBaEujEtcR38q6V7LCgG0mrb1Wellv6cC/IHD3j6WfeZZAgtiMPeOSJjbCDBOlVnzC2TlBqJFw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"es6-promise": "^4.2.8",
|
"es6-promise": "^4.2.8",
|
||||||
"form-data": "^3.0.0",
|
"form-data": "^3.0.0",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"homepage": ".",
|
"homepage": ".",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sasjs/adapter": "^1.18.2",
|
"@sasjs/adapter": "^1.12.0",
|
||||||
"@sasjs/test-framework": "^1.4.0",
|
"@sasjs/test-framework": "^1.4.0",
|
||||||
"@testing-library/jest-dom": "^4.2.4",
|
"@testing-library/jest-dom": "^4.2.4",
|
||||||
"@testing-library/react": "^9.5.0",
|
"@testing-library/react": "^9.5.0",
|
||||||
|
|||||||
9
sasjs-tests/src/App.test.js
Normal file
9
sasjs-tests/src/App.test.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { render } from "@testing-library/react";
|
||||||
|
import App from "./App";
|
||||||
|
|
||||||
|
test("renders learn react link", () => {
|
||||||
|
const { getByText } = render(<App />);
|
||||||
|
const linkElement = getByText(/learn react/i);
|
||||||
|
expect(linkElement).toBeInTheDocument();
|
||||||
|
});
|
||||||
@@ -3,12 +3,12 @@ import { TestSuite } from "@sasjs/test-framework";
|
|||||||
|
|
||||||
const defaultConfig: SASjsConfig = {
|
const defaultConfig: SASjsConfig = {
|
||||||
serverUrl: window.location.origin,
|
serverUrl: window.location.origin,
|
||||||
pathSAS9: '/SASStoredProcess/do',
|
pathSAS9: "/SASStoredProcess/do",
|
||||||
pathSASViya: '/SASJobExecution',
|
pathSASViya: "/SASJobExecution",
|
||||||
appLoc: '/Public/seedapp',
|
appLoc: "/Public/seedapp",
|
||||||
serverType: ServerType.SASViya,
|
serverType: ServerType.SASViya,
|
||||||
debug: false,
|
debug: true,
|
||||||
contextName: 'SAS Job Execution compute context',
|
contextName: "SAS Job Execution compute context",
|
||||||
useComputeApi: false
|
useComputeApi: false
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -57,7 +57,6 @@ export const basicTests = (
|
|||||||
},
|
},
|
||||||
assertion: (sasjsInstance: SASjs) => {
|
assertion: (sasjsInstance: SASjs) => {
|
||||||
const sasjsConfig = sasjsInstance.getSasjsConfig();
|
const sasjsConfig = sasjsInstance.getSasjsConfig();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
sasjsConfig.serverUrl === defaultConfig.serverUrl &&
|
sasjsConfig.serverUrl === defaultConfig.serverUrl &&
|
||||||
sasjsConfig.pathSAS9 === defaultConfig.pathSAS9 &&
|
sasjsConfig.pathSAS9 === defaultConfig.pathSAS9 &&
|
||||||
|
|||||||
@@ -25,71 +25,17 @@ export const computeTests = (adapter: SASjs): TestSuite => ({
|
|||||||
},
|
},
|
||||||
assertion: (res: any) => {
|
assertion: (res: any) => {
|
||||||
const expectedProperties = ["id", "state", "creationTimeStamp", "jobConditionCode"]
|
const expectedProperties = ["id", "state", "creationTimeStamp", "jobConditionCode"]
|
||||||
return validate(expectedProperties, res.result);
|
return validate(expectedProperties, res);
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Execute Script Viya - complete job",
|
|
||||||
description: "Should execute sas file and return log",
|
|
||||||
test: () => {
|
|
||||||
const fileLines = [
|
|
||||||
`data;`,
|
|
||||||
`do x=1 to 100;`,
|
|
||||||
`output;`,
|
|
||||||
`end;`,
|
|
||||||
`run;`
|
|
||||||
]
|
|
||||||
|
|
||||||
return adapter.executeScriptSASViya(
|
|
||||||
'sasCode.sas',
|
|
||||||
fileLines,
|
|
||||||
'SAS Studio compute context',
|
|
||||||
undefined,
|
|
||||||
true
|
|
||||||
)
|
|
||||||
},
|
|
||||||
assertion: (res: any) => {
|
|
||||||
const expectedLogContent = `1 data;\\n2 do x=1 to 100;\\n3 output;\\n4 end;\\n5 run;\\n\\n`
|
|
||||||
|
|
||||||
return validateLog(expectedLogContent, res.log);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Execute Script Viya - failed job",
|
|
||||||
description: "Should execute sas file and return log",
|
|
||||||
test: () => {
|
|
||||||
const fileLines = [
|
|
||||||
`%abort;`
|
|
||||||
]
|
|
||||||
|
|
||||||
return adapter.executeScriptSASViya(
|
|
||||||
'sasCode.sas',
|
|
||||||
fileLines,
|
|
||||||
'SAS Studio compute context',
|
|
||||||
undefined,
|
|
||||||
true
|
|
||||||
).catch((err: any) => err )
|
|
||||||
},
|
|
||||||
assertion: (res: any) => {
|
|
||||||
const expectedLogContent = `1 %abort;\\nERROR: The %ABORT statement is not valid in open code.\\n`
|
|
||||||
|
|
||||||
return validateLog(expectedLogContent, res.log);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
const validateLog = (text: string, log: string): boolean => {
|
|
||||||
const isValid = JSON.stringify(log).includes(text)
|
|
||||||
|
|
||||||
return isValid
|
|
||||||
}
|
|
||||||
|
|
||||||
const validate = (expectedProperties: string[], data: any): boolean => {
|
const validate = (expectedProperties: string[], data: any): boolean => {
|
||||||
const actualProperties = Object.keys(data);
|
const actualProperties = Object.keys(data);
|
||||||
|
|
||||||
const isValid = expectedProperties.every(
|
const isValid = expectedProperties.every(
|
||||||
(property) => actualProperties.includes(property)
|
(property) => actualProperties.includes(property)
|
||||||
);
|
);
|
||||||
return isValid
|
return isValid
|
||||||
}
|
}
|
||||||
@@ -88,7 +88,7 @@ export const sendArrTests = (adapter: SASjs): TestSuite => ({
|
|||||||
return adapter.request("common/sendArr", data).catch((e) => e);
|
return adapter.request("common/sendArr", data).catch((e) => e);
|
||||||
},
|
},
|
||||||
assertion: (error: any) => {
|
assertion: (error: any) => {
|
||||||
return !!error && !!error.error && !!error.error.message;
|
return !!error && !!error.body && !!error.body.message;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -185,7 +185,7 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
|
|||||||
};
|
};
|
||||||
return adapter.request("common/sendObj", invalidData).catch((e) => e);
|
return adapter.request("common/sendObj", invalidData).catch((e) => e);
|
||||||
},
|
},
|
||||||
assertion: (error: any) => !!error && !!error.error && !!error.error.message
|
assertion: (error: any) => !!error && !!error.body && !!error.body.message
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Single string value",
|
title: "Single string value",
|
||||||
@@ -219,7 +219,7 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
|
|||||||
.catch((e) => e);
|
.catch((e) => e);
|
||||||
},
|
},
|
||||||
assertion: (error: any) => {
|
assertion: (error: any) => {
|
||||||
return !!error && !!error.error && !!error.error.message;
|
return !!error && !!error.body && !!error.body.message;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -23,24 +23,22 @@ export const sasjsRequestTests = (adapter: SASjs): TestSuite => ({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Make error and capture log",
|
title: "Make error and capture log",
|
||||||
description: "Should make an error and capture log, in the same time it is testing if debug override is working",
|
description: "Should make an error and capture log",
|
||||||
test: async () => {
|
test: async () => {
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
adapter
|
adapter
|
||||||
.request("common/makeErr", data, {debug: true})
|
.request("common/makeErr", data)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
//no action here, this request must throw error
|
//no action here, this request must throw error
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
let sasRequests = adapter.getSasRequests();
|
let sasRequests = adapter.getSasRequests();
|
||||||
let makeErrRequest: any =
|
let makeErrRequest =
|
||||||
sasRequests.find((req) =>
|
sasRequests.find((req) =>
|
||||||
req.serviceLink.includes("makeErr")
|
req.serviceLink.includes("makeErr")
|
||||||
) || null;
|
) || null;
|
||||||
|
|
||||||
if (!makeErrRequest) resolve(false)
|
resolve(!!makeErrRequest);
|
||||||
|
|
||||||
resolve(!!(makeErrRequest.logFile && makeErrRequest.logFile.length > 0));
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,538 +0,0 @@
|
|||||||
import {
|
|
||||||
Context,
|
|
||||||
CsrfToken,
|
|
||||||
EditContextInput,
|
|
||||||
ContextAllAttributes
|
|
||||||
} from './types'
|
|
||||||
import { makeRequest, isUrl } from './utils'
|
|
||||||
import { SASViyaApiClient } from './SASViyaApiClient'
|
|
||||||
import { prefixMessage } from '@sasjs/utils/error'
|
|
||||||
|
|
||||||
export class ContextManager {
|
|
||||||
private defaultComputeContexts = [
|
|
||||||
'CAS Formats service compute context',
|
|
||||||
'Data Mining compute context',
|
|
||||||
'Import 9 service compute context',
|
|
||||||
'SAS Job Execution compute context',
|
|
||||||
'SAS Model Manager compute context',
|
|
||||||
'SAS Studio compute context',
|
|
||||||
'SAS Visual Forecasting compute context'
|
|
||||||
]
|
|
||||||
private defaultLauncherContexts = [
|
|
||||||
'CAS Formats service launcher context',
|
|
||||||
'Data Mining launcher context',
|
|
||||||
'Import 9 service launcher context',
|
|
||||||
'Job Flow Execution launcher context',
|
|
||||||
'SAS Job Execution launcher context',
|
|
||||||
'SAS Model Manager launcher context',
|
|
||||||
'SAS Studio launcher context',
|
|
||||||
'SAS Visual Forecasting launcher context'
|
|
||||||
]
|
|
||||||
|
|
||||||
private csrfToken: CsrfToken | null = null
|
|
||||||
|
|
||||||
get getDefaultComputeContexts() {
|
|
||||||
return this.defaultComputeContexts
|
|
||||||
}
|
|
||||||
get getDefaultLauncherContexts() {
|
|
||||||
return this.defaultLauncherContexts
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private serverUrl: string,
|
|
||||||
private setCsrfToken: (csrfToken: CsrfToken) => void
|
|
||||||
) {
|
|
||||||
if (serverUrl) isUrl(serverUrl)
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getComputeContexts(accessToken?: string) {
|
|
||||||
const headers: any = {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (accessToken) {
|
|
||||||
headers.Authorization = `Bearer ${accessToken}`
|
|
||||||
}
|
|
||||||
|
|
||||||
const { result: contexts } = await this.request<{ items: Context[] }>(
|
|
||||||
`${this.serverUrl}/compute/contexts?limit=10000`,
|
|
||||||
{ headers }
|
|
||||||
).catch((err) => {
|
|
||||||
throw prefixMessage(err, 'Error while getting compute contexts. ')
|
|
||||||
})
|
|
||||||
|
|
||||||
const contextsList = contexts && contexts.items ? contexts.items : []
|
|
||||||
|
|
||||||
return contextsList.map((context: any) => ({
|
|
||||||
createdBy: context.createdBy,
|
|
||||||
id: context.id,
|
|
||||||
name: context.name,
|
|
||||||
version: context.version,
|
|
||||||
attributes: {}
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getLauncherContexts(accessToken?: string) {
|
|
||||||
const headers: any = {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (accessToken) {
|
|
||||||
headers.Authorization = `Bearer ${accessToken}`
|
|
||||||
}
|
|
||||||
|
|
||||||
const { result: contexts } = await this.request<{ items: Context[] }>(
|
|
||||||
`${this.serverUrl}/launcher/contexts?limit=10000`,
|
|
||||||
{ headers }
|
|
||||||
).catch((err) => {
|
|
||||||
throw prefixMessage(err, 'Error while getting launcher contexts. ')
|
|
||||||
})
|
|
||||||
|
|
||||||
const contextsList = contexts && contexts.items ? contexts.items : []
|
|
||||||
|
|
||||||
return contextsList.map((context: any) => ({
|
|
||||||
createdBy: context.createdBy,
|
|
||||||
id: context.id,
|
|
||||||
name: context.name,
|
|
||||||
version: context.version,
|
|
||||||
attributes: {}
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
public async createComputeContext(
|
|
||||||
contextName: string,
|
|
||||||
launchContextName: string,
|
|
||||||
sharedAccountId: string,
|
|
||||||
autoExecLines: string[],
|
|
||||||
accessToken?: string,
|
|
||||||
authorizedUsers?: string[]
|
|
||||||
) {
|
|
||||||
this.validateContextName(contextName)
|
|
||||||
|
|
||||||
this.isDefaultContext(
|
|
||||||
contextName,
|
|
||||||
this.defaultComputeContexts,
|
|
||||||
`Compute context '${contextName}' already exists.`
|
|
||||||
)
|
|
||||||
|
|
||||||
const existingComputeContexts = await this.getComputeContexts(accessToken)
|
|
||||||
|
|
||||||
if (
|
|
||||||
existingComputeContexts.find((context) => context.name === contextName)
|
|
||||||
) {
|
|
||||||
throw new Error(`Compute context '${contextName}' already exists.`)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (launchContextName) {
|
|
||||||
if (!this.defaultLauncherContexts.includes(launchContextName)) {
|
|
||||||
const launcherContexts = await this.getLauncherContexts(accessToken)
|
|
||||||
|
|
||||||
if (
|
|
||||||
!launcherContexts.find(
|
|
||||||
(context) => context.name === launchContextName
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
const description = `The launcher context for ${launchContextName}`
|
|
||||||
const launchType = 'direct'
|
|
||||||
|
|
||||||
const newLauncherContext = await this.createLauncherContext(
|
|
||||||
launchContextName,
|
|
||||||
description,
|
|
||||||
launchType,
|
|
||||||
accessToken
|
|
||||||
).catch((err) => {
|
|
||||||
throw new Error(`Error while creating launcher context. ${err}`)
|
|
||||||
})
|
|
||||||
|
|
||||||
if (newLauncherContext && newLauncherContext.name) {
|
|
||||||
launchContextName = newLauncherContext.name
|
|
||||||
} else {
|
|
||||||
throw new Error('Error while creating launcher context.')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const headers: any = {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (accessToken) {
|
|
||||||
headers.Authorization = `Bearer ${accessToken}`
|
|
||||||
}
|
|
||||||
|
|
||||||
let attributes = { reuseServerProcesses: true } as object
|
|
||||||
|
|
||||||
if (sharedAccountId)
|
|
||||||
attributes = { ...attributes, runServerAs: sharedAccountId }
|
|
||||||
|
|
||||||
const requestBody: any = {
|
|
||||||
name: contextName,
|
|
||||||
launchContext: {
|
|
||||||
contextName: launchContextName || ''
|
|
||||||
},
|
|
||||||
attributes
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
).catch((err) => {
|
|
||||||
throw prefixMessage(err, 'Error while creating compute context. ')
|
|
||||||
})
|
|
||||||
|
|
||||||
return context
|
|
||||||
}
|
|
||||||
|
|
||||||
public async createLauncherContext(
|
|
||||||
contextName: string,
|
|
||||||
description: string,
|
|
||||||
launchType = 'direct',
|
|
||||||
accessToken?: string
|
|
||||||
) {
|
|
||||||
if (!contextName) {
|
|
||||||
throw new Error('Context name is required.')
|
|
||||||
}
|
|
||||||
|
|
||||||
this.isDefaultContext(
|
|
||||||
contextName,
|
|
||||||
this.defaultLauncherContexts,
|
|
||||||
`Launcher context '${contextName}' already exists.`
|
|
||||||
)
|
|
||||||
|
|
||||||
const existingLauncherContexts = await this.getLauncherContexts(accessToken)
|
|
||||||
|
|
||||||
if (
|
|
||||||
existingLauncherContexts.find((context) => context.name === contextName)
|
|
||||||
) {
|
|
||||||
throw new Error(`Launcher context '${contextName}' already exists.`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const headers: any = {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (accessToken) {
|
|
||||||
headers.Authorization = `Bearer ${accessToken}`
|
|
||||||
}
|
|
||||||
|
|
||||||
const requestBody: any = {
|
|
||||||
name: contextName,
|
|
||||||
description: description,
|
|
||||||
launchType
|
|
||||||
}
|
|
||||||
|
|
||||||
const createContextRequest: RequestInit = {
|
|
||||||
method: 'POST',
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify(requestBody)
|
|
||||||
}
|
|
||||||
|
|
||||||
const { result: context } = await this.request<Context>(
|
|
||||||
`${this.serverUrl}/launcher/contexts`,
|
|
||||||
createContextRequest
|
|
||||||
).catch((err) => {
|
|
||||||
throw prefixMessage(err, 'Error while creating launcher context. ')
|
|
||||||
})
|
|
||||||
|
|
||||||
return context
|
|
||||||
}
|
|
||||||
|
|
||||||
public async editComputeContext(
|
|
||||||
contextName: string,
|
|
||||||
editedContext: EditContextInput,
|
|
||||||
accessToken?: string
|
|
||||||
) {
|
|
||||||
this.validateContextName(contextName)
|
|
||||||
|
|
||||||
this.isDefaultContext(
|
|
||||||
contextName,
|
|
||||||
this.defaultComputeContexts,
|
|
||||||
'Editing default SAS compute contexts is not allowed.',
|
|
||||||
true
|
|
||||||
)
|
|
||||||
|
|
||||||
const headers: any = {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (accessToken) {
|
|
||||||
headers.Authorization = `Bearer ${accessToken}`
|
|
||||||
}
|
|
||||||
|
|
||||||
let originalContext
|
|
||||||
|
|
||||||
originalContext = await this.getComputeContextByName(
|
|
||||||
contextName,
|
|
||||||
accessToken
|
|
||||||
)
|
|
||||||
|
|
||||||
// Try to find context by id, when context name has been changed.
|
|
||||||
if (!originalContext) {
|
|
||||||
originalContext = await this.getComputeContextById(
|
|
||||||
editedContext.id!,
|
|
||||||
accessToken
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const { result: context, etag } = await this.request<Context>(
|
|
||||||
`${this.serverUrl}/compute/contexts/${originalContext.id}`,
|
|
||||||
{
|
|
||||||
headers
|
|
||||||
}
|
|
||||||
).catch((err) => {
|
|
||||||
if (err && err.status === 404) {
|
|
||||||
throw new Error(
|
|
||||||
`The context '${contextName}' was not found on this server.`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
throw err
|
|
||||||
})
|
|
||||||
|
|
||||||
// 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,
|
|
||||||
...editedContext,
|
|
||||||
attributes: { ...context.attributes, ...editedContext.attributes }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return await this.request<Context>(
|
|
||||||
`${this.serverUrl}/compute/contexts/${context.id}`,
|
|
||||||
updateContextRequest
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getComputeContextByName(
|
|
||||||
contextName: string,
|
|
||||||
accessToken?: string
|
|
||||||
): Promise<Context> {
|
|
||||||
const headers: any = {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (accessToken) {
|
|
||||||
headers.Authorization = `Bearer ${accessToken}`
|
|
||||||
}
|
|
||||||
|
|
||||||
const { result: contexts } = await this.request<{ items: Context[] }>(
|
|
||||||
`${this.serverUrl}/compute/contexts?filter=eq(name, "${contextName}")`,
|
|
||||||
{ headers }
|
|
||||||
).catch((err) => {
|
|
||||||
throw prefixMessage(err, 'Error while getting compute context by name. ')
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!contexts || !(contexts.items && contexts.items.length)) {
|
|
||||||
throw new Error(
|
|
||||||
`The context '${contextName}' was not found at '${this.serverUrl}'.`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return contexts.items[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getComputeContextById(
|
|
||||||
contextId: string,
|
|
||||||
accessToken?: string
|
|
||||||
): Promise<ContextAllAttributes> {
|
|
||||||
const headers: any = {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (accessToken) {
|
|
||||||
headers.Authorization = `Bearer ${accessToken}`
|
|
||||||
}
|
|
||||||
|
|
||||||
const { result: context } = await this.request<ContextAllAttributes>(
|
|
||||||
`${this.serverUrl}/compute/contexts/${contextId}`,
|
|
||||||
{ headers }
|
|
||||||
).catch((err) => {
|
|
||||||
throw prefixMessage(err, 'Error while getting compute context by id. ')
|
|
||||||
})
|
|
||||||
|
|
||||||
return context
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getExecutableContexts(
|
|
||||||
executeScript: Function,
|
|
||||||
accessToken?: string
|
|
||||||
) {
|
|
||||||
const headers: any = {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (accessToken) {
|
|
||||||
headers.Authorization = `Bearer ${accessToken}`
|
|
||||||
}
|
|
||||||
|
|
||||||
const { result: contexts } = await this.request<{ items: Context[] }>(
|
|
||||||
`${this.serverUrl}/compute/contexts?limit=10000`,
|
|
||||||
{ headers }
|
|
||||||
).catch((err) => {
|
|
||||||
throw prefixMessage(err, 'Error while fetching compute contexts.')
|
|
||||||
})
|
|
||||||
|
|
||||||
const contextsList = contexts.items || []
|
|
||||||
const executableContexts: any[] = []
|
|
||||||
|
|
||||||
const promises = contextsList.map((context: any) => {
|
|
||||||
const linesOfCode = ['%put &=sysuserid;']
|
|
||||||
|
|
||||||
return () =>
|
|
||||||
executeScript(
|
|
||||||
`test-${context.name}`,
|
|
||||||
linesOfCode,
|
|
||||||
context.name,
|
|
||||||
accessToken,
|
|
||||||
null,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true
|
|
||||||
).catch((err: any) => err)
|
|
||||||
})
|
|
||||||
|
|
||||||
let results: any[] = []
|
|
||||||
|
|
||||||
for (const promise of promises) results.push(await promise())
|
|
||||||
|
|
||||||
results.forEach((result: any, index: number) => {
|
|
||||||
if (result && result.log) {
|
|
||||||
try {
|
|
||||||
const resultParsed = result.log
|
|
||||||
let sysUserId = ''
|
|
||||||
|
|
||||||
const sysUserIdLog = resultParsed
|
|
||||||
.split('\n')
|
|
||||||
.find((line: string) => line.startsWith('SYSUSERID='))
|
|
||||||
|
|
||||||
if (sysUserIdLog) {
|
|
||||||
sysUserId = sysUserIdLog.replace('SYSUSERID=', '')
|
|
||||||
|
|
||||||
executableContexts.push({
|
|
||||||
createdBy: contextsList[index].createdBy,
|
|
||||||
id: contextsList[index].id,
|
|
||||||
name: contextsList[index].name,
|
|
||||||
version: contextsList[index].version,
|
|
||||||
attributes: {
|
|
||||||
sysUserId
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return executableContexts
|
|
||||||
}
|
|
||||||
|
|
||||||
public async deleteComputeContext(contextName: string, accessToken?: string) {
|
|
||||||
this.validateContextName(contextName)
|
|
||||||
|
|
||||||
this.isDefaultContext(
|
|
||||||
contextName,
|
|
||||||
this.defaultComputeContexts,
|
|
||||||
'Deleting default SAS compute contexts is not allowed.',
|
|
||||||
true
|
|
||||||
)
|
|
||||||
|
|
||||||
const headers: any = {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (accessToken) {
|
|
||||||
headers.Authorization = `Bearer ${accessToken}`
|
|
||||||
}
|
|
||||||
|
|
||||||
const context = await this.getComputeContextByName(contextName, accessToken)
|
|
||||||
|
|
||||||
const deleteContextRequest: RequestInit = {
|
|
||||||
method: 'DELETE',
|
|
||||||
headers
|
|
||||||
}
|
|
||||||
|
|
||||||
return await this.request<Context>(
|
|
||||||
`${this.serverUrl}/compute/contexts/${context.id}`,
|
|
||||||
deleteContextRequest
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: implement editLauncherContext method
|
|
||||||
|
|
||||||
// TODO: implement deleteLauncherContext method
|
|
||||||
|
|
||||||
private async request<T>(
|
|
||||||
url: string,
|
|
||||||
options: RequestInit,
|
|
||||||
contentType: 'text' | 'json' = 'json'
|
|
||||||
) {
|
|
||||||
if (this.csrfToken) {
|
|
||||||
options.headers = {
|
|
||||||
...options.headers,
|
|
||||||
[this.csrfToken.headerName]: this.csrfToken.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return await makeRequest<T>(
|
|
||||||
url,
|
|
||||||
options,
|
|
||||||
(token) => {
|
|
||||||
this.csrfToken = token
|
|
||||||
this.setCsrfToken(token)
|
|
||||||
},
|
|
||||||
contentType
|
|
||||||
).catch((err) => {
|
|
||||||
throw prefixMessage(
|
|
||||||
err,
|
|
||||||
'Error while making request in Context Manager. '
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
private validateContextName(name: string) {
|
|
||||||
if (!name) throw new Error('Context name is required.')
|
|
||||||
}
|
|
||||||
|
|
||||||
public isDefaultContext(
|
|
||||||
context: string,
|
|
||||||
defaultContexts: string[] = this.defaultComputeContexts,
|
|
||||||
errorMessage = '',
|
|
||||||
listDefaults = false
|
|
||||||
) {
|
|
||||||
if (defaultContexts.includes(context)) {
|
|
||||||
throw new Error(
|
|
||||||
`${errorMessage}${
|
|
||||||
listDefaults
|
|
||||||
? '\nDefault contexts:' +
|
|
||||||
defaultContexts.map((context, i) => `\n${i + 1}. ${context}`)
|
|
||||||
: ''
|
|
||||||
}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -16,14 +16,11 @@ import {
|
|||||||
Folder,
|
Folder,
|
||||||
CsrfToken,
|
CsrfToken,
|
||||||
EditContextInput,
|
EditContextInput,
|
||||||
JobDefinition,
|
ErrorResponse,
|
||||||
PollOptions
|
JobDefinition
|
||||||
} from './types'
|
} from './types'
|
||||||
import { formatDataForRequest } from './utils/formatDataForRequest'
|
import { formatDataForRequest } from './utils/formatDataForRequest'
|
||||||
import { SessionManager } from './SessionManager'
|
import { SessionManager } from './SessionManager'
|
||||||
import { ContextManager } from './ContextManager'
|
|
||||||
import { timestampToYYYYMMDDHHMMSS } from '@sasjs/utils/time'
|
|
||||||
import { Logger, LogLevel } from '@sasjs/utils/logger'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A client for interfacing with the SAS Viya REST API.
|
* A client for interfacing with the SAS Viya REST API.
|
||||||
@@ -47,7 +44,6 @@ export class SASViyaApiClient {
|
|||||||
this.contextName,
|
this.contextName,
|
||||||
this.setCsrfToken
|
this.setCsrfToken
|
||||||
)
|
)
|
||||||
private contextManager = new ContextManager(this.serverUrl, this.setCsrfToken)
|
|
||||||
private folderMap = new Map<string, Job[]>()
|
private folderMap = new Map<string, Job[]>()
|
||||||
|
|
||||||
public get debug() {
|
public get debug() {
|
||||||
@@ -100,23 +96,29 @@ export class SASViyaApiClient {
|
|||||||
* Returns all available compute contexts on this server.
|
* Returns all available compute contexts on this server.
|
||||||
* @param accessToken - an access token for an authorized user.
|
* @param accessToken - an access token for an authorized user.
|
||||||
*/
|
*/
|
||||||
public async getComputeContexts(accessToken?: string) {
|
public async getAllContexts(accessToken?: string) {
|
||||||
return await this.contextManager.getComputeContexts(accessToken)
|
const headers: any = {
|
||||||
}
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
if (accessToken) {
|
||||||
* Returns default(system) compute contexts.
|
headers.Authorization = `Bearer ${accessToken}`
|
||||||
*/
|
}
|
||||||
public getDefaultComputeContexts() {
|
|
||||||
return this.contextManager.getDefaultComputeContexts
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
const { result: contexts } = await this.request<{ items: Context[] }>(
|
||||||
* Returns all available launcher contexts on this server.
|
`${this.serverUrl}/compute/contexts?limit=10000`,
|
||||||
* @param accessToken - an access token for an authorized user.
|
{ headers }
|
||||||
*/
|
)
|
||||||
public async getLauncherContexts(accessToken?: string) {
|
|
||||||
return await this.contextManager.getLauncherContexts(accessToken)
|
const contextsList = contexts && contexts.items ? contexts.items : []
|
||||||
|
|
||||||
|
return contextsList.map((context: any) => ({
|
||||||
|
createdBy: context.createdBy,
|
||||||
|
id: context.id,
|
||||||
|
name: context.name,
|
||||||
|
version: context.version,
|
||||||
|
attributes: {}
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -124,12 +126,76 @@ export class SASViyaApiClient {
|
|||||||
* @param accessToken - an access token for an authorized user.
|
* @param accessToken - an access token for an authorized user.
|
||||||
*/
|
*/
|
||||||
public async getExecutableContexts(accessToken?: string) {
|
public async getExecutableContexts(accessToken?: string) {
|
||||||
const bindedExecuteScript = this.executeScript.bind(this)
|
const headers: any = {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
|
||||||
return await this.contextManager.getExecutableContexts(
|
if (accessToken) {
|
||||||
bindedExecuteScript,
|
headers.Authorization = `Bearer ${accessToken}`
|
||||||
accessToken
|
}
|
||||||
)
|
|
||||||
|
const { result: contexts } = await this.request<{ items: Context[] }>(
|
||||||
|
`${this.serverUrl}/compute/contexts?limit=10000`,
|
||||||
|
{ headers }
|
||||||
|
).catch((err) => {
|
||||||
|
throw err
|
||||||
|
})
|
||||||
|
|
||||||
|
const contextsList = contexts.items || []
|
||||||
|
const executableContexts: any[] = []
|
||||||
|
|
||||||
|
const promises = contextsList.map((context: any) => {
|
||||||
|
const linesOfCode = ['%put &=sysuserid;']
|
||||||
|
|
||||||
|
return () =>
|
||||||
|
this.executeScript(
|
||||||
|
`test-${context.name}`,
|
||||||
|
linesOfCode,
|
||||||
|
context.name,
|
||||||
|
accessToken,
|
||||||
|
null,
|
||||||
|
true,
|
||||||
|
true
|
||||||
|
).catch((err) => err)
|
||||||
|
})
|
||||||
|
|
||||||
|
let results: any[] = []
|
||||||
|
|
||||||
|
for (const promise of promises) results.push(await promise())
|
||||||
|
|
||||||
|
results.forEach((result: any, index: number) => {
|
||||||
|
if (result && result.error && result.error.details) {
|
||||||
|
try {
|
||||||
|
const resultParsed = result.error.details
|
||||||
|
|
||||||
|
if (resultParsed && resultParsed.body) {
|
||||||
|
let sysUserId = ''
|
||||||
|
|
||||||
|
const sysUserIdLog = resultParsed.body
|
||||||
|
.split('\n')
|
||||||
|
.find((line: string) => line.startsWith('SYSUSERID='))
|
||||||
|
|
||||||
|
if (sysUserIdLog) {
|
||||||
|
sysUserId = sysUserIdLog.replace('SYSUSERID=', '')
|
||||||
|
|
||||||
|
executableContexts.push({
|
||||||
|
createdBy: contextsList[index].createdBy,
|
||||||
|
id: contextsList[index].id,
|
||||||
|
name: contextsList[index].name,
|
||||||
|
version: contextsList[index].version,
|
||||||
|
attributes: {
|
||||||
|
sysUserId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return executableContexts
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -182,7 +248,7 @@ export class SASViyaApiClient {
|
|||||||
* @param accessToken - an access token for an authorized user.
|
* @param accessToken - an access token for an authorized user.
|
||||||
* @param authorizedUsers - an optional list of authorized user IDs.
|
* @param authorizedUsers - an optional list of authorized user IDs.
|
||||||
*/
|
*/
|
||||||
public async createComputeContext(
|
public async createContext(
|
||||||
contextName: string,
|
contextName: string,
|
||||||
launchContextName: string,
|
launchContextName: string,
|
||||||
sharedAccountId: string,
|
sharedAccountId: string,
|
||||||
@@ -190,35 +256,59 @@ export class SASViyaApiClient {
|
|||||||
accessToken?: string,
|
accessToken?: string,
|
||||||
authorizedUsers?: string[]
|
authorizedUsers?: string[]
|
||||||
) {
|
) {
|
||||||
return await this.contextManager.createComputeContext(
|
if (!contextName) {
|
||||||
contextName,
|
throw new Error('Context name is required.')
|
||||||
launchContextName,
|
}
|
||||||
sharedAccountId,
|
|
||||||
autoExecLines,
|
|
||||||
accessToken,
|
|
||||||
authorizedUsers
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
if (!launchContextName) {
|
||||||
* Creates a launcher context on the given server.
|
throw new Error('Launch context name is required.')
|
||||||
* @param contextName - the name of the context to be created.
|
}
|
||||||
* @param description - the description of the context to be created.
|
|
||||||
* @param launchType - launch type of the context to be created.
|
if (!sharedAccountId) {
|
||||||
* @param accessToken - an access token for an authorized user.
|
throw new Error('Shared account ID is required.')
|
||||||
*/
|
}
|
||||||
public async createLauncherContext(
|
|
||||||
contextName: string,
|
const headers: any = {
|
||||||
description: string,
|
'Content-Type': 'application/json'
|
||||||
launchType = 'direct',
|
}
|
||||||
accessToken?: string
|
|
||||||
) {
|
if (accessToken) {
|
||||||
return await this.contextManager.createLauncherContext(
|
headers.Authorization = `Bearer ${accessToken}`
|
||||||
contextName,
|
}
|
||||||
description,
|
|
||||||
launchType,
|
const requestBody: any = {
|
||||||
accessToken
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -227,15 +317,75 @@ export class SASViyaApiClient {
|
|||||||
* @param editedContext - an object with the properties to be updated.
|
* @param editedContext - an object with the properties to be updated.
|
||||||
* @param accessToken - an access token for an authorized user.
|
* @param accessToken - an access token for an authorized user.
|
||||||
*/
|
*/
|
||||||
public async editComputeContext(
|
public async editContext(
|
||||||
contextName: string,
|
contextName: string,
|
||||||
editedContext: EditContextInput,
|
editedContext: EditContextInput,
|
||||||
accessToken?: string
|
accessToken?: string
|
||||||
) {
|
) {
|
||||||
return await this.contextManager.editComputeContext(
|
if (!contextName) {
|
||||||
|
throw new Error('Invalid context name.')
|
||||||
|
}
|
||||||
|
|
||||||
|
const headers: any = {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (accessToken) {
|
||||||
|
headers.Authorization = `Bearer ${accessToken}`
|
||||||
|
}
|
||||||
|
|
||||||
|
let originalContext
|
||||||
|
|
||||||
|
originalContext = await this.getComputeContextByName(
|
||||||
contextName,
|
contextName,
|
||||||
editedContext,
|
|
||||||
accessToken
|
accessToken
|
||||||
|
).catch((err) => {
|
||||||
|
throw err
|
||||||
|
})
|
||||||
|
|
||||||
|
// Try to find context by id, when context name has been changed.
|
||||||
|
if (!originalContext) {
|
||||||
|
originalContext = await this.getComputeContextById(
|
||||||
|
editedContext.id!,
|
||||||
|
accessToken
|
||||||
|
).catch((err) => {
|
||||||
|
throw err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const { result: context, etag } = await this.request<Context>(
|
||||||
|
`${this.serverUrl}/compute/contexts/${originalContext.id}`,
|
||||||
|
{
|
||||||
|
headers
|
||||||
|
}
|
||||||
|
).catch((err) => {
|
||||||
|
if (err && err.status === 404) {
|
||||||
|
throw new Error(
|
||||||
|
`The context '${contextName}' was not found on this server.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
throw err
|
||||||
|
})
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
...editedContext,
|
||||||
|
attributes: { ...context.attributes, ...editedContext.attributes }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.request<Context>(
|
||||||
|
`${this.serverUrl}/compute/contexts/${context.id}`,
|
||||||
|
updateContextRequest
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,10 +394,29 @@ export class SASViyaApiClient {
|
|||||||
* @param contextName - the name of the context to be deleted.
|
* @param contextName - the name of the context to be deleted.
|
||||||
* @param accessToken - an access token for an authorized user.
|
* @param accessToken - an access token for an authorized user.
|
||||||
*/
|
*/
|
||||||
public async deleteComputeContext(contextName: string, accessToken?: string) {
|
public async deleteContext(contextName: string, accessToken?: string) {
|
||||||
return await this.contextManager.deleteComputeContext(
|
if (!contextName) {
|
||||||
contextName,
|
throw new Error('Invalid context name.')
|
||||||
accessToken
|
}
|
||||||
|
|
||||||
|
const headers: any = {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (accessToken) {
|
||||||
|
headers.Authorization = `Bearer ${accessToken}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const context = await this.getComputeContextByName(contextName, accessToken)
|
||||||
|
|
||||||
|
const deleteContextRequest: RequestInit = {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.request<Context>(
|
||||||
|
`${this.serverUrl}/compute/contexts/${context.id}`,
|
||||||
|
deleteContextRequest
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,12 +426,10 @@ export class SASViyaApiClient {
|
|||||||
* @param linesOfCode - an array of code lines to execute.
|
* @param linesOfCode - an array of code lines to execute.
|
||||||
* @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 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).
|
||||||
* @param waitForResult - when set to true, function will return the session
|
|
||||||
* @param pollOptions - an object that represents poll interval(milliseconds) and maximum amount of attempts. Object example: { MAX_POLL_COUNT: 24 * 60 * 60, POLL_INTERVAL: 1000 }.
|
|
||||||
* @param printPid - a boolean that indicates whether the function should print (PID) of the started job.
|
|
||||||
*/
|
*/
|
||||||
public async executeScript(
|
public async executeScript(
|
||||||
jobPath: string,
|
jobPath: string,
|
||||||
@@ -270,11 +437,8 @@ export class SASViyaApiClient {
|
|||||||
contextName: string,
|
contextName: string,
|
||||||
accessToken?: string,
|
accessToken?: string,
|
||||||
data = null,
|
data = null,
|
||||||
debug: boolean = false,
|
|
||||||
expectWebout = false,
|
expectWebout = false,
|
||||||
waitForResult = true,
|
waitForResult = true
|
||||||
pollOptions?: PollOptions,
|
|
||||||
printPid = false
|
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
try {
|
try {
|
||||||
const headers: any = {
|
const headers: any = {
|
||||||
@@ -294,28 +458,6 @@ export class SASViyaApiClient {
|
|||||||
|
|
||||||
executionSessionId = session!.id
|
executionSessionId = session!.id
|
||||||
|
|
||||||
if (printPid) {
|
|
||||||
const { result: jobIdVariable } = await this.sessionManager.getVariable(
|
|
||||||
executionSessionId,
|
|
||||||
'SYSJOBID',
|
|
||||||
accessToken
|
|
||||||
)
|
|
||||||
|
|
||||||
if (jobIdVariable && jobIdVariable.value) {
|
|
||||||
const relativeJobPath = this.rootFolderName
|
|
||||||
? jobPath.split(this.rootFolderName).join('').replace(/^\//, '')
|
|
||||||
: jobPath
|
|
||||||
|
|
||||||
const logger = new Logger(debug ? LogLevel.Debug : LogLevel.Info)
|
|
||||||
|
|
||||||
logger.info(
|
|
||||||
`Triggered '${relativeJobPath}' with PID ${
|
|
||||||
jobIdVariable.value
|
|
||||||
} at ${timestampToYYYYMMDDHHMMSS()}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const jobArguments: { [key: string]: any } = {
|
const jobArguments: { [key: string]: any } = {
|
||||||
_contextName: contextName,
|
_contextName: contextName,
|
||||||
_OMITJSONLISTING: true,
|
_OMITJSONLISTING: true,
|
||||||
@@ -325,7 +467,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
|
||||||
@@ -393,7 +535,7 @@ export class SASViyaApiClient {
|
|||||||
return session
|
return session
|
||||||
}
|
}
|
||||||
|
|
||||||
if (debug) {
|
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}${
|
||||||
@@ -402,12 +544,7 @@ export class SASViyaApiClient {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const jobStatus = await this.pollJobState(
|
const jobStatus = await this.pollJobState(postedJob, etag, accessToken)
|
||||||
postedJob,
|
|
||||||
etag,
|
|
||||||
accessToken,
|
|
||||||
pollOptions
|
|
||||||
)
|
|
||||||
|
|
||||||
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}`,
|
||||||
@@ -421,7 +558,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`,
|
||||||
{
|
{
|
||||||
@@ -437,7 +574,7 @@ export class SASViyaApiClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (jobStatus === 'failed' || jobStatus === 'error') {
|
if (jobStatus === 'failed' || jobStatus === 'error') {
|
||||||
return Promise.reject({ job: currentJob, log })
|
return Promise.reject({ error: currentJob.error, log })
|
||||||
}
|
}
|
||||||
|
|
||||||
let resultLink
|
let resultLink
|
||||||
@@ -445,7 +582,7 @@ export class SASViyaApiClient {
|
|||||||
if (expectWebout) {
|
if (expectWebout) {
|
||||||
resultLink = `/compute/sessions/${executionSessionId}/filerefs/_webout/content`
|
resultLink = `/compute/sessions/${executionSessionId}/filerefs/_webout/content`
|
||||||
} else {
|
} else {
|
||||||
return { job: currentJob, log }
|
return currentJob
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resultLink) {
|
if (resultLink) {
|
||||||
@@ -469,10 +606,12 @@ export class SASViyaApiClient {
|
|||||||
throw err
|
throw err
|
||||||
})
|
})
|
||||||
|
|
||||||
return Promise.reject({
|
return Promise.reject(
|
||||||
status: 500,
|
new ErrorResponse('Job execution failed.', {
|
||||||
log: log
|
status: 500,
|
||||||
})
|
body: log
|
||||||
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
@@ -496,7 +635,6 @@ export class SASViyaApiClient {
|
|||||||
contextName,
|
contextName,
|
||||||
accessToken,
|
accessToken,
|
||||||
data,
|
data,
|
||||||
debug,
|
|
||||||
false,
|
false,
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
@@ -813,19 +951,14 @@ export class SASViyaApiClient {
|
|||||||
* @param accessToken - an optional access token for an authorized user.
|
* @param accessToken - an optional access token for an authorized user.
|
||||||
* @param waitForResult - a boolean indicating if the function should wait for a result.
|
* @param waitForResult - a boolean indicating if the function should wait for a result.
|
||||||
* @param expectWebout - a boolean indicating whether to expect a _webout response.
|
* @param expectWebout - a boolean indicating whether to expect a _webout response.
|
||||||
* @param pollOptions - an object that represents poll interval(milliseconds) and maximum amount of attempts. Object example: { MAX_POLL_COUNT: 24 * 60 * 60, POLL_INTERVAL: 1000 }.
|
|
||||||
* @param printPid - a boolean that indicates whether the function should print (PID) of the started job.
|
|
||||||
*/
|
*/
|
||||||
public async executeComputeJob(
|
public async executeComputeJob(
|
||||||
sasJob: string,
|
sasJob: string,
|
||||||
contextName: string,
|
contextName: string,
|
||||||
debug?: boolean,
|
|
||||||
data?: any,
|
data?: any,
|
||||||
accessToken?: string,
|
accessToken?: string,
|
||||||
waitForResult = true,
|
waitForResult = true,
|
||||||
expectWebout = false,
|
expectWebout = false
|
||||||
pollOptions?: PollOptions,
|
|
||||||
printPid = false
|
|
||||||
) {
|
) {
|
||||||
if (isRelativePath(sasJob) && !this.rootFolderName) {
|
if (isRelativePath(sasJob) && !this.rootFolderName) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@@ -833,20 +966,23 @@ export class SASViyaApiClient {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const folderPathParts = sasJob.split('/')
|
if (isRelativePath(sasJob)) {
|
||||||
const jobName = folderPathParts.pop()
|
const folderName = sasJob.split('/')[0]
|
||||||
const folderPath = folderPathParts.join('/')
|
await this.populateFolderMap(
|
||||||
const fullFolderPath = isRelativePath(sasJob)
|
`${this.rootFolderName}/${folderName}`,
|
||||||
? `${this.rootFolderName}/${folderPath}`
|
accessToken
|
||||||
: folderPath
|
|
||||||
|
|
||||||
await this.populateFolderMap(fullFolderPath, accessToken)
|
|
||||||
|
|
||||||
const jobFolder = this.folderMap.get(fullFolderPath)
|
|
||||||
if (!jobFolder) {
|
|
||||||
throw new Error(
|
|
||||||
`The folder '${fullFolderPath}' was not found on '${this.serverUrl}'`
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (!this.folderMap.get(`${this.rootFolderName}/${folderName}`)) {
|
||||||
|
throw new Error(
|
||||||
|
`The folder '${folderName}' was not found at '${this.serverUrl}/${this.rootFolderName}'`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const folderPathParts = sasJob.split('/')
|
||||||
|
folderPathParts.pop()
|
||||||
|
const folderPath = folderPathParts.join('/')
|
||||||
|
await this.populateFolderMap(folderPath, accessToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
const headers: any = { 'Content-Type': 'application/json' }
|
const headers: any = { 'Content-Type': 'application/json' }
|
||||||
@@ -854,7 +990,21 @@ export class SASViyaApiClient {
|
|||||||
headers.Authorization = `Bearer ${accessToken}`
|
headers.Authorization = `Bearer ${accessToken}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const jobToExecute = jobFolder?.find((item) => item.name === jobName)
|
let jobToExecute
|
||||||
|
if (isRelativePath(sasJob)) {
|
||||||
|
const folderName = sasJob.split('/')[0]
|
||||||
|
const jobName = sasJob.split('/')[1]
|
||||||
|
const jobFolder = this.folderMap.get(
|
||||||
|
`${this.rootFolderName}/${folderName}`
|
||||||
|
)
|
||||||
|
jobToExecute = jobFolder?.find((item) => item.name === jobName)
|
||||||
|
} else {
|
||||||
|
const folderPathParts = sasJob.split('/')
|
||||||
|
const jobName = folderPathParts.pop()
|
||||||
|
const folderPath = folderPathParts.join('/')
|
||||||
|
const jobFolder = this.folderMap.get(folderPath)
|
||||||
|
jobToExecute = jobFolder?.find((item) => item.name === jobName)
|
||||||
|
}
|
||||||
|
|
||||||
if (!jobToExecute) {
|
if (!jobToExecute) {
|
||||||
throw new Error(`Job was not found.`)
|
throw new Error(`Job was not found.`)
|
||||||
@@ -891,11 +1041,8 @@ export class SASViyaApiClient {
|
|||||||
contextName,
|
contextName,
|
||||||
accessToken,
|
accessToken,
|
||||||
data,
|
data,
|
||||||
debug,
|
|
||||||
expectWebout,
|
expectWebout,
|
||||||
waitForResult,
|
waitForResult
|
||||||
pollOptions,
|
|
||||||
printPid
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -920,28 +1067,52 @@ export class SASViyaApiClient {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const folderPathParts = sasJob.split('/')
|
if (isRelativePath(sasJob)) {
|
||||||
const jobName = folderPathParts.pop()
|
const folderName = sasJob.split('/')[0]
|
||||||
const folderPath = folderPathParts.join('/')
|
await this.populateFolderMap(
|
||||||
const fullFolderPath = isRelativePath(sasJob)
|
`${this.rootFolderName}/${folderName}`,
|
||||||
? `${this.rootFolderName}/${folderPath}`
|
accessToken
|
||||||
: folderPath
|
|
||||||
await this.populateFolderMap(fullFolderPath, accessToken)
|
|
||||||
|
|
||||||
const jobFolder = this.folderMap.get(fullFolderPath)
|
|
||||||
if (!jobFolder) {
|
|
||||||
throw new Error(
|
|
||||||
`The folder '${fullFolderPath}' was not found on '${this.serverUrl}'.`
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
const jobToExecute = jobFolder?.find((item) => item.name === jobName)
|
if (!this.folderMap.get(`${this.rootFolderName}/${folderName}`)) {
|
||||||
|
throw new Error(
|
||||||
|
`The folder '${folderName}' was not found at '${this.serverUrl}/${this.rootFolderName}'.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const folderPathParts = sasJob.split('/')
|
||||||
|
folderPathParts.pop()
|
||||||
|
const folderPath = folderPathParts.join('/')
|
||||||
|
await this.populateFolderMap(folderPath, accessToken)
|
||||||
|
if (!this.folderMap.get(folderPath)) {
|
||||||
|
throw new Error(
|
||||||
|
`The folder '${folderPath}' was not found at '${this.serverUrl}'.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let files: any[] = []
|
let files: any[] = []
|
||||||
if (data && Object.keys(data).length) {
|
if (data && Object.keys(data).length) {
|
||||||
files = await this.uploadTables(data, accessToken)
|
files = await this.uploadTables(data, accessToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let jobToExecute: Job | undefined
|
||||||
|
let jobName: string | undefined
|
||||||
|
let jobPath: string | undefined
|
||||||
|
if (isRelativePath(sasJob)) {
|
||||||
|
const folderName = sasJob.split('/')[0]
|
||||||
|
jobName = sasJob.split('/')[1]
|
||||||
|
jobPath = `${this.rootFolderName}/${folderName}`
|
||||||
|
const jobFolder = this.folderMap.get(jobPath)
|
||||||
|
jobToExecute = jobFolder?.find((item) => item.name === jobName)
|
||||||
|
} else {
|
||||||
|
const folderPathParts = sasJob.split('/')
|
||||||
|
jobName = folderPathParts.pop()
|
||||||
|
jobPath = folderPathParts.join('/')
|
||||||
|
const jobFolder = this.folderMap.get(jobPath)
|
||||||
|
jobToExecute = jobFolder?.find((item) => item.name === jobName)
|
||||||
|
}
|
||||||
|
|
||||||
if (!jobToExecute) {
|
if (!jobToExecute) {
|
||||||
throw new Error(`Job was not found.`)
|
throw new Error(`Job was not found.`)
|
||||||
}
|
}
|
||||||
@@ -966,7 +1137,7 @@ export class SASViyaApiClient {
|
|||||||
|
|
||||||
const jobArguments: { [key: string]: any } = {
|
const jobArguments: { [key: string]: any } = {
|
||||||
_contextName: contextName,
|
_contextName: contextName,
|
||||||
_program: `${fullFolderPath}/${jobName}`,
|
_program: `${jobPath}/${jobName}`,
|
||||||
_webin_file_count: files.length,
|
_webin_file_count: files.length,
|
||||||
_OMITJSONLISTING: true,
|
_OMITJSONLISTING: true,
|
||||||
_OMITJSONLOG: true,
|
_OMITJSONLOG: true,
|
||||||
@@ -1056,7 +1227,7 @@ export class SASViyaApiClient {
|
|||||||
throw new Error(`The path ${path} does not exist on ${this.serverUrl}`)
|
throw new Error(`The path ${path} does not exist on ${this.serverUrl}`)
|
||||||
}
|
}
|
||||||
const { result: members } = await this.request<{ items: any[] }>(
|
const { result: members } = await this.request<{ items: any[] }>(
|
||||||
`${this.serverUrl}/folders/folders/${folder.id}/members?limit=${folder.memberCount}`,
|
`${this.serverUrl}/folders/folders/${folder.id}/members`,
|
||||||
requestInfo
|
requestInfo
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1064,21 +1235,13 @@ export class SASViyaApiClient {
|
|||||||
this.folderMap.set(path, itemsAtRoot)
|
this.folderMap.set(path, itemsAtRoot)
|
||||||
}
|
}
|
||||||
|
|
||||||
// REFACTOR: set default value for 'pollOptions' attribute
|
|
||||||
private async pollJobState(
|
private async pollJobState(
|
||||||
postedJob: any,
|
postedJob: any,
|
||||||
etag: string | null,
|
etag: string | null,
|
||||||
accessToken?: string,
|
accessToken?: string
|
||||||
pollOptions?: PollOptions
|
|
||||||
) {
|
) {
|
||||||
let POLL_INTERVAL = 100
|
const MAX_POLL_COUNT = 1000
|
||||||
let MAX_POLL_COUNT = 1000
|
const POLL_INTERVAL = 100
|
||||||
|
|
||||||
if (pollOptions) {
|
|
||||||
POLL_INTERVAL = pollOptions.POLL_INTERVAL || POLL_INTERVAL
|
|
||||||
MAX_POLL_COUNT = pollOptions.MAX_POLL_COUNT || MAX_POLL_COUNT
|
|
||||||
}
|
|
||||||
|
|
||||||
let postedJobState = ''
|
let postedJobState = ''
|
||||||
let pollCount = 0
|
let pollCount = 0
|
||||||
const headers: any = {
|
const headers: any = {
|
||||||
@@ -1107,8 +1270,6 @@ export class SASViyaApiClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return new Promise(async (resolve, _) => {
|
return new Promise(async (resolve, _) => {
|
||||||
let printedState = ''
|
|
||||||
|
|
||||||
const interval = setInterval(async () => {
|
const interval = setInterval(async () => {
|
||||||
if (
|
if (
|
||||||
postedJobState === 'running' ||
|
postedJobState === 'running' ||
|
||||||
@@ -1116,6 +1277,9 @@ export class SASViyaApiClient {
|
|||||||
postedJobState === 'pending'
|
postedJobState === 'pending'
|
||||||
) {
|
) {
|
||||||
if (stateLink) {
|
if (stateLink) {
|
||||||
|
if (this.debug) {
|
||||||
|
console.log('Polling job status... \n')
|
||||||
|
}
|
||||||
const { result: jobState } = await this.request<string>(
|
const { result: jobState } = await this.request<string>(
|
||||||
`${this.serverUrl}${stateLink.href}?_action=wait&wait=30`,
|
`${this.serverUrl}${stateLink.href}?_action=wait&wait=30`,
|
||||||
{
|
{
|
||||||
@@ -1125,16 +1289,10 @@ export class SASViyaApiClient {
|
|||||||
)
|
)
|
||||||
|
|
||||||
postedJobState = jobState.trim()
|
postedJobState = jobState.trim()
|
||||||
|
if (this.debug) {
|
||||||
if (this.debug && printedState !== postedJobState) {
|
console.log(`Current state: ${postedJobState}\n`)
|
||||||
console.log('Polling job status...')
|
|
||||||
console.log(`Current job state: ${postedJobState}`)
|
|
||||||
|
|
||||||
printedState = postedJobState
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pollCount++
|
pollCount++
|
||||||
|
|
||||||
if (pollCount >= MAX_POLL_COUNT) {
|
if (pollCount >= MAX_POLL_COUNT) {
|
||||||
resolve(postedJobState)
|
resolve(postedJobState)
|
||||||
}
|
}
|
||||||
@@ -1233,10 +1391,26 @@ export class SASViyaApiClient {
|
|||||||
contextName: string,
|
contextName: string,
|
||||||
accessToken?: string
|
accessToken?: string
|
||||||
): Promise<Context> {
|
): Promise<Context> {
|
||||||
return await this.contextManager.getComputeContextByName(
|
const headers: any = {
|
||||||
contextName,
|
'Content-Type': 'application/json'
|
||||||
accessToken
|
}
|
||||||
|
|
||||||
|
if (accessToken) {
|
||||||
|
headers.Authorization = `Bearer ${accessToken}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const { result: contexts } = await this.request<{ items: Context[] }>(
|
||||||
|
`${this.serverUrl}/compute/contexts?filter=eq(name, "${contextName}")`,
|
||||||
|
{ headers }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (!contexts || !(contexts.items && contexts.items.length)) {
|
||||||
|
throw new Error(
|
||||||
|
`The context '${contextName}' was not found at '${this.serverUrl}'.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return contexts.items[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1248,10 +1422,22 @@ export class SASViyaApiClient {
|
|||||||
contextId: string,
|
contextId: string,
|
||||||
accessToken?: string
|
accessToken?: string
|
||||||
): Promise<ContextAllAttributes> {
|
): Promise<ContextAllAttributes> {
|
||||||
return await this.contextManager.getComputeContextById(
|
const headers: any = {
|
||||||
contextId,
|
'Content-Type': 'application/json'
|
||||||
accessToken
|
}
|
||||||
)
|
|
||||||
|
if (accessToken) {
|
||||||
|
headers.Authorization = `Bearer ${accessToken}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const { result: context } = await this.request<ContextAllAttributes>(
|
||||||
|
`${this.serverUrl}/compute/contexts/${contextId}`,
|
||||||
|
{ headers }
|
||||||
|
).catch((err) => {
|
||||||
|
throw err
|
||||||
|
})
|
||||||
|
|
||||||
|
return context
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
123
src/SASjs.ts
123
src/SASjs.ts
@@ -32,8 +32,7 @@ import {
|
|||||||
CsrfToken,
|
CsrfToken,
|
||||||
UploadFile,
|
UploadFile,
|
||||||
EditContextInput,
|
EditContextInput,
|
||||||
ErrorResponse,
|
ErrorResponse
|
||||||
PollOptions
|
|
||||||
} from './types'
|
} from './types'
|
||||||
import { SASViyaApiClient } from './SASViyaApiClient'
|
import { SASViyaApiClient } from './SASViyaApiClient'
|
||||||
import { SAS9ApiClient } from './SAS9ApiClient'
|
import { SAS9ApiClient } from './SAS9ApiClient'
|
||||||
@@ -96,39 +95,12 @@ export default class SASjs {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public async getAllContexts(accessToken: string) {
|
||||||
* Gets compute contexts.
|
this.isMethodSupported('getAllContexts', ServerType.SASViya)
|
||||||
* @param accessToken - an access token for an authorized user.
|
|
||||||
*/
|
|
||||||
public async getComputeContexts(accessToken: string) {
|
|
||||||
this.isMethodSupported('getComputeContexts', ServerType.SASViya)
|
|
||||||
|
|
||||||
return await this.sasViyaApiClient!.getComputeContexts(accessToken)
|
return await this.sasViyaApiClient!.getAllContexts(accessToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets launcher contexts.
|
|
||||||
* @param accessToken - an access token for an authorized user.
|
|
||||||
*/
|
|
||||||
public async getLauncherContexts(accessToken: string) {
|
|
||||||
this.isMethodSupported('getLauncherContexts', ServerType.SASViya)
|
|
||||||
|
|
||||||
return await this.sasViyaApiClient!.getLauncherContexts(accessToken)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets default(system) launcher contexts.
|
|
||||||
*/
|
|
||||||
public getDefaultComputeContexts() {
|
|
||||||
this.isMethodSupported('getDefaultComputeContexts', ServerType.SASViya)
|
|
||||||
|
|
||||||
return this.sasViyaApiClient!.getDefaultComputeContexts()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets executable compute contexts.
|
|
||||||
* @param accessToken - an access token for an authorized user.
|
|
||||||
*/
|
|
||||||
public async getExecutableContexts(accessToken: string) {
|
public async getExecutableContexts(accessToken: string) {
|
||||||
this.isMethodSupported('getExecutableContexts', ServerType.SASViya)
|
this.isMethodSupported('getExecutableContexts', ServerType.SASViya)
|
||||||
|
|
||||||
@@ -144,7 +116,7 @@ export default class SASjs {
|
|||||||
* @param accessToken - an access token for an authorized user.
|
* @param accessToken - an access token for an authorized user.
|
||||||
* @param authorizedUsers - an optional list of authorized user IDs.
|
* @param authorizedUsers - an optional list of authorized user IDs.
|
||||||
*/
|
*/
|
||||||
public async createComputeContext(
|
public async createContext(
|
||||||
contextName: string,
|
contextName: string,
|
||||||
launchContextName: string,
|
launchContextName: string,
|
||||||
sharedAccountId: string,
|
sharedAccountId: string,
|
||||||
@@ -152,9 +124,9 @@ export default class SASjs {
|
|||||||
accessToken: string,
|
accessToken: string,
|
||||||
authorizedUsers?: string[]
|
authorizedUsers?: string[]
|
||||||
) {
|
) {
|
||||||
this.isMethodSupported('createComputeContext', ServerType.SASViya)
|
this.isMethodSupported('createContext', ServerType.SASViya)
|
||||||
|
|
||||||
return await this.sasViyaApiClient!.createComputeContext(
|
return await this.sasViyaApiClient!.createContext(
|
||||||
contextName,
|
contextName,
|
||||||
launchContextName,
|
launchContextName,
|
||||||
sharedAccountId,
|
sharedAccountId,
|
||||||
@@ -164,43 +136,20 @@ export default class SASjs {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a launcher context on the given server.
|
|
||||||
* @param contextName - the name of the context to be created.
|
|
||||||
* @param description - the description of the context to be created.
|
|
||||||
* @param launchType - launch type of the context to be created.
|
|
||||||
* @param accessToken - an access token for an authorized user.
|
|
||||||
*/
|
|
||||||
public async createLauncherContext(
|
|
||||||
contextName: string,
|
|
||||||
description: string,
|
|
||||||
launchType: string,
|
|
||||||
accessToken: string
|
|
||||||
) {
|
|
||||||
this.isMethodSupported('createLauncherContext', ServerType.SASViya)
|
|
||||||
|
|
||||||
return await this.sasViyaApiClient!.createLauncherContext(
|
|
||||||
contextName,
|
|
||||||
description,
|
|
||||||
launchType,
|
|
||||||
accessToken
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates a compute context on the given server.
|
* Updates a compute context on the given server.
|
||||||
* @param contextName - the original name of the context to be deleted.
|
* @param contextName - the original name of the context to be deleted.
|
||||||
* @param editedContext - an object with the properties to be updated.
|
* @param editedContext - an object with the properties to be updated.
|
||||||
* @param accessToken - an access token for an authorized user.
|
* @param accessToken - an access token for an authorized user.
|
||||||
*/
|
*/
|
||||||
public async editComputeContext(
|
public async editContext(
|
||||||
contextName: string,
|
contextName: string,
|
||||||
editedContext: EditContextInput,
|
editedContext: EditContextInput,
|
||||||
accessToken?: string
|
accessToken?: string
|
||||||
) {
|
) {
|
||||||
this.isMethodSupported('editComputeContext', ServerType.SASViya)
|
this.isMethodSupported('editContext', ServerType.SASViya)
|
||||||
|
|
||||||
return await this.sasViyaApiClient!.editComputeContext(
|
return await this.sasViyaApiClient!.editContext(
|
||||||
contextName,
|
contextName,
|
||||||
editedContext,
|
editedContext,
|
||||||
accessToken
|
accessToken
|
||||||
@@ -212,13 +161,10 @@ export default class SASjs {
|
|||||||
* @param contextName - the name of the context to be deleted.
|
* @param contextName - the name of the context to be deleted.
|
||||||
* @param accessToken - an access token for an authorized user.
|
* @param accessToken - an access token for an authorized user.
|
||||||
*/
|
*/
|
||||||
public async deleteComputeContext(contextName: string, accessToken?: string) {
|
public async deleteContext(contextName: string, accessToken?: string) {
|
||||||
this.isMethodSupported('deleteComputeContext', ServerType.SASViya)
|
this.isMethodSupported('deleteContext', ServerType.SASViya)
|
||||||
|
|
||||||
return await this.sasViyaApiClient!.deleteComputeContext(
|
return await this.sasViyaApiClient!.deleteContext(contextName, accessToken)
|
||||||
contextName,
|
|
||||||
accessToken
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -259,20 +205,11 @@ export default class SASjs {
|
|||||||
return await this.sasViyaApiClient!.createSession(contextName, accessToken)
|
return await this.sasViyaApiClient!.createSession(contextName, accessToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes the sas code against given sas server
|
|
||||||
* @param fileName - name of the file to run. It will be converted to path to the file being submitted for execution.
|
|
||||||
* @param linesOfCode - lines of sas code from the file to run.
|
|
||||||
* @param contextName - context name on which code will be run on the server.
|
|
||||||
* @param accessToken - (optional) the access token for authorizing the request.
|
|
||||||
* @param debug - (optional) if true, global debug config will be overriden
|
|
||||||
*/
|
|
||||||
public async executeScriptSASViya(
|
public async executeScriptSASViya(
|
||||||
fileName: string,
|
fileName: string,
|
||||||
linesOfCode: string[],
|
linesOfCode: string[],
|
||||||
contextName: string,
|
contextName: string,
|
||||||
accessToken?: string,
|
accessToken?: string
|
||||||
debug?: boolean
|
|
||||||
) {
|
) {
|
||||||
this.isMethodSupported('executeScriptSASViya', ServerType.SASViya)
|
this.isMethodSupported('executeScriptSASViya', ServerType.SASViya)
|
||||||
|
|
||||||
@@ -281,8 +218,7 @@ export default class SASjs {
|
|||||||
linesOfCode,
|
linesOfCode,
|
||||||
contextName,
|
contextName,
|
||||||
accessToken,
|
accessToken,
|
||||||
null,
|
null
|
||||||
debug ? debug : this.sasjsConfig.debug
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -747,7 +683,10 @@ export default class SASjs {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const members = serviceJson.members
|
const members =
|
||||||
|
serviceJson.members[0].name === 'services'
|
||||||
|
? serviceJson.members[0].members
|
||||||
|
: serviceJson.members
|
||||||
|
|
||||||
await this.createFoldersAndServices(
|
await this.createFoldersAndServices(
|
||||||
appLoc,
|
appLoc,
|
||||||
@@ -772,17 +711,13 @@ export default class SASjs {
|
|||||||
* @param accessToken - a valid access token that is authorised to execute compute jobs.
|
* @param accessToken - a valid access token that is authorised to execute compute jobs.
|
||||||
* The access token is not required when the user is authenticated via the browser.
|
* The access token is not required when the user is authenticated via the browser.
|
||||||
* @param waitForResult - a boolean that indicates whether the function needs to wait for execution to complete.
|
* @param waitForResult - a boolean that indicates whether the function needs to wait for execution to complete.
|
||||||
* @param pollOptions - an object that represents poll interval(milliseconds) and maximum amount of attempts. Object example: { MAX_POLL_COUNT: 24 * 60 * 60, POLL_INTERVAL: 1000 }.
|
|
||||||
* @param printPid - a boolean that indicates whether the function should print (PID) of the started job.
|
|
||||||
*/
|
*/
|
||||||
public async startComputeJob(
|
public async startComputeJob(
|
||||||
sasJob: string,
|
sasJob: string,
|
||||||
data: any,
|
data: any,
|
||||||
config: any = {},
|
config: any = {},
|
||||||
accessToken?: string,
|
accessToken?: string,
|
||||||
waitForResult?: boolean,
|
waitForResult?: boolean
|
||||||
pollOptions?: PollOptions,
|
|
||||||
printPid = false
|
|
||||||
) {
|
) {
|
||||||
config = {
|
config = {
|
||||||
...this.sasjsConfig,
|
...this.sasjsConfig,
|
||||||
@@ -799,13 +734,10 @@ export default class SASjs {
|
|||||||
return this.sasViyaApiClient?.executeComputeJob(
|
return this.sasViyaApiClient?.executeComputeJob(
|
||||||
sasJob,
|
sasJob,
|
||||||
config.contextName,
|
config.contextName,
|
||||||
config.debug,
|
|
||||||
data,
|
data,
|
||||||
accessToken,
|
accessToken,
|
||||||
!!waitForResult,
|
!!waitForResult,
|
||||||
false,
|
false
|
||||||
pollOptions,
|
|
||||||
printPid
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -834,7 +766,6 @@ export default class SASjs {
|
|||||||
?.executeComputeJob(
|
?.executeComputeJob(
|
||||||
sasJob,
|
sasJob,
|
||||||
config.contextName,
|
config.contextName,
|
||||||
config.debug,
|
|
||||||
data,
|
data,
|
||||||
accessToken,
|
accessToken,
|
||||||
waitForResult,
|
waitForResult,
|
||||||
@@ -964,8 +895,8 @@ export default class SASjs {
|
|||||||
|
|
||||||
return responseJson
|
return responseJson
|
||||||
})
|
})
|
||||||
.catch(async (response) => {
|
.catch(async (e) => {
|
||||||
if (needsRetry(JSON.stringify(response))) {
|
if (needsRetry(JSON.stringify(e))) {
|
||||||
if (this.retryCountJeseApi < requestRetryLimit) {
|
if (this.retryCountJeseApi < requestRetryLimit) {
|
||||||
let retryResponse = await this.executeJobViaJesApi(
|
let retryResponse = await this.executeJobViaJesApi(
|
||||||
sasJob,
|
sasJob,
|
||||||
@@ -986,11 +917,11 @@ export default class SASjs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response?.log) {
|
if (e?.log) {
|
||||||
this.appendSasjsRequest(response.log, sasJob, null)
|
this.appendSasjsRequest(e.log, sasJob, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.toString().includes('Job was not found')) {
|
if (e.toString().includes('Job was not found')) {
|
||||||
reject(
|
reject(
|
||||||
new ErrorResponse('Service not found on the server.', {
|
new ErrorResponse('Service not found on the server.', {
|
||||||
sasJob: sasJob
|
sasJob: sasJob
|
||||||
@@ -998,7 +929,7 @@ export default class SASjs {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
reject(new ErrorResponse('Job execution failed.', response))
|
reject(new ErrorResponse('Job execution failed.', e))
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Session, Context, CsrfToken, SessionVariable } from './types'
|
import { Session, Context, CsrfToken } from './types'
|
||||||
import { asyncForEach, makeRequest, isUrl } from './utils'
|
import { asyncForEach, makeRequest, isUrl } from './utils'
|
||||||
import { prefixMessage } from '@sasjs/utils/error'
|
|
||||||
|
|
||||||
const MAX_SESSION_COUNT = 1
|
const MAX_SESSION_COUNT = 1
|
||||||
const RETRY_LIMIT: number = 3
|
const RETRY_LIMIT: number = 3
|
||||||
@@ -23,10 +22,6 @@ export class SessionManager {
|
|||||||
private currentContext: Context | null = null
|
private currentContext: Context | null = null
|
||||||
private csrfToken: CsrfToken | null = null
|
private csrfToken: CsrfToken | null = null
|
||||||
private _debug: boolean = false
|
private _debug: boolean = false
|
||||||
private printedSessionState = {
|
|
||||||
printed: false,
|
|
||||||
state: ''
|
|
||||||
}
|
|
||||||
|
|
||||||
public get debug() {
|
public get debug() {
|
||||||
return this._debug
|
return this._debug
|
||||||
@@ -173,16 +168,10 @@ export class SessionManager {
|
|||||||
const stateLink = session.links.find((l: any) => l.rel === 'state')
|
const stateLink = session.links.find((l: any) => l.rel === 'state')
|
||||||
|
|
||||||
return new Promise(async (resolve, _) => {
|
return new Promise(async (resolve, _) => {
|
||||||
if (
|
if (sessionState === 'pending') {
|
||||||
sessionState === 'pending' ||
|
|
||||||
sessionState === 'running' ||
|
|
||||||
sessionState === ''
|
|
||||||
) {
|
|
||||||
if (stateLink) {
|
if (stateLink) {
|
||||||
if (this.debug && !this.printedSessionState.printed) {
|
if (this.debug) {
|
||||||
console.log('Polling session status...')
|
console.log('Polling session status... \n') // ?
|
||||||
|
|
||||||
this.printedSessionState.printed = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { result: state } = await this.requestSessionStatus<string>(
|
const { result: state } = await this.requestSessionStatus<string>(
|
||||||
@@ -197,11 +186,8 @@ export class SessionManager {
|
|||||||
|
|
||||||
sessionState = state.trim()
|
sessionState = state.trim()
|
||||||
|
|
||||||
if (this.debug && this.printedSessionState.state !== sessionState) {
|
if (this.debug) {
|
||||||
console.log(`Current session state is '${sessionState}'`)
|
console.log(`Current state is '${sessionState}'\n`)
|
||||||
|
|
||||||
this.printedSessionState.state = sessionState
|
|
||||||
this.printedSessionState.printed = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// There is an internal error present in SAS Viya 3.5
|
// There is an internal error present in SAS Viya 3.5
|
||||||
@@ -275,21 +261,4 @@ export class SessionManager {
|
|||||||
throw err
|
throw err
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async getVariable(sessionId: string, variable: string, accessToken?: string) {
|
|
||||||
const getSessionVariable = {
|
|
||||||
method: 'GET',
|
|
||||||
headers: this.getHeaders(accessToken)
|
|
||||||
}
|
|
||||||
|
|
||||||
return await this.request<SessionVariable>(
|
|
||||||
`${this.serverUrl}/compute/sessions/${sessionId}/variables/${variable}`,
|
|
||||||
getSessionVariable
|
|
||||||
).catch((err) => {
|
|
||||||
throw prefixMessage(
|
|
||||||
err,
|
|
||||||
`Error while fetching session variable '${variable}'.`
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,585 +0,0 @@
|
|||||||
import { ContextManager } from '../ContextManager'
|
|
||||||
|
|
||||||
describe('ContextManager', () => {
|
|
||||||
let originalFetch: any
|
|
||||||
let fetchCallNumber = 0
|
|
||||||
|
|
||||||
const fakeGlobalFetch = (fakeResponses: object[]) => {
|
|
||||||
;(global as any).fetch = jest.fn().mockImplementation(() => {
|
|
||||||
const fakeResponse = fakeResponses[fetchCallNumber]
|
|
||||||
|
|
||||||
if (
|
|
||||||
fetchCallNumber !== fakeResponses.length &&
|
|
||||||
fakeResponses.length > 1
|
|
||||||
) {
|
|
||||||
if (fetchCallNumber + 1 === fakeResponses.length) fetchCallNumber = 0
|
|
||||||
else fetchCallNumber += 1
|
|
||||||
} else {
|
|
||||||
fetchCallNumber = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.resolve({
|
|
||||||
ok: true,
|
|
||||||
headers: { get: () => '' },
|
|
||||||
json: () => Promise.resolve(fakeResponse)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const contextManager = new ContextManager(
|
|
||||||
process.env.SERVER_URL as string,
|
|
||||||
() => {}
|
|
||||||
)
|
|
||||||
|
|
||||||
const defaultComputeContexts = contextManager.getDefaultComputeContexts
|
|
||||||
const defaultLauncherContexts = contextManager.getDefaultLauncherContexts
|
|
||||||
|
|
||||||
const getRandomDefaultComputeContext = () =>
|
|
||||||
defaultComputeContexts[
|
|
||||||
Math.floor(Math.random() * defaultComputeContexts.length)
|
|
||||||
]
|
|
||||||
const getRandomDefaultLauncherContext = () =>
|
|
||||||
defaultLauncherContexts[
|
|
||||||
Math.floor(Math.random() * defaultLauncherContexts.length)
|
|
||||||
]
|
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
originalFetch = (global as any).fetch
|
|
||||||
})
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
;(global as any).fetch = originalFetch
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('getComputeContexts', () => {
|
|
||||||
it('should fetch compute contexts', async () => {
|
|
||||||
const sampleComputeContext = {
|
|
||||||
createdBy: 'fake creator',
|
|
||||||
id: 'fakeId',
|
|
||||||
version: 2,
|
|
||||||
name: 'Fake Compute Context',
|
|
||||||
attributes: {}
|
|
||||||
}
|
|
||||||
|
|
||||||
const sampleResponse = {
|
|
||||||
items: [sampleComputeContext]
|
|
||||||
}
|
|
||||||
|
|
||||||
fakeGlobalFetch([sampleResponse])
|
|
||||||
|
|
||||||
await expect(contextManager.getComputeContexts()).resolves.toEqual([
|
|
||||||
sampleComputeContext
|
|
||||||
])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('getLauncherContexts', () => {
|
|
||||||
it('should fetch launcher contexts', async () => {
|
|
||||||
const sampleComputeContext = {
|
|
||||||
createdBy: 'fake creator',
|
|
||||||
id: 'fakeId',
|
|
||||||
version: 2,
|
|
||||||
name: 'Fake Launcher Context',
|
|
||||||
attributes: {}
|
|
||||||
}
|
|
||||||
|
|
||||||
const sampleResponse = {
|
|
||||||
items: [sampleComputeContext]
|
|
||||||
}
|
|
||||||
|
|
||||||
fakeGlobalFetch([sampleResponse])
|
|
||||||
|
|
||||||
await expect(contextManager.getLauncherContexts()).resolves.toEqual([
|
|
||||||
sampleComputeContext
|
|
||||||
])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('createComputeContext', () => {
|
|
||||||
it('should throw an error if context name was not provided', async () => {
|
|
||||||
await expect(
|
|
||||||
contextManager.createComputeContext(
|
|
||||||
'',
|
|
||||||
'Test Launcher Context',
|
|
||||||
'fakeAccountId',
|
|
||||||
[]
|
|
||||||
)
|
|
||||||
).rejects.toEqual(new Error('Context name is required.'))
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should throw an error when attempt to create context with reserved name', async () => {
|
|
||||||
const contextName = getRandomDefaultComputeContext()
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
contextManager.createComputeContext(
|
|
||||||
contextName,
|
|
||||||
'Test Launcher Context',
|
|
||||||
'fakeAccountId',
|
|
||||||
[]
|
|
||||||
)
|
|
||||||
).rejects.toEqual(
|
|
||||||
new Error(`Compute context '${contextName}' already exists.`)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should throw an error if context already exists', async () => {
|
|
||||||
const contextName = 'Existing Compute Context'
|
|
||||||
|
|
||||||
const sampleComputeContext = {
|
|
||||||
createdBy: 'fake creator',
|
|
||||||
id: 'fakeId',
|
|
||||||
version: 2,
|
|
||||||
name: contextName,
|
|
||||||
attributes: {}
|
|
||||||
}
|
|
||||||
|
|
||||||
const sampleResponse = {
|
|
||||||
items: [sampleComputeContext]
|
|
||||||
}
|
|
||||||
|
|
||||||
fakeGlobalFetch([sampleResponse])
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
contextManager.createComputeContext(
|
|
||||||
contextName,
|
|
||||||
'Test Launcher Context',
|
|
||||||
'fakeAccountId',
|
|
||||||
[]
|
|
||||||
)
|
|
||||||
).rejects.toEqual(
|
|
||||||
new Error(`Compute context '${contextName}' already exists.`)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should create compute context without launcher context', async () => {
|
|
||||||
const contextName = 'New Compute Context'
|
|
||||||
|
|
||||||
const sampleExistingComputeContext = {
|
|
||||||
createdBy: 'fake creator',
|
|
||||||
id: 'fakeId',
|
|
||||||
version: 2,
|
|
||||||
name: 'Existing Compute Context',
|
|
||||||
attributes: {}
|
|
||||||
}
|
|
||||||
const sampleNewComputeContext = {
|
|
||||||
createdBy: 'fake creator',
|
|
||||||
id: 'fakeId',
|
|
||||||
version: 2,
|
|
||||||
name: contextName,
|
|
||||||
attributes: {}
|
|
||||||
}
|
|
||||||
|
|
||||||
const sampleResponseExistingComputeContexts = {
|
|
||||||
items: [sampleExistingComputeContext]
|
|
||||||
}
|
|
||||||
const sampleResponseCreatedComputeContext = {
|
|
||||||
items: [sampleNewComputeContext]
|
|
||||||
}
|
|
||||||
|
|
||||||
fakeGlobalFetch([
|
|
||||||
sampleResponseExistingComputeContexts,
|
|
||||||
sampleResponseCreatedComputeContext
|
|
||||||
])
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
contextManager.createComputeContext(
|
|
||||||
contextName,
|
|
||||||
'',
|
|
||||||
'fakeAccountId',
|
|
||||||
[]
|
|
||||||
)
|
|
||||||
).resolves.toEqual({
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
attributes: {},
|
|
||||||
createdBy: 'fake creator',
|
|
||||||
id: 'fakeId',
|
|
||||||
name: contextName,
|
|
||||||
version: 2
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should create compute context with default launcher context', async () => {
|
|
||||||
const contextName = 'New Compute Context'
|
|
||||||
|
|
||||||
const sampleExistingComputeContext = {
|
|
||||||
createdBy: 'fake creator',
|
|
||||||
id: 'fakeId',
|
|
||||||
version: 2,
|
|
||||||
name: 'Existing Compute Context',
|
|
||||||
attributes: {}
|
|
||||||
}
|
|
||||||
const sampleNewComputeContext = {
|
|
||||||
createdBy: 'fake creator',
|
|
||||||
id: 'fakeId',
|
|
||||||
version: 2,
|
|
||||||
name: contextName,
|
|
||||||
attributes: {}
|
|
||||||
}
|
|
||||||
|
|
||||||
const sampleResponseExistingComputeContexts = {
|
|
||||||
items: [sampleExistingComputeContext]
|
|
||||||
}
|
|
||||||
const sampleResponseCreatedComputeContext = {
|
|
||||||
items: [sampleNewComputeContext]
|
|
||||||
}
|
|
||||||
|
|
||||||
fakeGlobalFetch([
|
|
||||||
sampleResponseExistingComputeContexts,
|
|
||||||
sampleResponseCreatedComputeContext
|
|
||||||
])
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
contextManager.createComputeContext(
|
|
||||||
contextName,
|
|
||||||
getRandomDefaultLauncherContext(),
|
|
||||||
'fakeAccountId',
|
|
||||||
[]
|
|
||||||
)
|
|
||||||
).resolves.toEqual({
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
attributes: {},
|
|
||||||
createdBy: 'fake creator',
|
|
||||||
id: 'fakeId',
|
|
||||||
name: contextName,
|
|
||||||
version: 2
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should create compute context with not existing launcher context', async () => {
|
|
||||||
const computeContextName = 'New Compute Context'
|
|
||||||
const launcherContextName = 'New Launcher Context'
|
|
||||||
|
|
||||||
const sampleExistingComputeContext = {
|
|
||||||
createdBy: 'fake creator',
|
|
||||||
id: 'fakeId',
|
|
||||||
version: 2,
|
|
||||||
name: 'Existing Compute Context',
|
|
||||||
attributes: {}
|
|
||||||
}
|
|
||||||
const sampleNewComputeContext = {
|
|
||||||
createdBy: 'fake creator',
|
|
||||||
id: 'fakeId',
|
|
||||||
version: 2,
|
|
||||||
name: computeContextName,
|
|
||||||
attributes: {}
|
|
||||||
}
|
|
||||||
const sampleNewLauncherContext = {
|
|
||||||
createdBy: 'fake creator',
|
|
||||||
id: 'fakeId',
|
|
||||||
version: 2,
|
|
||||||
name: launcherContextName,
|
|
||||||
attributes: {}
|
|
||||||
}
|
|
||||||
|
|
||||||
const sampleResponseExistingComputeContexts = {
|
|
||||||
items: [sampleExistingComputeContext]
|
|
||||||
}
|
|
||||||
const sampleResponseCreatedLauncherContext = {
|
|
||||||
items: [sampleNewLauncherContext]
|
|
||||||
}
|
|
||||||
const sampleResponseCreatedComputeContext = {
|
|
||||||
items: [sampleNewComputeContext]
|
|
||||||
}
|
|
||||||
|
|
||||||
fakeGlobalFetch([
|
|
||||||
sampleResponseExistingComputeContexts,
|
|
||||||
sampleResponseCreatedLauncherContext,
|
|
||||||
sampleResponseCreatedComputeContext
|
|
||||||
])
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
contextManager.createComputeContext(
|
|
||||||
computeContextName,
|
|
||||||
launcherContextName,
|
|
||||||
'fakeAccountId',
|
|
||||||
[]
|
|
||||||
)
|
|
||||||
).resolves.toEqual({
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
attributes: {},
|
|
||||||
createdBy: 'fake creator',
|
|
||||||
id: 'fakeId',
|
|
||||||
name: computeContextName,
|
|
||||||
version: 2
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('createLauncherContext', () => {
|
|
||||||
it('should throw an error if context name was not provided', async () => {
|
|
||||||
await expect(
|
|
||||||
contextManager.createLauncherContext('', 'Test Description')
|
|
||||||
).rejects.toEqual(new Error('Context name is required.'))
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should throw an error when attempt to create context with reserved name', async () => {
|
|
||||||
const contextName = getRandomDefaultLauncherContext()
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
contextManager.createLauncherContext(contextName, 'Test Description')
|
|
||||||
).rejects.toEqual(
|
|
||||||
new Error(`Launcher context '${contextName}' already exists.`)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should throw an error if context already exists', async () => {
|
|
||||||
const contextName = 'Existing Launcher Context'
|
|
||||||
|
|
||||||
const sampleLauncherContext = {
|
|
||||||
createdBy: 'fake creator',
|
|
||||||
id: 'fakeId',
|
|
||||||
version: 2,
|
|
||||||
name: contextName,
|
|
||||||
attributes: {}
|
|
||||||
}
|
|
||||||
|
|
||||||
const sampleResponse = {
|
|
||||||
items: [sampleLauncherContext]
|
|
||||||
}
|
|
||||||
|
|
||||||
fakeGlobalFetch([sampleResponse])
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
contextManager.createLauncherContext(contextName, 'Test Description')
|
|
||||||
).rejects.toEqual(
|
|
||||||
new Error(`Launcher context '${contextName}' already exists.`)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should create launcher context', async () => {
|
|
||||||
const contextName = 'New Launcher Context'
|
|
||||||
|
|
||||||
const sampleExistingLauncherContext = {
|
|
||||||
createdBy: 'fake creator',
|
|
||||||
id: 'fakeId',
|
|
||||||
version: 2,
|
|
||||||
name: 'Existing Launcher Context',
|
|
||||||
attributes: {}
|
|
||||||
}
|
|
||||||
const sampleNewLauncherContext = {
|
|
||||||
createdBy: 'fake creator',
|
|
||||||
id: 'fakeId',
|
|
||||||
version: 2,
|
|
||||||
name: contextName,
|
|
||||||
attributes: {}
|
|
||||||
}
|
|
||||||
|
|
||||||
const sampleResponseExistingLauncherContext = {
|
|
||||||
items: [sampleExistingLauncherContext]
|
|
||||||
}
|
|
||||||
const sampleResponseCreatedLauncherContext = {
|
|
||||||
items: [sampleNewLauncherContext]
|
|
||||||
}
|
|
||||||
|
|
||||||
fakeGlobalFetch([
|
|
||||||
sampleResponseExistingLauncherContext,
|
|
||||||
sampleResponseCreatedLauncherContext
|
|
||||||
])
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
contextManager.createLauncherContext(contextName, 'Test Description')
|
|
||||||
).resolves.toEqual({
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
attributes: {},
|
|
||||||
createdBy: 'fake creator',
|
|
||||||
id: 'fakeId',
|
|
||||||
name: contextName,
|
|
||||||
version: 2
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('editComputeContext', () => {
|
|
||||||
const editedContext = {
|
|
||||||
name: 'updated name',
|
|
||||||
description: 'updated description',
|
|
||||||
id: 'someId'
|
|
||||||
}
|
|
||||||
|
|
||||||
it('should throw an error if context name was not provided', async () => {
|
|
||||||
await expect(
|
|
||||||
contextManager.editComputeContext('', editedContext)
|
|
||||||
).rejects.toEqual(new Error('Context name is required.'))
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should throw an error when attempt to edit context with reserved name', async () => {
|
|
||||||
const contextName = getRandomDefaultComputeContext()
|
|
||||||
|
|
||||||
let editError: Error = { name: '', message: '' }
|
|
||||||
|
|
||||||
try {
|
|
||||||
contextManager.isDefaultContext(
|
|
||||||
contextName,
|
|
||||||
defaultComputeContexts,
|
|
||||||
'Editing default SAS compute contexts is not allowed.',
|
|
||||||
true
|
|
||||||
)
|
|
||||||
} catch (error) {
|
|
||||||
editError = error
|
|
||||||
}
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
contextManager.editComputeContext(contextName, editedContext)
|
|
||||||
).rejects.toEqual(editError)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should edit context if founded by name', async () => {
|
|
||||||
const sampleComputeContext = {
|
|
||||||
createdBy: 'fake creator',
|
|
||||||
id: 'fakeId',
|
|
||||||
version: 2,
|
|
||||||
name: editedContext.name,
|
|
||||||
attributes: {}
|
|
||||||
}
|
|
||||||
|
|
||||||
const sampleResponseGetComputeContextByName = {
|
|
||||||
items: [sampleComputeContext]
|
|
||||||
}
|
|
||||||
|
|
||||||
fakeGlobalFetch([sampleResponseGetComputeContextByName])
|
|
||||||
|
|
||||||
const expectedResponse = {
|
|
||||||
etag: '',
|
|
||||||
result: sampleResponseGetComputeContextByName
|
|
||||||
}
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
contextManager.editComputeContext(editedContext.name, editedContext)
|
|
||||||
).resolves.toEqual(expectedResponse)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('getExecutableContexts', () => {
|
|
||||||
it('should return executable contexts', async () => {
|
|
||||||
const sampleComputeContext = {
|
|
||||||
createdBy: 'fake creator',
|
|
||||||
id: 'fakeId',
|
|
||||||
version: 2,
|
|
||||||
name: 'Executable Compute Context',
|
|
||||||
attributes: {}
|
|
||||||
}
|
|
||||||
|
|
||||||
const sampleResponse = {
|
|
||||||
items: [sampleComputeContext]
|
|
||||||
}
|
|
||||||
|
|
||||||
fakeGlobalFetch([sampleResponse])
|
|
||||||
|
|
||||||
const user = 'testUser'
|
|
||||||
|
|
||||||
const fakedExecuteScript = async () => {
|
|
||||||
return Promise.resolve({ log: `SYSUSERID=${user}` })
|
|
||||||
}
|
|
||||||
|
|
||||||
const expectedResponse = [
|
|
||||||
{
|
|
||||||
...sampleComputeContext,
|
|
||||||
attributes: { sysUserId: user }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
contextManager.getExecutableContexts(fakedExecuteScript)
|
|
||||||
).resolves.toEqual(expectedResponse)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should not return executable contexts', async () => {
|
|
||||||
const sampleComputeContext = {
|
|
||||||
createdBy: 'fake creator',
|
|
||||||
id: 'fakeId',
|
|
||||||
version: 2,
|
|
||||||
name: 'Not Executable Compute Context',
|
|
||||||
attributes: {}
|
|
||||||
}
|
|
||||||
|
|
||||||
const sampleResponse = {
|
|
||||||
items: [sampleComputeContext]
|
|
||||||
}
|
|
||||||
|
|
||||||
fakeGlobalFetch([sampleResponse])
|
|
||||||
|
|
||||||
const fakedExecuteScript = async () => {
|
|
||||||
return Promise.resolve({ log: '' })
|
|
||||||
}
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
contextManager.getExecutableContexts(fakedExecuteScript)
|
|
||||||
).resolves.toEqual([])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('deleteComputeContext', () => {
|
|
||||||
it('should throw an error if context name was not provided', async () => {
|
|
||||||
await expect(contextManager.deleteComputeContext('')).rejects.toEqual(
|
|
||||||
new Error('Context name is required.')
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should throw an error when attempt to delete context with reserved name', async () => {
|
|
||||||
const contextName = getRandomDefaultComputeContext()
|
|
||||||
|
|
||||||
let deleteError: Error = { name: '', message: '' }
|
|
||||||
|
|
||||||
try {
|
|
||||||
contextManager.isDefaultContext(
|
|
||||||
contextName,
|
|
||||||
defaultComputeContexts,
|
|
||||||
'Deleting default SAS compute contexts is not allowed.',
|
|
||||||
true
|
|
||||||
)
|
|
||||||
} catch (error) {
|
|
||||||
deleteError = error
|
|
||||||
}
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
contextManager.deleteComputeContext(contextName)
|
|
||||||
).rejects.toEqual(deleteError)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should delete context', async () => {
|
|
||||||
const contextName = 'Compute Context To Delete'
|
|
||||||
|
|
||||||
const sampleComputeContext = {
|
|
||||||
createdBy: 'fake creator',
|
|
||||||
id: 'fakeId',
|
|
||||||
version: 2,
|
|
||||||
name: contextName,
|
|
||||||
attributes: {}
|
|
||||||
}
|
|
||||||
|
|
||||||
const sampleResponseGetComputeContextByName = {
|
|
||||||
items: [sampleComputeContext]
|
|
||||||
}
|
|
||||||
|
|
||||||
const sampleResponseDeletedContext = {
|
|
||||||
items: [sampleComputeContext]
|
|
||||||
}
|
|
||||||
|
|
||||||
fakeGlobalFetch([
|
|
||||||
sampleResponseGetComputeContextByName,
|
|
||||||
sampleResponseDeletedContext
|
|
||||||
])
|
|
||||||
|
|
||||||
const expectedResponse = {
|
|
||||||
etag: '',
|
|
||||||
result: sampleResponseDeletedContext
|
|
||||||
}
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
contextManager.deleteComputeContext(contextName)
|
|
||||||
).resolves.toEqual(expectedResponse)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,53 +1,38 @@
|
|||||||
|
import dotenv from 'dotenv'
|
||||||
import { SessionManager } from '../SessionManager'
|
import { SessionManager } from '../SessionManager'
|
||||||
import * as dotenv from 'dotenv'
|
import { CsrfToken } from '../types'
|
||||||
|
|
||||||
describe('SessionManager', () => {
|
describe('SessionManager', () => {
|
||||||
dotenv.config()
|
const setCsrfToken = jest
|
||||||
|
.fn()
|
||||||
let originalFetch: any
|
.mockImplementation((csrfToken: CsrfToken) => console.log(csrfToken))
|
||||||
|
|
||||||
const sessionManager = new SessionManager(
|
|
||||||
process.env.SERVER_URL as string,
|
|
||||||
process.env.DEFAULT_COMPUTE_CONTEXT as string,
|
|
||||||
() => {}
|
|
||||||
)
|
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
originalFetch = (global as any).fetch
|
dotenv.config()
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(() => {
|
it('should instantiate', () => {
|
||||||
;(global as any).fetch = originalFetch
|
const sessionManager = new SessionManager(
|
||||||
|
'http://test-server.com',
|
||||||
|
'test context',
|
||||||
|
setCsrfToken
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(sessionManager).toBeInstanceOf(SessionManager)
|
||||||
|
expect(sessionManager.debug).toBeFalsy()
|
||||||
|
expect((sessionManager as any).serverUrl).toEqual('http://test-server.com')
|
||||||
|
expect((sessionManager as any).contextName).toEqual('test context')
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('getVariable', () => {
|
it('should set the debug flag', () => {
|
||||||
it('should fetch session variable', async () => {
|
const sessionManager = new SessionManager(
|
||||||
const sampleResponse = {
|
'http://test-server.com',
|
||||||
ok: true,
|
'test context',
|
||||||
links: [],
|
setCsrfToken
|
||||||
name: 'SYSJOBID',
|
)
|
||||||
scope: 'GLOBAL',
|
|
||||||
value: '25218',
|
|
||||||
version: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
;(global as any).fetch = jest.fn().mockImplementation(() =>
|
sessionManager.debug = true
|
||||||
Promise.resolve({
|
|
||||||
ok: true,
|
|
||||||
headers: { get: () => '' },
|
|
||||||
json: () => Promise.resolve(sampleResponse)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
const expectedResponse = { etag: '', result: sampleResponse }
|
expect(sessionManager.debug).toBeTruthy()
|
||||||
|
|
||||||
await expect(
|
|
||||||
sessionManager.getVariable(
|
|
||||||
'fakeSessionId',
|
|
||||||
'SYSJOBID',
|
|
||||||
'fakeAccessToken'
|
|
||||||
)
|
|
||||||
).resolves.toEqual(expectedResponse)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -4,5 +4,4 @@ export interface Folder {
|
|||||||
id: string
|
id: string
|
||||||
uri: string
|
uri: string
|
||||||
links: Link[]
|
links: Link[]
|
||||||
memberCount: number
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
export interface PollOptions {
|
|
||||||
MAX_POLL_COUNT?: number
|
|
||||||
POLL_INTERVAL?: number
|
|
||||||
}
|
|
||||||
@@ -9,7 +9,3 @@ export interface Session {
|
|||||||
}
|
}
|
||||||
creationTimeStamp: string
|
creationTimeStamp: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SessionVariable {
|
|
||||||
value: string
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -12,4 +12,3 @@ export * from './SASjsWaitingRequest'
|
|||||||
export * from './ServerType'
|
export * from './ServerType'
|
||||||
export * from './Session'
|
export * from './Session'
|
||||||
export * from './UploadFile'
|
export * from './UploadFile'
|
||||||
export * from './PollOptions'
|
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ const browserConfig = {
|
|||||||
const nodeConfig = {
|
const nodeConfig = {
|
||||||
...browserConfig,
|
...browserConfig,
|
||||||
target: 'node',
|
target: 'node',
|
||||||
entry: './node/index.ts',
|
|
||||||
output: {
|
output: {
|
||||||
...browserConfig.output,
|
...browserConfig.output,
|
||||||
path: path.resolve(__dirname, 'build', 'node')
|
path: path.resolve(__dirname, 'build', 'node')
|
||||||
|
|||||||
Reference in New Issue
Block a user