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>2019-05-15 02:43:15 +0300
committerGitHub <noreply@github.com>2019-05-15 02:43:15 +0300
commit0af1f22f728b2bb2232f84ace5f4b5f0c3c60c57 (patch)
tree0eb34d450b850c1fe4253261d5e6c7de9edd3b33 /plugins
parent4b81b3b8eea9d0e361a5219164b08b850a30392d (diff)
Indent actions belonging to a pageview (#14063)
* Proof of concept for grouping actions by the page they occur in. * Add pageview to goals/ecommerce actionDetails in Live.getLastVisitsDetails. * Make count of actions to display when collapsed configurable. * Quick selector fix. * unfinished commit * Collapse multiple adjacent content items in the visitor log. * Get content collapsing to work w/ 3.x-dev changes. * Forgot to add Live config file. * Get to work w/ visitor profile and make sure last action does not have border so it looks like it correctly ends. * Fix some issues from review. * More styling tweaks. * another styling tweak * Update screenshots. * Show page refreshes and allow expanding them in new implementation. * Update some screenshots. * Make sure tooltip is replaced correctly when showing refreshes. * Another styling tweak. * Add UI test + fix page refresh issue. * Fix action group merging logic. * Fix another actions grouping issue. * Fixes for ending left border in certain edge cases. * Another UI tweak. * comparison threshold, random failure fix, update screenshots + another css tweak * more css tweaks * possible bug fix * Last couple CSS fixes. * More test fixes.
Diffstat (limited to 'plugins')
-rw-r--r--plugins/Actions/tests/UI/expected-screenshots/ActionsDataTable_segmented_visitor_log.png4
-rw-r--r--plugins/Contents/templates/_actionContent.twig6
-rw-r--r--plugins/Ecommerce/VisitorDetails.php13
-rw-r--r--plugins/Goals/VisitorDetails.php5
-rw-r--r--plugins/Live/Controller.php5
-rw-r--r--plugins/Live/Live.php11
-rw-r--r--plugins/Live/Visualizations/VisitorLog.php96
-rw-r--r--plugins/Live/config/config.php11
-rw-r--r--plugins/Live/javascripts/visitorActions.js174
-rw-r--r--plugins/Live/lang/en.json3
-rw-r--r--plugins/Live/stylesheets/live.less109
-rw-r--r--plugins/Live/stylesheets/visitor_profile.less6
-rw-r--r--plugins/Live/templates/_actionEcommerce.twig4
-rw-r--r--plugins/Live/templates/_actionsList.twig37
-rw-r--r--plugins/Live/templates/_dataTableViz_visitorLog.twig4
-rw-r--r--plugins/Live/templates/getVisitList.twig3
-rw-r--r--plugins/Live/tests/Fixtures/VisitsWithAllActionsAndDevices.php3
-rw-r--r--plugins/Live/tests/UI/Live_spec.js31
-rw-r--r--plugins/Live/tests/UI/expected-screenshots/Live_visitor_log.png4
-rw-r--r--plugins/Live/tests/UI/expected-screenshots/Live_visitor_log_expand_actions.png4
-rw-r--r--plugins/Live/tests/UI/expected-screenshots/Live_visitor_log_expand_content_actions.png3
-rw-r--r--plugins/Live/tests/UI/expected-screenshots/Live_visitor_log_expand_pageview_actions.png3
-rw-r--r--plugins/Live/tests/UI/expected-screenshots/Live_visitor_profile.png4
-rw-r--r--plugins/Live/tests/UI/expected-screenshots/Live_visitor_profile_action_details.png4
-rw-r--r--plugins/Live/tests/UI/expected-screenshots/Live_visitor_profile_actions_hidden.png4
-rw-r--r--plugins/Live/tests/UI/expected-screenshots/Live_visitor_profile_limited.png4
-rw-r--r--plugins/Live/tests/UI/expected-screenshots/Live_visitor_profile_visit_details.png4
m---------plugins/LoginLdap0
-rw-r--r--plugins/PrivacyManager/tests/UI/expected-screenshots/PrivacyManager_gdpr_tools_visits_showprofile.png4
-rw-r--r--plugins/Referrers/tests/UI/CampaignBuilder_spec.js1
-rw-r--r--plugins/UsersManager/tests/UI/UsersManager_spec.js4
-rw-r--r--plugins/UsersManager/tests/UI/expected-screenshots/UsersManager_delete_single.png4
32 files changed, 488 insertions, 84 deletions
diff --git a/plugins/Actions/tests/UI/expected-screenshots/ActionsDataTable_segmented_visitor_log.png b/plugins/Actions/tests/UI/expected-screenshots/ActionsDataTable_segmented_visitor_log.png
index dd3d8011c3..0a9cfc6f93 100644
--- a/plugins/Actions/tests/UI/expected-screenshots/ActionsDataTable_segmented_visitor_log.png
+++ b/plugins/Actions/tests/UI/expected-screenshots/ActionsDataTable_segmented_visitor_log.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:5db8ec5cc759d0aa6903f7b83c5e3e547e4fa8adfc0fc42cc84f119fba35c489
-size 401604
+oid sha256:b6b328f08cccee3ed58d392d3261f09d6f514f0f01e1e2190985a784b0a7e608
+size 382907
diff --git a/plugins/Contents/templates/_actionContent.twig b/plugins/Contents/templates/_actionContent.twig
index 801974064b..0eb5f86d81 100644
--- a/plugins/Contents/templates/_actionContent.twig
+++ b/plugins/Contents/templates/_actionContent.twig
@@ -9,9 +9,9 @@
class="action-list-action-icon content-impression">
{% endif %}
{% if action.contentInteraction %}
- [{{ action.contentInteraction }}]
+ <span class="content-interaction">[{{ action.contentInteraction }}]</span>
{% endif %}
- {{ action.contentName }} -
- {{ action.contentPiece }}
+ <span class="content-name">{{ action.contentName }}</span> -
+ <span class="content-piece">{{ action.contentPiece }}</span>
</div>
</li>
diff --git a/plugins/Ecommerce/VisitorDetails.php b/plugins/Ecommerce/VisitorDetails.php
index 3dfd7eb3e6..0819160939 100644
--- a/plugins/Ecommerce/VisitorDetails.php
+++ b/plugins/Ecommerce/VisitorDetails.php
@@ -136,8 +136,8 @@ class VisitorDetails extends VisitorDetailsAbstract
*/
protected function queryEcommerceConversionsForVisits($idVisits)
{
- $sql = "SELECT
- idvisit,
+ $sql = "SELECT
+ log_conversion.idvisit,
case idgoal when " . GoalManager::IDGOAL_CART
. " then '" . Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_CART
. "' else '" . Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER . "' end as type,
@@ -149,11 +149,14 @@ class VisitorDetails extends VisitorDetailsAbstract
" . LogAggregator::getSqlRevenue('revenue_discount') . " as revenueDiscount,
items as items,
log_conversion.server_time as serverTimePretty,
- log_conversion.idlink_va
+ log_conversion.idlink_va,
+ log_link_visit_action.idpageview
FROM " . Common::prefixTable('log_conversion') . " AS log_conversion
- WHERE idvisit IN ('" . implode("','", $idVisits) . "')
+ LEFT JOIN " . Common::prefixTable('log_link_visit_action') . " AS log_link_visit_action
+ ON log_link_visit_action.idlink_va = log_conversion.idlink_va
+ WHERE log_conversion.idvisit IN ('" . implode("','", $idVisits) . "')
AND idgoal <= " . GoalManager::IDGOAL_ORDER . "
- ORDER BY idvisit, server_time ASC";
+ ORDER BY log_conversion.idvisit, log_conversion.server_time ASC";
$ecommerceDetails = Db::fetchAll($sql);
return $ecommerceDetails;
}
diff --git a/plugins/Goals/VisitorDetails.php b/plugins/Goals/VisitorDetails.php
index 2f94476093..7c52c0b899 100644
--- a/plugins/Goals/VisitorDetails.php
+++ b/plugins/Goals/VisitorDetails.php
@@ -70,12 +70,15 @@ class VisitorDetails extends VisitorDetailsAbstract
'goal' as type,
goal.name as goalName,
goal.idgoal as goalId,
+ log_link_visit_action.idpageview,
log_conversion.revenue as revenue,
log_conversion.idlink_va,
log_conversion.idlink_va as goalPageId,
log_conversion.server_time as serverTimePretty,
log_conversion.url as url
FROM " . Common::prefixTable('log_conversion') . " AS log_conversion
+ LEFT JOIN " . Common::prefixTable('log_link_visit_action') . " AS log_link_visit_action
+ ON log_link_visit_action.idlink_va = log_conversion.idlink_va
LEFT JOIN " . Common::prefixTable('goal') . " AS goal
ON (goal.idsite = log_conversion.idsite
AND
@@ -83,7 +86,7 @@ class VisitorDetails extends VisitorDetailsAbstract
AND goal.deleted = 0
WHERE log_conversion.idvisit IN ('" . implode("','", $idVisits) . "')
AND log_conversion.idgoal > 0
- ORDER BY log_conversion.idvisit, server_time ASC
+ ORDER BY log_conversion.idvisit, log_conversion.server_time ASC
";
return Db::fetchAll($sql);
}
diff --git a/plugins/Live/Controller.php b/plugins/Live/Controller.php
index e8556fe215..0ae82af98b 100644
--- a/plugins/Live/Controller.php
+++ b/plugins/Live/Controller.php
@@ -14,6 +14,7 @@ use Piwik\Config;
use Piwik\Container\StaticContainer;
use Piwik\Piwik;
use Piwik\Plugins\Goals\API as APIGoals;
+use Piwik\Plugins\Live\Visualizations\VisitorLog;
use Piwik\Url;
use Piwik\View;
@@ -129,7 +130,9 @@ class Controller extends \Piwik\Plugin\Controller
if (empty($visitorData)) {
throw new \Exception('Visitor could not be found'); // for example when URL parameter is not set
}
-
+
+ VisitorLog::groupActionsByPageviewId($visitorData['lastVisits']);
+
$view = new View('@Live/getVisitorProfilePopup.twig');
$view->idSite = $this->idSite;
$view->goals = Request::processRequest('Goals.getGoals', ['idSite' => $this->idSite, 'filter_limit' => '-1'], $default = []);
diff --git a/plugins/Live/Live.php b/plugins/Live/Live.php
index 83103630a6..29be212907 100644
--- a/plugins/Live/Live.php
+++ b/plugins/Live/Live.php
@@ -12,6 +12,7 @@ use Piwik\Cache;
use Piwik\CacheId;
use Piwik\API\Request;
use Piwik\Common;
+use Piwik\Container\StaticContainer;
/**
*
@@ -32,9 +33,18 @@ class Live extends \Piwik\Plugin
'Live.renderActionTooltip' => 'renderActionTooltip',
'Live.renderVisitorDetails' => 'renderVisitorDetails',
'Live.renderVisitorIcons' => 'renderVisitorIcons',
+ 'Template.jsGlobalVariables' => 'addJsGlobalVariables',
);
}
+ public function addJsGlobalVariables(&$out)
+ {
+ $actionsToDisplayCollapsed = (int)StaticContainer::get('Live.pageViewActionsToDisplayCollapsed');
+ $out .= "
+ piwik.visitorLogActionsToDisplayCollapsed = $actionsToDisplayCollapsed;
+ ";
+ }
+
public function getStylesheetFiles(&$stylesheets)
{
$stylesheets[] = "plugins/Live/stylesheets/live.less";
@@ -67,6 +77,7 @@ class Live extends \Piwik\Plugin
$translationKeys[] = "Live_SegmentedVisitorLogTitle";
$translationKeys[] = "General_Segment";
$translationKeys[] = "General_And";
+ $translationKeys[] = 'Live_ClickToSeeAllContents';
}
public function renderAction(&$renderedAction, $action, $previousAction, $visitorDetails)
diff --git a/plugins/Live/Visualizations/VisitorLog.php b/plugins/Live/Visualizations/VisitorLog.php
index 2e05eee788..4b8797510e 100644
--- a/plugins/Live/Visualizations/VisitorLog.php
+++ b/plugins/Live/Visualizations/VisitorLog.php
@@ -10,12 +10,15 @@ namespace Piwik\Plugins\Live\Visualizations;
use Piwik\Common;
use Piwik\Config;
+use Piwik\Container\StaticContainer;
use Piwik\DataTable;
use Piwik\Piwik;
use Piwik\Plugin;
use Piwik\Plugin\ViewDataTable;
use Piwik\Plugin\Visualization;
use Piwik\Plugins\PrivacyManager\PrivacyManager;
+use Piwik\Plugins\TagManager\Model\Container\StaticContainerIdGenerator;
+use Piwik\Tracker\Action;
use Piwik\View;
/**
@@ -65,6 +68,9 @@ class VisitorLog extends Visualization
}
}
};
+ $this->config->filters[] = function (DataTable $table) {
+ $this->groupActionsByPageviewId($table);
+ };
}
public function afterGenericFiltersAreAppliedToLoadedDataTable()
@@ -119,6 +125,8 @@ class VisitorLog extends Visualization
)
);
+ $this->assignTemplateVar('actionsToDisplayCollapsed', StaticContainer::get('Live.pageViewActionsToDisplayCollapsed'));
+
$enableAddNewSegment = Common::getRequestVar('enableAddNewSegment', false);
if ($enableAddNewSegment) {
$this->config->datatable_actions[] = [
@@ -133,4 +141,92 @@ class VisitorLog extends Visualization
{
return ($view->requestConfig->getApiModuleToRequest() === 'Live');
}
+
+ // TODO: need to unit test this
+ public static function groupActionsByPageviewId(DataTable $table)
+ {
+ foreach ($table->getRows() as $row) {
+ $actionGroups = [];
+ foreach ($row->getColumn('actionDetails') as $key => $action) {
+ // if action is not a pageview action
+ if (empty($action['idpageview'])
+ && self::isPageviewAction($action)
+ ) {
+ $actionGroups[] = [
+ 'pageviewAction' => null,
+ 'actionsOnPage' => [$action],
+ 'refreshActions' => [],
+ ];
+ continue;
+ }
+
+ // if there is no idpageview for wahtever reason, invent one
+ $idPageView = !empty($action['idpageview']) ? $action['idpageview'] : count($actionGroups);
+ if (empty($actionGroups[$idPageView])) {
+ $actionGroups[$idPageView] = [
+ 'pageviewAction' => null,
+ 'actionsOnPage' => [],
+ 'refreshActions' => [],
+ ];
+ }
+
+ if ($action['type'] == 'action') {
+ if (empty($actionGroups[$idPageView]['pageviewAction'])) {
+ $actionGroups[$idPageView]['pageviewAction'] = $action;
+ } else if (empty($actionGroups[$idPageView]['pageviewAction']['url'])) {
+ // set this action as the pageview action either if there isn't one set already, or the existing one
+ // has no URL
+ $actionGroups[$idPageView]['refreshActions'][] = $actionGroups[$idPageView]['pageviewAction'];
+ $actionGroups[$idPageView]['pageviewAction'] = $action;
+ } else {
+ $actionGroups[$idPageView]['refreshActions'][] = $actionGroups[$idPageView]['pageviewAction'];
+ }
+ } else {
+ $actionGroups[$idPageView]['actionsOnPage'][] = $action;
+ }
+ }
+
+ // merge action groups that have the same page url/action and no pageviewactions
+ $actionGroups = self::mergeRefreshes($actionGroups);
+
+ $row->setColumn('actionGroups', $actionGroups);
+ }
+ }
+
+ private static function mergeRefreshes(array $actionGroups)
+ {
+ $previousId = null;
+ foreach ($actionGroups as $idPageview => $group) {
+ if (empty($previousId)) {
+ $previousId = $idPageview;
+ continue;
+ }
+
+ $action = $group['pageviewAction'];
+ $lastActionGroup = $actionGroups[$previousId];
+
+ $isLastGroupEmpty = empty($actionGroups[$previousId]['actionsOnPage']);
+ $isPageviewActionSame = $lastActionGroup['pageviewAction']['url'] == $action['url']
+ && $lastActionGroup['pageviewAction']['pageTitle'] == $action['pageTitle'];
+
+ // if the current action has the same url/action name as the last, merge w/ the last action group
+ if ($isLastGroupEmpty
+ && $isPageviewActionSame
+ ) {
+ $actionGroups[$previousId]['refreshActions'][] = $action;
+ $actionGroups[$previousId]['actionsOnPage'] = array_merge($actionGroups[$previousId]['actionsOnPage'], $actionGroups[$idPageview]['actionsOnPage']);
+ unset($actionGroups[$idPageview]);
+ } else {
+ $previousId = $idPageview;
+ }
+ }
+ return $actionGroups;
+ }
+
+ private static function isPageviewAction($action)
+ {
+ return $action['type'] != 'action'
+ && $action['type'] != Action::TYPE_PAGE_URL
+ && $action['type'] != Action::TYPE_PAGE_TITLE;
+ }
}
diff --git a/plugins/Live/config/config.php b/plugins/Live/config/config.php
new file mode 100644
index 0000000000..0fc02cad3a
--- /dev/null
+++ b/plugins/Live/config/config.php
@@ -0,0 +1,11 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link http://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+return [
+ 'Live.pageViewActionsToDisplayCollapsed' => 3,
+];
diff --git a/plugins/Live/javascripts/visitorActions.js b/plugins/Live/javascripts/visitorActions.js
index f462bcfb8e..d85476c86a 100644
--- a/plugins/Live/javascripts/visitorActions.js
+++ b/plugins/Live/javascripts/visitorActions.js
@@ -35,58 +35,160 @@ function initializeVisitorActions(elem) {
close: function() { tooltipIsOpened = false; }
});
- // show refresh icon for duplicate page views in a row
+ // collapse adjacent content interactions
$("ol.visitorLog", elem).each(function () {
- var prevelement;
- var prevhtml;
- var counter = 0, duplicateCounter = 0;
- $(this).find("> li").each(function () {
- counter++;
- $(this).val(counter);
- var current = $(this).html();
-
- if (current == prevhtml) {
- duplicateCounter++;
- $(this).find('>div').prepend($("<span>"+(duplicateCounter+1)+"</span>").attr({'class': 'repeat icon-refresh', 'title': _pk_translate('Live_PageRefreshed')}));
- prevelement.addClass('duplicate');
-
- } else {
- duplicateCounter = 0;
+ var $actions = $(this).find("li");
+ $actions.each(function (index) {
+ var $li = $(this);
+ if (!$li.is('.content')) {
+ return;
+ }
+
+ if (!$actions[index - 1]
+ || !$($actions[index - 1]).is('.content')
+ || !$actions[index - 2]
+ || !$($actions[index - 2]).is('.content')
+ ) {
+ return;
+ }
+
+ var $collapsedContents = $li;
+ while ($collapsedContents.prev().is('.content')) {
+ $collapsedContents = $collapsedContents.prev();
}
- prevhtml = current;
- prevelement = $(this);
+ if (!$collapsedContents.is('.collapsed-contents')) {
+ $collapsedContents = makeCollapsedContents();
+ $collapsedContents.insertBefore($($actions[index - 2]));
+
+ addContentItem($collapsedContents, $($actions[index - 2]));
+ addContentItem($collapsedContents, $($actions[index - 1]));
+ }
+
+ addContentItem($collapsedContents, $li);
+
+ function makeCollapsedContents() {
+ var $li = $('<li/>')
+ .attr('class', 'content collapsed-contents')
+ .attr('title', _pk_translate('Live_ClickToSeeAllContents'));
+
+ $('<div>')
+ .html('<img src="plugins/Morpheus/images/contentimpression.svg" class="action-list-action-icon"/>' +
+ ' <span class="content-impressions">0</span> content impressions <span class="content-interactions">0</span> interactions')
+ .appendTo($li);
- var $this = $(this);
- var tooltipIsOpened = false;
+ return $li;
+ }
- $('a', $this).on('focus', function () {
- // see https://github.com/piwik/piwik/issues/4099
- if (tooltipIsOpened) {
- $this.tooltip('close');
+ function addContentItem($collapsedContents, $otherLi) {
+ if ($otherLi.find('.content-interaction').length) {
+ var $interactions = $collapsedContents.find('.content-interactions');
+ $interactions.text(parseInt($interactions.text()) + 1);
+ } else {
+ var $impressions = $collapsedContents.find('.content-impressions');
+ $impressions.text(parseInt($impressions.text()) + 1);
}
- });
+ $otherLi.addClass('duplicate').addClass('collapsed-content-item').val('').attr('style', '');
+ }
});
});
- $("ol.visitorLog > li:not(.duplicate)", elem).each(function(){
- if (!$('.icon-refresh', $(this)).length) {
+ // show refresh icon for duplicate page views in a row
+ $("li.pageviewActions", elem).each(function () {
+ var $divider = $(this).find('.refresh-divider');
+ $divider.prevUntil().addClass('duplicate');
+ $divider.remove();
+
+ var viewCount = +$(this).attr('data-view-count');
+ if (viewCount <= 1
+ || isNaN(viewCount)
+ ) {
return;
}
- $(this).attr('origtitle', $(this).attr('title'));
- $(this).attr('title', _pk_translate('Live_ClickToViewAllActions'));
- $(this).click(function(e){
+
+ var $pageviewAction = $(this).prev();
+ $pageviewAction.find('>div').prepend($("<span>"+viewCount+"</span>").attr({'class': 'repeat icon-refresh', 'title': _pk_translate('Live_PageRefreshed')}));
+
+ var actionsCount = +$(this).attr('data-actions-on-page');
+ if (actionsCount === 0) {
+ $pageviewAction.addClass('noPageviewActions');
+ }
+
+ $('a', $(this)).on('focus', function () {
+ // see https://github.com/piwik/piwik/issues/4099
+ if (tooltipIsOpened) {
+ $(this).tooltip('close');
+ }
+ });
+
+ var $this = $(this);
+ $pageviewAction.attr('origtitle', $pageviewAction.attr('title'));
+ $pageviewAction.attr('title', _pk_translate('Live_ClickToViewAllActions'));
+ $pageviewAction.click(function (e) {
e.preventDefault();
- $(this).prevUntil('li:not(.duplicate)').removeClass('duplicate').find('.icon-refresh').hide();
- var elem = $(this);
+ e.stopPropagation();
+
+ $pageviewAction.addClass('refreshesExpanded');
+ $this.children('.actionList').children().first().removeClass('duplicate').nextUntil('li:not(.duplicate)').removeClass('duplicate');
+
window.setTimeout(function() {
- elem.attr('title', elem.attr('origtitle'));
- elem.attr('origtitle', null);
+ $pageviewAction.attr('title', $pageviewAction.attr('origtitle'));
+ $pageviewAction.attr('origtitle', null);
}, 150);
- $(this).off('click').find('.icon-refresh').hide();
- return false;
+
+ $pageviewAction.off('click').find('.icon-refresh').hide();
+ $pageviewAction.triggerHandler('mouseleave'); // close tooltip so the title will replace
});
});
+
+ // hide expanders if content collapsing removed enough items
+ $("ol.actionList", elem).each(function () {
+ var actionsToDisplayCollapsed = +piwik.visitorLogActionsToDisplayCollapsed;
+
+ var $items = $(this).find("li:not(.pageviewActions):not(.actionsForPageExpander):not(.duplicate)");
+ var hasMoreItemsThanLimit = $items.length > actionsToDisplayCollapsed;
+
+ $(this).children('.actionsForPageExpander')
+ .toggle(hasMoreItemsThanLimit)
+ .find('.show-actions-count').text($items.length - actionsToDisplayCollapsed);
+
+ // add last-action class to the last action in each list
+ setLastActionClass($(this));
+ });
+
+ // event handler for content expander/collapser
+ elem.on('click', '.collapsed-contents', function () {
+ $(this).nextUntil(':not(.content)').toggleClass('duplicate');
+ setLastActionClass($(this).closest('ol.actionList'));
+ });
+
+ // event handler for action expander/collapser
+ elem.on('click', '.show-less-actions,.show-more-actions', function (e) {
+ e.preventDefault();
+
+ var actionsToDisplayCollapsed = +piwik.visitorLogActionsToDisplayCollapsed;
+
+ var $actions = $(e.target).closest('.actionList').find('li:not(.duplicate):not(.actionsForPageExpander)');
+ $actions.each(function () {
+ if ($actions.index(this) >= actionsToDisplayCollapsed) {
+ $(this).toggle({
+ duration: 250
+ });
+ }
+ });
+
+ $(e.target)
+ .parent().find('.show-less-actions,.show-more-actions').toggle();
+ $(e.target)
+ .closest('li')
+ .toggleClass('expanded collapsed');
+ });
+
+ elem.find('.show-less-actions:visible').click();
+
+ function setLastActionClass($list) {
+ $list.children(':not(.actionsForPageExpander):not(.duplicate)').removeClass('last-action').last().addClass('last-action');
+ }
}
diff --git a/plugins/Live/lang/en.json b/plugins/Live/lang/en.json
index 208051b927..62d85f3a0e 100644
--- a/plugins/Live/lang/en.json
+++ b/plugins/Live/lang/en.json
@@ -50,6 +50,7 @@
"RowActionTooltipTitle": "Open segmented Visit Log",
"SegmentedVisitorLogTitle": "Visit Log showing visits where %1$s is \"%2$s\"",
"OnClickPause": "%s is started. Click to pause.",
- "OnClickStart": "%s is stopped. Click to start."
+ "OnClickStart": "%s is stopped. Click to start.",
+ "ClickToSeeAllContents": "Click to see each content interaction/impression"
}
}
diff --git a/plugins/Live/stylesheets/live.less b/plugins/Live/stylesheets/live.less
index 925356f8b2..f91451dea7 100644
--- a/plugins/Live/stylesheets/live.less
+++ b/plugins/Live/stylesheets/live.less
@@ -129,11 +129,11 @@ ol.visitorLog {
overflow: -moz-hidden-unscrollable;
}
-ol.visitorLog > li {
- margin-bottom: 7px;
+ol.actionList > li:not(.pageviewActions) {
+ margin-bottom: 10px;
line-height: 20px;
position: relative;
- min-height: 25px;
+ min-height: 30px;
&:before {
vertical-align: top;
@@ -155,12 +155,12 @@ ol.visitorLog > li {
border-left: 2px solid #d2d2d2;
position: absolute;
left: -10px;
- height: 100%;
+ height: calc(~"100% - 20px");
margin-top: 20px;
z-index: 1;
}
- &:last-child:after {
+ &:last-of-type:after {
border-left: none;
}
@@ -182,6 +182,96 @@ ol.visitorLog > li {
vertical-align: middle;
}
}
+
+ &.actionsForPageExpander {
+ a {
+ color: @theme-color-text;
+ &:hover {
+ text-decoration:underline;
+ }
+ }
+
+ &:before {
+ margin-left: -5px;
+ visibility: hidden;
+ }
+
+ &.expanded:before {
+ content: "\f102";
+ }
+
+ &.collapsed:before {
+ content: "\f103";
+ }
+
+ &:before {
+ margin-top: 2px;
+ float: left;
+ margin-right: 5px;
+
+ // copied frim [class^=icon-] style
+ font-family: 'matomo' !important;
+ speak: none;
+ font-style: normal;
+ font-weight: normal;
+ font-variant: normal;
+ text-transform: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ }
+ }
+}
+
+ol.actionList > li.pageviewActions {
+ position: relative;
+ margin-top: -6px;
+
+ &:after {
+ content: " ";
+ border-left: 2px solid #d2d2d2;
+ position: absolute;
+ left: -10px;
+ height: calc(~"100% + 8px");
+ z-index: 1;
+ top: 0;
+ margin-top: -8px;
+ }
+
+ &:last-child:after {
+ height: calc(~"100% - 4px");
+ }
+
+ > ol > li:nth-last-child(2):after {
+ border-left: none;
+ }
+}
+
+// when last action group is just refreshes
+ol.actionList > li:nth-last-child(2).noPageviewActions:not(.refreshesExpanded):after {
+ border-left: none;
+}
+
+ol.actionList > li:not(.pageviewActions).last-action {
+ &:after {
+ border-left: none;
+ }
+}
+
+.pageviewActions.last-action > ol.actionList > li.last-action {
+ margin-bottom: 0;
+}
+
+li.collapsed-contents > div {
+ cursor: pointer;
+}
+
+li.collapsed-content-item {
+ margin-left: 1.5rem;
+}
+
+li.pageviewActions > ol.actionList {
+ margin-left: 1.5rem;
}
#visitsLive img {
@@ -294,7 +384,7 @@ ol.visitorLog p {
}
}
- .visitorLog > li > div {
+ .actionList > li > div {
width: 95%;
.segmentedVisitorLogPopover & {
@@ -352,7 +442,7 @@ a.visitor-log-visitor-profile-link {
}
}
-.visitorLog {
+.actionList {
> li > div {
display: inline-block;
width: 90%;
@@ -371,6 +461,7 @@ a.visitor-log-visitor-profile-link {
background-color: #fff;
z-index: 3;
margin-top: 1px;
+ color: #999;
}
.action-list-url {
@@ -500,6 +591,10 @@ a.visitor-log-visitor-profile-link {
}
}
+.refresh-divider {
+ display: none;
+}
+
@media only screen and (min-width: 800px) {
.card #visitsLive .visitorLogIcons:before {
content: none;
diff --git a/plugins/Live/stylesheets/visitor_profile.less b/plugins/Live/stylesheets/visitor_profile.less
index fdcf75e2db..b60ecbc885 100644
--- a/plugins/Live/stylesheets/visitor_profile.less
+++ b/plugins/Live/stylesheets/visitor_profile.less
@@ -383,6 +383,7 @@
display: inline-block;
float: left;
margin-right: 15px;
+ margin-left: -3px;
}
.visitorDetails {
@@ -438,3 +439,8 @@ ol.visitor-profile-actions {
color: #999;
font-size: 13px;
}
+
+.visitor-profile-visits li.pageviewActions.last-action > ol.actionList > li.last-action {
+ margin-bottom: 0;
+ padding-bottom: 0;
+}
diff --git a/plugins/Live/templates/_actionEcommerce.twig b/plugins/Live/templates/_actionEcommerce.twig
index f353781ac3..229067d998 100644
--- a/plugins/Live/templates/_actionEcommerce.twig
+++ b/plugins/Live/templates/_actionEcommerce.twig
@@ -12,7 +12,7 @@
{# TODO: would be nice to have the icons Orders / Cart in the ecommerce log footer #}
{% endif %}
<p>
- <span {% if not isWidget %}style='margin-left:22px;'{% endif %}>
+ <span>
{% if action.type == 'ecommerceOrder' %}
{# spacing is important for tooltip to look nice #}
{% set ecommerceOrderTooltip %}{{ 'General_ColumnRevenue'|translate }}: {{ action.revenue|money(visitInfo.idSite)|raw }}
@@ -36,7 +36,7 @@
{# Ecommerce items in Cart/Order #}
{% if action.itemDetails is not empty %}
- <ul style='list-style:square;margin-left:{% if isWidget %}15{% else %}50{% endif %}px;'>
+ <ul style='list-style:square;margin-left:{% if isWidget %}15{% else %}22{% endif %}px;margin-bottom: 4px;'>
{% for product in action.itemDetails %}
<li>
{{ product.itemSKU }}{% if product.itemName is not empty %}: {{ product.itemName }}{% endif -%}
diff --git a/plugins/Live/templates/_actionsList.twig b/plugins/Live/templates/_actionsList.twig
index 1f6ff3e7c9..78ca780135 100644
--- a/plugins/Live/templates/_actionsList.twig
+++ b/plugins/Live/templates/_actionsList.twig
@@ -1,9 +1,40 @@
{% set previousAction = false %}
-{% for action in actionDetails %}
+{% for actionGroup in actionGroups %}
+ {% if actionGroup.pageviewAction is not empty %}
+ {{ postEvent('Live.renderAction', actionGroup.pageviewAction, previousAction, visitInfo) }}
- {{ postEvent('Live.renderAction', action, previousAction, visitInfo) }}
+ {% set previousAction = actionGroup.pageviewAction %}
+ {% endif %}
-{% set previousAction = action %}
+ {% if actionGroup.actionsOnPage is not empty or actionGroup.refreshActions is not empty %}
+ {% if actionGroup.pageviewAction is not empty %}
+ <li class="pageviewActions" data-view-count="{{ actionGroup.refreshActions|length + 1 }}" data-actions-on-page="{{ actionGroup.actionsOnPage|length }}">
+ <ol class="actionList">
+ {% endif %}
+ {% for action in actionGroup.refreshActions %}
+
+ {{ postEvent('Live.renderAction', action, previousAction, visitInfo) }}
+
+ {% set previousAction = action %}
+ {% endfor %}
+ <li class="refresh-divider"></li>
+ {% for action in actionGroup.actionsOnPage %}
+
+ {{ postEvent('Live.renderAction', action, previousAction, visitInfo) }}
+
+ {% set previousAction = action %}
+ {% endfor %}
+ {% if actionGroup.pageviewAction is not empty %}
+ <li class="actionsForPageExpander expanded" style="display:none;">
+ <span>
+ <a class="show-more-actions" href="javascript:" style="display:none;">Show <span class="show-actions-count"></span> more actions that occurred on this page...</a>
+ <a class="show-less-actions" href="javascript:">Show less actions...</a>
+ </span>
+ </li>
+ </ol>
+ </li>
+ {% endif %}
+ {% endif %}
{% endfor %}
{% if visitInfo.truncatedActionsCount is defined %}
diff --git a/plugins/Live/templates/_dataTableViz_visitorLog.twig b/plugins/Live/templates/_dataTableViz_visitorLog.twig
index f9d0b21a32..a04ff69966 100644
--- a/plugins/Live/templates/_dataTableViz_visitorLog.twig
+++ b/plugins/Live/templates/_dataTableViz_visitorLog.twig
@@ -41,8 +41,8 @@
</strong>
<div class="visitor-log-page-list">
- <ol class='visitorLog'>
- {% include "@Live/_actionsList.twig" with {'actionDetails': visitor.getColumn('actionDetails'), 'visitInfo': visitor} %}
+ <ol class='visitorLog actionList'>
+ {% include "@Live/_actionsList.twig" with {'actionGroups': visitor.getColumn('actionGroups'), 'visitInfo': visitor} %}
</ol>
</div>
{{ postEvent('Live.visitorLogViewAfterActionsInfo', visitor) }}
diff --git a/plugins/Live/templates/getVisitList.twig b/plugins/Live/templates/getVisitList.twig
index b64ed585c5..d214fb8416 100644
--- a/plugins/Live/templates/getVisitList.twig
+++ b/plugins/Live/templates/getVisitList.twig
@@ -25,8 +25,9 @@
{% endif %}
</a>
</div>
- <ol class="visitorLog visitor-profile-actions">
+ <ol class="visitorLog visitor-profile-actions actionList">
{% include "@Live/_actionsList.twig" with {'actionDetails': visitInfo.getColumn('actionDetails'),
+ 'actionGroups': visitInfo.getColumn('actionGroups'),
'visitInfo': visitInfo} %}
</ol>
</div>
diff --git a/plugins/Live/tests/Fixtures/VisitsWithAllActionsAndDevices.php b/plugins/Live/tests/Fixtures/VisitsWithAllActionsAndDevices.php
index 1603e8526a..1bd509ff1e 100644
--- a/plugins/Live/tests/Fixtures/VisitsWithAllActionsAndDevices.php
+++ b/plugins/Live/tests/Fixtures/VisitsWithAllActionsAndDevices.php
@@ -223,5 +223,8 @@ class VisitsWithAllActionsAndDevices extends Fixture
self::checkResponse($t->doTrackPageView('home'));
$t->doTrackContentImpression('product slider', 'product_16.jpg', 'http://example.org/product16');
+ $t->doTrackContentImpression('product slider', 'product_17.jpg', 'http://example.org/product17');
+ $t->doTrackContentImpression('product slider', 'product_18.jpg', 'http://example.org/product18');
+ $t->doTrackContentImpression('product zoom', 'product_18.jpg', 'http://example.org/product18');
}
} \ No newline at end of file
diff --git a/plugins/Live/tests/UI/Live_spec.js b/plugins/Live/tests/UI/Live_spec.js
index 1c0d111fc0..acbd621f03 100644
--- a/plugins/Live/tests/UI/Live_spec.js
+++ b/plugins/Live/tests/UI/Live_spec.js
@@ -8,8 +8,6 @@
*/
describe("Live", function () {
- this.timeout(0);
-
this.fixture = "Piwik\\Plugins\\Live\\tests\\Fixtures\\VisitsWithAllActionsAndDevices";
after(function () {
@@ -23,6 +21,7 @@ describe("Live", function () {
await page.goto("?module=CoreHome&action=index&idSite=1&period=year&date=2010-01-03#?idSite=1&period=year&date=2010-01-03&category=General_Visitors&subcategory=Live_VisitorLog");
await page.waitForNetworkIdle();
+ await page.waitFor('.dataTableVizVisitorLog');
var report = await page.$('.reporting-page');
expect(await report.screenshot()).to.matchImage('visitor_log');
@@ -36,7 +35,35 @@ describe("Live", function () {
expect(await report.screenshot()).to.matchImage('visitor_log_expand_actions');
});
+ it('should expand collapsed pageview actions', async function() {
+ const link = await page.jQuery('.dataTableVizVisitorLog .card.row:eq(1) .show-more-actions:visible');
+ await link.click();
+
+ await page.mouse.move(-10, -10);
+
+ const report = await page.jQuery('.dataTableVizVisitorLog .card.row:eq(1)');
+ expect(await report.screenshot()).to.matchImage('visitor_log_expand_pageview_actions');
+ });
+
+ it('should expand collapsed content actions', async function() {
+ // collapse previously expanded section
+ const prevlink = await page.jQuery('.dataTableVizVisitorLog .card.row:eq(1) .show-less-actions:visible');
+ await prevlink.click();
+
+ const link = await page.jQuery('.dataTableVizVisitorLog .card.row:eq(2) .collapsed-contents:visible');
+ await link.click();
+
+ await page.mouse.move(-10, -10);
+
+ const report = await page.jQuery('.dataTableVizVisitorLog .card.row:eq(2)');
+ expect(await report.screenshot()).to.matchImage('visitor_log_expand_content_actions');
+ });
+
it('should show visitor profile', async function() {
+ // collapse previously expanded section
+ const prevlink = await page.jQuery('.dataTableVizVisitorLog .card.row:eq(2) .collapsed-contents:visible');
+ await prevlink.click();
+
await page.evaluate(function(){
$('.card:first-child .visitor-log-visitor-profile-link').click();
});
diff --git a/plugins/Live/tests/UI/expected-screenshots/Live_visitor_log.png b/plugins/Live/tests/UI/expected-screenshots/Live_visitor_log.png
index 5fcc726b2d..92152ce679 100644
--- a/plugins/Live/tests/UI/expected-screenshots/Live_visitor_log.png
+++ b/plugins/Live/tests/UI/expected-screenshots/Live_visitor_log.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:403d52d525566412b7f6556d9ea109f883fd0fdd79f1a36b0a97ab610f403d81
-size 382454
+oid sha256:ad5ef092dfd4f680395950d1c5e8e0f154c7294138bb9a8091e99d215d0a9966
+size 385839
diff --git a/plugins/Live/tests/UI/expected-screenshots/Live_visitor_log_expand_actions.png b/plugins/Live/tests/UI/expected-screenshots/Live_visitor_log_expand_actions.png
index 7670bd0320..7fdc3de326 100644
--- a/plugins/Live/tests/UI/expected-screenshots/Live_visitor_log_expand_actions.png
+++ b/plugins/Live/tests/UI/expected-screenshots/Live_visitor_log_expand_actions.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:61e520ac19eaf7e2ad0a3c000d4a9f2b422337031ec76c7895f3b92d84157e87
-size 51024
+oid sha256:9fe7032dc1800897a554a6ed30d495efe80998cea1316b6447d299bba13c69f4
+size 51479
diff --git a/plugins/Live/tests/UI/expected-screenshots/Live_visitor_log_expand_content_actions.png b/plugins/Live/tests/UI/expected-screenshots/Live_visitor_log_expand_content_actions.png
new file mode 100644
index 0000000000..0aab20b622
--- /dev/null
+++ b/plugins/Live/tests/UI/expected-screenshots/Live_visitor_log_expand_content_actions.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:48c1a68c50cfcc7ee6bbe9e1d1854911fe6b75311520ca01b32bf964e80cd777
+size 39805
diff --git a/plugins/Live/tests/UI/expected-screenshots/Live_visitor_log_expand_pageview_actions.png b/plugins/Live/tests/UI/expected-screenshots/Live_visitor_log_expand_pageview_actions.png
new file mode 100644
index 0000000000..51e72333da
--- /dev/null
+++ b/plugins/Live/tests/UI/expected-screenshots/Live_visitor_log_expand_pageview_actions.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:8c9f4ce6ba4fca038288a79d6b6d63e997991d055b89104ab425b2640a3e5200
+size 102079
diff --git a/plugins/Live/tests/UI/expected-screenshots/Live_visitor_profile.png b/plugins/Live/tests/UI/expected-screenshots/Live_visitor_profile.png
index 01108007f1..893944edcf 100644
--- a/plugins/Live/tests/UI/expected-screenshots/Live_visitor_profile.png
+++ b/plugins/Live/tests/UI/expected-screenshots/Live_visitor_profile.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:239c3cff34bdc37ef6e1a0a8f85ee537d548b2b0fcf255b25ce90f436c17caa8
-size 417219
+oid sha256:2d08854b59c88e7ab971f23df667daa6bb307955879063d561673b72a6c5e7f2
+size 421449
diff --git a/plugins/Live/tests/UI/expected-screenshots/Live_visitor_profile_action_details.png b/plugins/Live/tests/UI/expected-screenshots/Live_visitor_profile_action_details.png
index c420414212..892c9813f6 100644
--- a/plugins/Live/tests/UI/expected-screenshots/Live_visitor_profile_action_details.png
+++ b/plugins/Live/tests/UI/expected-screenshots/Live_visitor_profile_action_details.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:3baf4e26d25cb2cc08ba78a0c6d199ffadc88aff819af2882abbe7706824b85f
-size 286558
+oid sha256:cce674e80509201b636013a54e144992c16065983810638677d9aea905dcded0
+size 288310
diff --git a/plugins/Live/tests/UI/expected-screenshots/Live_visitor_profile_actions_hidden.png b/plugins/Live/tests/UI/expected-screenshots/Live_visitor_profile_actions_hidden.png
index 4fc4cfc765..3991102d4e 100644
--- a/plugins/Live/tests/UI/expected-screenshots/Live_visitor_profile_actions_hidden.png
+++ b/plugins/Live/tests/UI/expected-screenshots/Live_visitor_profile_actions_hidden.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:13493d125dc03edd1e3b698e9b86056f5e38ab4744aa117cf3d350a470d79b48
-size 254843
+oid sha256:d3d4d7a3ec7beb72d14853b5a99be9a8aa2c45995975de689f2a5b8e7a17ed68
+size 255311
diff --git a/plugins/Live/tests/UI/expected-screenshots/Live_visitor_profile_limited.png b/plugins/Live/tests/UI/expected-screenshots/Live_visitor_profile_limited.png
index 690d51b874..9223b651f6 100644
--- a/plugins/Live/tests/UI/expected-screenshots/Live_visitor_profile_limited.png
+++ b/plugins/Live/tests/UI/expected-screenshots/Live_visitor_profile_limited.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:407bbf4891ff749ec90af841adf6c26848848dcc2146d016c3f9c3a4c7d8abad
-size 309267
+oid sha256:f370ad5825ad62e3e9288062040046f9f15f2c6b522418747543218b912c47c4
+size 313341
diff --git a/plugins/Live/tests/UI/expected-screenshots/Live_visitor_profile_visit_details.png b/plugins/Live/tests/UI/expected-screenshots/Live_visitor_profile_visit_details.png
index c1880e2da4..9b07892f77 100644
--- a/plugins/Live/tests/UI/expected-screenshots/Live_visitor_profile_visit_details.png
+++ b/plugins/Live/tests/UI/expected-screenshots/Live_visitor_profile_visit_details.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:8ff64c409f8124f250da602fb6a4cfe2d7068245a0b70820b8ea7909b52ed1c4
-size 264543
+oid sha256:8326abd7243618a9b71f4cf341c4775c23766b5b244a5fed30db82f15b0091e1
+size 264957
diff --git a/plugins/LoginLdap b/plugins/LoginLdap
-Subproject 16912a9537e542be8edef0d34ed921101628d01
+Subproject c2de63df1887ec0409dceb5fa64f7fb735bce8e
diff --git a/plugins/PrivacyManager/tests/UI/expected-screenshots/PrivacyManager_gdpr_tools_visits_showprofile.png b/plugins/PrivacyManager/tests/UI/expected-screenshots/PrivacyManager_gdpr_tools_visits_showprofile.png
index b91410eaca..d8c991eca7 100644
--- a/plugins/PrivacyManager/tests/UI/expected-screenshots/PrivacyManager_gdpr_tools_visits_showprofile.png
+++ b/plugins/PrivacyManager/tests/UI/expected-screenshots/PrivacyManager_gdpr_tools_visits_showprofile.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:481716a3e85183d3ae7fa0b79ab52d4e990bcff3a9f3a541e2c5872fdf5139ab
-size 323585
+oid sha256:35ff9b4db313b8731142e24d9408481c1891726a359b001e53db0bc91b270c20
+size 327167
diff --git a/plugins/Referrers/tests/UI/CampaignBuilder_spec.js b/plugins/Referrers/tests/UI/CampaignBuilder_spec.js
index 986ae89465..d9fe67fc31 100644
--- a/plugins/Referrers/tests/UI/CampaignBuilder_spec.js
+++ b/plugins/Referrers/tests/UI/CampaignBuilder_spec.js
@@ -52,6 +52,7 @@ describe("CampaignBuilder", function () {
it('can reset form', async function () {
await captureUrlBuilder('generate_url_reset', async function () {
await page.click('.resetCampaignUrl');
+ await page.waitFor(500); // wait to re-render
});
});
diff --git a/plugins/UsersManager/tests/UI/UsersManager_spec.js b/plugins/UsersManager/tests/UI/UsersManager_spec.js
index 7a824728bd..6a7482ba5d 100644
--- a/plugins/UsersManager/tests/UI/UsersManager_spec.js
+++ b/plugins/UsersManager/tests/UI/UsersManager_spec.js
@@ -165,6 +165,9 @@ describe("UsersManager", function () {
await (await page.jQuery('.delete-user-confirm-modal .modal-close:not(.modal-no):visible')).click();
await page.waitForNetworkIdle();
+ await page.mouse.move(-10, -10);
+ await page.waitFor('.pagedUsersList:not(.loading)');
+
expect(await page.screenshotSelector('.usersManager')).to.matchImage('delete_single');
});
@@ -177,6 +180,7 @@ describe("UsersManager", function () {
await page.waitForNetworkIdle();
await page.mouse.move(-10, -10);
+ await page.waitFor('.pagedUsersList:not(.loading)');
expect(await page.screenshotSelector('.usersManager')).to.matchImage('delete_bulk_access');
});
diff --git a/plugins/UsersManager/tests/UI/expected-screenshots/UsersManager_delete_single.png b/plugins/UsersManager/tests/UI/expected-screenshots/UsersManager_delete_single.png
index a9e289c2e3..39c1435865 100644
--- a/plugins/UsersManager/tests/UI/expected-screenshots/UsersManager_delete_single.png
+++ b/plugins/UsersManager/tests/UI/expected-screenshots/UsersManager_delete_single.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:82cb3825149f0ca9e53a28628d1dcc3d6f70cfe5901a57724a9376c7a07d2ee2
-size 139627
+oid sha256:0058fc64d1b2a0d22990a34be329806d127f164c6e92aa8ef35b8cad1445a108
+size 138570