diff --git a/core/API/DataTableGenericFilter.php b/core/API/DataTableGenericFilter.php
index 2dc9ee82c16182f7e159512f49d186147833b8b6..a87f3305b0244a6df18705d0d6ec8c887c3642f6 100644
--- a/core/API/DataTableGenericFilter.php
+++ b/core/API/DataTableGenericFilter.php
@@ -12,7 +12,6 @@ use Exception;
 use Piwik\Common;
 use Piwik\DataTable\Filter\AddColumnsProcessedMetricsGoal;
 use Piwik\DataTable;
-use Piwik\Plugin\ProcessedMetric;
 use Piwik\Plugin\Report;
 
 class DataTableGenericFilter
@@ -24,22 +23,14 @@ class DataTableGenericFilter
      */
     private $disabledFilters = array();
 
-    /**
-     * TODO
-     *
-     * @var Report|null-
-     */
-    private $report;
-
     /**
      * Constructor
      *
      * @param $request
      */
-    function __construct($request, $report = null)
+    function __construct($request)
     {
         $this->request = $request;
-        $this->report = $report;
     }
 
     /**
@@ -178,27 +169,7 @@ class DataTableGenericFilter
         return $filterApplied;
     }
 
-    public function computeProcessedMetricsIfNeeded(DataTable $dataTable)
-    {
-        if (!$this->doesColumnQueryParamReferenceProcessedMetric()) {
-            return false;
-        }
-
-        $this->computeProcessedMetrics($dataTable);
-
-        return true;
-    }
-
-    public function computeProcessedMetrics(DataTable $dataTable)
-    {
-        if (empty($this->report)) {
-            return;
-        }
-
-        $this->report->computeProcessedMetrics($dataTable);
-    }
-
-    private function doesColumnQueryParamReferenceProcessedMetric()
+    public function areProcessedMetricsNeededFor(Report $report)
     {
         $columnQueryParameters = array(
             'filter_column',
@@ -208,9 +179,9 @@ class DataTableGenericFilter
         );
 
         foreach ($columnQueryParameters as $queryParamName) {
-            $queryParamValue = Common::getRequestVar($queryParamName, false);
+            $queryParamValue = Common::getRequestVar($queryParamName, false, $type = null, $this->request);
             if (!empty($queryParamValue)
-                && $this->report->hasProcessedMetric($queryParamValue)
+                && $report->hasProcessedMetric($queryParamValue)
             ) {
                 return true;
             }
diff --git a/core/API/DataTablePostProcessor.php b/core/API/DataTablePostProcessor.php
new file mode 100644
index 0000000000000000000000000000000000000000..6b80082f69679166d362b7ca0774f7f3fb979733
--- /dev/null
+++ b/core/API/DataTablePostProcessor.php
@@ -0,0 +1,336 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\API;
+
+use Piwik\API\DataTableManipulator\Flattener;
+use Piwik\API\DataTableManipulator\LabelFilter;
+use Piwik\API\DataTableManipulator\ReportTotalsCalculator;
+use Piwik\Common;
+use Piwik\DataTable;
+use Piwik\DataTable\DataTableInterface;
+use Piwik\DataTable\Filter\PivotByDimension;
+use Piwik\Plugin\ProcessedMetric;
+use Piwik\Plugin\Report;
+
+/**
+ * Processes DataTables that should be served through Piwik's APIs. This processing handles
+ * special query parameters and computes processed metrics. It does not included rendering to
+ * output formates (eg, 'xml').
+ */
+class DataTablePostProcessor
+{
+    const PROCESSED_METRICS_FORMATTED_FLAG = 'processed_metrics_formatted';
+    const PROCESSED_METRICS_COMPUTED_FLAG = 'processed_metrics_computed';
+
+    /**
+     * Apply post-processing logic to a DataTable of a report for an API request.
+     *
+     * @param DataTableInterface $dataTable The data table to process.
+     * @param Report|null $report The Report metadata class for the DataTable's report, or null if
+     *                            there is none.
+     * @param string[] $request The API request that
+     * @param bool $applyFormatting Whether to format processed metrics or not.
+     * @return DataTableInterface A new data table.
+     */
+    public function process(DataTableInterface $dataTable, $report, $request, $applyFormatting = true)
+    {
+        $label = self::getLabelFromRequest($request);
+
+        $dataTable = $this->applyPivotByFilter($dataTable, $report, $request);
+        $dataTable = $this->applyFlattener($dataTable, $report, $request);
+        $dataTable = $this->applyTotalsCalculator($dataTable, $report, $request);
+        $dataTable = $this->applyGenericFilters($label, $dataTable, $report, $request);
+
+        $dataTable->filter(array($this, 'computeProcessedMetrics'), array($report));
+
+        // we automatically safe decode all datatable labels (against xss)
+        $dataTable->queueFilter('SafeDecodeLabel');
+
+        $dataTable = $this->applyQueuedFilters($dataTable, $request);
+        $dataTable = $this->applyRequestedColumnDeletion($dataTable, $request);
+        $dataTable = $this->applyLabelFilter($label, $dataTable, $report, $request);
+        $dataTable = $this->applyProcessedMetricsFormatting($dataTable, $report, $applyFormatting);
+
+        return $dataTable;
+    }
+
+    /**
+     * @param DataTableInterface $dataTable
+     * @param Report|null $report
+     * @param $request
+     * @return DataTableInterface
+     */
+    private function applyPivotByFilter(DataTableInterface $dataTable, $report, $request)
+    {
+        $pivotBy = Common::getRequestVar('pivotBy', false, 'string', $request);
+        if (!empty($pivotBy)) {
+            $reportId = $report->getModule() . '.' . $report->getAction();
+            $pivotByColumn = Common::getRequestVar('pivotByColumn', false, 'string', $request);
+            $pivotByColumnLimit = Common::getRequestVar('pivotByColumnLimit', false, 'int', $request);
+
+            $dataTable->filter('PivotByDimension', array($reportId, $pivotBy, $pivotByColumn, $pivotByColumnLimit,
+                PivotByDimension::isSegmentFetchingEnabledInConfig()));
+        }
+        return $dataTable;
+    }
+
+    /**
+     * @param DataTableInterface $dataTable
+     * @param Report|null $report
+     * @param $request
+     * @return DataTable|DataTableInterface|DataTable\Map
+     */
+    private function applyFlattener($dataTable, $report, $request)
+    {
+        if (Common::getRequestVar('flat', '0', 'string', $request) == '1') {
+            $flattener = new Flattener($report->getModule(), $report->getAction(), $request);
+            if (Common::getRequestVar('include_aggregate_rows', '0', 'string', $request) == '1') {
+                $flattener->includeAggregateRows();
+            }
+            $dataTable = $flattener->flatten($dataTable);
+        }
+        return $dataTable;
+    }
+
+    /**
+     * @param DataTableInterface $dataTable
+     * @param Report|null $report
+     * @param $request
+     * @return DataTableInterface
+     */
+    private function applyTotalsCalculator($dataTable, $report, $request)
+    {
+        if (1 == Common::getRequestVar('totals', '1', 'integer', $request)) {
+            $reportTotalsCalculator = new ReportTotalsCalculator($report->getModule(), $report->getAction(), $request);
+            $dataTable     = $reportTotalsCalculator->calculate($dataTable);
+        }
+        return $dataTable;
+    }
+
+    /**
+     * @param string $label
+     * @param DataTableInterface $dataTable
+     * @param Report|null $report
+     * @param $request
+     * @return DataTableInterface
+     */
+    private function applyGenericFilters($label, $dataTable, $report, $request)
+    {
+        // if the flag disable_generic_filters is defined we skip the generic filters
+        if (0 == Common::getRequestVar('disable_generic_filters', '0', 'string', $request)) {
+            $self = $this;
+
+            $genericFilter = new DataTableGenericFilter($request, $report);
+
+            if ($genericFilter->areProcessedMetricsNeededFor($report)) {
+                $dataTable->filter(function (DataTable $table) use ($self, $report) {
+                    $self->computeProcessedMetrics($table, $report);
+                });
+            }
+
+            if (!empty($label)) {
+                $genericFilter->disableFilters(array('Limit', 'Truncate'));
+            }
+
+            $genericFilter->filter($dataTable);
+        }
+
+        return $dataTable;
+    }
+
+    /**
+     * @param DataTableInterface $dataTable
+     * @param $request
+     * @return DataTableInterface
+     */
+    private function applyQueuedFilters($dataTable, $request)
+    {
+        // if the flag disable_queued_filters is defined we skip the filters that were queued
+        if (Common::getRequestVar('disable_queued_filters', 0, 'int', $request) == 0) {
+            $dataTable->applyQueuedFilters();
+        }
+        return $dataTable;
+    }
+
+    /**
+     * @param DataTableInterface $dataTable
+     * @param $request
+     * @return DataTableInterface
+     */
+    private function applyRequestedColumnDeletion($dataTable, $request)
+    {
+        // use the ColumnDelete filter if hideColumns/showColumns is provided (must be done
+        // after queued filters are run so processed metrics can be removed, too)
+        $hideColumns = Common::getRequestVar('hideColumns', '', 'string', $request);
+        $showColumns = Common::getRequestVar('showColumns', '', 'string', $request);
+        if (empty($showColumns)) {
+            // if 'columns' is used, we remove all temporary metrics by showing only the columns specified in
+            // 'columns'
+            $showColumns = Common::getRequestVar('columns', '', 'string', $request);
+        }
+
+        if (!empty($hideColumns)
+            || !empty($showColumns)
+        ) {
+            $dataTable->filter('ColumnDelete', array($hideColumns, $showColumns));
+        }
+
+        return $dataTable;
+    }
+
+    /**
+     * @param string $label
+     * @param DataTableInterface $dataTable
+     * @param Report $report
+     * @return DataTableInterface
+     */
+    private function applyLabelFilter($label, $dataTable, $report, $request)
+    {
+        // apply label filter: only return rows matching the label parameter (more than one if more than one label)
+        if (!empty($label)) {
+            $addLabelIndex = Common::getRequestVar('labelFilterAddLabelIndex', 0, 'int', $request) == 1;
+
+            $filter = new LabelFilter($report->getModule(), $report->getAction(), $request);
+            $dataTable = $filter->filter($label, $dataTable, $addLabelIndex);
+        }
+        return $dataTable;
+    }
+
+    /**
+     * @param DataTableInterface $dataTable
+     * @param Report $report
+     * @return DataTableInterface
+     */
+    private function applyProcessedMetricsFormatting($dataTable, $report, $applyFormatting)
+    {
+        if ($applyFormatting) {
+            $dataTable->filter(array($this, 'formatProcessedMetrics'), array($report));
+        } else {
+            $dataTable->queueFilter(array($this, 'formatProcessedMetrics'), array($report)); // TODO: queuing does not always work.
+        }
+
+        return $dataTable;
+    }
+
+    /**
+     * Returns the value for the label query parameter which can be either a string
+     * (ie, label=...) or array (ie, label[]=...).
+     *
+     * @param array $request
+     * @return array
+     */
+    public static function getLabelFromRequest($request)
+    {
+        $label = Common::getRequestVar('label', array(), 'array', $request);
+        if (empty($label)) {
+            $label = Common::getRequestVar('label', '', 'string', $request);
+            if (!empty($label)) {
+                $label = array($label);
+            }
+        }
+
+        $label = self::unsanitizeLabelParameter($label);
+        return $label;
+    }
+
+    public static function unsanitizeLabelParameter($label)
+    {
+        // this is needed because Proxy uses Common::getRequestVar which in turn
+        // uses Common::sanitizeInputValue. This causes the > that separates recursive labels
+        // to become &gt; and we need to undo that here.
+        $label = Common::unsanitizeInputValues($label);
+        return $label;
+    }
+
+    private function computeProcessedMetrics(DataTable $dataTable, $report)
+    {
+        if ($dataTable->getMetadata(self::PROCESSED_METRICS_COMPUTED_FLAG)) {
+            return;
+        }
+
+        $dataTable->setMetadata(self::PROCESSED_METRICS_COMPUTED_FLAG, true);
+
+        $processedMetrics = $this->getProcessedMetricsFor($dataTable, $report);
+        if (empty($processedMetrics)) {
+            return;
+        }
+
+        foreach ($processedMetrics as $name => $processedMetric) {
+            if (!$processedMetric->beforeCompute($this, $dataTable)) {
+                continue;
+            }
+
+            foreach ($dataTable->getRows() as $row) {
+                if ($row->getColumn($name) === false) { // do not compute the metric if it has been computed already
+                    $row->addColumn($name, $processedMetric->compute($row));
+
+                    $subtable = $row->getSubtable();
+                    if (!empty($subtable)) {
+                        $this->computeProcessedMetrics($subtable, $report);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * public for use as callback.
+     */
+    public function formatProcessedMetrics(DataTable $dataTable, $report)
+    {
+        if ($dataTable->getMetadata(self::PROCESSED_METRICS_FORMATTED_FLAG)) {
+            return;
+        }
+
+        $dataTable->setMetadata(self::PROCESSED_METRICS_FORMATTED_FLAG, true);
+
+        $processedMetrics = $this->getProcessedMetricsFor($dataTable, $report);
+        if (empty($processedMetrics)) {
+            return;
+        }
+
+        foreach ($dataTable->getRows() as $row) {
+            foreach ($processedMetrics as $name => $processedMetric) {
+                $columnValue = $row->getColumn($name);
+                if ($columnValue !== false) {
+                    $row->setColumn($name, $processedMetric->format($columnValue));
+                }
+
+                $subtable = $row->getSubtable();
+                if (!empty($subtable)) {
+                    $this->formatProcessedMetrics($subtable, $report);
+                }
+            }
+        }
+    }
+
+    /**
+     * @param DataTable $dataTable
+     * @param Report $report
+     * @return ProcessedMetric[]
+     */
+    private function getProcessedMetricsFor(DataTable $dataTable, $report)
+    {
+        $processedMetrics = $dataTable->getMetadata(DataTable::EXTRA_PROCESSED_METRICS_METADATA_NAME) ?: array();
+
+        if (!empty($report)) {
+           $processedMetrics = array_merge($processedMetrics, $report->processedMetrics ?: array());
+        }
+
+        $result = array();
+        foreach ($processedMetrics as $metric) {
+            if (!($metric instanceof ProcessedMetric)) {
+                continue;
+            }
+
+            $result[$metric->getName()] = $metric;
+        }
+        return $result;
+    }
+}
\ No newline at end of file
diff --git a/core/API/ResponseBuilder.php b/core/API/ResponseBuilder.php
index 248c4401aea27f94e9dc15224169445d8456fbde..dbf79b1ec8a306894edf4a1e6e0a654e54b24a5d 100644
--- a/core/API/ResponseBuilder.php
+++ b/core/API/ResponseBuilder.php
@@ -9,12 +9,8 @@
 namespace Piwik\API;
 
 use Exception;
-use Piwik\API\DataTableManipulator\Flattener;
-use Piwik\API\DataTableManipulator\LabelFilter;
-use Piwik\API\DataTableManipulator\ReportTotalsCalculator;
 use Piwik\Common;
 use Piwik\DataTable;
-use Piwik\DataTable\Filter\PivotByDimension;
 use Piwik\DataTable\Renderer;
 use Piwik\DataTable\DataTableInterface;
 use Piwik\DataTable\Filter\ColumnDelete;
@@ -33,6 +29,8 @@ class ResponseBuilder
     private $apiModule = false;
     private $apiMethod = false;
 
+    private $postProcessor;
+
     /**
      * @param string $outputFormat
      * @param array $request
@@ -42,6 +40,7 @@ class ResponseBuilder
         $this->outputFormat = $outputFormat;
         $this->request      = $request;
         $this->apiRenderer  = ApiRenderer::factory($outputFormat, $request);
+        $this->postProcessor = new DataTablePostProcessor();
     }
 
     public function disableSendHeader()
@@ -168,90 +167,10 @@ class ResponseBuilder
 
     private function handleDataTable(DataTableInterface $datatable)
     {
-        $label = $this->getLabelFromRequest($this->request);
         $report = Report::factory($this->apiModule, $this->apiMethod);
+        $applyFormatting = !($this->apiRenderer instanceof Original);
 
-        $genericFilter = new DataTableGenericFilter($this->request, $report);
-
-        // handle pivot by dimension filter
-        $pivotBy = Common::getRequestVar('pivotBy', false, 'string', $this->request);
-        if (!empty($pivotBy)) {
-            $reportId = $this->apiModule . '.' . $this->apiMethod;
-            $pivotByColumn = Common::getRequestVar('pivotByColumn', false, 'string', $this->request);
-            $pivotByColumnLimit = Common::getRequestVar('pivotByColumnLimit', false, 'int', $this->request);
-
-            $datatable->filter('PivotByDimension', array($reportId, $pivotBy, $pivotByColumn, $pivotByColumnLimit,
-                PivotByDimension::isSegmentFetchingEnabledInConfig()));
-        }
-
-        // if requested, flatten nested tables
-        if (Common::getRequestVar('flat', '0', 'string', $this->request) == '1') {
-            $flattener = new Flattener($this->apiModule, $this->apiMethod, $this->request);
-            if (Common::getRequestVar('include_aggregate_rows', '0', 'string', $this->request) == '1') {
-                $flattener->includeAggregateRows();
-            }
-            $datatable = $flattener->flatten($datatable);
-        }
-
-        if (1 == Common::getRequestVar('totals', '1', 'integer', $this->request)) {
-            $reportTotalsCalculator = new ReportTotalsCalculator($this->apiModule, $this->apiMethod, $this->request);
-            $datatable     = $reportTotalsCalculator->calculate($datatable);
-        }
-
-        // if the flag disable_generic_filters is defined we skip the generic filters
-        if (0 == Common::getRequestVar('disable_generic_filters', '0', 'string', $this->request)) {
-            $datatable->filter(function (DataTable $table) use ($genericFilter) {
-                $genericFilter->computeProcessedMetricsIfNeeded($table);
-            });
-
-            if (!empty($label)) {
-                $genericFilter->disableFilters(array('Limit', 'Truncate'));
-            }
-
-            $genericFilter->filter($datatable);
-        }
-
-        $datatable->filter(function (DataTable $table) use ($genericFilter) {
-            $genericFilter->computeProcessedMetrics($table);
-        });
-
-        // we automatically safe decode all datatable labels (against xss)
-        $datatable->queueFilter('SafeDecodeLabel');
-
-        // if the flag disable_queued_filters is defined we skip the filters that were queued
-        if (Common::getRequestVar('disable_queued_filters', 0, 'int', $this->request) == 0) {
-            $datatable->applyQueuedFilters();
-        }
-
-        // use the ColumnDelete filter if hideColumns/showColumns is provided (must be done
-        // after queued filters are run so processed metrics can be removed, too)
-        $hideColumns = Common::getRequestVar('hideColumns', '', 'string', $this->request);
-        $showColumns = Common::getRequestVar('showColumns', '', 'string', $this->request);
-        if (empty($showColumns)) {
-            $showColumns = Common::getRequestVar('columns', '', 'string', $this->request); // TODO: note backwards compatibility
-        }
-
-        if (!empty($hideColumns)
-            || !empty($showColumns)
-        ) {
-            $datatable->filter('ColumnDelete', array($hideColumns, $showColumns));
-        }
-
-        // apply label filter: only return rows matching the label parameter (more than one if more than one label)
-        if (!empty($label)) {
-            $addLabelIndex = Common::getRequestVar('labelFilterAddLabelIndex', 0, 'int', $this->request) == 1;
-
-            $filter = new LabelFilter($this->apiModule, $this->apiMethod, $this->request);
-            $datatable = $filter->filter($label, $datatable, $addLabelIndex);
-        }
-
-        if (!empty($report)) {
-            if (!($this->apiRenderer instanceof Original)) {
-                $datatable->filter(array($report, 'formatProcessedMetrics'));
-            } else {
-                $datatable->queueFilter(array($report, 'formatProcessedMetrics')); // TODO: queuing does not always work.
-            }
-        }
+        $datatable = $this->postProcessor->process($datatable, $report, $this->request, $applyFormatting);
 
         return $this->apiRenderer->renderDataTable($datatable);
     }
@@ -286,36 +205,6 @@ class ResponseBuilder
         return $this->apiRenderer->renderArray($array);
     }
 
-    /**
-     * Returns the value for the label query parameter which can be either a string
-     * (ie, label=...) or array (ie, label[]=...).
-     *
-     * @param array $request
-     * @return array
-     */
-    public static function getLabelFromRequest($request)
-    {
-        $label = Common::getRequestVar('label', array(), 'array', $request);
-        if (empty($label)) {
-            $label = Common::getRequestVar('label', '', 'string', $request);
-            if (!empty($label)) {
-                $label = array($label);
-            }
-        }
-
-        $label = self::unsanitizeLabelParameter($label);
-        return $label;
-    }
-
-    public static function unsanitizeLabelParameter($label)
-    {
-        // this is needed because Proxy uses Common::getRequestVar which in turn
-        // uses Common::sanitizeInputValue. This causes the > that separates recursive labels
-        // to become &gt; and we need to undo that here.
-        $label = Common::unsanitizeInputValues($label);
-        return $label;
-    }
-
     private function sendHeaderIfEnabled()
     {
         if ($this->sendHeader) {
diff --git a/core/Plugin/Report.php b/core/Plugin/Report.php
index fb009f06a25b1cef9c8edd8d356f6fd624dd148e..11b50fc3efd2305b2768cce9bcfe9afc99cf8cfa 100644
--- a/core/Plugin/Report.php
+++ b/core/Plugin/Report.php
@@ -121,10 +121,8 @@ class Report
      * Eg `array('avg_time_on_site', 'nb_actions_per_visit', ...)`
      * @var array|false
      * @api
-     *
-     * TODO: shouldn't be public
      */
-    public $processedMetrics = array('nb_actions_per_visit', 'avg_time_on_site', 'bounce_rate', 'conversion_rate');
+    protected $processedMetrics = array('nb_actions_per_visit', 'avg_time_on_site', 'bounce_rate', 'conversion_rate');
     // for a little performance improvement we avoid having to call Metrics::getDefaultProcessedMetrics for each report
 
     /**
@@ -361,7 +359,18 @@ class Report
     }
 
     /**
-     * TODO
+     * Returns the list of metrics required at minimum for a report factoring in the columns requested by
+     * the report requester.
+     *
+     * This will return all the metrics requested (or all the metrics in the report if nothing is requested)
+     * **plus** the metrics required to calculate the requested processed metrics.
+     *
+     * This method should be used in **Plugin.get** API methods.
+     *
+     * @param string[]|null $allColumns The list of all available columns. Defaults to this report's metrics
+     *                                  and processed metrics.
+     * @param string[]|null $restrictToColumns The requested columns.
+     * @return string[]
      */
     public function getMetricsRequiredForReport($allColumns = null, $restrictToColumns = null)
     {
@@ -697,97 +706,13 @@ class Report
     }
 
     /**
-     * TODO
+     * Returns true if this report has a processed metric with the `$name` name.
      *
-     * @return ProcessedMetric[]
-     */
-    public function getProcessedMetricsFor(DataTable $dataTable)
-    {
-        $dataTableProcessedMetrics = $dataTable->getMetadata(DataTable::EXTRA_PROCESSED_METRICS_METADATA_NAME) ?: array();
-
-        $processedMetrics = $this->processedMetrics ?: array();
-        $processedMetrics = array_merge($processedMetrics, $dataTableProcessedMetrics);
-
-        $result = array();
-        foreach ($processedMetrics as $metric) {
-            if (!($metric instanceof ProcessedMetric)) {
-                continue;
-            }
-
-            $result[$metric->getName()] = $metric;
-        }
-        return $result;
-    }
-
-    /**
-     * TODO
+     * Will only search through {@link Piwik\Plugin\ProcessedMetric} instances, so string entries
+     * in {@link $processedMetrics} will be ignored.
      *
-     * TODO: put in new non-filter class. do not mark w/ @api.
-     */
-    public function computeProcessedMetrics(DataTable $dataTable)
-    {
-        if ($dataTable->getMetadata('processed_metrics_computed')) {
-            return;
-        }
-
-        $dataTable->setMetadata('processed_metrics_computed', true); // TODO: metadataname should be const
-
-        $processedMetrics = $this->getProcessedMetricsFor($dataTable);
-        if (empty($processedMetrics)) {
-            return;
-        }
-
-        foreach ($processedMetrics as $name => $processedMetric) {
-            if (!$processedMetric->beforeCompute($this, $dataTable)) {
-                continue;
-            }
-
-            foreach ($dataTable->getRows() as $row) {
-                if ($row->getColumn($name) === false) { // do not compute the metric if it has been computed already
-                    $row->addColumn($name, $processedMetric->compute($row));
-
-                    $subtable = $row->getSubtable();
-                    if (!empty($subtable)) {
-                        $this->computeProcessedMetrics($subtable);
-                    }
-                }
-            }
-        }
-    }
-
-    /**
-     * TODO
-     */
-    public function formatProcessedMetrics(DataTable $dataTable)
-    {
-        if ($dataTable->getMetadata('processed_metrics_formatted')) {
-            return;
-        }
-
-        $dataTable->setMetadata('processed_metrics_formatted', true); // TODO: metadataname should be const
-
-        $processedMetrics = $this->getProcessedMetricsFor($dataTable);
-        if (empty($processedMetrics)) {
-            return;
-        }
-
-        foreach ($dataTable->getRows() as $row) {
-            foreach ($processedMetrics as $name => $processedMetric) {
-                $columnValue = $row->getColumn($name);
-                if ($columnValue !== false) {
-                    $row->setColumn($name, $processedMetric->format($columnValue));
-                }
-
-                $subtable = $row->getSubtable();
-                if (!empty($subtable)) {
-                    $this->formatProcessedMetrics($subtable);
-                }
-            }
-        }
-    }
-
-    /**
-     * TODO
+     * @param string $name
+     * @return bool
      */
     public function hasProcessedMetric($name)
     {
diff --git a/plugins/API/RowEvolution.php b/plugins/API/RowEvolution.php
index b57a788050d54e553497ee5cf85e1fc1c228866e..c4d96fb9126c842a90f36456481e0096d35f165e 100644
--- a/plugins/API/RowEvolution.php
+++ b/plugins/API/RowEvolution.php
@@ -10,6 +10,7 @@ namespace Piwik\Plugins\API;
 
 use Exception;
 use Piwik\API\DataTableManipulator\LabelFilter;
+use Piwik\API\DataTablePostProcessor;
 use Piwik\API\Request;
 use Piwik\API\ResponseBuilder;
 use Piwik\Common;
@@ -48,7 +49,7 @@ class RowEvolution
             throw new Exception("Row evolutions can not be processed with this combination of \'date\' and \'period\' parameters.");
         }
 
-        $label = ResponseBuilder::unsanitizeLabelParameter($label);
+        $label = DataTablePostProcessor::unsanitizeLabelParameter($label);
         $labels = Piwik::getArrayFromApiParameter($label);
 
         $metadata = $this->getRowEvolutionMetaData($idSite, $period, $date, $apiModule, $apiAction, $language, $idGoal);
diff --git a/plugins/CoreHome/DataTableRowAction/RowEvolution.php b/plugins/CoreHome/DataTableRowAction/RowEvolution.php
index 6f2db7ab9dbb1476ac6f936922a6743164087f19..2e2631203a2f9e48378887e5384cfd6587a360d3 100644
--- a/plugins/CoreHome/DataTableRowAction/RowEvolution.php
+++ b/plugins/CoreHome/DataTableRowAction/RowEvolution.php
@@ -9,6 +9,7 @@
 namespace Piwik\Plugins\CoreHome\DataTableRowAction;
 
 use Exception;
+use Piwik\API\DataTablePostProcessor;
 use Piwik\API\Request;
 use Piwik\API\ResponseBuilder;
 use Piwik\Common;
@@ -85,7 +86,7 @@ class RowEvolution
         $this->apiMethod = Common::getRequestVar('apiMethod', '', 'string');
         if (empty($this->apiMethod)) throw new Exception("Parameter apiMethod not set.");
 
-        $this->label = ResponseBuilder::getLabelFromRequest($_GET);
+        $this->label = DataTablePostProcessor::getLabelFromRequest($_GET);
         if (!is_array($this->label)) {
             throw new Exception("Expected label to be an array, got instead: " . $this->label);
         }