From 97f500fe247a88455bab6e72f5c8c06ee3029be7 Mon Sep 17 00:00:00 2001 From: Thomas Steur Date: Fri, 2 May 2014 04:00:06 +0200 Subject: [PATCH] refs #1915 a first version of persist & restore report settings. not everything is saved yet (such as metricsToPlot) but most things are --- core/Plugin/ViewDataTable.php | 24 +++++++- core/Plugin/Visualization.php | 14 ++++- core/ViewDataTable/Factory.php | 60 +++++++++++++++---- core/ViewDataTable/Manager.php | 33 ++++++++++ core/ViewDataTable/Request.php | 2 + core/ViewDataTable/RequestConfig.php | 23 ++++++- plugins/Actions/Actions.php | 4 -- plugins/CoreHome/Controller.php | 14 +++++ plugins/CoreHome/javascripts/dataTable.js | 38 ++++++++++-- .../javascripts/seriesPicker.js | 5 +- 10 files changed, 191 insertions(+), 26 deletions(-) diff --git a/core/Plugin/ViewDataTable.php b/core/Plugin/ViewDataTable.php index 069e08deab7..c49aef59ba8 100644 --- a/core/Plugin/ViewDataTable.php +++ b/core/Plugin/ViewDataTable.php @@ -11,6 +11,7 @@ use Piwik\API\Request; use Piwik\Common; use Piwik\DataTable; +use Piwik\Option; use Piwik\Period; use Piwik\Piwik; use Piwik\View; @@ -175,7 +176,7 @@ abstract class ViewDataTable implements ViewInterface * Posts the {@hook ViewDataTable.configure} event which plugins can use to configure the * way reports are displayed. */ - public function __construct($controllerAction, $apiMethodToRequestDataTable) + public function __construct($controllerAction, $apiMethodToRequestDataTable, $overrideParams = array()) { list($controllerName, $controllerAction) = explode('.', $controllerAction); @@ -229,6 +230,7 @@ public function __construct($controllerAction, $apiMethodToRequestDataTable) $this->requestConfig->filter_excludelowpop_value = $function(); } + $this->overrideViewPropertiesWithParams($overrideParams); $this->overrideViewPropertiesWithQueryParams(); } @@ -399,7 +401,7 @@ private function overrideViewPropertiesWithQueryParams() if (property_exists($this->requestConfig, $name)) { $this->requestConfig->$name = $this->getPropertyFromQueryParam($name, $this->requestConfig->$name); } elseif (property_exists($this->config, $name)) { - $this->config->$name = $this->getPropertyFromQueryParam($name, $this->config->$name); + $this->config->$name = $this->getPropertyFromQueryParam($name, $this->config->$name); } } @@ -456,4 +458,22 @@ public static function canDisplayViewDataTable(ViewDataTable $view) { return $view->config->show_all_views_icons; } + + private function overrideViewPropertiesWithParams($overrideParams) + { + if (empty($overrideParams)) { + return; + } + + foreach ($overrideParams as $key => $value) { + if (property_exists($this->requestConfig, $key)) { + $this->requestConfig->$key = $value; + } elseif (property_exists($this->config, $key)) { + $this->config->$key = $value; + } elseif ($key != 'enable_filter_excludelowpop') { + $this->config->custom_parameters[$key] = $value; + } + } + } + } diff --git a/core/Plugin/Visualization.php b/core/Plugin/Visualization.php index a8ab8292f49..e7a291d52c6 100644 --- a/core/Plugin/Visualization.php +++ b/core/Plugin/Visualization.php @@ -144,7 +144,7 @@ class Visualization extends ViewDataTable private $reportLastUpdatedMessage = null; private $metadata = null; - final public function __construct($controllerAction, $apiMethodToRequestDataTable) + final public function __construct($controllerAction, $apiMethodToRequestDataTable, $params = array()) { $templateFile = static::TEMPLATE_FILE; @@ -152,7 +152,7 @@ final public function __construct($controllerAction, $apiMethodToRequestDataTabl throw new \Exception('You have not defined a constant named TEMPLATE_FILE in your visualization class.'); } - parent::__construct($controllerAction, $apiMethodToRequestDataTable); + parent::__construct($controllerAction, $apiMethodToRequestDataTable, $params); } protected function buildView() @@ -305,6 +305,11 @@ private function applyFilters() $this->beforeGenericFiltersAreAppliedToLoadedDataTable(); + if (!in_array($this->requestConfig->filter_sort_column, $this->config->columns_to_display)) { + $hasNbUniqVisitors = in_array('nb_uniq_visitors', $this->config->columns_to_display); + $this->requestConfig->setDefaultSort($this->config->columns_to_display, $hasNbUniqVisitors); + } + if (!$this->requestConfig->areGenericFiltersDisabled()) { $this->applyGenericFilters(); } @@ -563,6 +568,11 @@ private function logMessageIfRequestPropertiesHaveChanged(array $requestProperti $diff = array_diff_assoc($this->makeSureArrayContainsOnlyStrings($requestProperties), $this->makeSureArrayContainsOnlyStrings($requestPropertiesBefore)); + if (!empty($diff['filter_sort_column'])) { + // this here might be ok as it can be changed after data loaded but before filters applied + unset($diff['filter_sort_column']); + } + if (empty($diff)) { return; } diff --git a/core/ViewDataTable/Factory.php b/core/ViewDataTable/Factory.php index 1f61892096c..398af85a96f 100644 --- a/core/ViewDataTable/Factory.php +++ b/core/ViewDataTable/Factory.php @@ -8,7 +8,6 @@ */ namespace Piwik\ViewDataTable; -use Piwik\API\Proxy; use Piwik\Common; use Piwik\Piwik; use Piwik\Plugins\CoreVisualizations\Visualizations\HtmlTable; @@ -68,20 +67,20 @@ class Factory /** * Creates a {@link Piwik\Plugin\ViewDataTable} instance by ID. If the **viewDataTable** query parameter is set, * this parameter's value is used as the ID. - * + * * See {@link Piwik\Plugin\ViewDataTable} to read about the visualizations that are packaged with Piwik. - * + * * @param string|null $defaultType A ViewDataTable ID representing the default ViewDataTable type to use. If * the **viewDataTable** query parameter is not found, this value is used as * the ID of the ViewDataTable to create. - * + * * If a visualization type is configured for the report being displayed, it * is used instead of the default type. (See {@hook ViewDataTable.getDefaultType}). * If nothing is configured for the report and `null` is supplied for this * argument, **table** is used. - * @param string|false $apiAction The API method for the report that will be displayed, eg, + * @param bool|false|string $apiAction The API method for the report that will be displayed, eg, * `'UserSettings.getBrowser'`. - * @param string|false $controllerAction The controller name and action dedicated to displaying the report. This + * @param bool|false|string $controllerAction The controller name and action dedicated to displaying the report. This * action is used when reloading reports or changing the report visualization. * Defaulted to `$apiAction` if `false` is supplied. * @param bool $forceDefault If true, then the visualization type that was configured for the report will be @@ -101,7 +100,22 @@ public static function build($defaultType = null, $apiAction = false, $controlle $defaultType = $defaultViewType; } - $type = Common::getRequestVar('viewDataTable', false, 'string'); + $isWidget = Common::getRequestVar('widget', '0', 'string'); + + if (!empty($isWidget)) { + $params = array(); + } else { + $login = Piwik::getCurrentUserLogin(); + $params = Manager::getViewDataTableParameters($login, $controllerAction); + } + + $savedViewDataTable = false; + if (!empty($params['viewDataTable'])) { + $savedViewDataTable = $params['viewDataTable']; + } + + $type = Common::getRequestVar('viewDataTable', $savedViewDataTable, 'string'); + // Common::getRequestVar removes backslashes from the defaultValue in case magic quotes are enabled. // therefore do not pass this as a default value to getRequestVar() if ('' === $type) { @@ -111,19 +125,19 @@ public static function build($defaultType = null, $apiAction = false, $controlle $visualizations = Manager::getAvailableViewDataTables(); if (array_key_exists($type, $visualizations)) { - return new $visualizations[$type]($controllerAction, $apiAction); + return self::createViewDataTableInstance($visualizations[$type], $controllerAction, $apiAction, $params); } if (class_exists($type)) { - return new $type($controllerAction, $apiAction); + return self::createViewDataTableInstance($type, $controllerAction, $apiAction, $params); } if (array_key_exists($defaultType, $visualizations)) { - return new $visualizations[$defaultType]($controllerAction, $apiAction); + return self::createViewDataTableInstance($visualizations[$defaultType], $controllerAction, $apiAction, $params); } if (array_key_exists(HtmlTable::ID, $visualizations)) { - return new $visualizations[HtmlTable::ID]($controllerAction, $apiAction); + return self::createViewDataTableInstance($visualizations[HtmlTable::ID], $controllerAction, $apiAction, $params); } throw new \Exception('No visualization found to render ViewDataTable'); @@ -171,4 +185,28 @@ private static function getDefaultTypeViewDataTable() return self::$defaultViewTypes; } + + /** + * @param string $klass + * @param string $controllerAction + * @param string $apiAction + * @param array $params + * + * @internal param string $viewDataTableId + * @return \Piwik\Plugin\ViewDataTable + */ + private static function createViewDataTableInstance($klass, $controllerAction, $apiAction, $params) + { + if (empty($params)) { + $params = array(); + } + + if (!is_subclass_of($klass, 'Piwik\Plugin\Visualization')) { + // for now we ignore those params in case it is not a visualization. We do not want to apply + // any of those saved parameters to sparklines etc. Need to find a better solution here + $params = array(); + } + + return new $klass($controllerAction, $apiAction, $params); + } } diff --git a/core/ViewDataTable/Manager.php b/core/ViewDataTable/Manager.php index 8f05f8b17ff..0f745932b1c 100644 --- a/core/ViewDataTable/Manager.php +++ b/core/ViewDataTable/Manager.php @@ -9,6 +9,7 @@ namespace Piwik\ViewDataTable; use Piwik\Common; +use Piwik\Option; use Piwik\Piwik; use Piwik\Plugin\ViewDataTable; use Piwik\Plugins\CoreVisualizations\Visualizations\Cloud; @@ -270,4 +271,36 @@ private static function getFooterIconFor($viewDataTableId) 'icon' => $klass::FOOTER_ICON, ); } + + public static function getViewDataTableParameters($login, $controllerAction) + { + $paramsKey = self::buildViewDataTableParametersOptionKey($login, $controllerAction); + $params = Option::get($paramsKey); + + if (empty($params)) { + return array(); + } + + $params = json_decode($params); + $params = (array) $params; + + return $params; + } + + public static function saveViewDataTableParameters($login, $controllerAction, $parametersToOverride) + { + $params = self::getViewDataTableParameters($login, $controllerAction); + + foreach ($parametersToOverride as $key => $value) { + $params[$key] = $value; + } + + $paramsKey = self::buildViewDataTableParametersOptionKey($login, $controllerAction); + Option::set($paramsKey, json_encode($params)); + } + + private static function buildViewDataTableParametersOptionKey($login, $controllerAction) + { + return sprintf('ReportConfig_%s_%s', $login, $controllerAction); + } } diff --git a/core/ViewDataTable/Request.php b/core/ViewDataTable/Request.php index 8acfc9d66e0..920927b6f08 100644 --- a/core/ViewDataTable/Request.php +++ b/core/ViewDataTable/Request.php @@ -73,6 +73,8 @@ public function getRequestArray() 'filter_excludelowpop_value', 'filter_column', 'filter_pattern', + 'flat', + 'expanded' ); foreach ($toSetEventually as $varToSet) { diff --git a/core/ViewDataTable/RequestConfig.php b/core/ViewDataTable/RequestConfig.php index ff4a6c0698a..42d55ec3cc9 100644 --- a/core/ViewDataTable/RequestConfig.php +++ b/core/ViewDataTable/RequestConfig.php @@ -88,7 +88,9 @@ class RequestConfig 'filter_excludelowpop_value', 'filter_pattern', 'filter_column', - 'filter_offset' + 'filter_offset', + 'flat', + 'expanded' ); /** @@ -104,7 +106,9 @@ class RequestConfig 'filter_excludelowpop', 'filter_excludelowpop_value', 'disable_generic_filters', - 'disable_queued_filters' + 'disable_queued_filters', + 'flat', + 'expanded' ); /** @@ -130,6 +134,21 @@ class RequestConfig */ public $filter_limit = false; + /** + * If set to true, the returned data will contain the flattened view of the table data set. + * The children of all first level rows will be aggregated under one row. + * + * Default value: false + */ + public $flat = false; + + /** + * If set to true, the returned data will contain the first level results, as well as all sub-tables. + * + * Default value: false + */ + public $expanded = false; + /** * The number of items from the start of the data set that should be ignored. * diff --git a/plugins/Actions/Actions.php b/plugins/Actions/Actions.php index 30da4b51fdc..08e1d682a5f 100644 --- a/plugins/Actions/Actions.php +++ b/plugins/Actions/Actions.php @@ -603,10 +603,6 @@ private function addBaseDisplayProperties(ViewDataTable $view) $view->config->show_embedded_subtable = true; } - // if the flat parameter is not provided, make sure it is set to 0 in the URL, - // so users can see that they can set it to 1 (see #3365) - $view->config->custom_parameters = array('flat' => 0); - if (Request::shouldLoadExpanded()) { if ($view->isViewDataTableId(HtmlTable::ID)) { diff --git a/plugins/CoreHome/Controller.php b/plugins/CoreHome/Controller.php index a68b1f2245a..a34a3e10992 100644 --- a/plugins/CoreHome/Controller.php +++ b/plugins/CoreHome/Controller.php @@ -15,7 +15,9 @@ use Piwik\FrontController; use Piwik\Menu\MenuMain; use Piwik\Notification\Manager as NotificationManager; +use Piwik\Option; use Piwik\Piwik; +use Piwik\ViewDataTable\Manager as ViewDataTableManager; use Piwik\Plugins\CoreHome\DataTableRowAction\MultiRowEvolution; use Piwik\Plugins\CoreHome\DataTableRowAction\RowEvolution; use Piwik\Plugins\CorePluginsAdmin\MarketplaceApiClient; @@ -239,4 +241,16 @@ public function getPeriodSelector() $this->setGeneralVariablesView($view); return $view->render(); } + + public function saveViewDataTableParameters() + { + Piwik::checkUserIsNotAnonymous(); + $this->checkTokenInUrl(); + + $reportId = Common::getRequestVar('report_id', null, 'string'); + $parameters = (array) Common::getRequestVar('parameters', null, 'json'); + $login = Piwik::getCurrentUserLogin(); + + ViewDataTableManager::saveViewDataTableParameters($login, $reportId, $parameters); + } } diff --git a/plugins/CoreHome/javascripts/dataTable.js b/plugins/CoreHome/javascripts/dataTable.js index 7fb018cc1d5..015a428b137 100644 --- a/plugins/CoreHome/javascripts/dataTable.js +++ b/plugins/CoreHome/javascripts/dataTable.js @@ -115,6 +115,13 @@ $.extend(DataTable.prototype, UIControl.prototype, { } self.param.filter_offset = 0; self.param.filter_sort_column = newColumnToSort; + + if (!self.isDashboard()) { + self.notifyWidgetParametersChange(domElem, { + filter_sort_column: newColumnToSort + }); + } + self.reloadAjaxDataTable(); }, @@ -229,7 +236,6 @@ $.extend(DataTable.prototype, UIControl.prototype, { ajaxRequest.setFormat('html'); ajaxRequest.send(false); - }, // Function called when the AJAX request is successful @@ -1083,6 +1089,7 @@ $.extend(DataTable.prototype, UIControl.prototype, { // no manipulation when loading subtables return; } + if ((typeof self.numberOfSubtables == 'undefined' || self.numberOfSubtables == 0) && (typeof self.param.flat == 'undefined' || self.param.flat != 1)) { // if there are no subtables, remove the flatten action @@ -1227,7 +1234,26 @@ $.extend(DataTable.prototype, UIControl.prototype, { notifyWidgetParametersChange: function (domWidget, parameters) { var widget = $(domWidget).closest('[widgetId]'); // trigger setParameters event on base element - widget.trigger('setParameters', parameters); + + if (widget && widget.length) { + widget.trigger('setParameters', parameters); + } else { + + var reportId = $(domWidget).closest('[data-report]').attr('data-report'); + + var ajaxRequest = new ajaxHelper(); + ajaxRequest.addParams({ + module: 'CoreHome', + action: 'saveViewDataTableParameters', + report_id: reportId + }, 'get'); + ajaxRequest.addParams({ + parameters: JSON.stringify(parameters) + }, 'post'); + ajaxRequest.setCallback(function () {}); + ajaxRequest.setFormat('html'); + ajaxRequest.send(false); + } }, tooltip: function (domElement) { @@ -1287,11 +1313,15 @@ $.extend(DataTable.prototype, UIControl.prototype, { } var self = this; - function toggleFooter() + function toggleFooter(event) { var icons = $('.dataTableFooterIcons', domElem); $('.dataTableFeatures', domElem).toggleClass('expanded'); + if (event && event.doNotNotifyChange) { + return; + } + self.notifyWidgetParametersChange(domElem, { isFooterExpandedInDashboard: icons.is(':visible') }); @@ -1317,7 +1347,7 @@ $.extend(DataTable.prototype, UIControl.prototype, { } if (this.param.isFooterExpandedInDashboard) { - toggleFooter(); + toggleFooter({doNotNotifyChange: true}); } $('.foldDataTableFooterDrawer, .expandDataTableFooterDrawer', domElem).on('click', toggleFooter); diff --git a/plugins/CoreVisualizations/javascripts/seriesPicker.js b/plugins/CoreVisualizations/javascripts/seriesPicker.js index dbd2df0ae92..04bec3d6a9a 100644 --- a/plugins/CoreVisualizations/javascripts/seriesPicker.js +++ b/plugins/CoreVisualizations/javascripts/seriesPicker.js @@ -272,7 +272,10 @@ $(this).trigger('seriesPicked', [columns, rows]); // inform dashboard widget about changed parameters (to be restored on reload) - $('#' + this.dataTableId).closest('[widgetId]').trigger('setParameters', {columns: columns, rows: rows}); + var UI = require('piwik/UI') + var params = {columns: columns, rows: rows}; + var tableNode = $('#' + this.dataTableId); + UI.DataTable.prototype.notifyWidgetParametersChange(tableNode, params); } }