diff options
author | diosmosis <diosmosis@users.noreply.github.com> | 2018-08-09 14:24:16 +0300 |
---|---|---|
committer | Matthieu Aubry <mattab@users.noreply.github.com> | 2018-08-09 14:24:16 +0300 |
commit | 2b77bb4d609dd0ec377a9edc32536f22dd4dabb2 (patch) | |
tree | 14e38fd0b07b26dbcf50dd2e25793b556d45baa7 /plugins | |
parent | 6112cc2ddf955b9c7183ba767e0a8acce93111ef (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.
Diffstat (limited to 'plugins')
26 files changed, 712 insertions, 40 deletions
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> </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 &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 &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); } |