From ad9ab5d884bab393467b4b9140e3460f99b46f8b Mon Sep 17 00:00:00 2001
From: Thomas Steur <thomas.steur@googlemail.com>
Date: Thu, 6 Mar 2014 05:26:25 +0100
Subject: [PATCH] refs #57 more tweaks to insights and movers and shakers and
 more tests

---
 plugins/Insights/API.php                      | 155 +++++++++++-------
 plugins/Insights/Controller.php               |  16 +-
 .../Insights/DataTable/Filter/MinGrowth.php   |  14 +-
 plugins/Insights/InsightReport.php            |  90 ++++++++--
 plugins/Insights/Visualizations/Insight.php   |  46 ++++--
 .../Visualizations/Insight/Config.php         |  17 --
 .../Insights/javascripts/insightsDataTable.js |  15 +-
 plugins/Insights/lang/en.json                 |   6 +-
 .../stylesheets/insightVisualization.less     |   5 +-
 .../templates/cannotDisplayReport.twig        |   3 +
 .../Insights/templates/insightControls.twig   |  17 +-
 .../templates/insightVisualization.twig       |  57 ++++---
 .../templates/insightsOverviewWidget.twig     |  10 ++
 .../moversAndShakersOverviewWidget.twig       |  10 ++
 .../Insights/templates/overviewWidget.twig    |  20 +--
 plugins/Insights/templates/table_row.twig     |   5 +-
 plugins/Insights/tests/ApiTest.php            |   9 +-
 .../Insights/tests/FilterMinGrowthTest.php    |  23 ++-
 plugins/Insights/tests/InsightReportTest.php  | 137 ++++++++++++----
 19 files changed, 448 insertions(+), 207 deletions(-)
 delete mode 100644 plugins/Insights/Visualizations/Insight/Config.php
 create mode 100644 plugins/Insights/templates/cannotDisplayReport.twig
 create mode 100644 plugins/Insights/templates/insightsOverviewWidget.twig
 create mode 100644 plugins/Insights/templates/moversAndShakersOverviewWidget.twig

diff --git a/plugins/Insights/API.php b/plugins/Insights/API.php
index 0305caaf8a..3be7ac495e 100644
--- a/plugins/Insights/API.php
+++ b/plugins/Insights/API.php
@@ -33,16 +33,20 @@ class API extends \Piwik\Plugin\API
      */
     private $model;
 
+    /**
+     * Those reports will be included in the insight / moversAndShakers overview reports.
+     * You can configure any API parameter for each report such as "flat", "limitIncreaser", "minGrowth", ...
+     * @var array
+     */
     private $reportIds = array(
-        'Actions_getPageUrls',
-        'Actions_getPageTitles',
-        'Actions_getDownloads',
-        'Referrers_getAll',
-        'Referrers_getKeywords',
-        'Referrers_getCampaigns',
-        'Referrers_getSocials',
-        'Referrers_getSearchEngines',
-        'UserCountry_getCountry',
+        'Actions_getPageUrls' => array(),
+        'Actions_getPageTitles' => array(),
+        'Actions_getDownloads' => array('flat' => 1),
+        'Referrers_getWebsites' => array(),
+        'Referrers_getCampaigns' => array(),
+        'Referrers_getSocials' => array(),
+        'Referrers_getSearchEngines' => array(),
+        'UserCountry_getCountry' => array(),
     );
 
     protected function __construct()
@@ -52,45 +56,69 @@ class API extends \Piwik\Plugin\API
         $this->model = new Model();
     }
 
-    public function getInsightsOverview($idSite, $period, $date, $segment = false)
+    public function canGenerateInsights($period, $date)
     {
-        Piwik::checkUserHasViewAccess(array($idSite));
+        try {
+            $model    = new Model();
+            $lastDate = $model->getLastDate($date, $period, 1);
+        } catch (\Exception $e) {
+            return false;
+        }
 
-        $reportTableIds = array();
+        if (empty($lastDate)) {
+            return false;
+        }
 
-        /** @var DataTable[] $tables */
-        $tables = array();
-        foreach ($this->reportIds as $reportId) {
-            $firstTableId     = DataTable\Manager::getInstance()->getMostRecentTableId();
-            $table            = $this->getInsights($idSite, $period, $date, $reportId, $segment, 3, 3, '', 2, 25);
-            $reportTableIds[] = $table->getId();
-            DataTable\Manager::getInstance()->deleteTablesExceptIgnored($reportTableIds, $firstTableId);
+        return true;
+    }
 
-            $tables[] = $table;
-        }
+    public function getInsightsOverview($idSite, $period, $date, $segment = false)
+    {
+        Piwik::checkUserHasViewAccess(array($idSite));
 
-        $map = new DataTable\Map();
+        $defaultParams = array(
+            'limitIncreaser' => 3,
+            'limitDecreaser' => 3,
+            'minImpactPercent' => 2,
+            'minGrowthPercent' => 25,
+        );
 
-        foreach ($tables as $table) {
-            $map->addTable($table, $table->getMetadata('reportName'));
-        }
+        $map = $this->generateOverviewReport('getInsights', $idSite, $period, $date, $segment, $defaultParams);
 
         return $map;
     }
 
