8000 Various improvements and fixes by hibnico · Pull Request #106 · saagie/sdk · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content
8000

Various improvements and fixes #106

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 27 commits into from
May 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
c068f52
Handle 'null' values in stringify
hibnico Apr 26, 2022
7df372a
Only get logs if an action getLogs is configured
hibnico Apr 26, 2022
3af2f0e
Properly restore data form the local storage in case only the paramet…
hibnico Apr 26, 2022
be9fba9
Do not retry failed queries
hibnico Apr 26, 2022
54373d5
Add a "Sandbox" form to run any kind of function in a script
hibnico Apr 26, 2022
f0e3f8b
Store "Sandbox" form in local storage
hibnico Apr 27, 2022
3427250
Handle more kind of data in stringify
hibnico Apr 27, 2022
e97e82a
Wrap long log lines in the debug panel
hibnico Apr 27, 2022
4454253
Run the script in a forked process
hibnico Apr 27, 2022
74b7eef
Fix how logs are fetched when they are a stream of objects
hibnico Apr 28, 2022
3543360
Simulate the split of the logs by line
hibnico Apr 28, 2022
ea5db7a
Handle script that returns noting, like stop
hibnico Apr 28, 2022
1408bfb
Show the actual error message in the UI
hibnico May 2, 2022
edb1e35
Fix showing the error message on actions
hibnico May 2, 2022
0cb5872
If we don't succeed in returning the error, at least log it locally
hibnico May 2, 2022
74bfaae
Add a max timeout on script execution
hibnico May 2, 2022
2939705
Handle 'undefined' in stringified data
hibnico May 3, 2022
57ef34f
Add support for 'checkConnection'
hibnico May 3, 2022
3abaa47
Fix script log streaming and validate log format
arnaudbos May 4, 2022
f35d0cc
Fix log stream error and add check connection form display condition
arnaudbos May 6, 2022
1de79d7
Fix checkConnection script path
hibnico May 16, 2022
778fcf5
Display log timestamps
hibnico May 16, 2022
fb1ebfa
Add checkConnection to the template
hibnico May 16, 2022
550fdc5
Handle all script responses as stream
arnaudbos May 19, 2022
aec041c
Make the first lines of logs displayed and the ull content downloadable
hibnico May 19, 2022
944ad08
Check script return value for null or undefined and add a few asserti…
arnaudbos May 23, 2022
48232d0
Bump ejs from 3.1.6 to 3.1.8
dependabot[bot] May 16, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion packages/sdk/src/cli/commands/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ Inside that directory, you can run several commands:
{cyan yarn dev}
Start the development server.

{cyan yarn run build}
{cyan yarn build}
Bundle the technology for the Saagie platform.

