mirror of
https://github.com/sasjs/adapter.git
synced 2026-04-21 05:01:31 +00:00
Compare commits
25 Commits
v1.0.1
...
suggestions
| Author | SHA1 | Date | |
|---|---|---|---|
| 9598c11f42 | |||
| 334a849caa | |||
| b614bafd03 | |||
| 34cabcde2d | |||
| de82058850 | |||
| 327be9e141 | |||
| f1502c0773 | |||
| c8a2df2d1f | |||
| 2be6200b90 | |||
| 333289cd20 | |||
| 204139cd01 | |||
| 2a38b68e69 | |||
| 39cc20b680 | |||
| 8b3c9746fc | |||
| 7a76f5f343 | |||
| 2bbcd7dee7 | |||
| b02ce07ddf | |||
| 41400bea86 | |||
| 991ac100f6 | |||
| 66c156d299 | |||
| 7d0d830391 | |||
| 0b038380c7 | |||
| 162ba5e837 | |||
| f217b3eb04 | |||
| 6b9436b1c6 |
@@ -6,7 +6,7 @@ name: SASjs Build and Publish
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
+7
-5
@@ -1,6 +1,6 @@
|
||||
# Contributing
|
||||
|
||||
Contributions to SASjs are very welcome! When making a PR, test cases should be included. To help in unit testing, be sure to run the following when making changes:
|
||||
Contributions to SASjs are very welcome! When making a PR, test cases should be included. To help in unit testing, be sure to run the following when making changes:
|
||||
|
||||
```
|
||||
# the following creates a tarball in the build folder of SASjs
|
||||
@@ -10,12 +10,13 @@ npm run-script package:lib
|
||||
npm install ../sasjs/build/<tarball filename>
|
||||
```
|
||||
|
||||
Tests are run using cypress. Before running tests, you need to define the following backend services:
|
||||
Tests are run using cypress. Before running tests, you need to define the following backend services:
|
||||
|
||||
# SAS 9
|
||||
|
||||
```
|
||||
|
||||
filename mc url "https://raw.githubusercontent.com/macropeople/macrocore/master/mc_all.sas?_=1";
|
||||
filename mc url "https://raw.githubusercontent.com/macropeople/macrocore/main/mc_all.sas?_=1";
|
||||
%inc mc;
|
||||
filename ft15f001 temp;
|
||||
parmcards4;
|
||||
@@ -37,8 +38,9 @@ parmcards4;
|
||||
```
|
||||
|
||||
# Viya
|
||||
|
||||
```
|
||||
filename mc url "https://raw.githubusercontent.com/macropeople/macrocore/master/mc_all.sas";
|
||||
filename mc url "https://raw.githubusercontent.com/macropeople/macrocore/main/mc_all.sas";
|
||||
%inc mc;
|
||||
|
||||
filename ft15f001 temp;
|
||||
@@ -77,4 +79,4 @@ parmcards4;
|
||||
%mv_createwebservice(path=/Public/app/common,name=sendArr)
|
||||
```
|
||||
|
||||
The above services will return anything you send. To run the tests simply launch `npm run cypress`.
|
||||
The above services will return anything you send. To run the tests simply launch `npm run cypress`.
|
||||
|
||||
@@ -1,31 +1,29 @@
|
||||
[](https://www.jsdelivr.com/package/npm/sasjs)
|
||||
[](https://www.jsdelivr.com/package/npm/@sasjs/adapter)
|
||||
|
||||
# SASjs
|
||||
# @sasjs/adapter
|
||||
|
||||
SASjs is a open-source framework for building Web Apps on SAS® platforms. You can use as much or as little of it as you like. This repository contains the JS adapter, the part that handles the to/from SAS communication on the client side. There are 3 ways to install it:
|
||||
|
||||
1 - `npm install sasjs` - for use in a node project
|
||||
1 - `npm install @sasjs/adapter` - for use in a node project
|
||||
|
||||
2 - [Download](https://cdn.jsdelivr.net/npm/sasjs/index.js) and use a copy of the latest JS file
|
||||
2 - [Download](https://cdn.jsdelivr.net/npm/@sasjs/adapter@1/index.js) and use a copy of the latest JS file
|
||||
|
||||
3 - Reference directly from the CDN - in which case click [here](https://www.jsdelivr.com/package/npm/sasjs?tab=collection) and select "SRI" to get the script tag with the integrity hash.
|
||||
3 - Reference directly from the CDN - in which case click [here](https://www.jsdelivr.com/package/npm/@sasjs/adapter?tab=collection) and select "SRI" to get the script tag with the integrity hash.
|
||||
|
||||
If you are short on time and just need to build an app quickly, then check out [this video](https://vimeo.com/393161794) and the [react-seed-app](https://github.com/macropeople/react-seed-app) which provides some boilerplate.
|
||||
|
||||
For more information on building web apps with SAS, check out [sasjs.io](https://sasjs.io)
|
||||
|
||||
## None of this makes sense. How do I build an app with it?
|
||||
|
||||
## None of this makes sense. How do I build an app with it?
|
||||
|
||||
Ok ok. Deploy this [example.html](https://github.com/macropeople/sasjs/blob/master/example.html) file to your web server, and update `servertype` to `SAS9` or `SASVIYA` depending on your backend.
|
||||
Ok ok. Deploy this [example.html](https://github.com/sasjs/adapter/blob/main/example.html) file to your web server, and update `servertype` to `SAS9` or `SASVIYA` depending on your backend.
|
||||
|
||||
The backend part can be deployed as follows:
|
||||
|
||||
```
|
||||
%let appLoc=/Public/app/readme; /* Metadata or Viya Folder location as per SASjs config */
|
||||
/* compile macros (can also be downloaded & compiled seperately) */
|
||||
filename mc url "https://raw.githubusercontent.com/macropeople/macrocore/master/mc_all.sas";
|
||||
%inc mc;
|
||||
%let appLoc=/Public/app/readme; /* Metadata or Viya Folder per SASjs config */
|
||||
filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
|
||||
%inc mc; /* compile macros (can also be downloaded & compiled seperately) */
|
||||
filename ft15f001 temp;
|
||||
parmcards4;
|
||||
%webout(FETCH) /* receive all data as SAS datasets */
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
theme: jekyll-theme-minimal
|
||||
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
+10
-35
File diff suppressed because one or more lines are too long
+222
-115
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
+108
-101
@@ -1,107 +1,114 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
|
||||
<script src="https://cdn.jsdelivr.net/combine/npm/chart.js@2.9.3,npm/jquery@3.5.1,npm/sasjs@2.11.0"></script>
|
||||
<script>
|
||||
var sasJs = new SASjs.default({appLoc: "/Products/demo/readme"
|
||||
,serverType:"SAS9", debug: "false"
|
||||
});
|
||||
function initSasJs() {
|
||||
$('#loading-spinner').show()
|
||||
// instantiate sasjs with options such as backend app location
|
||||
// login (it's also possible to set an autologin when making requests)
|
||||
sasJs.logIn(
|
||||
$('#username')[0].value
|
||||
,$('#password')[0].value
|
||||
).then((response) => {
|
||||
if (response.isLoggedIn === true) {
|
||||
$('#loading-spinner').hide()
|
||||
$('.login').hide()
|
||||
$('#getdata').show()
|
||||
$('#cars').show()
|
||||
}
|
||||
})
|
||||
}
|
||||
function getData(){
|
||||
$('#loading-spinner').show()
|
||||
$('#myChart').remove();
|
||||
$('#chart-container').append('<canvas id="myChart" style="display: none;"></canvas>')
|
||||
// make a request to a SAS service
|
||||
var type = $("#cars")[0].options[$("#cars")[0].selectedIndex].value;
|
||||
// request data from an endpoint under your appLoc
|
||||
sasJs.request("/common/getdata", {
|
||||
// send data as an array of objects - each object is one row
|
||||
fromjs: [{ type: type }]
|
||||
}).then((response) => {
|
||||
$('#myChart').show();
|
||||
var labels = []
|
||||
var data = []
|
||||
response.areas.map((d) => {
|
||||
labels.push(d.MAKE);
|
||||
data.push(d.AVPRICE);
|
||||
})
|
||||
$('#loading-spinner').hide()
|
||||
initGraph(labels, data, type);
|
||||
})
|
||||
}
|
||||
function initGraph(labels, data, type){
|
||||
var myCanvas = document.getElementById("myChart");
|
||||
var ctx = myCanvas.getContext("2d");
|
||||
var myChart = new Chart(ctx, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: [{
|
||||
label: "Average Invoice Price in USD for " + type + " Cars by Manufacturer",
|
||||
data: data,
|
||||
backgroundColor: "rgba(255,99,132,0.2)",
|
||||
borderColor: "rgba(255,99,132,1)",
|
||||
borderWidth: 1,
|
||||
hoverBackgroundColor: "rgba(255,99,132,0.4)",
|
||||
hoverBorderColor: "rgba(255,99,132,1)",
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
maintainAspectRatio: false,
|
||||
scales: {yAxes: [{ticks: {beginAtZero: true}}]}
|
||||
<head>
|
||||
<meta charset='utf-8' http-equiv='X-UA-Compatible' content='IE=edge' />
|
||||
<link rel='stylesheet' href='https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css' integrity='sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh' crossorigin='anonymous'>
|
||||
<script src='https://cdn.jsdelivr.net/combine/npm/chart.js@2.9.3,npm/jquery@3.5.1,npm/@sasjs/adapter@1'></script>
|
||||
<script>
|
||||
const sasJs = new SASjs.default({
|
||||
appLoc: '/Products/demo/readme',
|
||||
serverType:'SAS9',
|
||||
debug: 'false'
|
||||
})
|
||||
|
||||
const initSasJs = () => {
|
||||
$('#loading-spinner').show()
|
||||
|
||||
// instantiate sasJs with options such as backend app location
|
||||
// login (it's also possible to set an auto login when making requests)
|
||||
sasJs.logIn($('#username')[0].value, $('#password')[0].value)
|
||||
.then((response) => {
|
||||
if (response.isLoggedIn === true) {
|
||||
$('#loading-spinner').hide()
|
||||
$('.login').hide()
|
||||
$('#getDataBtn').show()
|
||||
$('#cars').show()
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container-fluid" style="text-align: center; margin-top: 10px;">
|
||||
<div class="row">
|
||||
<div class="col-lg-5 col-md-7 col-sm-10 mx-auto mx-auto">
|
||||
<h1>Demo Seed App for <span class="code">SASjs</span></h1>
|
||||
<div class="login" id="login-form">
|
||||
<div class="form-group">
|
||||
<input class="form-control" type="text" id="username" placeholder="Enter username" />
|
||||
|
||||
// make a request to a SAS service
|
||||
const getData = () => {
|
||||
$('#loading-spinner').show()
|
||||
$('#myChart').remove()
|
||||
$('#chart-container').append("<canvas id='myChart' style='display: none'></canvas>")
|
||||
|
||||
const type = $('#cars')[0].options[$('#cars')[0].selectedIndex].value
|
||||
|
||||
// request data from an endpoint under your appLoc
|
||||
// send data as an array of objects - each object is one row
|
||||
sasJs.request('/common/getdata', {fromjs: [{ type: type }]})
|
||||
.then((response) => {
|
||||
$('#myChart').show()
|
||||
$('#loading-spinner').hide()
|
||||
|
||||
const labels = response.areas.map(area => area.MAKE)
|
||||
const data = response.areas.map(area => area.AVPRICE)
|
||||
|
||||
initGraph(labels, data, type)
|
||||
})
|
||||
}
|
||||
|
||||
const initGraph = (labels, data, type) => {
|
||||
const myCanvas = document.getElementById('myChart')
|
||||
const ctx = myCanvas.getContext('2d')
|
||||
|
||||
const myChart = new Chart(ctx, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: [{
|
||||
label: `Average Invoice Price in USD for ${type} Cars by Manufacturer`,
|
||||
data: data,
|
||||
backgroundColor: 'rgba(255,99,132,0.2)',
|
||||
borderColor: 'rgba(255,99,132,1)',
|
||||
borderWidth: 1,
|
||||
hoverBackgroundColor: 'rgba(255,99,132,0.4)',
|
||||
hoverBorderColor: 'rgba(255,99,132,1)',
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
maintainAspectRatio: false,
|
||||
scales: {yAxes: [{ticks: {beginAtZero: true}}]}
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class='container-fluid' style='text-align: center; margin-top: 10px'>
|
||||
<div class='row'>
|
||||
<div class='col-lg-5 col-md-7 col-sm-10 mx-auto mx-auto'>
|
||||
<h1>Demo Seed App for <span class='code'>SASjs</span></h1>
|
||||
<div class='login' id='login-form'>
|
||||
<div class='form-group'>
|
||||
<input class='form-control' type='text' id='username' placeholder='Enter username' />
|
||||
</div>
|
||||
<div class='form-group'>
|
||||
<input class='form-control' type='password' id='password' placeholder='Enter password' />
|
||||
</div>
|
||||
<button id='login' onclick='initSasJs()' class='login btn btn-primary' style='margin-bottom: 5px'>Log In</button>
|
||||
</div>
|
||||
<select name='cars' id='cars' style='margin-bottom: 5px; display: none' class='form-control'>
|
||||
<option value='Hybrid'>Hybrid</option>
|
||||
<option value='SUV'>SUV</option>
|
||||
<option value='Sedan'>Sedan</option>
|
||||
<option value='Sports'>Sports</option>
|
||||
<option value='Truck'>Truck</option>
|
||||
<option value='Wagon'>Wagon</option>
|
||||
</select>
|
||||
<button id='getDataBtn' onclick='getData()' style='margin-bottom: 5px; display: none' class='btn btn-success'>Get Data</button>
|
||||
<br>
|
||||
<br>
|
||||
<div id='loading-spinner' class='spinner-border text-primary' role='status' style='display: none'>
|
||||
<span class='sr-only'>Loading...</span>
|
||||
</div>
|
||||
<br>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input class="form-control" type="password" id="password" placeholder="Enter password" />
|
||||
</div>
|
||||
<button id="login" onclick="initSasJs()" class="login btn btn-primary" style="margin-bottom: 5px;">Log In</button>
|
||||
</div>
|
||||
<select name="cars" id="cars" style="margin-bottom: 5px; display: none;" class="form-control">
|
||||
<option value="Hybrid">Hybrid</option>
|
||||
<option value="SUV">SUV</option>
|
||||
<option value="Sedan">Sedan</option>
|
||||
<option value="Sports">Sports</option>
|
||||
<option value="Truck">Truck</option>
|
||||
<option value="Wagon">Wagon</option>
|
||||
</select>
|
||||
<button id="getdata" onclick="getData()" style="margin-bottom: 5px; display: none;" class="btn btn-success">Get Data</button><br><br>
|
||||
<div id="loading-spinner" class="spinner-border text-primary" role="status" style="display: none;">
|
||||
<span class="sr-only">Loading...</span>
|
||||
</div><br>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="chart-container" style="height: 65vh; width: 100%; position: relative; margin: auto;">
|
||||
<canvas id="myChart" style="display: none;"></canvas>
|
||||
</div>
|
||||
</body>
|
||||
<div id='chart-container' style='height: 65vh; width: 100%; position: relative; margin: auto'>
|
||||
<canvas id='myChart' style='display: none'></canvas>
|
||||
</div>
|
||||
</body>
|
||||
</head>
|
||||
Generated
+18
-2305
File diff suppressed because it is too large
Load Diff
+5
-4
@@ -22,6 +22,9 @@
|
||||
{
|
||||
"pkgRoot": "/build"
|
||||
}
|
||||
],
|
||||
"branches": [
|
||||
"main"
|
||||
]
|
||||
},
|
||||
"keywords": [
|
||||
@@ -36,11 +39,9 @@
|
||||
},
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@cypress/webpack-preprocessor": "^4.1.5",
|
||||
"@types/isomorphic-fetch": "0.0.35",
|
||||
"@types/jest": "^26.0.3",
|
||||
"@types/jest": "^26.0.4",
|
||||
"cp": "^0.2.0",
|
||||
"cypress": "^4.9.0",
|
||||
"jest": "^25.5.4",
|
||||
"path": "^0.12.7",
|
||||
"prettier": "^2.0.5",
|
||||
@@ -51,7 +52,7 @@
|
||||
"tslint": "^6.1.2",
|
||||
"tslint-config-prettier": "^1.18.0",
|
||||
"typedoc": "^0.17.8",
|
||||
"typedoc-neo-theme": "^1.0.8",
|
||||
"typedoc-neo-theme": "^1.0.9",
|
||||
"typedoc-plugin-external-module-name": "^4.0.3",
|
||||
"typescript": "^3.9.6",
|
||||
"uglifyjs-webpack-plugin": "^2.2.0",
|
||||
|
||||
@@ -5,5 +5,6 @@ import App from './App';
|
||||
test('renders learn react link', () => {
|
||||
const { getByText } = render(<App />);
|
||||
const linkElement = getByText(/learn react/i);
|
||||
|
||||
expect(linkElement).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -9,9 +9,7 @@ const App = (): ReactElement<{}> => {
|
||||
const { adapter } = useContext(AppContext);
|
||||
|
||||
useEffect(() => {
|
||||
if (adapter) {
|
||||
adapter.setDebugState(debug);
|
||||
}
|
||||
if (adapter) adapter.setDebugState(debug);
|
||||
}, [debug, adapter]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -33,7 +31,7 @@ const App = (): ReactElement<{}> => {
|
||||
<label className="switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
onChange={(e) => setDebug(e.target.checked)}
|
||||
onChange={(e) => setDebug(e.target.checked)} // FIXME: rename 'e' => 'event'
|
||||
/>
|
||||
<span className="knob"></span>
|
||||
</label>
|
||||
@@ -45,7 +43,7 @@ const App = (): ReactElement<{}> => {
|
||||
type="text"
|
||||
className="app-loc-input"
|
||||
value={appLoc}
|
||||
onChange={(e) => setAppLoc(e.target.value)}
|
||||
onChange={(e) => setAppLoc(e.target.value)} // FIXME: rename 'e' => 'event'
|
||||
placeholder="AppLoc"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -9,11 +9,14 @@ const Login = (): ReactElement<{}> => {
|
||||
const appContext = useContext(AppContext);
|
||||
|
||||
const handleSubmit = useCallback(
|
||||
(e) => {
|
||||
(e) => { // FIXME: rename 'e' => 'event'
|
||||
e.preventDefault();
|
||||
appContext.adapter.logIn(username, password).then(() => {
|
||||
appContext.setIsLoggedIn(true);
|
||||
});
|
||||
|
||||
appContext.adapter.logIn(username, password)
|
||||
.then(() => {
|
||||
appContext.setIsLoggedIn(true);
|
||||
});
|
||||
// FIXME: catch block
|
||||
},
|
||||
[username, password, appContext]
|
||||
);
|
||||
@@ -38,7 +41,7 @@ const Login = (): ReactElement<{}> => {
|
||||
type="password"
|
||||
value={password}
|
||||
required
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
onChange={(e) => setPassword(e.target.value)} // FIXME: rename 'e' => 'event'
|
||||
/>
|
||||
</div>
|
||||
<button type="submit" className="submit-button">
|
||||
|
||||
@@ -8,16 +8,14 @@ interface PrivateRouteProps {
|
||||
path: string;
|
||||
}
|
||||
|
||||
const PrivateRoute = (
|
||||
props: PrivateRouteProps
|
||||
): ReactElement<PrivateRouteProps> => {
|
||||
const PrivateRoute = (props: PrivateRouteProps): ReactElement<PrivateRouteProps> => {
|
||||
const { component, path, exact } = props;
|
||||
const appContext = useContext(AppContext);
|
||||
return appContext.isLoggedIn ? (
|
||||
|
||||
return appContext.isLoggedIn ?
|
||||
<Route component={component} path={path} exact={exact} />
|
||||
) : (
|
||||
:
|
||||
<Redirect to="/login" />
|
||||
);
|
||||
};
|
||||
|
||||
export default PrivateRoute;
|
||||
@@ -2,25 +2,24 @@ import React, { useEffect, useState, ReactElement, useContext } from "react";
|
||||
import TestSuiteComponent from "./components/TestSuite";
|
||||
import TestSuiteCard from "./components/TestSuiteCard";
|
||||
import { TestSuite, Test } from "./types";
|
||||
import { basicTests } from "./testSuites/Basic";
|
||||
import { basicTests } from "./testSuites/Basic"; // FIXME: declared but never used
|
||||
import "./TestSuiteRunner.scss";
|
||||
import SASjs from "sasjs";
|
||||
import { AppContext } from "./context/AppContext";
|
||||
import { sendArrTests, sendObjTests } from "./testSuites/RequestData";
|
||||
import { sendArrTests, sendObjTests } from "./testSuites/RequestData"; // FIXME: declared but never used
|
||||
import { specialCaseTests } from "./testSuites/SpecialCases";
|
||||
import { sasjsRequestTests } from "./testSuites/SasjsRequests";
|
||||
import { sasjsRequestTests } from "./testSuites/SasjsRequests"; // FIXME: declared but never used
|
||||
|
||||
interface TestSuiteRunnerProps {
|
||||
adapter: SASjs;
|
||||
}
|
||||
const TestSuiteRunner = (
|
||||
props: TestSuiteRunnerProps
|
||||
): ReactElement<TestSuiteRunnerProps> => {
|
||||
|
||||
const TestSuiteRunner = (props: TestSuiteRunnerProps): ReactElement<TestSuiteRunnerProps> => {
|
||||
const { adapter } = props;
|
||||
const { config } = useContext(AppContext);
|
||||
const { config } = useContext(AppContext); // FIXME: declared but never used
|
||||
const [testSuites, setTestSuites] = useState<TestSuite[]>([]);
|
||||
const [runTests, setRunTests] = useState(false);
|
||||
const [completedTestSuites, setCompletedTestSuites] = useState<
|
||||
const [completedTestSuites, setCompletedTestSuites] = useState< // FIXME: create interface
|
||||
{
|
||||
name: string;
|
||||
completedTests: {
|
||||
@@ -65,12 +64,15 @@ const TestSuiteRunner = (
|
||||
<>
|
||||
<div className="button-container">
|
||||
<button
|
||||
className={runTests ? "submit-button disabled" : "submit-button"}
|
||||
className={runTests ? "submit-button disabled" : "submit-button"} // TODO: 'submit-button' class should be assigned by default
|
||||
onClick={() => setRunTests(true)}
|
||||
disabled={runTests}
|
||||
>
|
||||
{runTests ? (
|
||||
<>
|
||||
{
|
||||
// FIXME: fragment is not needed in this case
|
||||
}
|
||||
<div className="loading-spinner"></div>Running tests...
|
||||
</>
|
||||
) : (
|
||||
@@ -78,7 +80,7 @@ const TestSuiteRunner = (
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
{completedTestSuites.map((completedTestSuite, index) => {
|
||||
{completedTestSuites.map((completedTestSuite, index) => { // TODO: refactor
|
||||
return (
|
||||
<TestSuiteCard
|
||||
key={index}
|
||||
@@ -100,22 +102,19 @@ const TestSuiteRunner = (
|
||||
}[]
|
||||
) => {
|
||||
const currentIndex = testSuites.indexOf(currentTestSuite);
|
||||
const nextIndex =
|
||||
currentIndex < testSuites.length - 1 ? currentIndex + 1 : -1;
|
||||
if (nextIndex >= 0) {
|
||||
setCurrentTestSuite(testSuites[nextIndex]);
|
||||
} else {
|
||||
setCurrentTestSuite(null);
|
||||
}
|
||||
const nextIndex = currentIndex < testSuites.length - 1 ? currentIndex + 1 : -1;
|
||||
|
||||
if (nextIndex >= 0) setCurrentTestSuite(testSuites[nextIndex]);
|
||||
else setCurrentTestSuite(null);
|
||||
|
||||
const newCompletedTestSuites = [
|
||||
...completedTestSuites,
|
||||
{ name, completedTests },
|
||||
];
|
||||
|
||||
setCompletedTestSuites(newCompletedTestSuites);
|
||||
|
||||
if (newCompletedTestSuites.length === testSuites.length) {
|
||||
setRunTests(false);
|
||||
}
|
||||
if (newCompletedTestSuites.length === testSuites.length) setRunTests(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { ReactElement, useEffect, useState } from "react";
|
||||
import TestCard from "./TestCard";
|
||||
import { start } from "repl";
|
||||
import { start } from "repl"; // FIXME: declared but never used
|
||||
|
||||
interface TestProps {
|
||||
title: string;
|
||||
@@ -39,28 +39,36 @@ const Test = (props: TestProps): ReactElement<TestProps> => {
|
||||
|
||||
useEffect(() => {
|
||||
if (test && assertion) {
|
||||
const startTime = new Date().valueOf();
|
||||
const startTime = new Date().valueOf()
|
||||
|
||||
setIsRunning(true);
|
||||
setIsPassed(false);
|
||||
|
||||
beforeTestFunction()
|
||||
.then(() => test(context))
|
||||
.then((res) => {
|
||||
setIsRunning(false);
|
||||
setIsPassed(assertion(res, context));
|
||||
setIsPassed(assertion(res, context))
|
||||
|
||||
return Promise.resolve(assertion(res, context));
|
||||
})
|
||||
.then((testResult) => {
|
||||
afterTestFunction();
|
||||
|
||||
const endTime = new Date().valueOf();
|
||||
const executionTime = (endTime - startTime) / 1000;
|
||||
|
||||
onCompleted({ result: testResult, error: null, executionTime });
|
||||
})
|
||||
.catch((e) => {
|
||||
setIsRunning(false);
|
||||
setIsPassed(false);
|
||||
|
||||
console.error(e);
|
||||
|
||||
const endTime = new Date().valueOf();
|
||||
const executionTime = (endTime - startTime) / 1000;
|
||||
|
||||
onCompleted({ result: false, error: e, executionTime });
|
||||
});
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ const TestCard = (props: TestCardProps): ReactElement<TestCardProps> => {
|
||||
<span className="execution-time">
|
||||
{executionTime ? executionTime.toFixed(2) + "s" : ""}
|
||||
</span>
|
||||
{status === "running" && (
|
||||
{status === "running" && ( // FIXME: use switch statement
|
||||
<div>
|
||||
<span className="icon running"></span>Running...
|
||||
</div>
|
||||
|
||||
@@ -22,7 +22,7 @@ interface TestSuiteProps {
|
||||
const TestSuite = (props: TestSuiteProps): ReactElement<TestSuiteProps> => {
|
||||
const { name, tests, beforeAll, afterAll, onCompleted } = props;
|
||||
const [context, setContext] = useState<any>(null);
|
||||
const [completedTests, setCompletedTests] = useState<
|
||||
const [completedTests, setCompletedTests] = useState< // TODO: create an interface
|
||||
{
|
||||
test: Test;
|
||||
result: boolean;
|
||||
@@ -31,24 +31,21 @@ const TestSuite = (props: TestSuiteProps): ReactElement<TestSuiteProps> => {
|
||||
}[]
|
||||
>([]);
|
||||
const [currentTest, setCurrentTest] = useState<Test | null>(
|
||||
(null as unknown) as Test
|
||||
(null as unknown) as Test // ?
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (beforeAll) {
|
||||
beforeAll().then((data) => setContext({ data }));
|
||||
}
|
||||
if (beforeAll) beforeAll().then((data) => setContext({ data }))
|
||||
}, [beforeAll]);
|
||||
|
||||
useEffect(() => {
|
||||
if (tests.length) {
|
||||
setCurrentTest(tests[0]);
|
||||
}
|
||||
if (tests.length) setCurrentTest(tests[0])
|
||||
|
||||
setCompletedTests([]);
|
||||
setContext(null);
|
||||
}, [tests]);
|
||||
|
||||
return (!!beforeAll && !!context) || !beforeAll ? (
|
||||
return (!!beforeAll && !!context) || !beforeAll ? ( // ?
|
||||
<div className="test-suite">
|
||||
<div className="test-suite-name running">{name}</div>
|
||||
{currentTest && (
|
||||
@@ -64,29 +61,27 @@ const TestSuite = (props: TestSuiteProps): ReactElement<TestSuiteProps> => {
|
||||
error: completedTest.error,
|
||||
executionTime: completedTest.executionTime,
|
||||
},
|
||||
];
|
||||
]
|
||||
|
||||
setCompletedTests(newCompleteTests);
|
||||
|
||||
const currentIndex = tests.indexOf(currentTest);
|
||||
const nextIndex =
|
||||
currentIndex < tests.length - 1 ? currentIndex + 1 : -1;
|
||||
if (nextIndex >= 0) {
|
||||
setCurrentTest(tests[nextIndex]);
|
||||
} else {
|
||||
setCurrentTest(null);
|
||||
}
|
||||
const nextIndex = currentIndex < tests.length - 1 ? currentIndex + 1 : -1;
|
||||
|
||||
if (nextIndex >= 0) setCurrentTest(tests[nextIndex]);
|
||||
else setCurrentTest(null);
|
||||
|
||||
if (newCompleteTests.length === tests.length) {
|
||||
if (afterAll) {
|
||||
afterAll().then(() => onCompleted(name, newCompleteTests));
|
||||
} else {
|
||||
onCompleted(name, newCompleteTests);
|
||||
}
|
||||
if (afterAll) afterAll().then(() => onCompleted(name, newCompleteTests))
|
||||
else onCompleted(name, newCompleteTests)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{completedTests.map((completedTest, index) => {
|
||||
const { test, result, error } = completedTest;
|
||||
{completedTests.map((test, index) => {
|
||||
const { test, result, error } = test;
|
||||
const { title, description } = test;
|
||||
|
||||
return (
|
||||
<TestCard
|
||||
key={index}
|
||||
@@ -99,7 +94,7 @@ const TestSuite = (props: TestSuiteProps): ReactElement<TestSuiteProps> => {
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
<></> // FIXME: use {null} instead
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -12,20 +12,19 @@ interface TestSuiteCardProps {
|
||||
executionTime: number;
|
||||
}[];
|
||||
}
|
||||
const TestSuiteCard = (
|
||||
props: TestSuiteCardProps
|
||||
): ReactElement<TestSuiteCardProps> => {
|
||||
const TestSuiteCard = (props: TestSuiteCardProps): ReactElement<TestSuiteCardProps> => {
|
||||
const { name, tests } = props;
|
||||
const overallStatus = tests.map((t) => t.result).reduce((x, y) => x && y);
|
||||
const overallStatus = tests.map((t) => t.result).reduce((x, y) => x && y); // TODO: refactor variable names
|
||||
|
||||
return (
|
||||
<div className="test-suite">
|
||||
<div className={`test-suite-name ${overallStatus ? "passed" : "failed"}`}>
|
||||
{name}
|
||||
</div>
|
||||
{tests.map((completedTest, index) => {
|
||||
const { test, result, error, executionTime } = completedTest;
|
||||
{tests.map((test, index) => {
|
||||
const { test, result, error, executionTime } = test;
|
||||
const { title, description } = test;
|
||||
|
||||
return (
|
||||
<TestCard
|
||||
key={index}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React, { createContext, useState, useEffect, ReactNode } from "react";
|
||||
import SASjs from "sasjs";
|
||||
|
||||
export const AppContext = createContext<{
|
||||
config: any;
|
||||
sasJsConfig: any;
|
||||
export const AppContext = createContext<{ // TODO: create an interface
|
||||
config: any; // TODO: be more specific on type declaration
|
||||
sasJsConfig: any; // TODO: be more specific on type declaration
|
||||
isLoggedIn: boolean;
|
||||
setIsLoggedIn: (value: boolean) => void;
|
||||
adapter: SASjs;
|
||||
@@ -16,25 +16,24 @@ export const AppContext = createContext<{
|
||||
});
|
||||
|
||||
export const AppProvider = (props: { children: ReactNode }) => {
|
||||
const [config, setConfig] = useState<{ sasJsConfig: any }>({
|
||||
sasJsConfig: null,
|
||||
});
|
||||
|
||||
const [config, setConfig] = useState<{ sasJsConfig: any }>({sasJsConfig: null}); // TODO: be more specific on type declaration
|
||||
const [adapter, setAdapter] = useState<SASjs>((null as unknown) as SASjs);
|
||||
|
||||
const [isLoggedIn, setIsLoggedIn] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
fetch("config.json")
|
||||
fetch("config.json") // TODO: use axios instead of fetch
|
||||
.then((res) => res.json())
|
||||
.then((configJson: any) => {
|
||||
.then((configJson: any) => { // TODO: be more specific on type declaration
|
||||
setConfig(configJson);
|
||||
|
||||
const sasjs = new SASjs(configJson.sasJsConfig);
|
||||
|
||||
setAdapter(sasjs);
|
||||
|
||||
sasjs.checkSession().then((response) => {
|
||||
setIsLoggedIn(response.isLoggedIn);
|
||||
});
|
||||
});
|
||||
}); // FIXME: add catch block
|
||||
});// FIXME: add catch block
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
||||
@@ -33,18 +33,16 @@ export const basicTests = (
|
||||
test: async () => {
|
||||
return adapter.logIn(userName, password);
|
||||
},
|
||||
assertion: (response: any) =>
|
||||
assertion: (response: any) => // FIXME: be more specific on type declaration
|
||||
response && response.isLoggedIn && response.userName === userName,
|
||||
},
|
||||
{
|
||||
title: "Default config",
|
||||
description:
|
||||
"Should instantiate with default config when none is provided",
|
||||
test: async () => {
|
||||
return Promise.resolve(new SASjs());
|
||||
},
|
||||
description: "Should instantiate with default config when none is provided",
|
||||
test: async () => Promise.resolve(new SASjs()),
|
||||
assertion: (sasjsInstance: SASjs) => {
|
||||
const sasjsConfig = sasjsInstance.getSasjsConfig();
|
||||
|
||||
return (
|
||||
sasjsConfig.serverUrl === defaultConfig.serverUrl &&
|
||||
sasjsConfig.pathSAS9 === defaultConfig.pathSAS9 &&
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import SASjs from "sasjs";
|
||||
import { TestSuite } from "../types";
|
||||
|
||||
const stringData: any = { table1: [{ col1: "first col value" }] };
|
||||
const numericData: any = { table1: [{ col1: 3.14159265 }] };
|
||||
const multiColumnData: any = {
|
||||
const stringData: any = { table1: [{ col1: "first col value" }] }; // TODO: be more specific on type declaration
|
||||
const numericData: any = { table1: [{ col1: 3.14159265 }] }; // TODO: be more specific on type declaration
|
||||
const multiColumnData: any = { // TODO: be more specific on type declaration
|
||||
table1: [{ col1: 42, col2: 1.618, col3: "x", col4: "x" }],
|
||||
};
|
||||
const multipleRowsWithNulls: any = {
|
||||
const multipleRowsWithNulls: any = { // TODO: be more specific on type declaration
|
||||
table1: [
|
||||
{ col1: 42, col2: null, col3: "x", col4: "" },
|
||||
{ col1: 42, col2: null, col3: "x", col4: "" },
|
||||
@@ -15,7 +15,7 @@ const multipleRowsWithNulls: any = {
|
||||
{ col1: 42, col2: 1.62, col3: "x", col4: "x" },
|
||||
],
|
||||
};
|
||||
const multipleColumnsWithNulls: any = {
|
||||
const multipleColumnsWithNulls: any = { // TODO: be more specific on type declaration
|
||||
table1: [
|
||||
{ col1: 42, col2: null, col3: "x", col4: null },
|
||||
{ col1: 42, col2: null, col3: "x", col4: null },
|
||||
@@ -25,21 +25,20 @@ const multipleColumnsWithNulls: any = {
|
||||
],
|
||||
};
|
||||
|
||||
const getLongStringData = (length = 32764) => {
|
||||
const getLongStringData = (length = 32764) => { // FIXME: add type declaration
|
||||
let x = "X";
|
||||
for (let i = 1; i <= length; i++) {
|
||||
x = x + "X";
|
||||
}
|
||||
const data: any = { table1: [{ col1: x }] };
|
||||
|
||||
for (let i = 1; i <= length; i++) x += 'X'
|
||||
|
||||
const data: any = { table1: [{ col1: x }] }; // TODO: be more specific on type declaration
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const getLargeObjectData = () => {
|
||||
const data = { table1: [{ big: "data" }] };
|
||||
|
||||
for (let i = 1; i < 10000; i++) {
|
||||
data.table1.push(data.table1[0]);
|
||||
}
|
||||
for (let i = 1; i < 10000; i++) data.table1.push(data.table1[0])
|
||||
|
||||
return data;
|
||||
};
|
||||
@@ -50,12 +49,8 @@ export const sendArrTests = (adapter: SASjs): TestSuite => ({
|
||||
{
|
||||
title: "Single string value",
|
||||
description: "Should send an array with a single string value",
|
||||
test: () => {
|
||||
return adapter.request("common/sendArr", stringData);
|
||||
},
|
||||
assertion: (res: any) => {
|
||||
return res.table1[0][0] === stringData.table1[0].col1;
|
||||
},
|
||||
test: () => adapter.request("common/sendArr", stringData),
|
||||
assertion: (res: any) => res.table1[0][0] === stringData.table1[0].col1 // TODO: be more specific on type declaration
|
||||
},
|
||||
{
|
||||
title: "Long string value",
|
||||
@@ -64,22 +59,17 @@ export const sendArrTests = (adapter: SASjs): TestSuite => ({
|
||||
test: () => {
|
||||
return adapter.request("common/sendArr", getLongStringData());
|
||||
},
|
||||
assertion: (res: any) => {
|
||||
const longStringData = getLongStringData();
|
||||
return res.table1[0][0] === longStringData.table1[0].col1;
|
||||
},
|
||||
assertion: (res: any) => res.table1[0][0] === getLongStringData().table1[0].col1 // TODO: be more specific on type declaration
|
||||
},
|
||||
{
|
||||
title: "Overly long string value",
|
||||
description:
|
||||
"Should error out with long string values over 32765 characters",
|
||||
test: () => {
|
||||
return adapter
|
||||
.request("common/sendArr", getLongStringData(32767))
|
||||
.catch((e) => e);
|
||||
},
|
||||
assertion: (error: any) => {
|
||||
return !!error && !!error.MESSAGE;
|
||||
test: () => adapter
|
||||
.request("common/sendArr", getLongStringData(32767))
|
||||
.catch((e) => e), // TODO: rename
|
||||
assertion: (error: any) => { // TODO: be more specific on type declaration
|
||||
return !!error && !!error.MESSAGE; // FIXME: refactor
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -88,7 +78,7 @@ export const sendArrTests = (adapter: SASjs): TestSuite => ({
|
||||
test: () => {
|
||||
return adapter.request("common/sendArr", numericData);
|
||||
},
|
||||
assertion: (res: any) => {
|
||||
assertion: (res: any) => { // TODO: be more specific on type declaration
|
||||
return res.table1[0][0] === numericData.table1[0].col1;
|
||||
},
|
||||
},
|
||||
@@ -98,7 +88,7 @@ export const sendArrTests = (adapter: SASjs): TestSuite => ({
|
||||
test: () => {
|
||||
return adapter.request("common/sendArr", multiColumnData);
|
||||
},
|
||||
assertion: (res: any) => {
|
||||
assertion: (res: any) => { // TODO: be more specific on type declaration
|
||||
return (
|
||||
res.table1[0][0] === multiColumnData.table1[0].col1 &&
|
||||
res.table1[0][1] === multiColumnData.table1[0].col2 &&
|
||||
@@ -113,9 +103,10 @@ export const sendArrTests = (adapter: SASjs): TestSuite => ({
|
||||
test: () => {
|
||||
return adapter.request("common/sendArr", multipleRowsWithNulls);
|
||||
},
|
||||
assertion: (res: any) => {
|
||||
assertion: (res: any) => { // TODO: be more specific on type declaration
|
||||
let result = true;
|
||||
multipleRowsWithNulls.table1.forEach((_: any, index: number) => {
|
||||
multipleRowsWithNulls.table1.forEach((_: any, index: number) => { // TODO: be more specific on type declaration
|
||||
// FIXME: use loop
|
||||
result =
|
||||
result &&
|
||||
res.table1[index][0] === multipleRowsWithNulls.table1[index].col1;
|
||||
@@ -129,6 +120,7 @@ export const sendArrTests = (adapter: SASjs): TestSuite => ({
|
||||
result &&
|
||||
res.table1[index][3] === multipleRowsWithNulls.table1[index].col4;
|
||||
});
|
||||
|
||||
return result;
|
||||
},
|
||||
},
|
||||
@@ -138,9 +130,9 @@ export const sendArrTests = (adapter: SASjs): TestSuite => ({
|
||||
test: () => {
|
||||
return adapter.request("common/sendArr", multipleColumnsWithNulls);
|
||||
},
|
||||
assertion: (res: any) => {
|
||||
assertion: (res: any) => { // TODO: be more specific on type declaration
|
||||
let result = true;
|
||||
multipleColumnsWithNulls.table1.forEach((_: any, index: number) => {
|
||||
multipleColumnsWithNulls.table1.forEach((_: any, index: number) => { // TODO: be more specific on type declaration
|
||||
result =
|
||||
result &&
|
||||
res.table1[index][0] ===
|
||||
@@ -171,12 +163,12 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
|
||||
title: "Invalid column name",
|
||||
description: "Should throw an error",
|
||||
test: async () => {
|
||||
const invalidData: any = {
|
||||
const invalidData: any = { // TODO: be more specific on type declaration
|
||||
"1 invalid table": [{ col1: 42 }],
|
||||
};
|
||||
return adapter.request("common/sendObj", invalidData).catch((e) => e);
|
||||
},
|
||||
assertion: (error: any) => !!error && !!error.MESSAGE,
|
||||
assertion: (error: any) => !!error && !!error.MESSAGE, // TODO: be more specific on type declaration
|
||||
},
|
||||
{
|
||||
title: "Single string value",
|
||||
@@ -184,7 +176,7 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
|
||||
test: () => {
|
||||
return adapter.request("common/sendObj", stringData);
|
||||
},
|
||||
assertion: (res: any) => {
|
||||
assertion: (res: any) => { // TODO: be more specific on type declaration
|
||||
return res.table1[0].COL1 === stringData.table1[0].col1;
|
||||
},
|
||||
},
|
||||
@@ -195,7 +187,7 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
|
||||
test: () => {
|
||||
return adapter.request("common/sendObj", getLongStringData());
|
||||
},
|
||||
assertion: (res: any) => {
|
||||
assertion: (res: any) => { // TODO: be more specific on type declaration
|
||||
const longStringData = getLongStringData();
|
||||
return res.table1[0].COL1 === longStringData.table1[0].col1;
|
||||
},
|
||||
@@ -209,7 +201,7 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
|
||||
.request("common/sendObj", getLongStringData(32767))
|
||||
.catch((e) => e);
|
||||
},
|
||||
assertion: (error: any) => {
|
||||
assertion: (error: any) => { // TODO: be more specific on type declaration
|
||||
return !!error && !!error.MESSAGE;
|
||||
},
|
||||
},
|
||||
@@ -219,7 +211,7 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
|
||||
test: () => {
|
||||
return adapter.request("common/sendObj", numericData);
|
||||
},
|
||||
assertion: (res: any) => {
|
||||
assertion: (res: any) => { // TODO: be more specific on type declaration
|
||||
return res.table1[0].COL1 === numericData.table1[0].col1;
|
||||
},
|
||||
},
|
||||
@@ -230,7 +222,7 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
|
||||
test: () => {
|
||||
return adapter.request("common/sendObj", getLargeObjectData());
|
||||
},
|
||||
assertion: (res: any) => {
|
||||
assertion: (res: any) => { // TODO: be more specific on type declaration
|
||||
const data = getLargeObjectData();
|
||||
return res.table1[9000].BIG === data.table1[9000].big;
|
||||
},
|
||||
@@ -241,7 +233,7 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
|
||||
test: () => {
|
||||
return adapter.request("common/sendObj", multiColumnData);
|
||||
},
|
||||
assertion: (res: any) => {
|
||||
assertion: (res: any) => { // TODO: be more specific on type declaration
|
||||
return (
|
||||
res.table1[0].COL1 === multiColumnData.table1[0].col1 &&
|
||||
res.table1[0].COL2 === multiColumnData.table1[0].col2 &&
|
||||
@@ -256,9 +248,9 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
|
||||
test: () => {
|
||||
return adapter.request("common/sendObj", multipleRowsWithNulls);
|
||||
},
|
||||
assertion: (res: any) => {
|
||||
assertion: (res: any) => { // TODO: be more specific on type declaration
|
||||
let result = true;
|
||||
multipleRowsWithNulls.table1.forEach((_: any, index: number) => {
|
||||
multipleRowsWithNulls.table1.forEach((_: any, index: number) => { // TODO: be more specific on type declaration
|
||||
result =
|
||||
result &&
|
||||
res.table1[index].COL1 === multipleRowsWithNulls.table1[index].col1;
|
||||
@@ -281,9 +273,9 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
|
||||
test: () => {
|
||||
return adapter.request("common/sendObj", multipleColumnsWithNulls);
|
||||
},
|
||||
assertion: (res: any) => {
|
||||
assertion: (res: any) => { // TODO: be more specific on type declaration
|
||||
let result = true;
|
||||
multipleColumnsWithNulls.table1.forEach((_: any, index: number) => {
|
||||
multipleColumnsWithNulls.table1.forEach((_: any, index: number) => { // TODO: be more specific on type declaration
|
||||
result =
|
||||
result &&
|
||||
res.table1[index].COL1 ===
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import SASjs from "sasjs";
|
||||
import { TestSuite } from "../types";
|
||||
|
||||
const data: any = { table1: [{ col1: "first col value" }] };
|
||||
const data: any = { table1: [{ col1: "first col value" }] }; // TODO: be more specific on type declaration
|
||||
|
||||
export const sasjsRequestTests = (adapter: SASjs): TestSuite => ({
|
||||
name: "SASjs Requests",
|
||||
@@ -9,16 +9,11 @@ export const sasjsRequestTests = (adapter: SASjs): TestSuite => ({
|
||||
{
|
||||
title: "WORK tables",
|
||||
description: "Should get WORK tables after request",
|
||||
test: async () => {
|
||||
return adapter.request("common/sendArr", data);
|
||||
},
|
||||
test: async () => adapter.request("common/sendArr", data),
|
||||
assertion: (res: any) => {
|
||||
const requests = adapter.getSasRequests();
|
||||
if (adapter.getSasjsConfig().debug) {
|
||||
return requests[0].SASWORK !== null;
|
||||
} else {
|
||||
return requests[0].SASWORK === null;
|
||||
}
|
||||
|
||||
return adapter.getSasjsConfig().debug ? requests[0].SASWORK !== null : requests[0].SASWORK === null
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import SASjs from "sasjs";
|
||||
import { TestSuite } from "../types";
|
||||
|
||||
const specialCharData: any = {
|
||||
const specialCharData: any = { // TODO: be more specific on type definition
|
||||
table1: [
|
||||
{
|
||||
tab: "\t",
|
||||
@@ -9,8 +9,8 @@ const specialCharData: any = {
|
||||
cr: "\r",
|
||||
semicolon: ";semi",
|
||||
percent: "%",
|
||||
singleQuote: "'",
|
||||
doubleQuote: '"',
|
||||
singleQuote: "'", // TODO: use ``
|
||||
doubleQuote: '"', // TODO: use ``
|
||||
crlf: "\r\n",
|
||||
euro: "€euro",
|
||||
banghash: "!#banghash",
|
||||
@@ -18,7 +18,7 @@ const specialCharData: any = {
|
||||
],
|
||||
};
|
||||
|
||||
const moreSpecialCharData: any = {
|
||||
const moreSpecialCharData: any = { // TODO: be more specific on type definition
|
||||
table1: [
|
||||
{
|
||||
speech0: '"speech',
|
||||
@@ -36,44 +36,46 @@ const moreSpecialCharData: any = {
|
||||
],
|
||||
};
|
||||
|
||||
const getWideData = () => {
|
||||
const cols: any = {};
|
||||
for (let i = 1; i <= 10000; i++) {
|
||||
const getWideData = () => { // FIXME: declared but never used
|
||||
const cols: any = {}; // TODO: be more specific on type definition
|
||||
for (let i = 1; i <= 10000; i++) { // Why 10000?
|
||||
cols["col" + i] = "test" + i;
|
||||
}
|
||||
|
||||
const data: any = {
|
||||
const data: any = { // TODO: be more specific on type definition
|
||||
table1: [cols],
|
||||
};
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const getTables = () => {
|
||||
const tables: any = {};
|
||||
const getTables = () => { // FIXME: declared but never used
|
||||
const tables: any = {}; // TODO: be more specific on type definition
|
||||
|
||||
for (let i = 1; i <= 100; i++) {
|
||||
for (let i = 1; i <= 100; i++) { // why 100
|
||||
tables["table" + i] = [{ col1: "x", col2: "x", col3: "x", col4: "x" }];
|
||||
}
|
||||
|
||||
return tables;
|
||||
};
|
||||
|
||||
const getLargeDataset = () => {
|
||||
const rows: any = [];
|
||||
const colData: string =
|
||||
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
|
||||
const rows: any = []; // TODO: be more specific on type definition
|
||||
const colData: string = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; // FIXME: no need to explicitly mention data type
|
||||
|
||||
for (let i = 1; i <= 10000; i++) {
|
||||
rows.push({ col1: colData, col2: colData, col3: colData, col4: colData });
|
||||
}
|
||||
|
||||
const data: any = {
|
||||
const data: any = { // TODO: be more specific on type definition
|
||||
table1: rows,
|
||||
};
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const errorAndCsrfData: any = {
|
||||
// FIXME: declared but never used
|
||||
const errorAndCsrfData: any = { // TODO: be more specific on type definition
|
||||
error: [{ col1: "q", col2: "w", col3: "e", col4: "r" }],
|
||||
_csrf: [{ col1: "q", col2: "w", col3: "e", col4: "r" }],
|
||||
};
|
||||
@@ -84,10 +86,8 @@ export const specialCaseTests = (adapter: SASjs): TestSuite => ({
|
||||
{
|
||||
title: "Common special characters",
|
||||
description: "Should handle common special characters",
|
||||
test: () => {
|
||||
return adapter.request("common/sendArr", specialCharData);
|
||||
},
|
||||
assertion: (res: any) => {
|
||||
test: () => adapter.request("common/sendArr", specialCharData),
|
||||
assertion: (res: any) => { // TODO: be more specific on type definition
|
||||
return (
|
||||
res.table1[0][0] === specialCharData.table1[0].tab &&
|
||||
res.table1[0][1] === specialCharData.table1[0].lf &&
|
||||
@@ -102,6 +102,8 @@ export const specialCaseTests = (adapter: SASjs): TestSuite => ({
|
||||
);
|
||||
},
|
||||
},
|
||||
// TODO: delete commented out code
|
||||
|
||||
// {
|
||||
// title: "Other special characters",
|
||||
// description: "Should handle other special characters",
|
||||
@@ -180,15 +182,15 @@ export const specialCaseTests = (adapter: SASjs): TestSuite => ({
|
||||
{
|
||||
title: "Large dataset",
|
||||
description: "Should handle 5mb of data",
|
||||
test: () => {
|
||||
return adapter.request("common/sendArr", getLargeDataset());
|
||||
},
|
||||
assertion: (res: any) => {
|
||||
test: () => adapter.request("common/sendArr", getLargeDataset()),
|
||||
assertion: (res: any) => { // TODO: be more specific on type definition
|
||||
const data = getLargeDataset();
|
||||
let result = true;
|
||||
let result = true; // TODO: rename
|
||||
|
||||
for (let i = 0; i <= 10; i++) {
|
||||
result = result && res.table1[i][0] === data.table1[i][0];
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
},
|
||||
|
||||
@@ -3,20 +3,21 @@ export const assert = (
|
||||
message = "Assertion failed"
|
||||
) => {
|
||||
let result;
|
||||
|
||||
try {
|
||||
if (typeof expression === "boolean") {
|
||||
result = expression;
|
||||
} else {
|
||||
result = expression();
|
||||
}
|
||||
if (typeof expression === "boolean") result = expression;
|
||||
else result = expression();
|
||||
} catch (e) {
|
||||
console.error(message);
|
||||
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
if (!!result) {
|
||||
return;
|
||||
} else {
|
||||
console.error(message);
|
||||
|
||||
throw new Error(message);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -3,19 +3,23 @@ export const uploadFile = (file: File, fileName: string, url: string) => {
|
||||
const data = new FormData();
|
||||
data.append("file", file);
|
||||
data.append("filename", fileName);
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.withCredentials = true;
|
||||
xhr.addEventListener("readystatechange", function () {
|
||||
xhr.addEventListener("readystatechange", function () { // TODO: use ES6
|
||||
if (this.readyState === 4) {
|
||||
let response: any;
|
||||
|
||||
try {
|
||||
response = JSON.parse(this.responseText);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
|
||||
resolve(response);
|
||||
}
|
||||
});
|
||||
|
||||
xhr.open("POST", url);
|
||||
xhr.setRequestHeader("cache-control", "no-cache");
|
||||
xhr.send(data);
|
||||
|
||||
+41
-41
@@ -3,49 +3,49 @@
|
||||
*
|
||||
*/
|
||||
export class SAS9ApiClient {
|
||||
constructor(private serverUrl: string) {}
|
||||
constructor(private serverUrl: string) {}
|
||||
|
||||
/**
|
||||
* returns on object containing the server URL
|
||||
*/
|
||||
public getConfig() {
|
||||
return {
|
||||
serverUrl: this.serverUrl,
|
||||
};
|
||||
}
|
||||
/**
|
||||
* @returns an object containing the server URL
|
||||
*/
|
||||
public getConfig() {
|
||||
return {
|
||||
serverUrl: this.serverUrl,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates serverurl which is not null
|
||||
* @param serverUrl - the URL of the server.
|
||||
*/
|
||||
public setConfig(serverUrl: string) {
|
||||
if (serverUrl) this.serverUrl = serverUrl;
|
||||
}
|
||||
/**
|
||||
* Updates serverUrl which is not null
|
||||
* @param serverUrl - the URL of the server.
|
||||
*/
|
||||
public setConfig(serverUrl: string) {
|
||||
if (serverUrl) this.serverUrl = serverUrl
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes code on a SAS9 server.
|
||||
* @param linesOfCode - an array of lines of code to execute
|
||||
* @param serverName - the server to execute the code on
|
||||
* @param repositoryName - the repository to execute the code on
|
||||
*/
|
||||
public async executeScript(
|
||||
linesOfCode: string[],
|
||||
serverName: string,
|
||||
repositoryName: string
|
||||
) {
|
||||
const requestPayload = linesOfCode.join("\n");
|
||||
const executeScriptRequest = {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
},
|
||||
body: `command=${requestPayload}`,
|
||||
};
|
||||
const executeScriptResponse = await fetch(
|
||||
`${this.serverUrl}/sas/servers/${serverName}/cmd?repositoryName=${repositoryName}`,
|
||||
executeScriptRequest
|
||||
).then((res) => res.text());
|
||||
/**
|
||||
* Executes code on a SAS9 server.
|
||||
* @param linesOfCode - an array of lines of code to execute
|
||||
* @param serverName - the server to execute the code on
|
||||
* @param repositoryName - the repository to execute the code on
|
||||
*/
|
||||
public async executeScript(
|
||||
linesOfCode: string[], // FIXME: rename
|
||||
serverName: string,
|
||||
repositoryName: string
|
||||
) {
|
||||
const requestPayload = linesOfCode.join('\n')
|
||||
const executeScriptRequest = {
|
||||
method: 'PUT',
|
||||
headers: {Accept: 'application/json'},
|
||||
body: `command=${requestPayload}`,
|
||||
}
|
||||
// FIXME: use axios instead of fetch
|
||||
const executeScriptResponse = await fetch(
|
||||
`${this.serverUrl}/sas/servers/${serverName}/cmd?repositoryName=${repositoryName}`,
|
||||
executeScriptRequest
|
||||
).then((res) => res.text())
|
||||
// FIXME: no catch block
|
||||
|
||||
return executeScriptResponse;
|
||||
}
|
||||
return executeScriptResponse
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,13 @@
|
||||
/**
|
||||
* TODO: needs to be split into logical blocks:
|
||||
* - Folder
|
||||
* - Config
|
||||
* - Context
|
||||
* - Session
|
||||
* - Job
|
||||
* - Auth
|
||||
*/
|
||||
|
||||
import {
|
||||
isAuthorizeFormRequired,
|
||||
parseAndSubmitAuthorizeForm,
|
||||
|
||||
@@ -2,6 +2,7 @@ import SASjs from "./index";
|
||||
|
||||
const adapter = new SASjs();
|
||||
|
||||
// FIXME: adapter doesn't have 'parseSAS9SourceCode' and 'parseGeneratedCode'
|
||||
it("should parse SAS9 source code", async done => {
|
||||
expect(sampleResponse).toBeTruthy();
|
||||
const parsedSourceCode = (adapter as any).parseSAS9SourceCode(sampleResponse);
|
||||
|
||||
@@ -1,3 +1,16 @@
|
||||
/**
|
||||
* TODO: needs to be split into logical blocks:
|
||||
* - Execute
|
||||
* - Context
|
||||
* - Session
|
||||
* - Folder and services
|
||||
* - Job
|
||||
* - Auth
|
||||
* - Config
|
||||
* - Debug
|
||||
* - Response
|
||||
*/
|
||||
|
||||
import "isomorphic-fetch";
|
||||
import * as e6p from "es6-promise";
|
||||
(e6p as any).polyfill();
|
||||
@@ -839,6 +852,7 @@ export default class SASjs {
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: this method never used
|
||||
private fetchLogFileContent(logLink: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fetch(logLink, {
|
||||
|
||||
+7
-5
@@ -1,5 +1,7 @@
|
||||
import SASjs from "./SASjs";
|
||||
export * from "./types";
|
||||
export * from "./SASViyaApiClient";
|
||||
export * from "./SAS9ApiClient";
|
||||
export default SASjs;
|
||||
import SASjs from './SASjs'
|
||||
|
||||
export * from './types'
|
||||
export * from './SASViyaApiClient'
|
||||
export * from './SAS9ApiClient'
|
||||
|
||||
export default SASjs
|
||||
@@ -10,6 +10,7 @@ export class SASjsConfig {
|
||||
* Can be omitted, eg if serving directly from the SAS Web Server or being
|
||||
* streamed.
|
||||
*/
|
||||
// TODO: we should clarify what location we are talking about
|
||||
serverUrl: string = "";
|
||||
pathSAS9: string = "";
|
||||
pathSASViya: string = "";
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
* Represents requests that are queued, pending a signon event
|
||||
*
|
||||
*/
|
||||
|
||||
// FIXME: be more specific on type declaration
|
||||
export interface SASjsWaitingRequest {
|
||||
requestPromise: {
|
||||
promise: any;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// FIXME: use ES6
|
||||
export async function asyncForEach(array: any[], callback: any) {
|
||||
for (let index = 0; index < array.length; index++) {
|
||||
await callback(array[index], index, array);
|
||||
|
||||
@@ -2,7 +2,6 @@ import { SASjsRequest } from "../types/SASjsRequest";
|
||||
|
||||
/**
|
||||
* Comparator for SASjs request timestamps
|
||||
*
|
||||
*/
|
||||
export const compareTimestamps = (a: SASjsRequest, b: SASjsRequest) => {
|
||||
return b.timestamp.getTime() - a.timestamp.getTime();
|
||||
|
||||
+20
-15
@@ -3,43 +3,43 @@
|
||||
* @param data - the JSON object to convert.
|
||||
*/
|
||||
export const convertToCSV = (data: any) => {
|
||||
const replacer = (key: any, value: any) => (value === null ? "" : value);
|
||||
const headerFields = Object.keys(data[0]);
|
||||
const replacer = (key: any, value: any) => (value === null ? "" : value); // FIXME: 'key' parameter was not used, why do we compare with null (undefined, NaN)?
|
||||
const headerFields = Object.keys(data[0]); // FIXME: data can be of any type, but we are working with it as with object
|
||||
let csvTest;
|
||||
let invalidString = false;
|
||||
const headers = headerFields.map((field) => {
|
||||
let firstFoundType: string | null = null;
|
||||
let hasMixedTypes: boolean = false;
|
||||
let rowNumError: number = -1;
|
||||
let hasMixedTypes: boolean = false; // FIXME: unnecessary type declaration
|
||||
let rowNumError: number = -1; // FIXME: unnecessary type declaration
|
||||
|
||||
const longestValueForField = data
|
||||
.map((row: any, index: number) => {
|
||||
.map((row: any, index: number) => { // FIXME: row should be of type string | number
|
||||
if (row[field] || row[field] === "") {
|
||||
if (firstFoundType) {
|
||||
let currentFieldType =
|
||||
row[field] === "" || typeof row[field] === "string"
|
||||
let currentFieldType = // FIXME: use const
|
||||
row[field] === "" || typeof row[field] === "string" // FIXME: "" is also of type string
|
||||
? "chars"
|
||||
: "number";
|
||||
|
||||
if (!hasMixedTypes) {
|
||||
hasMixedTypes = currentFieldType !== firstFoundType;
|
||||
rowNumError = hasMixedTypes ? index + 1 : -1;
|
||||
rowNumError = hasMixedTypes ? index + 1 : -1; // TODO: refactor
|
||||
}
|
||||
} else {
|
||||
if (row[field] === "") {
|
||||
firstFoundType = "chars";
|
||||
} else {
|
||||
firstFoundType =
|
||||
typeof row[field] === "string" ? "chars" : "number";
|
||||
typeof row[field] === "string" ? "chars" : "number"; // TODO: refactor
|
||||
}
|
||||
}
|
||||
|
||||
let byteSize;
|
||||
|
||||
if (typeof row[field] === "string") {
|
||||
let doubleQuotesFound = row[field]
|
||||
let doubleQuotesFound = row[field] // FIXME: use const
|
||||
.split("")
|
||||
.filter((char: any) => char === '"');
|
||||
.filter((char: any) => char === '"'); // FIXME: why char is of type any?
|
||||
|
||||
byteSize = getByteSize(row[field]);
|
||||
|
||||
@@ -52,16 +52,18 @@ export const convertToCSV = (data: any) => {
|
||||
}
|
||||
})
|
||||
.sort((a: number, b: number) => b - a)[0];
|
||||
if (longestValueForField && longestValueForField > 32765) {
|
||||
|
||||
if (longestValueForField && longestValueForField > 32765) { // FIXME: longestValueForField is an array and it is not comparable to a number
|
||||
invalidString = true;
|
||||
}
|
||||
|
||||
if (hasMixedTypes) {
|
||||
console.error(
|
||||
`Row (${rowNumError}), Column (${field}) has mixed types: ERROR`
|
||||
);
|
||||
}
|
||||
|
||||
return `${field}:${firstFoundType === "chars" ? "$" : ""}${
|
||||
return `${field}:${firstFoundType === "chars" ? "$" : ""}${ // TODO: format return string before return statement
|
||||
longestValueForField
|
||||
? longestValueForField
|
||||
: firstFoundType === "chars"
|
||||
@@ -73,10 +75,11 @@ export const convertToCSV = (data: any) => {
|
||||
if (invalidString) {
|
||||
return "ERROR: LARGE STRING LENGTH";
|
||||
}
|
||||
|
||||
csvTest = data.map((row: any) => {
|
||||
const fields = Object.keys(row).map((fieldName, index) => {
|
||||
let value;
|
||||
let containsSpecialChar = false;
|
||||
let containsSpecialChar = false; // FIXME: should be const
|
||||
const currentCell = row[fieldName];
|
||||
|
||||
if (JSON.stringify(currentCell).search(/(\\t|\\n|\\r)/gm) > -1) {
|
||||
@@ -89,7 +92,7 @@ export const convertToCSV = (data: any) => {
|
||||
value = value.replace(/\\\\/gm, "\\");
|
||||
|
||||
if (containsSpecialChar) {
|
||||
if (value.includes(",") || value.includes('"')) {
|
||||
if (value.includes(",") || value.includes('"')) { // FIXME: use `"`
|
||||
value = '"' + value + '"';
|
||||
}
|
||||
} else {
|
||||
@@ -112,6 +115,7 @@ export const convertToCSV = (data: any) => {
|
||||
|
||||
return value;
|
||||
});
|
||||
|
||||
return fields.join(",");
|
||||
});
|
||||
|
||||
@@ -121,6 +125,7 @@ export const convertToCSV = (data: any) => {
|
||||
return finalCSV;
|
||||
};
|
||||
|
||||
// TODO: refactor
|
||||
const getByteSize = (str: string) => {
|
||||
let byteSize = str.length;
|
||||
for (let i = str.length - 1; i >= 0; i--) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export const isLogInRequired = (response: string): boolean => {
|
||||
const pattern: RegExp = /<form.+action="(.*Logon[^"]*).*>/gm;
|
||||
const pattern: RegExp = /<form.+action="(.*Logon[^"]*).*>/gm; // FIXME: unnecessary type declaration
|
||||
const matches = pattern.test(response);
|
||||
|
||||
return matches;
|
||||
};
|
||||
@@ -1,2 +1 @@
|
||||
export const isLogInSuccess = (response: string): boolean =>
|
||||
/You have signed in/gm.test(response);
|
||||
export const isLogInSuccess = (response: string): boolean => /You have signed in/gm.test(response); // TODO: maybe we have more reliable way to verify that login was successful?
|
||||
@@ -2,30 +2,33 @@ import { CsrfToken } from "../types";
|
||||
|
||||
export async function makeRequest<T>(
|
||||
url: string,
|
||||
request: RequestInit,
|
||||
request: RequestInit, // Where 'RequestInit' is coming from?
|
||||
callback: (value: CsrfToken) => any,
|
||||
contentType: "text" | "json" = "json"
|
||||
): Promise<T> {
|
||||
const responseTransform =
|
||||
contentType === "json"
|
||||
? (res: Response) => res.json()
|
||||
: (res: Response) => res.text();
|
||||
const result = await fetch(url, request).then((response) => {
|
||||
if (!response.ok) {
|
||||
const responseTransform = contentType === "json" ?
|
||||
(res: Response) => res.json()
|
||||
:
|
||||
(res: Response) => res.text();
|
||||
|
||||
const result = await fetch(url, request).then((response) => { // FIXME: use axios instead of fetch
|
||||
if (!response.ok) { // FIXME we can just check if status === 403
|
||||
if (response.status === 403) {
|
||||
const tokenHeader = response.headers.get("X-CSRF-HEADER");
|
||||
|
||||
if (tokenHeader) {
|
||||
const token = response.headers.get(tokenHeader);
|
||||
const token = response.headers.get(tokenHeader) || ''; // TODO: refactor
|
||||
|
||||
callback({
|
||||
headerName: tokenHeader,
|
||||
value: token || "",
|
||||
value: token,
|
||||
});
|
||||
|
||||
const retryRequest = {
|
||||
...request,
|
||||
headers: { ...request.headers, [tokenHeader]: token },
|
||||
};
|
||||
|
||||
return fetch(url, retryRequest).then(responseTransform);
|
||||
}
|
||||
}
|
||||
@@ -33,5 +36,6 @@ export async function makeRequest<T>(
|
||||
return responseTransform(response);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// TODO: refactor
|
||||
export const needsRetry = (responseText: string): boolean => {
|
||||
return (
|
||||
(responseText.includes('"errorCode":403') &&
|
||||
|
||||
@@ -6,7 +6,7 @@ export const parseAndSubmitAuthorizeForm = async (
|
||||
const params: any = {};
|
||||
|
||||
const responseBody = response.split("<body>")[1].split("</body>")[0];
|
||||
const bodyElement = document.createElement("div");
|
||||
const bodyElement = document.createElement("div"); // TODO: rename
|
||||
bodyElement.innerHTML = responseBody;
|
||||
|
||||
const form = bodyElement.querySelector("#application_authorization");
|
||||
@@ -24,7 +24,7 @@ export const parseAndSubmitAuthorizeForm = async (
|
||||
|
||||
const formData = new FormData();
|
||||
|
||||
for (const key in params) {
|
||||
for (const key in params) { // TODO: use forEach
|
||||
if (params.hasOwnProperty(key)) {
|
||||
formData.append(key, params[key]);
|
||||
}
|
||||
@@ -32,7 +32,7 @@ export const parseAndSubmitAuthorizeForm = async (
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
if (authUrl) {
|
||||
fetch(authUrl, {
|
||||
fetch(authUrl, { // TODO use axios instead of fetch
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
body: formData,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export const parseGeneratedCode = (log: string) => {
|
||||
const startsWith = "MPRINT";
|
||||
const isGeneratedCodeLine = (line: string) =>
|
||||
line.trim().startsWith(startsWith);
|
||||
const isGeneratedCodeLine = (line: string) => line.trim().startsWith(startsWith);
|
||||
const logLines = log.split("\n").filter(isGeneratedCodeLine);
|
||||
|
||||
return logLines.join("\r\n");
|
||||
};
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
export const parseSasViyaLog = (logResponse: { items: any[] }) => {
|
||||
export const parseSasViyaLog = (logResponse: { items: any[] }) => { // TODO: be more specific on type declaration
|
||||
let log;
|
||||
|
||||
try {
|
||||
log = logResponse.items
|
||||
? logResponse.items.map((i) => i.line).join("\n")
|
||||
: JSON.stringify(logResponse);
|
||||
} catch (e) {
|
||||
} catch (e) { // TODO: rename parameter to err or error
|
||||
console.error("An error has occurred while parsing the log response", e);
|
||||
|
||||
log = logResponse;
|
||||
}
|
||||
|
||||
return log;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export const parseSourceCode = (log: string): string => {
|
||||
const isSourceCodeLine = (line: string) =>
|
||||
line.trim().substring(0, 10).trimStart().match(/^\d/);
|
||||
const isSourceCodeLine = (line: string) => line.trim().substring(0, 10).trimStart().match(/^\d/);
|
||||
const logLines = log.split("\n").filter(isSourceCodeLine);
|
||||
|
||||
return logLines.join("\r\n");
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export const serialize = (obj: any) => {
|
||||
export const serialize = (obj: any) => { // TODO: be more specific on type declaration
|
||||
const str: any[] = [];
|
||||
for (const p in obj) {
|
||||
|
||||
for (const p in obj) { // FIXME: name variables properly
|
||||
if (obj.hasOwnProperty(p)) {
|
||||
if (obj[p] instanceof Array) {
|
||||
for (let i = 0, n = obj[p].length; i < n; i++) {
|
||||
@@ -11,5 +12,6 @@ export const serialize = (obj: any) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return str.join("&");
|
||||
};
|
||||
@@ -1,10 +1,10 @@
|
||||
export const splitChunks = (content: string) => {
|
||||
const size = 16000;
|
||||
export const splitChunks = (content: string) => { // TODO: set return type
|
||||
const size = 16000; // why 16000?
|
||||
|
||||
const numChunks = Math.ceil(content.length / size);
|
||||
const chunks = new Array(numChunks);
|
||||
|
||||
for (let i = 0, o = 0; i < numChunks; ++i, o += size) {
|
||||
for (let i = 0, o = 0; i < numChunks; ++i, o += size) { // FIXME: name variables properly
|
||||
chunks[i] = content.substr(o, size);
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -24,7 +24,7 @@
|
||||
"links": [
|
||||
{
|
||||
"label": "SASjs on Github",
|
||||
"url": "https://github.com/macropeople/sasjs"
|
||||
"url": "https://github.com/sasjs/adapter"
|
||||
},
|
||||
{
|
||||
"label": "SASjs.io",
|
||||
@@ -32,11 +32,11 @@
|
||||
},
|
||||
{
|
||||
"label": "SASjs CLI",
|
||||
"url": "https://github.com/macropeople/sasjs-cli"
|
||||
"url": "https://github.com/sasjs/cli"
|
||||
},
|
||||
{
|
||||
"label": "React Seed App",
|
||||
"url": "https://github.com/macropeople/react-seed-app"
|
||||
"url": "https://github.com/sasjs/react-seed-app"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user