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 @@
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/object_action</string>
<string>action_type/object_jio_action</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_action</string> </value>
<value> <string>object_jio_action</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
......
request = context.REQUEST
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="")
return context.Base_renderForm('ComponentTool_viewLiveTestDialog')
......@@ -50,7 +50,7 @@
</item>
<item>
<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>
<key> <string>id</string> </key>
......
......@@ -75,7 +75,6 @@
<key> <string>bottom</string> </key>
<value>
<list>
<string>your_text_output</string>
<string>live_test_gadget</string>
</list>
</value>
......
......@@ -11,7 +11,9 @@
<value>
<list>
<string>editable</string>
<string>enabled</string>
<string>gadget_url</string>
<string>renderjs_extra</string>
<string>title</string>
</list>
</value>
......@@ -50,6 +52,16 @@
<key> <string>tales</string> </key>
<value>
<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>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
......@@ -61,9 +73,19 @@
<item>
<key> <string>gadget_url</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</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>
</value>
</item>
......@@ -75,6 +97,10 @@
<key> <string>editable</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_gadget_field</string> </value>
......@@ -87,6 +113,12 @@
<key> <string>gadget_url</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>renderjs_extra</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Test Watcher</string> </value>
......@@ -98,6 +130,19 @@
</pickle>
</record>
<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>
<global name="TALESMethod" module="Products.Formulator.TALESField"/>
</pickle>
......@@ -110,4 +155,17 @@
</dictionary>
</pickle>
</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>
......@@ -227,13 +227,17 @@
<key> <string>hidden</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>input_type</string> </key>
<value> <string>text</string> </value>
</item>
<item>
<key> <string>max_length</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>required</string> </key>
<value> <int>0</int> </value>
<value> <int>1</int> </value>
</item>
<item>
<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>
<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="gadget_live_tests.js" type="text/javascript"></script>
</head>
<body>
<textarea class="live_test_output" rows="30" cols="120" spellcheck="false" style="font-family: monospace"></textarea>
</body>
</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 */
(function (rJS, jIO, RSVP, window, document, FormData) {
(function (rJS, jIO, RSVP, window, FormData) {
"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) {
return this.changeState({
test_list: options.test_list,
run_only: options.run_only,
debug: options.debug,
verbose: options.verbose,
run_test_url: options.run_test_url,
read_test_url: options.read_test_url,
// Reset everything on render
render_timestamp: new Date().getTime(),
last_call: false,
data_size: 0,
test_running: false,
continue_loop: true,
paused: false
});
})
form_data.append("test_list",
document.querySelector("[name='field_your_test']").value);
form_data.append("run_only",
document.querySelector("[name='field_your_run_only']").value);
form_data.append("debug",
document.querySelector("[name='field_your_debug']").checked ===
true ? 1 : 0);
form_data.append("verbose",
document.querySelector("[name='field_your_verbose']").checked ===
true ? 1 : 0);
.onStateChange(function (modification_dict) {
if (modification_dict.hasOwnProperty('render_timestamp')) {
return this.triggerLiveTest();
}
})
// if the user scrolls in the window we do not want it to be updated.
// so set paused flag to false
function scrollFunction() {
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;
}
}
.declareJob('triggerLiveTest', function () {
var form_data = new FormData(),
gadget = this;
data_textarea.onscroll = scrollFunction;
form_data.append("test_list", gadget.state.test_list);
form_data.append("run_only", gadget.state.run_only);
form_data.append("debug", gadget.state.debug);
form_data.append("verbose", gadget.state.verbose);
rJS(window).declareService(function () {
var queue = new RSVP.Queue();
// Reset textarea content
gadget.element.querySelector('textarea').value = '';
function launchLiveTest() {
queue.push(function () {
return jIO.util.ajax({
type: "POST",
url: my_url_run_test,
data: form_data
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",
url: gadget.state.run_test_url,
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 () {
var state_dict = {test_running: false};
// set continue_loop to false ONLY IF the test is not paused.
// Otherwise it will be set when user scrolls to the end
if (!gadget.state.paused) {
state_dict.continue_loop = false;
}
return gadget.changeState(state_dict);
})
.push(undefined, function (error) {
console.warn("Error launching live tests", error);
throw error;
});
}).push(function () {
tests_still_running = false;
// set continue_loop to false ONLY IF the test is not paused.
// Otherwise it will be set when user scrolls to the end
if (!paused) {
continue_loop = false;
}
}, function (error) {
console.error("Error launching live tests", error);
});
}
return queue.push(function () {
return launchLiveTest();
});
}).declareService(function () {
var queue = new RSVP.Queue();
})
function getLiveTestOutput() {
queue.push(function () {
return jIO.util.ajax({
type: "GET",
url: my_url_read_test
});
}).push(function (evt) {
var data = evt.target.response;
// cut the characters that are already presented
data = data.substring(data_size);
if ((!paused || last_call) && data.length !== undefined) {
// to put the data in the correct place
data_size = data_size + data.length;
// add the new data
data_textarea.value = data_textarea.value + data;
data_textarea.scrollTop = data_textarea.scrollHeight;
}
return RSVP.delay(1000);
}, function (error) {
console.error("Error refreshing live test output", error);
}).push(function () {
if (continue_loop) {
return getLiveTestOutput();
.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;
}
if (!continue_loop) {
if (!last_call) {
last_call = true;
return getLiveTestOutput();
state_dict.last_call = true;
}
return new RSVP.Queue()
.push(function () {
return jIO.util.ajax({
type: "GET",
url: gadget.state.read_test_url
});
}).push(function (evt) {
var data = evt.target.response;
// cut the characters that are already presented
data = data.substring(gadget.state.data_size);
if ((!gadget.state.paused) && data.length !== undefined) {
// to put the data in the correct place
state_dict.data_size = gadget.state.data_size + data.length;
// add the new data
data_textarea.value = data_textarea.value + data;
data_textarea.scrollTop = data_textarea.scrollHeight;
}
}
});
}
return queue.push(function () {
// 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));
\ No newline at end of file
}, function (error) {
// If last call failed, retry
state_dict.last_call = false;
console.warn("Error refreshing live test output", error);
})
.push(function () {
return gadget.changeState(state_dict);
});
}, 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 gadget.changeState(state_dict);
});
}(rJS, jIO, RSVP, window, FormData));
\ 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