96212ec5608b3aa9280d3db48bbb39442d07528e

Author: chawbacca

Date: 2009-03-26 09:58:39 -0500

Merge from debug_kit.git

diff --git a/controllers/components/toolbar.php b/controllers/components/toolbar.php index 7028fd8..9b65a71 100644 --- a/controllers/components/toolbar.php +++ b/controllers/components/toolbar.php @@ -92,7 +92,7 @@ class ToolbarComponent extends Object { return false; } App::import('Vendor', 'DebugKit.DebugKitDebugger'); - + DebugKitDebugger::startTimer('componentInit', __('Component initialization and startup', true)); $panels = $this->_defaultPanels; @@ -104,14 +104,14 @@ class ToolbarComponent extends Object { if (!isset($settings['history']) || (isset($settings['history']) && $settings['history'] !== false)) { $this->_createCacheConfig(); } - + if (isset($settings['javascript'])) { $settings['javascript'] = $this->_setJavascript($settings['javascript']); } else { $settings['javascript'] = $this->_defaultJavascript; } $this->_loadPanels($panels, $settings); - + $this->_set($settings); $this->controller =& $controller; return false; @@ -127,7 +127,7 @@ class ToolbarComponent extends Object { $this->_makeViewClass($currentViewClass); $controller->view = 'DebugKit.Debug'; $isHtml = ( - !isset($controller->params['url']['ext']) || + !isset($controller->params['url']['ext']) || (isset($controller->params['url']['ext']) && $controller->params['url']['ext'] == 'html') ); @@ -202,7 +202,7 @@ class ToolbarComponent extends Object { * * @return array Array of all panel beforeRender() * @access protected - **/ + **/ function _gatherVars(&$controller) { $vars = array(); $panels = array_keys($this->panels); @@ -282,7 +282,7 @@ class ToolbarComponent extends Object { * Makes the DoppleGangerView class if it doesn't already exist. * This allows DebugView to be compatible with all view classes. * - * @param string $baseClassName + * @param string $baseClassName * @access protected * @return void */ @@ -372,7 +372,7 @@ class HistoryPanel extends DebugPanel { var $history = 5; /** * Constructor - * + * * @param array $settings Array of settings. * @return void **/ @@ -506,6 +506,13 @@ class TimerPanel extends DebugPanel { class sqlLogPanel extends DebugPanel { var $plugin = 'debug_kit'; /** + * Minimum number of Rows Per Millisecond that must be returned by a query before an explain + * is done. + * + * @var int + **/ + var $slowRate = 20; +/** * Get Sql Logs for each DB config * * @param string $controller @@ -513,21 +520,87 @@ class sqlLogPanel extends DebugPanel { * @return void */ function beforeRender(&$controller) { - $queryLogs = array(); if (!class_exists('ConnectionManager')) { return array(); } + App::import('Core', 'Xml'); + $queryLogs = array(); + $dbConfigs = ConnectionManager::sourceList(); foreach ($dbConfigs as $configName) { $db =& ConnectionManager::getDataSource($configName); if ($db->isInterfaceSupported('showLog')) { ob_start(); $db->showLog(); - $queryLogs[$configName] = ob_get_clean(); + $htmlBlob = ob_get_clean(); + + $Xml =& new Xml($htmlBlob); + $sqlLog = $Xml->toArray(); + if (empty($sqlLog['Table']['Tbody']['Tr'])) { + continue; + } + $queries = $explained = array(); + foreach ($sqlLog['Table']['Tbody']['Tr'] as $query) { + $tds = $this->_restructureCells($query['Td']); + $queries[] = $tds; + $isSlow = ($tds[5] > 0 && $tds[4] / $tds[5] <= $this->slowRate); + if ($isSlow && preg_match('/^SELECT /', $tds[1])) { + $explain = $this->_explainQuery($db, $tds[1]); + if (!empty($explain)) { + $explained[] = $explain; + } + } + } + $queryLogs[$configName]['queries'] = $queries; + $queryLogs[$configName]['explains'] = $explained; } } return $queryLogs; } +/** + * Restructure a row if error cell is empty + * + * @return void + **/ + function _restructureCells($tds) { + if (count($tds) == 5) { + $tds[5] = $tds[4]['value']; + $tds[4] = $tds[3]['value']; + $tds[3] = $tds[2]['value']; + $tds[2] = ''; + } else { + $tds[2] = $tds[2]['value']; + $tds[3] = $tds[3]['value']; + $tds[4] = $tds[4]['value']; + $tds[5] = $tds[5]['value']; + } + return $tds; + } +/** + * Run an explain query for a slow query. + * + * @param object $db Dbo instance + * @param string $queryString The Query to explain + * @access public + * @return void + **/ + function _explainQuery(&$db, $queryString) { + $driver = $db->config['driver']; + $results = null; + if ($driver === 'myslqi' || $driver === 'mysql' || $driver === 'postgres') { + $results = $db->query('EXPLAIN ' . $queryString); + if ($driver === 'postgres') { + $queryPlan = array(); + foreach ($results as $postgreValue) { + $queryPlan[] = $postgreValue[0]['QUERY PLAN']; + } + $results[0][0] = array('Query Plan' => implode("<br />", $queryPlan)); + } + $results = $results[0][0]; + $results['query'] = $queryString; + } + return $results; + } } /** @@ -591,4 +664,5 @@ class LogPanel extends DebugPanel { return array_values($chunks); } } + ?> \ No newline at end of file diff --git a/controllers/toolbar_access_controller.php b/controllers/toolbar_access_controller.php index 2085470..b45f2e5 100644 --- a/controllers/toolbar_access_controller.php +++ b/controllers/toolbar_access_controller.php @@ -72,6 +72,9 @@ class ToolbarAccessController extends DebugKitAppController { * @return void **/ function history_state($key = null) { + if (Configure::read('debug') == 0) { + return $this->redirect($this->referer()); + } $oldState = $this->Toolbar->loadState($key); $this->set('toolbarState', $oldState); $this->set('debugKitInHistoryMode', true); diff --git a/tests/cases/controllers/components/toolbar.test.php b/tests/cases/controllers/components/toolbar.test.php index ae32e41..fbfe3be 100644 --- a/tests/cases/controllers/components/toolbar.test.php +++ b/tests/cases/controllers/components/toolbar.test.php @@ -18,12 +18,8 @@ * @filesource * @copyright Copyright 2006-2008, Cake Software Foundation, Inc. * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project - * @package cake - * @subpackage cake.cake.libs. - * @since CakePHP v 1.2.0.4487 - * @version $Revision$ - * @modifiedby $LastChangedBy$ - * @lastmodified $Date$ + * @package debug_kit + * @subpackage debug_kit.tests * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ App::import('Component', 'DebugKit.Toolbar'); @@ -36,20 +32,49 @@ class TestToolbarComponent extends ToolbarComponent { Mock::generate('DebugPanel'); +if (!class_exists('AppController')) { + class AppController extends Controller { + + } +} + /** * DebugToolbar Test case */ class DebugToolbarTestCase extends CakeTestCase { - function setUp() { + function startTest() { Router::connect('/', array('controller' => 'pages', 'action' => 'display', 'home')); $this->Controller =& ClassRegistry::init('Controller'); $this->Controller->params = Router::parse('/'); $this->Controller->params['url']['url'] = '/'; $this->Controller->Component =& ClassRegistry::init('Component'); $this->Controller->Toolbar =& ClassRegistry::init('TestToolBarComponent', 'Component'); + + $this->_server = $_SERVER; + $this->_paths = array(); + $this->_paths['plugin'] = Configure::read('pluginPaths'); + $this->_paths['view'] = Configure::read('viewPaths'); + $this->_paths['vendor'] = Configure::read('vendorPaths'); + $this->_paths['controller'] = Configure::read('controllerPaths'); } +/** + * endTest + * + * @return void + **/ + function endTest() { + $_SERVER = $this->_server; + Configure::write('pluginPaths', $this->_paths['plugin']); + Configure::write('viewPaths', $this->_paths['view']); + Configure::write('vendorPaths', $this->_paths['vendor']); + Configure::write('controllerPaths', $this->_paths['controller']); + unset($this->Controller); + if (class_exists('DebugKitDebugger')) { + DebugKitDebugger::clearTimers(); + } + } /** * test Loading of panel classes * @@ -72,7 +97,6 @@ class DebugToolbarTestCase extends CakeTestCase { */ function testVendorPanels() { $f = Configure::read('pluginPaths'); - $_back = Configure::read('vendorPaths'); Configure::write('vendorPaths', array($f[1] . 'debug_kit' . DS . 'tests' . DS . 'test_app' . DS . 'vendors' . DS)); $this->Controller->components = array( 'DebugKit.Toolbar' => array( @@ -84,8 +108,6 @@ class DebugToolbarTestCase extends CakeTestCase { $this->Controller->Component->startup($this->Controller); $this->assertTrue(isset($this->Controller->Toolbar->panels['test'])); $this->assertTrue(is_a($this->Controller->Toolbar->panels['test'], 'TestPanel')); - - Configure::write('vendorPaths', $_back); } /** @@ -356,15 +378,28 @@ class DebugToolbarTestCase extends CakeTestCase { $this->assertEqual($this->Controller->helpers['DebugKit.Toolbar']['output'], 'DebugKit.FirePhpToolbar'); } /** - * teardown + * Test that the toolbar does not interfere with requestAction * * @return void **/ - function tearDown() { - unset($this->Controller); - if (class_exists('DebugKitDebugger')) { - DebugKitDebugger::clearTimers(); - } + function testNoRequestActionInterference() { + $f = Configure::read('pluginPaths'); + $testapp = $f[1] . 'debug_kit' . DS . 'tests' . DS . 'test_app' . DS . 'controllers' . DS; + array_unshift($f, $testapp); + Configure::write('controllerPaths', $f); + + $plugins = Configure::read('pluginPaths'); + $views = Configure::read('viewPaths'); + $testapp = $plugins[1] . 'debug_kit' . DS . 'tests' . DS . 'test_app' . DS . 'views' . DS; + array_unshift($views, $testapp); + Configure::write('viewPaths', $views); + + $result = $this->Controller->requestAction('/debug_kit_test/request_action_return', array('return')); + $this->assertEqual($result, 'I am some value from requestAction.'); + + $result = $this->Controller->requestAction('/debug_kit_test/request_action_render', array('return')); + $this->assertEqual($result, 'I have been rendered.'); } + } ?> \ No newline at end of file diff --git a/tests/cases/views/debug.test.php b/tests/cases/views/debug.test.php index 916a67d..17ce7c1 100644 --- a/tests/cases/views/debug.test.php +++ b/tests/cases/views/debug.test.php @@ -45,12 +45,32 @@ class DebugViewTestCase extends CakeTestCase { * * @return void **/ - function setUp() { + function startTest() { Router::connect('/', array('controller' => 'pages', 'action' => 'display', 'home')); Router::parse('/'); $this->Controller =& ClassRegistry::init('Controller'); $this->View =& new DebugView($this->Controller, false); $this->_debug = Configure::read('debug'); + $this->_paths = array(); + $this->_paths['plugin'] = Configure::read('pluginPaths'); + $this->_paths['view'] = Configure::read('viewPaths'); + $this->_paths['vendor'] = Configure::read('vendorPaths'); + $this->_paths['controller'] = Configure::read('controllerPaths'); + } +/** + * tear down function + * + * @return void + **/ + function endTest() { + Configure::write('pluginPaths', $this->_paths['plugin']); + Configure::write('viewPaths', $this->_paths['view']); + Configure::write('vendorPaths', $this->_paths['vendor']); + Configure::write('controllerPaths', $this->_paths['controller']); + + unset($this->View, $this->Controller); + DebugKitDebugger::clearTimers(); + Configure::write('debug', $this->_debug); } /** @@ -120,25 +140,25 @@ class DebugViewTestCase extends CakeTestCase { $this->assertTrue(is_object($result['Javascript'])); $this->assertTrue(is_object($result['Number'])); } - /** - * reset the view paths + * test that $out is returned when a layout is rendered instead of the empty + * $this->output. As this causes issues with requestAction() * * @return void **/ - function endCase() { - Configure::write('viewPaths', $this->_viewPaths); - } + function testProperReturnUnderRequestAction() { + $plugins = Configure::read('pluginPaths'); + $views = Configure::read('viewPaths'); + $testapp = $plugins[1] . 'debug_kit' . DS . 'tests' . DS . 'test_app' . DS . 'views' . DS; + array_unshift($views, $testapp); + Configure::write('viewPaths', $views); + + $this->View->set('test', 'I have been rendered.'); + $this->View->viewPath = 'debug_kit_test'; + $this->View->layout = false; + $result = $this->View->render('request_action_render'); -/** - * tear down function - * - * @return void - **/ - function tearDown() { - unset($this->View, $this->Controller); - DebugKitDebugger::clearTimers(); - Configure::write('debug', $this->_debug); + $this->assertEqual($result, 'I have been rendered.'); } } ?> \ No newline at end of file diff --git a/tests/test_app/controllers/debug_kit_test_controller.php b/tests/test_app/controllers/debug_kit_test_controller.php new file mode 100644 index 0000000..7ee1d13 --- /dev/null +++ b/tests/test_app/controllers/debug_kit_test_controller.php @@ -0,0 +1,38 @@ +<?php +/* SVN FILE: $Id$ */ +/** + * DebugKitTestController + * + * + * + * PHP versions 4 and 5 + * + * CakePHP : Rapid Development Framework <http://www.cakephp.org/> + * Copyright 2006-2008, Cake Software Foundation, Inc. + * 1785 E. Sahara Avenue, Suite 490-204 + * Las Vegas, Nevada 89104 + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @filesource + * @copyright Copyright 2006-2008, Cake Software Foundation, Inc. + * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project + * @package debug_kit + * @subpackage tests.test_app.controllers + * @license http://www.opensource.org/licenses/mit-license.php The MIT License + */ +class DebugKitTestController extends Controller { + var $uses = array(); + var $components = array('DebugKit.Toolbar'); + + function request_action_return() { + $this->autoRender = false; + return 'I am some value from requestAction.'; + } + + function request_action_render() { + $this->set('test', 'I have been rendered.'); + } + +} \ No newline at end of file diff --git a/tests/test_app/views/debug_kit_test/request_action_render.ctp b/tests/test_app/views/debug_kit_test/request_action_render.ctp new file mode 100644 index 0000000..172d541 --- /dev/null +++ b/tests/test_app/views/debug_kit_test/request_action_render.ctp @@ -0,0 +1 @@ +<?php echo $test; ?> \ No newline at end of file diff --git a/vendors/css/debug_toolbar.css b/vendors/css/debug_toolbar.css index 8e6258c..6106bcb 100644 --- a/vendors/css/debug_toolbar.css +++ b/vendors/css/debug_toolbar.css @@ -1,4 +1,4 @@ -/* @override http://localhost/cake_debug_kit/debug_kit/css/debug_toolbar.css */ +/* @override http://localhost/mark_story/site/debug_kit/css/debug_toolbar.css */ #debug-kit-toolbar { position: fixed; top: 0px; @@ -8,7 +8,9 @@ overflow: visible; z-index:10000; font-family: helvetica, arial, sans-serif; + font-size: 12px; } + /* panel tabs */ #debug-kit-toolbar #panel-tabs { float: right; @@ -22,7 +24,6 @@ padding: 0; list-style: none; } - #debug-kit-toolbar .panel-tab a { float: left; clear: none; @@ -48,6 +49,12 @@ #debug-kit-toolbar .panel-tab a + .panel-content:hover { display: block; } +#debug-kit-toolbar .panel-tab.icon a{ + -webkit-border-top-left-radius: 8px 8px; + -webkit-border-bottom-left-radius: 8px 8px; + -moz-border-radius-topleft: 8px; + -moz-border-radius-bottomleft: 8px +} /* panel content */ #debug-kit-toolbar .panel-content { @@ -69,7 +76,6 @@ .panel-content { display: none; } - .panel-content p { margin: 1em 0; } @@ -92,10 +98,21 @@ #debug-kit-toolbar h4, #debug-kit-toolbar h5, #debug-kit-toolbar th { + color: #5d1717; font-family: "Trebuchet MS", trebuchet, helvetica, arial, sans-serif; margin-bottom:0.6em; background:none; } +#debug-kit-toolbar h1 { + font-size: 18px; +} +#debug-kit-toolbar h2 { + font-size: 16px; +} +#debug-kit-toolbar h4 { + font-size: 14px; +} + /* panel tables */ #debug-kit-toolbar table.debug-table { @@ -110,7 +127,8 @@ } #debug-kit-toolbar table.debug-table th { border-bottom: 1px solid #222; - background: 0;; + background: 0; + color: #252525; } #debug-kit-toolbar table.debug-table tr:nth-child(2n+1) td { background:#f6f6f6; @@ -136,7 +154,6 @@ display: block; } - /** Neat Array styles **/ #debug-kit-toolbar .neat-array, #debug-kit-toolbar .neat-array li { diff --git a/vendors/js/js_debug_toolbar.js b/vendors/js/js_debug_toolbar.js index dffef71..8b71e4f 100755 --- a/vendors/js/js_debug_toolbar.js +++ b/vendors/js/js_debug_toolbar.js @@ -92,6 +92,10 @@ DEBUGKIT.Util.Element = { hide : function (element) { element.style.display = 'none'; + }, + + toggle : function (element) { + element.style.display == 'none' ? this.show(element) : this.hide(element); } }; diff --git a/views/debug.php b/views/debug.php index d296bc6..b950292 100644 --- a/views/debug.php +++ b/views/debug.php @@ -84,6 +84,9 @@ class DebugView extends DoppelGangerView { if (isset($this->loaded['toolbar'])) { $this->loaded['toolbar']->postRender(); } + if (empty($this->output)) { + return $out; + } //Temporary work around to hide the SQL dump at page bottom Configure::write('debug', 0); return $this->output; diff --git a/views/elements/sql_log_panel.ctp b/views/elements/sql_log_panel.ctp index 56c20fa..7e7e2f6 100644 --- a/views/elements/sql_log_panel.ctp +++ b/views/elements/sql_log_panel.ctp @@ -32,9 +32,50 @@ <?php foreach ($content as $dbName => $queryLog) : ?> <div class="sql-log-panel-query-log"> <h4><?php echo $dbName ?></h4> - <?php echo $queryLog; ?> + <?php + $headers = array('Nr', 'Query', 'Error', 'Affected', 'Num. rows', 'Took (ms)'); + echo $toolbar->table($queryLog['queries'], $headers, array('title' => 'SQL Log ' . $dbName)); + + if (!empty($queryLog['explains'])): + $name = sprintf(__('toggle (%s) query explains for %s', true), count($queryLog['explains']), $dbName); + echo $html->link($name, '#', array('class' => 'show-slow')); + + echo '<div class="slow-query-container">'; + $headers = array_keys($queryLog['explains'][0]); + echo $toolbar->table($queryLog['explains'], $headers, array('title' => 'Slow Queries ' . $dbName)); + echo '</div>'; + else: + echo $toolbar->message('Warning', __('No slow queries!, or your database does not support EXPLAIN', true)); + endif; ?> </div> <?php endforeach; ?> -<?php else: ?> - <p class="warning"><?php __('No active database connections'); ?></p> -<?php endif; ?> \ No newline at end of file +<?php else: + echo $toolbar->message('Warning', __('No active database connections', true)); +endif; ?> + +<script type="text/javascript"> +DEBUGKIT.module('sqlLog'); +DEBUGKIT.sqlLog = function () { + var Element = DEBUGKIT.Util.Element, + Event = DEBUGKIT.Util.Event; + + return { + init : function () { + var sqlPanel = document.getElementById('sql_log-tab'); + var buttons = sqlPanel.getElementsByTagName('A'); + for (var i in buttons) { + var button = buttons[i]; + if (Element.hasClass(button, 'show-slow')) { + var nextDiv = button.nextSibling; + Event.addEvent(button, 'click', function (event) { + event.preventDefault(); + Element.toggle(nextDiv); + }); + Element.hide(nextDiv); + } + } + } + }; +}(); +DEBUGKIT.loader.register(DEBUGKIT.sqlLog); +</script> \ No newline at end of file