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

Compare commits

...

25 Commits

Author SHA1 Message Date
9598c11f42 Added suggestions 2020-07-16 15:02:40 -04:00
334a849caa Added suggestions 2020-07-16 15:02:17 -04:00
Krishna Acondy
b614bafd03 chore(*): update references to master 2020-07-09 08:14:50 +01:00
Krishna Acondy
34cabcde2d fix(ci): change target branch for build action 2020-07-09 08:13:53 +01:00
Krishna Acondy
de82058850 fix(deps-ci): update build, remove unnecessary dependencies 2020-07-09 08:13:28 +01:00
Krishna Acondy
327be9e141 Merge pull request #2 from sasjs/dependabot/npm_and_yarn/npm-6.14.6
chore(deps): [security] bump npm from 6.14.5 to 6.14.6
2020-07-09 08:09:16 +01:00
dependabot-preview[bot]
f1502c0773 chore(deps): [security] bump npm from 6.14.5 to 6.14.6
Bumps [npm](https://github.com/npm/cli) from 6.14.5 to 6.14.6. **This update includes a security fix.**
- [Release notes](https://github.com/npm/cli/releases)
- [Changelog](https://github.com/npm/cli/blob/latest/CHANGELOG.md)
- [Commits](https://github.com/npm/cli/compare/v6.14.5...v6.14.6)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-07-09 07:08:09 +00:00
Krishna Acondy
c8a2df2d1f Merge pull request #3 from sasjs/dependabot/npm_and_yarn/types/jest-26.0.4
chore(deps-dev): bump @types/jest from 26.0.3 to 26.0.4
2020-07-09 08:04:09 +01:00
Krishna Acondy
2be6200b90 Merge branch 'master' into dependabot/npm_and_yarn/types/jest-26.0.4 2020-07-09 07:56:47 +01:00
Krishna Acondy
333289cd20 Merge pull request #4 from sasjs/dependabot/npm_and_yarn/npm-registry-fetch-4.0.5
chore(deps): [security] bump npm-registry-fetch from 4.0.4 to 4.0.5
2020-07-09 07:56:31 +01:00
Krishna Acondy
204139cd01 Merge branch 'master' into dependabot/npm_and_yarn/npm-registry-fetch-4.0.5 2020-07-09 07:55:28 +01:00
Krishna Acondy
2a38b68e69 Merge pull request #5 from sasjs/dependabot/npm_and_yarn/typedoc-neo-theme-1.0.9
chore(deps-dev): bump typedoc-neo-theme from 1.0.8 to 1.0.9
2020-07-09 07:55:13 +01:00
Krishna Acondy
39cc20b680 Merge branch 'master' into dependabot/npm_and_yarn/typedoc-neo-theme-1.0.9 2020-07-09 07:53:48 +01:00
Allan Bowe
8b3c9746fc Merge pull request #6 from sasjs/allanbowe-patch-1
Update README.md
2020-07-08 23:35:59 +02:00
Allan Bowe
7a76f5f343 Merge branch 'master' into allanbowe-patch-1 2020-07-08 23:35:49 +02:00
Allan Bowe
2bbcd7dee7 Update README.md 2020-07-08 23:35:20 +02:00
Allan Bowe
b02ce07ddf Update README.md 2020-07-08 23:34:59 +02:00
dependabot-preview[bot]
41400bea86 chore(deps-dev): bump typedoc-neo-theme from 1.0.8 to 1.0.9
Bumps [typedoc-neo-theme](https://github.com/google/typedoc-neo-theme) from 1.0.8 to 1.0.9.
- [Release notes](https://github.com/google/typedoc-neo-theme/releases)
- [Commits](https://github.com/google/typedoc-neo-theme/compare/v1.0.8...v1.0.9)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-07-07 20:07:49 +00:00
dependabot-preview[bot]
991ac100f6 chore(deps): [security] bump npm-registry-fetch from 4.0.4 to 4.0.5
Bumps [npm-registry-fetch](https://github.com/npm/registry-fetch) from 4.0.4 to 4.0.5. **This update includes a security fix.**
- [Release notes](https://github.com/npm/registry-fetch/releases)
- [Changelog](https://github.com/npm/npm-registry-fetch/blob/latest/CHANGELOG.md)
- [Commits](https://github.com/npm/registry-fetch/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-07-07 20:07:02 +00:00
dependabot-preview[bot]
66c156d299 chore(deps-dev): bump @types/jest from 26.0.3 to 26.0.4
Bumps [@types/jest](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jest) from 26.0.3 to 26.0.4.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/jest)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-07-07 20:06:44 +00:00
Krishna Acondy
7d0d830391 Set theme jekyll-theme-minimal 2020-07-07 21:00:20 +01:00
Krishna Acondy
0b038380c7 chore(doc): update documentation 2020-07-07 20:51:44 +01:00
Krishna Acondy
162ba5e837 chore(example): update adapter import in example 2020-07-07 20:47:28 +01:00
Krishna Acondy
f217b3eb04 fix(doc): update readme references 2020-07-07 20:44:15 +01:00
Krishna Acondy
6b9436b1c6 fix(doc): update readme 2020-07-07 20:35:26 +01:00
78 changed files with 6014 additions and 2884 deletions

View File

@@ -6,7 +6,7 @@ name: SASjs Build and Publish
on: on:
push: push:
branches: branches:
- master - main
jobs: jobs:
build: build:

View File

@@ -1,6 +1,6 @@
# Contributing # 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 # 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> 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;
@@ -77,4 +79,4 @@ parmcards4;
%mv_createwebservice(path=/Public/app/common,name=sendArr) %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`.

View File

@@ -1,31 +1,29 @@
[![](https://data.jsdelivr.com/v1/package/npm/sasjs/badge)](https://www.jsdelivr.com/package/npm/sasjs) [![](https://data.jsdelivr.com/v1/package/npm/@sasjs/adapter/badge)](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.
For more information on building web apps with SAS, check out [sasjs.io](https://sasjs.io) 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/sasjs/adapter/blob/main/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/macropeople/sasjs/blob/master/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 */
@@ -45,6 +43,6 @@ You now have a simple web app with a backend service!
# More resources # More resources
For more information specific to this adapter you can check out this [user guide](https://sasjs.io/sasjs/sasjs-adapter/) or the [technical](http://adapter.sasjs.io/) documentation. For more information specific to this adapter you can check out this [user guide](https://sasjs.io/sasjs/sasjs-adapter/) or the [technical](http://adapter.sasjs.io/) documentation.
For more information on building web apps in general, check out these [resources](https://sasjs.io/training/resources/) or contact the [author](https://www.linkedin.com/in/allanbowe/) directly. For more information on building web apps in general, check out these [resources](https://sasjs.io/training/resources/) or contact the [author](https://www.linkedin.com/in/allanbowe/) directly.

1
docs/_config.yml Normal file
View File

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

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

160
docs/modules/types.html Normal file

File diff suppressed because one or more lines are too long

549
docs/modules/utils.html Normal file

File diff suppressed because one or more lines are too long

View File

@@ -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'
$('#loading-spinner').show() })
// instantiate sasjs with options such as backend app location
// login (it's also possible to set an autologin when making requests) const initSasJs = () => {
sasJs.logIn( $('#loading-spinner').show()
$('#username')[0].value
,$('#password')[0].value // instantiate sasJs with options such as backend app location
).then((response) => { // login (it's also possible to set an auto login when making requests)
if (response.isLoggedIn === true) { sasJs.logIn($('#username')[0].value, $('#password')[0].value)
$('#loading-spinner').hide() .then((response) => {
$('.login').hide() if (response.isLoggedIn === true) {
$('#getdata').show() $('#loading-spinner').hide()
$('#cars').show() $('.login').hide()
} $('#getDataBtn').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}}]}
} }
});
} // make a request to a SAS service
</script> const getData = () => {
</head> $('#loading-spinner').show()
<body> $('#myChart').remove()
<div class="container-fluid" style="text-align: center; margin-top: 10px;"> $('#chart-container').append("<canvas id='myChart' style='display: none'></canvas>")
<div class="row">
<div class="col-lg-5 col-md-7 col-sm-10 mx-auto mx-auto"> const type = $('#cars')[0].options[$('#cars')[0].selectedIndex].value
<h1>Demo Seed App for <span class="code">SASjs</span></h1>
<div class="login" id="login-form"> // request data from an endpoint under your appLoc
<div class="form-group"> // send data as an array of objects - each object is one row
<input class="form-control" type="text" id="username" placeholder="Enter username" /> 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>
<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'>
</div> <canvas id='myChart' style='display: none'></canvas>
<div id="chart-container" style="height: 65vh; width: 100%; position: relative; margin: auto;"> </div>
<canvas id="myChart" style="display: none;"></canvas> </body>
</div>
</body>
</head> </head>

2323
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -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",

View File

@@ -44,4 +44,4 @@
"devDependencies": { "devDependencies": {
"node-sass": "^4.14.1" "node-sass": "^4.14.1"
} }
} }

View File

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

View File

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

View File

@@ -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.setIsLoggedIn(true); appContext.adapter.logIn(username, password)
}); .then(() => {
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">
@@ -51,4 +54,4 @@ const Login = (): ReactElement<{}> => {
); );
}; };
export default Login; export default Login;

View File

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

View File

@@ -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,20 +64,23 @@ 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 ? (
<> <>
<div className="loading-spinner"></div>Running tests... {
// FIXME: fragment is not needed in this case
}
<div className="loading-spinner"></div>Running tests...
</> </>
) : ( ) : (
"Run tests!" "Run tests!"
)} )}
</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);
}
}} }}
/> />
)} )}
@@ -123,4 +122,4 @@ const TestSuiteRunner = (
); );
}; };
export default TestSuiteRunner; export default TestSuiteRunner;

View File

@@ -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 });
}); });
} }
@@ -76,4 +84,4 @@ const Test = (props: TestProps): ReactElement<TestProps> => {
); );
}; };
export default Test; export default Test;

View File

@@ -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>
@@ -40,4 +40,4 @@ const TestCard = (props: TestCardProps): ReactElement<TestCardProps> => {
); );
}; };
export default TestCard; export default TestCard;

View File

@@ -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,8 +94,8 @@ const TestSuite = (props: TestSuiteProps): ReactElement<TestSuiteProps> => {
})} })}
</div> </div>
) : ( ) : (
<></> <></> // FIXME: use {null} instead
); );
}; };
export default TestSuite; export default TestSuite;

View File

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

View File

@@ -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 (
@@ -50,4 +49,4 @@ export const AppProvider = (props: { children: ReactNode }) => {
{props.children} {props.children}
</AppContext.Provider> </AppContext.Provider>
); );
}; };

View File

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

View File

@@ -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), // TODO: rename
.catch((e) => e); assertion: (error: any) => { // TODO: be more specific on type declaration
}, return !!error && !!error.MESSAGE; // FIXME: refactor
assertion: (error: any) => {
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 ===
@@ -305,4 +297,4 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
}, },
}, },
], ],
}); });

View File

@@ -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;
}
}, },
}, },
], ],

View File

@@ -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;
}, },
}, },
@@ -232,4 +234,4 @@ export const specialCaseTests = (adapter: SASjs): TestSuite => ({
// }, // },
// }, // },
], ],
}); });

View File

@@ -12,4 +12,4 @@ export interface TestSuite {
tests: Test[]; tests: Test[];
beforeAll?: (...args: any) => Promise<any>; beforeAll?: (...args: any) => Promise<any>;
afterAll?: (...args: any) => Promise<any>; afterAll?: (...args: any) => Promise<any>;
} }

View File

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

View File

@@ -3,21 +3,25 @@ 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);
}); });
}; };

View File

@@ -3,49 +3,49 @@
* *
*/ */
export class SAS9ApiClient { 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
} }
/** /**
* Executes code on a SAS9 server. * Executes code on a SAS9 server.
* @param linesOfCode - an array of lines of code to execute * @param linesOfCode - an array of lines of code to execute
* @param serverName - the server to execute the code on * @param serverName - the server to execute the code on
* @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
} }
} }

View File

@@ -1,3 +1,13 @@
/**
* TODO: needs to be split into logical blocks:
* - Folder
* - Config
* - Context
* - Session
* - Job
* - Auth
*/
import { import {
isAuthorizeFormRequired, isAuthorizeFormRequired,
parseAndSubmitAuthorizeForm, parseAndSubmitAuthorizeForm,
@@ -32,7 +42,7 @@ export class SASViyaApiClient {
if (this.rootFolderMap.size) { if (this.rootFolderMap.size) {
return this.rootFolderMap; return this.rootFolderMap;
} }
this.populateRootFolderMap(); this.populateRootFolderMap();
return this.rootFolderMap; return this.rootFolderMap;
} }

View File

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

View File

@@ -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, {
@@ -1038,4 +1052,4 @@ export default class SASjs {
); );
}); });
} }
} }

View File

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

View File

@@ -3,4 +3,4 @@ export interface Context {
id: string; id: string;
createdBy: string; createdBy: string;
version: number; version: number;
} }

View File

@@ -1,4 +1,4 @@
export interface CsrfToken { export interface CsrfToken {
headerName: string; headerName: string;
value: string; value: string;
} }

View File

@@ -4,4 +4,4 @@ export interface Folder {
id: string; id: string;
uri: string; uri: string;
links: Link[]; links: Link[];
} }

View File

@@ -8,4 +8,4 @@ export interface Job {
createdBy: string; createdBy: string;
links: Link[]; links: Link[];
results: JobResult; results: JobResult;
} }

View File

@@ -1,3 +1,3 @@
export interface JobResult { export interface JobResult {
"_webout.json": string; "_webout.json": string;
} }

View File

@@ -4,4 +4,4 @@ export interface Link {
href: string; href: string;
uri: string; uri: string;
type: string; type: string;
} }

View File

@@ -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 = "";

View File

@@ -9,4 +9,4 @@ export interface SASjsRequest {
generatedCode: string; generatedCode: string;
logFile: string; logFile: string;
SASWORK: any; SASWORK: any;
} }

View File

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

View File

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

View File

@@ -1,3 +1,3 @@
export interface Session { export interface Session {
id: string; id: string;
} }

View File

@@ -7,4 +7,4 @@ export * from "./SASjsConfig";
export * from "./SASjsRequest"; export * from "./SASjsRequest";
export * from "./SASjsWaitingRequest"; export * from "./SASjsWaitingRequest";
export * from "./ServerType"; export * from "./ServerType";
export * from "./Session"; export * from "./Session";

View File

@@ -1,5 +1,6 @@
// 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);
} }
} }

View File

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

View File

@@ -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--) {
@@ -130,4 +135,4 @@ const getByteSize = (str: string) => {
if (code >= 0xdc00 && code <= 0xdfff) i--; //trail surrogate if (code >= 0xdc00 && code <= 0xdfff) i--; //trail surrogate
} }
return byteSize; return byteSize;
}; };

View File

@@ -1,3 +1,3 @@
export const isAuthorizeFormRequired = (response: string): boolean => { export const isAuthorizeFormRequired = (response: string): boolean => {
return /<form.+action="(.*Logon\/oauth\/authorize[^"]*).*>/gm.test(response); return /<form.+action="(.*Logon\/oauth\/authorize[^"]*).*>/gm.test(response);
}; };

View File

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

View File

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

View File

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

View File

@@ -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') &&
@@ -8,4 +9,4 @@ export const needsRetry = (responseText: string): boolean => {
(responseText.includes('"status":449') && (responseText.includes('"status":449') &&
responseText.includes("Authentication success, retry original request")) responseText.includes("Authentication success, retry original request"))
); );
}; };

View File

@@ -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,
@@ -46,4 +46,4 @@ export const parseAndSubmitAuthorizeForm = async (
reject("Auth form url is null"); reject("Auth form url is null");
} }
}); });
}; };

View File

@@ -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");
}; };

View File

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

View File

@@ -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");
}; };

View File

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

View File

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

View File

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