1
0
mirror of https://github.com/sasjs/adapter.git synced 2026-01-02 02:00:06 +00:00

Compare commits

..

26 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
Krishna Acondy
3b40e6cd14 fix(package): make package public 2020-07-07 20:11:55 +01:00
78 changed files with 6017 additions and 2884 deletions

View File

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

View File

@@ -1,6 +1,6 @@
# Contributing
Contributions to SASjs are very welcome! When making a PR, test cases should be included. To help in unit testing, be sure to run the following when making changes:
Contributions to SASjs are very welcome! When making a PR, test cases should be included. To help in unit testing, be sure to run the following when making changes:
```
# the following creates a tarball in the build folder of SASjs
@@ -10,12 +10,13 @@ npm run-script package:lib
npm install ../sasjs/build/<tarball filename>
```
Tests are run using cypress. Before running tests, you need to define the following backend services:
Tests are run using cypress. Before running tests, you need to define the following backend services:
# SAS 9
```
filename mc url "https://raw.githubusercontent.com/macropeople/macrocore/master/mc_all.sas?_=1";
filename mc url "https://raw.githubusercontent.com/macropeople/macrocore/main/mc_all.sas?_=1";
%inc mc;
filename ft15f001 temp;
parmcards4;
@@ -37,8 +38,9 @@ parmcards4;
```
# Viya
```
filename mc url "https://raw.githubusercontent.com/macropeople/macrocore/master/mc_all.sas";
filename mc url "https://raw.githubusercontent.com/macropeople/macrocore/main/mc_all.sas";
%inc mc;
filename ft15f001 temp;
@@ -77,4 +79,4 @@ parmcards4;
%mv_createwebservice(path=/Public/app/common,name=sendArr)
```
The above services will return anything you send. To run the tests simply launch `npm run cypress`.
The above services will return anything you send. To run the tests simply launch `npm run cypress`.

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:
1 - `npm install sasjs` - for use in a node project
1 - `npm install @sasjs/adapter` - for use in a node project
2 - [Download](https://cdn.jsdelivr.net/npm/sasjs/index.js) and use a copy of the latest JS file
2 - [Download](https://cdn.jsdelivr.net/npm/@sasjs/adapter@1/index.js) and use a copy of the latest JS file
3 - Reference directly from the CDN - in which case click [here](https://www.jsdelivr.com/package/npm/sasjs?tab=collection) and select "SRI" to get the script tag with the integrity hash.
3 - Reference directly from the CDN - in which case click [here](https://www.jsdelivr.com/package/npm/@sasjs/adapter?tab=collection) and select "SRI" to get the script tag with the integrity hash.
If you are short on time and just need to build an app quickly, then check out [this video](https://vimeo.com/393161794) and the [react-seed-app](https://github.com/macropeople/react-seed-app) which provides some boilerplate.
For more information on building web apps with SAS, check out [sasjs.io](https://sasjs.io)
## None of this makes sense. How do I build an app with it?
## None of this makes sense. How do I build an app with it?
Ok ok. Deploy this [example.html](https://github.com/macropeople/sasjs/blob/master/example.html) file to your web server, and update `servertype` to `SAS9` or `SASVIYA` depending on your backend.
Ok ok. Deploy this [example.html](https://github.com/sasjs/adapter/blob/main/example.html) file to your web server, and update `servertype` to `SAS9` or `SASVIYA` depending on your backend.
The backend part can be deployed as follows:
```
%let appLoc=/Public/app/readme; /* Metadata or Viya Folder location as per SASjs config */
/* compile macros (can also be downloaded & compiled seperately) */
filename mc url "https://raw.githubusercontent.com/macropeople/macrocore/master/mc_all.sas";
%inc mc;
%let appLoc=/Public/app/readme; /* Metadata or Viya Folder per SASjs config */
filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
%inc mc; /* compile macros (can also be downloaded & compiled seperately) */
filename ft15f001 temp;
parmcards4;
%webout(FETCH) /* receive all data as SAS datasets */
@@ -45,6 +43,6 @@ You now have a simple web app with a backend service!
# 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>
<html>
<head>
<meta charset="utf-8" http-equiv="X-UA-Compatible" content="IE=edge" />
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/combine/npm/chart.js@2.9.3,npm/jquery@3.5.1,npm/sasjs@2.11.0"></script>
<script>
var sasJs = new SASjs.default({appLoc: "/Products/demo/readme"
,serverType:"SAS9", debug: "false"
});
function initSasJs() {
$('#loading-spinner').show()
// instantiate sasjs with options such as backend app location
// login (it's also possible to set an autologin when making requests)
sasJs.logIn(
$('#username')[0].value
,$('#password')[0].value
).then((response) => {
if (response.isLoggedIn === true) {
$('#loading-spinner').hide()
$('.login').hide()
$('#getdata').show()
$('#cars').show()
}
})
}
function getData(){
$('#loading-spinner').show()
$('#myChart').remove();
$('#chart-container').append('<canvas id="myChart" style="display: none;"></canvas>')
// make a request to a SAS service
var type = $("#cars")[0].options[$("#cars")[0].selectedIndex].value;
// request data from an endpoint under your appLoc
sasJs.request("/common/getdata", {
// send data as an array of objects - each object is one row
fromjs: [{ type: type }]
}).then((response) => {
$('#myChart').show();
var labels = []
var data = []
response.areas.map((d) => {
labels.push(d.MAKE);
data.push(d.AVPRICE);
})
$('#loading-spinner').hide()
initGraph(labels, data, type);
})
}
function initGraph(labels, data, type){
var myCanvas = document.getElementById("myChart");
var ctx = myCanvas.getContext("2d");
var myChart = new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets: [{
label: "Average Invoice Price in USD for " + type + " Cars by Manufacturer",
data: data,
backgroundColor: "rgba(255,99,132,0.2)",
borderColor: "rgba(255,99,132,1)",
borderWidth: 1,
hoverBackgroundColor: "rgba(255,99,132,0.4)",
hoverBorderColor: "rgba(255,99,132,1)",
}]
},
options: {
maintainAspectRatio: false,
scales: {yAxes: [{ticks: {beginAtZero: true}}]}
<head>
<meta charset='utf-8' http-equiv='X-UA-Compatible' content='IE=edge' />
<link rel='stylesheet' href='https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css' integrity='sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh' crossorigin='anonymous'>
<script src='https://cdn.jsdelivr.net/combine/npm/chart.js@2.9.3,npm/jquery@3.5.1,npm/@sasjs/adapter@1'></script>
<script>
const sasJs = new SASjs.default({
appLoc: '/Products/demo/readme',
serverType:'SAS9',
debug: 'false'
})
const initSasJs = () => {
$('#loading-spinner').show()
// instantiate sasJs with options such as backend app location
// login (it's also possible to set an auto login when making requests)
sasJs.logIn($('#username')[0].value, $('#password')[0].value)
.then((response) => {
if (response.isLoggedIn === true) {
$('#loading-spinner').hide()
$('.login').hide()
$('#getDataBtn').show()
$('#cars').show()
}
})
}
});
}
</script>
</head>
<body>
<div class="container-fluid" style="text-align: center; margin-top: 10px;">
<div class="row">
<div class="col-lg-5 col-md-7 col-sm-10 mx-auto mx-auto">
<h1>Demo Seed App for <span class="code">SASjs</span></h1>
<div class="login" id="login-form">
<div class="form-group">
<input class="form-control" type="text" id="username" placeholder="Enter username" />
// make a request to a SAS service
const getData = () => {
$('#loading-spinner').show()
$('#myChart').remove()
$('#chart-container').append("<canvas id='myChart' style='display: none'></canvas>")
const type = $('#cars')[0].options[$('#cars')[0].selectedIndex].value
// request data from an endpoint under your appLoc
// send data as an array of objects - each object is one row
sasJs.request('/common/getdata', {fromjs: [{ type: type }]})
.then((response) => {
$('#myChart').show()
$('#loading-spinner').hide()
const labels = response.areas.map(area => area.MAKE)
const data = response.areas.map(area => area.AVPRICE)
initGraph(labels, data, type)
})
}
const initGraph = (labels, data, type) => {
const myCanvas = document.getElementById('myChart')
const ctx = myCanvas.getContext('2d')
const myChart = new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets: [{
label: `Average Invoice Price in USD for ${type} Cars by Manufacturer`,
data: data,
backgroundColor: 'rgba(255,99,132,0.2)',
borderColor: 'rgba(255,99,132,1)',
borderWidth: 1,
hoverBackgroundColor: 'rgba(255,99,132,0.4)',
hoverBorderColor: 'rgba(255,99,132,1)',
}]
},
options: {
maintainAspectRatio: false,
scales: {yAxes: [{ticks: {beginAtZero: true}}]}
}
})
}
</script>
</head>
<body>
<div class='container-fluid' style='text-align: center; margin-top: 10px'>
<div class='row'>
<div class='col-lg-5 col-md-7 col-sm-10 mx-auto mx-auto'>
<h1>Demo Seed App for <span class='code'>SASjs</span></h1>
<div class='login' id='login-form'>
<div class='form-group'>
<input class='form-control' type='text' id='username' placeholder='Enter username' />
</div>
<div class='form-group'>
<input class='form-control' type='password' id='password' placeholder='Enter password' />
</div>
<button id='login' onclick='initSasJs()' class='login btn btn-primary' style='margin-bottom: 5px'>Log In</button>
</div>
<select name='cars' id='cars' style='margin-bottom: 5px; display: none' class='form-control'>
<option value='Hybrid'>Hybrid</option>
<option value='SUV'>SUV</option>
<option value='Sedan'>Sedan</option>
<option value='Sports'>Sports</option>
<option value='Truck'>Truck</option>
<option value='Wagon'>Wagon</option>
</select>
<button id='getDataBtn' onclick='getData()' style='margin-bottom: 5px; display: none' class='btn btn-success'>Get Data</button>
<br>
<br>
<div id='loading-spinner' class='spinner-border text-primary' role='status' style='display: none'>
<span class='sr-only'>Loading...</span>
</div>
<br>
</div>
</div>
<div class="form-group">
<input class="form-control" type="password" id="password" placeholder="Enter password" />
</div>
<button id="login" onclick="initSasJs()" class="login btn btn-primary" style="margin-bottom: 5px;">Log In</button>
</div>
<select name="cars" id="cars" style="margin-bottom: 5px; display: none;" class="form-control">
<option value="Hybrid">Hybrid</option>
<option value="SUV">SUV</option>
<option value="Sedan">Sedan</option>
<option value="Sports">Sports</option>
<option value="Truck">Truck</option>
<option value="Wagon">Wagon</option>
</select>
<button id="getdata" onclick="getData()" style="margin-bottom: 5px; display: none;" class="btn btn-success">Get Data</button><br><br>
<div id="loading-spinner" class="spinner-border text-primary" role="status" style="display: none;">
<span class="sr-only">Loading...</span>
</div><br>
</div>
</div>
</div>
<div id="chart-container" style="height: 65vh; width: 100%; position: relative; margin: auto;">
<canvas id="myChart" style="display: none;"></canvas>
</div>
</body>
<div id='chart-container' style='height: 65vh; width: 100%; position: relative; margin: auto'>
<canvas id='myChart' style='display: none'></canvas>
</div>
</body>
</head>

2323
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -13,12 +13,18 @@
"semantic-release": "semantic-release",
"typedoc": "typedoc"
},
"publishConfig": {
"access": "public"
},
"release": {
"plugins": [
"@semantic-release/npm",
{
"pkgRoot": "/build"
}
],
"branches": [
"main"
]
},
"keywords": [
@@ -33,11 +39,9 @@
},
"license": "ISC",
"devDependencies": {
"@cypress/webpack-preprocessor": "^4.1.5",
"@types/isomorphic-fetch": "0.0.35",
"@types/jest": "^26.0.3",
"@types/jest": "^26.0.4",
"cp": "^0.2.0",
"cypress": "^4.9.0",
"jest": "^25.5.4",
"path": "^0.12.7",
"prettier": "^2.0.5",
@@ -48,7 +52,7 @@
"tslint": "^6.1.2",
"tslint-config-prettier": "^1.18.0",
"typedoc": "^0.17.8",
"typedoc-neo-theme": "^1.0.8",
"typedoc-neo-theme": "^1.0.9",
"typedoc-plugin-external-module-name": "^4.0.3",
"typescript": "^3.9.6",
"uglifyjs-webpack-plugin": "^2.2.0",

View File

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

View File

@@ -5,5 +5,6 @@ import App from './App';
test('renders learn react link', () => {
const { getByText } = render(<App />);
const linkElement = getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});

View File

@@ -9,9 +9,7 @@ const App = (): ReactElement<{}> => {
const { adapter } = useContext(AppContext);
useEffect(() => {
if (adapter) {
adapter.setDebugState(debug);
}
if (adapter) adapter.setDebugState(debug);
}, [debug, adapter]);
useEffect(() => {
@@ -33,7 +31,7 @@ const App = (): ReactElement<{}> => {
<label className="switch">
<input
type="checkbox"
onChange={(e) => setDebug(e.target.checked)}
onChange={(e) => setDebug(e.target.checked)} // FIXME: rename 'e' => 'event'
/>
<span className="knob"></span>
</label>
@@ -45,7 +43,7 @@ const App = (): ReactElement<{}> => {
type="text"
className="app-loc-input"
value={appLoc}
onChange={(e) => setAppLoc(e.target.value)}
onChange={(e) => setAppLoc(e.target.value)} // FIXME: rename 'e' => 'event'
placeholder="AppLoc"
/>
</div>

View File

@@ -9,11 +9,14 @@ const Login = (): ReactElement<{}> => {
const appContext = useContext(AppContext);
const handleSubmit = useCallback(
(e) => {
(e) => { // FIXME: rename 'e' => 'event'
e.preventDefault();
appContext.adapter.logIn(username, password).then(() => {
appContext.setIsLoggedIn(true);
});
appContext.adapter.logIn(username, password)
.then(() => {
appContext.setIsLoggedIn(true);
});
// FIXME: catch block
},
[username, password, appContext]
);
@@ -38,7 +41,7 @@ const Login = (): ReactElement<{}> => {
type="password"
value={password}
required
onChange={(e) => setPassword(e.target.value)}
onChange={(e) => setPassword(e.target.value)} // FIXME: rename 'e' => 'event'
/>
</div>
<button type="submit" className="submit-button">
@@ -51,4 +54,4 @@ const Login = (): ReactElement<{}> => {
);
};
export default Login;
export default Login;

View File

@@ -8,16 +8,14 @@ interface PrivateRouteProps {
path: string;
}
const PrivateRoute = (
props: PrivateRouteProps
): ReactElement<PrivateRouteProps> => {
const PrivateRoute = (props: PrivateRouteProps): ReactElement<PrivateRouteProps> => {
const { component, path, exact } = props;
const appContext = useContext(AppContext);
return appContext.isLoggedIn ? (
return appContext.isLoggedIn ?
<Route component={component} path={path} exact={exact} />
) : (
:
<Redirect to="/login" />
);
};
export default PrivateRoute;
export default PrivateRoute;

View File

@@ -2,25 +2,24 @@ import React, { useEffect, useState, ReactElement, useContext } from "react";
import TestSuiteComponent from "./components/TestSuite";
import TestSuiteCard from "./components/TestSuiteCard";
import { TestSuite, Test } from "./types";
import { basicTests } from "./testSuites/Basic";
import { basicTests } from "./testSuites/Basic"; // FIXME: declared but never used
import "./TestSuiteRunner.scss";
import SASjs from "sasjs";
import { AppContext } from "./context/AppContext";
import { sendArrTests, sendObjTests } from "./testSuites/RequestData";
import { sendArrTests, sendObjTests } from "./testSuites/RequestData"; // FIXME: declared but never used
import { specialCaseTests } from "./testSuites/SpecialCases";
import { sasjsRequestTests } from "./testSuites/SasjsRequests";
import { sasjsRequestTests } from "./testSuites/SasjsRequests"; // FIXME: declared but never used
interface TestSuiteRunnerProps {
adapter: SASjs;
}
const TestSuiteRunner = (
props: TestSuiteRunnerProps
): ReactElement<TestSuiteRunnerProps> => {
const TestSuiteRunner = (props: TestSuiteRunnerProps): ReactElement<TestSuiteRunnerProps> => {
const { adapter } = props;
const { config } = useContext(AppContext);
const { config } = useContext(AppContext); // FIXME: declared but never used
const [testSuites, setTestSuites] = useState<TestSuite[]>([]);
const [runTests, setRunTests] = useState(false);
const [completedTestSuites, setCompletedTestSuites] = useState<
const [completedTestSuites, setCompletedTestSuites] = useState< // FIXME: create interface
{
name: string;
completedTests: {
@@ -65,20 +64,23 @@ const TestSuiteRunner = (
<>
<div className="button-container">
<button
className={runTests ? "submit-button disabled" : "submit-button"}
className={runTests ? "submit-button disabled" : "submit-button"} // TODO: 'submit-button' class should be assigned by default
onClick={() => setRunTests(true)}
disabled={runTests}
>
{runTests ? (
<>
<div className="loading-spinner"></div>Running tests...
{
// FIXME: fragment is not needed in this case
}
<div className="loading-spinner"></div>Running tests...
</>
) : (
"Run tests!"
)}
</button>
</div>
{completedTestSuites.map((completedTestSuite, index) => {
{completedTestSuites.map((completedTestSuite, index) => { // TODO: refactor
return (
<TestSuiteCard
key={index}
@@ -100,22 +102,19 @@ const TestSuiteRunner = (
}[]
) => {
const currentIndex = testSuites.indexOf(currentTestSuite);
const nextIndex =
currentIndex < testSuites.length - 1 ? currentIndex + 1 : -1;
if (nextIndex >= 0) {
setCurrentTestSuite(testSuites[nextIndex]);
} else {
setCurrentTestSuite(null);
}
const nextIndex = currentIndex < testSuites.length - 1 ? currentIndex + 1 : -1;
if (nextIndex >= 0) setCurrentTestSuite(testSuites[nextIndex]);
else setCurrentTestSuite(null);
const newCompletedTestSuites = [
...completedTestSuites,
{ name, completedTests },
];
setCompletedTestSuites(newCompletedTestSuites);
if (newCompletedTestSuites.length === testSuites.length) {
setRunTests(false);
}
if (newCompletedTestSuites.length === testSuites.length) setRunTests(false);
}}
/>
)}
@@ -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 TestCard from "./TestCard";
import { start } from "repl";
import { start } from "repl"; // FIXME: declared but never used
interface TestProps {
title: string;
@@ -39,28 +39,36 @@ const Test = (props: TestProps): ReactElement<TestProps> => {
useEffect(() => {
if (test && assertion) {
const startTime = new Date().valueOf();
const startTime = new Date().valueOf()
setIsRunning(true);
setIsPassed(false);
beforeTestFunction()
.then(() => test(context))
.then((res) => {
setIsRunning(false);
setIsPassed(assertion(res, context));
setIsPassed(assertion(res, context))
return Promise.resolve(assertion(res, context));
})
.then((testResult) => {
afterTestFunction();
const endTime = new Date().valueOf();
const executionTime = (endTime - startTime) / 1000;
onCompleted({ result: testResult, error: null, executionTime });
})
.catch((e) => {
setIsRunning(false);
setIsPassed(false);
console.error(e);
const endTime = new Date().valueOf();
const executionTime = (endTime - startTime) / 1000;
onCompleted({ result: false, error: e, executionTime });
});
}
@@ -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">
{executionTime ? executionTime.toFixed(2) + "s" : ""}
</span>
{status === "running" && (
{status === "running" && ( // FIXME: use switch statement
<div>
<span className="icon running"></span>Running...
</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 { name, tests, beforeAll, afterAll, onCompleted } = props;
const [context, setContext] = useState<any>(null);
const [completedTests, setCompletedTests] = useState<
const [completedTests, setCompletedTests] = useState< // TODO: create an interface
{
test: Test;
result: boolean;
@@ -31,24 +31,21 @@ const TestSuite = (props: TestSuiteProps): ReactElement<TestSuiteProps> => {
}[]
>([]);
const [currentTest, setCurrentTest] = useState<Test | null>(
(null as unknown) as Test
(null as unknown) as Test // ?
);
useEffect(() => {
if (beforeAll) {
beforeAll().then((data) => setContext({ data }));
}
if (beforeAll) beforeAll().then((data) => setContext({ data }))
}, [beforeAll]);
useEffect(() => {
if (tests.length) {
setCurrentTest(tests[0]);
}
if (tests.length) setCurrentTest(tests[0])
setCompletedTests([]);
setContext(null);
}, [tests]);
return (!!beforeAll && !!context) || !beforeAll ? (
return (!!beforeAll && !!context) || !beforeAll ? ( // ?
<div className="test-suite">
<div className="test-suite-name running">{name}</div>
{currentTest && (
@@ -64,29 +61,27 @@ const TestSuite = (props: TestSuiteProps): ReactElement<TestSuiteProps> => {
error: completedTest.error,
executionTime: completedTest.executionTime,
},
];
]
setCompletedTests(newCompleteTests);
const currentIndex = tests.indexOf(currentTest);
const nextIndex =
currentIndex < tests.length - 1 ? currentIndex + 1 : -1;
if (nextIndex >= 0) {
setCurrentTest(tests[nextIndex]);
} else {
setCurrentTest(null);
}
const nextIndex = currentIndex < tests.length - 1 ? currentIndex + 1 : -1;
if (nextIndex >= 0) setCurrentTest(tests[nextIndex]);
else setCurrentTest(null);
if (newCompleteTests.length === tests.length) {
if (afterAll) {
afterAll().then(() => onCompleted(name, newCompleteTests));
} else {
onCompleted(name, newCompleteTests);
}
if (afterAll) afterAll().then(() => onCompleted(name, newCompleteTests))
else onCompleted(name, newCompleteTests)
}
}}
/>
)}
{completedTests.map((completedTest, index) => {
const { test, result, error } = completedTest;
{completedTests.map((test, index) => {
const { test, result, error } = test;
const { title, description } = test;
return (
<TestCard
key={index}
@@ -99,8 +94,8 @@ const TestSuite = (props: TestSuiteProps): ReactElement<TestSuiteProps> => {
})}
</div>
) : (
<></>
<></> // FIXME: use {null} instead
);
};
export default TestSuite;
export default TestSuite;

View File

@@ -12,20 +12,19 @@ interface TestSuiteCardProps {
executionTime: number;
}[];
}
const TestSuiteCard = (
props: TestSuiteCardProps
): ReactElement<TestSuiteCardProps> => {
const TestSuiteCard = (props: TestSuiteCardProps): ReactElement<TestSuiteCardProps> => {
const { name, tests } = props;
const overallStatus = tests.map((t) => t.result).reduce((x, y) => x && y);
const overallStatus = tests.map((t) => t.result).reduce((x, y) => x && y); // TODO: refactor variable names
return (
<div className="test-suite">
<div className={`test-suite-name ${overallStatus ? "passed" : "failed"}`}>
{name}
</div>
{tests.map((completedTest, index) => {
const { test, result, error, executionTime } = completedTest;
{tests.map((test, index) => {
const { test, result, error, executionTime } = test;
const { title, description } = test;
return (
<TestCard
key={index}

View File

@@ -1,9 +1,9 @@
import React, { createContext, useState, useEffect, ReactNode } from "react";
import SASjs from "sasjs";
export const AppContext = createContext<{
config: any;
sasJsConfig: any;
export const AppContext = createContext<{ // TODO: create an interface
config: any; // TODO: be more specific on type declaration
sasJsConfig: any; // TODO: be more specific on type declaration
isLoggedIn: boolean;
setIsLoggedIn: (value: boolean) => void;
adapter: SASjs;
@@ -16,25 +16,24 @@ export const AppContext = createContext<{
});
export const AppProvider = (props: { children: ReactNode }) => {
const [config, setConfig] = useState<{ sasJsConfig: any }>({
sasJsConfig: null,
});
const [config, setConfig] = useState<{ sasJsConfig: any }>({sasJsConfig: null}); // TODO: be more specific on type declaration
const [adapter, setAdapter] = useState<SASjs>((null as unknown) as SASjs);
const [isLoggedIn, setIsLoggedIn] = useState(false);
useEffect(() => {
fetch("config.json")
fetch("config.json") // TODO: use axios instead of fetch
.then((res) => res.json())
.then((configJson: any) => {
.then((configJson: any) => { // TODO: be more specific on type declaration
setConfig(configJson);
const sasjs = new SASjs(configJson.sasJsConfig);
setAdapter(sasjs);
sasjs.checkSession().then((response) => {
setIsLoggedIn(response.isLoggedIn);
});
});
}); // FIXME: add catch block
});// FIXME: add catch block
}, []);
return (
@@ -50,4 +49,4 @@ export const AppProvider = (props: { children: ReactNode }) => {
{props.children}
</AppContext.Provider>
);
};
};

View File

@@ -33,18 +33,16 @@ export const basicTests = (
test: async () => {
return adapter.logIn(userName, password);
},
assertion: (response: any) =>
assertion: (response: any) => // FIXME: be more specific on type declaration
response && response.isLoggedIn && response.userName === userName,
},
{
title: "Default config",
description:
"Should instantiate with default config when none is provided",
test: async () => {
return Promise.resolve(new SASjs());
},
description: "Should instantiate with default config when none is provided",
test: async () => Promise.resolve(new SASjs()),
assertion: (sasjsInstance: SASjs) => {
const sasjsConfig = sasjsInstance.getSasjsConfig();
return (
sasjsConfig.serverUrl === defaultConfig.serverUrl &&
sasjsConfig.pathSAS9 === defaultConfig.pathSAS9 &&

View File

@@ -1,12 +1,12 @@
import SASjs from "sasjs";
import { TestSuite } from "../types";
const stringData: any = { table1: [{ col1: "first col value" }] };
const numericData: any = { table1: [{ col1: 3.14159265 }] };
const multiColumnData: any = {
const stringData: any = { table1: [{ col1: "first col value" }] }; // TODO: be more specific on type declaration
const numericData: any = { table1: [{ col1: 3.14159265 }] }; // TODO: be more specific on type declaration
const multiColumnData: any = { // TODO: be more specific on type declaration
table1: [{ col1: 42, col2: 1.618, col3: "x", col4: "x" }],
};
const multipleRowsWithNulls: any = {
const multipleRowsWithNulls: any = { // TODO: be more specific on type declaration
table1: [
{ col1: 42, col2: null, col3: "x", col4: "" },
{ col1: 42, col2: null, col3: "x", col4: "" },
@@ -15,7 +15,7 @@ const multipleRowsWithNulls: any = {
{ col1: 42, col2: 1.62, col3: "x", col4: "x" },
],
};
const multipleColumnsWithNulls: any = {
const multipleColumnsWithNulls: any = { // TODO: be more specific on type declaration
table1: [
{ col1: 42, col2: null, col3: "x", col4: null },
{ col1: 42, col2: null, col3: "x", col4: null },
@@ -25,21 +25,20 @@ const multipleColumnsWithNulls: any = {
],
};
const getLongStringData = (length = 32764) => {
const getLongStringData = (length = 32764) => { // FIXME: add type declaration
let x = "X";
for (let i = 1; i <= length; i++) {
x = x + "X";
}
const data: any = { table1: [{ col1: x }] };
for (let i = 1; i <= length; i++) x += 'X'
const data: any = { table1: [{ col1: x }] }; // TODO: be more specific on type declaration
return data;
};
const getLargeObjectData = () => {
const data = { table1: [{ big: "data" }] };
for (let i = 1; i < 10000; i++) {
data.table1.push(data.table1[0]);
}
for (let i = 1; i < 10000; i++) data.table1.push(data.table1[0])
return data;
};
@@ -50,12 +49,8 @@ export const sendArrTests = (adapter: SASjs): TestSuite => ({
{
title: "Single string value",
description: "Should send an array with a single string value",
test: () => {
return adapter.request("common/sendArr", stringData);
},
assertion: (res: any) => {
return res.table1[0][0] === stringData.table1[0].col1;
},
test: () => adapter.request("common/sendArr", stringData),
assertion: (res: any) => res.table1[0][0] === stringData.table1[0].col1 // TODO: be more specific on type declaration
},
{
title: "Long string value",
@@ -64,22 +59,17 @@ export const sendArrTests = (adapter: SASjs): TestSuite => ({
test: () => {
return adapter.request("common/sendArr", getLongStringData());
},
assertion: (res: any) => {
const longStringData = getLongStringData();
return res.table1[0][0] === longStringData.table1[0].col1;
},
assertion: (res: any) => res.table1[0][0] === getLongStringData().table1[0].col1 // TODO: be more specific on type declaration
},
{
title: "Overly long string value",
description:
"Should error out with long string values over 32765 characters",
test: () => {
return adapter
.request("common/sendArr", getLongStringData(32767))
.catch((e) => e);
},
assertion: (error: any) => {
return !!error && !!error.MESSAGE;
test: () => adapter
.request("common/sendArr", getLongStringData(32767))
.catch((e) => e), // TODO: rename
assertion: (error: any) => { // TODO: be more specific on type declaration
return !!error && !!error.MESSAGE; // FIXME: refactor
},
},
{
@@ -88,7 +78,7 @@ export const sendArrTests = (adapter: SASjs): TestSuite => ({
test: () => {
return adapter.request("common/sendArr", numericData);
},
assertion: (res: any) => {
assertion: (res: any) => { // TODO: be more specific on type declaration
return res.table1[0][0] === numericData.table1[0].col1;
},
},
@@ -98,7 +88,7 @@ export const sendArrTests = (adapter: SASjs): TestSuite => ({
test: () => {
return adapter.request("common/sendArr", multiColumnData);
},
assertion: (res: any) => {
assertion: (res: any) => { // TODO: be more specific on type declaration
return (
res.table1[0][0] === multiColumnData.table1[0].col1 &&
res.table1[0][1] === multiColumnData.table1[0].col2 &&
@@ -113,9 +103,10 @@ export const sendArrTests = (adapter: SASjs): TestSuite => ({
test: () => {
return adapter.request("common/sendArr", multipleRowsWithNulls);
},
assertion: (res: any) => {
assertion: (res: any) => { // TODO: be more specific on type declaration
let result = true;
multipleRowsWithNulls.table1.forEach((_: any, index: number) => {
multipleRowsWithNulls.table1.forEach((_: any, index: number) => { // TODO: be more specific on type declaration
// FIXME: use loop
result =
result &&
res.table1[index][0] === multipleRowsWithNulls.table1[index].col1;
@@ -129,6 +120,7 @@ export const sendArrTests = (adapter: SASjs): TestSuite => ({
result &&
res.table1[index][3] === multipleRowsWithNulls.table1[index].col4;
});
return result;
},
},
@@ -138,9 +130,9 @@ export const sendArrTests = (adapter: SASjs): TestSuite => ({
test: () => {
return adapter.request("common/sendArr", multipleColumnsWithNulls);
},
assertion: (res: any) => {
assertion: (res: any) => { // TODO: be more specific on type declaration
let result = true;
multipleColumnsWithNulls.table1.forEach((_: any, index: number) => {
multipleColumnsWithNulls.table1.forEach((_: any, index: number) => { // TODO: be more specific on type declaration
result =
result &&
res.table1[index][0] ===
@@ -171,12 +163,12 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
title: "Invalid column name",
description: "Should throw an error",
test: async () => {
const invalidData: any = {
const invalidData: any = { // TODO: be more specific on type declaration
"1 invalid table": [{ col1: 42 }],
};
return adapter.request("common/sendObj", invalidData).catch((e) => e);
},
assertion: (error: any) => !!error && !!error.MESSAGE,
assertion: (error: any) => !!error && !!error.MESSAGE, // TODO: be more specific on type declaration
},
{
title: "Single string value",
@@ -184,7 +176,7 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
test: () => {
return adapter.request("common/sendObj", stringData);
},
assertion: (res: any) => {
assertion: (res: any) => { // TODO: be more specific on type declaration
return res.table1[0].COL1 === stringData.table1[0].col1;
},
},
@@ -195,7 +187,7 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
test: () => {
return adapter.request("common/sendObj", getLongStringData());
},
assertion: (res: any) => {
assertion: (res: any) => { // TODO: be more specific on type declaration
const longStringData = getLongStringData();
return res.table1[0].COL1 === longStringData.table1[0].col1;
},
@@ -209,7 +201,7 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
.request("common/sendObj", getLongStringData(32767))
.catch((e) => e);
},
assertion: (error: any) => {
assertion: (error: any) => { // TODO: be more specific on type declaration
return !!error && !!error.MESSAGE;
},
},
@@ -219,7 +211,7 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
test: () => {
return adapter.request("common/sendObj", numericData);
},
assertion: (res: any) => {
assertion: (res: any) => { // TODO: be more specific on type declaration
return res.table1[0].COL1 === numericData.table1[0].col1;
},
},
@@ -230,7 +222,7 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
test: () => {
return adapter.request("common/sendObj", getLargeObjectData());
},
assertion: (res: any) => {
assertion: (res: any) => { // TODO: be more specific on type declaration
const data = getLargeObjectData();
return res.table1[9000].BIG === data.table1[9000].big;
},
@@ -241,7 +233,7 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
test: () => {
return adapter.request("common/sendObj", multiColumnData);
},
assertion: (res: any) => {
assertion: (res: any) => { // TODO: be more specific on type declaration
return (
res.table1[0].COL1 === multiColumnData.table1[0].col1 &&
res.table1[0].COL2 === multiColumnData.table1[0].col2 &&
@@ -256,9 +248,9 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
test: () => {
return adapter.request("common/sendObj", multipleRowsWithNulls);
},
assertion: (res: any) => {
assertion: (res: any) => { // TODO: be more specific on type declaration
let result = true;
multipleRowsWithNulls.table1.forEach((_: any, index: number) => {
multipleRowsWithNulls.table1.forEach((_: any, index: number) => { // TODO: be more specific on type declaration
result =
result &&
res.table1[index].COL1 === multipleRowsWithNulls.table1[index].col1;
@@ -281,9 +273,9 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
test: () => {
return adapter.request("common/sendObj", multipleColumnsWithNulls);
},
assertion: (res: any) => {
assertion: (res: any) => { // TODO: be more specific on type declaration
let result = true;
multipleColumnsWithNulls.table1.forEach((_: any, index: number) => {
multipleColumnsWithNulls.table1.forEach((_: any, index: number) => { // TODO: be more specific on type declaration
result =
result &&
res.table1[index].COL1 ===
@@ -305,4 +297,4 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
},
},
],
});
});

View File

@@ -1,7 +1,7 @@
import SASjs from "sasjs";
import { TestSuite } from "../types";
const data: any = { table1: [{ col1: "first col value" }] };
const data: any = { table1: [{ col1: "first col value" }] }; // TODO: be more specific on type declaration
export const sasjsRequestTests = (adapter: SASjs): TestSuite => ({
name: "SASjs Requests",
@@ -9,16 +9,11 @@ export const sasjsRequestTests = (adapter: SASjs): TestSuite => ({
{
title: "WORK tables",
description: "Should get WORK tables after request",
test: async () => {
return adapter.request("common/sendArr", data);
},
test: async () => adapter.request("common/sendArr", data),
assertion: (res: any) => {
const requests = adapter.getSasRequests();
if (adapter.getSasjsConfig().debug) {
return requests[0].SASWORK !== null;
} else {
return requests[0].SASWORK === null;
}
return adapter.getSasjsConfig().debug ? requests[0].SASWORK !== null : requests[0].SASWORK === null
},
},
],

View File

@@ -1,7 +1,7 @@
import SASjs from "sasjs";
import { TestSuite } from "../types";
const specialCharData: any = {
const specialCharData: any = { // TODO: be more specific on type definition
table1: [
{
tab: "\t",
@@ -9,8 +9,8 @@ const specialCharData: any = {
cr: "\r",
semicolon: ";semi",
percent: "%",
singleQuote: "'",
doubleQuote: '"',
singleQuote: "'", // TODO: use ``
doubleQuote: '"', // TODO: use ``
crlf: "\r\n",
euro: "€euro",
banghash: "!#banghash",
@@ -18,7 +18,7 @@ const specialCharData: any = {
],
};
const moreSpecialCharData: any = {
const moreSpecialCharData: any = { // TODO: be more specific on type definition
table1: [
{
speech0: '"speech',
@@ -36,44 +36,46 @@ const moreSpecialCharData: any = {
],
};
const getWideData = () => {
const cols: any = {};
for (let i = 1; i <= 10000; i++) {
const getWideData = () => { // FIXME: declared but never used
const cols: any = {}; // TODO: be more specific on type definition
for (let i = 1; i <= 10000; i++) { // Why 10000?
cols["col" + i] = "test" + i;
}
const data: any = {
const data: any = { // TODO: be more specific on type definition
table1: [cols],
};
return data;
};
const getTables = () => {
const tables: any = {};
const getTables = () => { // FIXME: declared but never used
const tables: any = {}; // TODO: be more specific on type definition
for (let i = 1; i <= 100; i++) {
for (let i = 1; i <= 100; i++) { // why 100
tables["table" + i] = [{ col1: "x", col2: "x", col3: "x", col4: "x" }];
}
return tables;
};
const getLargeDataset = () => {
const rows: any = [];
const colData: string =
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
const rows: any = []; // TODO: be more specific on type definition
const colData: string = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; // FIXME: no need to explicitly mention data type
for (let i = 1; i <= 10000; i++) {
rows.push({ col1: colData, col2: colData, col3: colData, col4: colData });
}
const data: any = {
const data: any = { // TODO: be more specific on type definition
table1: rows,
};
return data;
};
const errorAndCsrfData: any = {
// FIXME: declared but never used
const errorAndCsrfData: any = { // TODO: be more specific on type definition
error: [{ col1: "q", col2: "w", col3: "e", col4: "r" }],
_csrf: [{ col1: "q", col2: "w", col3: "e", col4: "r" }],
};
@@ -84,10 +86,8 @@ export const specialCaseTests = (adapter: SASjs): TestSuite => ({
{
title: "Common special characters",
description: "Should handle common special characters",
test: () => {
return adapter.request("common/sendArr", specialCharData);
},
assertion: (res: any) => {
test: () => adapter.request("common/sendArr", specialCharData),
assertion: (res: any) => { // TODO: be more specific on type definition
return (
res.table1[0][0] === specialCharData.table1[0].tab &&
res.table1[0][1] === specialCharData.table1[0].lf &&
@@ -102,6 +102,8 @@ export const specialCaseTests = (adapter: SASjs): TestSuite => ({
);
},
},
// TODO: delete commented out code
// {
// title: "Other special characters",
// description: "Should handle other special characters",
@@ -180,15 +182,15 @@ export const specialCaseTests = (adapter: SASjs): TestSuite => ({
{
title: "Large dataset",
description: "Should handle 5mb of data",
test: () => {
return adapter.request("common/sendArr", getLargeDataset());
},
assertion: (res: any) => {
test: () => adapter.request("common/sendArr", getLargeDataset()),
assertion: (res: any) => { // TODO: be more specific on type definition
const data = getLargeDataset();
let result = true;
let result = true; // TODO: rename
for (let i = 0; i <= 10; i++) {
result = result && res.table1[i][0] === data.table1[i][0];
}
return result;
},
},
@@ -232,4 +234,4 @@ export const specialCaseTests = (adapter: SASjs): TestSuite => ({
// },
// },
],
});
});

View File

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

View File

@@ -3,20 +3,21 @@ export const assert = (
message = "Assertion failed"
) => {
let result;
try {
if (typeof expression === "boolean") {
result = expression;
} else {
result = expression();
}
if (typeof expression === "boolean") result = expression;
else result = expression();
} catch (e) {
console.error(message);
throw new Error(message);
}
if (!!result) {
return;
} else {
console.error(message);
throw new Error(message);
}
};

View File

@@ -3,21 +3,25 @@ export const uploadFile = (file: File, fileName: string, url: string) => {
const data = new FormData();
data.append("file", file);
data.append("filename", fileName);
const xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.addEventListener("readystatechange", function () {
xhr.addEventListener("readystatechange", function () { // TODO: use ES6
if (this.readyState === 4) {
let response: any;
try {
response = JSON.parse(this.responseText);
} catch (e) {
reject(e);
}
resolve(response);
}
});
xhr.open("POST", url);
xhr.setRequestHeader("cache-control", "no-cache");
xhr.send(data);
});
};
};

View File

@@ -3,49 +3,49 @@
*
*/
export class SAS9ApiClient {
constructor(private serverUrl: string) {}
constructor(private serverUrl: string) {}
/**
* returns on object containing the server URL
*/
public getConfig() {
return {
serverUrl: this.serverUrl,
};
}
/**
* @returns an object containing the server URL
*/
public getConfig() {
return {
serverUrl: this.serverUrl,
}
}
/**
* Updates serverurl which is not null
* @param serverUrl - the URL of the server.
*/
public setConfig(serverUrl: string) {
if (serverUrl) this.serverUrl = serverUrl;
}
/**
* Updates serverUrl which is not null
* @param serverUrl - the URL of the server.
*/
public setConfig(serverUrl: string) {
if (serverUrl) this.serverUrl = serverUrl
}
/**
* Executes code on a SAS9 server.
* @param linesOfCode - an array of lines of code to execute
* @param serverName - the server to execute the code on
* @param repositoryName - the repository to execute the code on
*/
public async executeScript(
linesOfCode: string[],
serverName: string,
repositoryName: string
) {
const requestPayload = linesOfCode.join("\n");
const executeScriptRequest = {
method: "PUT",
headers: {
Accept: "application/json",
},
body: `command=${requestPayload}`,
};
const executeScriptResponse = await fetch(
`${this.serverUrl}/sas/servers/${serverName}/cmd?repositoryName=${repositoryName}`,
executeScriptRequest
).then((res) => res.text());
/**
* Executes code on a SAS9 server.
* @param linesOfCode - an array of lines of code to execute
* @param serverName - the server to execute the code on
* @param repositoryName - the repository to execute the code on
*/
public async executeScript(
linesOfCode: string[], // FIXME: rename
serverName: string,
repositoryName: string
) {
const requestPayload = linesOfCode.join('\n')
const executeScriptRequest = {
method: 'PUT',
headers: {Accept: 'application/json'},
body: `command=${requestPayload}`,
}
// FIXME: use axios instead of fetch
const executeScriptResponse = await fetch(
`${this.serverUrl}/sas/servers/${serverName}/cmd?repositoryName=${repositoryName}`,
executeScriptRequest
).then((res) => res.text())
// FIXME: no catch block
return executeScriptResponse;
}
}
return executeScriptResponse
}
}

View File

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

View File

@@ -2,6 +2,7 @@ import SASjs from "./index";
const adapter = new SASjs();
// FIXME: adapter doesn't have 'parseSAS9SourceCode' and 'parseGeneratedCode'
it("should parse SAS9 source code", async done => {
expect(sampleResponse).toBeTruthy();
const parsedSourceCode = (adapter as any).parseSAS9SourceCode(sampleResponse);

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 * as e6p from "es6-promise";
(e6p as any).polyfill();
@@ -839,6 +852,7 @@ export default class SASjs {
}
}
// FIXME: this method never used
private fetchLogFileContent(logLink: string) {
return new Promise((resolve, reject) => {
fetch(logLink, {
@@ -1038,4 +1052,4 @@ export default class SASjs {
);
});
}
}
}

View File

@@ -1,5 +1,7 @@
import SASjs from "./SASjs";
export * from "./types";
export * from "./SASViyaApiClient";
export * from "./SAS9ApiClient";
export default SASjs;
import SASjs from './SASjs'
export * from './types'
export * from './SASViyaApiClient'
export * from './SAS9ApiClient'
export default SASjs

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,4 +4,4 @@ export interface Link {
href: string;
uri: 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
* streamed.
*/
// TODO: we should clarify what location we are talking about
serverUrl: string = "";
pathSAS9: string = "";
pathSASViya: string = "";

View File

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

View File

@@ -2,6 +2,8 @@
* Represents requests that are queued, pending a signon event
*
*/
// FIXME: be more specific on type declaration
export interface SASjsWaitingRequest {
requestPromise: {
promise: any;

View File

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

View File

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

View File

@@ -7,4 +7,4 @@ export * from "./SASjsConfig";
export * from "./SASjsRequest";
export * from "./SASjsWaitingRequest";
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) {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array);
}
}
}

View File

@@ -2,7 +2,6 @@ import { SASjsRequest } from "../types/SASjsRequest";
/**
* Comparator for SASjs request timestamps
*
*/
export const compareTimestamps = (a: SASjsRequest, b: SASjsRequest) => {
return b.timestamp.getTime() - a.timestamp.getTime();

View File

@@ -3,43 +3,43 @@
* @param data - the JSON object to convert.
*/
export const convertToCSV = (data: any) => {
const replacer = (key: any, value: any) => (value === null ? "" : value);
const headerFields = Object.keys(data[0]);
const replacer = (key: any, value: any) => (value === null ? "" : value); // FIXME: 'key' parameter was not used, why do we compare with null (undefined, NaN)?
const headerFields = Object.keys(data[0]); // FIXME: data can be of any type, but we are working with it as with object
let csvTest;
let invalidString = false;
const headers = headerFields.map((field) => {
let firstFoundType: string | null = null;
let hasMixedTypes: boolean = false;
let rowNumError: number = -1;
let hasMixedTypes: boolean = false; // FIXME: unnecessary type declaration
let rowNumError: number = -1; // FIXME: unnecessary type declaration
const longestValueForField = data
.map((row: any, index: number) => {
.map((row: any, index: number) => { // FIXME: row should be of type string | number
if (row[field] || row[field] === "") {
if (firstFoundType) {
let currentFieldType =
row[field] === "" || typeof row[field] === "string"
let currentFieldType = // FIXME: use const
row[field] === "" || typeof row[field] === "string" // FIXME: "" is also of type string
? "chars"
: "number";
if (!hasMixedTypes) {
hasMixedTypes = currentFieldType !== firstFoundType;
rowNumError = hasMixedTypes ? index + 1 : -1;
rowNumError = hasMixedTypes ? index + 1 : -1; // TODO: refactor
}
} else {
if (row[field] === "") {
firstFoundType = "chars";
} else {
firstFoundType =
typeof row[field] === "string" ? "chars" : "number";
typeof row[field] === "string" ? "chars" : "number"; // TODO: refactor
}
}
let byteSize;
if (typeof row[field] === "string") {
let doubleQuotesFound = row[field]
let doubleQuotesFound = row[field] // FIXME: use const
.split("")
.filter((char: any) => char === '"');
.filter((char: any) => char === '"'); // FIXME: why char is of type any?
byteSize = getByteSize(row[field]);
@@ -52,16 +52,18 @@ export const convertToCSV = (data: any) => {
}
})
.sort((a: number, b: number) => b - a)[0];
if (longestValueForField && longestValueForField > 32765) {
if (longestValueForField && longestValueForField > 32765) { // FIXME: longestValueForField is an array and it is not comparable to a number
invalidString = true;
}
if (hasMixedTypes) {
console.error(
`Row (${rowNumError}), Column (${field}) has mixed types: ERROR`
);
}
return `${field}:${firstFoundType === "chars" ? "$" : ""}${
return `${field}:${firstFoundType === "chars" ? "$" : ""}${ // TODO: format return string before return statement
longestValueForField
? longestValueForField
: firstFoundType === "chars"
@@ -73,10 +75,11 @@ export const convertToCSV = (data: any) => {
if (invalidString) {
return "ERROR: LARGE STRING LENGTH";
}
csvTest = data.map((row: any) => {
const fields = Object.keys(row).map((fieldName, index) => {
let value;
let containsSpecialChar = false;
let containsSpecialChar = false; // FIXME: should be const
const currentCell = row[fieldName];
if (JSON.stringify(currentCell).search(/(\\t|\\n|\\r)/gm) > -1) {
@@ -89,7 +92,7 @@ export const convertToCSV = (data: any) => {
value = value.replace(/\\\\/gm, "\\");
if (containsSpecialChar) {
if (value.includes(",") || value.includes('"')) {
if (value.includes(",") || value.includes('"')) { // FIXME: use `"`
value = '"' + value + '"';
}
} else {
@@ -112,6 +115,7 @@ export const convertToCSV = (data: any) => {
return value;
});
return fields.join(",");
});
@@ -121,6 +125,7 @@ export const convertToCSV = (data: any) => {
return finalCSV;
};
// TODO: refactor
const getByteSize = (str: string) => {
let byteSize = str.length;
for (let i = str.length - 1; i >= 0; i--) {
@@ -130,4 +135,4 @@ const getByteSize = (str: string) => {
if (code >= 0xdc00 && code <= 0xdfff) i--; //trail surrogate
}
return byteSize;
};
};

View File

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

View File

@@ -1,5 +1,6 @@
export const isLogInRequired = (response: string): boolean => {
const pattern: RegExp = /<form.+action="(.*Logon[^"]*).*>/gm;
const pattern: RegExp = /<form.+action="(.*Logon[^"]*).*>/gm; // FIXME: unnecessary type declaration
const matches = pattern.test(response);
return matches;
};
};

