1
0
Fork 0
liza/src/client/debug/BucketClientDebugTab.js

629 lines
18 KiB
JavaScript

/**
* Contains BucketClientDebugTab class
*
* Copyright (C) 2017 R-T Specialty, LLC.
*
* 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 ) )
)
)
);
}
}
} );