-    public function getOverallMoversAndShakers($idSite, $period, $date, $segment = false)
+    public function getMoversAndShakersOverview($idSite, $period, $date, $segment = false)
     {
         Piwik::checkUserHasViewAccess(array($idSite));
 
-        $reportTableIds = array();
+        $defaultParams = array(
+            'limitIncreaser' => 4,
+            'limitDecreaser' => 4
+        );
+
+        $map = $this->generateOverviewReport('getMoversAndShakers', $idSite, $period, $date, $segment, $defaultParams);
+
+        return $map;
+    }
+
+    private function generateOverviewReport($method, $idSite, $period, $date, $segment, array $defaultParams)
+    {
+        $tableManager = DataTable\Manager::getInstance();
 
         /** @var DataTable[] $tables */
         $tables = array();
-        foreach ($this->reportIds as $reportId) {
-            $firstTableId     = DataTable\Manager::getInstance()->getMostRecentTableId();
-            $table            = $this->getMoversAndShakers($idSite, $period, $date, $reportId, $segment, 4, 4);
+        foreach ($this->reportIds as $reportId => $reportParams) {
+            foreach ($defaultParams as $key => $defaultParam) {
+                if (!array_key_exists($key, $reportParams)) {
+                    $reportParams[$key] = $defaultParam;
+                }
+            }
+
+            $firstTableId     = $tableManager->getMostRecentTableId();
+            $table            = $this->requestApiMethod($method, $idSite, $period, $date, $reportId, $segment, $reportParams);
             $reportTableIds[] = $table->getId();
-            DataTable\Manager::getInstance()->deleteTablesExceptIgnored($reportTableIds, $firstTableId);
+            $tableManager->deleteTablesExceptIgnored($reportTableIds, $firstTableId);
 
             $tables[] = $table;
         }
@@ -105,37 +133,24 @@ class API extends \Piwik\Plugin\API
     }
 
     public function getMoversAndShakers($idSite, $period, $date, $reportUniqueId, $segment = false,
-                                        $limitIncreaser = 4, $limitDecreaser = 4)
+                                        $comparedToXPeriods = 1, $limitIncreaser = 4, $limitDecreaser = 4)
     {
-        $orderBy = 'absolute';
-        $minGrowthPercent = 30;
-        $minMoversPercent = 2;
-        $minNewPercent = 2;
-        $minDisappearedPercent = 2;
-
         Piwik::checkUserHasViewAccess(array($idSite));
 
-        $metric = 'nb_visits';
+        $metric  = 'nb_visits';
+        $orderBy = InsightReport::ORDER_BY_ABSOLUTE;
 
         $reportMetadata = $this->model->getReportByUniqueId($idSite, $reportUniqueId);
+
         $totalValue     = $this->model->getTotalValue($idSite, $period, $date, $metric);
         $currentReport  = $this->model->requestReport($idSite, $period, $date, $reportUniqueId, $metric, $segment);
 
-        if ($period === 'day') {
-            // if website is too young, than use website creation date
-            // for faster performance just compare against last week?
-            $pastDate   = $this->model->getLastDate($date, $period, 7);
-            $lastReport = $this->model->requestReport($idSite, 'week', $pastDate, $reportUniqueId, $metric, $segment);
-            $lastReport->filter('Piwik\Plugins\Insights\DataTable\Filter\Average', array($metric, 7));
-            $lastDate   = Range::factory('week', $pastDate);
-            $lastDate   = $lastDate->getRangeString();
-        } else {
-            $lastDate   = $this->model->getLastDate($date, $period, 1);
-            $lastReport = $this->model->requestReport($idSite, $period, $lastDate, $reportUniqueId, $metric, $segment);
-        }
+        $lastDate       = $this->model->getLastDate($date, $period, $comparedToXPeriods);
+        $lastTotalValue = $this->model->getTotalValue($idSite, $period, $lastDate, $metric);
+        $lastReport     = $this->model->requestReport($idSite, $period, $lastDate, $reportUniqueId, $metric, $segment);
 
         $insight = new InsightReport();
-        return $insight->generateInsight($reportMetadata, $period, $date, $lastDate, $metric, $currentReport, $lastReport, $totalValue, $minMoversPercent, $minNewPercent, $minDisappearedPercent, $minGrowthPercent, $orderBy, $limitIncreaser, $limitDecreaser);
+        return $insight->generateMoverAndShaker($reportMetadata, $period, $date, $lastDate, $metric, $currentReport, $lastReport, $totalValue, $lastTotalValue, $orderBy, $limitIncreaser, $limitDecreaser);
     }
 
     public function getInsights(
@@ -148,11 +163,15 @@ class API extends \Piwik\Plugin\API
         $metric = 'nb_visits';
 
         $reportMetadata = $this->model->getReportByUniqueId($idSite, $reportUniqueId);
-        $lastDate       = $this->model->getLastDate($date, $period, $comparedToXPeriods);
+
         $currentReport  = $this->model->requestReport($idSite, $period, $date, $reportUniqueId, $metric, $segment);
-        $lastReport     = $this->model->requestReport($idSite, $period, $lastDate, $reportUniqueId, $metric, $segment);
         $totalValue     = $this->model->getRelevantTotalValue($currentReport, $idSite, $period, $date, $metric);
 
+        $lastDate       = $this->model->getLastDate($date, $period, $comparedToXPeriods);
+        $lastReport     = $this->model->requestReport($idSite, $period, $lastDate, $reportUniqueId, $metric, $segment);
+
+        $minGrowthPercentPositive = abs($minGrowthPercent);
+        $minGrowthPercentNegative = -1 * $minGrowthPercentPositive;
         $minMoversPercent = -1;
         $minNewPercent = -1;
         $minDisappearedPercent = -1;
@@ -174,6 +193,32 @@ class API extends \Piwik\Plugin\API
         }
 
         $insight = new InsightReport();
-        return $insight->generateInsight($reportMetadata, $period, $date, $lastDate, $metric, $currentReport, $lastReport, $totalValue, $minMoversPercent, $minNewPercent, $minDisappearedPercent, $minGrowthPercent, $orderBy, $limitIncreaser, $limitDecreaser);
+        return $insight->generateInsight($reportMetadata, $period, $date, $lastDate, $metric, $currentReport, $lastReport, $totalValue, $minMoversPercent, $minNewPercent, $minDisappearedPercent, $minGrowthPercentPositive, $minGrowthPercentNegative, $orderBy, $limitIncreaser, $limitDecreaser);
     }
+
+    private function requestApiMethod($method, $idSite, $period, $date, $reportId, $segment, $additionalParams)
+    {
+        $params = array(
+            'method' => 'Insights.' . $method,
+            'idSite' => $idSite,
+            'date'   => $date,
+            'period' => $period,
+            'format' => 'original',
+            'reportUniqueId' => $reportId,
+        );
+
+        if ($segment) {
+            $params['segment'] = $segment;
+        }
+
+        if (!empty($additionalParams)) {
+            foreach ($additionalParams as $key => $value) {
+                $params[$key] = $value;
+            }
+        }
+
+        $request = new ApiRequest($params);
+        return $request->process();
+    }
+
 }
diff --git a/plugins/Insights/Controller.php b/plugins/Insights/Controller.php
index b9431f5a3c..73765276bc 100644
--- a/plugins/Insights/Controller.php
+++ b/plugins/Insights/Controller.php
@@ -10,6 +10,7 @@ namespace Piwik\Plugins\Insights;
 
 use Piwik\Common;
 use Piwik\Piwik;
