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

Compare commits

...

11 Commits

Author SHA1 Message Date
Krishna Acondy
619833db29 Merge pull request #56 from sasjs/performance-improvements
fix(*): Performance improvements
2020-09-01 11:49:34 +01:00
Krishna Acondy
a587d9f6de chore(ci): add lint action 2020-09-01 11:20:46 +01:00
Krishna Acondy
83fb89f779 fix(*): cache job definition code after first fetch, make initial state request before poll 2020-09-01 11:13:52 +01:00
Krishna Acondy
6b98bbce7c chore(types): add code property to Job model 2020-09-01 11:12:56 +01:00
Krishna Acondy
3c2487e423 Merge pull request #49 from sasjs/dependabot/npm_and_yarn/ts-loader-8.0.3
chore(deps-dev): bump ts-loader from 8.0.2 to 8.0.3
2020-08-31 10:23:01 +01:00
dependabot-preview[bot]
0d52af5375 chore(deps-dev): bump ts-loader from 8.0.2 to 8.0.3
Bumps [ts-loader](https://github.com/TypeStrong/ts-loader) from 8.0.2 to 8.0.3.
- [Release notes](https://github.com/TypeStrong/ts-loader/releases)
- [Changelog](https://github.com/TypeStrong/ts-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/TypeStrong/ts-loader/compare/v8.0.2...v8.0.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-08-31 09:04:08 +00:00
Krishna Acondy
d0da343efc Merge pull request #53 from sasjs/dependabot/npm_and_yarn/prettier-2.1.1
chore(deps-dev): bump prettier from 2.0.5 to 2.1.1
2020-08-31 09:58:58 +01:00
dependabot-preview[bot]
54f401a319 chore(deps-dev): bump prettier from 2.0.5 to 2.1.1
Bumps [prettier](https://github.com/prettier/prettier) from 2.0.5 to 2.1.1.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/master/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/2.0.5...2.1.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-08-31 08:24:44 +00:00
Krishna Acondy
5efcb11b7d chore(*): add lint fix command 2020-08-31 09:22:21 +01:00
Krishna Acondy
929d7b993b chore(*): add .prettierrc, fix formatting 2020-08-31 09:15:02 +01:00
Allan Bowe
688221c042 Update example.html 2020-08-28 20:11:04 +02:00
28 changed files with 288 additions and 240 deletions

View File

@@ -21,7 +21,11 @@ jobs:
uses: actions/setup-node@v1 uses: actions/setup-node@v1
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
- run: npm ci - name: Install Dependencies
- run: npm run package:lib run: npm ci
- name: Check code style
run: npm run lint
- name: Build Package
run: npm run package:lib
env: env:
CI: true CI: true

View File

@@ -16,6 +16,8 @@ jobs:
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Install Dependencies - name: Install Dependencies
run: npm ci run: npm ci
- name: Check code style
run: npm run lint
- name: Build Project - name: Build Project
run: npm run build run: npm run build
- name: Semantic Release - name: Semantic Release

6
.prettierrc Normal file
View File

@@ -0,0 +1,6 @@
{
"trailingComma": "none",
"tabWidth": 2,
"semi": true,
"singleQuote": false
}

View File

@@ -1,7 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<script src="https://cdn.jsdelivr.net/combine/npm/chart.js@2.9.3,npm/jquery@3.5.1,npm/@sasjs/adapter@1.0.6"></script> <script src="https://cdn.jsdelivr.net/combine/npm/chart.js@2.9.3,npm/jquery@3.5.1,npm/@sasjs/adapter@1"></script>
<script> <script>
var sasJs = new SASjs.default({ var sasJs = new SASjs.default({
appLoc: "/Public/app/readme" appLoc: "/Public/app/readme"
@@ -106,4 +106,4 @@
<canvas id="myChart" style="display: none;"></canvas> <canvas id="myChart" style="display: none;"></canvas>
</div> </div>
</body> </body>
</head> </head>

12
package-lock.json generated
View File

@@ -12495,9 +12495,9 @@
"dev": true "dev": true
}, },
"prettier": { "prettier": {
"version": "2.0.5", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.5.tgz", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.1.1.tgz",
"integrity": "sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg==", "integrity": "sha512-9bY+5ZWCfqj3ghYBLxApy2zf6m+NJo5GzmLTpr9FsApsfjriNnS2dahWReHMi7qNPhhHl9SYHJs2cHZLgexNIw==",
"dev": true "dev": true
}, },
"pretty-format": { "pretty-format": {
@@ -14258,9 +14258,9 @@
} }
}, },
"ts-loader": { "ts-loader": {
"version": "8.0.2", "version": "8.0.3",
"resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.0.2.tgz", "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.0.3.tgz",
"integrity": "sha512-oYT7wOTUawYXQ8XIDsRhziyW0KUEV38jISYlE+9adP6tDtG+O5GkRe4QKQXrHVH4mJJ88DysvEtvGP65wMLlhg==", "integrity": "sha512-wsqfnVdB7xQiqhqbz2ZPLGHLPZbHVV5Qn/MNFZkCFxRU1miDyxKORucDGxKtsQJ63Rfza0udiUxWF5nHY6bpdQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"chalk": "^2.3.0", "chalk": "^2.3.0",

View File

@@ -5,8 +5,8 @@
"build": "rimraf build && webpack", "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",
"format": "prettier --write \"src/**/*.ts\" \"src/**/*.js\"", "lint:fix": "npx prettier --write 'src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}'",
"lint": "tslint -p tsconfig.json", "lint": "npx prettier --check 'src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}'",
"test": "jest", "test": "jest",
"prepublishOnly": "cp -r ./build/* . && rm -rf ./build", "prepublishOnly": "cp -r ./build/* . && rm -rf ./build",
"postpublish": "git clean -fd", "postpublish": "git clean -fd",
@@ -41,11 +41,10 @@
"cp": "^0.2.0", "cp": "^0.2.0",
"jest": "^25.5.4", "jest": "^25.5.4",
"path": "^0.12.7", "path": "^0.12.7",
"prettier": "^2.0.5",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"semantic-release": "^17.1.1", "semantic-release": "^17.1.1",
"ts-jest": "^25.5.1", "ts-jest": "^25.5.1",
"ts-loader": "^8.0.2", "ts-loader": "^8.0.3",
"tslint": "^6.1.3", "tslint": "^6.1.3",
"tslint-config-prettier": "^1.18.0", "tslint-config-prettier": "^1.18.0",
"typedoc": "^0.17.8", "typedoc": "^0.17.8",

6
sasjs-tests/.prettierrc Normal file
View File

@@ -0,0 +1,6 @@
{
"trailingComma": "none",
"tabWidth": 2,
"semi": true,
"singleQuote": false
}

View File

@@ -1,8 +1,8 @@
import React from 'react'; import React from "react";
import { render } from '@testing-library/react'; import { render } from "@testing-library/react";
import App from './App'; import App from "./App";
test('renders learn react link', () => { test("renders learn react link", () => {
const { getByText } = render(<App />); const { getByText } = render(<App />);
const linkElement = getByText(/learn react/i); const linkElement = getByText(/learn react/i);
expect(linkElement).toBeInTheDocument(); expect(linkElement).toBeInTheDocument();

View File

@@ -17,7 +17,7 @@ const App = (): ReactElement<{}> => {
sendArrTests(adapter), sendArrTests(adapter),
sendObjTests(adapter), sendObjTests(adapter),
specialCaseTests(adapter), specialCaseTests(adapter),
sasjsRequestTests(adapter), sasjsRequestTests(adapter)
]); ]);
} }
}, [adapter, config]); }, [adapter, config]);

View File

@@ -1,8 +1,7 @@
body { body {
margin: 0; margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans",
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", "Droid Sans", "Helvetica Neue", sans-serif;
sans-serif;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
background-color: #1f2027; background-color: #1f2027;
@@ -10,8 +9,7 @@ body {
} }
* { * {
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace;
monospace;
} }
input { input {

View File

@@ -11,9 +11,9 @@
// opt-in, read https://bit.ly/CRA-PWA // opt-in, read https://bit.ly/CRA-PWA
const isLocalhost = Boolean( const isLocalhost = Boolean(
window.location.hostname === 'localhost' || window.location.hostname === "localhost" ||
// [::1] is the IPv6 localhost address. // [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' || window.location.hostname === "[::1]" ||
// 127.0.0.0/8 are considered localhost for IPv4. // 127.0.0.0/8 are considered localhost for IPv4.
window.location.hostname.match( window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
@@ -21,7 +21,7 @@ const isLocalhost = Boolean(
); );
export function register(config) { export function register(config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) {
// The URL constructor is available in all browsers that support SW. // The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
if (publicUrl.origin !== window.location.origin) { if (publicUrl.origin !== window.location.origin) {
@@ -31,7 +31,7 @@ export function register(config) {
return; return;
} }
window.addEventListener('load', () => { window.addEventListener("load", () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) { if (isLocalhost) {
@@ -42,8 +42,8 @@ export function register(config) {
// service worker/PWA documentation. // service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => { navigator.serviceWorker.ready.then(() => {
console.log( console.log(
'This web app is being served cache-first by a service ' + "This web app is being served cache-first by a service " +
'worker. To learn more, visit https://bit.ly/CRA-PWA' "worker. To learn more, visit https://bit.ly/CRA-PWA"
); );
}); });
} else { } else {
@@ -57,21 +57,21 @@ export function register(config) {
function registerValidSW(swUrl, config) { function registerValidSW(swUrl, config) {
navigator.serviceWorker navigator.serviceWorker
.register(swUrl) .register(swUrl)
.then(registration => { .then((registration) => {
registration.onupdatefound = () => { registration.onupdatefound = () => {
const installingWorker = registration.installing; const installingWorker = registration.installing;
if (installingWorker == null) { if (installingWorker == null) {
return; return;
} }
installingWorker.onstatechange = () => { installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') { if (installingWorker.state === "installed") {
if (navigator.serviceWorker.controller) { if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched, // At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older // but the previous service worker will still serve the older
// content until all client tabs are closed. // content until all client tabs are closed.
console.log( console.log(
'New content is available and will be used when all ' + "New content is available and will be used when all " +
'tabs for this page are closed. See https://bit.ly/CRA-PWA.' "tabs for this page are closed. See https://bit.ly/CRA-PWA."
); );
// Execute callback // Execute callback
@@ -82,7 +82,7 @@ function registerValidSW(swUrl, config) {
// At this point, everything has been precached. // At this point, everything has been precached.
// It's the perfect time to display a // It's the perfect time to display a
// "Content is cached for offline use." message. // "Content is cached for offline use." message.
console.log('Content is cached for offline use.'); console.log("Content is cached for offline use.");
// Execute callback // Execute callback
if (config && config.onSuccess) { if (config && config.onSuccess) {
@@ -93,25 +93,25 @@ function registerValidSW(swUrl, config) {
}; };
}; };
}) })
.catch(error => { .catch((error) => {
console.error('Error during service worker registration:', error); console.error("Error during service worker registration:", error);
}); });
} }
function checkValidServiceWorker(swUrl, config) { function checkValidServiceWorker(swUrl, config) {
// Check if the service worker can be found. If it can't reload the page. // Check if the service worker can be found. If it can't reload the page.
fetch(swUrl, { fetch(swUrl, {
headers: { 'Service-Worker': 'script' }, headers: { "Service-Worker": "script" }
}) })
.then(response => { .then((response) => {
// Ensure service worker exists, and that we really are getting a JS file. // Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type'); const contentType = response.headers.get("content-type");
if ( if (
response.status === 404 || response.status === 404 ||
(contentType != null && contentType.indexOf('javascript') === -1) (contentType != null && contentType.indexOf("javascript") === -1)
) { ) {
// No service worker found. Probably a different app. Reload the page. // No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => { navigator.serviceWorker.ready.then((registration) => {
registration.unregister().then(() => { registration.unregister().then(() => {
window.location.reload(); window.location.reload();
}); });
@@ -123,18 +123,18 @@ function checkValidServiceWorker(swUrl, config) {
}) })
.catch(() => { .catch(() => {
console.log( console.log(
'No internet connection found. App is running in offline mode.' "No internet connection found. App is running in offline mode."
); );
}); });
} }
export function unregister() { export function unregister() {
if ('serviceWorker' in navigator) { if ("serviceWorker" in navigator) {
navigator.serviceWorker.ready navigator.serviceWorker.ready
.then(registration => { .then((registration) => {
registration.unregister(); registration.unregister();
}) })
.catch(error => { .catch((error) => {
console.error(error.message); console.error(error.message);
}); });
} }

View File

@@ -2,4 +2,4 @@
// allows you to do things like: // allows you to do things like:
// expect(element).toHaveTextContent(/react/i) // expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom // learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom/extend-expect'; import "@testing-library/jest-dom/extend-expect";

View File

@@ -9,7 +9,7 @@ const defaultConfig: SASjsConfig = {
serverType: ServerType.SASViya, serverType: ServerType.SASViya,
debug: true, debug: true,
contextName: "SAS Job Execution compute context", contextName: "SAS Job Execution compute context",
useComputeApi: false, useComputeApi: false
}; };
const customConfig = { const customConfig = {
@@ -18,7 +18,7 @@ const customConfig = {
pathSASViya: "viya", pathSASViya: "viya",
appLoc: "/Public/seedapp", appLoc: "/Public/seedapp",
serverType: ServerType.SAS9, serverType: ServerType.SAS9,
debug: false, debug: false
}; };
export const basicTests = ( export const basicTests = (
@@ -35,7 +35,7 @@ export const basicTests = (
return adapter.logIn(userName, password); return adapter.logIn(userName, password);
}, },
assertion: (response: any) => assertion: (response: any) =>
response && response.isLoggedIn && response.userName === userName, response && response.isLoggedIn && response.userName === userName
}, },
{ {
title: "Default config", title: "Default config",
@@ -54,7 +54,7 @@ export const basicTests = (
sasjsConfig.serverType === defaultConfig.serverType && sasjsConfig.serverType === defaultConfig.serverType &&
sasjsConfig.debug === defaultConfig.debug sasjsConfig.debug === defaultConfig.debug
); );
}, }
}, },
{ {
title: "Custom config", title: "Custom config",
@@ -72,7 +72,7 @@ export const basicTests = (
sasjsConfig.serverType === customConfig.serverType && sasjsConfig.serverType === customConfig.serverType &&
sasjsConfig.debug === customConfig.debug sasjsConfig.debug === customConfig.debug
); );
}, }
}, },
{ {
title: "Config overrides", title: "Config overrides",
@@ -92,7 +92,7 @@ export const basicTests = (
sasjsConfig.serverType === defaultConfig.serverType && sasjsConfig.serverType === defaultConfig.serverType &&
sasjsConfig.debug === false sasjsConfig.debug === false
); );
}, }
}, }
], ]
}); });

View File

@@ -4,7 +4,7 @@ import { TestSuite } from "@sasjs/test-framework";
const stringData: any = { table1: [{ col1: "first col value" }] }; const stringData: any = { table1: [{ col1: "first col value" }] };
const numericData: any = { table1: [{ col1: 3.14159265 }] }; const numericData: any = { table1: [{ col1: 3.14159265 }] };
const multiColumnData: any = { const multiColumnData: any = {
table1: [{ col1: 42, col2: 1.618, col3: "x", col4: "x" }], table1: [{ col1: 42, col2: 1.618, col3: "x", col4: "x" }]
}; };
const multipleRowsWithNulls: any = { const multipleRowsWithNulls: any = {
table1: [ table1: [
@@ -12,8 +12,8 @@ const multipleRowsWithNulls: any = {
{ col1: 42, col2: null, col3: "x", col4: "" }, { col1: 42, col2: null, col3: "x", col4: "" },
{ col1: 42, col2: null, col3: "x", col4: "" }, { col1: 42, col2: null, col3: "x", col4: "" },
{ col1: 42, col2: 1.62, col3: "x", col4: "x" }, { col1: 42, col2: 1.62, col3: "x", col4: "x" },
{ col1: 42, col2: 1.62, col3: "x", col4: "x" }, { col1: 42, col2: 1.62, col3: "x", col4: "x" }
], ]
}; };
const multipleColumnsWithNulls: any = { const multipleColumnsWithNulls: any = {
table1: [ table1: [
@@ -21,8 +21,8 @@ const multipleColumnsWithNulls: any = {
{ col1: 42, col2: null, col3: "x", col4: null }, { col1: 42, col2: null, col3: "x", col4: null },
{ col1: 42, col2: null, col3: "x", col4: null }, { col1: 42, col2: null, col3: "x", col4: null },
{ col1: 42, col2: null, col3: "x", col4: "" }, { col1: 42, col2: null, col3: "x", col4: "" },
{ col1: 42, col2: null, col3: "x", col4: "" }, { col1: 42, col2: null, col3: "x", col4: "" }
], ]
}; };
const getLongStringData = (length = 32764) => { const getLongStringData = (length = 32764) => {
@@ -55,7 +55,7 @@ export const sendArrTests = (adapter: SASjs): TestSuite => ({
}, },
assertion: (res: any) => { assertion: (res: any) => {
return res.table1[0][0] === stringData.table1[0].col1; return res.table1[0][0] === stringData.table1[0].col1;
}, }
}, },
{ {
title: "Long string value", title: "Long string value",
@@ -67,7 +67,7 @@ export const sendArrTests = (adapter: SASjs): TestSuite => ({
assertion: (res: any) => { assertion: (res: any) => {
const longStringData = getLongStringData(); const longStringData = getLongStringData();
return res.table1[0][0] === longStringData.table1[0].col1; return res.table1[0][0] === longStringData.table1[0].col1;
}, }
}, },
{ {
title: "Overly long string value", title: "Overly long string value",
@@ -79,7 +79,7 @@ export const sendArrTests = (adapter: SASjs): TestSuite => ({
}, },
assertion: (error: any) => { assertion: (error: any) => {
return !!error && !!error.MESSAGE; return !!error && !!error.MESSAGE;
}, }
}, },
{ {
title: "Single numeric value", title: "Single numeric value",
@@ -89,7 +89,7 @@ export const sendArrTests = (adapter: SASjs): TestSuite => ({
}, },
assertion: (res: any) => { assertion: (res: any) => {
return res.table1[0][0] === numericData.table1[0].col1; return res.table1[0][0] === numericData.table1[0].col1;
}, }
}, },
{ {
title: "Multiple columns", title: "Multiple columns",
@@ -104,7 +104,7 @@ export const sendArrTests = (adapter: SASjs): TestSuite => ({
res.table1[0][2] === multiColumnData.table1[0].col3 && res.table1[0][2] === multiColumnData.table1[0].col3 &&
res.table1[0][3] === multiColumnData.table1[0].col4 res.table1[0][3] === multiColumnData.table1[0].col4
); );
}, }
}, },
{ {
title: "Multiple rows with nulls", title: "Multiple rows with nulls",
@@ -129,7 +129,7 @@ export const sendArrTests = (adapter: SASjs): TestSuite => ({
res.table1[index][3] === multipleRowsWithNulls.table1[index].col4; res.table1[index][3] === multipleRowsWithNulls.table1[index].col4;
}); });
return result; return result;
}, }
}, },
{ {
title: "Multiple columns with nulls", title: "Multiple columns with nulls",
@@ -158,9 +158,9 @@ export const sendArrTests = (adapter: SASjs): TestSuite => ({
(multipleColumnsWithNulls.table1[index].col4 || ""); (multipleColumnsWithNulls.table1[index].col4 || "");
}); });
return result; return result;
}, }
}, }
], ]
}); });
export const sendObjTests = (adapter: SASjs): TestSuite => ({ export const sendObjTests = (adapter: SASjs): TestSuite => ({
@@ -171,11 +171,11 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
description: "Should throw an error", description: "Should throw an error",
test: async () => { test: async () => {
const invalidData: any = { const invalidData: any = {
"1 invalid table": [{ col1: 42 }], "1 invalid table": [{ col1: 42 }]
}; };
return adapter.request("common/sendObj", invalidData).catch((e) => e); return adapter.request("common/sendObj", invalidData).catch((e) => e);
}, },
assertion: (error: any) => !!error && !!error.MESSAGE, assertion: (error: any) => !!error && !!error.MESSAGE
}, },
{ {
title: "Single string value", title: "Single string value",
@@ -185,7 +185,7 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
}, },
assertion: (res: any) => { assertion: (res: any) => {
return res.table1[0].COL1 === stringData.table1[0].col1; return res.table1[0].COL1 === stringData.table1[0].col1;
}, }
}, },
{ {
title: "Long string value", title: "Long string value",
@@ -197,7 +197,7 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
assertion: (res: any) => { assertion: (res: any) => {
const longStringData = getLongStringData(); const longStringData = getLongStringData();
return res.table1[0].COL1 === longStringData.table1[0].col1; return res.table1[0].COL1 === longStringData.table1[0].col1;
}, }
}, },
{ {
title: "Overly long string value", title: "Overly long string value",
@@ -210,7 +210,7 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
}, },
assertion: (error: any) => { assertion: (error: any) => {
return !!error && !!error.MESSAGE; return !!error && !!error.MESSAGE;
}, }
}, },
{ {
title: "Single numeric value", title: "Single numeric value",
@@ -220,7 +220,7 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
}, },
assertion: (res: any) => { assertion: (res: any) => {
return res.table1[0].COL1 === numericData.table1[0].col1; return res.table1[0].COL1 === numericData.table1[0].col1;
}, }
}, },
{ {
@@ -232,7 +232,7 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
assertion: (res: any) => { assertion: (res: any) => {
const data = getLargeObjectData(); const data = getLargeObjectData();
return res.table1[9000].BIG === data.table1[9000].big; return res.table1[9000].BIG === data.table1[9000].big;
}, }
}, },
{ {
title: "Multiple columns", title: "Multiple columns",
@@ -247,7 +247,7 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
res.table1[0].COL3 === multiColumnData.table1[0].col3 && res.table1[0].COL3 === multiColumnData.table1[0].col3 &&
res.table1[0].COL4 === multiColumnData.table1[0].col4 res.table1[0].COL4 === multiColumnData.table1[0].col4
); );
}, }
}, },
{ {
title: "Multiple rows with nulls", title: "Multiple rows with nulls",
@@ -272,7 +272,7 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
res.table1[index].COL4 === multipleRowsWithNulls.table1[index].col4; res.table1[index].COL4 === multipleRowsWithNulls.table1[index].col4;
}); });
return result; return result;
}, }
}, },
{ {
title: "Multiple columns with nulls", title: "Multiple columns with nulls",
@@ -301,7 +301,7 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
(multipleColumnsWithNulls.table1[index].col4 || ""); (multipleColumnsWithNulls.table1[index].col4 || "");
}); });
return result; return result;
}, }
}, }
], ]
}); });