{cyan yarn run new:context}
Expand Down Expand Up @@ -166,6 +166,19 @@ const createConnectionType = async () => {
{bold {green 🎉 ${connectionAnswers.label} connection type generated with success 🎉}}

New connection type available in {italic ${folder}}
Inside that directory, you can run several commands:

{cyan yarn dev}
Start the build watch.

{cyan yarn build}
Bundle the javascript file.

We suggest that you begin by typing:

{cyan cd} {italic ${folder}}
{cyan yarn install}
{cyan yarn dev}
`);
};

Expand Down
174 changes: 151 additions & 23 deletions packages/sdk/src/cli/server/services/action.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
// eslint-disable-next-line max-classes-per-file
const fs = require('fs');
const fse = require('fs-extra');
const path = require('path');
const { Stream } = require('stream');
const output = require('../../utils/output');
const { stringify, runScript } = require('../../utils/runScript');
const { Transform, Readable } = require('stream');
const { runScript } = require('../../utils/script-runner');
const { stringify } = require('../../utils/script-utils');

const Response = {
success: (data, logs) => ({
data,
success: (payload, logs) => ({
payload,
logs,
}),
error: (error, logs) => ({
Expand All @@ -15,35 +17,161 @@ const Response = {
}),
};

function onError(error, res, logs) {
output.error(error);
res.status(420).send(Response.error(stringify(error), logs));
class PayloadTransform extends Transform {
constructor() {
super({ readableOjectMode: false, writableObjectMode: true });
this.empty = true;
}

_transform(chunk, _, callback) {
if (this.empty) {
this.empty = false;
this.push('[');
} else {
this.push(',');
}
this.push(JSON.stringify(chunk));
callback();
}

_flush(callback) {
if (this.empty) {
this.push('[');
}
this.push(']');
callback();
}
}

class LogTransform extends Transform {
constructor() {
super({ readableOjectMode: false, writableObjectMode: true });
}

_transform(chunk, _, callback) {
this.push(new Date(chunk.timestamp).toISOString());
this.push(': ');
this.push(chunk.log);
this.push('\n');
callback();
}
}

class BufferHeadTransform extends Transform {
constructor(max) {
super({ readableOjectMode: true, writableObjectMode: true, objectMode: true });
this.max = max;
this.head = [];
this.hasMore = false;
}

_transform(chunk, _, callback) {
if (this.head.length < this.max) {
this.head.push(chunk);
} else {
this.hasMore = true;
}
this.push(chunk);
callback();
}
}

module.exports = async (req, res) => {
const logs = [];
const scriptStdOut = [];
const scriptPath = path.resolve(process.cwd(), req.body.script);
try {
const scriptPath = path.resolve(process.cwd(), req.body.script);

if (!await fse.pathExists(scriptPath || '')) {
const message = `Unable to find file ${scriptPath}, please check the path in context.yaml`;
output.error(message);
res.status(421).send(Response.error({ message }));
res.set('Content-Type', 'application/json');
res.status(421)
.send(Response.error({ message }));

return;
}

const logger = (log) => logs.push(log);
const scriptData = fse.readFileSync(scriptPath);
const data = await runScript(scriptData, req.body.function, req.body.params, logger);
if (data instanceof Stream) {
data.pause();
data.on('error', (err) => onError(err, res, logs));
data.pipe(res);
} else {
res.send(Response.success(data, logs));

const scriptLogger = (name, log) => {
log.split(/\r\n|\n\r|\n|\r/)
.forEach((logPart) => {
scriptStdOut.push({
name,
message: logPart,
});
});
};

const {
download = false,
} = req.body.opts || {};

let stream = await runScript(
scriptPath,
scriptData.toString(),
req.body.function,
[req.body.params],
scriptLogger,
);

res.set('Content-Type', 'application/json');

let downloadLink = null;
let hasMore = null;
if (download) {
const logpath = `${process.cwd()}/action.log`;
downloadLink = {
href: '/api/static?path=action.log',
name: 'action.log',
};
const destination = fs.createWriteStream(logpath);
const head = new BufferHeadTransform(100);
await new Promise((fulfill) => {
stream
.pipe(head)
.pipe(new LogTransform())
.pipe(destination)
.on('finish', fulfill);
});
hasMore = head.hasMore;
stream = Readable.from(head.head);
}
res.write('{"payload":');

const payload = stream.pipe(new PayloadTransform());

payload.pipe(res, { end: false });

payload.on('end', () => {
res.write(',"logs":[');
scriptStdOut.forEach((log, i) => {
if (i > 0) {
res.write(',');
}
res.write(JSON.stringify(log));
});
res.write(']');
if (downloadLink !== null) {
res.write(',"download":');
res.write(JSON.stringify(downloadLink));
}
if (hasMore !== null) {
res.write(',"hasMore":');
res.write(hasMore ? 'true' : 'false');
}
res.write('}');
res.end();
});
} catch (e) {
if (e == null) {
return;
}
try {
res.status(420)
.send(Response.error(stringify(e), scriptStdOut));
} catch (ee) {
// we may not be able to sent a proper http error response, as the response may have been already started to be streamed.
// eslint-disable-next-line no-console
console.error('Unexpected http response error', ee, '. Script error is:', e);
}
} catch (err) {
onError(err, res, logs);
}
};
5 changes: 3 additions & 2 deletions packages/sdk/src/cli/server/services/demo.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,11 @@ const jobStates = [];

const log = (state, message) => {
let msg = message;
const now = new Date();
if (state.logDate) {
msg = `${new Date().toLocaleTimeString()} ${message}`;
msg = `${message} at ${now.toLocaleTimeString()}.`;
}
state.logs.push(msg);
state.logs.push(`${now.getTime()} - ${msg}`);
};

const run = (method, n, i, a, state) => {
Expand Down
1 change: 1 addition & 0 deletions packages/sdk/src/cli/templates/.gitignore 10000
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
action.log
5 changes: 5 additions & 0 deletions packages/sdk/src/cli/templates/connectiontype/metadata.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,8 @@ parameters:
id: password
label: Password
mandatory: true

actions:
checkConnection:
script: ./dist/module.js
function: checkConnection
22 changes: 22 additions & 0 deletions packages/sdk/src/cli/templates/connectiontype/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "{{id}}",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "webpack --watch",
"build": "webpack",
"test": "echo \"No test specified\""
},
"dependencies": {
"@babel/runtime": "^7.17.7",
"axios": "0.26.0"
},
"devDependencies": {
"@babel/core": "^7.17.7",
"@babel/plugin-transform-runtime": "^7.17.0",
"@babel/preset-env": "^7.16.11",
"babel-loader": "^8.2.3",
"webpack": "^5.70.0",
"webpack-cli": "^4.9.1"
}
}
38 changes: 38 additions & 0 deletions packages/sdk/src/cli/templates/connectiontype/src/module.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
const axios = require('axios');
const axiosHttp = require('axios/lib/adapters/http');

const client = axios.create({
adapter: axiosHttp,
});

/**
* Example of function to check the connection data.
* @param {Object} params - Contains entity data including featuresValues.
* @param {Object} params.connection - Contains values configuring the associated connection.
* @return {{ok, message}} - an object with a boolean field 'ok' indicating the connection status, and a string field 'message' describing the issue if the connection is not ok.
*/
exports.checkConnection = async ({ connection}) => {
const url = `${connection.url}`
console.log('Requesting GET at', url);
let response;
try {
response = await client.get(url);
} catch (e) {
console.log('HTTP error:', e.response?.status, e.response?.statusText, e.response?.data);
if (e.response?.status === 401) {
return {
ok: false,
message: 'Bad credentials',
};
}
return {
ok: false,
message: `Unexpected HTTP error with status ${e.response?.status}`,
};
}
console.log('Response:', response.data);

return {
ok: true,
};
};
33 changes: 33 additions & 0 deletions packages/sdk/src/cli/templates/connectiontype/webpack.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const path = require('path');

module.exports = {
target: 'node16',
mode: 'production',
entry: {
'module': './src/module.js',
},
module: {
rules: [
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
plugins: [
['@babel/transform-runtime']
]
}
}
}
],
},
output: {
library: {
type: 'commonjs',
},
path: path.resolve(__dirname, 'dist'),
filename: '[name].js'
},
};
Loading
0