+use Piwik\Plugins\Insights\Visualizations\Insight;
 use Piwik\View;
 
 /**
@@ -20,19 +21,19 @@ class Controller extends \Piwik\Plugin\Controller
 
     public function getInsightsOverview()
     {
-        $view = $this->prepareWidget($apiReport = 'getInsightsOverview');
+        $view = $this->prepareWidget('insightsOverviewWidget.twig', $apiReport = 'getInsightsOverview');
 
         return $view->render();
     }
 
     public function getOverallMoversAndShakers()
     {
-        $view = $this->prepareWidget($apiReport = 'getOverallMoversAndShakers');
+        $view = $this->prepareWidget('moversAndShakersOverviewWidget.twig', $apiReport = 'getMoversAndShakersOverview');
 
         return $view->render();
     }
 
-    private function prepareWidget($apiReport)
+    private function prepareWidget($template, $apiReport)
     {
         $idSite = Common::getRequestVar('idSite', null, 'int');
         $period = Common::getRequestVar('period', null, 'string');
@@ -40,7 +41,14 @@ class Controller extends \Piwik\Plugin\Controller
 
         Piwik::checkUserHasViewAccess(array($idSite));
 
-        $view = new View('@Insights/overviewWidget.twig');
+        if (!API::getInstance()->canGenerateInsights($period, $date)) {
+
+            $view = new View('@Insights/cannotDisplayReport.twig');
+            $this->setBasicVariablesView($view);
+            return $view;
+        }
+
+        $view = new View('@Insights/' . $template);
         $this->setBasicVariablesView($view);
 
         $view->reports = API::getInstance()->$apiReport($idSite, $period, $date);
diff --git a/plugins/Insights/DataTable/Filter/MinGrowth.php b/plugins/Insights/DataTable/Filter/MinGrowth.php
index 14164ef561..a296223751 100644
--- a/plugins/Insights/DataTable/Filter/MinGrowth.php
+++ b/plugins/Insights/DataTable/Filter/MinGrowth.php
@@ -13,18 +13,20 @@ use Piwik\DataTable;
 
 class MinGrowth extends BaseFilter
 {
-    private $minValue;
+    private $minPositiveGrowth;
+    private $minNegativeGrowth;
     private $columnToRead;
 
-    public function __construct($table, $columnToRead, $minValue)
+    public function __construct($table, $columnToRead, $minPositiveGrowth, $minNegativeGrowth)
     {
         $this->columnToRead = $columnToRead;
-        $this->minValue = abs($minValue);
+        $this->minPositiveGrowth = $minPositiveGrowth;
+        $this->minNegativeGrowth = $minNegativeGrowth;
     }
 
     public function filter($table)
     {
-        if (!$this->minValue) {
+        if (!$this->minPositiveGrowth && !$this->minNegativeGrowth) {
             return;
         }
 
@@ -32,9 +34,9 @@ class MinGrowth extends BaseFilter
 
             $growthNumeric = $row->getColumn($this->columnToRead);
 
-            if ($growthNumeric >= $this->minValue) {
+            if ($growthNumeric >= $this->minPositiveGrowth && $growthNumeric >= 0) {
                 continue;
-            } elseif ($growthNumeric <= -$this->minValue) {
+            } elseif ($growthNumeric <= $this->minNegativeGrowth && $growthNumeric < 0) {
                 continue;
             }
 
diff --git a/plugins/Insights/InsightReport.php b/plugins/Insights/InsightReport.php
index 72d4dc6fe0..3a3cbb9d27 100644
--- a/plugins/Insights/InsightReport.php
+++ b/plugins/Insights/InsightReport.php
@@ -9,6 +9,7 @@
 namespace Piwik\Plugins\Insights;
 
 use Piwik\DataTable;
+use Piwik\Piwik;
 
 /**
  * API for plugin Insights
@@ -30,24 +31,79 @@ class InsightReport
      * @param DataTable $currentReport
      * @param DataTable $lastReport
      * @param int $totalValue
-     * @param int $minVisitsMoversPercent      Exclude rows who moved and the difference is not at least min percent
+     * @param int $lastTotalValue
+     * @param string $orderBy
+     * @param int $limitIncreaser
+     * @param int $limitDecreaser
+     * @return DataTable
+     */
+    public function generateMoverAndShaker($reportMetadata, $period, $date, $lastDate, $metric, $currentReport, $lastReport, $totalValue, $lastTotalValue, $orderBy, $limitIncreaser, $limitDecreaser)
+    {
+        $totalEvolution = Piwik::getPercentageSafe($totalValue - $lastTotalValue, $lastTotalValue, 1);
+
+        $minMoversPercent = 0.5;
+
+        if ($totalEvolution >= 100) {
+            // eg change from 50 to 150 = 200%
+            // $evolutionReverse = Piwik::getPercentageSafe($totalValue > $lastTotal ? $lastTotal : $totalValue, $totalValue > $lastTotal ? $totalValue : $lastTotal, 1);
+
+            $minGrowthPercentPositive = $totalEvolution + 40; // min +240%
+            $minGrowthPercentNegative = -70;         // min -70%
+            $minDisappearedPercent = 8;              // min 12
+            $minNewPercent = min(($totalEvolution / 100) * 3, 10);    // min 6% = min 10 of total visits up to max 10%
+
+        } elseif ($totalEvolution >= 0) {
+            // eg change from 50 to 75 = 50%
+            $minGrowthPercentPositive = $totalEvolution + 20;  // min 70%
+            $minGrowthPercentNegative = -1 * $minGrowthPercentPositive;  // min -70%
+            $minDisappearedPercent = 7;
+            $minNewPercent         = 5;
+        } else {
+            // eg change from 50 to 25 = -50%
+            $minGrowthPercentNegative = $totalEvolution - 20;                  // min -70%
+            $minGrowthPercentPositive = abs($minGrowthPercentNegative); // min 70%
+            $minDisappearedPercent = 7;
+            $minNewPercent         = 5;
+        }
+
+        $dataTable = $this->generateInsight($reportMetadata, $period, $date, $lastDate, $metric, $currentReport, $lastReport, $totalValue, $minMoversPercent, $minNewPercent, $minDisappearedPercent, $minGrowthPercentPositive, $minGrowthPercentNegative, $orderBy, $limitIncreaser, $limitDecreaser);
+
+        $dataTable->setMetadata('lastTotalValue', $lastTotalValue);
+        $dataTable->setMetadata('evolutionTotal', $totalEvolution);
+        $dataTable->setMetadata('evolutionDifference', $totalValue - $lastTotalValue);
+
+        return $dataTable;
+    }
+
+    /**
+     * @param array $reportMetadata
+     * @param string $period
+     * @param string $date
+     * @param string $lastDate
+     * @param string $metric
+     * @param DataTable $currentReport
+     * @param DataTable $lastReport
+     * @param int $totalValue
+     * @param int $minMoversPercent      Exclude rows who moved and the difference is not at least min percent
      *                                         visits of totalVisits. -1 excludes movers.
-     * @param int $minVisitsNewPercent         Exclude rows who are new and the difference is not at least min percent
+     * @param int $minNewPercent         Exclude rows who are new and the difference is not at least min percent
      *                                         visits of totalVisits. -1 excludes all new.
-     * @param int $minVisitsDisappearedPercent Exclude rows who are disappeared and the difference is not at least min
+     * @param int $minDisappearedPercent Exclude rows who are disappeared and the difference is not at least min
      *                                         percent visits of totalVisits. -1 excludes all disappeared.
-     * @param int $minGrowthPercent            The actual growth of a row must be at least percent compared to the
+     * @param int $minGrowthPercentPositive    The actual growth of a row must be at least percent compared to the
+     *                                         previous value (not total value)
+     * @param int $minGrowthPercentNegative    The actual growth of a row must be lower percent compared to the
      *                                         previous value (not total value)
      * @param string $orderBy                  Order by absolute, relative, importance
      * @param int $limitIncreaser
      * @param int $limitDecreaser
      * @return DataTable
      */
-    public function generateInsight($reportMetadata, $period, $date, $lastDate, $metric, $currentReport, $lastReport, $totalValue, $minVisitsMoversPercent, $minVisitsNewPercent, $minVisitsDisappearedPercent, $minGrowthPercent, $orderBy, $limitIncreaser, $limitDecreaser)
+    public function generateInsight($reportMetadata, $period, $date, $lastDate, $metric, $currentReport, $lastReport, $totalValue, $minMoversPercent, $minNewPercent, $minDisappearedPercent, $minGrowthPercentPositive, $minGrowthPercentNegative, $orderBy, $limitIncreaser, $limitDecreaser)
     {
-        $minChangeMovers = $this->getMinVisits($totalValue, $minVisitsMoversPercent);
-        $minIncreaseNew = $this->getMinVisits($totalValue, $minVisitsNewPercent);
-        $minDecreaseDisappeared = $this->getMinVisits($totalValue, $minVisitsDisappearedPercent);
+        $minChangeMovers = $this->getMinVisits($totalValue, $minMoversPercent);
+        $minIncreaseNew = $this->getMinVisits($totalValue, $minNewPercent);
+        $minDecreaseDisappeared = $this->getMinVisits($totalValue, $minDisappearedPercent);
 
         $dataTable = new DataTable();
         $dataTable->filter(
@@ -56,9 +112,9 @@ class InsightReport
                 $currentReport,
                 $lastReport,
                 $metric,
-                $considerMovers = (-1 !== $minVisitsMoversPercent),
-                $considerNew = (-1 !== $minVisitsNewPercent),
-                $considerDisappeared = (-1 !== $minVisitsDisappearedPercent)
+                $considerMovers = (-1 !== $minMoversPercent),
+                $considerNew = (-1 !== $minNewPercent),
+                $considerDisappeared = (-1 !== $minDisappearedPercent)
             )
         );
 
