Commit b22e7b69 authored by Romain Courteaud's avatar Romain Courteaud

[erp5_core] Make live test gadget reusable

Remove all globals to allow multiple live tests running on the same page.
Stop reading the test output if the browser tab doesn't have the focus.
Stop hardcoding the URL in the javascript.
Stop hardcoding other fields path in the javascript (+ embed the textarea in the gadget).

Propagate all parameters via formulator.
It will allow to use this gadget from another context document.

Run live test from the RJS UI
parent 34e4ada5
...@@ -16,13 +16,13 @@ ...@@ -16,13 +16,13 @@
<key> <string>categories</string> </key> <key> <string>categories</string> </key>
<value> <value>
<tuple> <tuple>
<string>action_type/object_action</string> <string>action_type/object_jio_action</string>
</tuple> </tuple>
</value> </value>
</item> </item>
<item> <item>
<key> <string>category</string> </key> <key> <string>category</string> </key>
<value> <string>object_action</string> </value> <value> <string>object_jio_action</string> </value>
</item> </item>
<item> <item>
<key> <string>condition</string> </key> <key> <string>condition</string> </key>
......
request = context.REQUEST return context.Base_renderForm('ComponentTool_viewLiveTestDialog')
if request.get('form', None) is not None:
context.REQUEST['form']['field_your_text_output']=""
context.REQUEST['form']['text_output']=""
return context.ComponentTool_viewLiveTestDialog(
text_output="")
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
</item> </item>
<item> <item>
<key> <string>_params</string> </key> <key> <string>_params</string> </key>
<value> <string>debug=None,verbose=None,test=None,run_only=None,**kw</string> </value> <value> <string>**kw</string> </value>
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
......
...@@ -75,7 +75,6 @@ ...@@ -75,7 +75,6 @@
<key> <string>bottom</string> </key> <key> <string>bottom</string> </key>
<value> <value>
<list> <list>
<string>your_text_output</string>
<string>live_test_gadget</string> <string>live_test_gadget</string>
</list> </list>
</value> </value>
......
...@@ -11,7 +11,9 @@ ...@@ -11,7 +11,9 @@
<value> <value>
<list> <list>
<string>editable</string> <string>editable</string>
<string>enabled</string>
<string>gadget_url</string> <string>gadget_url</string>
<string>renderjs_extra</string>
<string>title</string> <string>title</string>
</list> </list>
</value> </value>
...@@ -50,6 +52,16 @@ ...@@ -50,6 +52,16 @@
<key> <string>tales</string> </key> <key> <string>tales</string> </key>
<value> <value>
<dictionary> <dictionary>
<item>
<key> <string>editable</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item> <item>
<key> <string>field_id</string> </key> <key> <string>field_id</string> </key>
<value> <string></string> </value> <value> <string></string> </value>
...@@ -61,9 +73,19 @@ ...@@ -61,9 +73,19 @@
<item> <item>
<key> <string>gadget_url</string> </key> <key> <string>gadget_url</string> </key>
<value> <value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent> <persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value> </value>
</item> </item>
<item>
<key> <string>renderjs_extra</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary> </dictionary>
</value> </value>
</item> </item>
...@@ -75,6 +97,10 @@ ...@@ -75,6 +97,10 @@
<key> <string>editable</string> </key> <key> <string>editable</string> </key>
<value> <int>0</int> </value> <value> <int>0</int> </value>
</item> </item>
<item>
<key> <string>enabled</string> </key>
<value> <int>1</int> </value>
</item>
<item> <item>
<key> <string>field_id</string> </key> <key> <string>field_id</string> </key>
<value> <string>my_gadget_field</string> </value> <value> <string>my_gadget_field</string> </value>
...@@ -87,6 +113,12 @@ ...@@ -87,6 +113,12 @@
<key> <string>gadget_url</string> </key> <key> <string>gadget_url</string> </key>
<value> <string></string> </value> <value> <string></string> </value>
</item> </item>
<item>
<key> <string>renderjs_extra</string> </key>
<value>
<list/>
</value>
</item>
<item> <item>
<key> <string>title</string> </key> <key> <string>title</string> </key>
<value> <string>Test Watcher</string> </value> <value> <string>Test Watcher</string> </value>
...@@ -98,6 +130,19 @@ ...@@ -98,6 +130,19 @@
</pickle> </pickle>
</record> </record>
<record id="2" aka="AAAAAAAAAAI="> <record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="TALESMethod" module="Products.Formulator.TALESField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>python: here.REQUEST.get(\'field_your_test\', \'\')</string> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle> <pickle>
<global name="TALESMethod" module="Products.Formulator.TALESField"/> <global name="TALESMethod" module="Products.Formulator.TALESField"/>
</pickle> </pickle>
...@@ -110,4 +155,17 @@ ...@@ -110,4 +155,17 @@
</dictionary> </dictionary>
</pickle> </pickle>
</record> </record>
<record id="4" aka="AAAAAAAAAAQ=">
<pickle>
<global name="TALESMethod" module="Products.Formulator.TALESField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>python: {\'run_test_url\': here.absolute_url() + \'/runLiveTest\', \'read_test_url\': here.absolute_url() + \'/readTestOutput\', \'test_list\': here.REQUEST.get(\'field_your_test\', \'\'), \'run_only\': here.REQUEST.get(\'field_your_run_only\', \'\'), \'debug\': int(here.REQUEST.get(\'field_your_debug\', \'\') == \'on\'), \'verbose\': int(here.REQUEST.get(\'field_your_verbose\', \'\') == \'on\')}</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData> </ZopeData>
...@@ -227,13 +227,17 @@ ...@@ -227,13 +227,17 @@
<key> <string>hidden</string> </key> <key> <string>hidden</string> </key>
<value> <int>0</int> </value> <value> <int>0</int> </value>
</item> </item>
<item>
<key> <string>input_type</string> </key>
<value> <string>text</string> </value>
</item>
<item> <item>
<key> <string>max_length</string> </key> <key> <string>max_length</string> </key>
<value> <string></string> </value> <value> <string></string> </value>
</item> </item>
<item> <item>
<key> <string>required</string> </key> <key> <string>required</string> </key>
<value> <int>0</int> </value> <value> <int>1</int> </value>
</item> </item>
<item> <item>
<key> <string>title</string> </key> <key> <string>title</string> </key>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>css_class</string>
<string>height</string>
<string>title</string>
<string>width</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>your_text_output</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>css_class</string> </key>
<value> <string>live_test_output</string> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_view_mode_raw_text_content</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>height</string> </key>
<value> <int>30</int> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Test Output</string> </value>
</item>
<item>
<key> <string>width</string> </key>
<value> <int>120</int> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<!DOCTYPE html>
<html> <html>
<head> <head>
<title>Live test gadget</title>
<script src="rsvp.js" type="text/javascript"></script>
<script src="renderjs.js" type="text/javascript"></script>
<script src="jio.js" type="text/javascript"></script> <script src="jio.js" type="text/javascript"></script>
<script src="gadget_live_tests.js" type="text/javascript"></script> <script src="gadget_live_tests.js" type="text/javascript"></script>
</head> </head>
<body> <body>
<textarea class="live_test_output" rows="30" cols="120" spellcheck="false" style="font-family: monospace"></textarea>
</body> </body>
</html> </html>
\ No newline at end of file
/*global window, rJS, jIO, RSVP, location, document, FormData, console */ /*global window, rJS, jIO, RSVP, location, FormData, console */
/*jslint indent: 2, maxlen: 80, nomen: true */ /*jslint indent: 2, maxlen: 80, nomen: true */
(function (rJS, jIO, RSVP, window, document, FormData) { (function (rJS, jIO, RSVP, window, FormData) {
"use strict"; "use strict";
var my_url_run_test = document.baseURI + 'runLiveTest',
my_url_read_test = document.baseURI + 'readTestOutput',
paused = false,
data_textarea =
document.querySelector("[name='field_your_text_output']"),
continue_loop = true,
tests_still_running = true,
last_call = false,
data_size = 0,
form_data = new FormData();
data_textarea.value = ""; rJS(window)
.declareMethod('render', function (options) {
form_data.append("test_list", return this.changeState({
document.querySelector("[name='field_your_test']").value); test_list: options.test_list,
form_data.append("run_only", run_only: options.run_only,
document.querySelector("[name='field_your_run_only']").value); debug: options.debug,
form_data.append("debug", verbose: options.verbose,
document.querySelector("[name='field_your_debug']").checked === run_test_url: options.run_test_url,
true ? 1 : 0); read_test_url: options.read_test_url,
form_data.append("verbose", // Reset everything on render
document.querySelector("[name='field_your_verbose']").checked === render_timestamp: new Date().getTime(),
true ? 1 : 0); last_call: false,
data_size: 0,
test_running: false,
continue_loop: true,
paused: false
});
})
// if the user scrolls in the window we do not want it to be updated. .onStateChange(function (modification_dict) {
// so set paused flag to false if (modification_dict.hasOwnProperty('render_timestamp')) {
function scrollFunction() { return this.triggerLiveTest();
paused = (data_textarea.scrollHeight - data_textarea.scrollTop) >
(data_textarea.clientHeight + 1);
// if the service was paused when the tests are finished,
// set continue_loop to false
if (!paused && !tests_still_running) {
continue_loop = false;
}
} }
})
data_textarea.onscroll = scrollFunction; .declareJob('triggerLiveTest', function () {
var form_data = new FormData(),
gadget = this;
rJS(window).declareService(function () { form_data.append("test_list", gadget.state.test_list);
var queue = new RSVP.Queue(); form_data.append("run_only", gadget.state.run_only);
form_data.append("debug", gadget.state.debug);
form_data.append("verbose", gadget.state.verbose);
function launchLiveTest() { // Reset textarea content
queue.push(function () { gadget.element.querySelector('textarea').value = '';
return jIO.util.ajax({
return new RSVP.Queue()
.push(function () {
return RSVP.all([
// Trigger the test.
// It is considered as running while the ajax query is not over
jIO.util.ajax({
type: "POST", type: "POST",
url: my_url_run_test, url: gadget.state.run_test_url,
data: form_data data: form_data
}); }),
// a delay of 2 seconds so the test can be launched
// before results are read
RSVP.Queue()
.push(function () {
return RSVP.delay(2000);
})
.push(function () {
return gadget.changeState({test_running: true});
})
]);
}).push(function () { }).push(function () {
tests_still_running = false; var state_dict = {test_running: false};
// set continue_loop to false ONLY IF the test is not paused. // set continue_loop to false ONLY IF the test is not paused.
// Otherwise it will be set when user scrolls to the end // Otherwise it will be set when user scrolls to the end
if (!paused) { if (!gadget.state.paused) {
continue_loop = false; state_dict.continue_loop = false;
} }
}, function (error) { return gadget.changeState(state_dict);
console.error("Error launching live tests", error); })
.push(undefined, function (error) {
console.warn("Error launching live tests", error);
throw error;
}); });
})
.onLoop(function () {
var data_textarea = this.element.querySelector('textarea'),
gadget = this,
state_dict = {};
if (gadget.state.paused) {
return;
}
if (!gadget.state.continue_loop) {
if (gadget.state.last_call) {
// Stop reading test output
return;
}
state_dict.last_call = true;
} }
return queue.push(function () {
return launchLiveTest();
});
}).declareService(function () {
var queue = new RSVP.Queue();
function getLiveTestOutput() { return new RSVP.Queue()
queue.push(function () { .push(function () {
return jIO.util.ajax({ return jIO.util.ajax({
type: "GET", type: "GET",
url: my_url_read_test url: gadget.state.read_test_url
}); });
}).push(function (evt) { }).push(function (evt) {
var data = evt.target.response; var data = evt.target.response;
// cut the characters that are already presented // cut the characters that are already presented
data = data.substring(data_size); data = data.substring(gadget.state.data_size);
if ((!paused || last_call) && data.length !== undefined) { if ((!gadget.state.paused) && data.length !== undefined) {
// to put the data in the correct place // to put the data in the correct place
data_size = data_size + data.length; state_dict.data_size = gadget.state.data_size + data.length;
// add the new data // add the new data
data_textarea.value = data_textarea.value + data; data_textarea.value = data_textarea.value + data;
data_textarea.scrollTop = data_textarea.scrollHeight; data_textarea.scrollTop = data_textarea.scrollHeight;
} }
return RSVP.delay(1000);
}, function (error) { }, function (error) {
console.error("Error refreshing live test output", error); // If last call failed, retry
}).push(function () { state_dict.last_call = false;
if (continue_loop) { console.warn("Error refreshing live test output", error);
return getLiveTestOutput(); })
} .push(function () {
if (!continue_loop) { return gadget.changeState(state_dict);
if (!last_call) {
last_call = true;
return getLiveTestOutput();
}
}
}); });
}, 1000)
.onEvent('scroll', function scrollFunction() {
var data_textarea = this.element.querySelector('textarea'),
state_dict = {},
gadget = this;
// if the user scrolls in the window we do not want it to be updated.
// so set paused flag to false
state_dict.paused =
(data_textarea.scrollHeight - data_textarea.scrollTop) >
(data_textarea.clientHeight + 1);
// if the service was paused when the tests are finished,
// set continue_loop to false
if (!gadget.state.paused && !gadget.state.test_running) {
state_dict.continue_loop = false;
} }
return queue.push(function () { return gadget.changeState(state_dict);
// a delay of 2 seconds so the test can be launched
// before results are read
return RSVP.delay(2000);
}).push(getLiveTestOutput());
}); });
}(rJS, jIO, RSVP, window, document, FormData)); }(rJS, jIO, RSVP, window, FormData));
\ No newline at end of file \ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment