@yousolution/node-red-contrib-you-sap-service-layer 0.0.6 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,85 +1,100 @@
1
1
  <script type="text/javascript">
2
- RED.nodes.registerType('nextLink',{
2
+ RED.nodes.registerType('nextLink', {
3
3
  category: 'Sap',
4
4
  color: '#FFC300',
5
5
  defaults: {
6
- name: {value: ''},
7
- nextLink: {value: ''}
6
+ name: { value: '' },
7
+ nextLink: { value: '' },
8
8
  },
9
- inputs:1,
10
- outputs:1,
9
+ inputs: 1,
10
+ outputs: 2,
11
+ outputLabels: ['next', 'end'],
12
+ // outputLabels: function (index) {
13
+ // if (index == 0) {
14
+ // return 'next iteration';
15
+ // }
16
+ // return 'last iteration';
17
+ // },
11
18
  icon: 'font-awesome/fa-arrows-h',
12
- label: function() {
13
- return this.name||"Next link";
19
+ label: function () {
20
+ return this.name || 'Next link';
14
21
  },
15
- oneditprepare: function() {
16
- $("#node-input-nextLink").typedInput({
17
- type:"msg",
18
- types:["msg"],
19
- typeField: "#node-input-nextLink-type",
20
- value: 'nextLink'
22
+ oneditprepare: function () {
23
+ $('#node-input-nextLink').typedInput({
24
+ type: 'msg',
25
+ types: ['msg'],
26
+ typeField: '#node-input-nextLink-type',
27
+ value: 'nextLink',
21
28
  });
22
- }
29
+ },
23
30
  });
24
31
  </script>
25
32
 
26
-
27
-
28
-
29
33
  <script type="text/html" data-template-name="nextLink">
30
34
  <div class="form-row">
31
35
  <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
32
- <input type="text" id="node-input-name" placeholder="Name">
36
+ <input type="text" id="node-input-name" placeholder="Name" />
33
37
  </div>
34
38
 
35
39
  <div class="form-row">
36
40
  <label for="node-input-type"><i class="fa fa-gears"></i> NextLink</label>
37
- <input type="text" id="node-input-nextLink">
38
- <input type="hidden" id="node-input-nextLink-type">
41
+ <input type="text" id="node-input-nextLink" />
42
+ <input type="hidden" id="node-input-nextLink-type" />
39
43
  </div>
40
44
  </script>
41
-
45
+
42
46
  <!-- Documentation -->
43
47
  <script type="text/html" data-help-name="nextLink">
44
48
  <p>Next link</p>
45
-
49
+
46
50
  <h3>Inputs</h3>
47
- <dl class="message-properties">
48
- <dt>Name
49
- <span class="property-type">string</span>
50
- </dt>
51
- <dd> the node's name </dd>
52
- <dt>NextLink
53
- <span class="property-type">string</span>
54
- </dt>
55
- <dd> the url link to next Service Layer page </dd>
56
- </dl>
57
-
58
- <h3>Outputs</h3>
59
- <ol class="node-ports">
60
- <li>Standard output
61
- <dl class="message-properties">
62
- <dt>payload <span class="property-type">string</span></dt>
63
- <dd>the standard output of the command.</dd>
64
- </dl>
65
- </li>
66
- </ol>
67
-
51
+ <dl class="message-properties">
52
+ <dt>
53
+ Name
54
+ <span class="property-type">string</span>
55
+ </dt>
56
+ <dd>the node's name</dd>
57
+ <dt>
58
+ NextLink
59
+ <span class="property-type">string</span>
60
+ </dt>
61
+ <dd>the url link to next Service Layer page</dd>
62
+ </dl>
63
+
64
+ <h3>Outputs</h3>
65
+ <ol class="node-ports">
66
+ <li>
67
+ Standard output
68
+ <dl class="message-properties">
69
+ <dt>payload <span class="property-type">msg</span></dt>
70
+ <dd>It returns the message with next link to the next page.</dd>
71
+ </dl>
72
+ </li>
73
+ <li>
74
+ Standard output
75
+ <dl class="message-properties">
76
+ <dt>payload <span class="property-type">msg</span></dt>
77
+ <dd>It returns the message when the last page is reached.</dd>
78
+ </dl>
79
+ </li>
80
+ </ol>
81
+
68
82
  <h3>Details</h3>
69
- <p>this node is used to get the next page of result of items from service layer.
70
- See the examples to understand how to use it.
71
- </p>
72
- <!-- <p><code>msg.payload</code> is used as the payload of the published message.
83
+ <p>this node is used to get the next page of result of items from service layer. See the examples to understand how to use it.</p>
84
+ <!-- <p><code>msg.payload</code> is used as the payload of the published message.
73
85
  If it contains an Object it will be converted to a JSON string before being sent.
74
86
  If it contains a binary Buffer the message will be published as-is.</p>
75
87
  <p>The topic used can be configured in the node or, if left blank, can be set
76
88
  by <code>msg.topic</code>.</p>
77
89
  <p>Likewise the QoS and retain values can be configured in the node or, if left
78
90
  blank, set by <code>msg.qos</code> and <code>msg.retain</code> respectively.</p> -->
79
-
91
+
80
92
  <h3>References</h3>
81
- <ul>
82
- <li><a href="https://sap-samples.github.io/smb-summit-hackathon/b1sl.html" target="_black">Service layer API docs</a> - for more details </li>
83
- <li><a href="https://github.com/yousolution-cloud/node-red-contrib-you-sap-service-layer">@yousolution-cloud/node-red-contrib-you-sap-service-layer</a> - the nodes github repository</li>
84
- </ul>
85
- </script>
93
+ <ul>
94
+ <li><a href="https://sap-samples.github.io/smb-summit-hackathon/b1sl.html" target="_black">Service layer API docs</a> - for more details</li>
95
+ <li>
96
+ <a href="https://github.com/yousolution-cloud/node-red-contrib-you-sap-service-layer">@yousolution-cloud/node-red-contrib-you-sap-service-layer</a> - the
97
+ nodes github repository
98
+ </li>
99
+ </ul>
100
+ </script>
package/nodes/nextLink.js CHANGED
@@ -7,12 +7,12 @@ module.exports = function (RED) {
7
7
  const nextLink = msg[config.nextLink];
8
8
 
9
9
  if (!nextLink) {
10
+ node.send([null, msg]);
10
11
  return;
11
12
  }
12
-
13
- node.send(msg);
13
+
14
+ node.send([msg, null]);
14
15
  });
15
16
  }
16
17
  RED.nodes.registerType('nextLink', NextLinkNode, {});
17
18
  };
18
-
@@ -4,6 +4,7 @@
4
4
  color: '#FFC300',
5
5
  defaults: {
6
6
  name: {value: ''},
7
+ method: {value: 'PATCH'},
7
8
  entity: {value: ''},
8
9
  udo: {value: ''},
9
10
  udt: {value: ''},
@@ -84,6 +85,14 @@
84
85
  <input type="text" id="node-input-name" placeholder="Name">
85
86
  </div>
86
87
 
88
+ <div class="form-row">
89
+ <label for="node-input-type"><i class="fa fa-cog"></i> Method</label>
90
+ <select name="node-input-method" id="node-input-method">
91
+ <option value="PATCH">PATCH</option>
92
+ <option value="PUT">PUT</option>
93
+ </select>
94
+ </div>
95
+
87
96
  <div class="form-row">
88
97
  <label for="node-input-type"><i class="fa fa-cube"></i> Entity</label>
89
98
  <select name="node-input-entity" id="node-input-entity">
package/nodes/support.js CHANGED
@@ -62,15 +62,15 @@ const thickIdApi = [
62
62
  ];
63
63
 
64
64
  async function login(node, idAuth) {
65
- const flowContext = node.context().flow;
65
+ const globalContext = node.context().global;
66
66
 
67
- const host = flowContext.get(`_YOU_SapServiceLayer_${idAuth}.host`);
68
- const port = flowContext.get(`_YOU_SapServiceLayer_${idAuth}.port`);
69
- const version = flowContext.get(`_YOU_SapServiceLayer_${idAuth}.version`);
67
+ const host = globalContext.get(`_YOU_SapServiceLayer_${idAuth}.host`);
68
+ const port = globalContext.get(`_YOU_SapServiceLayer_${idAuth}.port`);
69
+ const version = globalContext.get(`_YOU_SapServiceLayer_${idAuth}.version`);
70
70
 
71
71
  const url = `https://${host}:${port}/b1s/${version}/Login`;
72
72
 
73
- const credentials = flowContext.get(`_YOU_SapServiceLayer_${idAuth}.credentials`);
73
+ const credentials = globalContext.get(`_YOU_SapServiceLayer_${idAuth}.credentials`);
74
74
  const dataString = JSON.stringify(credentials);
75
75
 
76
76
  const options = {
@@ -107,14 +107,14 @@ async function sendRequest({ node, msg, config, axios, login, options }) {
107
107
  } catch (error) {
108
108
  // Refresh headers re-login
109
109
  if (error.response && (error.response.status == 401 || error.response.status == 301)) {
110
- const flowContext = node.context().flow;
110
+ const globalCotext = node.context().global;
111
111
  // try {
112
112
  // update cookies for session timeout
113
113
  const result = await login(node, requestOptions.idAuthNode);
114
- flowContext.set(`_YOU_SapServiceLayer_${requestOptions.idAuthNode}.headers`, result.headers['set-cookie']);
114
+ globalCotext.set(`_YOU_SapServiceLayer_${requestOptions.idAuthNode}.headers`, result.headers['set-cookie']);
115
115
 
116
116
  try {
117
- const headers = flowContext.get(`_YOU_SapServiceLayer_${requestOptions.idAuthNode}.headers`).join(';');
117
+ const headers = globalCotext.get(`_YOU_SapServiceLayer_${requestOptions.idAuthNode}.headers`).join(';');
118
118
 
119
119
  requestOptions.axiosOptions.headers.Cookie = headers;
120
120
 
@@ -164,6 +164,7 @@ function generateRequest(node, msg, config, options) {
164
164
  options.isCrossJoin = options.isCrossJoin || false;
165
165
  options.isManipulate = options.isManipulate || false;
166
166
  options.isService = options.isService || false;
167
+ options.isCreateSQLQuery = options.isCreateSQLQuery || false;
167
168
  options.service = options.service || null;
168
169
  options.manipulateMethod = options.manipulateMethod || null;
169
170
 
@@ -171,6 +172,7 @@ function generateRequest(node, msg, config, options) {
171
172
 
172
173
  let rawQuery = null;
173
174
  let url;
175
+
174
176
  if (options.hasRawQuery) {
175
177
  try {
176
178
  rawQuery = eval(config.query);
@@ -180,7 +182,7 @@ function generateRequest(node, msg, config, options) {
180
182
  }
181
183
 
182
184
  let entity = config.entity;
183
- if (!entity && !options.isService) {
185
+ if (!entity && !options.isService && !options.isCreateSQLQuery && !options.isSQLQuery) {
184
186
  throw new Error('Missing entity');
185
187
  }
186
188
 
@@ -214,6 +216,13 @@ function generateRequest(node, msg, config, options) {
214
216
  url = `https://${host}:${port}/b1s/${version}/$crossjoin(${entity})`;
215
217
  }
216
218
 
219
+ if (options.isSQLQuery) {
220
+ if (!config.sqlCode) {
221
+ throw new Error('Missing sqlCode');
222
+ }
223
+ url = `https://${host}:${port}/b1s/${version}/SQLQueries('${msg[config.sqlCode]}')/List`;
224
+ }
225
+
217
226
  if (odataNextLink) {
218
227
  url = `https://${host}:${port}/b1s/${version}/${odataNextLink}`;
219
228
  }
@@ -268,6 +277,19 @@ function generateRequest(node, msg, config, options) {
268
277
  url = `https://${host}:${port}/b1s/${version}/${config.service}`;
269
278
  }
270
279
 
280
+ if (options.isCreateSQLQuery) {
281
+ if (!config.sqlCode) {
282
+ throw new Error('Missing sqlCode');
283
+ }
284
+ if (!config.sqlName) {
285
+ throw new Error('Missing sqlName');
286
+ }
287
+ if (!config.sqlText) {
288
+ throw new Error('Missing sqlText');
289
+ }
290
+ url = `https://${host}:${port}/b1s/${version}/SQLQueries`;
291
+ }
292
+
271
293
  if (rawQuery && !odataNextLink) {
272
294
  const urlOdata = buildQuery(rawQuery);
273
295
  msg.odata = urlOdata;
@@ -297,17 +319,17 @@ function generateRequest(node, msg, config, options) {
297
319
 
298
320
  function getSapParams(node, msg) {
299
321
  try {
300
- const flowContext = node.context().flow;
322
+ const globalContext = node.context().global;
301
323
 
302
324
  const idAuthNode = msg._YOU_SapServiceLayer.idAuth;
303
- const host = flowContext.get(`_YOU_SapServiceLayer_${idAuthNode}.host`);
304
- const port = flowContext.get(`_YOU_SapServiceLayer_${idAuthNode}.port`);
305
- const version = flowContext.get(`_YOU_SapServiceLayer_${idAuthNode}.version`);
325
+ const host = globalContext.get(`_YOU_SapServiceLayer_${idAuthNode}.host`);
326
+ const port = globalContext.get(`_YOU_SapServiceLayer_${idAuthNode}.port`);
327
+ const version = globalContext.get(`_YOU_SapServiceLayer_${idAuthNode}.version`);
306
328
 
307
329
  // if (!flowContext.get(`_YOU_SapServiceLayer_${idAuthNode}.headers`)) {
308
330
  // throw new Error('Authentication failed');
309
331
  // }
310
- const cookies = flowContext.get(`_YOU_SapServiceLayer_${idAuthNode}.headers`).join(';');
332
+ const cookies = globalContext.get(`_YOU_SapServiceLayer_${idAuthNode}.headers`).join(';');
311
333
 
312
334
  return { idAuthNode: idAuthNode, host: host, port: port, version: version, cookies: cookies };
313
335
  } catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yousolution/node-red-contrib-you-sap-service-layer",
3
- "version": "0.0.6",
3
+ "version": "0.1.1",
4
4
  "description": "Unofficial module SAP Service Layer for NODE-RED",
5
5
  "license": "MIT",
6
6
  "scripts": {
@@ -19,6 +19,7 @@
19
19
  ],
20
20
  "author": "Andrea Trentin <andrea.trentin@yousolution.cloud>",
21
21
  "node-red": {
22
+ "version": ">=2.0.0",
22
23
  "nodes": {
23
24
  "authenticateSap": "/nodes/authenticateSap.js",
24
25
  "listSap": "/nodes/listSap.js",
@@ -30,7 +31,9 @@
30
31
  "crossJoinSap": "/nodes/crossJoinSap.js",
31
32
  "nextLink": "/nodes/nextLink.js",
32
33
  "serviceSap": "/nodes/serviceSap.js",
33
- "manipulateEntitySap": "/nodes/manipulateEntitySap.js"
34
+ "manipulateEntitySap": "/nodes/manipulateEntitySap.js",
35
+ "createSQLQuery": "/nodes/createSQLQuery.js",
36
+ "SQLQuery": "/nodes/SQLQuery.js"
34
37
  }
35
38
  },
36
39
  "dependencies": {
@@ -144,6 +144,52 @@ describe('authenticateSap Node', () => {
144
144
  });
145
145
  });
146
146
 
147
+ it('should generic error ', (done) => {
148
+ const flow = [
149
+ {
150
+ id: 'n1',
151
+ type: 'authenticateSap',
152
+ name: 'authenticateSap',
153
+ wires: [['n2']],
154
+ z: 'flow',
155
+ rules: [{ t: 'set', p: 'payload', to: '#:(memory1)::flowValue', tot: 'flow' }],
156
+ },
157
+ { id: 'n2', type: 'helper' },
158
+ ];
159
+ helper.load(authenticateSap, flow, () => {
160
+ const n2 = helper.getNode('n2');
161
+ const n1 = helper.getNode('n1');
162
+ n1.credentials.user = 'user';
163
+ n1.credentials.password = 'password';
164
+ n1.credentials.company = 'company';
165
+
166
+ sinon.stub(Support, 'login').rejects(new Error('Custom error'));
167
+
168
+ // n1.context().flow.set(`_YOU_SapServiceLayer_${n1.id}.headers`, true, 'memory1', function (error) {
169
+ // // console.log(error);
170
+ // });
171
+
172
+ n1.receive({});
173
+
174
+ n2.on('input', (msg) => {
175
+ try {
176
+ msg.should.have.property('payload', new Error('Custom error'));
177
+ done();
178
+ } catch (err) {
179
+ done(err);
180
+ }
181
+ });
182
+
183
+ n1.on('call:error', (error) => {
184
+ try {
185
+ error.should.have.property('firstArg', new Error('Custom error'));
186
+ } catch (err) {
187
+ done(err);
188
+ }
189
+ });
190
+ });
191
+ });
192
+
147
193
  it('should have without headers connected', (done) => {
148
194
  const flow = [
149
195
  {
@@ -0,0 +1,174 @@
1
+ const should = require('should');
2
+ const helper = require('node-red-node-test-helper');
3
+ const createSQLQuery = require('../nodes/createSQLQuery');
4
+ const Context = require('../node_modules/./@node-red/runtime/lib/nodes/context/index');
5
+ const sinon = require('sinon');
6
+ const Support = require('../nodes/support');
7
+
8
+ helper.init(require.resolve('node-red'));
9
+
10
+ describe('createSQLQuery Node', () => {
11
+ beforeEach((done) => {
12
+ helper.startServer(done);
13
+ });
14
+
15
+ function initContext(done) {
16
+ Context.init({
17
+ contextStorage: {
18
+ memory0: {
19
+ module: 'memory',
20
+ },
21
+ memory1: {
22
+ module: 'memory',
23
+ },
24
+ },
25
+ });
26
+ Context.load().then(function () {
27
+ done();
28
+ });
29
+ }
30
+
31
+ afterEach((done) => {
32
+ helper
33
+ .unload()
34
+ .then(function () {
35
+ return Context.clean({ allNodes: {} });
36
+ })
37
+ .then(function () {
38
+ return Context.close();
39
+ })
40
+ .then(function () {
41
+ helper.stopServer(done);
42
+ });
43
+
44
+ // Restore the default sandbox here
45
+ sinon.restore();
46
+
47
+ // helper.unload();
48
+ // helper.stopServer(done);
49
+ });
50
+
51
+ it('should be loaded', (done) => {
52
+ const flow = [
53
+ {
54
+ id: 'n1',
55
+ type: 'createSQLQuery',
56
+ name: 'createSQLQuery',
57
+ wires: [['n2']],
58
+ z: 'flow',
59
+ rules: [{ t: 'set', p: 'payload', to: '#:(memory1)::flowValue', tot: 'flow' }],
60
+ },
61
+ ];
62
+
63
+ helper.load(createSQLQuery, flow, () => {
64
+ initContext(function () {
65
+ const n1 = helper.getNode('n1');
66
+ try {
67
+ n1.should.have.property('name', 'createSQLQuery');
68
+ done();
69
+ } catch (err) {
70
+ done(err);
71
+ }
72
+ });
73
+ });
74
+ });
75
+
76
+ it('should have correct request with data', (done) => {
77
+ const flow = [
78
+ {
79
+ id: 'n1',
80
+ type: 'createSQLQuery',
81
+ name: 'createSQLQuery',
82
+ wires: [['n2']],
83
+ z: 'flow',
84
+ sqlCode: 'sqlCode',
85
+ sqlName: 'sqlName',
86
+ sqlText: 'sqlText',
87
+ rules: [{ t: 'set', p: 'payload', to: '#:(memory1)::flowValue', tot: 'flow' }],
88
+ },
89
+ { id: 'n2', type: 'helper' },
90
+ ];
91
+ helper.load(createSQLQuery, flow, () => {
92
+ const n2 = helper.getNode('n2');
93
+ const n1 = helper.getNode('n1');
94
+
95
+ sinon.stub(Support, 'sendRequest').resolves('ok');
96
+
97
+ n1.receive({ sqlCode: 'code', sqlName: 'name', sqlText: 'text' });
98
+
99
+ n2.on('input', (msg) => {
100
+ try {
101
+ msg.should.have.property('_msgid');
102
+ msg.should.have.property('payload', 'ok');
103
+ done();
104
+ } catch (err) {
105
+ done(err);
106
+ }
107
+ });
108
+ });
109
+ });
110
+
111
+ // it('should have request without data', (done) => {
112
+ // const flow = [
113
+ // {
114
+ // id: 'n1',
115
+ // type: 'serviceSap',
116
+ // name: 'serviceSap',
117
+ // wires: [['n2']],
118
+ // z: 'flow',
119
+ // bodyPost: 'data',
120
+ // rules: [{ t: 'set', p: 'payload', to: '#:(memory1)::flowValue', tot: 'flow' }],
121
+ // },
122
+ // { id: 'n2', type: 'helper' },
123
+ // ];
124
+ // helper.load(serviceSap, flow, () => {
125
+ // const n2 = helper.getNode('n2');
126
+ // const n1 = helper.getNode('n1');
127
+
128
+ // sinon.stub(Support, 'sendRequest').resolves('ok');
129
+ // n1.receive({});
130
+
131
+ // n2.on('input', (msg) => {
132
+ // try {
133
+ // msg.should.have.property('_msgid');
134
+ // msg.should.have.property('payload', 'ok');
135
+ // done();
136
+ // } catch (err) {
137
+ // done(err);
138
+ // }
139
+ // });
140
+ // });
141
+ // });
142
+
143
+ it('should handle the error', (done) => {
144
+ const flow = [
145
+ {
146
+ id: 'n1',
147
+ type: 'createSQLQuery',
148
+ name: 'createSQLQuery',
149
+ wires: [['n2']],
150
+ z: 'flow',
151
+ sqlCode: 'sqlCode',
152
+ sqlName: 'sqlName',
153
+ sqlText: 'sqlText',
154
+ rules: [{ t: 'set', p: 'payload', to: '#:(memory1)::flowValue', tot: 'flow' }],
155
+ },
156
+ { id: 'n2', type: 'helper' },
157
+ ];
158
+ helper.load(createSQLQuery, flow, () => {
159
+ const n2 = helper.getNode('n2');
160
+ const n1 = helper.getNode('n1');
161
+
162
+ const expected = new Error('Missing mandatory params: SqlCode.');
163
+
164
+ sinon.stub(Support, 'sendRequest').rejects(expected);
165
+
166
+ n1.receive({ sqlName: 'name', sqlText: 'text' });
167
+
168
+ n1.on('call:error', (error) => {
169
+ should.deepEqual(error.args[0], expected);
170
+ done();
171
+ });
172
+ });
173
+ });
174
+ });