@@ -66,7 +122,8 @@ class InsightReport
             'Piwik\Plugins\Insights\DataTable\Filter\MinGrowth',
             array(
                 'growth_percent_numeric',
-                $minGrowthPercent,
+                $minGrowthPercentPositive,
+                $minGrowthPercentNegative
             )
         );
 
@@ -132,10 +189,11 @@ class InsightReport
             'minChangeMovers' => $minChangeMovers,
             'minIncreaseNew' => $minIncreaseNew,
             'minDecreaseDisappeared' => $minDecreaseDisappeared,
-            'minGrowthPercent' => $minGrowthPercent,
-            'minVisitsMoversPercent' => $minVisitsMoversPercent,
-            'minVisitsNewPercent' => $minVisitsNewPercent,
-            'minVisitsDisappearedPercent' => $minVisitsDisappearedPercent
+            'minGrowthPercentPositive' => $minGrowthPercentPositive,
+            'minGrowthPercentNegative' => $minGrowthPercentNegative,
+            'minMoversPercent' => $minMoversPercent,
+            'minNewPercent' => $minNewPercent,
+            'minDisappearedPercent' => $minDisappearedPercent
         ));
 
         return $dataTable;
diff --git a/plugins/Insights/Visualizations/Insight.php b/plugins/Insights/Visualizations/Insight.php
index 24d4ebd244..6853776cca 100644
--- a/plugins/Insights/Visualizations/Insight.php
+++ b/plugins/Insights/Visualizations/Insight.php
@@ -14,12 +14,12 @@ use Piwik\DataTable;
 use Piwik\Period\Range;
 use Piwik\Plugin\ViewDataTable;
 use Piwik\Plugin\Visualization;
+use Piwik\Plugins\Insights\API;
 use Piwik\Plugins\Insights\Model;
 
 /**
  * InsightsVisualization Visualization.
  *
- * @property Insight\Config        $config
  * @property Insight\RequestConfig $requestConfig
  */
 class Insight extends Visualization
@@ -31,6 +31,10 @@ class Insight extends Visualization
 
     public function beforeLoadDataTable()
     {
+        if (!self::canDisplayViewDataTable($this)) {
+            return;
+        }
+
         $report = $this->requestConfig->apiMethodToRequestDataTable;
         $report = str_replace('.', '_', $report);
 
@@ -64,11 +68,6 @@ class Insight extends Visualization
         );
     }
 
-    public static function getDefaultConfig()
-    {
-        return new Insight\Config();
-    }
-
     public static function getDefaultRequestConfig()
     {
         return new Insight\RequestConfig();
@@ -81,14 +80,34 @@ class Insight extends Visualization
 
     public function beforeRender()
     {
-        $this->assignTemplateVar('period', Common::getRequestVar('period', null, 'string'));
-
         $this->config->datatable_js_type = 'InsightsDataTable';
         $this->config->show_limit_control = true;
         $this->config->show_pagination_control = false;
         $this->config->show_offset_information = false;
         $this->config->show_search = false;
-        $this->config->title = 'Insights of ' . $this->config->title;
+
+        if (!self::canDisplayViewDataTable($this)) {
+            $this->assignTemplateVar('cannotDisplayReport', true);
+            return;
+        }
+
+        $period  = Common::getRequestVar('period', null, 'string');
+        $date    = Common::getRequestVar('date', null, 'string');
+        $idSite  = Common::getRequestVar('idSite', null, 'string');
+        $segment = Common::getRequestVar('segment', false, 'string');
+
+        $this->assignTemplateVar('period', $period);
+
+        $reportId = $this->requestConfig->request_parameters_to_modify['reportUniqueId'];
+        $comparedToXPeriods = $this->requestConfig->compared_to_x_periods_ago;
+        $limitIncreaser = min($this->requestConfig->request_parameters_to_modify['limitIncreaser'], 2);
+        $limitDecreaser = min($this->requestConfig->request_parameters_to_modify['limitDecreaser'], 2);
+
+        $shaker = API::getInstance()->getMoversAndShakers(
+            $idSite, $period, $date, $reportId, $segment, $comparedToXPeriods, $limitIncreaser, $limitDecreaser
+        );
+
+        $this->assignTemplateVar('shaker', $shaker);
     }
 
     public static function canDisplayViewDataTable(ViewDataTable $view)
@@ -96,14 +115,9 @@ class Insight extends Visualization
         $period = Common::getRequestVar('period', null, 'string');
         $date   = Common::getRequestVar('date', null, 'string');
 
-        try {
-            $model    = new Model();
-            $lastDate = $model->getLastDate($date, $period, 1);
-        } catch (\Exception $e) {
-            return false;
-        }
+        $canGenerateInsights = API::getInstance()->canGenerateInsights($period, $date);
 
-        if (empty($lastDate)) {
+        if (!$canGenerateInsights) {
             return false;
         }
 
diff --git a/plugins/Insights/Visualizations/Insight/Config.php b/plugins/Insights/Visualizations/Insight/Config.php
deleted file mode 100644
index 327572aaf4..0000000000
--- a/plugins/Insights/Visualizations/Insight/Config.php
+++ /dev/null
@@ -1,17 +0,0 @@
-<?php
-/**
- * Piwik - Open source web analytics
- *
- * @link http://piwik.org
- * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
- *
- */
-
-namespace Piwik\Plugins\Insights\Visualizations\Insight;
-
-use Piwik\ViewDataTable\Config as VisualizationConfig;
-
-class Config extends VisualizationConfig
-{
-    public $show_filter_by = true;
-}
diff --git a/plugins/Insights/javascripts/insightsDataTable.js b/plugins/Insights/javascripts/insightsDataTable.js
index 4dd4824c06..2ebfe04a72 100644
--- a/plugins/Insights/javascripts/insightsDataTable.js
+++ b/plugins/Insights/javascripts/insightsDataTable.js
@@ -11,6 +11,8 @@
         DataTable = exports.DataTable,
         dataTablePrototype = DataTable.prototype;
 
+     var UIControl = exports.UIControl;
+
      function getValueFromEvent(event)
      {
          return event.target.value ? event.target.value : $(event.target).attr('value');
@@ -26,7 +28,16 @@
         this.parentId = '';
         this.disabledRowDom = {}; // to handle double click on '+' row
 
-        DataTable.call(this, element);
+        if ($(element).attr('data-table-onlyinsightsinit')) {
+            // overview-widget
+            UIControl.call(this, element);
+            this._init($(element));
+            this.workingDivId = this._createDivId();
+            $(element).attr('id', this.workingDivId);
+
+        } else {
+            DataTable.call(this, element);
+        }
     };
 
     $.extend(exports.InsightsDataTable.prototype, dataTablePrototype, {
@@ -45,7 +56,7 @@
         setFixWidthToMakeEllipsisWork: function (domElem) {
             var width = domElem.width();
             if (width) {
-                $('td.label', domElem).width(parseInt(width * 0.60, 10));
+                $('td.label', domElem).width(parseInt(width * 0.50, 10));
             }
         },
 
diff --git a/plugins/Insights/lang/en.json b/plugins/Insights/lang/en.json
index 63c43f3c15..3a7667db50 100644
--- a/plugins/Insights/lang/en.json
+++ b/plugins/Insights/lang/en.json
@@ -4,10 +4,12 @@
         "WidgetCategory": "Insights",
         "NoResultMatchesCriteria": "No rows match the criteria",
         "MoversAndShakersWidgetTitle": "Movers and Shakers",
-        "TitleConsideredVisits": "Considered rows having at least a growth of at least %s%% compared to %s.",
+        "TitleConsideredVisits": "Considered rows having a growth of at least %s%% compared to %s.",
         "TitleConsideredChanges": "Considered movers only if they increased or decreased by more than %s visits, new entries only if they increase by more than %s visits, and disappeared rows if they decreased by more than %s visits based on %s total visits.",
         "TitleReportBasedOn": "Based on %s %s, rows less than %s %s were ignored",
         "TitleRowChangeDetails": "'%s' changed from %s (%s) to %s (%s) %s",
-        "TitleIgnoredChanges": "Changes affecting less than %s visits were ignored."
+        "TitleIgnoredChanges": "Changes affecting less than %s visits were ignored.",
+        "TitleConsideredMoversAndShakers": "Visits changed from %s to %s compared to %s. Based on this an evolution of each row of %s%% is expected.",
+        "TitleConsideredChangesMoversAndShakers": "Considered movers only if they grew by more than %s%% visits or less than %s%% visits, new entries only if they increased by more than %s%% visits (%s), and disappeared rows if they descreased by less than %s%% visits (%s)"
     }
 }
\ No newline at end of file
diff --git a/plugins/Insights/stylesheets/insightVisualization.less b/plugins/Insights/stylesheets/insightVisualization.less
index 1dca12f4f8..f70ea83ee5 100644
--- a/plugins/Insights/stylesheets/insightVisualization.less
+++ b/plugins/Insights/stylesheets/insightVisualization.less
@@ -14,7 +14,6 @@
     overflow: hidden;
     text-overflow: ellipsis;
     width: inherit;
-    padding-right: 10px;
     display: block;
   }
 
@@ -26,4 +25,8 @@
     color:red;
   }
 
