Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/matomo-org/matomo.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordiosmosis <diosmosis@users.noreply.github.com>2018-08-09 14:24:16 +0300
committerMatthieu Aubry <mattab@users.noreply.github.com>2018-08-09 14:24:16 +0300
commit2b77bb4d609dd0ec377a9edc32536f22dd4dabb2 (patch)
tree14e38fd0b07b26dbcf50dd2e25793b556d45baa7
parent6112cc2ddf955b9c7183ba767e0a8acce93111ef (diff)
Add notification when report w/ segment has no data, but segment is unprocessed (#12823)3.6.0-b4
* When a report has no data, check if it is for an unprocessed segment and display an explanatory notification. * Remove transient notifications on reporting page change, allow datatable views to add notifications, use to display unprocessed segment message. * Update changelog. * Internationalize unprocessed segment message. * Parse notification divs in dashboardWidget.js when setting widget content. * Tweak message. * Change PR to use different approach: throw exception when no archives found and segment is used, then detect exception in new event. * Update changelog + document new event. * Unfinished review changes * more review fixes * Do not show notification w/ custom segments. * apply pr review * Apply review. * undo escaping since it was not needed & get test to pass * Different strategy + new test. * Fix tests. * Update two expected screenshots.
-rw-r--r--core/API/Request.php50
-rw-r--r--core/Archive.php6
-rw-r--r--core/ArchiveProcessor/Rules.php2
-rw-r--r--core/Plugin/Visualization.php21
-rw-r--r--core/View.php10
-rw-r--r--plugins/API/API.php2
-rw-r--r--plugins/CoreHome/CoreHome.php2
-rw-r--r--plugins/CoreHome/angularjs/notification/notification.service.js53
-rw-r--r--plugins/CoreHome/angularjs/reporting-page/reportingpage.controller.js6
-rw-r--r--plugins/CoreHome/angularjs/widget-loader/widgetloader.directive.js6
-rw-r--r--plugins/CoreHome/javascripts/notification_parser.js31
-rw-r--r--plugins/CoreHome/templates/_dataTable.twig8
-rw-r--r--plugins/Dashboard/javascripts/dashboardWidget.js4
-rw-r--r--plugins/SegmentEditor/Model.php10
-rw-r--r--plugins/SegmentEditor/SegmentEditor.php133
-rw-r--r--plugins/SegmentEditor/UnprocessedSegmentException.php98
-rw-r--r--plugins/SegmentEditor/lang/en.json19
-rw-r--r--plugins/SegmentEditor/templates/_unprocessedSegmentMessage.twig18
-rw-r--r--plugins/SegmentEditor/tests/System/UnprocessedSegmentsTest.php226
-rw-r--r--plugins/SegmentEditor/tests/System/expected/test___VisitsSummary.get_autoArchiveSegmentNoDataPreprocessed.xml14
-rw-r--r--plugins/SegmentEditor/tests/System/expected/test___VisitsSummary.get_autoArchiveSegmentPreprocessed.xml14
-rw-r--r--plugins/SegmentEditor/tests/System/expected/test___VisitsSummary.get_autoArchiveSegmentUnprocessed.xml4
-rw-r--r--plugins/SegmentEditor/tests/System/expected/test___VisitsSummary.get_customSegmentPreprocessed.xml14
-rw-r--r--plugins/SegmentEditor/tests/System/expected/test___VisitsSummary.get_customSegmentUnprocessed.xml4
-rw-r--r--plugins/SegmentEditor/tests/System/expected/test___VisitsSummary.get_noLogDataSegmentUnprocessed.xml14
-rw-r--r--plugins/SegmentEditor/tests/System/expected/test___VisitsSummary.get_noLogDataSegmentUnprocessedMultiSite.xml6
-rw-r--r--plugins/SegmentEditor/tests/System/expected/test___VisitsSummary.get_realTimeSegmentUnprocessed.xml4
-rw-r--r--plugins/SegmentEditor/tests/UI/UnprocessedSegment_spec.js55
-rw-r--r--plugins/SegmentEditor/tests/UI/expected-screenshots/UnprocessedSegmentTest_custom_segment.png3
-rw-r--r--plugins/SegmentEditor/tests/UI/expected-screenshots/UnprocessedSegmentTest_unprocessed_segment.png3
-rw-r--r--plugins/UsersManager/tests/Integration/UsersManagerTest.php1
-rw-r--r--tests/PHPUnit/Framework/Mock/FakeAccess.php4
-rw-r--r--tests/lib/screenshot-testing/support/test-environment.js1
33 files changed, 795 insertions, 51 deletions
diff --git a/core/API/Request.php b/core/API/Request.php
index b4859bb85a..1e8895a281 100644
--- a/core/API/Request.php
+++ b/core/API/Request.php
@@ -72,6 +72,13 @@ use Piwik\UrlHelper;
*/
class Request
{
+ /**
+ * The count of nested API request invocations. Used to determine if the currently executing request is the root or not.
+ *
+ * @var int
+ */
+ private static $nestedApiInvocationCount = 0;
+
private $request = null;
/**
@@ -223,6 +230,7 @@ class Request
$shouldReloadAuth = false;
try {
+ ++self::$nestedApiInvocationCount;
// IP check is needed here as we cannot listen to API.Request.authenticate as it would then not return proper API format response.
// We can also not do it by listening to API.Request.dispatch as by then the user is already authenticated and we want to make sure
@@ -258,6 +266,8 @@ class Request
Log::debug($e);
$toReturn = $response->getResponseException($e);
+ } finally {
+ --self::$nestedApiInvocationCount;
}
if ($shouldReloadAuth) {
@@ -297,11 +307,11 @@ class Request
/**
* @ignore
* @internal
- * @param bool $isRootRequestApiRequest
+ * @param string $currentApiMethod
*/
- public static function setIsRootRequestApiRequest($isRootRequestApiRequest)
+ public static function setIsRootRequestApiRequest($currentApiMethod)
{
- Cache::getTransientCache()->save('API.setIsRootRequestApiRequest', $isRootRequestApiRequest);
+ Cache::getTransientCache()->save('API.setIsRootRequestApiRequest', $currentApiMethod);
}
/**
@@ -313,8 +323,22 @@ class Request
*/
public static function isRootRequestApiRequest()
{
- $isApi = Cache::getTransientCache()->fetch('API.setIsRootRequestApiRequest');
- return !empty($isApi);
+ $apiMethod = Cache::getTransientCache()->fetch('API.setIsRootRequestApiRequest');
+ return !empty($apiMethod);
+ }
+
+ /**
+ * Checks if the currently executing API request is the root API request or not.
+ *
+ * Note: the "root" API request is the first request made. Within that request, further API methods
+ * can be called programmatically. These requests are considered "child" API requests.
+ *
+ * @return bool
+ * @throws Exception
+ */
+ public static function isCurrentApiRequestTheRootApiRequest()
+ {
+ return self::$nestedApiInvocationCount == 1;
}
/**
@@ -330,10 +354,24 @@ class Request
*/
public static function isApiRequest($request)
{
+ $method = self::getMethodIfApiRequest($request);
+ return !empty($method);
+ }
+
+ /**
+ * Returns the current API method being executed, if the current request is an API request.
+ *
+ * @param array $request eg array('module' => 'API', 'method' => 'Test.getMethod')
+ * @return string|null
+ * @throws Exception
+ */
+ public static function getMethodIfApiRequest($request)
+ {
$module = Common::getRequestVar('module', '', 'string', $request);
$method = Common::getRequestVar('method', '', 'string', $request);
- return $module === 'API' && !empty($method) && (count(explode('.', $method)) === 2);
+ $isApi = $module === 'API' && !empty($method) && (count(explode('.', $method)) === 2);
+ return $isApi ? $method : null;
}
/**
diff --git a/core/Archive.php b/core/Archive.php
index 3a598eae88..72e281eed8 100644
--- a/core/Archive.php
+++ b/core/Archive.php
@@ -547,8 +547,12 @@ class Archive implements ArchiveQuery
$dataNames, $archiveDataType, $this->params->getIdSites(), $this->params->getPeriods(), $defaultRow = null);
$archiveIds = $this->getArchiveIds($archiveNames);
-
if (empty($archiveIds)) {
+ /**
+ * Triggered when no archive data is found in an API request.
+ * @ignore
+ */
+ Piwik::postEvent('Archive.noArchivedData');
return $result;
}
diff --git a/core/ArchiveProcessor/Rules.php b/core/ArchiveProcessor/Rules.php
index 09be79eea2..5767334941 100644
--- a/core/ArchiveProcessor/Rules.php
+++ b/core/ArchiveProcessor/Rules.php
@@ -68,7 +68,7 @@ class Rules
* @param $idSites
* @return array
*/
- private static function getSegmentsToProcess($idSites)
+ public static function getSegmentsToProcess($idSites)
{
$knownSegmentsToArchiveAllSites = SettingsPiwik::getKnownSegmentsToArchive();
diff --git a/core/Plugin/Visualization.php b/core/Plugin/Visualization.php
index 5f5a58462f..6d948ae536 100644
--- a/core/Plugin/Visualization.php
+++ b/core/Plugin/Visualization.php
@@ -209,12 +209,12 @@ class Visualization extends ViewDataTable
}
$view = new View("@CoreHome/_dataTable");
+ $view->assign($this->templateVars);
if (!empty($loadingError)) {
$view->error = $loadingError;
}
- $view->assign($this->templateVars);
$view->visualization = $this;
$view->visualizationTemplate = static::TEMPLATE_FILE;
$view->visualizationCssClass = $this->getDefaultDataTableCssClass();
@@ -243,10 +243,29 @@ class Visualization extends ViewDataTable
$view->reportLastUpdatedMessage = $this->reportLastUpdatedMessage;
$view->footerIcons = $this->config->footer_icons;
$view->isWidget = Common::getRequestVar('widget', 0, 'int');
+ $view->notifications = [];
+
+ if (empty($this->dataTable) || !$this->hasAnyData($this->dataTable)) {
+ /**
+ * @ignore
+ */
+ Piwik::postEvent('Visualization.onNoData', [$view]);
+ }
return $view->render();
}
+ private function hasAnyData(DataTable\DataTableInterface $dataTable)
+ {
+ $hasData = false;
+ $dataTable->filter(function (DataTable $table) use (&$hasData) {
+ if ($table->getRowsCount() > 0) {
+ $hasData = true;
+ }
+ });
+ return $hasData;
+ }
+
/**
* @internal
*/
diff --git a/core/View.php b/core/View.php
index 9fac3137f0..bf17b80926 100644
--- a/core/View.php
+++ b/core/View.php
@@ -214,6 +214,16 @@ class View implements ViewInterface
return isset($this->templateVars[$name]);
}
+ /**
+ * Unsets a template variable.
+ *
+ * @param string $name The name of the template variable.
+ */
+ public function __unset($name)
+ {
+ unset($this->templateVars[$name]);
+ }
+
/** @var Twig */
static $twigCached = null;
diff --git a/plugins/API/API.php b/plugins/API/API.php
index 32733174d0..83bdc04623 100644
--- a/plugins/API/API.php
+++ b/plugins/API/API.php
@@ -756,7 +756,7 @@ class Plugin extends \Piwik\Plugin
public function detectIsApiRequest()
{
- Request::setIsRootRequestApiRequest(Request::isApiRequest($request = null));
+ Request::setIsRootRequestApiRequest(Request::getMethodIfApiRequest($request = null));
}
public function getStylesheetFiles(&$stylesheets)
diff --git a/plugins/CoreHome/CoreHome.php b/plugins/CoreHome/CoreHome.php
index 4499ee37b8..76e933ae51 100644
--- a/plugins/CoreHome/CoreHome.php
+++ b/plugins/CoreHome/CoreHome.php
@@ -167,7 +167,6 @@ class CoreHome extends \Piwik\Plugin
$jsFiles[] = "libs/jqplot/jqplot-custom.min.js";
$jsFiles[] = "plugins/CoreHome/javascripts/color_manager.js";
$jsFiles[] = "plugins/CoreHome/javascripts/notification.js";
- $jsFiles[] = "plugins/CoreHome/javascripts/notification_parser.js";
$jsFiles[] = "plugins/CoreHome/javascripts/numberFormatter.js";
$jsFiles[] = "plugins/CoreHome/javascripts/zen-mode.js";
$jsFiles[] = "plugins/CoreHome/javascripts/noreferrer.js";
@@ -237,6 +236,7 @@ class CoreHome extends \Piwik\Plugin
$jsFiles[] = "plugins/CoreHome/angularjs/notification/notification.controller.js";
$jsFiles[] = "plugins/CoreHome/angularjs/notification/notification.directive.js";
+ $jsFiles[] = "plugins/CoreHome/angularjs/notification/notification.service.js";
$jsFiles[] = "plugins/CoreHome/angularjs/ajax-form/ajax-form.controller.js";
$jsFiles[] = "plugins/CoreHome/angularjs/ajax-form/ajax-form.directive.js";
diff --git a/plugins/CoreHome/angularjs/notification/notification.service.js b/plugins/CoreHome/angularjs/notification/notification.service.js
new file mode 100644
index 0000000000..80e23d7a80
--- /dev/null
+++ b/plugins/CoreHome/angularjs/notification/notification.service.js
@@ -0,0 +1,53 @@
+/*!
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+(function () {
+ angular.module('piwikApp').factory('notifications', NotificationFactory);
+
+ NotificationFactory.$inject = [];
+
+ function NotificationFactory() {
+
+ return {
+ parseNotificationDivs: parseNotificationDivs,
+ clearTransientNotifications: clearTransientNotifications,
+ };
+
+ function parseNotificationDivs() {
+ var UI = require('piwik/UI');
+
+ var $notificationNodes = $('[data-role="notification"]');
+
+ $notificationNodes.each(function (index, notificationNode) {
+ $notificationNode = $(notificationNode);
+ var attributes = $notificationNode.data();
+ var message = $notificationNode.html();
+
+ if (message) {
+ var notification = new UI.Notification();
+ attributes.animate = false;
+ notification.show(message, attributes);
+ }
+
+ $notificationNodes.remove();
+ });
+ }
+
+ function clearTransientNotifications() {
+ $('[piwik-notification][type=transient]').each(function () {
+ var $element = angular.element(this);
+ $element.scope().$destroy();
+ $element.remove();
+ });
+ }
+ }
+
+ angular.module('piwikApp').run(['notifications', function (notifications) {
+ $(function () {
+ notifications.parseNotificationDivs();
+ });
+ }]);
+})();
diff --git a/plugins/CoreHome/angularjs/reporting-page/reportingpage.controller.js b/plugins/CoreHome/angularjs/reporting-page/reportingpage.controller.js
index 606f30e334..a0d38ab1e7 100644
--- a/plugins/CoreHome/angularjs/reporting-page/reportingpage.controller.js
+++ b/plugins/CoreHome/angularjs/reporting-page/reportingpage.controller.js
@@ -7,9 +7,9 @@
(function () {
angular.module('piwikApp').controller('ReportingPageController', ReportingPageController);
- ReportingPageController.$inject = ['$scope', 'piwik', '$rootScope', '$location', 'reportingPageModel', 'reportingPagesModel'];
+ ReportingPageController.$inject = ['$scope', 'piwik', '$rootScope', '$location', 'reportingPageModel', 'reportingPagesModel', 'notifications'];
- function ReportingPageController($scope, piwik, $rootScope, $location, pageModel, pagesModel) {
+ function ReportingPageController($scope, piwik, $rootScope, $location, pageModel, pagesModel, notifications) {
pageModel.resetPage();
$scope.pageModel = pageModel;
@@ -40,6 +40,8 @@
currentCategory = category;
currentSubcategory = subcategory;
+ notifications.clearTransientNotifications();
+
if (category === 'Dashboard_Dashboard' && $.isNumeric(subcategory) && $('[piwik-dashboard]').length) {
// hack to make loading of dashboards faster since all the information is already there in the
// piwik-dashboard widget, we can let the piwik-dashboard widget render the page. We need to find
diff --git a/plugins/CoreHome/angularjs/widget-loader/widgetloader.directive.js b/plugins/CoreHome/angularjs/widget-loader/widgetloader.directive.js
index c15eb97af6..c6d2326a58 100644
--- a/plugins/CoreHome/angularjs/widget-loader/widgetloader.directive.js
+++ b/plugins/CoreHome/angularjs/widget-loader/widgetloader.directive.js
@@ -19,9 +19,9 @@
(function () {
angular.module('piwikApp').directive('piwikWidgetLoader', piwikWidgetLoader);
- piwikWidgetLoader.$inject = ['piwik', 'piwikUrl', '$http', '$compile', '$q', '$location'];
+ piwikWidgetLoader.$inject = ['piwik', 'piwikUrl', '$http', '$compile', '$q', '$location', 'notifications'];
- function piwikWidgetLoader(piwik, piwikUrl, $http, $compile, $q, $location){
+ function piwikWidgetLoader(piwik, piwikUrl, $http, $compile, $q, $location, notifications){
return {
restrict: 'A',
transclude: true,
@@ -147,6 +147,8 @@
}
$compile(currentElement)(newScope);
+
+ notifications.parseNotificationDivs();
})['catch'](function () {
if (thisChangeId !== changeCounter) {
// another widget was requested meanwhile, ignore this response
diff --git a/plugins/CoreHome/javascripts/notification_parser.js b/plugins/CoreHome/javascripts/notification_parser.js
deleted file mode 100644
index 0747d02373..0000000000
--- a/plugins/CoreHome/javascripts/notification_parser.js
+++ /dev/null
@@ -1,31 +0,0 @@
-/**
- * Piwik - free/libre analytics platform
- *
- * @link http://piwik.org
- * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
- */
-
-$(document).ready((function ($, require) {
- return function () {
-
- var UI = require('piwik/UI');
-
- var $notificationNodes = $('[data-role="notification"]');
-
- $notificationNodes.each(function (index, notificationNode) {
- $notificationNode = $(notificationNode);
- var attributes = $notificationNode.data();
- var message = $notificationNode.html();
-
- if (message) {
- var notification = new UI.Notification();
- attributes.animate = false;
- notification.show(message, attributes);
- }
-
- $notificationNodes.remove();
- });
-
- }
-
-})(jQuery, require)); \ No newline at end of file
diff --git a/plugins/CoreHome/templates/_dataTable.twig b/plugins/CoreHome/templates/_dataTable.twig
index cdd5905933..90864d105c 100644
--- a/plugins/CoreHome/templates/_dataTable.twig
+++ b/plugins/CoreHome/templates/_dataTable.twig
@@ -76,6 +76,14 @@
</div>
</div>
+{% if notifications is not empty and notifications|length %}
+ {% for notificationId, n in notifications %}
+
+ {{ n.message|notification({'id': notificationId, 'type': n.type, 'title': n.title, 'noclear': n.hasNoClear, 'context': n.context, 'raw': n.raw}, false) }}
+
+ {% endfor %}
+{% endif %}
+
{% if showCardTableIsEmpty %}
</div></div>
{% endif %}
diff --git a/plugins/Dashboard/javascripts/dashboardWidget.js b/plugins/Dashboard/javascripts/dashboardWidget.js
index 22c0538471..749459005a 100644
--- a/plugins/Dashboard/javascripts/dashboardWidget.js
+++ b/plugins/Dashboard/javascripts/dashboardWidget.js
@@ -139,6 +139,10 @@
}
$widgetContent.removeClass('loading');
$widgetContent.trigger('widget:create', [self]);
+
+ angular.element(document).injector().invoke(['notifications', function (notifications) {
+ notifications.parseNotificationDivs();
+ }]);
}
// Reading segment from hash tag (standard case) or from the URL (when embedding dashboard)
diff --git a/plugins/SegmentEditor/Model.php b/plugins/SegmentEditor/Model.php
index 7e73b6a1ab..3aec99eb23 100644
--- a/plugins/SegmentEditor/Model.php
+++ b/plugins/SegmentEditor/Model.php
@@ -119,6 +119,15 @@ class Model
return $segments;
}
+ public function getSegmentByDefinition($definition)
+ {
+ $sql = $this->buildQuerySortedByName("definition = ?");
+ $bind = [$definition];
+
+ $segment = $this->getDb()->fetchRow($sql, $bind);
+ return $segment;
+ }
+
public function deleteSegment($idSegment)
{
$db = $this->getDb();
@@ -178,5 +187,4 @@ class Model
DbHelper::createTable(self::$rawPrefix, $segmentTable);
}
-
}
diff --git a/plugins/SegmentEditor/SegmentEditor.php b/plugins/SegmentEditor/SegmentEditor.php
index 9af29e2194..2ede0215a8 100644
--- a/plugins/SegmentEditor/SegmentEditor.php
+++ b/plugins/SegmentEditor/SegmentEditor.php
@@ -8,15 +8,29 @@
*/
namespace Piwik\Plugins\SegmentEditor;
+use Piwik\API\Request;
+use Piwik\ArchiveProcessor\Rules;
+use Piwik\Common;
use Piwik\Config;
use Piwik\Container\StaticContainer;
+use Piwik\DataAccess\ArchiveSelector;
+use Piwik\Notification;
use Piwik\Piwik;
use Piwik\Plugins\CoreHome\SystemSummary;
+use Piwik\Segment;
+use Piwik\SettingsPiwik;
+use Piwik\SettingsServer;
+use Piwik\Site;
+use Piwik\Period;
+use Piwik\Url;
+use Piwik\View;
/**
*/
class SegmentEditor extends \Piwik\Plugin
{
+ const NO_DATA_UNPROCESSED_SEGMENT_ID = 'nodata_segment_not_processed';
+
/**
* @see Piwik\Plugin::registerEvents
*/
@@ -30,6 +44,8 @@ class SegmentEditor extends \Piwik\Plugin
'Template.nextToCalendar' => 'getSegmentEditorHtml',
'System.addSystemSummaryItems' => 'addSystemSummaryItems',
'Translate.getClientSideTranslationKeys' => 'getClientSideTranslationKeys',
+ 'Visualization.onNoData' => 'onNoData',
+ 'Archive.noArchivedData' => 'onNoArchiveData',
);
}
@@ -71,6 +87,123 @@ class SegmentEditor extends \Piwik\Plugin
$segments = array_unique($segments);
}
+ public function onNoArchiveData()
+ {
+ // don't do check unless this is the root API request and it is an HTTP API request
+ if (!Request::isCurrentApiRequestTheRootApiRequest()
+ || !Request::isRootRequestApiRequest()
+ ) {
+ return null;
+ }
+
+ // don't do check during cron archiving
+ if (SettingsServer::isArchivePhpTriggered()) {
+ return null;
+ }
+
+ $segmentInfo = $this->getSegmentIfIsUnprocessed();
+ if (empty($segmentInfo)) {
+ return;
+ }
+
+ list($segment, $storedSegment, $isSegmentToPreprocess) = $segmentInfo;
+
+ throw new UnprocessedSegmentException($segment, $isSegmentToPreprocess, $storedSegment);
+ }
+
+ public function onNoData(View $dataTableView)
+ {
+ $segmentInfo = $this->getSegmentIfIsUnprocessed();
+ if (empty($segmentInfo)) {
+ return;
+ }
+
+ list($segment, $storedSegment, $isSegmentToPreprocess) = $segmentInfo;
+
+ if (!$isSegmentToPreprocess) {
+ return; // do not display the notification for custom segments
+ }
+
+ $segmentDisplayName = !empty($storedSegment['name']) ? $storedSegment['name'] : $segment;
+
+ $view = new View('@SegmentEditor/_unprocessedSegmentMessage.twig');
+ $view->isSegmentToPreprocess = $isSegmentToPreprocess;
+ $view->segmentName = $segmentDisplayName;
+ $view->visitorLogLink = '#' . Url::getCurrentQueryStringWithParametersModified([
+ 'category' => 'General_Visitors',
+ 'subcategory' => 'Live_VisitorLog',
+ ]);
+
+ $notification = new Notification($view->render());
+ $notification->priority = Notification::PRIORITY_HIGH;
+ $notification->context = Notification::CONTEXT_INFO;
+ $notification->flags = Notification::FLAG_NO_CLEAR;
+ $notification->type = Notification::TYPE_TRANSIENT;
+ $notification->raw = true;
+
+ $dataTableView->notifications[self::NO_DATA_UNPROCESSED_SEGMENT_ID] = $notification;
+ }
+
+ private function getSegmentIfIsUnprocessed()
+ {
+ // get idSites
+ $idSite = Common::getRequestVar('idSite', false);
+ if (empty($idSite)
+ || !is_numeric($idSite)
+ ) {
+ return null;
+ }
+
+ // get segment
+ $segment = Request::getRawSegmentFromRequest();
+ if (empty($segment)) {
+ return null;
+ }
+ $segment = new Segment($segment, [$idSite]);
+
+ // get period
+ $date = Common::getRequestVar('date', false);
+ $periodStr = Common::getRequestVar('period', false);
+ $period = Period\Factory::build($periodStr, $date);
+
+ // check if archiving is enabled. if so, the segment should have been processed.
+ $isArchivingDisabled = Rules::isArchivingDisabledFor([$idSite], $segment, $period);
+ if (!$isArchivingDisabled) {
+ return null;
+ }
+
+ // check if segment archive does not exist
+ $processorParams = new \Piwik\ArchiveProcessor\Parameters(new Site($idSite), $period, $segment);
+ $archiveIdAndStats = ArchiveSelector::getArchiveIdAndVisits($processorParams, null);
+ if (!empty($archiveIdAndStats[0])) {
+ return null;
+ }
+
+ $idSites = Site::getIdSitesFromIdSitesString($idSite);
+
+ if (strpos($date, ',') !== false) { // if getting multiple periods, check the whole range for visits
+ $periodStr = 'range';
+ }
+
+ // if no visits recorded, data will not appear, so don't show the message
+ $liveModel = new \Piwik\Plugins\Live\Model();
+ $visits = $liveModel->queryLogVisits($idSites, $periodStr, $date, $segment->getString(), $offset = 0, $limit = 1, null, null, 'ASC');
+ if (empty($visits)) {
+ return null;
+ }
+
+ // check if requested segment is segment to preprocess
+ $isSegmentToPreprocess = Rules::isSegmentPreProcessed([$idSite], $segment);
+
+ // this archive has no data, the report is for a segment that gets preprocessed, and the archive for this
+ // data does not exist. this means the data will be processed later. we let the user know so they will not
+ // be confused.
+ $model = new Model();
+ $storedSegment = $model->getSegmentByDefinition($segment->getString()) ?: null;
+
+ return [$segment, $storedSegment, $isSegmentToPreprocess];
+ }
+
public function install()
{
Model::install();
diff --git a/plugins/SegmentEditor/UnprocessedSegmentException.php b/plugins/SegmentEditor/UnprocessedSegmentException.php
new file mode 100644
index 0000000000..c0ddc338e2
--- /dev/null
+++ b/plugins/SegmentEditor/UnprocessedSegmentException.php
@@ -0,0 +1,98 @@
+<?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\Plugins\SegmentEditor;
+
+
+use Piwik\Piwik;
+use Piwik\Segment;
+
+class UnprocessedSegmentException extends \Exception
+{
+ /**
+ * @var Segment
+ */
+ private $segment;
+
+ /**
+ * @var array|null
+ */
+ private $storedSegment;
+
+ /**
+ * @var bool
+ */
+ private $isSegmentToPreprocess;
+
+ /**
+ * @param $segment
+ */
+ public function __construct(Segment $segment, $isSegmentToPreprocess, array $storedSegment = null)
+ {
+ parent::__construct(self::getErrorMessage($segment, $isSegmentToPreprocess, $storedSegment));
+
+ $this->segment = $segment;
+ $this->storedSegment = $storedSegment;
+ $this->isSegmentToPreprocess = $isSegmentToPreprocess;
+ }
+
+ /**
+ * @return Segment
+ */
+ public function getSegment()
+ {
+ return $this->segment;
+ }
+
+ /**
+ * @return array|null
+ */
+ public function getStoredSegment()
+ {
+ return $this->storedSegment;
+ }
+
+ private static function getErrorMessage(Segment $segment, $isSegmentToPreprocess, array $storedSegment = null)
+ {
+ if (empty($storedSegment)) {
+ // the segment was not created through the segment editor
+ return Piwik::translate('SegmentEditor_CustomUnprocessedSegmentApiError1')
+ . ' ' . Piwik::translate('SegmentEditor_CustomUnprocessedSegmentApiError2')
+ . ' ' . Piwik::translate('SegmentEditor_CustomUnprocessedSegmentApiError3')
+ . ' ' . Piwik::translate('SegmentEditor_CustomUnprocessedSegmentApiError4')
+ . ' ' . Piwik::translate('SegmentEditor_CustomUnprocessedSegmentApiError5')
+ . ' ' . Piwik::translate('SegmentEditor_CustomUnprocessedSegmentApiError6')
+ . ' ' . Piwik::translate('SegmentEditor_UnprocessedSegmentInVisitorLog3');
+ }
+
+ $segmentName = !empty($storedSegment['name']) ? $storedSegment['name'] : $segment->getString();
+
+ if (!$isSegmentToPreprocess) {
+ // the segment was created in the segment editor, but set to be processed in real time
+ return Piwik::translate('SegmentEditor_UnprocessedSegmentApiError1', [$segmentName, Piwik::translate('SegmentEditor_AutoArchiveRealTime')])
+ . ' ' . Piwik::translate('SegmentEditor_UnprocessedSegmentApiError2', [Piwik::translate('SegmentEditor_AutoArchivePreProcessed')])
+ . ' ' . Piwik::translate('SegmentEditor_UnprocessedSegmentApiError3');
+ }
+
+ // the segment is set to be processed during cron archiving, but has not been processed yet
+ return Piwik::translate('SegmentEditor_UnprocessedSegmentNoData1', ['(' . $segmentName . ')'])
+ . ' ' . Piwik::translate('SegmentEditor_UnprocessedSegmentNoData2')
+ . ' ' . Piwik::translate('SegmentEditor_CustomUnprocessedSegmentApiError5')
+ . ' ' . Piwik::translate('SegmentEditor_CustomUnprocessedSegmentApiError6')
+ . ' ' . Piwik::translate('SegmentEditor_UnprocessedSegmentInVisitorLog3');
+ }
+
+ /**
+ * @return bool
+ */
+ public function isSegmentToPreprocess()
+ {
+ return $this->isSegmentToPreprocess;
+ }
+} \ No newline at end of file
diff --git a/plugins/SegmentEditor/lang/en.json b/plugins/SegmentEditor/lang/en.json
index 1ea1eb8bae..b2ba38c07e 100644
--- a/plugins/SegmentEditor/lang/en.json
+++ b/plugins/SegmentEditor/lang/en.json
@@ -22,7 +22,7 @@
"SegmentDisplayedThisWebsiteOnly": "this website only",
"SegmentIsDisplayedForWebsite": "and processed for",
"SegmentNotApplied": "Segment '%s' not applied",
- "SegmentNotAppliedMessage": "You are requesting data for the Custom Segment '%s', this Matomo configuration currently prevents real time processing of reports for performance reasons.",
+ "SegmentNotAppliedMessage": "You are requesting data for the Custom Segment '%s', this Matomo's configuration currently prevents real time processing of reports for performance reasons.",
"SelectSegmentOfVisits": "Select a segment of visits:",
"ThisSegmentIsVisibleTo": "This segment is visible to:",
"VisibleToAllUsers": "all users",
@@ -36,6 +36,21 @@
"SegmentXIsAUnionOf": "%s is a union of these segments:",
"CustomSegment": "Custom Segment",
"SegmentOperatorIsNullOrEmpty": "is null or empty",
- "SegmentOperatorIsNotNullNorEmpty": "is not null nor empty"
+ "SegmentOperatorIsNotNullNorEmpty": "is not null nor empty",
+ "UnprocessedSegmentNoData1": "These reports have no data, because the Segment you requested %1$s has not yet been processed by the system.",
+ "UnprocessedSegmentNoData2": "Data for this Segment should become available in a few hours when processing completes. (If it does not, there may be a problem.)",
+ "UnprocessedSegmentInVisitorLog1": "%1$sMeanwhile you can use the Visitor Log%2$s to test whether your segment will match your users correctly by applying it there.",
+ "UnprocessedSegmentInVisitorLog2": "When applied, you can see immediately which visits and actions were matched by your segment.",
+ "UnprocessedSegmentInVisitorLog3": "This can help you confirm your Segment matches the users and actions you expect it to.",
+ "UnprocessedSegmentApiError1": "The Segment '%1$s' is set to '%2$s' but Matomo is not currently configured to process segmented reports in API requests.",
+ "UnprocessedSegmentApiError2": "To see data for this report in the future, you will need to edit your segment and choose the option labeled '%s'.",
+ "UnprocessedSegmentApiError3": "Then after a few hours your segment data should become available through the API. (If it does not, there may be a problem.)",
+ "CustomUnprocessedSegmentApiError1": "The Segment you requested has not yet been created in the Segment Editor and so the report data has not been pre-processed.",
+ "CustomUnprocessedSegmentApiError2": "To see data for this segment, you must go to Matomo and create this segment manually in the Segment Editor.",
+ "CustomUnprocessedSegmentApiError3": "(Alternatively, you can create a new segment programatically using the SegmentEditor.add API method).",
+ "CustomUnprocessedSegmentApiError4": "Once created the segment in the editor (or via API), this error message will disappear and within a few hours you will see your segmented report data, after the segment data has been pre-processed. (If it does not, there may be a problem.)",
+ "CustomUnprocessedSegmentApiError5": "Please note that you can test whether your segment will work without having to wait for it to be processed by using the Live.getLastVisitsDetails API.",
+ "CustomUnprocessedSegmentApiError6": "When using this API method, you will see which users and actions were matched by your &segment= parameter.",
+ "CustomUnprocessedSegmentNoData": "To see data for this segment, you must create this segment manually in the Segment Editor, then wait a couple hours for preprocessing to complete."
}
} \ No newline at end of file
diff --git a/plugins/SegmentEditor/templates/_unprocessedSegmentMessage.twig b/plugins/SegmentEditor/templates/_unprocessedSegmentMessage.twig
new file mode 100644
index 0000000000..4c8891dfde
--- /dev/null
+++ b/plugins/SegmentEditor/templates/_unprocessedSegmentMessage.twig
@@ -0,0 +1,18 @@
+{% set visitorLogLinkHtml %}<a href="{{ visitorLogLink }}" target="_blank">{% endset %}
+{% if isSegmentToPreprocess %}
+<p>
+ {{ 'SegmentEditor_UnprocessedSegmentNoData1'|translate('<strong>(' ~ segmentName ~ ')</strong>')|raw }}
+ {{ 'SegmentEditor_UnprocessedSegmentNoData2'|translate }}
+</p>
+{% else %}
+<p>
+ {{ 'SegmentEditor_CustomUnprocessedSegmentApiError1'|translate }}
+ {{ 'SegmentEditor_CustomUnprocessedSegmentNoData'|translate }}
+</p>
+{% endif %}
+<p>&nbsp;</p>
+<p>
+ {{ 'SegmentEditor_UnprocessedSegmentInVisitorLog1'|translate(visitorLogLinkHtml, '</a>')|raw }}
+ {{ 'SegmentEditor_UnprocessedSegmentInVisitorLog2'|translate }}
+ {{ 'SegmentEditor_UnprocessedSegmentInVisitorLog3'|translate }}
+</p> \ No newline at end of file
diff --git a/plugins/SegmentEditor/tests/System/UnprocessedSegmentsTest.php b/plugins/SegmentEditor/tests/System/UnprocessedSegmentsTest.php
new file mode 100644
index 0000000000..e4d3d45c17
--- /dev/null
+++ b/plugins/SegmentEditor/tests/System/UnprocessedSegmentsTest.php
@@ -0,0 +1,226 @@
+<?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\Plugins\SegmentEditor\tests\System;
+
+use Piwik\ArchiveProcessor\Rules;
+use Piwik\Common;
+use Piwik\Config;
+use Piwik\Date;
+use Piwik\Db;
+use Piwik\Plugins\SegmentEditor\API;
+use Piwik\Plugins\VisitsSummary;
+use Piwik\Tests\Fixtures\OneVisitorTwoVisits;
+use Piwik\Tests\Framework\TestCase\IntegrationTestCase;
+
+/**
+ * @group SegmentEditor
+ * @group System
+ * @group UnprocessedSegmentsTest
+ */
+class UnprocessedSegmentsTest extends IntegrationTestCase
+{
+ /**
+ * @var OneVisitorTwoVisits
+ */
+ public static $fixture;
+
+ const TEST_SEGMENT = 'browserCode==ff';
+
+ public function test_apiOutput_whenCustomSegmentUsed_WithBrowserArchivingDisabled()
+ {
+ Rules::setBrowserTriggerArchiving(false);
+
+ $segments = Rules::getSegmentsToProcess([self::$fixture->idSite]);
+ $this->assertNotContains(self::TEST_SEGMENT, $segments);
+
+ $this->runAnyApiTest('VisitsSummary.get', 'customSegmentUnprocessed', [
+ 'idSite' => self::$fixture->idSite,
+ 'date' => Date::factory(self::$fixture->dateTime)->toString(),
+ 'period' => 'week',
+ 'segment' => self::TEST_SEGMENT,
+ ]);
+ }
+
+ public function test_apiOutput_whenRealTimeProcessedSegmentUsed_WithBrowserArchivingDisabled()
+ {
+ $idSegment = API::getInstance()->add('testsegment', self::TEST_SEGMENT, self::$fixture->idSite, $autoArchive = false);
+
+ $storedSegment = API::getInstance()->get($idSegment);
+ $this->assertNotEmpty($storedSegment);
+
+ Rules::setBrowserTriggerArchiving(false);
+
+ $segments = Rules::getSegmentsToProcess([self::$fixture->idSite]);
+ $this->assertNotContains(self::TEST_SEGMENT, $segments);
+
+ $this->runAnyApiTest('VisitsSummary.get', 'realTimeSegmentUnprocessed', [
+ 'idSite' => self::$fixture->idSite,
+ 'date' => Date::factory(self::$fixture->dateTime)->toString(),
+ 'period' => 'week',
+ 'segment' => self::TEST_SEGMENT,
+ ]);
+ }
+
+ public function test_apiOutput_whenUnprocessedAutoArchiveSegmentUsed_WithBrowserArchivingDisabled()
+ {
+ $idSegment = API::getInstance()->add('testsegment', self::TEST_SEGMENT, self::$fixture->idSite, $autoArchive = true);
+
+ $storedSegment = API::getInstance()->get($idSegment);
+ $this->assertNotEmpty($storedSegment);
+
+ Rules::setBrowserTriggerArchiving(false);
+
+ $segments = Rules::getSegmentsToProcess([self::$fixture->idSite]);
+ $this->assertContains(self::TEST_SEGMENT, $segments);
+
+ $this->runAnyApiTest('VisitsSummary.get', 'autoArchiveSegmentUnprocessed', [
+ 'idSite' => self::$fixture->idSite,
+ 'date' => Date::factory(self::$fixture->dateTime)->toString(),
+ 'period' => 'week',
+ 'segment' => self::TEST_SEGMENT,
+ ]);
+ }
+
+ public function test_apiOutput_whenPreprocessedSegmentUsed_WithBrowserArchivingDisabled()
+ {
+ $idSegment = API::getInstance()->add('testsegment', self::TEST_SEGMENT, self::$fixture->idSite, $autoArchive = true);
+
+ $storedSegment = API::getInstance()->get($idSegment);
+ $this->assertNotEmpty($storedSegment);
+
+ VisitsSummary\API::getInstance()->get(self::$fixture->idSite, 'week',
+ Date::factory(self::$fixture->dateTime)->toString(), self::TEST_SEGMENT); // archive
+
+ Rules::setBrowserTriggerArchiving(false);
+
+ $segments = Rules::getSegmentsToProcess([self::$fixture->idSite]);
+ $this->assertContains(self::TEST_SEGMENT, $segments);
+
+ $this->runAnyApiTest('VisitsSummary.get', 'autoArchiveSegmentPreprocessed', [
+ 'idSite' => self::$fixture->idSite,
+ 'date' => Date::factory(self::$fixture->dateTime)->toString(),
+ 'period' => 'week',
+ 'segment' => self::TEST_SEGMENT,
+ ]);
+ }
+
+ public function test_apiOutput_whenPreprocessedCustomSegmentUsed_WithBrowserArchivingDisabled()
+ {
+ VisitsSummary\API::getInstance()->get(self::$fixture->idSite, 'week',
+ Date::factory(self::$fixture->dateTime)->toString(), self::TEST_SEGMENT); // archive
+
+ Rules::setBrowserTriggerArchiving(false);
+
+ $segments = Rules::getSegmentsToProcess([self::$fixture->idSite]);
+ $this->assertNotContains(self::TEST_SEGMENT, $segments);
+
+ $this->runAnyApiTest('VisitsSummary.get', 'customSegmentPreprocessed', [
+ 'idSite' => self::$fixture->idSite,
+ 'date' => Date::factory(self::$fixture->dateTime)->toString(),
+ 'period' => 'week',
+ 'segment' => self::TEST_SEGMENT,
+ ]);
+ }
+
+ public function test_apiOutput_whenPreprocessedSegmentUsed_WithNoData_AndBrowserArchivingDisabled()
+ {
+ $this->clearLogData();
+
+ $idSegment = API::getInstance()->add('testsegment', self::TEST_SEGMENT, self::$fixture->idSite, $autoArchive = true);
+
+ $storedSegment = API::getInstance()->get($idSegment);
+ $this->assertNotEmpty($storedSegment);
+
+ VisitsSummary\API::getInstance()->get(self::$fixture->idSite, 'week',
+ Date::factory(self::$fixture->dateTime)->toString(), self::TEST_SEGMENT); // archive
+
+ Rules::setBrowserTriggerArchiving(false);
+
+ $segments = Rules::getSegmentsToProcess([self::$fixture->idSite]);
+ $this->assertContains(self::TEST_SEGMENT, $segments);
+
+ $this->runAnyApiTest('VisitsSummary.get', 'autoArchiveSegmentNoDataPreprocessed', [
+ 'idSite' => self::$fixture->idSite,
+ 'date' => Date::factory(self::$fixture->dateTime)->toString(),
+ 'period' => 'week',
+ 'segment' => self::TEST_SEGMENT,
+ ]);
+ }
+
+ public function test_apiOutput_whenNoLogDataAndUnprocessedSegmentUsed_AndBrowserArchivingDisabled()
+ {
+ $this->clearLogData();
+
+ $idSegment = API::getInstance()->add('testsegment', self::TEST_SEGMENT, self::$fixture->idSite, $autoArchive = true);
+
+ $storedSegment = API::getInstance()->get($idSegment);
+ $this->assertNotEmpty($storedSegment);
+
+ Rules::setBrowserTriggerArchiving(false);
+
+ $segments = Rules::getSegmentsToProcess([self::$fixture->idSite]);
+ $this->assertContains(self::TEST_SEGMENT, $segments);
+
+ $this->runAnyApiTest('VisitsSummary.get', 'noLogDataSegmentUnprocessed', [
+ 'idSite' => self::$fixture->idSite,
+ 'date' => Date::factory(self::$fixture->dateTime)->toString(),
+ 'period' => 'week',
+ 'segment' => self::TEST_SEGMENT,
+ ]);
+ }
+
+ public function test_apiOutput_whenMultipleSitesRequested_OneWithDataOneNot_AndBrowserArchivingDisabled()
+ {
+ $idSegment = API::getInstance()->add('testsegment', self::TEST_SEGMENT, $idSite = false, $autoArchive = true);
+
+ $storedSegment = API::getInstance()->get($idSegment);
+ $this->assertNotEmpty($storedSegment);
+
+ Rules::setBrowserTriggerArchiving(false);
+
+ $segments = Rules::getSegmentsToProcess([self::$fixture->idSite]);
+ $this->assertContains(self::TEST_SEGMENT, $segments);
+
+ $this->runAnyApiTest('VisitsSummary.get', 'noLogDataSegmentUnprocessedMultiSite', [
+ 'idSite' => 'all',
+ 'date' => Date::factory(self::$fixture->dateTime)->toString(),
+ 'period' => 'week',
+ 'segment' => self::TEST_SEGMENT,
+ ]);
+ }
+
+ public static function getOutputPrefix()
+ {
+ return '';
+ }
+
+ public static function getPathToTestDirectory()
+ {
+ return dirname(__FILE__);
+ }
+
+ public function provideContainerConfig()
+ {
+ return [
+ Config::class => \DI\decorate(function (Config $previous) {
+ $previous->General['browser_archiving_disabled_enforce'] = 1;
+ return $previous;
+ }),
+ ];
+ }
+
+ private function clearLogData()
+ {
+ Db::query('TRUNCATE ' . Common::prefixTable('log_visit'));
+ Db::query('TRUNCATE ' . Common::prefixTable('log_link_visit_action'));
+ Db::query('TRUNCATE ' . Common::prefixTable('log_conversion'));
+ }
+}
+
+UnprocessedSegmentsTest::$fixture = new OneVisitorTwoVisits();
diff --git a/plugins/SegmentEditor/tests/System/expected/test___VisitsSummary.get_autoArchiveSegmentNoDataPreprocessed.xml b/plugins/SegmentEditor/tests/System/expected/test___VisitsSummary.get_autoArchiveSegmentNoDataPreprocessed.xml
new file mode 100644
index 0000000000..32b66284be
--- /dev/null
+++ b/plugins/SegmentEditor/tests/System/expected/test___VisitsSummary.get_autoArchiveSegmentNoDataPreprocessed.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <nb_uniq_visitors>0</nb_uniq_visitors>
+ <nb_users>0</nb_users>
+ <nb_visits>0</nb_visits>
+ <nb_actions>0</nb_actions>
+ <nb_visits_converted>0</nb_visits_converted>
+ <bounce_count>0</bounce_count>
+ <sum_visit_length>0</sum_visit_length>
+ <max_actions>0</max_actions>
+ <bounce_rate>0%</bounce_rate>
+ <nb_actions_per_visit>0</nb_actions_per_visit>
+ <avg_time_on_site>0</avg_time_on_site>
+</result> \ No newline at end of file
diff --git a/plugins/SegmentEditor/tests/System/expected/test___VisitsSummary.get_autoArchiveSegmentPreprocessed.xml b/plugins/SegmentEditor/tests/System/expected/test___VisitsSummary.get_autoArchiveSegmentPreprocessed.xml
new file mode 100644
index 0000000000..c8a2c198e8
--- /dev/null
+++ b/plugins/SegmentEditor/tests/System/expected/test___VisitsSummary.get_autoArchiveSegmentPreprocessed.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <nb_uniq_visitors>1</nb_uniq_visitors>
+ <nb_users>0</nb_users>
+ <nb_visits>2</nb_visits>
+ <nb_actions>8</nb_actions>
+ <nb_visits_converted>2</nb_visits_converted>
+ <bounce_count>1</bounce_count>
+ <sum_visit_length>1621</sum_visit_length>
+ <max_actions>7</max_actions>
+ <bounce_rate>50%</bounce_rate>
+ <nb_actions_per_visit>4</nb_actions_per_visit>
+ <avg_time_on_site>811</avg_time_on_site>
+</result> \ No newline at end of file
diff --git a/plugins/SegmentEditor/tests/System/expected/test___VisitsSummary.get_autoArchiveSegmentUnprocessed.xml b/plugins/SegmentEditor/tests/System/expected/test___VisitsSummary.get_autoArchiveSegmentUnprocessed.xml
new file mode 100644
index 0000000000..57b75e01db
--- /dev/null
+++ b/plugins/SegmentEditor/tests/System/expected/test___VisitsSummary.get_autoArchiveSegmentUnprocessed.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <error message="These reports have no data, because the Segment you requested (testsegment) has not yet been processed by the system. Data for this Segment should become available in a few hours when processing completes. (If it does not, there may be a problem.) Please note that you can test whether your segment will work without having to wait for it to be processed by using the Live.getLastVisitsDetails API. When using this API method, you will see which users and actions were matched by your &amp;segment= parameter. This can help you confirm your Segment matches the users and actions you expect it to." />
+</result> \ No newline at end of file
diff --git a/plugins/SegmentEditor/tests/System/expected/test___VisitsSummary.get_customSegmentPreprocessed.xml b/plugins/SegmentEditor/tests/System/expected/test___VisitsSummary.get_customSegmentPreprocessed.xml
new file mode 100644
index 0000000000..c8a2c198e8
--- /dev/null
+++ b/plugins/SegmentEditor/tests/System/expected/test___VisitsSummary.get_customSegmentPreprocessed.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <nb_uniq_visitors>1</nb_uniq_visitors>
+ <nb_users>0</nb_users>
+ <nb_visits>2</nb_visits>
+ <nb_actions>8</nb_actions>
+ <nb_visits_converted>2</nb_visits_converted>
+ <bounce_count>1</bounce_count>
+ <sum_visit_length>1621</sum_visit_length>
+ <max_actions>7</max_actions>
+ <bounce_rate>50%</bounce_rate>
+ <nb_actions_per_visit>4</nb_actions_per_visit>
+ <avg_time_on_site>811</avg_time_on_site>
+</result> \ No newline at end of file
diff --git a/plugins/SegmentEditor/tests/System/expected/test___VisitsSummary.get_customSegmentUnprocessed.xml b/plugins/SegmentEditor/tests/System/expected/test___VisitsSummary.get_customSegmentUnprocessed.xml
new file mode 100644
index 0000000000..ed4fec1ebd
--- /dev/null
+++ b/plugins/SegmentEditor/tests/System/expected/test___VisitsSummary.get_customSegmentUnprocessed.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <error message="The Segment you requested has not yet been created in the Segment Editor and so the report data has not been pre-processed. To see data for this segment, you must go to Matomo and create this segment manually in the Segment Editor. (Alternatively, you can create a new segment programatically using the SegmentEditor.add API method). Once created the segment in the editor (or via API), this error message will disappear and within a few hours you will see your segmented report data, after the segment data has been pre-processed. (If it does not, there may be a problem.) Please note that you can test whether your segment will work without having to wait for it to be processed by using the Live.getLastVisitsDetails API. When using this API method, you will see which users and actions were matched by your &amp;segment= parameter. This can help you confirm your Segment matches the users and actions you expect it to." />
+</result> \ No newline at end of file
diff --git a/plugins/SegmentEditor/tests/System/expected/test___VisitsSummary.get_noLogDataSegmentUnprocessed.xml b/plugins/SegmentEditor/tests/System/expected/test___VisitsSummary.get_noLogDataSegmentUnprocessed.xml
new file mode 100644
index 0000000000..32b66284be
--- /dev/null
+++ b/plugins/SegmentEditor/tests/System/expected/test___VisitsSummary.get_noLogDataSegmentUnprocessed.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <nb_uniq_visitors>0</nb_uniq_visitors>
+ <nb_users>0</nb_users>
+ <nb_visits>0</nb_visits>
+ <nb_actions>0</nb_actions>
+ <nb_visits_converted>0</nb_visits_converted>
+ <bounce_count>0</bounce_count>
+ <sum_visit_length>0</sum_visit_length>
+ <max_actions>0</max_actions>
+ <bounce_rate>0%</bounce_rate>
+ <nb_actions_per_visit>0</nb_actions_per_visit>
+ <avg_time_on_site>0</avg_time_on_site>
+</result> \ No newline at end of file
diff --git a/plugins/SegmentEditor/tests/System/expected/test___VisitsSummary.get_noLogDataSegmentUnprocessedMultiSite.xml b/plugins/SegmentEditor/tests/System/expected/test___VisitsSummary.get_noLogDataSegmentUnprocessedMultiSite.xml
new file mode 100644
index 0000000000..dd52dc4ff4
--- /dev/null
+++ b/plugins/SegmentEditor/tests/System/expected/test___VisitsSummary.get_noLogDataSegmentUnprocessedMultiSite.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<results>
+ <result idSite="1" />
+ <result idSite="2" />
+ <result idSite="3" />
+</results> \ No newline at end of file
diff --git a/plugins/SegmentEditor/tests/System/expected/test___VisitsSummary.get_realTimeSegmentUnprocessed.xml b/plugins/SegmentEditor/tests/System/expected/test___VisitsSummary.get_realTimeSegmentUnprocessed.xml
new file mode 100644
index 0000000000..0dcab5ab6d
--- /dev/null
+++ b/plugins/SegmentEditor/tests/System/expected/test___VisitsSummary.get_realTimeSegmentUnprocessed.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <error message="The Segment 'testsegment' is set to 'segmented reports are processed in real time' but Matomo is not currently configured to process segmented reports in API requests. To see data for this report in the future, you will need to edit your segment and choose the option labeled 'segmented reports are pre-processed (faster, requires cron)'. Then after a few hours your segment data should become available through the API. (If it does not, there may be a problem.)" />
+</result> \ No newline at end of file
diff --git a/plugins/SegmentEditor/tests/UI/UnprocessedSegment_spec.js b/plugins/SegmentEditor/tests/UI/UnprocessedSegment_spec.js
new file mode 100644
index 0000000000..5278471cba
--- /dev/null
+++ b/plugins/SegmentEditor/tests/UI/UnprocessedSegment_spec.js
@@ -0,0 +1,55 @@
+/*!
+ * Matomo - free/libre analytics platform
+ *
+ * SegmentEditor screenshot tests.
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+describe("UnprocessedSegmentTest", function () {
+ this.fixture = 'Piwik\\Tests\\Fixtures\\OneVisitorTwoVisits';
+ this.timeout(0);
+
+ var generalParams = 'idSite=1&period=day&date=2010-03-06';
+ var segment = 'browserCode==ff';
+ var customSegment = 'languageCode==fr';
+ var url = '?module=CoreHome&action=index&' + generalParams + '#?' + generalParams + '&category=General_Visitors&subcategory=General_Overview';
+
+ before(function (done) {
+ testEnvironment.callApi('SegmentEditor.add', {
+ name: '<script>alert("testsegment");</script>',
+ definition: segment,
+ idSite: 1,
+ autoArchive: 1,
+ enableAllUsers: 1,
+ }, done);
+ });
+
+ before(function () {
+ testEnvironment.configOverride.General = {
+ browser_archiving_disabled_enforce: '1',
+ enable_browser_archiving_triggering: '0',
+ };
+ testEnvironment.optionsOverride = {
+ enableBrowserTriggerArchiving: '0',
+ };
+ testEnvironment.save();
+ });
+
+ after(function (done) {
+ testEnvironment.callApi('SegmentEditor.delete', { idSegment: 1 }, done);
+ });
+
+ it("should show a notification for unprocessed segments", function (done) {
+ expect.screenshot("unprocessed_segment").to.be.captureSelector('.pageWrap', function (page) {
+ page.load(url + '&segment=' + encodeURIComponent(segment));
+ }, done);
+ });
+
+ it('should not show a notification for custom segments that are not preprocessed', function (done) {
+ expect.screenshot("custom_segment").to.be.captureSelector('.pageWrap', function (page) {
+ page.load(url + '&segment=' + encodeURIComponent(customSegment));
+ }, done);
+ });
+});
diff --git a/plugins/SegmentEditor/tests/UI/expected-screenshots/UnprocessedSegmentTest_custom_segment.png b/plugins/SegmentEditor/tests/UI/expected-screenshots/UnprocessedSegmentTest_custom_segment.png
new file mode 100644
index 0000000000..542460805d
--- /dev/null
+++ b/plugins/SegmentEditor/tests/UI/expected-screenshots/UnprocessedSegmentTest_custom_segment.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:6f90e885887d8623ebfcc63ecb641aa1b4cee70c718194fb22d183dc275c0ed5
+size 60004
diff --git a/plugins/SegmentEditor/tests/UI/expected-screenshots/UnprocessedSegmentTest_unprocessed_segment.png b/plugins/SegmentEditor/tests/UI/expected-screenshots/UnprocessedSegmentTest_unprocessed_segment.png
new file mode 100644
index 0000000000..35c4aa46b7
--- /dev/null
+++ b/plugins/SegmentEditor/tests/UI/expected-screenshots/UnprocessedSegmentTest_unprocessed_segment.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:568747c15966b58fa0242c94e91bf0d8e33d4f19609d2b7053cc3487a1d6e8a8
+size 104572
diff --git a/plugins/UsersManager/tests/Integration/UsersManagerTest.php b/plugins/UsersManager/tests/Integration/UsersManagerTest.php
index af70b58a92..8347fe2811 100644
--- a/plugins/UsersManager/tests/Integration/UsersManagerTest.php
+++ b/plugins/UsersManager/tests/Integration/UsersManagerTest.php
@@ -897,6 +897,7 @@ class UsersManagerTest extends IntegrationTestCase
*/
public function testGetUsersAccessFromSiteWrongIdSite()
{
+ FakeAccess::$superUser = false;
$this->api->getUsersAccessFromSite(1);
}
diff --git a/tests/PHPUnit/Framework/Mock/FakeAccess.php b/tests/PHPUnit/Framework/Mock/FakeAccess.php
index be86704305..4f18cd717b 100644
--- a/tests/PHPUnit/Framework/Mock/FakeAccess.php
+++ b/tests/PHPUnit/Framework/Mock/FakeAccess.php
@@ -100,7 +100,7 @@ class FakeAccess extends Access
if (!self::$superUser) {
$websitesAccess = self::$idSitesAdmin;
} else {
- $websitesAccess = API::getInstance()->getAllSitesId();
+ return;
}
$idSites = PiwikSite::getIdSitesFromIdSitesString($idSites);
@@ -117,7 +117,7 @@ class FakeAccess extends Access
if (!self::$superUser) {
$websitesAccess = array_merge(self::$idSitesWrite, self::$idSitesAdmin);
} else {
- $websitesAccess = API::getInstance()->getAllSitesId();
+ return;
}
$idSites = PiwikSite::getIdSitesFromIdSitesString($idSites);
diff --git a/tests/lib/screenshot-testing/support/test-environment.js b/tests/lib/screenshot-testing/support/test-environment.js
index ffe43cb431..a812f5d83e 100644
--- a/tests/lib/screenshot-testing/support/test-environment.js
+++ b/tests/lib/screenshot-testing/support/test-environment.js
@@ -26,6 +26,7 @@ TestingEnvironment.prototype.reload = function () {
this['loadRealTranslations'] = true; // UI tests should test w/ real translations, not translation keys
this['testUseMockAuth'] = true;
this['configOverride'] = {};
+ this['optionsOverride'] = {};
if (fs.exists(testingEnvironmentOverridePath)) {
var data = JSON.parse(fs.read(testingEnvironmentOverridePath));