View File

@@ -19,28 +19,32 @@ export const sasjsRequestTests = (adapter: SASjs): TestSuite => ({
} else { } else {
return requests[0].SASWORK === null; return requests[0].SASWORK === null;
} }
}, }
}, },
{ {
title: "Make error and capture log", title: "Make error and capture log",
description: "Should make an error and capture log", 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.request("common/makeErr", data) adapter
.then((res) => { .request("common/makeErr", data)
//no action here, this request must throw error .then((res) => {
}) //no action here, this request must throw error
.catch((err) => { })
let sasRequests = adapter.getSasRequests(); .catch((err) => {
let makeErrRequest = sasRequests.find(req => req.serviceLink.includes('makeErr')) || null; let sasRequests = adapter.getSasRequests();
let makeErrRequest =
sasRequests.find((req) =>
req.serviceLink.includes("makeErr")
) || null;
resolve(!!makeErrRequest); resolve(!!makeErrRequest);
}) });
}) });
}, },
assertion: (response) => { assertion: (response) => {
return response; return response;
}, }
}, }
], ]
}); });

View File

@@ -13,9 +13,9 @@ const specialCharData: any = {
doubleQuote: '"', doubleQuote: '"',
crlf: "\r\n", crlf: "\r\n",
euro: "€euro", euro: "€euro",
banghash: "!#banghash", banghash: "!#banghash"
}, }
], ]
}; };
const moreSpecialCharData: any = { const moreSpecialCharData: any = {
@@ -31,9 +31,9 @@ const moreSpecialCharData: any = {
sigma: "Σsigma", sigma: "Σsigma",
at: "@at", at: "@at",
serbian: "Српски", serbian: "Српски",
dollar: "$", dollar: "$"
}, }
], ]
}; };
const getWideData = () => { const getWideData = () => {
@@ -43,7 +43,7 @@ const getWideData = () => {
} }
const data: any = { const data: any = {
table1: [cols], table1: [cols]
}; };
return data; return data;
@@ -67,7 +67,7 @@ const getLargeDataset = () => {
} }
const data: any = { const data: any = {
table1: rows, table1: rows
}; };
return data; return data;
@@ -75,7 +75,7 @@ const getLargeDataset = () => {
const errorAndCsrfData: any = { const errorAndCsrfData: any = {
error: [{ col1: "q", col2: "w", col3: "e", col4: "r" }], error: [{ col1: "q", col2: "w", col3: "e", col4: "r" }],
_csrf: [{ col1: "q", col2: "w", col3: "e", col4: "r" }], _csrf: [{ col1: "q", col2: "w", col3: "e", col4: "r" }]
}; };
export const specialCaseTests = (adapter: SASjs): TestSuite => ({ export const specialCaseTests = (adapter: SASjs): TestSuite => ({
@@ -100,7 +100,7 @@ export const specialCaseTests = (adapter: SASjs): TestSuite => ({
res.table1[0][8] === specialCharData.table1[0].euro && res.table1[0][8] === specialCharData.table1[0].euro &&
res.table1[0][9] === specialCharData.table1[0].banghash res.table1[0][9] === specialCharData.table1[0].banghash
); );
}, }
}, },
{ {
title: "Other special characters", title: "Other special characters",
@@ -122,7 +122,7 @@ export const specialCaseTests = (adapter: SASjs): TestSuite => ({
res.table1[0][9] === moreSpecialCharData.table1[0].serbian && res.table1[0][9] === moreSpecialCharData.table1[0].serbian &&
res.table1[0][10] === moreSpecialCharData.table1[0].dollar res.table1[0][10] === moreSpecialCharData.table1[0].dollar
); );
}, }
}, },
{ {
title: "Wide table with sendArr", title: "Wide table with sendArr",
@@ -138,7 +138,7 @@ export const specialCaseTests = (adapter: SASjs): TestSuite => ({
result && res.table1[0][i] === data.table1[0]["col" + (i + 1)]; result && res.table1[0][i] === data.table1[0]["col" + (i + 1)];
} }
return result; return result;
}, }
}, },
{ {
title: "Wide table with sendObj", title: "Wide table with sendObj",
@@ -155,7 +155,7 @@ export const specialCaseTests = (adapter: SASjs): TestSuite => ({
res.table1[0]["COL" + (i + 1)] === data.table1[0]["col" + (i + 1)]; res.table1[0]["COL" + (i + 1)] === data.table1[0]["col" + (i + 1)];
} }
return result; return result;
}, }
}, },
{ {
title: "Multiple tables", title: "Multiple tables",
@@ -175,7 +175,7 @@ export const specialCaseTests = (adapter: SASjs): TestSuite => ({
res.table50[0][2] === data.table50[0].col3 && res.table50[0][2] === data.table50[0].col3 &&
res.table50[0][3] === data.table50[0].col4 res.table50[0][3] === data.table50[0].col4
); );
}, }
}, },
{ {
title: "Large dataset with sendObj", title: "Large dataset with sendObj",
@@ -190,7 +190,7 @@ export const specialCaseTests = (adapter: SASjs): TestSuite => ({
result = result && res.table1[i][0] === data.table1[i][0]; result = result && res.table1[i][0] === data.table1[i][0];
} }
return result; return result;
}, }
}, },
{ {
title: "Large dataset with sendArr", title: "Large dataset with sendArr",
@@ -206,7 +206,7 @@ export const specialCaseTests = (adapter: SASjs): TestSuite => ({
result && res.table1[i][0] === Object.values(data.table1[i])[0]; result && res.table1[i][0] === Object.values(data.table1[i])[0];
} }
return result; return result;
}, }
}, },
{ {
title: "Error and _csrf tables with sendArr", title: "Error and _csrf tables with sendArr",
@@ -225,7 +225,7 @@ export const specialCaseTests = (adapter: SASjs): TestSuite => ({
res._csrf[0][2] === errorAndCsrfData._csrf[0].col3 && res._csrf[0][2] === errorAndCsrfData._csrf[0].col3 &&
res._csrf[0][3] === errorAndCsrfData._csrf[0].col4 res._csrf[0][3] === errorAndCsrfData._csrf[0].col4
); );
}, }
}, },
{ {
title: "Error and _csrf tables with sendObj", title: "Error and _csrf tables with sendObj",
@@ -244,7 +244,7 @@ export const specialCaseTests = (adapter: SASjs): TestSuite => ({
res._csrf[0].COL3 === errorAndCsrfData._csrf[0].col3 && res._csrf[0].COL3 === errorAndCsrfData._csrf[0].col3 &&
res._csrf[0].COL4 === errorAndCsrfData._csrf[0].col4 res._csrf[0].COL4 === errorAndCsrfData._csrf[0].col4
); );
}, }
}, }
], ]
}); });