+  .isMoverAndShaker {
+    font-weight:bold;
+  }
+
 }
\ No newline at end of file
diff --git a/plugins/Insights/templates/cannotDisplayReport.twig b/plugins/Insights/templates/cannotDisplayReport.twig
new file mode 100644
index 0000000000..d32a9d1a36
--- /dev/null
+++ b/plugins/Insights/templates/cannotDisplayReport.twig
@@ -0,0 +1,3 @@
+<div class="pk-emptyDataTable">
+    It is not possible to generate insights for this date / period combination.
+</div>
\ No newline at end of file
diff --git a/plugins/Insights/templates/insightControls.twig b/plugins/Insights/templates/insightControls.twig
index ca792fe306..a572ee8507 100644
--- a/plugins/Insights/templates/insightControls.twig
+++ b/plugins/Insights/templates/insightControls.twig
@@ -1,4 +1,5 @@
 <div style="padding: 10px;padding-bottom: 0px;">
+
     Minimum growth of
     <select size="1" name="minGrowthPercent">
         {% for i in range(0, 1, 1) %}
@@ -43,15 +44,13 @@
     <hr style="height: 1px;border: 0px;background-color: #cccccc;" />
 
     Filter
-    
-    {% if properties.show_filter_by %}
-        <select size="1" name="filterBy" title="Show all, only movers, only new, only disappeared">
-            <option {% if not properties.filter_by %}selected{% endif %} value="">All</option>
-            <option {% if properties.filter_by == 'movers' %}selected{% endif %} value="movers">Only movers</option>
-            <option {% if properties.filter_by == 'new' %}selected{% endif %} value="new">Only new</option>
-            <option {% if properties.filter_by == 'disappeared' %}selected{% endif %} value="disappeared">Only disappeared</option>
-        </select>
-    {% endif %}
+
+    <select size="1" name="filterBy" title="Show all, only movers, only new, only disappeared">
+        <option {% if not properties.filter_by %}selected{% endif %} value="">All</option>
+        <option {% if properties.filter_by == 'movers' %}selected{% endif %} value="movers">Only movers</option>
+        <option {% if properties.filter_by == 'new' %}selected{% endif %} value="new">Only new</option>
+        <option {% if properties.filter_by == 'disappeared' %}selected{% endif %} value="disappeared">Only disappeared</option>
+    </select>
 
     <select size="1" name="showIncreaseOrDecrease" title="Show increaser and/or decreaser">
         <option value="both" {% if properties.limit_increaser and properties.limit_decreaser %}selected{%endif%}>
diff --git a/plugins/Insights/templates/insightVisualization.twig b/plugins/Insights/templates/insightVisualization.twig
index 5a1057de41..8ddba92aed 100644
--- a/plugins/Insights/templates/insightVisualization.twig
+++ b/plugins/Insights/templates/insightVisualization.twig
@@ -1,29 +1,36 @@
-{% set metadata = dataTable.getAllTableMetadata%}
-{% set consideredVisits = 'Insights_TitleConsideredVisits'|translate(metadata.minGrowthPercent, metadata.lastDate|prettyDate(metadata.period)) %}
-{% set consideredChanges = '' %}
+{% if cannotDisplayReport is defined and cannotDisplayReport %}
+    {% include "@Insights/cannotDisplayReport.twig" %}
+{% else %}
+    {% set metadata = dataTable.getAllTableMetadata%}
+    {% set consideredVisits = 'Insights_TitleConsideredVisits'|translate(metadata.minGrowthPercentPositive, metadata.lastDate|prettyDate(metadata.period)) %}
+    {% set consideredChanges = '' %}
 
-{% if metadata.minChangeMovers and metadata.minChangeMovers > 1 %}
-    {% set consideredChanges = 'Insights_TitleIgnoredChanges'|translate(metadata.minChangeMovers) %}
-{% endif %}
+    {% if metadata.minChangeMovers and metadata.minChangeMovers > 1 %}
+        {% set consideredChanges = 'Insights_TitleIgnoredChanges'|translate(metadata.minChangeMovers) %}
+    {% endif %}
 
-<div class="insightsDataTable" title="{{ consideredVisits|e('html_attr') }} {{ consideredChanges|e('html_attr') }}">
-    {% if dataTable.getRowsCount > 0 %}
-        <table class="dataTable">
-            <thead>
-                {% include "@Insights/table_header.twig" %}
-            </thead>
+    <div class="insightsDataTable" title="{{ consideredVisits|e('html_attr') }} {{ consideredChanges|e('html_attr') }}">
+        {% if dataTable.getRowsCount %}
+            <table class="dataTable">
 
-            <tbody>
-                {% for row in dataTable.getRows %}
-                    {% include "@Insights/table_row.twig" %}
-                {% endfor %}
-            </tbody>
-        </table>
-    {% else %}
-        <div class="pk-emptyDataTable">
-            {{ 'Insights_NoResultMatchesCriteria'|translate }}
-        </div>
-    {% endif %}
+                <thead>
+                    {% include "@Insights/table_header.twig" %}
+                </thead>
+
+                <tbody>
+                    {% for row in dataTable.getRows %}
+                        {% set isShaker = shaker.getRowFromLabel(row.getColumn('label')) %}
+
+                        {% include "@Insights/table_row.twig" with {"isShaker": isShaker} %}
+                    {% endfor %}
+                </tbody>
+            </table>
+        {% else %}
+            <div class="pk-emptyDataTable">
+                {{ 'Insights_NoResultMatchesCriteria'|translate }}
+            </div>
+        {% endif %}
 