View File

@@ -1,2 +1 @@
export const isLogInSuccess = (response: string): boolean =>
/You have signed in/gm.test(response);
export const isLogInSuccess = (response: string): boolean => /You have signed in/gm.test(response); // TODO: maybe we have more reliable way to verify that login was successful?

View File

@@ -2,30 +2,33 @@ import { CsrfToken } from "../types";
export async function makeRequest<T>(
url: string,
request: RequestInit,
request: RequestInit, // Where 'RequestInit' is coming from?
callback: (value: CsrfToken) => any,
contentType: "text" | "json" = "json"
): Promise<T> {
const responseTransform =
contentType === "json"
? (res: Response) => res.json()
: (res: Response) => res.text();
const result = await fetch(url, request).then((response) => {
if (!response.ok) {
const responseTransform = contentType === "json" ?
(res: Response) => res.json()
:
(res: Response) => res.text();
const result = await fetch(url, request).then((response) => { // FIXME: use axios instead of fetch
if (!response.ok) { // FIXME we can just check if status === 403
if (response.status === 403) {
const tokenHeader = response.headers.get("X-CSRF-HEADER");
if (tokenHeader) {
const token = response.headers.get(tokenHeader);
const token = response.headers.get(tokenHeader) || ''; // TODO: refactor
callback({
headerName: tokenHeader,
value: token || "",
value: token,
});
const retryRequest = {
...request,
headers: { ...request.headers, [tokenHeader]: token },
};
return fetch(url, retryRequest).then(responseTransform);
}
}
@@ -33,5 +36,6 @@ export async function makeRequest<T>(
return responseTransform(response);
}
});
return result;
}

View File

@@ -1,3 +1,4 @@
// TODO: refactor
export const needsRetry = (responseText: string): boolean => {
return (
(responseText.includes('"errorCode":403') &&
@@ -8,4 +9,4 @@ export const needsRetry = (responseText: string): boolean => {
(responseText.includes('"status":449') &&
responseText.includes("Authentication success, retry original request"))
);
};
};

View File

@@ -6,7 +6,7 @@ export const parseAndSubmitAuthorizeForm = async (
const params: any = {};
const responseBody = response.split("<body>")[1].split("</body>")[0];
const bodyElement = document.createElement("div");
const bodyElement = document.createElement("div"); // TODO: rename
bodyElement.innerHTML = responseBody;
const form = bodyElement.querySelector("#application_authorization");
@@ -24,7 +24,7 @@ export const parseAndSubmitAuthorizeForm = async (
const formData = new FormData();
for (const key in params) {
for (const key in params) { // TODO: use forEach
if (params.hasOwnProperty(key)) {
formData.append(key, params[key]);
}
@@ -32,7 +32,7 @@ export const parseAndSubmitAuthorizeForm = async (
return new Promise((resolve, reject) => {
if (authUrl) {
fetch(authUrl, {
fetch(authUrl, { // TODO use axios instead of fetch
method: "POST",
credentials: "include",
body: formData,
@@ -46,4 +46,4 @@ export const parseAndSubmitAuthorizeForm = async (
reject("Auth form url is null");
}
});
};
};

View File

@@ -1,7 +1,7 @@
export const parseGeneratedCode = (log: string) => {
const startsWith = "MPRINT";
const isGeneratedCodeLine = (line: string) =>
line.trim().startsWith(startsWith);
const isGeneratedCodeLine = (line: string) => line.trim().startsWith(startsWith);
const logLines = log.split("\n").filter(isGeneratedCodeLine);
return logLines.join("\r\n");
};

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;
try {
log = logResponse.items
? logResponse.items.map((i) => i.line).join("\n")
: JSON.stringify(logResponse);
} catch (e) {
} catch (e) { // TODO: rename parameter to err or error
console.error("An error has occurred while parsing the log response", e);
log = logResponse;
}
return log;
};

View File

@@ -1,6 +1,6 @@
export const parseSourceCode = (log: string): string => {
const isSourceCodeLine = (line: string) =>
line.trim().substring(0, 10).trimStart().match(/^\d/);
const isSourceCodeLine = (line: string) => line.trim().substring(0, 10).trimStart().match(/^\d/);
const logLines = log.split("\n").filter(isSourceCodeLine);
return logLines.join("\r\n");
};

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[] = [];
for (const p in obj) {
for (const p in obj) { // FIXME: name variables properly
if (obj.hasOwnProperty(p)) {
if (obj[p] instanceof Array) {
for (let i = 0, n = obj[p].length; i < n; i++) {
@@ -11,5 +12,6 @@ export const serialize = (obj: any) => {
}
}
}
return str.join("&");
};
};

View File

@@ -1,10 +1,10 @@
export const splitChunks = (content: string) => {
const size = 16000;
export const splitChunks = (content: string) => { // TODO: set return type
const size = 16000; // why 16000?
const numChunks = Math.ceil(content.length / size);
const chunks = new Array(numChunks);
for (let i = 0, o = 0; i < numChunks; ++i, o += size) {
for (let i = 0, o = 0; i < numChunks; ++i, o += size) { // FIXME: name variables properly
chunks[i] = content.substr(o, size);
}

View File

@@ -24,7 +24,7 @@
"links": [
{
"label": "SASjs on Github",
"url": "https://github.com/macropeople/sasjs"
"url": "https://github.com/sasjs/adapter"
},
{
"label": "SASjs.io",
@@ -32,11 +32,11 @@
},
{
"label": "SASjs CLI",
"url": "https://github.com/macropeople/sasjs-cli"
"url": "https://github.com/sasjs/cli"
},
{
"label": "React Seed App",
"url": "https://github.com/macropeople/react-seed-app"
"url": "https://github.com/sasjs/react-seed-app"
}
]
}