View File

@@ -10,7 +10,7 @@ export class FileUploader {
private serverUrl: string, private serverUrl: string,
private jobsPath: string, private jobsPath: string,
private setCsrfTokenWeb: any, private setCsrfTokenWeb: any,
private csrfToken: CsrfToken | null = null, private csrfToken: CsrfToken | null = null
) {} ) {}
private retryCount = 0; private retryCount = 0;
@@ -33,7 +33,7 @@ export class FileUploader {
}${paramsString}`; }${paramsString}`;
const headers = { const headers = {
"cache-control": "no-cache", "cache-control": "no-cache"
}; };
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@@ -49,7 +49,7 @@ export class FileUploader {
method: "POST", method: "POST",
body: formData, body: formData,
referrerPolicy: "same-origin", referrerPolicy: "same-origin",
headers, headers
}) })
.then(async (response) => { .then(async (response) => {
if (!response.ok) { if (!response.ok) {
@@ -60,7 +60,7 @@ export class FileUploader {
const token = response.headers.get(tokenHeader); const token = response.headers.get(tokenHeader);
this.csrfToken = { this.csrfToken = {
headerName: tokenHeader, headerName: tokenHeader,
value: token || "", value: token || ""
}; };
this.setCsrfTokenWeb(this.csrfToken); this.setCsrfTokenWeb(this.csrfToken);

View File

@@ -10,7 +10,7 @@ export class SAS9ApiClient {
*/ */
public getConfig() { public getConfig() {
return { return {
serverUrl: this.serverUrl, serverUrl: this.serverUrl
}; };
} }
@@ -37,9 +37,9 @@ export class SAS9ApiClient {
const executeScriptRequest = { const executeScriptRequest = {
method: "PUT", method: "PUT",
headers: { headers: {
Accept: "application/json", Accept: "application/json"
}, },
body: `command=${requestPayload}`, body: `command=${requestPayload}`
}; };
const executeScriptResponse = await fetch( const executeScriptResponse = await fetch(
`${this.serverUrl}/sas/servers/${serverName}/cmd?repositoryName=${repositoryName}`, `${this.serverUrl}/sas/servers/${serverName}/cmd?repositoryName=${repositoryName}`,

View File

@@ -2,7 +2,7 @@ import {
isAuthorizeFormRequired, isAuthorizeFormRequired,
parseAndSubmitAuthorizeForm, parseAndSubmitAuthorizeForm,
convertToCSV, convertToCSV,
makeRequest, makeRequest
} from "./utils"; } from "./utils";
import * as NodeFormData from "form-data"; import * as NodeFormData from "form-data";
import * as path from "path"; import * as path from "path";
@@ -53,7 +53,7 @@ export class SASViyaApiClient {
public getConfig() { public getConfig() {
return { return {
serverUrl: this.serverUrl, serverUrl: this.serverUrl,
rootFolderName: this.rootFolderName, rootFolderName: this.rootFolderName
}; };
} }
@@ -73,7 +73,7 @@ export class SASViyaApiClient {
*/ */
public async getAllContexts(accessToken?: string) { public async getAllContexts(accessToken?: string) {
const headers: any = { const headers: any = {
"Content-Type": "application/json", "Content-Type": "application/json"
}; };
if (accessToken) { if (accessToken) {
headers.Authorization = `Bearer ${accessToken}`; headers.Authorization = `Bearer ${accessToken}`;
@@ -88,7 +88,7 @@ export class SASViyaApiClient {
id: context.id, id: context.id,
name: context.name, name: context.name,
version: context.version, version: context.version,
attributes: {}, attributes: {}
})); }));
} }
@@ -98,7 +98,7 @@ export class SASViyaApiClient {
*/ */
public async getExecutableContexts(accessToken?: string) { public async getExecutableContexts(accessToken?: string) {
const headers: any = { const headers: any = {
"Content-Type": "application/json", "Content-Type": "application/json"
}; };
if (accessToken) { if (accessToken) {
headers.Authorization = `Bearer ${accessToken}`; headers.Authorization = `Bearer ${accessToken}`;
@@ -139,8 +139,8 @@ export class SASViyaApiClient {
name: contextsList[index].name, name: contextsList[index].name,
version: contextsList[index].version, version: contextsList[index].version,
attributes: { attributes: {
sysUserId, sysUserId
}, }
}); });
} }
}); });
@@ -155,7 +155,7 @@ export class SASViyaApiClient {
*/ */
public async createSession(contextName: string, accessToken?: string) { public async createSession(contextName: string, accessToken?: string) {
const headers: any = { const headers: any = {
"Content-Type": "application/json", "Content-Type": "application/json"
}; };
if (accessToken) { if (accessToken) {
@@ -178,8 +178,8 @@ export class SASViyaApiClient {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${accessToken}`, Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json", "Content-Type": "application/json"
}, }
}; };
const { result: createdSession } = await this.request<Session>( const { result: createdSession } = await this.request<Session>(
`${this.serverUrl}/compute/contexts/${executionContext.id}/sessions`, `${this.serverUrl}/compute/contexts/${executionContext.id}/sessions`,
@@ -210,7 +210,7 @@ export class SASViyaApiClient {
silent = !debug; silent = !debug;
try { try {
const headers: any = { const headers: any = {
"Content-Type": "application/json", "Content-Type": "application/json"
}; };
if (accessToken) { if (accessToken) {
@@ -227,7 +227,7 @@ export class SASViyaApiClient {
_OMITJSONLOG: true, _OMITJSONLOG: true,
_OMITSESSIONRESULTS: true, _OMITSESSIONRESULTS: true,
_OMITTEXTLISTING: true, _OMITTEXTLISTING: true,
_OMITTEXTLOG: true, _OMITTEXTLOG: true
}; };
if (debug) { if (debug) {
@@ -242,7 +242,7 @@ export class SASViyaApiClient {
let jobVariables: any = { let jobVariables: any = {
SYS_JES_JOB_URI: "", SYS_JES_JOB_URI: "",
_program: this.rootFolderName + "/" + jobName, _program: this.rootFolderName + "/" + jobName
}; };
let files: any[] = []; let files: any[] = [];
@@ -271,8 +271,8 @@ export class SASViyaApiClient {
description: "Powered by SASjs", description: "Powered by SASjs",
code: linesOfCode, code: linesOfCode,
variables: jobVariables, variables: jobVariables,
arguments: jobArguments, arguments: jobArguments
}), })
}; };
const { result: postedJob, etag } = await this.request<Job>( const { result: postedJob, etag } = await this.request<Job>(
@@ -309,7 +309,7 @@ export class SASViyaApiClient {
log = await this.request<any>( log = await this.request<any>(
`${this.serverUrl}${logLink.href}/content?limit=10000`, `${this.serverUrl}${logLink.href}/content?limit=10000`,
{ {
headers, headers
} }
).then((res: any) => ).then((res: any) =>
res.result.items.map((i: any) => i.line).join("\n") res.result.items.map((i: any) => i.line).join("\n")
@@ -327,7 +327,7 @@ export class SASViyaApiClient {
{ headers }, { headers },
"text" "text"
).catch((e) => ({ ).catch((e) => ({
result: JSON.stringify(e), result: JSON.stringify(e)
})); }));
} }
@@ -401,8 +401,8 @@ export class SASViyaApiClient {
method: "POST", method: "POST",
body: JSON.stringify({ body: JSON.stringify({
name: folderName, name: folderName,
type: "folder", type: "folder"
}), })
}; };
createFolderRequest.headers = { "Content-Type": "application/json" }; createFolderRequest.headers = { "Content-Type": "application/json" };
@@ -449,7 +449,7 @@ export class SASViyaApiClient {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/vnd.sas.job.definition+json", "Content-Type": "application/vnd.sas.job.definition+json",
Accept: "application/vnd.sas.job.definition+json", Accept: "application/vnd.sas.job.definition+json"
}, },
body: JSON.stringify({ body: JSON.stringify({
name: jobName, name: jobName,
@@ -457,18 +457,18 @@ export class SASViyaApiClient {
{ {
name: "_addjesbeginendmacros", name: "_addjesbeginendmacros",
type: "CHARACTER", type: "CHARACTER",
defaultValue: "false", defaultValue: "false"
}, }
], ],
type: "Compute", type: "Compute",
code, code
}), })
}; };
if (accessToken) { if (accessToken) {
createJobDefinitionRequest!.headers = { createJobDefinitionRequest!.headers = {
...createJobDefinitionRequest.headers, ...createJobDefinitionRequest.headers,
Authorization: `Bearer ${accessToken}`, Authorization: `Bearer ${accessToken}`
}; };
} }
@@ -487,7 +487,7 @@ export class SASViyaApiClient {
const authCode = await fetch(authUrl, { const authCode = await fetch(authUrl, {
referrerPolicy: "same-origin", referrerPolicy: "same-origin",
credentials: "include", credentials: "include"
}) })
.then((response) => response.text()) .then((response) => response.text())
.then(async (response) => { .then(async (response) => {
@@ -543,7 +543,7 @@ export class SASViyaApiClient {
token = Buffer.from(clientId + ":" + clientSecret).toString("base64"); token = Buffer.from(clientId + ":" + clientSecret).toString("base64");
} }
const headers = { const headers = {
Authorization: "Basic " + token, Authorization: "Basic " + token
}; };
let formData; let formData;
@@ -562,7 +562,7 @@ export class SASViyaApiClient {
credentials: "include", credentials: "include",
headers, headers,
body: formData as any, body: formData as any,
referrerPolicy: "same-origin", referrerPolicy: "same-origin"
}).then((res) => res.json()); }).then((res) => res.json());
return authResponse; return authResponse;
@@ -587,7 +587,7 @@ export class SASViyaApiClient {
token = Buffer.from(clientId + ":" + clientSecret).toString("base64"); token = Buffer.from(clientId + ":" + clientSecret).toString("base64");
} }
const headers = { const headers = {
Authorization: "Basic " + token, Authorization: "Basic " + token
}; };
let formData; let formData;
@@ -606,7 +606,7 @@ export class SASViyaApiClient {
credentials: "include", credentials: "include",
headers, headers,
body: formData as any, body: formData as any,
referrerPolicy: "same-origin", referrerPolicy: "same-origin"
}).then((res) => res.json()); }).then((res) => res.json());
return authResponse; return authResponse;
@@ -626,7 +626,7 @@ export class SASViyaApiClient {
const deleteResponse = await this.request(url, { const deleteResponse = await this.request(url, {
method: "DELETE", method: "DELETE",
credentials: "include", credentials: "include",
headers, headers
}); });
return deleteResponse; return deleteResponse;
@@ -675,20 +675,30 @@ export class SASViyaApiClient {
const jobName = sasJob.split("/")[1]; const jobName = sasJob.split("/")[1];
const jobFolder = this.rootFolderMap.get(folderName); const jobFolder = this.rootFolderMap.get(folderName);
const jobToExecute = jobFolder?.find((item) => item.name === jobName); const jobToExecute = jobFolder?.find((item) => item.name === jobName);
const jobDefinitionLink = jobToExecute?.links.find( if (!jobToExecute) {
(l) => l.rel === "getResource" throw new Error("Job was not found.");
);
if (!jobDefinitionLink) {
console.error("Job definition URI was not found.");
throw new Error("Job definition URI was not found.");
} }
const { result: jobDefinition } = await this.request<JobDefinition>(
`${this.serverUrl}${jobDefinitionLink.href}`, let code = jobToExecute?.code;
headers if (!code) {
); const jobDefinitionLink = jobToExecute?.links.find(
const linesToExecute = jobDefinition.code (l) => l.rel === "getResource"
.replace(/\r\n/g, "\n") );
.split("\n"); if (!jobDefinitionLink) {
console.error("Job definition URI was not found.");
throw new Error("Job definition URI was not found.");
}
const { result: jobDefinition } = await this.request<JobDefinition>(
`${this.serverUrl}${jobDefinitionLink.href}`,
headers
);
code = jobDefinition.code;
// Add code to existing job definition
jobToExecute.code = code;
}
const linesToExecute = code.replace(/\r\n/g, "\n").split("\n");
return await this.executeScript( return await this.executeScript(
sasJob, sasJob,
linesToExecute, linesToExecute,
@@ -745,7 +755,7 @@ export class SASViyaApiClient {
(l) => l.rel === "getResource" (l) => l.rel === "getResource"
)?.href; )?.href;
const requestInfo: any = { const requestInfo: any = {
method: "GET", method: "GET"
}; };
const headers: any = { "Content-Type": "application/json" }; const headers: any = { "Content-Type": "application/json" };
if (!!accessToken) { if (!!accessToken) {
@@ -765,7 +775,7 @@ export class SASViyaApiClient {
_OMITJSONLOG: true, _OMITJSONLOG: true,
_OMITSESSIONRESULTS: true, _OMITSESSIONRESULTS: true,
_OMITTEXTLISTING: true, _OMITTEXTLISTING: true,
_OMITTEXTLOG: true, _OMITTEXTLOG: true
}; };
if (debug) { if (debug) {
@@ -788,8 +798,8 @@ export class SASViyaApiClient {
name: `exec-${jobName}`, name: `exec-${jobName}`,
description: "Powered by SASjs", description: "Powered by SASjs",
jobDefinition, jobDefinition,
arguments: jobArguments, arguments: jobArguments
}), })
}; };
const { result: postedJob, etag } = await this.request<Job>( const { result: postedJob, etag } = await this.request<Job>(
`${this.serverUrl}/jobExecution/jobs?_action=wait`, `${this.serverUrl}/jobExecution/jobs?_action=wait`,
@@ -823,7 +833,7 @@ export class SASViyaApiClient {
log = await this.request<any>( log = await this.request<any>(
`${this.serverUrl}${logLink.href}/content`, `${this.serverUrl}${logLink.href}/content`,
{ {
headers, headers
} }
).then((res: any) => ).then((res: any) =>
res.result.items.map((i: any) => i.line).join("\n") res.result.items.map((i: any) => i.line).join("\n")
@@ -841,7 +851,7 @@ export class SASViyaApiClient {
const allItems = new Map<string, Job[]>(); const allItems = new Map<string, Job[]>();
const url = "/folders/folders/@item?path=" + this.rootFolderName; const url = "/folders/folders/@item?path=" + this.rootFolderName;
const requestInfo: any = { const requestInfo: any = {
method: "GET", method: "GET"
}; };
if (accessToken) { if (accessToken) {
requestInfo.headers = { Authorization: `Bearer ${accessToken}` }; requestInfo.headers = { Authorization: `Bearer ${accessToken}` };
@@ -893,7 +903,7 @@ export class SASViyaApiClient {
private async populateRootFolder(accessToken?: string) { private async populateRootFolder(accessToken?: string) {
const url = "/folders/folders/@item?path=" + this.rootFolderName; const url = "/folders/folders/@item?path=" + this.rootFolderName;
const requestInfo: RequestInit = { const requestInfo: RequestInit = {
method: "GET", method: "GET"
}; };
if (accessToken) { if (accessToken) {
requestInfo.headers = { Authorization: `Bearer ${accessToken}` }; requestInfo.headers = { Authorization: `Bearer ${accessToken}` };
@@ -922,12 +932,29 @@ export class SASViyaApiClient {
let pollCount = 0; let pollCount = 0;
const headers: any = { const headers: any = {
"Content-Type": "application/json", "Content-Type": "application/json",
"If-None-Match": etag, "If-None-Match": etag
}; };
if (accessToken) { if (accessToken) {
headers.Authorization = `Bearer ${accessToken}`; headers.Authorization = `Bearer ${accessToken}`;
} }
const stateLink = postedJob.links.find((l: any) => l.rel === "state"); const stateLink = postedJob.links.find((l: any) => l.rel === "state");
if (!stateLink) {
Promise.reject("Job state link was not found.");
}
const { result: state } = await this.request<string>(
`${this.serverUrl}${stateLink.href}?_action=wait&wait=30`,
{
headers
},
"text"
);
const currentState = state.trim();
if (currentState === "completed") {
return Promise.resolve(currentState);
}
return new Promise(async (resolve, _) => { return new Promise(async (resolve, _) => {
const interval = setInterval(async () => { const interval = setInterval(async () => {
if ( if (
@@ -942,7 +969,7 @@ export class SASViyaApiClient {
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`,
{ {
headers, headers
}, },
"text" "text"
); );
@@ -974,7 +1001,7 @@ export class SASViyaApiClient {
let pollCount = 0; let pollCount = 0;
const headers: any = { const headers: any = {
"Content-Type": "application/json", "Content-Type": "application/json",
"If-None-Match": etag, "If-None-Match": etag
}; };
if (accessToken) { if (accessToken) {
headers.Authorization = `Bearer ${accessToken}`; headers.Authorization = `Bearer ${accessToken}`;
@@ -989,7 +1016,7 @@ export class SASViyaApiClient {
const { result: state } = await this.request<string>( const { result: state } = await this.request<string>(
`${this.serverUrl}${stateLink.href}?wait=30`, `${this.serverUrl}${stateLink.href}?wait=30`,
{ {
headers, headers
}, },
"text" "text"
); );
@@ -1010,7 +1037,7 @@ export class SASViyaApiClient {
private async uploadTables(data: any, accessToken?: string) { private async uploadTables(data: any, accessToken?: string) {
const uploadedFiles = []; const uploadedFiles = [];
const headers: any = { const headers: any = {
"Content-Type": "application/json", "Content-Type": "application/json"
}; };
if (accessToken) { if (accessToken) {
headers.Authorization = `Bearer ${accessToken}`; headers.Authorization = `Bearer ${accessToken}`;
@@ -1027,7 +1054,7 @@ export class SASViyaApiClient {
const createFileRequest = { const createFileRequest = {
method: "POST", method: "POST",
body: csv, body: csv,
headers, headers
}; };
const uploadResponse = await this.request<any>( const uploadResponse = await this.request<any>(
@@ -1043,7 +1070,7 @@ export class SASViyaApiClient {
private async getFolderUri(folderPath: string, accessToken?: string) { private async getFolderUri(folderPath: string, accessToken?: string) {
const url = "/folders/folders/@item?path=" + folderPath; const url = "/folders/folders/@item?path=" + folderPath;
const requestInfo: any = { const requestInfo: any = {
method: "GET", method: "GET"
}; };
if (accessToken) { if (accessToken) {
requestInfo.headers = { Authorization: `Bearer ${accessToken}` }; requestInfo.headers = { Authorization: `Bearer ${accessToken}` };
@@ -1072,7 +1099,7 @@ export class SASViyaApiClient {
if (this.csrfToken) { if (this.csrfToken) {
options.headers = { options.headers = {
...options.headers, ...options.headers,
[this.csrfToken.headerName]: this.csrfToken.value, [this.csrfToken.headerName]: this.csrfToken.value
}; };
} }
return await makeRequest<T>( return await makeRequest<T>(

View File

@@ -2,7 +2,7 @@ import SASjs from "./index";
const adapter = new SASjs(); const adapter = new SASjs();
it("should parse SAS9 source code", async done => { it("should parse SAS9 source code", async (done) => {
expect(sampleResponse).toBeTruthy(); expect(sampleResponse).toBeTruthy();
const parsedSourceCode = (adapter as any).parseSAS9SourceCode(sampleResponse); const parsedSourceCode = (adapter as any).parseSAS9SourceCode(sampleResponse);
expect(parsedSourceCode).toBeTruthy(); expect(parsedSourceCode).toBeTruthy();
@@ -16,7 +16,7 @@ it("should parse SAS9 source code", async done => {
done(); done();
}); });
it("should parse generated code", async done => { it("should parse generated code", async (done) => {
expect(sampleResponse).toBeTruthy(); expect(sampleResponse).toBeTruthy();
const parsedGeneratedCode = (adapter as any).parseGeneratedCode( const parsedGeneratedCode = (adapter as any).parseGeneratedCode(
sampleResponse sampleResponse

View File

@@ -21,7 +21,7 @@ import {
parseGeneratedCode, parseGeneratedCode,
parseWeboutResponse, parseWeboutResponse,
needsRetry, needsRetry,
asyncForEach, asyncForEach
} from "./utils"; } from "./utils";
import { import {
SASjsConfig, SASjsConfig,
@@ -29,7 +29,7 @@ import {
SASjsWaitingRequest, SASjsWaitingRequest,
ServerType, ServerType,
CsrfToken, CsrfToken,
UploadFile, UploadFile
} from "./types"; } from "./types";
import { SASViyaApiClient } from "./SASViyaApiClient"; import { SASViyaApiClient } from "./SASViyaApiClient";
import { SAS9ApiClient } from "./SAS9ApiClient"; import { SAS9ApiClient } from "./SAS9ApiClient";
@@ -43,7 +43,7 @@ const defaultConfig: SASjsConfig = {
serverType: ServerType.SASViya, serverType: ServerType.SASViya,
debug: true, debug: true,
contextName: "SAS Job Execution compute context", contextName: "SAS Job Execution compute context",
useComputeApi: false, useComputeApi: false
}; };
const requestRetryLimit = 5; const requestRetryLimit = 5;
@@ -72,7 +72,7 @@ export default class SASjs {
constructor(config?: any) { constructor(config?: any) {
this.sasjsConfig = { this.sasjsConfig = {
...defaultConfig, ...defaultConfig,
...config, ...config
}; };
this.setupConfiguration(); this.setupConfiguration();
@@ -270,7 +270,7 @@ export default class SASjs {
public async setSASjsConfig(config: SASjsConfig) { public async setSASjsConfig(config: SASjsConfig) {
this.sasjsConfig = { this.sasjsConfig = {
...this.sasjsConfig, ...this.sasjsConfig,
...config, ...config
}; };
await this.setupConfiguration(); await this.setupConfiguration();
} }
@@ -295,7 +295,7 @@ export default class SASjs {
return Promise.resolve({ return Promise.resolve({
isLoggedIn, isLoggedIn,
userName: this.userName, userName: this.userName
}); });
} }
@@ -308,7 +308,7 @@ export default class SASjs {
const loginParams: any = { const loginParams: any = {
_service: "default", _service: "default",
username, username,
password, password
}; };
this.userName = loginParams.username; this.userName = loginParams.username;
@@ -319,7 +319,7 @@ export default class SASjs {
return Promise.resolve({ return Promise.resolve({
isLoggedIn, isLoggedIn,
userName: this.userName, userName: this.userName
}); });
} }
@@ -336,8 +336,8 @@ export default class SASjs {
referrerPolicy: "same-origin", referrerPolicy: "same-origin",
body: loginParamsStr, body: loginParamsStr,
headers: new Headers({ headers: new Headers({
"Content-Type": "application/x-www-form-urlencoded", "Content-Type": "application/x-www-form-urlencoded"
}), })
}) })
.then((response) => response.text()) .then((response) => response.text())
.then(async (responseText) => { .then(async (responseText) => {
@@ -364,7 +364,7 @@ export default class SASjs {
return { return {
isLoggedIn: loggedIn, isLoggedIn: loggedIn,
userName: this.userName, userName: this.userName
}; };
}) })
.catch((e) => Promise.reject(e)); .catch((e) => Promise.reject(e));
@@ -402,7 +402,7 @@ export default class SASjs {
this.setCsrfTokenWeb, this.setCsrfTokenWeb,
this.csrfTokenWeb this.csrfTokenWeb
); );
return fileUploader.uploadFile(sasJob, files, params); return fileUploader.uploadFile(sasJob, files, params);
} }
@@ -435,7 +435,7 @@ export default class SASjs {
config = { config = {
...this.sasjsConfig, ...this.sasjsConfig,
...config, ...config
}; };
sasJob = sasJob.startsWith("/") ? sasJob.replace("/", "") : sasJob; sasJob = sasJob.startsWith("/") ? sasJob.replace("/", "") : sasJob;
@@ -526,13 +526,15 @@ export default class SASjs {
// members of type 'folder' should be processed first // members of type 'folder' should be processed first
if (serviceJson.members[0].members) { if (serviceJson.members[0].members) {
serviceJson.members[0].members.sort((member: {type: string}) => member.type === 'folder' ? -1 : 1) serviceJson.members[0].members.sort((member: { type: string }) =>
member.type === "folder" ? -1 : 1
);
} }
const members = const members =
serviceJson.members[0].name === "services" serviceJson.members[0].name === "services"
? serviceJson.members[0].members ? serviceJson.members[0].members
: serviceJson.members : serviceJson.members;
await this.createFoldersAndServices( await this.createFoldersAndServices(
appLoc, appLoc,
@@ -553,10 +555,10 @@ export default class SASjs {
requestPromise: { requestPromise: {
promise: null, promise: null,
resolve: null, resolve: null,
reject: null, reject: null
}, },
SASjob: sasJob, SASjob: sasJob,
data, data
}; };
sasjsWaitingRequest.requestPromise.promise = new Promise( sasjsWaitingRequest.requestPromise.promise = new Promise(
@@ -588,7 +590,7 @@ export default class SASjs {
}) })
.catch(async (response) => { .catch(async (response) => {
let error = response.error || response; let error = response.error || response;
if (needsRetry(JSON.stringify(error))) { if (needsRetry(JSON.stringify(error))) {
if (this.retryCountComputeApi < requestRetryLimit) { if (this.retryCountComputeApi < requestRetryLimit) {
let retryResponse = await this.executeJobViaComputeApi( let retryResponse = await this.executeJobViaComputeApi(
@@ -636,10 +638,10 @@ export default class SASjs {
requestPromise: { requestPromise: {
promise: null, promise: null,
resolve: null, resolve: null,
reject: null, reject: null
}, },
SASjob: sasJob, SASjob: sasJob,
data, data
}; };
sasjsWaitingRequest.requestPromise.promise = new Promise( sasjsWaitingRequest.requestPromise.promise = new Promise(
@@ -720,10 +722,10 @@ export default class SASjs {
requestPromise: { requestPromise: {
promise: null, promise: null,
resolve: null, resolve: null,
reject: null, reject: null
}, },
SASjob: sasJob, SASjob: sasJob,
data, data
}; };
const program = config.appLoc const program = config.appLoc
? config.appLoc.replace(/\/?$/, "/") + sasJob.replace(/^\//, "") ? config.appLoc.replace(/\/?$/, "/") + sasJob.replace(/^\//, "")
@@ -737,7 +739,7 @@ export default class SASjs {
}`; }`;
const requestParams = { const requestParams = {
...this.getRequestParamsWeb(config), ...this.getRequestParamsWeb(config)
}; };
const formData = new FormData(); const formData = new FormData();
@@ -766,7 +768,7 @@ export default class SASjs {
} }
const file = new Blob([csv], { const file = new Blob([csv], {
type: "application/csv", type: "application/csv"
}); });
formData.append(name, file, `${name}.csv`); formData.append(name, file, `${name}.csv`);
@@ -823,7 +825,7 @@ export default class SASjs {
method: "POST", method: "POST",
body: formData, body: formData,
referrerPolicy: "same-origin", referrerPolicy: "same-origin",
headers, headers
}) })
.then(async (response) => { .then(async (response) => {
if (!response.ok) { if (!response.ok) {
@@ -834,7 +836,7 @@ export default class SASjs {
const token = response.headers.get(tokenHeader); const token = response.headers.get(tokenHeader);
this.csrfTokenWeb = { this.csrfTokenWeb = {
headerName: tokenHeader, headerName: tokenHeader,
value: token || "", value: token || ""
}; };
} }
} }
@@ -880,7 +882,7 @@ export default class SASjs {
resolve(JSON.parse(jsonResponseText)); resolve(JSON.parse(jsonResponseText));
} else { } else {
reject({ reject({
MESSAGE: this.parseSAS9ErrorResponse(responseText), MESSAGE: this.parseSAS9ErrorResponse(responseText)
}); });
} }
} else if ( } else if (
@@ -924,7 +926,7 @@ export default class SASjs {
return sasjsWaitingRequest.requestPromise.promise; return sasjsWaitingRequest.requestPromise.promise;
} }
private setCsrfTokenWeb = (csrfToken: CsrfToken) => { private setCsrfTokenWeb = (csrfToken: CsrfToken) => {
this.csrfTokenWeb = csrfToken; this.csrfTokenWeb = csrfToken;
}; };
@@ -1056,7 +1058,7 @@ export default class SASjs {
private fetchLogFileContent(logLink: string) { private fetchLogFileContent(logLink: string) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
fetch(logLink, { fetch(logLink, {
method: "GET", method: "GET"
}) })
.then((response: any) => response.text()) .then((response: any) => response.text())
.then((response: any) => resolve(response)) .then((response: any) => resolve(response))
@@ -1100,7 +1102,7 @@ export default class SASjs {
timestamp: new Date(), timestamp: new Date(),
sourceCode, sourceCode,
generatedCode, generatedCode,
SASWORK: sasWork, SASWORK: sasWork
}); });
if (this.sasjsRequests.length > 20) { if (this.sasjsRequests.length > 20) {

View File

@@ -33,7 +33,7 @@ export class SessionManager {
async clearSession(id: string, accessToken?: string) { async clearSession(id: string, accessToken?: string) {
const deleteSessionRequest = { const deleteSessionRequest = {
method: "DELETE", method: "DELETE",
headers: this.getHeaders(accessToken), headers: this.getHeaders(accessToken)
}; };
return await this.request<Session>( return await this.request<Session>(
`${this.serverUrl}/compute/sessions/${id}`, `${this.serverUrl}/compute/sessions/${id}`,
@@ -58,7 +58,7 @@ export class SessionManager {
private async createAndWaitForSession(accessToken?: string) { private async createAndWaitForSession(accessToken?: string) {
const createSessionRequest = { const createSessionRequest = {
method: "POST", method: "POST",
headers: this.getHeaders(accessToken), headers: this.getHeaders(accessToken)
}; };
const { result: createdSession, etag } = await this.request<Session>( const { result: createdSession, etag } = await this.request<Session>(
`${this.serverUrl}/compute/contexts/${this.currentContext!.id}/sessions`, `${this.serverUrl}/compute/contexts/${this.currentContext!.id}/sessions`,
@@ -75,7 +75,7 @@ export class SessionManager {
const { result: contexts } = await this.request<{ const { result: contexts } = await this.request<{
items: Context[]; items: Context[];
}>(`${this.serverUrl}/compute/contexts`, { }>(`${this.serverUrl}/compute/contexts`, {
headers: this.getHeaders(accessToken), headers: this.getHeaders(accessToken)
}); });
const contextsList = const contextsList =
@@ -99,7 +99,7 @@ export class SessionManager {
private getHeaders(accessToken?: string) { private getHeaders(accessToken?: string) {
const headers: any = { const headers: any = {
"Content-Type": "application/json", "Content-Type": "application/json"
}; };
if (accessToken) { if (accessToken) {
headers.Authorization = `Bearer ${accessToken}`; headers.Authorization = `Bearer ${accessToken}`;
@@ -117,7 +117,7 @@ export class SessionManager {
let sessionState = session.state; let sessionState = session.state;
const headers: any = { const headers: any = {
...this.getHeaders(accessToken), ...this.getHeaders(accessToken),
"If-None-Match": etag, "If-None-Match": etag
}; };
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, _) => {
@@ -129,7 +129,7 @@ export class SessionManager {
const { result: state } = await this.request<string>( const { result: state } = await this.request<string>(
`${this.serverUrl}${stateLink.href}?wait=30`, `${this.serverUrl}${stateLink.href}?wait=30`,
{ {
headers, headers
}, },
"text" "text"
); );
@@ -154,7 +154,7 @@ export class SessionManager {
if (this.csrfToken) { if (this.csrfToken) {
options.headers = { options.headers = {
...options.headers, ...options.headers,
[this.csrfToken.headerName]: this.csrfToken.value, [this.csrfToken.headerName]: this.csrfToken.value
}; };
} }
return await makeRequest<T>( return await makeRequest<T>(

View File

@@ -6,6 +6,7 @@ export interface Job {
name: string; name: string;
uri: string; uri: string;
createdBy: string; createdBy: string;
code?: string;
links: Link[]; links: Link[];
results: JobResult; results: JobResult;
error?: any; error?: any;

View File

@@ -4,5 +4,5 @@
*/ */
export enum ServerType { export enum ServerType {
SASViya = "SASVIYA", SASViya = "SASVIYA",
SAS9 = "SAS9", SAS9 = "SAS9"
} }

View File

@@ -3,7 +3,6 @@
* *
*/ */
export interface UploadFile { export interface UploadFile {
file: File; file: File;
fileName: string; fileName: string;
} }

View File

@@ -29,12 +29,12 @@ export async function makeRequest<T>(
const token = response.headers.get(tokenHeader); const token = response.headers.get(tokenHeader);
callback({ callback({
headerName: tokenHeader, headerName: tokenHeader,
value: token || "", value: token || ""
}); });
retryRequest = { retryRequest = {
...request, ...request,
headers: { ...request.headers, [tokenHeader]: token }, headers: { ...request.headers, [tokenHeader]: token }
}; };
return fetch(url, retryRequest).then((res) => { return fetch(url, retryRequest).then((res) => {
etag = res.headers.get("ETag"); etag = res.headers.get("ETag");

View File

@@ -36,7 +36,7 @@ export const parseAndSubmitAuthorizeForm = async (
method: "POST", method: "POST",
credentials: "include", credentials: "include",
body: formData, body: formData,
referrerPolicy: "same-origin", referrerPolicy: "same-origin"
}) })
.then((res) => res.text()) .then((res) => res.text())
.then((res) => { .then((res) => {

View File

@@ -1,16 +1,16 @@
export const parseWeboutResponse = (response: string) => { export const parseWeboutResponse = (response: string) => {
let sasResponse = ""; let sasResponse = "";
if (response.includes(">>weboutBEGIN<<")) { if (response.includes(">>weboutBEGIN<<")) {
try { try {
sasResponse = response sasResponse = response
.split(">>weboutBEGIN<<")[1] .split(">>weboutBEGIN<<")[1]
.split(">>weboutEND<<")[0]; .split(">>weboutEND<<")[0];
} catch (e) { } catch (e) {
sasResponse = ""; sasResponse = "";
console.error(e); console.error(e);
}
} }
}
return sasResponse; return sasResponse;
} };