-    {% include "@Insights/insightControls.twig" %}
-</div>
\ No newline at end of file
+        {% include "@Insights/insightControls.twig" %}
+    </div>
+{% endif %}
\ No newline at end of file
diff --git a/plugins/Insights/templates/insightsOverviewWidget.twig b/plugins/Insights/templates/insightsOverviewWidget.twig
new file mode 100644
index 0000000000..8f99ba11ba
--- /dev/null
+++ b/plugins/Insights/templates/insightsOverviewWidget.twig
@@ -0,0 +1,10 @@
+{% set allMetadata = reports.getFirstRow.getAllTableMetadata %}
+
+{% set consideredVisits = 'Insights_TitleConsideredVisits'|translate(allMetadata.minGrowthPercentPositive, allMetadata.lastDate|prettyDate(allMetadata.period)) %}
+{% set consideredChanges = '' %}
+
+{% if allMetadata.minChangeMovers or allMetadata.minIncreaseNew or allMetadata.minDecreaseDisappeared %}
+    {% set consideredChanges = 'Insights_TitleConsideredChanges'|translate(allMetadata.minChangeMovers, allMetadata.minIncreaseNew, allMetadata.minDecreaseDisappeared, allMetadata.totalValue) %}
+{% endif %}
+
+{% include "@Insights/overviewWidget.twig" %}
\ No newline at end of file
diff --git a/plugins/Insights/templates/moversAndShakersOverviewWidget.twig b/plugins/Insights/templates/moversAndShakersOverviewWidget.twig
new file mode 100644
index 0000000000..185a61b37e
--- /dev/null
+++ b/plugins/Insights/templates/moversAndShakersOverviewWidget.twig
@@ -0,0 +1,10 @@
+{% set allMetadata = reports.getFirstRow.getAllTableMetadata %}
+
+{% set consideredVisits = 'Insights_TitleConsideredMoversAndShakers'|translate(allMetadata.lastTotalValue, allMetadata.totalValue, allMetadata.lastDate|prettyDate(allMetadata.period), allMetadata.evolutionTotal) %}
+{% set consideredChanges = '' %}
+
+{% if allMetadata.minChangeMovers or allMetadata.minIncreaseNew or allMetadata.minDecreaseDisappeared %}
+    {% set consideredChanges = 'Insights_TitleConsideredChangesMoversAndShakers'|translate(allMetadata.minGrowthPercentPositive, allMetadata.minGrowthPercentNegative, allMetadata.minNewPercent, allMetadata.minIncreaseNew, allMetadata.minDisappearedPercent, allMetadata.minDecreaseDisappeared, allMetadata.totalValue) %}
+{% endif %}
+
+{% include "@Insights/overviewWidget.twig" %}
\ No newline at end of file
diff --git a/plugins/Insights/templates/overviewWidget.twig b/plugins/Insights/templates/overviewWidget.twig
index 2a672af69b..d3c43b917b 100644
--- a/plugins/Insights/templates/overviewWidget.twig
+++ b/plugins/Insights/templates/overviewWidget.twig
@@ -1,13 +1,7 @@
-{% set allMetadata = reports.getFirstRow.getAllTableMetadata %}
-
-{% set consideredVisits = 'Insights_TitleConsideredVisits'|translate(allMetadata.minGrowthPercent, allMetadata.lastDate|prettyDate(allMetadata.period)) %}
-{% set consideredChanges = '' %}
-
-{% if allMetadata.minChangeMovers or allMetadata.minIncreaseNew or allMetadata.minDecreaseDisappeared %}
-    {% set consideredChanges = 'Insights_TitleConsideredChanges'|translate(allMetadata.minChangeMovers, allMetadata.minIncreaseNew, allMetadata.minDecreaseDisappeared, allMetadata.totalValue) %}
-{% endif %}
-
-<div class="insightsDataTable" title="{{ consideredVisits|e('html_attr') }} {{ consideredChanges|e('html_attr') }}">
+<div class="insightsDataTable dataTable"
+     data-table-type="InsightsDataTable"
+     data-table-onlyinsightsinit="1"
+     title="{{ consideredVisits|e('html_attr') }} {{ consideredChanges|e('html_attr') }}">
     {% if reports.getColumns|length > 0 %}
 
         <table class="dataTable">
@@ -27,6 +21,12 @@
             {% endfor %}
         </table>
 
+        <script type="text/javascript" defer="defer">
+            $(document).ready(function () {
+                require('piwik/UI/DataTable').initNewDataTables();
+            });
+        </script>
+
     {% else %}
 
         <div class="pk-emptyDataTable">
diff --git a/plugins/Insights/templates/table_row.twig b/plugins/Insights/templates/table_row.twig
index bd3b04a2e7..3c8c46576f 100644
--- a/plugins/Insights/templates/table_row.twig
+++ b/plugins/Insights/templates/table_row.twig
@@ -1,5 +1,6 @@
-<tr title="{{ 'Insights_TitleRowChangeDetails'|translate(row.getColumn('label'), row.getColumn('value_old'), metadata.lastDate|prettyDate(metadata.period), row.getColumn('value_new'), metadata.date|prettyDate(metadata.period), metadata.metricName)|e('html_attr') }}">
-    <td>
+<tr title="{{ 'Insights_TitleRowChangeDetails'|translate(row.getColumn('label'), row.getColumn('value_old'), metadata.lastDate|prettyDate(metadata.period), row.getColumn('value_new'), metadata.date|prettyDate(metadata.period), metadata.metricName)|e('html_attr') }}"
+    {% if isShaker is defined and isShaker %}class="isMoverAndShaker"{% endif %}>
+    <td class="label">
         <span class="title">
             {{ row.getColumn('label') }}
         </span>
diff --git a/plugins/Insights/tests/ApiTest.php b/plugins/Insights/tests/ApiTest.php
index b7e85f4142..40a793a8ea 100644
--- a/plugins/Insights/tests/ApiTest.php
+++ b/plugins/Insights/tests/ApiTest.php
@@ -63,10 +63,11 @@ class ApiTest extends \IntegrationTestCase
             'minChangeMovers' => 1,
             'minIncreaseNew'  => 1,
             'minDecreaseDisappeared' => 1,
-            'minGrowthPercent' => 20,
-            'minVisitsMoversPercent' => 2,
-            'minVisitsNewPercent' => 2,
-            'minVisitsDisappearedPercent' => 2,
+            'minGrowthPercentPositive' => 20,
+            'minGrowthPercentNegative' => -20,
+            'minMoversPercent' => 2,
+            'minNewPercent' => 2,
+            'minDisappearedPercent' => 2,
         );
 
         $this->assertInternalType('array', $metadata['report']);
diff --git a/plugins/Insights/tests/FilterMinGrowthTest.php b/plugins/Insights/tests/FilterMinGrowthTest.php
index 9b3ba878e8..499311fc11 100644
--- a/plugins/Insights/tests/FilterMinGrowthTest.php
+++ b/plugins/Insights/tests/FilterMinGrowthTest.php
@@ -44,42 +44,47 @@ class FilterMinGrowthTest extends BaseUnitTest
         $rowsCountBefore = $this->table->getRowsCount();
         $this->assertGreaterThan(0, $rowsCountBefore);
 
