Skip to content

Commit

Permalink
Refs #3089, added ability to browse through all visitors w/ 'previous…
Browse files Browse the repository at this point in the history
…/next visitor' links or by hitting the left/right keys and fix widget css issue.

Notes:
  - Added ability to reload widget w/ overridden parameters that are not persisted.
  • Loading branch information
Benaka Moorthi committed Aug 28, 2013
1 parent fa88a9c commit 27e0da5
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 26 deletions.
4 changes: 3 additions & 1 deletion lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1157,7 +1157,9 @@
"ConvertedNGoals": "Converted %s Goals",
"EcommerceSummary": "Ecommerce: %1$s%2$s orders for a total of %3$s%4$s, purchased %5$s items.",
"VisitedPages": "Visited pages",
"ViewMoreVisitInfo": "View more visitor information"
"ViewMoreVisitInfo": "View more visitor information",
"PreviousVisitor": "Previous visitor",
"NextVisitor": "Next visitor"
},
"Login": {
"PluginDescription": "Login Authentication plugin, reading the credentials from the config\/config.inc.php file for the Super User, and from the Database for the other users. Can be easily replaced to introduce a new Authentication mechanism (OpenID, htaccess, custom Auth, etc.).",
Expand Down
5 changes: 3 additions & 2 deletions plugins/Dashboard/javascripts/dashboardWidget.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@
/**
* Reloads the widgets content with the currently set parameters
*/
reload: function (hideLoading, notJQueryUI) {
reload: function (hideLoading, notJQueryUI, overrideParams) {
if (!notJQueryUI) {
piwikHelper.log('widget.reload() was called by jquery.ui, ignoring', arguments.callee.caller);
return;
Expand All @@ -155,7 +155,8 @@
$('.widgetContent', currentWidget).addClass('loading');
}

widgetsHelper.loadWidgetAjax(this.uniqueId, this.widgetParameters, onWidgetLoadedReplaceElementWithContent);
var params = $.extend(this.widgetParameters, overrideParams || {});
widgetsHelper.loadWidgetAjax(this.uniqueId, params, onWidgetLoadedReplaceElementWithContent);

return this;
},
Expand Down
44 changes: 44 additions & 0 deletions plugins/Live/API.php
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,13 @@ public function getVisitorProfile($idSite, $idVisitor = false, $segment = false)
$visit->setColumn('serverDatePrettyFirstAction', $dateTimePretty);
}

// get visitor IDs that are adjacent to this one in log_visit
// TODO: make sure order of visitor ids is not changed if a returning visitor visits while the user is
// looking at the popup.
$latestVisitTime = reset($rows)->getColumn('lastActionDateTime');
$result['nextVisitorId'] = $this->getAdjacentVisitorId($idSite, $idVisitor, $latestVisitTime, $getNext = true);
$result['prevVisitorId'] = $this->getAdjacentVisitorId($idSite, $idVisitor, $latestVisitTime, $getNext = false);

return $result;
}

Expand All @@ -291,6 +298,43 @@ public function getMostRecentVisitorId($idSite, $segment = false)
return $visitor->getVisitorId();
}

/**
* Returns the ID of a visitor that is adjacent to another visitor (by time of last action)
* in the log_visit table.
*
* @param int $idSite The ID of the site whose visits should be looked at.
* @param string $idVisitor The ID of the visitor to get an adjacent visitor for.
* @param string $visitLastActionTime The last action time of the latest visit for $idVisitor.
* @param bool $getNext Whether to retrieve the next visitor or the previous visitor. The next
* visitor will be the visitor that appears chronologically later in the
* log_visit table. The previous visitor will be the visitor that appears
* earlier.
* @return string The hex visitor ID.
*/
private function getAdjacentVisitorId($idSite, $idVisitor, $visitLastActionTime, $getNext)
{
if ($getNext) {
$visitLastActionTimeCondition = "visit_last_action_time <= ?";
$orderByDir = "DESC";
} else {
$visitLastActionTimeCondition = "visit_last_action_time >= ?";
$orderByDir = "ASC";
}

$sql = "SELECT idvisitor, MAX(visit_last_action_time)
FROM " . Common::prefixTable('log_visit') . "
WHERE idsite = ? AND idvisitor <> UNHEX(?) AND $visitLastActionTimeCondition
GROUP BY idvisitor
ORDER BY MAX(visit_last_action_time) $orderByDir
LIMIT 1";

$idVisitor = Db::fetchOne($sql, array($idSite, $idVisitor, $visitLastActionTime));
if (!empty($idVisitor)) {
$idVisitor = bin2hex($idVisitor);
}
return $idVisitor;
}

