Liberate ClientDebug classes
This is a separate program of sorts that sits alongside and hooks the Client. * src/client/debug/AssertionClientDebugTab.js: Add class. * src/client/debug/BucketClientDebugTab.js: Add class. * src/client/debug/CalcClientDebugTab.js: Add class. * src/client/debug/ClassifyClientDebugTab.js: Add class. * src/client/debug/ClientDebug.js: Add class. * src/client/debug/ClientDebugDialog.js: Add class. * src/client/debug/ClientDebugTab.js: Add interface.master
parent
dd7c2760f4
commit
e7700e8b69
|
@ -0,0 +1,385 @@
|
||||||
|
/**
|
||||||
|
* Contains AssertionClientDebugTab class
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017 LoVullo Associates, Inc.
|
||||||
|
*
|
||||||
|
* This file is part of the Liza Data Collection Framework
|
||||||
|
*
|
||||||
|
* Liza is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var Class = require( 'easejs' ).Class,
|
||||||
|
|
||||||
|
ClientDebugTab = require( './ClientDebugTab' )
|
||||||
|
;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Monitors client-side assertions
|
||||||
|
*/
|
||||||
|
module.exports = Class( 'AssertionClientDebugTab' )
|
||||||
|
.implement( ClientDebugTab )
|
||||||
|
.extend(
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Client being monitored
|
||||||
|
* @type {Client}
|
||||||
|
*/
|
||||||
|
'private _client': null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current log index
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
'private _logIndex': 0,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Table holding assertion log entries
|
||||||
|
* @type {jQuery}
|
||||||
|
*/
|
||||||
|
'private _$table': null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to paint timeout timer
|
||||||
|
* @type {?number}
|
||||||
|
*/
|
||||||
|
'private _paintTimeout': null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event stack
|
||||||
|
* @type {Array.<Arguments|Array>}
|
||||||
|
*/
|
||||||
|
'private _stack': [],
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve tab title
|
||||||
|
*
|
||||||
|
* @return {string} tab title
|
||||||
|
*/
|
||||||
|
'public getTitle': function()
|
||||||
|
{
|
||||||
|
return 'Assertions';
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve tab content
|
||||||
|
*
|
||||||
|
* @param {Client} client active client being debugged
|
||||||
|
* @param {StagingBucket} bucket bucket to reference for data
|
||||||
|
*
|
||||||
|
* @return {jQuery} tab content
|
||||||
|
*/
|
||||||
|
'public getContent': function( client, bucket )
|
||||||
|
{
|
||||||
|
// cut down on argument list
|
||||||
|
this._client = client;
|
||||||
|
|
||||||
|
return this._createAssertionsContent();
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create tab content
|
||||||
|
*
|
||||||
|
* @return {jQuery} tab content
|
||||||
|
*/
|
||||||
|
'private _createAssertionsContent': function()
|
||||||
|
{
|
||||||
|
var _self = this;
|
||||||
|
|
||||||
|
this._hookAssertEvent();
|
||||||
|
this._hookTriggerEvent();
|
||||||
|
|
||||||
|
return $( '<div> ' )
|
||||||
|
.append( $( '<p>' )
|
||||||
|
.text(
|
||||||
|
"Below is a list of all assertions performed (in real " +
|
||||||
|
"time)."
|
||||||
|
)
|
||||||
|
.append( $( '<button>' )
|
||||||
|
.text( 'Clear' )
|
||||||
|
.click( function()
|
||||||
|
{
|
||||||
|
return _self._clearTable();
|
||||||
|
} )
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.append( this._getAssertionsLegend() )
|
||||||
|
.append( this._getAssertionsTable() )
|
||||||
|
;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Monitor assertions
|
||||||
|
*
|
||||||
|
* Each time an assertion occurs, it will be added to a stack (since the
|
||||||
|
* events will occur in reverse order). Once the assertion depth reaches
|
||||||
|
* zero, it will clear the stack and output each of the assertions/events.
|
||||||
|
*
|
||||||
|
* @return {undefined}
|
||||||
|
*/
|
||||||
|
'private _hookAssertEvent': function()
|
||||||
|
{
|
||||||
|
var _self = this;
|
||||||
|
|
||||||
|
this._client.program.on( 'assert', function()
|
||||||
|
{
|
||||||
|
// the hook needs to be refactored; too many arguments
|
||||||
|
var depth = arguments[ 7 ];
|
||||||
|
|
||||||
|
// add to the stack so that we can output the assertion and its
|
||||||
|
// subassertions once we reach the root node (this trigger is
|
||||||
|
// called in reverse order, since we don't know the end result
|
||||||
|
// of a parent assertion until we know the results of its
|
||||||
|
// children)
|
||||||
|
_self._stack.push( arguments );
|
||||||
|
if ( depth > 0 )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// our depth is 0; output the log data
|
||||||
|
_self._processStack();
|
||||||
|
} );
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Monitor triggers
|
||||||
|
*
|
||||||
|
* @return {undefined}
|
||||||
|
*/
|
||||||
|
'private _hookTriggerEvent': function()
|
||||||
|
{
|
||||||
|
var _self = this;
|
||||||
|
|
||||||
|
this._client.on( 'trigger', function( event_name, data )
|
||||||
|
{
|
||||||
|
_self._stack.push( [ 'trigger', event_name, data ] );
|
||||||
|
} );
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process stack, appending data to log table
|
||||||
|
*
|
||||||
|
* @return {undefined}
|
||||||
|
*/
|
||||||
|
'private _processStack': function()
|
||||||
|
{
|
||||||
|
var _self = this,
|
||||||
|
item;
|
||||||
|
|
||||||
|
while ( item = this._stack.pop() )
|
||||||
|
{
|
||||||
|
if ( item[ 0 ] === 'trigger' )
|
||||||
|
{
|
||||||
|
_self._appendTrigger.apply( _self, item );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_self._appendAssertion.apply( _self, item );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear all results from the assertions log table
|
||||||
|
*
|
||||||
|
* @return {boolean} true (to prevent navigation)
|
||||||
|
*/
|
||||||
|
'private _clearTable': function()
|
||||||
|
{
|
||||||
|
// remove all records and reset counter
|
||||||
|
this._getAssertionsTable().find( 'tbody tr' ).remove();
|
||||||
|
this._logIndex = 0;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate table to contain assertion log
|
||||||
|
*
|
||||||
|
* @return {jQuery} generated log table
|
||||||
|
*/
|
||||||
|
'private _getAssertionsTable': function()
|
||||||
|
{
|
||||||
|
return this._$table = ( this._$table || ( function()
|
||||||
|
{
|
||||||
|
return $( '<table>' )
|
||||||
|
.attr( 'id', 'assertions-table' )
|
||||||
|
.append( $( '<thead>' ).append( $( '<tr>' )
|
||||||
|
.append( $( '<th>' ).text( '#' ) )
|
||||||
|
.append( $( '<th>' ).text( 'question_id' ) )
|
||||||
|
.append( $( '<th>' ).text( 'method' ) )
|
||||||
|
.append( $( '<th>' ).text( 'expected' ) )
|
||||||
|
.append( $( '<th>' ).text( 'given' ) )
|
||||||
|
.append( $( '<th>' ).text( 'thisresult' ) )
|
||||||
|
.append( $( '<th>' ).text( 'result' ) )
|
||||||
|
) )
|
||||||
|
.append( $( '<tbody>' ) )
|
||||||
|
;
|
||||||
|
} )() );
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Append an assertion to the log
|
||||||
|
*
|
||||||
|
* XXX: This needs refactoring (rather, the hook does)
|
||||||
|
*
|
||||||
|
* @param {string} assertion assertion method
|
||||||
|
* @param {string} qid question id
|
||||||
|
* @param {Array} expected expected data
|
||||||
|
* @param {Array} given data given to the assertion
|
||||||
|
* @param {boolean} thisresult result before sub-assertions
|
||||||
|
* @param {boolean} result result after sub-assertions
|
||||||
|
* @param {boolean} record whether failures will be recorded as such
|
||||||
|
* @param {number} depth sub-assertion depth
|
||||||
|
*
|
||||||
|
* @return {undefined}
|
||||||
|
*/
|
||||||
|
'private _appendAssertion': function(
|
||||||
|
assertion, qid, expected, given, thisresult, result, record, depth
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this._getAssertionsTable().find( 'tbody' ).append( $( '<tr>' )
|
||||||
|
.addClass( ( result ) ? 'success' : 'failure' )
|
||||||
|
.addClass( ( thisresult ) ? 'thissuccess' : 'thisfailure' )
|
||||||
|
.addClass( ( record ) ? 'recorded' : '' )
|
||||||
|
.addClass( 'adepth' + depth )
|
||||||
|
.append( $( '<td>' )
|
||||||
|
.text( this._logIndex++ )
|
||||||
|
.addClass( 'index' )
|
||||||
|
)
|
||||||
|
.append( $( '<td>' ).append( $( '<span>' )
|
||||||
|
.attr( 'title', ( 'Depth: ' + depth ) )
|
||||||
|
.html(
|
||||||
|
Array( ( depth + 1 ) * 4 ).join( ' ') + qid
|
||||||
|
)
|
||||||
|
) )
|
||||||
|
.append( $( '<td>' ).text( assertion.getName() ) )
|
||||||
|
.append( $( '<td>' ).text( JSON.stringify( expected ) ) )
|
||||||
|
.append( $( '<td>' ).text( JSON.stringify( given ) ) )
|
||||||
|
.append( $( '<td>' ).text( ''+( thisresult ) ) )
|
||||||
|
.append( $( '<td>' ).text(
|
||||||
|
''+( result ) + ( ( record ) ? '' : '*' )
|
||||||
|
) )
|
||||||
|
);
|
||||||
|
|
||||||
|
// let the system know that the paint line should be drawn
|
||||||
|
this._paintLine();
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
'private _appendTrigger': function( _, event_name, data )
|
||||||
|
{
|
||||||
|
this._getAssertionsTable().find( 'tbody' ).append( $( '<tr>' )
|
||||||
|
.addClass( 'trigger' )
|
||||||
|
.append( $( '<td>' ).text( ' ' ) )
|
||||||
|
.append( $( '<td>' ).text( event_name ) )
|
||||||
|
.append( $( '<td>' )
|
||||||
|
.attr( 'colspan', 6 )
|
||||||
|
.text( JSON.stringify( data ) )
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
this._paintLine();
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate assertions legend
|
||||||
|
*
|
||||||
|
* @return {jQuery} div containing legend
|
||||||
|
*/
|
||||||
|
'private _getAssertionsLegend': function()
|
||||||
|
{
|
||||||
|
return $( '<div>' )
|
||||||
|
.attr( 'id', 'assert-legend' )
|
||||||
|
.append(
|
||||||
|
$( '<div>' )
|
||||||
|
.addClass( 'assert-legend-item' )
|
||||||
|
.addClass( 'root' )
|
||||||
|
)
|
||||||
|
.append( "<span>Root Assertion</span>" )
|
||||||
|
.append(
|
||||||
|
$( '<div>' )
|
||||||
|
.addClass( 'assert-legend-item' )
|
||||||
|
.text( '*' )
|
||||||
|
)
|
||||||
|
.append( "<span>Unrecorded</span>" )
|
||||||
|
.append(
|
||||||
|
$( '<div>' )
|
||||||
|
.addClass( 'assert-legend-item' )
|
||||||
|
.addClass( 'trigger' )
|
||||||
|
)
|
||||||
|
.append( "<span>Trigger</span>" )
|
||||||
|
.append(
|
||||||
|
$( '<div>' )
|
||||||
|
.addClass( 'assert-legend-item' )
|
||||||
|
.addClass( 'paint' )
|
||||||
|
)
|
||||||
|
.append( "<span>Paint</span>" )
|
||||||
|
.append( '<br />' )
|
||||||
|
.append(
|
||||||
|
$( '<div>' )
|
||||||
|
.addClass( 'assert-legend-item' )
|
||||||
|
.addClass( 'failure' )
|
||||||
|
)
|
||||||
|
.append( "<span>Failure</span>" )
|
||||||
|
.append(
|
||||||
|
$( '<div>' )
|
||||||
|
.addClass( 'assert-legend-item' )
|
||||||
|
.addClass( 'unrecorded' )
|
||||||
|
)
|
||||||
|
.append(
|
||||||
|
"<span>Failure, but suceeded by subassertion or " +
|
||||||
|
"unrecorded</span>"
|
||||||
|
)
|
||||||
|
;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw paint line
|
||||||
|
*
|
||||||
|
* The paint line represents when a paint operation was able to occur. This
|
||||||
|
* allows us to see how many bucket values were updated between paints,
|
||||||
|
* which (depending on what hooks the bucket) could have negative
|
||||||
|
* consequences on performance.
|
||||||
|
*
|
||||||
|
* This is simple to detect - simply use a setTimeout() and it will execute
|
||||||
|
* after the stack has cleared and the page has been painted.
|
||||||
|
*
|
||||||
|
* @return {undefined}
|
||||||
|
*/
|
||||||
|
'private _paintLine': function()
|
||||||
|
{
|
||||||
|
var _self = this;
|
||||||
|
|
||||||
|
this._paintTimeout && clearTimeout( this._paintTimeout );
|
||||||
|
this._paintTimeout = setTimeout( function()
|
||||||
|
{
|
||||||
|
_self._getAssertionsTable().find( 'tr:last' )
|
||||||
|
.addClass( 'last-pre-paint' );
|
||||||
|
}, 25 );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
|
@ -0,0 +1,628 @@
|
||||||
|
/**
|
||||||
|
* Contains BucketClientDebugTab class
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017 LoVullo Associates, Inc.
|
||||||
|
*
|
||||||
|
* This file is part of the Liza Data Collection Framework
|
||||||
|
*
|
||||||
|
* Liza is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var Class = require( 'easejs' ).Class,
|
||||||
|
EventEmitter = require( 'events' ).EventEmitter,
|
||||||
|
ClientDebugTab = require( './ClientDebugTab' );
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides additional information and manipulation options for buckets
|
||||||
|
*/
|
||||||
|
module.exports = Class( 'BucketClientDebugTab' )
|
||||||
|
.implement( ClientDebugTab )
|
||||||
|
.extend( EventEmitter,
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Current index of bucket monitor
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
'private _bmonIndex': 0,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Table representing the bucket monitor
|
||||||
|
* @type {jQuery}
|
||||||
|
*/
|
||||||
|
'private _$bmonTable': null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Input box used to filter the bmonTable
|
||||||
|
* @type {jQuery}
|
||||||
|
*/
|
||||||
|
'private _$bmonTableFilter': null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to paint timeout timer
|
||||||
|
* @type {?number}
|
||||||
|
*/
|
||||||
|
'private _paintTimeout': null,
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve tab title
|
||||||
|
*
|
||||||
|
* @return {string} tab title
|
||||||
|
*/
|
||||||
|
'public getTitle': function()
|
||||||
|
{
|
||||||
|
return 'Bucket';
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve tab content
|
||||||
|
*
|
||||||
|
* @param {Client} client active client being debugged
|
||||||
|
* @param {StagingBucket} bucket bucket to reference for data
|
||||||
|
*
|
||||||
|
* @return {jQuery|string} tab content
|
||||||
|
*/
|
||||||
|
'public getContent': function( client, bucket )
|
||||||
|
{
|
||||||
|
return this._$content =
|
||||||
|
( this._content || this._createBaseContent( bucket ) );
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create content
|
||||||
|
*
|
||||||
|
* @param {StagingBucket} staging bucket
|
||||||
|
*
|
||||||
|
* @return {jQuery} div containing tab content
|
||||||
|
*/
|
||||||
|
'private _createBaseContent': function( staging )
|
||||||
|
{
|
||||||
|
this._getBucketMonitorTable();
|
||||||
|
this._hookBucket( staging );
|
||||||
|
|
||||||
|
return $( '<div>' )
|
||||||
|
.append( this._getHeader() )
|
||||||
|
.append( $( '<fieldset>' )
|
||||||
|
.append( $( '<legend>' ).text( "Staging Bucket" ) )
|
||||||
|
.append( $( '<p>' ).text(
|
||||||
|
"The staging bucket contains modified data that has not " +
|
||||||
|
"yet been committed to the quote data bucket in " +
|
||||||
|
"addition to the actual quote data. This " +
|
||||||
|
"bucket will be passed to assertions."
|
||||||
|
) )
|
||||||
|
.append( this._getStagingButtons( staging ) )
|
||||||
|
.append( this._getBucketMonitorLegend() )
|
||||||
|
.append( this._getBucketMonitorTable() )
|
||||||
|
)
|
||||||
|
;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate tab header text
|
||||||
|
*
|
||||||
|
* @return {jQuery} div containing header paragraphs
|
||||||
|
*/
|
||||||
|
'private _getHeader': function()
|
||||||
|
{
|
||||||
|
var _self = this;
|
||||||
|
|
||||||
|
return $( '<div>' )
|
||||||
|
.append( $( '<p>' ).text(
|
||||||
|
"All quote data is contained within the buckets. The Client " +
|
||||||
|
"exists purely to populate the quote data bucket."
|
||||||
|
) )
|
||||||
|
.append( $( '<p>' ).html(
|
||||||
|
"<strong>N.B.</strong> This tab does not currently bind " +
|
||||||
|
"to the new data bucket on quote change. Please refresh " +
|
||||||
|
"the page when changing quotes if you wish to use this tab."
|
||||||
|
) )
|
||||||
|
.append( $( '<p>' )
|
||||||
|
.append( $( '<input>' )
|
||||||
|
.attr( 'type', 'checkbox' )
|
||||||
|
.attr( 'id', 'field-overlay' )
|
||||||
|
.change( function()
|
||||||
|
{
|
||||||
|
// trigger toggle event
|
||||||
|
_self.emit( 'fieldOverlayToggle',
|
||||||
|
$( this ).is( ':checked' )
|
||||||
|
);
|
||||||
|
} )
|
||||||
|
)
|
||||||
|
.append( $( '<label>' )
|
||||||
|
.attr( 'for', 'field-overlay' )
|
||||||
|
.text( 'Render field overlays (requires modern browser)' )
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate staging bucket buttons
|
||||||
|
*
|
||||||
|
* TODO: This could use further refactoring.
|
||||||
|
*
|
||||||
|
* @param {StagingBucket} staging bucket
|
||||||
|
*
|
||||||
|
* @return {jQuery} div containing buttons
|
||||||
|
*/
|
||||||
|
'private _getStagingButtons': function( staging )
|
||||||
|
{
|
||||||
|
var _self = this;
|
||||||
|
|
||||||
|
return $( '<div>' )
|
||||||
|
.append( $( '<button>' )
|
||||||
|
.text( 'Data To Console' )
|
||||||
|
.click( function()
|
||||||
|
{
|
||||||
|
console.log( staging.getData() );
|
||||||
|
} )
|
||||||
|
)
|
||||||
|
.append( $( '<button>' )
|
||||||
|
.text( 'Diff To Console' )
|
||||||
|
.click( function()
|
||||||
|
{
|
||||||
|
console.log( staging.getDiff() );
|
||||||
|
} )
|
||||||
|
)
|
||||||
|
.append( $( '<button>' )
|
||||||
|
.text( 'Commit' )
|
||||||
|
.click( function()
|
||||||
|
{
|
||||||
|
staging.commit();
|
||||||
|
console.info(
|
||||||
|
'Commited staged changes to data bucket'
|
||||||
|
);
|
||||||
|
} )
|
||||||
|
)
|
||||||
|
.append( $( '<button>' )
|
||||||
|
.text( 'Editor' )
|
||||||
|
.click( function()
|
||||||
|
{
|
||||||
|
_self.showBucketEditor(
|
||||||
|
staging,
|
||||||
|
function( name, value )
|
||||||
|
{
|
||||||
|
var data = {};
|
||||||
|
data[ name ] = value;
|
||||||
|
|
||||||
|
// set the data
|
||||||
|
staging.setValues( data );
|
||||||
|
console.log( "%s updated", name, data );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} )
|
||||||
|
)
|
||||||
|
.append( $( '<button>' )
|
||||||
|
.text( 'Clear Monitor' )
|
||||||
|
.click( function()
|
||||||
|
{
|
||||||
|
return _self._clearTable();
|
||||||
|
} )
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear all results from the assertions log table
|
||||||
|
*
|
||||||
|
* @return {boolean} true (to prevent navigation)
|
||||||
|
*/
|
||||||
|
'private _clearTable': function()
|
||||||
|
{
|
||||||
|
// clear monitor and reset count
|
||||||
|
this._$bmonTable.find( 'tbody tr' ).remove();
|
||||||
|
this._bmonIndex = 0;
|
||||||
|
|
||||||
|
// re-filter table
|
||||||
|
this._filterBucketTable();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate bucket monitor table or return existing table
|
||||||
|
*
|
||||||
|
* @return {jQuery} bucket monitor table
|
||||||
|
*/
|
||||||
|
'private _getBucketMonitorTable': function()
|
||||||
|
{
|
||||||
|
return this._$bmonTable = ( this._$bmonTable || ( function()
|
||||||
|
{
|
||||||
|
return $( '<table>' )
|
||||||
|
.attr( 'id', 'bmon-table' )
|
||||||
|
.append( $( '<thead>' ).append( $( '<tr>' )
|
||||||
|
.append( $( '<th>' ).text( '#' ) )
|
||||||
|
.append( $( '<th>' ).text( 'key' ) )
|
||||||
|
.append( $( '<th>' ).text( 'staged value' ) )
|
||||||
|
.append( $( '<th>' ).text( 'prev. staged value' ) )
|
||||||
|
.append( $( '<th>' ).text( 'modifier' ) )
|
||||||
|
.append( $( '<th>' ).text( 'bucket value' ) )
|
||||||
|
) )
|
||||||
|
.append( $( '<tbody>' ) )
|
||||||
|
;
|
||||||
|
} )() );
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate legend for bucket monitor
|
||||||
|
*
|
||||||
|
* @return {jQuery} div containing legend
|
||||||
|
*/
|
||||||
|
'private _getBucketMonitorLegend': function()
|
||||||
|
{
|
||||||
|
return $( '<div>' )
|
||||||
|
.attr( 'id', 'bmon-legend' )
|
||||||
|
.append(
|
||||||
|
$( '<div>' )
|
||||||
|
.addClass( 'bmon-legend-item' )
|
||||||
|
.addClass( 'set' )
|
||||||
|
)
|
||||||
|
.append( '<span>End of set</span>' )
|
||||||
|
.append(
|
||||||
|
$( '<div>' )
|
||||||
|
.addClass( 'bmon-legend-item' )
|
||||||
|
.addClass( 'paint' )
|
||||||
|
)
|
||||||
|
.append( '<span>Paint</span>' )
|
||||||
|
.append(
|
||||||
|
$( '<div>' )
|
||||||
|
.addClass( 'bmon-legend-item' )
|
||||||
|
.addClass( 'commit' )
|
||||||
|
)
|
||||||
|
.append( '<span>Commit</span>' )
|
||||||
|
.append(
|
||||||
|
$( '<div>' )
|
||||||
|
.addClass( 'bmon-legend-item' )
|
||||||
|
.addClass( 'nochange' )
|
||||||
|
)
|
||||||
|
.append( '<span>Unchanged</span>' )
|
||||||
|
.append( this._getBucketMonitorFilter() )
|
||||||
|
;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate filter for bucket monitor
|
||||||
|
*
|
||||||
|
* @return {jQuery} div containing filter
|
||||||
|
*/
|
||||||
|
'private _getBucketMonitorFilter': function()
|
||||||
|
{
|
||||||
|
var _self = this;
|
||||||
|
|
||||||
|
return this._$bmonTableFilter = $( '<input>' )
|
||||||
|
.keyup(
|
||||||
|
function() { _self._filterBucketTable() }
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter bucket monitor table
|
||||||
|
*/
|
||||||
|
'private _filterBucketTable': function()
|
||||||
|
{
|
||||||
|
var search_qry = this._$bmonTableFilter.val();
|
||||||
|
this._$bmonTable.find( 'tbody tr' ).show();
|
||||||
|
|
||||||
|
if ( search_qry != "" )
|
||||||
|
{
|
||||||
|
var reg = new RegExp( search_qry );
|
||||||
|
this._$bmonTable.find( 'tbody tr' )
|
||||||
|
.filter( function() { return !$( this ).find( 'td' ).eq( 1 ).text().match( reg ) } )
|
||||||
|
.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform all necessary hooks for bucket monitor
|
||||||
|
*
|
||||||
|
* @param {StagingBucket} staging bucket
|
||||||
|
*
|
||||||
|
* @return {undefined}
|
||||||
|
*/
|
||||||
|
'private _hookBucket': function( staging )
|
||||||
|
{
|
||||||
|
this._hookBucketUpdate( staging );
|
||||||
|
this._hookBucketCommit( staging );
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook staging bucket for updates
|
||||||
|
*
|
||||||
|
* @param {StagingBucket} staging bucket
|
||||||
|
*
|
||||||
|
* @return {undefined}
|
||||||
|
*/
|
||||||
|
'private _hookBucketUpdate': function( staging )
|
||||||
|
{
|
||||||
|
var _self = this,
|
||||||
|
$table = this._$bmonTable,
|
||||||
|
pre = {};
|
||||||
|
|
||||||
|
staging.on( 'preStagingUpdate', function( data )
|
||||||
|
{
|
||||||
|
// set previous data so we can output it after the update (when we
|
||||||
|
// output the actual row in the table)
|
||||||
|
for ( var key in data )
|
||||||
|
{
|
||||||
|
pre[ key ] = staging.getDataByName( key );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
staging.on( 'stagingUpdate', function( data )
|
||||||
|
{
|
||||||
|
for ( var key in data )
|
||||||
|
{
|
||||||
|
// get the new value
|
||||||
|
var value = JSON.stringify( staging.getDataByName( key ) ),
|
||||||
|
pre_val = JSON.stringify( pre[ key ] ),
|
||||||
|
pre_out = ( pre_val === value )
|
||||||
|
? '(identical)'
|
||||||
|
: ( ( pre_val !== undefined )
|
||||||
|
? pre_val
|
||||||
|
: '(undefined)'
|
||||||
|
),
|
||||||
|
|
||||||
|
orig_value = JSON.stringify(
|
||||||
|
staging.getOriginalDataByName( key )
|
||||||
|
),
|
||||||
|
orig_out = ( ( orig_value === value )
|
||||||
|
? '(identical)'
|
||||||
|
: ( ( orig_value !== undefined )
|
||||||
|
? orig_value
|
||||||
|
: '(undefined)'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
|
err = Error(),
|
||||||
|
|
||||||
|
// get stack trace
|
||||||
|
stack = err.stack && err.stack.replace(
|
||||||
|
/(.*\n){2}/,
|
||||||
|
( key + ' set stack trace:\n' )
|
||||||
|
)
|
||||||
|
;
|
||||||
|
|
||||||
|
$table.find( 'tbody' ).append( $( '<tr>' )
|
||||||
|
.addClass( ( pre_val === value ) ? 'nochange' : '' )
|
||||||
|
.append( $( '<td>' )
|
||||||
|
.text( _self._bmonIndex++ )
|
||||||
|
.addClass( 'index' ) )
|
||||||
|
.append( $( '<td>' ).text( key ) )
|
||||||
|
.append( $( '<td>' ).text( value ) )
|
||||||
|
.append( $( '<td>' ).text( pre_out ) )
|
||||||
|
.append( $( '<td>' ).text( JSON.stringify( data[ key ] ) ) )
|
||||||
|
.append( $( '<td>' ).text( orig_out ) )
|
||||||
|
.click( ( function( stack_trace )
|
||||||
|
{
|
||||||
|
return function()
|
||||||
|
{
|
||||||
|
console.log( stack_trace );
|
||||||
|
};
|
||||||
|
} )( stack ) )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$table.find( 'tr:last' )
|
||||||
|
.addClass( 'last-in-set' );
|
||||||
|
|
||||||
|
_self._paintLine();
|
||||||
|
|
||||||
|
// clear out prev. data
|
||||||
|
pre = {};
|
||||||
|
|
||||||
|
// re-filter table
|
||||||
|
_self._filterBucketTable();
|
||||||
|
} );
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook staging bucket for commits
|
||||||
|
*
|
||||||
|
* @param {StagingBucket} staging bucket
|
||||||
|
*
|
||||||
|
* @return {undefined}
|
||||||
|
*/
|
||||||
|
'private _hookBucketCommit': function( staging )
|
||||||
|
{
|
||||||
|
var _self = this,
|
||||||
|
$table = this._getBucketMonitorTable();
|
||||||
|
|
||||||
|
staging.on( 'preCommit', function()
|
||||||
|
{
|
||||||
|
var data = staging.getDiff();
|
||||||
|
|
||||||
|
for ( var key in data )
|
||||||
|
{
|
||||||
|
var value = JSON.stringify( data[ key ] ),
|
||||||
|
commit_value = JSON.stringify(
|
||||||
|
staging.getDataByName( key )
|
||||||
|
);
|
||||||
|
|
||||||
|
$table.find( 'tbody' ).append( $( '<tr>' )
|
||||||
|
.addClass( 'commit' )
|
||||||
|
.append( $( '<td>' )
|
||||||
|
.text( _self._bmonIndex++ )
|
||||||
|
.addClass( 'index' ) )
|
||||||
|
.append( $( '<td>' ).text( key ) )
|
||||||
|
.append( $( '<td>' ).text( commit_value ) )
|
||||||
|
.append( $( '<td>' )
|
||||||
|
.attr( 'colspan', 3 )
|
||||||
|
.text( value )
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
_self._paintLine();
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw paint line
|
||||||
|
*
|
||||||
|
* The paint line represents when a paint operation was able to occur. This
|
||||||
|
* allows us to see how many bucket values were updated between paints,
|
||||||
|
* which (depending on what hooks the bucket) could have negative
|
||||||
|
* consequences on performance.
|
||||||
|
*
|
||||||
|
* This is simple to detect - simply use a setTimeout() and it will execute
|
||||||
|
* after the stack has cleared and the page has been painted.
|
||||||
|
*
|
||||||
|
* @return {undefined}
|
||||||
|
*/
|
||||||
|
'private _paintLine': function()
|
||||||
|
{
|
||||||
|
var _self = this;
|
||||||
|
|
||||||
|
this._paintTimeout && clearTimeout( this._paintTimeout );
|
||||||
|
this._paintTimeout = setTimeout( function()
|
||||||
|
{
|
||||||
|
_self._getBucketMonitorTable().find( 'tr:last' )
|
||||||
|
.addClass( 'last-pre-paint' );
|
||||||
|
}, 25 );
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays the bucket editor
|
||||||
|
*
|
||||||
|
* The bucket editor allows the monitoring and modification of bucket
|
||||||
|
* values.
|
||||||
|
*
|
||||||
|
* @param {StagingBucket} staging bucket
|
||||||
|
* @param {Function} change_callback callback to call on value change
|
||||||
|
*
|
||||||
|
* @todo move into another class
|
||||||
|
*/
|
||||||
|
'public showBucketEditor': function( staging, change_callback )
|
||||||
|
{
|
||||||
|
var $editor = $( '<div>' )
|
||||||
|
.dialog( {
|
||||||
|
title: "Bucket Editor",
|
||||||
|
width: 500,
|
||||||
|
height: 600,
|
||||||
|
|
||||||
|
close: function()
|
||||||
|
{
|
||||||
|
staging.removeListener( 'stagingUpdate', listener );
|
||||||
|
}
|
||||||
|
} ),
|
||||||
|
|
||||||
|
listener = function( data )
|
||||||
|
{
|
||||||
|
for ( name in data )
|
||||||
|
{
|
||||||
|
$editor.find( 'input[name="' + name + '"]' )
|
||||||
|
.val( JSON.stringify(
|
||||||
|
// get the full data set for this key
|
||||||
|
staging.getDataByName( name )
|
||||||
|
) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
|
staging.on( 'stagingUpdate', listener );
|
||||||
|
|
||||||
|
$editor
|
||||||
|
.append( $( '<button>' )
|
||||||
|
.text( 'Import' )
|
||||||
|
.click( function()
|
||||||
|
{
|
||||||
|
var data = prompt( 'Paste bucket JSON' );
|
||||||
|
if ( data )
|
||||||
|
{
|
||||||
|
console.log( 'Overwriting bucket.' );
|
||||||
|
staging.overwriteValues( JSON.parse( data ) );
|
||||||
|
}
|
||||||
|
} )
|
||||||
|
)
|
||||||
|
.append( $( '<button>' )
|
||||||
|
.text( 'Dump' )
|
||||||
|
.click( function()
|
||||||
|
{
|
||||||
|
console.log( staging.getDataJson() );
|
||||||
|
} )
|
||||||
|
);
|
||||||
|
|
||||||
|
this._genBucketEditorFields( staging, $editor, change_callback );
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a field for each value in the bucket
|
||||||
|
*
|
||||||
|
* @param {StagingBucket} staging bucket
|
||||||
|
* @param {jQuery} $editor editor to append fields to
|
||||||
|
* @param {Function} change_callback callback to call on value change
|
||||||
|
*
|
||||||
|
* @return {undefined}
|
||||||
|
*/
|
||||||
|
'private _genBucketEditorFields': function(
|
||||||
|
staging, $editor, change_callback
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var data = staging.getData();
|
||||||
|
|
||||||
|
for ( name in data )
|
||||||
|
{
|
||||||
|
// The data we've been provided with does not include the staging
|
||||||
|
// data. If we request it by name, however, that data will then be
|
||||||
|
// merged in.
|
||||||
|
var vals = staging.getDataByName( name );
|
||||||
|
|
||||||
|
$editor.append(
|
||||||
|
$( '<div>' )
|
||||||
|
.append( $( '<div>' )
|
||||||
|
.text( name )
|
||||||
|
.css( {
|
||||||
|
fontWeight: 'bold'
|
||||||
|
} )
|
||||||
|
.append( $( '<input>' )
|
||||||
|
.attr( {
|
||||||
|
name: name,
|
||||||
|
type: 'text'
|
||||||
|
} )
|
||||||
|
.val( JSON.stringify( vals ) )
|
||||||
|
.css( {
|
||||||
|
width: '450px',
|
||||||
|
marginBottom: '1em'
|
||||||
|
} )
|
||||||
|
.change( ( function( name )
|
||||||
|
{
|
||||||
|
return function()
|
||||||
|
{
|
||||||
|
var $this = $( this );
|
||||||
|
change_callback(
|
||||||
|
name, JSON.parse( $this.val() )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} )( name ) )
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} );
|
|
@ -0,0 +1,193 @@
|
||||||
|
/**
|
||||||
|
* Contains CalcClientDebugTab class
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017 LoVullo Associates, Inc.
|
||||||
|
*
|
||||||
|
* This file is part of the Liza Data Collection Framework
|
||||||
|
*
|
||||||
|
* Liza is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var Class = require( 'easejs' ).Class,
|
||||||
|
|
||||||
|
ClientDebugTab = require( './ClientDebugTab' ),
|
||||||
|
|
||||||
|
calc = require( 'program/Calc' )
|
||||||
|
;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Monitors client-side assertions
|
||||||
|
*/
|
||||||
|
module.exports = Class( 'CalcClientDebugTab' )
|
||||||
|
.implement( ClientDebugTab )
|
||||||
|
.extend(
|
||||||
|
{
|
||||||
|
'private _$content': null,
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve tab title
|
||||||
|
*
|
||||||
|
* @return {string} tab title
|
||||||
|
*/
|
||||||
|
'public getTitle': function()
|
||||||
|
{
|
||||||
|
return 'Calculated Values';
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve tab content
|
||||||
|
*
|
||||||
|
* @param {Client} client active client being debugged
|
||||||
|
* @param {StagingBucket} bucket bucket to reference for data
|
||||||
|
*
|
||||||
|
* @return {jQuery|string} tab content
|
||||||
|
*/
|
||||||
|
'public getContent': function( client, bucket )
|
||||||
|
{
|
||||||
|
this._$content = $( '<div>' )
|
||||||
|
.append( $( '<p>' ).text(
|
||||||
|
"Quick-n-dirty calculated value test tool. Select the " +
|
||||||
|
"method to test below, followed by the data and value " +
|
||||||
|
"arguments. See the Calc module for more information."
|
||||||
|
) );
|
||||||
|
|
||||||
|
this._addRow();
|
||||||
|
this._addButtons();
|
||||||
|
|
||||||
|
return this._$content;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add calculated value row which will perform the requested calculation
|
||||||
|
* with the provided parameter values
|
||||||
|
*
|
||||||
|
* @return {undefined}
|
||||||
|
*/
|
||||||
|
'private _addRow': function()
|
||||||
|
{
|
||||||
|
var _self = this,
|
||||||
|
$sel = null,
|
||||||
|
$data = null,
|
||||||
|
$value = null,
|
||||||
|
$result = null,
|
||||||
|
|
||||||
|
changeCallback = function()
|
||||||
|
{
|
||||||
|
_self._doCalc( $sel.val(), $data.val(), $value.val(), $result );
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
|
this._$content.append( $( '<div>' )
|
||||||
|
.addClass( 'row' )
|
||||||
|
.append( $sel = $( '<select>' )
|
||||||
|
.change( changeCallback )
|
||||||
|
)
|
||||||
|
.append( $data = $( '<input type="text">' )
|
||||||
|
.val( '[]' )
|
||||||
|
.change( changeCallback )
|
||||||
|
)
|
||||||
|
.append( $value = $( '<input type="text">' )
|
||||||
|
.val( '[]' )
|
||||||
|
.change( changeCallback )
|
||||||
|
)
|
||||||
|
.append( $result = $( '<span>' ) )
|
||||||
|
);
|
||||||
|
|
||||||
|
for ( method in calc )
|
||||||
|
{
|
||||||
|
$sel.append( $( '<option>' )
|
||||||
|
.val( method )
|
||||||
|
.text( method )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
changeCallback();
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform calculation and update given result element
|
||||||
|
*
|
||||||
|
* @param {string} sel selected method
|
||||||
|
* @param {string} data given data argument
|
||||||
|
* @param {string} value given value argument
|
||||||
|
* @param {jQuery} $result result element to update
|
||||||
|
*
|
||||||
|
* @return {undefined}
|
||||||
|
*/
|
||||||
|
'private _doCalc': function( sel, data, value, $result )
|
||||||
|
{
|
||||||
|
var result = null;
|
||||||
|
|
||||||
|
// don't do anything if no method was selected
|
||||||
|
if ( !sel)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
result = calc[ sel ](
|
||||||
|
JSON.parse( data || [] ),
|
||||||
|
JSON.parse( value || [] )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
catch ( e )
|
||||||
|
{
|
||||||
|
result = 'ERROR (see console)';
|
||||||
|
console.error( e );
|
||||||
|
}
|
||||||
|
|
||||||
|
$result.text( JSON.stringify( result ) );
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Append button that allows for the creation of additional calc rows and a
|
||||||
|
* clear button
|
||||||
|
*
|
||||||
|
* @return {undefined}
|
||||||
|
*/
|
||||||
|
'private _addButtons': function()
|
||||||
|
{
|
||||||
|
var _self = this;
|
||||||
|
|
||||||
|
this._$content.append( $( '<div>' )
|
||||||
|
.append( $( '<button>' )
|
||||||
|
.text( '+' )
|
||||||
|
.click( function()
|
||||||
|
{
|
||||||
|
_self._addRow();
|
||||||
|
|
||||||
|
// move the button down to the bottom (quick and easy means
|
||||||
|
// of doing so)
|
||||||
|
$( this ).parents( 'div:first' )
|
||||||
|
.detach().appendTo( _self._$content );
|
||||||
|
} )
|
||||||
|
)
|
||||||
|
.append( $( '<button>' )
|
||||||
|
.text( 'Clear' )
|
||||||
|
.click( function()
|
||||||
|
{
|
||||||
|
_self._$content.find( 'div.row:not(:first)' ).remove();
|
||||||
|
} )
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
|
@ -0,0 +1,239 @@
|
||||||
|
/**
|
||||||
|
* Contains ClassifyClientDebugTab class
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017 LoVullo Associates, Inc.
|
||||||
|
*
|
||||||
|
* This file is part of the Liza Data Collection Framework
|
||||||
|
*
|
||||||
|
* Liza is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var Class = require( 'easejs' ).Class,
|
||||||
|
EventEmitter = require( 'events' ).EventEmitter,
|
||||||
|
ClientDebugTab = require( './ClientDebugTab' );
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Monitors client-side assertions
|
||||||
|
*/
|
||||||
|
module.exports = Class( 'AssertionClientDebugTab' )
|
||||||
|
.implement( ClientDebugTab )
|
||||||
|
.extend( EventEmitter,
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Client being monitored
|
||||||
|
* @type {Client}
|
||||||
|
*/
|
||||||
|
'private _client': null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List storing classes
|
||||||
|
* @type {jQuery}
|
||||||
|
*/
|
||||||
|
'private _$list': null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class cache
|
||||||
|
* @type {Object}
|
||||||
|
*/
|
||||||
|
'private _cache': {},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve tab title
|
||||||
|
*
|
||||||
|
* @return {string} tab title
|
||||||
|
*/
|
||||||
|
'public getTitle': function()
|
||||||
|
{
|
||||||
|
return 'Classifications';
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve tab content
|
||||||
|
*
|
||||||
|
* @param {Client} client active client being debugged
|
||||||
|
* @param {StagingBucket} bucket bucket to reference for data
|
||||||
|
*
|
||||||
|
* @return {jQuery} tab content
|
||||||
|
*/
|
||||||
|
'public getContent': function( client, bucket )
|
||||||
|
{
|
||||||
|
// cut down on argument list
|
||||||
|
this._client = client;
|
||||||
|
|
||||||
|
this._hookClient( client );
|
||||||
|
|
||||||
|
return this._createContent();
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
'private _hookClient': function( client )
|
||||||
|
{
|
||||||
|
var _self = this,
|
||||||
|
cache = this._cache;
|
||||||
|
|
||||||
|
var sorted = null;
|
||||||
|
|
||||||
|
this._client.getQuote().on( 'classify', function( classes )
|
||||||
|
{
|
||||||
|
setTimeout( function()
|
||||||
|
{
|
||||||
|
// only sort the first time around (since we should always
|
||||||
|
// receive the same list of classifiers)
|
||||||
|
if ( sorted === null )
|
||||||
|
{
|
||||||
|
sorted = _self._sortClasses( classes );
|
||||||
|
}
|
||||||
|
|
||||||
|
for ( var i in sorted )
|
||||||
|
{
|
||||||
|
var c = sorted[ i ];
|
||||||
|
|
||||||
|
if ( cache[ c ] === undefined )
|
||||||
|
{
|
||||||
|
cache[ c ] = classes[ c ].is;
|
||||||
|
_self._addClass( c );
|
||||||
|
|
||||||
|
added = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// no need to update if the status hasn't changed
|
||||||
|
if ( cache[ c ] === c.is )
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var sc = _self._sanitizeName( c );
|
||||||
|
_self._markClass( sc, classes[ c ].is );
|
||||||
|
_self._updateIndexes( sc, classes[ c ].indexes );
|
||||||
|
}
|
||||||
|
}, 25 );
|
||||||
|
} );
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
'private _addClass': function( cname )
|
||||||
|
{
|
||||||
|
var sc = this._sanitizeName( cname );
|
||||||
|
|
||||||
|
this._$list.append( $( '<div>' )
|
||||||
|
.attr( 'id', ( '-class-' + sc ) )
|
||||||
|
.text( cname )
|
||||||
|
.append( $ ( '<span/>' ) )
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
'private _updateIndexes': function( cname, indexes )
|
||||||
|
{
|
||||||
|
this._$list.find( '#' + '-class-' + cname + ' > span' )
|
||||||
|
.text( JSON.stringify( indexes ) );
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
'private _markClass': function( cname, is )
|
||||||
|
{
|
||||||
|
this._$list.find( '#' + '-class-' + cname ).toggleClass( 'is', is );
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
'private _sanitizeName': function( cname )
|
||||||
|
{
|
||||||
|
return cname.replace( /:/, '-' );
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
'private _sortClasses': function( classes )
|
||||||
|
{
|
||||||
|
var names = [];
|
||||||
|
for ( var c in classes )
|
||||||
|
{
|
||||||
|
names.push( c );
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort the classifiers by name
|
||||||
|
return names.sort( function( a, b )
|
||||||
|
{
|
||||||
|
if ( a < b ) return -1;
|
||||||
|
else if ( a > b ) return 1;
|
||||||
|
else return 0;
|
||||||
|
} );
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create tab content
|
||||||
|
*
|
||||||
|
* @return {jQuery} tab content
|
||||||
|
*/
|
||||||
|
'private _createContent': function()
|
||||||
|
{
|
||||||
|
var _self = this,
|
||||||
|
$div = null;
|
||||||
|
|
||||||
|
// classifier list with filter box
|
||||||
|
this._$list = $( '<div>' )
|
||||||
|
.addClass( 'class-listing' )
|
||||||
|
.append( $( '<input>' )
|
||||||
|
.keyup( function()
|
||||||
|
{
|
||||||
|
var reg = new RegExp( this.value );
|
||||||
|
$( 'div.class-listing div' )
|
||||||
|
.toggle( true )
|
||||||
|
.filter( function() { return !$( this ).text().match( reg ) } )
|
||||||
|
.toggle( false );
|
||||||
|
}
|
||||||
|
) );
|
||||||
|
|
||||||
|
return $div = $( '<div>' )
|
||||||
|
.append( $( '<button>' )
|
||||||
|
.text( 'Toggle Dock' )
|
||||||
|
.toggle(
|
||||||
|
function()
|
||||||
|
{
|
||||||
|
$( '#content' ).append(
|
||||||
|
_self._$list.addClass( 'dock' ).detach()
|
||||||
|
);
|
||||||
|
},
|
||||||
|
function()
|
||||||
|
{
|
||||||
|
$div.append(
|
||||||
|
_self._$list.removeClass( 'dock' ).detach()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.append( $( '<input>' )
|
||||||
|
.attr( 'type', 'checkbox' )
|
||||||
|
.attr( 'id', 'classify-nohide' )
|
||||||
|
.change( function()
|
||||||
|
{
|
||||||
|
// trigger toggle event
|
||||||
|
_self.emit( 'classifyNoHideToggle',
|
||||||
|
$( this ).is( ':checked' )
|
||||||
|
);
|
||||||
|
} )
|
||||||
|
)
|
||||||
|
.append( $( '<label>' )
|
||||||
|
.attr( 'for', 'classify-nohide' )
|
||||||
|
.text( 'Inhibit field hiding by classifications' )
|
||||||
|
)
|
||||||
|
.append( this._$list );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
|
@ -0,0 +1,295 @@
|
||||||
|
/**
|
||||||
|
* Contains ClientDebug class
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017 LoVullo Associates, Inc.
|
||||||
|
*
|
||||||
|
* This file is part of the Liza Data Collection Framework
|
||||||
|
*
|
||||||
|
* Liza is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var Class = require( 'easejs' ).Class,
|
||||||
|
Client = require( '../Client' ),
|
||||||
|
ClientDebugDialog = require( './ClientDebugDialog' );
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Facade for the debug dialog
|
||||||
|
*
|
||||||
|
* The tight coupling is intentional.
|
||||||
|
*/
|
||||||
|
module.exports = Class( 'ClientDebug',
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Name of flag that will determine whether the dialog should auto-load
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
'private const AUTOLOAD_FLAG': 'devdialog-autoload',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name of flag that will determine whether the debugger should be invoked
|
||||||
|
* on client-handled errors
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
'private const ERRDEBUG_FLAG': 'devdialog-errdebug',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Program client to debug
|
||||||
|
* @type {Client}
|
||||||
|
*/
|
||||||
|
'private _client': null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Developer dialog
|
||||||
|
* @type {ClientDebugDialog}
|
||||||
|
*/
|
||||||
|
'private _dialog': null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persistent session storage
|
||||||
|
* @type {Storage}
|
||||||
|
*/
|
||||||
|
'private _storage': null,
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize debugger with the program client instance to debug
|
||||||
|
*
|
||||||
|
* @param {Client} client program client to debug
|
||||||
|
* @param {Storage} storage [persistent] session storage
|
||||||
|
*/
|
||||||
|
__construct: function( client, storage )
|
||||||
|
{
|
||||||
|
|
||||||
|
if ( !( Class.isA( Client, program_client ) ) )
|
||||||
|
{
|
||||||
|
throw TypeError( 'Expected Client, given ' + program_client );
|
||||||
|
}
|
||||||
|
|
||||||
|
var _self = this,
|
||||||
|
staging = this.getStagingBucketFrom( client );
|
||||||
|
|
||||||
|
this._client = client;
|
||||||
|
this._storage = storage;
|
||||||
|
|
||||||
|
this._dialog = ClientDebugDialog( client, staging )
|
||||||
|
.addTab(
|
||||||
|
require( './BucketClientDebugTab' )()
|
||||||
|
.on( 'fieldOverlayToggle', function( value )
|
||||||
|
{
|
||||||
|
client.$body.toggleClass( 'show-field-overlay', value );
|
||||||
|
} )
|
||||||
|
)
|
||||||
|
.addTab( require( './AssertionClientDebugTab' )() )
|
||||||
|
.addTab( require( './CalcClientDebugTab' )() )
|
||||||
|
.addTab(
|
||||||
|
require( './ClassifyClientDebugTab' )()
|
||||||
|
.on( 'classifyNoHideToggle', function( value )
|
||||||
|
{
|
||||||
|
// XXX this should be encapsulated
|
||||||
|
client.$body.toggleClass( 'show-hidden-fields', value );
|
||||||
|
} )
|
||||||
|
)
|
||||||
|
.on( 'autoloadToggle', function( value )
|
||||||
|
{
|
||||||
|
_self.setAutoloadFlag( value );
|
||||||
|
} )
|
||||||
|
.on( 'errDebugToggle', function( value )
|
||||||
|
{
|
||||||
|
_self.setErrorDebugFlag( value );
|
||||||
|
} )
|
||||||
|
;
|
||||||
|
|
||||||
|
this._bindKeys();
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize dialog in background to begin gathering data
|
||||||
|
*
|
||||||
|
* This is useful to ensure data from the beginning of the page load is
|
||||||
|
* gathered without pestering the user.
|
||||||
|
*
|
||||||
|
* @return {ClientDebug} self
|
||||||
|
*/
|
||||||
|
'public bgInit': function()
|
||||||
|
{
|
||||||
|
// autoload only if the flag has been set
|
||||||
|
if ( this._hasAutoloadFlag() )
|
||||||
|
{
|
||||||
|
this._dialog
|
||||||
|
.show( false )
|
||||||
|
.setAutoloadStatus( true );
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if the dialog should be displayed automatically
|
||||||
|
*
|
||||||
|
* All session data is stored as as a string.
|
||||||
|
*
|
||||||
|
* @return {boolean} true if autoload should be performed, otherwise false
|
||||||
|
*/
|
||||||
|
'private _hasAutoloadFlag': function()
|
||||||
|
{
|
||||||
|
// if we do not support session storage, then the flag cannot be set
|
||||||
|
if ( !( this._storage ) )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var flag = this._storage.getItem( this.__self.$( 'AUTOLOAD_FLAG' ) );
|
||||||
|
return ( flag === "true" );
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether the dialog should be loaded in the background on page load
|
||||||
|
*
|
||||||
|
* @param {boolean} val whether to load in background on page load
|
||||||
|
*
|
||||||
|
* @return {ClientDebug} self
|
||||||
|
*/
|
||||||
|
'public setAutoloadFlag': function( val )
|
||||||
|
{
|
||||||
|
// only set the flag if we support session storage
|
||||||
|
if ( this._storage )
|
||||||
|
{
|
||||||
|
this._storage.setItem( this.__self.$( 'AUTOLOAD_FLAG' ), !!val );
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
'public setErrorDebugFlag': function( val )
|
||||||
|
{
|
||||||
|
// only set the flag if we support session storage
|
||||||
|
if ( this._storage )
|
||||||
|
{
|
||||||
|
this._storage.setItem( this.__self.$( 'ERRDEBUG_FLAG' ), !!val );
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
'public hasErrorDebugFlag': function()
|
||||||
|
{
|
||||||
|
// if we do not support session storage, then the flag cannot be set
|
||||||
|
if ( !( this._storage ) )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var flag = this._storage.getItem( this.__self.$( 'ERRDEBUG_FLAG' ) );
|
||||||
|
return ( flag === "true" );
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle display of developer dialog
|
||||||
|
*
|
||||||
|
* @return {ClientDebug} self
|
||||||
|
*/
|
||||||
|
'public toggle': function()
|
||||||
|
{
|
||||||
|
this._dialog
|
||||||
|
.toggle()
|
||||||
|
.setErrorDebugStatus( this.hasErrorDebugFlag() );
|
||||||
|
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CTRL+SHIFT+D will display
|
||||||
|
*
|
||||||
|
* @return {undefined}
|
||||||
|
*/
|
||||||
|
'private _bindKeys': function()
|
||||||
|
{
|
||||||
|
var _self = this,
|
||||||
|
ctrl = false,
|
||||||
|
shift = false;
|
||||||
|
|
||||||
|
$( document ).keydown( function( event )
|
||||||
|
{
|
||||||
|
switch ( event.which )
|
||||||
|
{
|
||||||
|
case 16:
|
||||||
|
shift = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 17:
|
||||||
|
ctrl = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
// d
|
||||||
|
case 68:
|
||||||
|
if ( shift && ctrl )
|
||||||
|
{
|
||||||
|
// show developer dialog
|
||||||
|
_self.toggle();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} )
|
||||||
|
.keyup( function( event )
|
||||||
|
{
|
||||||
|
switch ( event.which )
|
||||||
|
{
|
||||||
|
case 16:
|
||||||
|
shift = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 17:
|
||||||
|
ctrl = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the staging bucket from the given client instance
|
||||||
|
*
|
||||||
|
* This breaks encapsulation! Use it for debugging only.
|
||||||
|
*
|
||||||
|
* @param {Client} program_client client from which to retrieve staging
|
||||||
|
* bucket
|
||||||
|
*
|
||||||
|
* @return {StagingBucket} staging bucket associated with given client
|
||||||
|
*/
|
||||||
|
'public getStagingBucketFrom': function( program_client )
|
||||||
|
{
|
||||||
|
if ( !( Class.isA( Client, program_client ) ) )
|
||||||
|
{
|
||||||
|
throw Error( 'Expected Client, given ' + program_client );
|
||||||
|
}
|
||||||
|
|
||||||
|
var retval;
|
||||||
|
program_client.getQuote().visitData( function( bucket )
|
||||||
|
{
|
||||||
|
retval = bucket;
|
||||||
|
} );
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
|
@ -0,0 +1,351 @@
|
||||||
|
/**
|
||||||
|
* Contains ClientDialog class
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017 LoVullo Associates, Inc.
|
||||||
|
*
|
||||||
|
* This file is part of the Liza Data Collection Framework
|
||||||
|
*
|
||||||
|
* Liza is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var Class = require( 'easejs' ).Class,
|
||||||
|
EventEmitter = require( 'events' ).EventEmitter,
|
||||||
|
Client = require( '../Client' ),
|
||||||
|
ClientDebugTab = require( './ClientDebugTab' );
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides runtime debugging options
|
||||||
|
*
|
||||||
|
* Everything in here can be done from the console. This just makes life easier.
|
||||||
|
*/
|
||||||
|
module.exports = Class( 'ClientDebugDialog' )
|
||||||
|
.extend( EventEmitter,
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Program client
|
||||||
|
* @type {ProgramClient}
|
||||||
|
*/
|
||||||
|
'private _client': null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Staging bucket associated with the given client
|
||||||
|
* @type {StagingBucket}
|
||||||
|
*/
|
||||||
|
'private _staging': null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dialog to be displayed
|
||||||
|
* @type {jQuery}
|
||||||
|
*/
|
||||||
|
'private _$dialog': null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the dialog is visible
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
'private _dialogVisible': false,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of tabs to be displayed
|
||||||
|
* @type {Object.<ClientDebugTab>}
|
||||||
|
*/
|
||||||
|
'private _tabs': {},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize client debugger with a client instance
|
||||||
|
*
|
||||||
|
* @param {Client} program_client client instance to debug
|
||||||
|
* @param {StagingBucket} staging bucket
|
||||||
|
*/
|
||||||
|
__construct: function( program_client, staging, storage )
|
||||||
|
{
|
||||||
|
if ( !( Class.isA( Client, program_client ) ) )
|
||||||
|
{
|
||||||
|
throw TypeError( 'Expected Client, given ' + program_client );
|
||||||
|
}
|
||||||
|
|
||||||
|
this._client = program_client;
|
||||||
|
this._staging = staging;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a tab to the dialog
|
||||||
|
*
|
||||||
|
* @param {ClientDebugTab} tab tab to add
|
||||||
|
*
|
||||||
|
* @return {ClientDebugDialog} self
|
||||||
|
*/
|
||||||
|
'public addTab': function( tab )
|
||||||
|
{
|
||||||
|
if ( !( Class.isA( ClientDebugTab, tab ) ) )
|
||||||
|
{
|
||||||
|
throw TypeError( 'Expected ClientDebugTab, given ' + tab );
|
||||||
|
}
|
||||||
|
|
||||||
|
this._tabs[ 'dbg-' + tab.getTitle().replace( / /, '-' ) ] = tab;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display developer dialog
|
||||||
|
*
|
||||||
|
* If a dialog had been previously displayed (in this instance), it will be
|
||||||
|
* re-opened.
|
||||||
|
*
|
||||||
|
* @param {boolean=} fg if set to false, initialize the dialog in the
|
||||||
|
* background, but do not display
|
||||||
|
*
|
||||||
|
* @return {ClientDebugTab} self
|
||||||
|
*/
|
||||||
|
'public show': function( fg )
|
||||||
|
{
|
||||||
|
this._$dialog = ( this._$dialog || this._createDialog() );
|
||||||
|
|
||||||
|
if ( fg !== false )
|
||||||
|
{
|
||||||
|
this._$dialog.dialog( 'open' );
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide developer dialog
|
||||||
|
*
|
||||||
|
* @return {ClientDebugTab} self
|
||||||
|
*/
|
||||||
|
'public hide': function()
|
||||||
|
{
|
||||||
|
this._$dialog && this._$dialog.dialog( 'close' );
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle developer dialog
|
||||||
|
*
|
||||||
|
* @return {ClientDebugTab} self
|
||||||
|
*/
|
||||||
|
'public toggle': function()
|
||||||
|
{
|
||||||
|
return ( ( this._dialogVisible ) ? this.hide() : this.show() );
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the autoload toggle value (display only)
|
||||||
|
*
|
||||||
|
* @param {boolean} val whether or not autoload is enabled
|
||||||
|
*
|
||||||
|
* @return {ClientDebugDialog} self
|
||||||
|
*/
|
||||||
|
'public setAutoloadStatus': function( val )
|
||||||
|
{
|
||||||
|
this._$dialog.find( '#devdialog-autoload' ).attr( 'checked', !!val );
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
'public setErrorDebugStatus': function( val )
|
||||||
|
{
|
||||||
|
this._$dialog.find( '#devdialog-errdebug' ).attr( 'checked', !!val );
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the dialog
|
||||||
|
*
|
||||||
|
* @return {jQuery} dialog
|
||||||
|
*/
|
||||||
|
'private _createDialog': function()
|
||||||
|
{
|
||||||
|
var _self = this,
|
||||||
|
$tabs;
|
||||||
|
|
||||||
|
this._showSidebarWarning();
|
||||||
|
|
||||||
|
return $( '<div>' )
|
||||||
|
.append( $( '<p>' ).text(
|
||||||
|
"Everything in this dialog can be done via the console. " +
|
||||||
|
"This simply exists to make life easier."
|
||||||
|
) )
|
||||||
|
.append( $( '<p>' ).html(
|
||||||
|
"<strong>To view this dialog:</strong> " +
|
||||||
|
"one can use the key combination " +
|
||||||
|
"<samp>CTRL+SHIFT+D</samp>, or <code>getProgramDebug()" +
|
||||||
|
".toggle()</code> from the console. The latter may " +
|
||||||
|
"also be used even if the user is not logged in internally."
|
||||||
|
) )
|
||||||
|
.append( this._createAutoloadToggle() )
|
||||||
|
.append( $tabs = this._createTabs() )
|
||||||
|
.dialog( {
|
||||||
|
title: "Developer Dialog",
|
||||||
|
modal: false,
|
||||||
|
width: 800,
|
||||||
|
height: 600,
|
||||||
|
|
||||||
|
autoOpen: false,
|
||||||
|
|
||||||
|
open: function()
|
||||||
|
{
|
||||||
|
$tabs.tabs();
|
||||||
|
_self._dialogVisible = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
close: function()
|
||||||
|
{
|
||||||
|
_self._dialogVisible = false;
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create autoload toggle elements
|
||||||
|
*
|
||||||
|
* When toggled, the autoloadToggle event will be triggered with its value.
|
||||||
|
*
|
||||||
|
* @return {undefined}
|
||||||
|
*/
|
||||||
|
'private _createAutoloadToggle': function()
|
||||||
|
{
|
||||||
|
var _self = this;
|
||||||
|
|
||||||
|
return $( '<p>' )
|
||||||
|
.append( $( '<div>' )
|
||||||
|
.append( $( '<input>' )
|
||||||
|
.attr( 'type', 'checkbox' )
|
||||||
|
.attr( 'id', 'devdialog-autoload' )
|
||||||
|
.change( function()
|
||||||
|
{
|
||||||
|
// trigger toggle event
|
||||||
|
_self.emit( 'autoloadToggle',
|
||||||
|
$( this ).is( ':checked' )
|
||||||
|
);
|
||||||
|
} )
|
||||||
|
)
|
||||||
|
.append( $( '<label>' )
|
||||||
|
.attr( 'for', 'devdialog-autoload' )
|
||||||
|
.text( 'Load automatically in background on page load' )
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.append( $( '<div>' )
|
||||||
|
.append( $( '<input>' )
|
||||||
|
.attr( 'type', 'checkbox' )
|
||||||
|
.attr( 'id', 'devdialog-errdebug' )
|
||||||
|
.change( function()
|
||||||
|
{
|
||||||
|
// trigger toggle event
|
||||||
|
_self.emit( 'errDebugToggle',
|
||||||
|
$( this ).is( ':checked' )
|
||||||
|
);
|
||||||
|
} )
|
||||||
|
)
|
||||||
|
.append( $( '<label>' )
|
||||||
|
.attr( 'for', 'devdialog-errdebug' )
|
||||||
|
.text( 'Execute debugger on client-handled errors' )
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
// XXX: This doesn't belong in this class!
|
||||||
|
'private _showSidebarWarning': function()
|
||||||
|
{
|
||||||
|
$( '#sidebar-help-text' ).after( $( '<div>' )
|
||||||
|
.attr( 'id', 'dev-dialog-perf-warning' )
|
||||||
|
.text(
|
||||||
|
'Developer dialog is monitoring events on this page. ' +
|
||||||
|
'Performance may be negatively impacted.'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate tabs and their content
|
||||||
|
*
|
||||||
|
* The developer dialog contains a tab for each major section.
|
||||||
|
*
|
||||||
|
* The div that is returned can be used by jQuery UI for tab styling. It is
|
||||||
|
* important that you do not attempt to style the tabs until after it has
|
||||||
|
* been appended to the DOM, or jQuery UI will fail to properly process it.
|
||||||
|
*
|
||||||
|
* @return {jQuery} tab div
|
||||||
|
*/
|
||||||
|
'private _createTabs': function()
|
||||||
|
{
|
||||||
|
var $tabs = $( '<div>' )
|
||||||
|
.attr( 'id', 'dbg-tabs' )
|
||||||
|
.append( this._getTabUl() )
|
||||||
|
;
|
||||||
|
|
||||||
|
this._appendTabContent( $tabs );
|
||||||
|
|
||||||
|
return $tabs;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate tab ul element
|
||||||
|
*
|
||||||
|
* @return {jQuery} tab ul element
|
||||||
|
*/
|
||||||
|
'private _getTabUl': function()
|
||||||
|
{
|
||||||
|
var $ul = $( '<ul>' );
|
||||||
|
|
||||||
|
for ( var i in this._tabs )
|
||||||
|
{
|
||||||
|
$ul.append(
|
||||||
|
$( '<li>' ).append(
|
||||||
|
$( '<a>' )
|
||||||
|
.attr( 'href', ( '#' + i ) )
|
||||||
|
.text( this._tabs[ i ].getTitle() )
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $ul;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends the content of each of the tabs to the provided tab div
|
||||||
|
*
|
||||||
|
* @param {jQuery} $tabs tab div to append to
|
||||||
|
*
|
||||||
|
* @return {undefined}
|
||||||
|
*/
|
||||||
|
'private _appendTabContent': function( $tabs )
|
||||||
|
{
|
||||||
|
for ( var i in this._tabs )
|
||||||
|
{
|
||||||
|
$tabs.append(
|
||||||
|
$( '<div>' )
|
||||||
|
.attr( 'id', i )
|
||||||
|
.append( this._tabs[ i ].getContent(
|
||||||
|
this._client, this._staging
|
||||||
|
) )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
/**
|
||||||
|
* Contains ClientDebugTab interface
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017 LoVullo Associates, Inc.
|
||||||
|
*
|
||||||
|
* This file is part of the Liza Data Collection Framework
|
||||||
|
*
|
||||||
|
* Liza is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var Interface = require( 'easejs' ).Interface;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a tab within the developer dialog
|
||||||
|
*/
|
||||||
|
module.exports = Interface( 'ClientDebugTab',
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Retrieve tab title
|
||||||
|
*
|
||||||
|
* @return {string} tab title
|
||||||
|
*/
|
||||||
|
'public getTitle': [],
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve tab content
|
||||||
|
*
|
||||||
|
* @param {Client} client active client being debugged
|
||||||
|
* @param {StagingBucket} bucket bucket to reference for data
|
||||||
|
*
|
||||||
|
* @return {jQuery|string} tab content
|
||||||
|
*/
|
||||||
|
'public getContent': [ 'client', 'bucket' ]
|
||||||
|
} );
|
||||||
|
|
Loading…
Reference in New Issue