-        $this->applyMinGrowthFilter(0);
+        $this->applyMinGrowthFilter(0, 0);
 
         $this->assertSame($rowsCountBefore, $this->table->getRowsCount());
     }
 
     public function testShouldKeepAllRowsHavingHigherGrowth()
     {
-        $this->applyMinGrowthFilter(15);
+        $this->applyMinGrowthFilter(15, -15);
 
         $this->assertOrder(array('pos1', 'neg1', 'pos3', 'neg2', 'neg3', 'pos4', 'pos5', 'neg4', 'neg5'));
     }
 
     public function testShouldKeepRowsIfTheyHaveGivenMinGrowth()
     {
-        $this->applyMinGrowthFilter(22);
+        $this->applyMinGrowthFilter(22, -22);
 
         $this->assertOrder(array('pos1', 'neg2', 'neg3'));
     }
 
-    public function testShouldIgnoreSign()
+    public function testDifferentGrowth()
     {
-        $this->applyMinGrowthFilter(-22);
+        $this->applyMinGrowthFilter(22, -16);
+        $this->assertOrder(array('pos1', 'neg1', 'neg2', 'neg3', 'neg5'));
+    }
 
-        $this->assertOrder(array('pos1', 'neg2', 'neg3'));
+    public function testDifferentGrowth2()
+    {
+        $this->applyMinGrowthFilter(15, -24);
+        $this->assertOrder(array('pos1', 'pos3', 'neg3', 'pos4', 'pos5'));
     }
 
     public function testShouldRemoveAllIfMinGrowthIsTooHigh()
     {
-        $this->applyMinGrowthFilter(999);
+        $this->applyMinGrowthFilter(999, -999);
 
         $this->assertOrder(array());
     }
 
-    private function applyMinGrowthFilter($minGrowthPercent)
+    private function applyMinGrowthFilter($minGrowthPercentPositive, $minGrowthPercentNegative)
     {
-        $filter = new MinGrowth($this->table, 'growth', $minGrowthPercent);
+        $filter = new MinGrowth($this->table, 'growth', $minGrowthPercentPositive, $minGrowthPercentNegative);
         $filter->filter($this->table);
     }
 
diff --git a/plugins/Insights/tests/InsightReportTest.php b/plugins/Insights/tests/InsightReportTest.php
index b2a6ca3a5a..1c19a24c0d 100644
--- a/plugins/Insights/tests/InsightReportTest.php
+++ b/plugins/Insights/tests/InsightReportTest.php
@@ -93,9 +93,9 @@ class InsightReportTest extends \PHPUnit_Framework_TestCase
     /**
      * @dataProvider provideOrderTestData
      */
-    public function testOrder($orderBy, $expectedOrder)
+    public function test_generateInsight_Order($orderBy, $expectedOrder)
     {
-        $report = $this->generateInsight(2, 2, 2, 17, $orderBy);
+        $report = $this->generateInsight(2, 2, 2, 17, -17, $orderBy);
 
         $this->assertOrder($report, $expectedOrder);
     }
@@ -113,38 +113,41 @@ class InsightReportTest extends \PHPUnit_Framework_TestCase
      * @expectedException \Exception
      * @expectedExceptionMessage Unsupported orderBy
      */
-    public function testOrder_ShouldThrowException_IfInvalid()
+    public function test_generateInsight_Order_ShouldThrowException_IfInvalid()
     {
-        $this->generateInsight(2, 2, 2, 17, 'InvalidOrDeRbY');
+        $this->generateInsight(2, 2, 2, 17, -17, 'InvalidOrDeRbY');
     }
 
     /**
      * @dataProvider provideMinGrowthTestData
      */
-    public function testMinGrowth($minGrowth, $expectedOrder)
+    public function test_generateInsight_MinGrowth($minGrowthPositive, $minGrowthNegative, $expectedOrder)
     {
-        $report = $this->generateInsight(2, 2, 2, $minGrowth, InsightReport::ORDER_BY_ABSOLUTE);
+        $report = $this->generateInsight(2, 2, 2, $minGrowthPositive, $minGrowthNegative, InsightReport::ORDER_BY_ABSOLUTE);
         $this->assertOrder($report, $expectedOrder);
     }
 
     public function provideMinGrowthTestData()
     {
         return array(
-            array(4000, array()),
-            array(1000, array('val11')),
-            array(120, array('val11', 'val12')),
-            array(80, array('val11', 'val7', 'val3', 'val10', 'val2', 'val12', 'val6', 'val107', 'val9', 'val102')),
-            array(19, array('val11', 'val7', 'val3', 'val10', 'val2', 'val12', 'val6', 'val107', 'val9', 'val8', 'val102')),
-            array(17, array('val11', 'val7', 'val3', 'val10', 'val2', 'val1', 'val12', 'val6', 'val107', 'val9', 'val8', 'val102', 'val4')),
+            array(4000, -4000, array()),
+            array(1000, -1000, array('val11')),
+            array(120, -120, array('val11', 'val12')),
+            array(80, -80, array('val11', 'val7', 'val3', 'val10', 'val2', 'val12', 'val6', 'val107', 'val9', 'val102')),
+            array(19, -19, array('val11', 'val7', 'val3', 'val10', 'val2', 'val12', 'val6', 'val107', 'val9', 'val8', 'val102')),
+            array(17, -17, array('val11', 'val7', 'val3', 'val10', 'val2', 'val1', 'val12', 'val6', 'val107', 'val9', 'val8', 'val102', 'val4')),
+            array(17, -80, array('val11', 'val7', 'val3', 'val10', 'val2', 'val1', 'val12', 'val6', 'val107', 'val9', 'val102')),
+            array(17, -4000, array('val11', 'val7', 'val3', 'val10', 'val2', 'val1', 'val12')),
+            array(4000, -17, array('val6', 'val107', 'val9', 'val8', 'val102', 'val4')),
         );
     }
 
     /**
      * @dataProvider provideLimitTestData
      */
-    public function testLimit($limitIncrease, $limitDecrease, $expectedOrder)
+    public function test_generateInsight_Limit($limitIncrease, $limitDecrease, $expectedOrder)
     {
-        $report = $this->generateInsight(2, 2, 2, 20, InsightReport::ORDER_BY_ABSOLUTE, $limitIncrease, $limitDecrease);
+        $report = $this->generateInsight(2, 2, 2, 20, -20, InsightReport::ORDER_BY_ABSOLUTE, $limitIncrease, $limitDecrease);
 
         $this->assertOrder($report, $expectedOrder);
     }
@@ -159,9 +162,9 @@ class InsightReportTest extends \PHPUnit_Framework_TestCase
         );
     }
 
-    public function testNoMovers()
+    public function test_generateInsight_NoMovers()
     {
-        $report = $this->generateInsight(-1, 2, 2, 20);
+        $report = $this->generateInsight(-1, 2, 2, 20, -20);
 
         $this->assertOrder($report, array('val7', 'val3', 'val2', 'val107', 'val102'));
     }
@@ -169,9 +172,9 @@ class InsightReportTest extends \PHPUnit_Framework_TestCase
     /**
      * @dataProvider provideMinImpactMoversTestData
      */
-    public function testMinImpactMovers($minVisitsMoversPercent, $expectedOrder)
+    public function test_generateInsight_MinImpactMovers($minMoversPercent, $expectedOrder)
     {
-        $report = $this->generateInsight($minVisitsMoversPercent, -1, -1, 17);
+        $report = $this->generateInsight($minMoversPercent, -1, -1, 17, -17);
 
         $this->assertOrder($report, $expectedOrder);
     }