/**
* Returns visit data for a single visit.
*
Expand Down
2 changes: 1 addition & 1 deletion plugins/Live/Controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ private static function getSegmentWithVisitorId()
}

$idVisitor = Common::getRequestVar('idVisitor', false);
if ($idVisitor ===false) {
if ($idVisitor === false) {
$idVisitor = Request::processRequest('Live.getMostRecentVisitorId');
}

Expand Down
99 changes: 78 additions & 21 deletions plugins/Live/javascripts/visitorProfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/

(function ($, require) {
(function ($, require, doc) {

var piwik = require('piwik'),
exports = require('piwik/UI');
Expand All @@ -19,10 +19,12 @@
* action. Should have the CSS class 'visitor-profile'.
*/
var VisitorProfileControl = function (element) {
this.element = $(element);
this.$element = $(element);

this._setupControl();
this._bindEventCallbacks();

this.$element.focus();
};

/**
Expand Down Expand Up @@ -56,43 +58,65 @@
VisitorProfileControl.prototype = {

_setupControl: function () {
$('.visitor-profile-visits-container', this.element).jScrollPane({
$('.visitor-profile-visits-container', this.$element).jScrollPane({
showArrows: true,
verticalArrowPositions: 'os',
horizontalArrowPositions: 'os'
});
},

_bindEventCallbacks: function () {
var self = this;
var self = this,
$element = this.$element;

this.element.on('click', '.visitor-profile-close', function (e) {
$element.on('click', '.visitor-profile-close', function (e) {
e.preventDefault();
Piwik_Popover.close();
return false;
});

this.element.on('click', '.visitor-profile-pages-visited,.visitor-profile-more-info', function (e) {
$element.on('click', '.visitor-profile-pages-visited,.visitor-profile-more-info', function (e) {
e.preventDefault();
self._loadMoreVisits();
return false;
});

this.element.on('click', '.visitor-profile-see-more-cvars>a', function (e) {
$element.on('click', '.visitor-profile-see-more-cvars>a', function (e) {
e.preventDefault();
$('.visitor-profile-extra-cvars', self.element).slideToggle();
$('.visitor-profile-extra-cvars', $element).slideToggle();
return false;
});

this.element.on('click', '.visitor-profile-visit-title', function () {
$element.on('click', '.visitor-profile-visit-title', function () {
self._loadIndividualVisitDetails($(this).attr('data-idvisit'));
});

$element.on('click', '.visitor-profile-prev-visitor', function (e) {
e.preventDefault();
self._loadPreviousVisitor();
return false;
});

$element.on('click', '.visitor-profile-next-visitor', function (e) {
e.preventDefault();
self._loadNextVisitor();
return false;
});

$element.on('keydown', function (e) {
if (event.which == 37) { // on <- key press, load previous visitor
self._loadPreviousVisitor();
} else if (event.which == 39) { // on -> key press, load next visitor
self._loadNextVisitor();
}
});
},

_loadMoreVisits: function () {
var self = this;
var self = this,
$element = this.$element;

var loading = $('.visitor-profile-visits-info > .loadingPiwik', this.element);
var loading = $('.visitor-profile-visits-info > .loadingPiwik', $element);
loading.css('display', 'table');

var ajax = new ajaxHelper();
Expand All @@ -101,18 +125,18 @@
action: 'getVisitList',
period: 'range',
date: piwik.minDateYear + '-01-01' + ',today',
idVisitor: this.element.attr('data-visitor-id'),
filter_offset: $('.visitor-profile-visits>li', this.element).length
idVisitor: $element.attr('data-visitor-id'),
filter_offset: $('.visitor-profile-visits>li', $element).length
}, 'GET');
ajax.setCallback(function (response) {
loading.css('display', 'none');

var jsp = $('.visitor-profile-visits-container', self.element).data('jsp');
var jsp = $('.visitor-profile-visits-container', $element).data('jsp');
if (response == '') {
jsp.scrollToElement($('.visitor-profile-visits>li:last-child', self.element).children().last(), false, true);
jsp.scrollToElement($('.visitor-profile-visits>li:last-child', $element).children().last(), false, true);
} else {
response = $(response);
$('.visitor-profile-visits', self.element).append(response);
$('.visitor-profile-visits', $element).append(response);
jsp.reinitialise();
jsp.scrollToElement(response[0], true, true);
}
Expand All @@ -122,9 +146,10 @@
},

_loadIndividualVisitDetails: function (visitId) {
var self = this;
var self = this,
$element = this.$element;

$('.visitor-profile-avatar .loadingPiwik', this.element).css('display', 'inline-block');
$('.visitor-profile-avatar .loadingPiwik', $element).css('display', 'inline-block');

var ajax = new ajaxHelper();
ajax.addParams({
Expand All @@ -133,17 +158,49 @@
idVisit: visitId
}, 'GET');
ajax.setCallback(function (response) {
$('.visitor-profile-avatar .loadingPiwik', self.element).hide();
$('.visitor-profile-latest-visit', self.element).html(response);
$('.visitor-profile-avatar .loadingPiwik', $element).hide();
$('.visitor-profile-latest-visit', $element).html(response);
});
ajax.setFormat('html');
ajax.send();
},

_loadPreviousVisitor: function () {
this._gotoAdjacentVisitor(this.$element.attr('data-prev-visitor'));
},

_loadNextVisitor: function () {
this._gotoAdjacentVisitor(this.$element.attr('data-next-visitor'));
},

_gotoAdjacentVisitor: function (idVisitor) {
if (!idVisitor) {
return;
}

if (this._inPopover()) {
broadcast.propagateNewPopoverParameter('visitorProfile', idVisitor);
} else if (this._inWidget()) {
this.$element.closest('[widgetid]').dashboardWidget('reload', false, true, {idVisitor: idVisitor});
}
},

_getFirstVisitId: function () {
return $('.visitor-profile-visits>li:first-child>h2', this.$element).attr('data-idvisit');
},

_inPopover: function () {
return !! this.$element.closest('#Piwik_Popover').length;
},

_inWidget: function () {
return !! this.$element.closest('.widget').length;
},
};

exports.VisitorProfileControl = VisitorProfileControl;

// add the popup handler that creates a visitor profile
broadcast.addPopoverHandler('visitorProfile', VisitorProfileControl.showPopover);

})(jQuery, require);
})(jQuery, require, document);
26 changes: 26 additions & 0 deletions plugins/Live/stylesheets/visitor_profile.less
Original file line number Diff line number Diff line change
Expand Up @@ -449,8 +449,34 @@ span.visitor-profile-goal-name {
}
}

div.visitor-profile-navigation {
height:auto;
min-height:inherit;
font-size:12px;
float:none;
display:block;
padding:0 0 0 22px;
}

.visitor-profile-prev-visitor {
float:left;
display:inline-block;
margin-bottom:6px;
}

.visitor-profile-next-visitor {
float:right;
display:inline-block;
margin-right:26px;
margin-bottom:6px;
}

.widget .visitor-profile {
min-width: 100% !important;

p {
padding-bottom: 0;
}

.visitor-profile-close {
display:none;
Expand Down
10 changes: 9 additions & 1 deletion plugins/Live/templates/getVisitorProfilePopup.twig
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
<div class="visitor-profile" data-visitor-id="{{ visitorData.lastVisits.getFirstRow().getColumn('visitorId') }}">
<div class="visitor-profile"
data-visitor-id="{{ visitorData.lastVisits.getFirstRow().getColumn('visitorId') }}"
data-next-visitor="{{ visitorData.nextVisitorId }}"
data-prev-visitor="{{ visitorData.prevVisitorId }}"
tabindex="0">
<a href class="visitor-profile-close"></a>
<div class="visitor-profile-info">
<div>
Expand All @@ -16,6 +20,10 @@
</div>
</div>
<p style="clear:both; border:none!important;"></p>
<div class="visitor-profile-navigation">
{% if visitorData.prevVisitorId is not empty %}<a class="visitor-profile-prev-visitor" href="#">&lt;&lt; {{ 'Live_PreviousVisitor'|translate }}</a>{% endif %}
{% if visitorData.nextVisitorId is not empty %}<a class="visitor-profile-next-visitor" href="#">{{ 'Live_NextVisitor'|translate }} &gt;&gt;</a>{% endif %}
</div>
</div>
<div>
<div class="visitor-profile-location">
Expand Down

0 comments on commit 27e0da5

Please sign in to comment.