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