@@ -186,9 +189,9 @@ class InsightReportTest extends \PHPUnit_Framework_TestCase
         );
     }
 
-    public function testNoNew()
+    public function test_generateInsight_NoNew()
     {
-        $report = $this->generateInsight(2, -1, 2, 17);
+        $report = $this->generateInsight(2, -1, 2, 17, -17);
 
         $this->assertOrder($report, array('val11', 'val10', 'val1', 'val12', 'val6', 'val107', 'val9', 'val8', 'val102', 'val4'));
     }
@@ -196,9 +199,9 @@ class InsightReportTest extends \PHPUnit_Framework_TestCase
     /**
      * @dataProvider provideMinImpactNewTestData
      */
-    public function testMinImpactNew($minVisitsNewPercent, $expectedOrder)
+    public function test_generateInsight_MinImpactNew($minNewPercent, $expectedOrder)
     {
-        $report = $this->generateInsight(-1, $minVisitsNewPercent, -1, 17);
+        $report = $this->generateInsight(-1, $minNewPercent, -1, 17, -17);
 
         $this->assertOrder($report, $expectedOrder);
     }
@@ -214,9 +217,9 @@ class InsightReportTest extends \PHPUnit_Framework_TestCase
         );
     }
 
-    public function testNoDisappeared()
+    public function test_generateInsight_NoDisappeared()
     {
-        $report = $this->generateInsight(2, 2, -1, 17);
+        $report = $this->generateInsight(2, 2, -1, 17, -17);
 
         $this->assertOrder($report, array('val11', 'val7', 'val3', 'val10', 'val2', 'val1', 'val12', 'val6', 'val9', 'val8', 'val4'));
     }
@@ -224,9 +227,9 @@ class InsightReportTest extends \PHPUnit_Framework_TestCase
     /**
      * @dataProvider provideMinImpactDisappearedData
      */
-    public function testMinDisappeared($minVisitsDisappearedPercent, $expectedOrder)
+    public function test_generateInsight_MinDisappeared($minDisappearedPercent, $expectedOrder)
     {
-        $report = $this->generateInsight(-1, -1, $minVisitsDisappearedPercent, 17);
+        $report = $this->generateInsight(-1, -1, $minDisappearedPercent, 17, -17);
         $this->assertOrder($report, $expectedOrder);
     }
 
@@ -242,7 +245,83 @@ class InsightReportTest extends \PHPUnit_Framework_TestCase
         );
     }
 
-    private function generateInsight($minVisitsMoversPercent, $minVisitsNewPercent, $minVisitsDisappearedPercent, $minGrowthPercent, $orderBy = null, $limitIncreaser = 99, $limitDecreaser = 99)
+    public function test_generateMoversAndShakers_Metadata()
+    {
+        $report   = $this->generateMoverAndShaker(50, 150);
+        $metadata = $report->getAllTableMetadata();
+
+        $this->assertEquals(50, $metadata['lastTotalValue']);
+        $this->assertEquals(150, $metadata['totalValue']);
+        $this->assertEquals(100, $metadata['evolutionDifference']);
+        $this->assertEquals(200, $metadata['evolutionTotal']);
+
+
+        $report   = $this->generateMoverAndShaker(50, 75);
+        $metadata = $report->getAllTableMetadata();
+
+        $this->assertEquals(50, $metadata['lastTotalValue']);
+        $this->assertEquals(75, $metadata['totalValue']);
+        $this->assertEquals(25, $metadata['evolutionDifference']);
+        $this->assertEquals(50, $metadata['evolutionTotal']);
+
+
+        $report   = $this->generateMoverAndShaker(50, 25);
+        $metadata = $report->getAllTableMetadata();
+
+        $this->assertEquals(50, $metadata['lastTotalValue']);
+        $this->assertEquals(25, $metadata['totalValue']);
+        $this->assertEquals(-25, $metadata['evolutionDifference']);
+        $this->assertEquals(-50, $metadata['evolutionTotal']);
+    }
+
+    public function test_generateMoversAndShakers_ParameterCalculation()
+    {
+        $report   = $this->generateMoverAndShaker(50, 150);
+        $metadata = $report->getAllTableMetadata();
+
+        $this->assertEquals(240, $metadata['minGrowthPercentPositive']);
+        $this->assertEquals(-70, $metadata['minGrowthPercentNegative']);
+        $this->assertEquals(0.5, $metadata['minMoversPercent']);
+        $this->assertEquals(6, $metadata['minNewPercent']);
+        $this->assertEquals(8, $metadata['minDisappearedPercent']);
+
+
+        $report   = $this->generateMoverAndShaker(50, 75);
+        $metadata = $report->getAllTableMetadata();
+
+        $this->assertEquals(70, $metadata['minGrowthPercentPositive']);
+        $this->assertEquals(-70, $metadata['minGrowthPercentNegative']);
+        $this->assertEquals(0.5, $metadata['minMoversPercent']);
+        $this->assertEquals(5, $metadata['minNewPercent']);
+        $this->assertEquals(7, $metadata['minDisappearedPercent']);
+
+
+        $report   = $this->generateMoverAndShaker(50, 25);
+        $metadata = $report->getAllTableMetadata();
+
+        $this->assertEquals(70, $metadata['minGrowthPercentPositive']);
+        $this->assertEquals(-70, $metadata['minGrowthPercentNegative']);
+        $this->assertEquals(0.5, $metadata['minMoversPercent']);
+        $this->assertEquals(5, $metadata['minNewPercent']);
+        $this->assertEquals(7, $metadata['minDisappearedPercent']);
+    }
+
+    private function generateMoverAndShaker($lastTotalValue, $totalValue, $orderBy = null, $limitIncreaser = 99, $limitDecreaser = 99)
+    {
+        if (is_null($orderBy)) {
+            $orderBy = InsightReport::ORDER_BY_ABSOLUTE;
+        }
+
+        $reportMetadata = array('name' => 'TestReport',  'metrics' => array('nb_visits' => 'Visits'));
+
+        $report = $this->insightReport->generateMoverAndShaker(
+            $reportMetadata, 'day', 'today', 'yesterday', 'nb_visits', $this->currentTable, $this->pastTable,
+            $totalValue, $lastTotalValue, $orderBy, $limitIncreaser, $limitDecreaser);
+
+        return $report;
+    }
+
+    private function generateInsight($minMoversPercent, $minNewPercent, $minDisappearedPercent, $minGrowthPercentPositive, $minGrowthPercentNegative, $orderBy = null, $limitIncreaser = 99, $limitDecreaser = 99)
     {
         if (is_null($orderBy)) {
             $orderBy = InsightReport::ORDER_BY_ABSOLUTE;
@@ -252,8 +331,8 @@ class InsightReportTest extends \PHPUnit_Framework_TestCase
 
         $report = $this->insightReport->generateInsight(
                     $reportMetadata, 'day', 'today', 'yesterday', 'nb_visits', $this->currentTable, $this->pastTable,
-                    $totalValue = 200, $minVisitsMoversPercent, $minVisitsNewPercent, $minVisitsDisappearedPercent,
-                    $minGrowthPercent, $orderBy, $limitIncreaser, $limitDecreaser);
+                    $totalValue = 200, $minMoversPercent, $minNewPercent, $minDisappearedPercent,
+            $minGrowthPercentPositive, $minGrowthPercentNegative, $orderBy, $limitIncreaser, $limitDecreaser);
 
         return $report;
     }
-- 
GitLab