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:
authorThomas Steur <tsteur@users.noreply.github.com>2017-10-03 23:22:01 +0300
committerGitHub <noreply@github.com>2017-10-03 23:22:01 +0300
commit9af4e95aa976f3a6533e95b776b5298f73e5f916 (patch)
treed612cd4d32019e9e52ce1398b8bf214ec06a8e0f /plugins/SegmentEditor
parent359c3ec875b554c7b71a933b26d18cdde0bb8f4e (diff)
Better segment editor and fixes (#12040)
* column tweak * fix install * more tweaks * rename column to dimension * various fixes * added new control expandable select * starting to refactor segment selector * make segment editor work again * use translation keys * defined some metrics * set types * simplify * simplify * fix join generator * add possibility to use custom join table names when using query builder and it uses an inner query * fix bug in query selector when selecting same field name from different tables twice * more metadata * more tweaks * improve selector * add possibility to use custom entity names * also processed archived metrics * generate sql filter, suggested values callback, and accept values automatically for columns with enums * several tweaks * focus search field when opening it * various tweaks * added missing method * format and fix more metadata * more fixes * better definition * define custom filter * fix definition * fix various tests * fix more tests * fix bug in logquery builder * fix referrerurl segment was missing * fix some tests * fix more tests * add group * refactor for better definition * fix a bug in log query builder when similar columns are used in archiver * add goal metrics * various fixes * make datatable row more flexible * various fixes and visualization enhancements * simply segment editor and make it smaller * remove trailing comma * various fixes and added new dimension * fix formatting of returning customer * added missing primary key * fixes * various fixes and improvements * make sure to update segment definition when selecting a value from auto complete list * various fixes and more metrics * more metrics * more dimensions and fixes * fix some tests * fix some integration tests * update submodule * fix some system tests * fix ui tests * trigger new test run * fix more ui tests * fix system tests * update submodule * fix categories * sort segments by category for more consistency * add custom variables * some translations and fixes * add minute segment * more segments * added plurals * added some docs * fix test * fix tests * fix tests * added suggested values * fix some tests * various fixes * fix more tests * allow to select segments on any site * make sure to include file * added doc block * fix some system tests * fix most system tests * fix ui test * fix system test * adjust examples * added more tests and docs * no metrics for these dimensions * added developer changelog and made some classes public api * some fixes for entity names * add possibility to set format metrics in test * more consistency in defining the name * get idsites only if provided * fix integration tests * added another segment for visit start hour and visit start minute * more clear name for segment * use old segment name to not break bc * various fixes * more test fixes * fix no suggested values for new segment * add event value * for boolean dimensions only sum metric * update available widgets when updating reporting menu * Add new segments in developer changelog + typo * fix system tests * fix screenshot test
Diffstat (limited to 'plugins/SegmentEditor')
-rw-r--r--plugins/SegmentEditor/SegmentEditor.php19
-rw-r--r--plugins/SegmentEditor/angularjs/segment-generator/segmentgenerator-model.js69
-rw-r--r--plugins/SegmentEditor/angularjs/segment-generator/segmentgenerator.controller.js319
-rw-r--r--plugins/SegmentEditor/angularjs/segment-generator/segmentgenerator.directive.html61
-rw-r--r--plugins/SegmentEditor/angularjs/segment-generator/segmentgenerator.directive.js64
-rw-r--r--plugins/SegmentEditor/angularjs/segment-generator/segmentgenerator.directive.less204
-rw-r--r--plugins/SegmentEditor/javascripts/Segmentation.js516
-rw-r--r--plugins/SegmentEditor/stylesheets/segmentation.less264
-rw-r--r--plugins/SegmentEditor/templates/_segmentSelector.twig100
-rw-r--r--plugins/SegmentEditor/tests/Integration/SegmentEditorTest.php2
10 files changed, 769 insertions, 849 deletions
diff --git a/plugins/SegmentEditor/SegmentEditor.php b/plugins/SegmentEditor/SegmentEditor.php
index 63cee53c30..9af29e2194 100644
--- a/plugins/SegmentEditor/SegmentEditor.php
+++ b/plugins/SegmentEditor/SegmentEditor.php
@@ -79,11 +79,15 @@ class SegmentEditor extends \Piwik\Plugin
public function getJsFiles(&$jsFiles)
{
$jsFiles[] = "plugins/SegmentEditor/javascripts/Segmentation.js";
+ $jsFiles[] = "plugins/SegmentEditor/angularjs/segment-generator/segmentgenerator-model.js";
+ $jsFiles[] = "plugins/SegmentEditor/angularjs/segment-generator/segmentgenerator.controller.js";
+ $jsFiles[] = "plugins/SegmentEditor/angularjs/segment-generator/segmentgenerator.directive.js";
}
public function getStylesheetFiles(&$stylesheets)
{
$stylesheets[] = "plugins/SegmentEditor/stylesheets/segmentation.less";
+ $stylesheets[] = "plugins/SegmentEditor/angularjs/segment-generator/segmentgenerator.directive.less";
}
/**
@@ -103,5 +107,20 @@ class SegmentEditor extends \Piwik\Plugin
$translationKeys[] = 'SegmentEditor_SharedWithYou';
$translationKeys[] = 'SegmentEditor_ChooseASegment';
$translationKeys[] = 'SegmentEditor_CurrentlySelectedSegment';
+ $translationKeys[] = 'SegmentEditor_OperatorAND';
+ $translationKeys[] = 'SegmentEditor_OperatorOR';
+ $translationKeys[] = 'SegmentEditor_AddANDorORCondition';
+ $translationKeys[] = 'General_OperationEquals';
+ $translationKeys[] = 'General_OperationNotEquals';
+ $translationKeys[] = 'General_OperationAtMost';
+ $translationKeys[] = 'General_OperationAtLeast';
+ $translationKeys[] = 'General_OperationLessThan';
+ $translationKeys[] = 'General_OperationGreaterThan';
+ $translationKeys[] = 'General_OperationIs';
+ $translationKeys[] = 'General_OperationIsNot';
+ $translationKeys[] = 'General_OperationContains';
+ $translationKeys[] = 'General_OperationDoesNotContain';
+ $translationKeys[] = 'General_OperationStartsWith';
+ $translationKeys[] = 'General_OperationEndsWith';
}
}
diff --git a/plugins/SegmentEditor/angularjs/segment-generator/segmentgenerator-model.js b/plugins/SegmentEditor/angularjs/segment-generator/segmentgenerator-model.js
new file mode 100644
index 0000000000..113352c9d3
--- /dev/null
+++ b/plugins/SegmentEditor/angularjs/segment-generator/segmentgenerator-model.js
@@ -0,0 +1,69 @@
+/*!
+ * 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('segmentGeneratorModel', segmentGeneratorModel);
+
+ segmentGeneratorModel.$inject = ['piwikApi', 'piwik'];
+
+ function segmentGeneratorModel(piwikApi, piwik) {
+
+ var initialSegments = null;
+ var limitPromise = null;
+ var fetchedSiteId = null;
+
+ var model = {
+ isLoading: false,
+ segments : [],
+ loadSegments: loadSegments
+ };
+
+ return model;
+
+ function loadSegments(siteId) {
+ if (model.isLoading) {
+ if (limitPromise) {
+ limitPromise.abort();
+ limitPromise = null;
+ }
+ }
+
+ model.isLoading = true;
+
+ // we need to clear last limit result because we now fetch different data
+ if (fetchedSiteId != siteId) {
+ limitPromise = null;
+ fetchedSiteId = siteId;
+ }
+
+ if (!limitPromise) {
+ var params = {method: 'API.getSegmentsMetadata',filter_limit: '-1'};
+
+ if (siteId === 'all' || !siteId) {
+ params.idSites = 'all';
+ params.idSite = 'all';
+ } else if (siteId) {
+ params.idSites = siteId;
+ params.idSite = siteId;
+ }
+
+ limitPromise = piwikApi.fetch(params);
+ }
+
+ return limitPromise.then(function (response) {
+ model.isLoading = false;
+
+ if (angular.isDefined(response)) {
+ model.segments = response;
+ }
+
+ return response;
+ }).finally(function () {
+ model.isLoading = false;
+ });
+ }
+ }
+})(); \ No newline at end of file
diff --git a/plugins/SegmentEditor/angularjs/segment-generator/segmentgenerator.controller.js b/plugins/SegmentEditor/angularjs/segment-generator/segmentgenerator.controller.js
new file mode 100644
index 0000000000..21eed81367
--- /dev/null
+++ b/plugins/SegmentEditor/angularjs/segment-generator/segmentgenerator.controller.js
@@ -0,0 +1,319 @@
+/*!
+ * 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').controller('SegmentGeneratorController', SegmentGeneratorController);
+
+ SegmentGeneratorController.$inject = ['$scope', 'piwik', 'piwikApi', 'segmentGeneratorModel', '$filter', '$timeout'];
+
+ var findAndExplodeByMatch = function(metric){
+ var matches = ["==" , "!=" , "<=", ">=", "=@" , "!@","<",">", "=^", "=$"];
+ var newMetric = {};
+ var minPos = metric.length;
+ var match, index;
+ var singleChar = false;
+
+ for (var key=0; key < matches.length; key++) {
+ match = matches[key];
+ index = metric.indexOf(match);
+ if(index !== -1){
+ if(index < minPos){
+ minPos = index;
+ if(match.length === 1){
+ singleChar = true;
+ }
+ }
+ }
+ }
+
+ if (minPos < metric.length) {
+ // sth found - explode
+ if(singleChar == true){
+ newMetric.segment = metric.substr(0,minPos);
+ newMetric.matches = metric.substr(minPos,1);
+ newMetric.value = metric.substr(minPos+1);
+ } else {
+ newMetric.segment = metric.substr(0,minPos);
+ newMetric.matches = metric.substr(minPos,2);
+ newMetric.value = metric.substr(minPos+2);
+ }
+ // if value is only "" -> change to empty string
+ if(newMetric.value === '""')
+ {
+ newMetric.value = "";
+ }
+ }
+
+ newMetric.value = decodeURIComponent(newMetric.value);
+ return newMetric;
+ };
+
+ function generateUniqueId() {
+ var id = '';
+ var chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
+
+ for (var i = 1; i <= 10; i++) {
+ id += chars.charAt(Math.floor(Math.random() * chars.length));
+ }
+
+ return id;
+ }
+
+ function stripTags(text) {
+ if (text) {
+ text = ('' + text).replace(/(<([^>]+)>)/ig,"");
+ }
+ return text;
+ }
+
+ function SegmentGeneratorController($scope, piwik, piwikApi, segmentGeneratorModel, $filter, $timeout) {
+ var translate = $filter('translate');
+
+ var self = this;
+ var firstSegment = '';
+ var firstMatch = '';
+ this.conditions = [];
+ this.model = segmentGeneratorModel;
+
+ this.segments = {};
+
+ this.matches = {
+ metric: [
+ {key: '==', value: translate('General_OperationEquals')},
+ {key: '!=', value: translate('General_OperationNotEquals')},
+ {key: '<=', value: translate('General_OperationAtMost')},
+ {key: '>=', value: translate('General_OperationAtLeast')},
+ {key: '<', value: translate('General_OperationLessThan')},
+ {key: '>', value: translate('General_OperationGreaterThan')}
+ ],
+ dimension: [
+ {key: '==', value: translate('General_OperationIs')},
+ {key: '!=', value: translate('General_OperationIsNot')},
+ {key: '=@', value: translate('General_OperationContains')},
+ {key: '!@', value: translate('General_OperationDoesNotContain')},
+ {key: '=^', value: translate('General_OperationStartsWith')},
+ {key: '=$', value: translate('General_OperationEndsWith')}
+ ],
+ };
+ this.matches[''] = this.matches.dimension;
+
+ this.andConditionLabel = '';
+
+ this.addNewAndCondition = function () {
+ var condition = {orConditions: []};
+
+ this.addAndCondition(condition);
+ this.addNewOrCondition(condition);
+
+ return condition;
+ };
+
+ this.addAndCondition = function (condition) {
+ this.andConditionLabel = translate('SegmentEditor_OperatorAND');
+ this.conditions.push(condition);
+ this.updateSegmentDefinition();
+ }
+
+ this.addNewOrCondition = function (condition) {
+ var orCondition = {
+ segment: firstSegment,
+ matches: firstMatch,
+ value: ''
+ };
+
+ this.addOrCondition(condition, orCondition);
+ };
+
+ this.addOrCondition = function (condition, orCondition) {
+ orCondition.isLoading = false;
+ orCondition.id = generateUniqueId();
+
+ condition.orConditions.push(orCondition);
+ this.updateSegmentDefinition();
+
+ $timeout(function () {
+ self.updateAutocomplete(orCondition);
+ });
+ };
+
+ this.updateAutocomplete = function (orCondition) {
+ orCondition.isLoading = true;
+
+ this.updateSegmentDefinition();
+
+ var resolved = false;
+
+ var promise = piwikApi.fetch({
+ module: 'API',
+ format: 'json',
+ method: 'API.getSuggestedValuesForSegment',
+ segmentName: orCondition.segment
+ }, {createErrorNotification: false})
+
+ promise.then(function(response) {
+ orCondition.isLoading = false;
+ resolved = true;
+
+ var inputElement = $('.orCondId' + orCondition.id + " .metricValueBlock input");
+
+ if (response && response.result != 'error') {
+
+ inputElement.autocomplete({
+ source: response,
+ minLength: 0,
+ select: function(event, ui){
+ event.preventDefault();
+ orCondition.value = ui.item.value;
+ self.updateSegmentDefinition();
+ $timeout(function () {
+ $scope.$apply();
+ });
+ }
+ });
+ }
+
+ inputElement.off('click');
+ inputElement.click(function (e) {
+ $(inputElement).autocomplete('search', orCondition.value);
+ });
+ }, function(response) {
+ resolved = true;
+ orCondition.isLoading = false;
+
+ var inputElement = $('.orCondId' + orCondition.id + " .metricValueBlock input");
+ inputElement.autocomplete({
+ source: [],
+ minLength: 0
+ });
+ $(inputElement).autocomplete('search', orCondition.value);
+ });
+
+ $timeout(function () {
+ if (!resolved) {
+ promise.abort();
+ }
+ }, 20000);
+ }
+
+ this.removeOrCondition = function (condition, orCondition) {
+ var index = condition.orConditions.indexOf(orCondition);
+ if (index > -1) {
+ condition.orConditions.splice(index, 1);
+ }
+
+ if (condition.orConditions.length === 0) {
+ var index = self.conditions.indexOf(condition);
+ if (index > -1) {
+ self.conditions.splice(index, 1);
+ }
+ if (self.conditions.length === 0) {
+ self.andConditionLabel = '';
+ }
+ }
+
+ this.updateSegmentDefinition();
+ };
+
+ this.getSegmentString = function () {
+ var segmentStr = '';
+
+ angular.forEach(this.conditions, function (conditions) {
+ var subSegmentStr = '';
+
+ angular.forEach(conditions.orConditions, function (orCondition){
+ if (subSegmentStr !== ''){
+ subSegmentStr += ","; // OR operator
+ }
+
+ subSegmentStr += orCondition.segment + orCondition.matches + encodeURIComponent(orCondition.value);
+ })
+
+ if (segmentStr !== '') {
+ segmentStr += ";"; // add AND operator between segment blocks
+ }
+
+ segmentStr += subSegmentStr;
+ });
+
+ return segmentStr
+ };
+
+ this.setSegmentString = function (segmentStr) {
+ var orCondition, condition;
+
+ this.conditions = [];
+
+ if (!segmentStr) {
+ return;
+ }
+
+ var blocks = segmentStr.split(';');
+
+ for (var key = 0; key < blocks.length; key++) {
+ var condition = {orConditions: []};
+ this.addAndCondition(condition);
+
+ blocks[key] = blocks[key].split(',');
+ for (var innerkey = 0; innerkey < blocks[key].length; innerkey++) {
+ orCondition = findAndExplodeByMatch(blocks[key][innerkey]);
+ this.addOrCondition(condition, orCondition);
+ }
+ }
+ };
+
+ this.updateSegmentDefinition = function () {
+ $scope.segmentDefinition = this.getSegmentString();
+ }
+
+ if ($scope.segmentDefinition) {
+ this.setSegmentString($scope.segmentDefinition);
+ }
+
+ $scope.$watch('idsite', function (newValue, oldValue) {
+ if (newValue != oldValue) {
+ reloadSegments(newValue);
+ }
+ });
+
+ reloadSegments($scope.idsite);
+
+ function reloadSegments(idsite) {
+ segmentGeneratorModel.loadSegments(idsite).then(function (segments) {
+
+ self.segmentList = [];
+
+ var groups = {};
+ angular.forEach(segments, function (segment) {
+ if (!segment.category) {
+ segment.category = 'Others';
+ }
+
+ if (!firstSegment) {
+ firstSegment = segment.segment;
+ if (segment.type && self.matches[segment.type]) {
+ firstMatch = self.matches[segment.type][0].key;
+ } else {
+ firstMatch = self.matches[''][0].key
+ }
+ }
+
+ self.segments[segment.segment] = segment;
+
+ var segmentData = {group: segment.category, key: segment.segment, value: segment.name};
+ if ('acceptedValues' in segment && segment.acceptedValues) {
+ segmentData.tooltip = stripTags(segment.acceptedValues);
+ }
+ self.segmentList.push(segmentData);
+ });
+
+ if ($scope.addInitialCondition && self.conditions.length === 0) {
+ self.addNewAndCondition();
+ }
+ });
+ }
+ }
+
+})();
diff --git a/plugins/SegmentEditor/angularjs/segment-generator/segmentgenerator.directive.html b/plugins/SegmentEditor/angularjs/segment-generator/segmentgenerator.directive.html
new file mode 100644
index 0000000000..9fd8ab548f
--- /dev/null
+++ b/plugins/SegmentEditor/angularjs/segment-generator/segmentgenerator.directive.html
@@ -0,0 +1,61 @@
+<div class="segment-generator">
+ <div piwik-activity-indicator loading="segmentGenerator.model.isLoading"></div>
+
+ <div ng-repeat="(conditionIndex,condition) in segmentGenerator.conditions" class="segmentRow{{conditionIndex}}">
+ <div class="segment-rows">
+ <div ng-repeat="orCondition in condition.orConditions" class="orCondId{{ orCondition.id }}">
+ <div class="segment-row">
+ <a ng-click="segmentGenerator.removeOrCondition(condition, orCondition)" class="segment-close"></a>
+ <a href="#" class="segment-loading" ng-show="orCondition.isLoading"></a>
+ <div class="segment-row-inputs valign-wrapper">
+ <div class="segment-input metricListBlock valign-wrapper">
+ <div piwik-field uicontrol="expandable-select" name="segments"
+ title="{{ segmentGenerator.segments[orCondition.segment].name }}"
+ full-width="true"
+ style="width: 100%;"
+ ng-change="segmentGenerator.updateAutocomplete(orCondition)"
+ ng-model="orCondition.segment"
+ options="segmentGenerator.segmentList">
+ </div>
+ </div>
+ <div class="segment-input metricMatchBlock valign-wrapper">
+ <div piwik-field uicontrol="select" name="matches"
+ style="display: inline-block"
+ full-width="true"
+ ng-change="segmentGenerator.updateSegmentDefinition()"
+ ng-model="orCondition.matches"
+ options="segmentGenerator.matches[segmentGenerator.segments[orCondition.segment].type]">
+ </div>
+ </div>
+ <div class="segment-input metricValueBlock valign-wrapper">
+ <div class="form-group row" style="width: 100%;">
+ <div class="input-field col s12">
+ <span role="status" aria-live="polite" class="ui-helper-hidden-accessible"></span>
+ <input ng-model="orCondition.value" placeholder="Value"
+ ng-change="segmentGenerator.updateSegmentDefinition()"
+ type="text" class="autocomplete" title="Value" autocomplete="off">
+ </div>
+ </div>
+ </div>
+ <div class="clear"></div>
+ </div>
+ </div>
+
+ <div class="segment-or">{{ 'SegmentEditor_OperatorOR'|translate }}</div>
+ </div>
+
+ <div class="segment-add-or" ng-click="segmentGenerator.addNewOrCondition(condition)">
+ <div>
+ <a ng-bind-html="'+' + ('SegmentEditor_AddANDorORCondition'|translate:'&lt;span>' + ('SegmentEditor_OperatorOR'|translate) + '&lt;/span>')"></a>
+ </div>
+ </div>
+ </div>
+ <div class="segment-and">{{ 'SegmentEditor_OperatorAND'|translate }}</div>
+ </div>
+
+ <div class="segment-add-row initial" ng-click="segmentGenerator.addNewAndCondition()">
+ <div>
+ <a ng-bind-html="'+' + ('SegmentEditor_AddANDorORCondition'|translate:'&lt;span>' + segmentGenerator.andConditionLabel + '&lt;/span>')"></a>
+ </div>
+ </div>
+</div>
diff --git a/plugins/SegmentEditor/angularjs/segment-generator/segmentgenerator.directive.js b/plugins/SegmentEditor/angularjs/segment-generator/segmentgenerator.directive.js
new file mode 100644
index 0000000000..25a0141f19
--- /dev/null
+++ b/plugins/SegmentEditor/angularjs/segment-generator/segmentgenerator.directive.js
@@ -0,0 +1,64 @@
+/*!
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+/**
+ * Usage:
+ * <div piwik-segment-generator>
+ */
+(function () {
+ angular.module('piwikApp').directive('piwikSegmentGenerator', piwikSegmentGenerator);
+
+ piwikSegmentGenerator.$inject = ['$document', 'piwik', '$filter', '$timeout'];
+
+ function piwikSegmentGenerator($document, piwik, $filter, $timeout){
+ var defaults = {
+ segmentDefinition: '',
+ addInitialCondition: false,
+ idsite: piwik.idSite
+ };
+
+ return {
+ restrict: 'A',
+ scope: {
+ segmentDefinition: '@',
+ addInitialCondition: '=',
+ idsite: '='
+ },
+ require: "?ngModel",
+ templateUrl: 'plugins/SegmentEditor/angularjs/segment-generator/segmentgenerator.directive.html?cb=' + piwik.cacheBuster,
+ controller: 'SegmentGeneratorController',
+ controllerAs: 'segmentGenerator',
+ compile: function (element, attrs) {
+
+ for (var index in defaults) {
+ if (attrs[index] === undefined) {
+ attrs[index] = defaults[index];
+ }
+ }
+
+ return function (scope, element, attrs, ngModel) {
+ if (ngModel) {
+ ngModel.$render = function() {
+ scope.segmentDefinition = ngModel.$viewValue;
+ if (scope.segmentDefinition) {
+ scope.segmentGenerator.setSegmentString(scope.segmentDefinition);
+ } else {
+ scope.segmentGenerator.setSegmentString('');
+ }
+ };
+ }
+
+ scope.$watch('segmentDefinition', function (newValue) {
+ if (ngModel) {
+ ngModel.$setViewValue(newValue);
+ }
+ });
+ };
+ }
+ };
+ }
+})(); \ No newline at end of file
diff --git a/plugins/SegmentEditor/angularjs/segment-generator/segmentgenerator.directive.less b/plugins/SegmentEditor/angularjs/segment-generator/segmentgenerator.directive.less
new file mode 100644
index 0000000000..782db0a88e
--- /dev/null
+++ b/plugins/SegmentEditor/angularjs/segment-generator/segmentgenerator.directive.less
@@ -0,0 +1,204 @@
+.segment-generator {
+ width: 900px;
+
+ .segment-row-inputs {
+ .form-group {
+ margin-top: 0;
+ margin-bottom: 0;
+
+ .input-field {
+ margin-top: 0;
+ }
+ }
+ }
+
+ .segment-input input {
+ display: block;
+ width: 96%;
+ padding: 8px 2%;
+ }
+ .segment-input label {
+ display: block;
+ margin: 0 0 5px 0;
+ font-size: 11px;
+ color: #505050;
+ }
+ .segment-input {
+ float: left;
+ padding: 6px 0 5px 3px;
+ border: 2px dashed #EFEFEB;
+ margin-right: 3px;
+ }
+
+ .segment-rows {
+ padding: 4px;
+ margin: 0 3px 0 0;
+ border: 1px solid #a9a399;
+ border-radius: 3px 3px 3px 3px;
+ position: relative;
+ box-shadow: 0 12px 6px -10px rgba(0, 0, 0, 0.42);
+ }
+
+ .segment-add-row,
+ .segment-add-or {
+ cursor: pointer;
+ font-size: 14px;
+ font-weight: bold;
+ background: @theme-color-background-contrast;
+ color: #b9b9b9;
+ text-align: center;
+ position: relative;
+ .border-radius(5px);
+ }
+
+ .segment-add-row > div,
+ .segment-add-or > div {
+ border-radius: 4px;
+ border: 2px dashed #fff;
+ padding: 10px 0;
+ }
+
+ .segment-add-row > div a,
+ .segment-add-or > div a {
+ color: #b9b9b9;
+ text-decoration: none;
+ }
+
+ .segment-input {
+ select, input {
+ .font-default(12px, 14px);
+ color: @theme-color-text;
+ font-weight: 600;
+ margin: 0;
+ height: 32px;
+ }
+ }
+
+ .segment-add-row > div a span,
+ .segment-add-or > div a span {
+ color: @theme-color-brand;
+ text-shadow: none;
+ }
+
+ .segment-add-row {
+ margin: 0 3px 0 0;
+ padding: 0 12px;
+ border: 1px solid #a9a399;
+ border-radius: 3px 3px 3px 3px;
+ box-shadow: 0 12px 6px -10px rgba(0, 0, 0, 0.42);
+ }
+
+ .segment-add-or {
+ text-shadow: 0 1px 0 #fff;
+ display: inline-block;
+ width: 100%;
+ padding: 0 1%;
+ background: #efefeb;
+ border-radius: 3px 3px 3px 3px;
+ }
+
+ .segment-add-or > div {
+ border: 2px dashed #EFEFEB;
+ background-color: #efefeb;
+ }
+
+ .segment-row {
+ border-radius: 3px;
+ display: inline-block;
+ position: relative;
+ width: 887px;
+ padding: 12px 1%;
+ background: #efefeb;
+ padding: 0 5px 0 5px;
+ }
+
+ .segment-row .segment-close {
+ top: 15px;
+ right: 6px;
+ position: absolute;
+ width: 15px;
+ height: 15px;
+ background: url(plugins/SegmentEditor/images/segment-close.png) 0 0 no-repeat;
+ z-index: 9999;
+ }
+
+ .segment-row .segment-loading {
+ top: 25px;
+ right: 30px;
+ position: absolute;
+ width: 15px;
+ height: 15px;
+ background: url(plugins/Morpheus/images/loading-blue.gif) 0 0 no-repeat;
+ }
+
+ .segment-or {
+ display: inline-block;
+ margin: 0 0 0 6%;
+ position: relative;
+ background: #efefeb;
+ padding: 5px 28px;
+ color: #4f4f4f;
+ font-weight: bold;
+ font-size: 14px;
+ text-shadow: 0 1px 0 #fff;
+ }
+
+ .segment-or:before,
+ .segment-or:after {
+ content: '';
+ position: absolute;
+ background: @theme-color-background-base;
+ border: 1px solid #efefeb;
+ width: 10px;
+ top: -1px;
+ bottom: -1px;
+ }
+
+ .segment-or:before {
+ border-left: none;
+ left: 0;
+ border-radius: 0 5px 5px 0;
+ }
+
+ .segment-or:after {
+ border-right: none;
+ right: 0;
+ border-radius: 5px 0 0 5px;
+ }
+
+ .segment-and {
+ display: inline-block;
+ margin: -1px 0 -1px 6%;
+ z-index: 1;
+ position: relative;
+ background: @theme-color-background-contrast;
+ padding: 5px 35px;
+ color: #4f4f4f;
+ font-size: 14px;
+ font-weight: bold;
+ text-shadow: 0 1px 0 #fff;
+ }
+
+ .segment-and:before,
+ .segment-and:after {
+ content: '';
+ position: absolute;
+ background: url(plugins/SegmentEditor/images/bg-inverted-corners.png);
+ border: 1px solid #a9a399;
+ width: 10px;
+ top: 0;
+ bottom: 0;
+ }
+
+ .segment-and:before {
+ border-left: none;
+ left: 0;
+ border-radius: 0 5px 5px 0;
+ }
+
+ .segment-and:after {
+ border-right: none;
+ right: 0;
+ border-radius: 5px 0 0 5px;
+ }
+}
diff --git a/plugins/SegmentEditor/javascripts/Segmentation.js b/plugins/SegmentEditor/javascripts/Segmentation.js
index 0153a6b491..e018fec7cd 100644
--- a/plugins/SegmentEditor/javascripts/Segmentation.js
+++ b/plugins/SegmentEditor/javascripts/Segmentation.js
@@ -19,11 +19,6 @@ Segmentation = (function($) {
$('.segmentListContainer .segmentationContainer .title').trigger('click').focus();
});
- function preselectFirstMetricMatch(rowNode)
- {
- var matchValue = $(rowNode).find('.metricMatchBlock option:first').attr('value');
- $(rowNode).find('.metricMatchBlock select').val(matchValue);
- }
var segmentation = function segmentation(config) {
if (!config.target) {
@@ -128,98 +123,11 @@ Segmentation = (function($) {
}
};
- var getAndDiv = function(){
- if(typeof andDiv === "undefined"){
- var andDiv = self.editorTemplate.find("> div.segment-and").clone();
- }
- return andDiv.clone();
- };
-
- var getOrDiv = function(){
- if(typeof orDiv === "undefined"){
- var orDiv = self.editorTemplate.find("> div.segment-or").clone();
- }
- return orDiv.clone();
- };
-
- var getMockedInputSet = function(){
- var mockedInputSet = self.editorTemplate.find("div.segment-row-inputs").clone();
- var clonedInput = mockedInputSet.clone();
- preselectFirstMetricMatch(clonedInput);
-
- return clonedInput;
- };
-
- var getMockedInputRowHtml = function(){
- return '<div class="segment-row"><a class="segment-close" href="#"></a><div class="segment-row-inputs">'+getMockedInputSet().html()+'</div></div>';
- };
-
- var getMockedFormRow = function(){
- var mockedFormRow = self.editorTemplate.find("div.segment-rows").clone();
- $(mockedFormRow).find(".segment-row").append(getMockedInputSet()).after(getAddOrBlockButtonHtml).after(getOrDiv());
- var clonedRow = mockedFormRow.clone();
- preselectFirstMetricMatch(clonedRow);
-
- return clonedRow;
- };
-
- var getInitialStateRowsHtml = function(){
- if(typeof initialStateRows === "undefined"){
- var content = self.editorTemplate.find("div.initial-state-rows").html();
- var initialStateRows = $(content).clone();
- }
- return initialStateRows;
- };
-
- var revokeInitialStateRows = function(){
- $(self.form).find(".segment-add-row").remove();
- $(self.form).find(".segment-and").remove();
- };
-
- var appendSpecifiedRowHtml= function(metric) {
- var mockedRow = getMockedFormRow();
- $(self.form).find(".segment-content > h3").after(mockedRow);
- $(self.form).find(".segment-content").append(getAndDiv());
- $(self.form).find(".segment-content").append(getAddNewBlockButtonHtml());
- doDragDropBindings();
- $(self.form).find(".metricList").val(metric).trigger("change");
- preselectFirstMetricMatch(mockedRow);
- };
-
- var appendComplexRowHtml = function(block){
- var key;
- var newRow = getMockedFormRow();
-
- var x = $(newRow).find(".metricMatchBlock select");
- $(newRow).find(".metricListBlock select").val(block[0].metric);
- $(newRow).find(".metricMatchBlock select").val(block[0].match);
- $(newRow).find(".metricValueBlock input").val(block[0].value);
-
- if(block.length > 1) {
- $(newRow).find(".segment-add-or").remove();
- for(key = 1; key < block.length;key++) {
- var newSubRow = $(getMockedInputRowHtml()).clone();
- $(newSubRow).find(".metricListBlock select").val(block[key].metric);
- $(newSubRow).find(".metricMatchBlock select").val(block[key].match);
- $(newSubRow).find(".metricValueBlock input").val(block[key].value);
- $(newRow).append(newSubRow).append(getOrDiv());
- }
- $(newRow).append(getAddOrBlockButtonHtml());
- }
- $(self.form).find(".segment-content").append(newRow).append(getAndDiv());
- };
-
- var applyInitialStateModification = function(){
- $(self.form).find(".segment-add-row").remove();
- $(self.form).find(".segment-content").append(getInitialStateRowsHtml());
- doDragDropBindings();
- };
-
var getSegmentFromId = function (id) {
if(self.availableSegments.length > 0) {
for(var i = 0; i < self.availableSegments.length; i++)
{
- segment = self.availableSegments[i];
+ var segment = self.availableSegments[i];
if(segment.idsegment == id) {
return segment;
}
@@ -361,7 +269,7 @@ Segmentation = (function($) {
newOption = '<option data-idsegment="'+segment.idsegment+'" data-definition="'+(segment.definition).replace(/"/g, '&quot;')+'" title="'+getSegmentTooltipEnrichedWithUsername(segment)+'">'+getSegmentName(segment)+'</option>';
segmentsDropdown.append(newOption);
}
- $(html).find(".segment-content > h3").after(getInitialStateRowsHtml()).show();
+ $(html).find(".segment-content > h3").after('<div piwik-segment-generator add-initial-condition="true"></div>').show();
return html;
};
@@ -372,62 +280,6 @@ Segmentation = (function($) {
});
};
- var findAndExplodeByMatch = function(metric){
- var matches = ["==" , "!=" , "<=", ">=", "=@" , "!@","<",">", "=^", "=$"];
- var newMetric = {};
- var minPos = metric.length;
- var match, index;
- var singleChar = false;
-
- for(var key=0; key < matches.length; key++)
- {
- match = matches[key];
- index = metric.indexOf(match);
- if( index != -1){
- if(index < minPos){
- minPos = index;
- if(match.length == 1){
- singleChar = true;
- }
- }
- }
- }
-
- if(minPos < metric.length){
- // sth found - explode
- if(singleChar == true){
- newMetric.metric = metric.substr(0,minPos);
- newMetric.match = metric.substr(minPos,1);
- newMetric.value = metric.substr(minPos+1);
- } else {
- newMetric.metric = metric.substr(0,minPos);
- newMetric.match = metric.substr(minPos,2);
- newMetric.value = metric.substr(minPos+2);
- }
- // if value is only "" -> change to empty string
- if(newMetric.value == '""')
- {
- newMetric.value = "";
- }
- }
-
- newMetric.value = decodeURIComponent(newMetric.value);
- return newMetric;
- };
-
- var parseSegmentStr = function(segmentStr)
- {
- var blocks;
- blocks = segmentStr.split(";");
- for(var key in blocks){
- blocks[key] = blocks[key].split(",");
- for(var innerkey = 0; innerkey < blocks[key].length; innerkey++){
- blocks[key][innerkey] = findAndExplodeByMatch(blocks[key][innerkey]);
- }
- }
- return blocks;
- };
-
var openEditForm = function(segment){
addForm("edit", segment);
@@ -441,24 +293,14 @@ Segmentation = (function($) {
.html( getSegmentName(segment) )
.prop( 'title', getSegmentTooltipEnrichedWithUsername(segment));
- if(segment.definition != ""){
- revokeInitialStateRows();
- var blocks = parseSegmentStr(segment.definition);
- for(var key in blocks){
- appendComplexRowHtml(blocks[key]);
- }
- $(self.form).find(".segment-content").append(getAddNewBlockButtonHtml());
- }
$(self.form).find(".metricList").each( function(){
$(this).trigger("change", true);
});
- doDragDropBindings();
};
var displayFormAddNewSegment = function (e) {
closeAllOpenLists();
addForm("new");
- doDragDropBindings();
};
var filterSegmentList = function (keyword) {
@@ -561,17 +403,6 @@ Segmentation = (function($) {
displayFormAddNewSegment(e);
});
- self.target.on('change', "select.metricList", function (e, persist) {
- if (typeof persist === "undefined") {
- persist = false;
- }
- alterMatchesList(this, true);
-
- doDragDropBindings();
-
- autoSuggestValues(this, persist);
- });
-
// attach event that will clear segment list filtering input after clicking x
self.target.on('click', ".segmentFilterContainer span", function (e) {
$(e.target).parent().find(".segmentFilter").val(self.translations['General_Search']).trigger('keyup');
@@ -613,8 +444,10 @@ Segmentation = (function($) {
//
self.target.on('click', "a.editSegmentName", function (e) {
- var oldName = $(e.currentTarget).parents("h3").find("span").text();
- $(e.currentTarget).parents("h3").find("span").hide();
+ var $h3 = $(e.currentTarget).parents("h3");
+ $h3.css({'margin': '0 0 0 6px'});
+ var oldName = $h3.find("span").text();
+ $h3.find("span").hide();
$(e.currentTarget).hide();
$(e.currentTarget).before('<input class="edit_segment_name" type="text"/>');
$(e.currentTarget).siblings(".edit_segment_name").focus().val(oldName);
@@ -645,48 +478,6 @@ Segmentation = (function($) {
openEditFormGivenSegment(option);
});
- // attach event that shows/hides child elements of each metric category
- self.target.on('click', '.segment-nav a.metric_category', function (e) {
- $(e.currentTarget).siblings("ul").toggle();
- });
-
- self.target.on('click', ".custom_select_search a", function (e) {
- $(self.form).find(".segmentSearch").val("").trigger("keyup").val(self.translations['General_Search']);
- });
-
- // attach event that will clear search input upon focus if its content is default
- self.target.on('focus', '.segmentSearch', function (e) {
- var search = $(e.currentTarget).val();
- if(search == self.translations['General_Search'])
- $(e.currentTarget).val("");
- });
-
- // attach event that will set search input value upon blur if its content is not null
- self.target.on('blur', '.segmentSearch', function (e) {
- var search = $(e.currentTarget).val();
- if(search == ""){
- clearSearchMetricHighlight();
- $(e.currentTarget).val(self.translations['General_Search']);
- }
- });
-
- // bind search action triggering - only when input text is longer than 2 chars
- self.target.on('keyup', '.segmentSearch', function (e) {
- var search = $(e.currentTarget).val();
- if( search.length >= 2)
- {
- clearTimeout(self.timer);
- self.searchAllowed = true;
- self.timer = setTimeout(function(){
- searchSegments(search);
- }, 500);
- }
- else{
- self.searchAllowed = false;
- clearSearchMetricHighlight();
- }
- });
-
self.target.on('click', ".delete", function() {
var segmentName = $(self.form).find(".segment-content > h3 > span").text();
var segmentId = $(self.form).find(".available_segments_select option:selected").attr("data-idsegment");
@@ -709,7 +500,10 @@ Segmentation = (function($) {
});
$("body").on("keyup", function (e) {
- if(e.keyCode == "27"){
+ if(e.keyCode == "27" || e.which === 27) {
+ if (self.target.find('[uicontrol="expandable-select"] .expandableList:visible').length) {
+ return;
+ }
$(".segmentListContainer", self.target).show();
closeForm();
}
@@ -719,146 +513,6 @@ Segmentation = (function($) {
// segment manipulation events
//
- // upon clicking - add new segment block, then bind 'x' action to newly added row
- self.target.on('click', ".segment-add-row a", function(event, data){
- var mockedRow = getMockedFormRow();
- $(self.form).find(".segment-and:last").after(getAndDiv()).after(mockedRow);
- if(typeof data !== "undefined"){
- $(self.form).find(".metricList:last").val(data);
- }
- $(self.form).find(".metricList:last").trigger('change');
- preselectFirstMetricMatch(mockedRow);
- doDragDropBindings();
- });
-
- self.target.on("click", ".segment-add-row span", function(event, data){
- if(typeof data !== "undefined") {
- var mockedRow = getMockedFormRow();
- $(self.form).find(".segment-and:last").after(getAndDiv()).after(mockedRow);
- preselectFirstMetricMatch(mockedRow);
- $(self.form).find(".metricList:last").val(data).trigger('change');
- doDragDropBindings();
- }
- });
-
- // add new OR block
- self.target.on("click", ".segment-add-or a", function(event, data){
- var parentRows = $(event.currentTarget).parents(".segment-rows");
-
- parentRows.find(".segment-or:last").after(getOrDiv()).after(getMockedInputRowHtml());
- if(typeof data !== "undefined"){
- parentRows.find(".metricList:last").val(data);
- }
- parentRows.find(".metricList:last").trigger('change');
-
- var addedRow = parentRows.find('.segment-row:last');
- preselectFirstMetricMatch(addedRow);
- doDragDropBindings();
- });
-
- self.target.on("click", ".segment-close", function (e) {
- var target = e.currentTarget;
- var rowCnt = $(target).parents(".segment-rows").find(".segment-row").length;
- var globalRowCnt = $(self.form).find(".segment-close").length;
- if(rowCnt > 1){
- $(target).parents(".segment-row").next().remove();
- $(target).parents(".segment-row").remove();
- }
- else if(rowCnt == 1){
- $(target).parents(".segment-rows").next().remove();
- $(target).parents(".segment-rows").remove();
- if(globalRowCnt == 1){
- applyInitialStateModification();
- }
- }
- });
- };
-
- // Request auto-suggest values
- var autoSuggestValues = function(select, persist) {
- var type = $(select).find("option:selected").attr("value");
- if(!persist) {
- var parents = $(select).parents('.segment-row');
- var loadingElement = parents.find(".segment-loading");
- loadingElement.show();
- var inputElement = parents.find(".metricValueBlock input");
- var segmentName = $('option:selected',select).attr('value');
-
- var ajaxHandler = new ajaxHelper();
- ajaxHandler.addParams({
- module: 'API',
- format: 'json',
- method: 'API.getSuggestedValuesForSegment',
- segmentName: segmentName
- }, 'GET');
- ajaxHandler.useRegularCallbackInCaseOfError = true;
- ajaxHandler.setTimeout(20000);
- ajaxHandler.setErrorCallback(function(response) {
- loadingElement.hide();
- inputElement.autocomplete({
- source: [],
- minLength: 0
- });
- $(inputElement).autocomplete('search', $(inputElement).val());
- });
- ajaxHandler.setCallback(function(response) {
- loadingElement.hide();
-
- if (response && response.result != 'error') {
-
- inputElement.autocomplete({
- source: response,
- minLength: 0,
- select: function(event, ui){
- event.preventDefault();
- $(inputElement).val(ui.item.value);
- }
- });
- }
-
- inputElement.click(function (e) {
- $(inputElement).autocomplete('search', $(inputElement).val());
- });
- });
- ajaxHandler.send();
- }
- };
-
- var alterMatchesList = function(select, persist){
- var oldMatch;
- var type = $(select).find("option:selected").attr("data-type");
- var matchSelector = $(select).parents(".segment-input").siblings(".metricMatchBlock").find("select");
- if(persist === true){
- oldMatch = matchSelector.find("option:selected").val();
- } else {
- oldMatch = "";
- }
-
- if(type === "dimension" || type === "metric"){
- matchSelector.empty();
- var optionsHtml = "";
- for(var key in self.availableMatches[type]){
- optionsHtml += '<option value="'+key+'">'+self.availableMatches[type][key]+'</option>';
- }
- }
-
- matchSelector.append(optionsHtml);
-
- if (matchSelector.find('option[value="' + oldMatch + '"]').length) {
- matchSelector.val(oldMatch);
- } else {
- preselectFirstMetricMatch(matchSelector.parent());
- }
- };
-
- var getAddNewBlockButtonHtml = function()
- {
- if(typeof addNewBlockButton === "undefined")
- {
- var addNewBlockButton = self.editorTemplate.find("> div.segment-add-row").clone();
- }
- return addNewBlockButton.clone();
-
};
var getAddOrBlockButtonHtml = function(){
@@ -868,16 +522,6 @@ Segmentation = (function($) {
return addOrBlockButton.clone();
};
- var placeSegmentationFormControls = function(){
- doDragDropBindings();
- $(self.form).find(".scrollable").jScrollPane({
- showArrows: true,
- autoReinitialise: true,
- verticalArrowPositions: 'os',
- horizontalArrowPositions: 'os'
- });
- };
-
function openEditFormGivenSegment(option) {
var idsegment = option.attr("data-idsegment");
@@ -890,103 +534,6 @@ Segmentation = (function($) {
}
}
- var doDragDropBindings = function(){
- $(self.form).find(".segment-nav div > ul > li > ul > li").sortable({
- cursor: 'move',
- revert: 10,
- revertDuration: 0,
- snap: false,
- helper: 'clone',
- appendTo: 'body'
- });
-
- $(self.form).find(".metricListBlock").droppable({
- hoverClass: "hovered",
- drop: function( event, ui ) {
- $(this).find("select").val(ui.draggable.parent().attr("data-metric")).trigger("change");
- }
- });
-
- $(self.form).find(".segment-add-row > div").droppable({
- hoverClass: "hovered",
- drop: function( event, ui ) {
- $(this).find("a").trigger("click", [ui.draggable.parent().attr("data-metric")]);
- if($(this).find("a > span").length == 0){
- revokeInitialStateRows();
- appendSpecifiedRowHtml([ui.draggable.parent().attr("data-metric")]);
- }
- }
- });
-
- $(self.form).find(".segment-add-or > div").droppable({
- hoverClass: "hovered",
- drop: function( event, ui ) {
- $(this).find("a").trigger("click", [ui.draggable.parent().attr("data-metric")]);
- }
- });
- };
-
- var searchSegments = function(search){
- // pre-process search string to normalized form
- search = normalizeSearchString(search);
-
- // ---
- // clear all previous search highlights and hide all categories
- // to allow further showing only matching ones, while others remain invisible
- clearSearchMetricHighlight();
- $(self.form).find('.segment-nav div > ul > li').hide();
- var curStr = "";
- var curMetric = "";
-
- // 1 - do most obvious selection -> mark whole categories matching search string
- // also expand whole category
- $(self.form).find('.segment-nav div > ul > li').each( function(){
- curStr = normalizeSearchString($(this).find("a.metric_category").text());
- if(curStr.indexOf(search) > -1) {
- $(this).addClass("searchFound");
- $(this).find("ul").show();
- $(this).find("li").show();
- $(this).show();
- }
- }
- );
-
- // 2 - among all unselected categories find metrics which match and mark parent as search result
- $(self.form).find(".segment-nav div > ul > li:not(.searchFound)").each(function(){
- var parent = this;
- $(this).find("li").each( function(){
- var curStr = normalizeSearchString($(this).text());
- var curMetric = normalizeSearchString($(this).attr("data-metric"));
- $(this).hide();
- if(curStr.indexOf(search) > -1 || curMetric.indexOf(search) > -1){
- $(this).show();
- $(parent).find("ul").show();
- $(parent).addClass("searchFound").show();
- }
- });
- });
-
- if( $(self.form).find("li.searchFound").length == 0)
- {
- $(self.form).find("div > ul").prepend('<li class="no_results"><a>'+self.translations['General_SearchNoResults']+'</a></li>').show();
- }
- // check if search allow flag was revoked - then clear all search results
- if(self.searchAllowed == false)
- {
- clearSearchMetricHighlight();
- self.searchAllowed = true;
- }
-
- };
-
- var clearSearchMetricHighlight = function(){
- $(self.form).find('.no_results').remove();
- $(self.form).find('.segment-nav div > ul > li').removeClass("searchFound").show();
- $(self.form).find('.segment-nav div > ul > li').removeClass("others").show();
- $(self.form).find('.segment-nav div > ul > li > ul > li').show();
- $(self.form).find('.segment-nav div > ul > li > ul').hide();
- };
-
var normalizeSearchString = function(search){
search = search.replace(/^\s+|\s+$/g, ''); // trim
search = search.toLowerCase();
@@ -1012,6 +559,7 @@ Segmentation = (function($) {
}
// remove any remaining forms
+
self.form = getFormHtml();
self.target.prepend(self.form);
@@ -1025,13 +573,8 @@ Segmentation = (function($) {
self.form.addClass('anchorRight');
}
- placeSegmentationFormControls();
-
- // needs to be done before jQuery selects are built, as angular compiler screws up the selected values
- piwikHelper.compileAngularComponents(self.target);
-
if(mode == "edit") {
- $(self.form).find('.enable_all_users_select > option[value="' + segment.enable_all_users + '"]').prop("selected",true);
+ var userSelector = $(self.form).find('.enable_all_users_select > option[value="' + segment.enable_all_users + '"]').prop("selected",true);
// Replace "Visible to me" by "Visible to $login" when user is super user
if(hasSuperUserAccessAndSegmentCreatedByAnotherUser(segment)) {
@@ -1040,6 +583,9 @@ Segmentation = (function($) {
$(self.form).find('.visible_to_website_select > option[value="'+segment.enable_only_idsite+'"]').prop("selected",true);
$(self.form).find('.auto_archive_select > option[value="'+segment.auto_archive+'"]').prop("selected",true);
+ if (segment.definition != ""){
+ self.form.find('[piwik-segment-generator]').attr('segment-definition', segment.definition);
+ }
}
makeDropList(".enable_all_users" , ".enable_all_users_select");
@@ -1058,6 +604,8 @@ Segmentation = (function($) {
$(".segmentListContainer", self.target).hide();
self.target.closest('.segmentEditorPanel').addClass('editing');
+
+ piwikHelper.compileAngularComponents(self.target);
};
var closeForm = function () {
@@ -1065,34 +613,14 @@ Segmentation = (function($) {
self.target.closest('.segmentEditorPanel').removeClass('editing');
};
- var parseForm = function(){
- var segmentStr = "";
- $(self.form).find(".segment-rows").each( function(){
- var subSegmentStr = "";
-
- $(this).find(".segment-row").each( function(){
- if(subSegmentStr != ""){
- subSegmentStr += ","; // OR operator
- }
- $(this).find(".segment-row-inputs").each( function(){
- var metric = $(this).find(".metricList option:selected").val();
- var match = $(this).find(".metricMatchBlock > select option:selected").val();
- var value = $(this).find(".segment-input input").val();
- subSegmentStr += metric + match + encodeURIComponent(value);
- });
- });
- if(segmentStr != "")
- {
- segmentStr += ";"; // add AND operator between segment blocks
- }
- segmentStr += subSegmentStr;
- });
- return segmentStr
- };
+ function getSegmentGeneratorController()
+ {
+ return angular.element(self.form.find('.segment-generator')).scope().segmentGenerator;
+ }
var parseFormAndSave = function(){
var segmentName = $(self.form).find(".segment-content > h3 >span").text();
- var segmentStr = parseForm();
+ var segmentStr = getSegmentGeneratorController().getSegmentString();
var segmentId = $(self.form).find('.available_segments_select > option:selected').attr("data-idsegment");
var user = $(self.form).find(".enable_all_users_select option:selected").val();
var autoArchive = $(self.form).find(".auto_archive_select option:selected").val() || 0;
diff --git a/plugins/SegmentEditor/stylesheets/segmentation.less b/plugins/SegmentEditor/stylesheets/segmentation.less
index a4e291ed46..a38df86939 100644
--- a/plugins/SegmentEditor/stylesheets/segmentation.less
+++ b/plugins/SegmentEditor/stylesheets/segmentation.less
@@ -41,12 +41,11 @@
}
.metricMatchBlock {
- width: 120px;
margin-right: 11px;
}
.metricValueBlock {
- width: 352px;
+ width: 500px;
}
div.scrollable {
@@ -98,81 +97,16 @@ div.scrollable {
background: url(plugins/SegmentEditor/images/reset_search.png);
}
-.segment-element .segment-nav {
- position: absolute;
- top: 7px;
- left: 5px;
- bottom: 135px;
- float: left;
- width: 170px;
-}
-
-.segment-element .segment-nav h4 {
- font-size: 14px;
- font-weight: bold;
- padding: 0 0 8px 0;
-}
-
-.segment-element .segment-nav h4 a {
- color: #255792;
- text-decoration: none;
-}
-
-.segment-element .segment-nav div > ul {
- padding: 0 0 0 15px;
-}
-
-.segment-element .segment-nav div > ul > li {
- padding: 2px 0;
- line-height: 14px;
-}
-
-.segment-element .segment-nav div > ul > li li {
- padding: 1px;
- border-radius: 3px 3px 3px 3px;
-}
-
-.segment-element .segment-nav div > ul > li li:hover {
- padding: 0;
- border: 1px solid #cfccbd;
- border-bottom: 1px solid #7c7a72;
-}
-
-.segment-element .segment-nav div > ul > li li:hover a {
- cursor: move;
- padding: 1px 0 2px 8px;
- border-top: 1px solid #fff;
- background: #eae8e3 url(plugins/SegmentEditor/images/segment-move.png) 100% 50% no-repeat;
-}
-
-.segment-element .segment-nav div > ul > li li a {
- padding: 2px 0 2px 8px;
- font-weight: normal;
- display: block;
-}
-
-.segment-element .segment-nav div > ul > li ul {
- margin: 2px 0 -3px 10px;
-}
-
-.segment-element .segment-nav div > ul > li a {
- color: @theme-color-text-light;
- font-size: 11px;
- font-weight: bold;
- text-decoration: none;
- text-shadow: 0 1px 0 #fff;
-}
-
.segment-element .segment-content {
min-height: 300px;
- padding: 0 0 20px 203px;
+ padding: 0 0 20px 0;
}
.segment-element .segment-content h3 {
font-size: 16px;
font-weight: bold;
color: #505050;
- margin: 11px 0 0 0;
+ margin: 0 0 0 0;
text-shadow: 0 1px 0 #fff;
}
@@ -182,194 +116,10 @@ div.scrollable {
margin: -1px 0 0 0;
}
-.segment-element .segment-content .segment-rows {
- padding: 4px;
- margin: 0 3px 0 0;
- border: 1px solid #a9a399;
- border-radius: 3px 3px 3px 3px;
- position: relative;
- box-shadow: 0 12px 6px -10px rgba(0, 0, 0, 0.42);
-}
-
-.segment-element .segment-content .segment-add-row,
-.segment-element .segment-content .segment-add-or {
- font-size: 14px;
- font-weight: bold;
- background: @theme-color-background-contrast;
- color: #b9b9b9;
- text-align: center;
- position: relative;
-}
-
-.segment-element .segment-content .segment-add-row > div,
-.segment-element .segment-content .segment-add-or > div {
- border-radius: 4px;
- border: 2px dashed #fff;
- padding: 10px 0;
-}
-
-.segment-element .segment-content .segment-add-row > div a,
-.segment-element .segment-content .segment-add-or > div a {
- color: #b9b9b9;
- text-decoration: none;
-}
-
-.segment-element .segment-content .segment-add-row > div a span,
-.segment-element .segment-content .segment-add-or > div a span {
- color: #255792;
-}
-
-.segment-element .segment-content .segment-add-row {
- margin: 0 3px 0 0;
- padding: 0 12px;
- border: 1px solid #a9a399;
- border-radius: 3px 3px 3px 3px;
- box-shadow: 0 12px 6px -10px rgba(0, 0, 0, 0.42);
-}
-
-.segment-element .segment-content .segment-add-or {
- text-shadow: 0 1px 0 #fff;
- display: inline-block;
- width: 100%;
- padding: 0 1%;
- background: #efefeb;
- border-radius: 3px 3px 3px 3px;
-}
-
-.segment-element .segment-content .segment-add-or > div {
- border: 2px dashed #EFEFEB;
- background-color: #efefeb;
-}
-
-.segment-element .segment-content .segment-row {
- border-radius: 3px;
- display: inline-block;
- position: relative;
- width: 811px;
- padding: 12px 1%;
- background: #efefeb;
- padding: 7px 5px 0 5px;
-}
-
-.segment-element .segment-content .segment-row .segment-close {
- top: 15px;
- right: 6px;
- position: absolute;
- width: 15px;
- height: 15px;
- background: url(plugins/SegmentEditor/images/segment-close.png) 0 0 no-repeat;
-}
-
-.segment-element .segment-content .segment-row .segment-loading {
- display: none;
- top: 25px;
- right: 30px;
- position: absolute;
- width: 15px;
- height: 15px;
- background: url(plugins/Morpheus/images/loading-blue.gif) 0 0 no-repeat;
-}
-
-.segment-element .segment-content .segment-or {
- display: inline-block;
- margin: 0 0 0 6%;
- position: relative;
- background: #efefeb;
- padding: 5px 28px;
- color: #4f4f4f;
- font-weight: bold;
- font-size: 14px;
- text-shadow: 0 1px 0 #fff;
-}
-
-.segment-element .segment-content .segment-or:before,
-.segment-element .segment-content .segment-or:after {
- content: '';
- position: absolute;
- background: @theme-color-background-base;
- border: 1px solid #efefeb;
- width: 10px;
- top: -1px;
- bottom: -1px;
-}
-
-.segment-element .segment-content .segment-or:before {
- border-left: none;
- left: 0px;
- border-radius: 0 5px 5px 0;
-}
-
-.segment-element .segment-content .segment-or:after {
- border-right: none;
- right: 0px;
- border-radius: 5px 0 0 5px;
-}
-
.segment-element .edit_segment_name {
width: 200px;
}
-.segment-element .segment-content .segment-and {
- display: inline-block;
- margin: -1px 0 -1px 6%;
- z-index: 1;
- position: relative;
- background: @theme-color-background-contrast;
- padding: 5px 35px;
- color: #4f4f4f;
- font-size: 14px;
- font-weight: bold;
- text-shadow: 0 1px 0 #fff;
-}
-
-.segment-element .segment-content .segment-and:before,
-.segment-element .segment-content .segment-and:after {
- content: '';
- position: absolute;
- background: url(plugins/SegmentEditor/images/bg-inverted-corners.png);
- border: 1px solid #a9a399;
- width: 10px;
- top: 0px;
- bottom: 0px;
-}
-
-.segment-element .segment-content .segment-and:before {
- border-left: none;
- left: 0px;
- border-radius: 0 5px 5px 0;
-}
-
-.segment-element .segment-content .segment-and:after {
- border-right: none;
- right: 0px;
- border-radius: 5px 0 0 5px;
-}
-
-.segment-element .segment-content .segment-input {
- float: left;
- padding: 6px 0 5px 3px;
- border: 2px dashed #EFEFEB;
- margin-right: 3px;
-}
-
-.segment-element .segment-content .segment-input label {
- display: block;
- margin: 0 0 5px 0;
- font-size: 11px;
- color: #505050;
-}
-
-.segment-element .segment-content .segment-input select,
-.segment-element .segment-content .segment-input input {
- display: block;
- width: 96%;
- padding-top: 7px;
- padding-bottom: 7px;
-}
-
-.segment-element .segment-content .segment-input input {
- padding: 8px 2%;
-}
.segment-element .segment-top {
font-size: 11px;
@@ -627,7 +377,7 @@ a.metric_category {
.segment-element {
z-index: 999;
- width: 1040px;
+ width: 908px;
}
.segmentationSelectorContainer {
@@ -688,12 +438,6 @@ a.metric_category {
left: auto;
}
-.available_segments {
- display: inline-block;
- width: 150px;
- padding-left: 5px;
-}
-
.segmentationTitle,
.segment-element .segment-nav a.dropdown,
.segname {
diff --git a/plugins/SegmentEditor/templates/_segmentSelector.twig b/plugins/SegmentEditor/templates/_segmentSelector.twig
index 8a4ae950c8..ae9b9d6c16 100644
--- a/plugins/SegmentEditor/templates/_segmentSelector.twig
+++ b/plugins/SegmentEditor/templates/_segmentSelector.twig
@@ -32,103 +32,15 @@
</div>
</div>
- <div class="initial-state-rows">{# no space here important for jQuery #}<div class="segment-add-row initial"><div>
- <span>+ {{ 'SegmentEditor_DragDropCondition'|translate|raw }}</span>
- </div></div>
- <div class="segment-and">{{ 'SegmentEditor_OperatorAND'|translate|raw }}</div>
- <div class="segment-add-row initial"><div>
- <span>+ {{ 'SegmentEditor_DragDropCondition'|translate|raw }}</span>
- </div></div>
- </div>
-
- <div class="segment-row-inputs">
- <div class="segment-input metricListBlock">
- <select title="{{ 'SegmentEditor_ChooseASegment'|translate }}" class="metricList browser-default">
- {% for category,segmentsInCategory in segmentsByCategory %}
- <optgroup label="{{ category }}">
- {% for segmentInCategory in segmentsInCategory %}
- <option data-type="{{ segmentInCategory.type }}" value="{{ segmentInCategory.segment }}">{{ segmentInCategory.name }}</option>
- {% endfor %}
- </optgroup>
- {% endfor %}
- </select>
- </div>
- <div class="segment-input metricMatchBlock">
- <select title="{{ 'General_Matches'|translate }}" class="browser-default">
- <option value="==">{{ 'General_OperationEquals'|translate }}</option>
- <option value="!=">{{ 'General_OperationNotEquals'|translate }}</option>
- <option value="<=">{{ 'General_OperationAtMost'|translate }}</option>
- <option value=">=">{{ 'General_OperationAtLeast'|translate }}</option>
- <option value="<">{{ 'General_OperationLessThan'|translate }}</option>
- <option value=">">{{ 'General_OperationGreaterThan'|translate }}</option>
- <option value="=@">{{ 'General_OperationContains'|translate }}</option>
- <option value="!@">{{ 'General_OperationDoesNotContain'|translate }}</option>
- <option value="=^">{{ 'General_OperationStartsWith'|translate }}</option>
- <option value="=$">{{ 'General_OperationEndsWith'|translate }}</option>
- </select>
- </div>
- <div class="segment-input metricValueBlock">
- <input type="text" class="browser-default" title="{{ 'General_Value'|translate }}">
- </div>
- <div class="clear"></div>
- </div>
- <div class="segment-rows">
- <div class="segment-row">
- <a href="#" class="segment-close"></a>
- <a href="#" class="segment-loading"></a>
- </div>
- </div>
- <div class="segment-or">{{ 'SegmentEditor_OperatorOR'|translate }}</div>
- <div class="segment-add-or"><div>
- {% set orCondition %}<span>{{ 'SegmentEditor_OperatorOR'|translate }}</span>{% endset %}
- <a href="#"> + {{ 'SegmentEditor_AddANDorORCondition'|translate(orCondition)|raw }} </a>
- </div>
- </div>
- <div class="segment-and">{{ 'SegmentEditor_OperatorAND'|translate }}</div>
- <div class="segment-add-row"><div>
- {% set andCondition %}<span>{{ 'SegmentEditor_OperatorAND'|translate }}</span>{% endset %}
- <a href="#">+ {{ 'SegmentEditor_AddANDorORCondition'|translate(andCondition)|raw }}</a>
- </div>
- </div>
<div class="segment-element borderedControl expanded">
- <div class="segment-nav">
- <h4 class="visits">
- <span class="icon-segment"></span><span class="available_segments"><strong>
- <select class="available_segments_select browser-default"></select>
- </strong></span></h4>
-
- <div class="custom_select_search">
- <a href="#"></a>
- <input type="text" aria-haspopup="true" aria-autocomplete="list" role="textbox" autocomplete="off" class="inp ui-autocomplete-input segmentSearch browser-default" value="{{ 'General_Search'|translate }}" length="15">
- </div>
- <div class="scrollable">
- <ul>
- {% for category,segmentsInCategory in segmentsByCategory %}
- <li data="visit">
- <a class="metric_category" href="#">{{ category }}</a>
- <ul style="display:none;">
- {% for segmentInCategory in segmentsInCategory %}
- {% set title = segmentInCategory.name %}
- {% if segmentInCategory.unionOfSegments is defined and segmentInCategory.unionOfSegments %}
- {% set title = 'SegmentEditor_SegmentXIsAUnionOf'|translate(title) %}
- {% for unionSegment in segmentInCategory.unionOfSegments %}
- {% set title = title ~ ' ' ~ unionSegment %}
- {% if not loop.last %}
- {% set title = title ~ ',' %}
- {% endif %}
- {% endfor %}
- {% endif %}
- <li data-metric="{{ segmentInCategory.segment }}" title="{{ title|e('html_attr') }}"><a class="ddmetric" href="#">{{ segmentInCategory.name }}</a></li>
- {% endfor %}
- </ul>
- </li>
- {% endfor %}
- </ul>
- </div>
- </div>
<div class="segment-content">
<div class="segment-top" {% if not isSuperUser %}style="display:none"{% endif %}>
+
+ <span class="icon-segment"></span><span class="available_segments"><strong>
+ <select class="available_segments_select browser-default"></select>
+ </strong></span>
+
{{ 'SegmentEditor_ThisSegmentIsVisibleTo'|translate }} <span class="enable_all_users"><strong>
<select class="enable_all_users_select">
<option selected="1" value="0">{{ 'SegmentEditor_VisibleToMe'|translate }}</option>
@@ -152,7 +64,7 @@
</strong></span>
</div>
- <h3>{{ 'General_Name'|translate }}: <span class="segmentName"></span> <a class="editSegmentName" href="#">{{ 'General_Edit'|translate|lower }}</a></h3>
+ <h3 style="margin: 12px 6px;">{{ 'General_Name'|translate }}: <span class="segmentName"></span> <a class="editSegmentName" href="#">{{ 'General_Edit'|translate|lower }}</a></h3>
</div>
<div class="segment-footer">
<div piwik-rate-feature title="Segment Editor" style="display:inline-block;float: left;margin-top: 2px;margin-right: 10px;"></div>
diff --git a/plugins/SegmentEditor/tests/Integration/SegmentEditorTest.php b/plugins/SegmentEditor/tests/Integration/SegmentEditorTest.php
index 2635db94f2..37ba8ffb65 100644
--- a/plugins/SegmentEditor/tests/Integration/SegmentEditorTest.php
+++ b/plugins/SegmentEditor/tests/Integration/SegmentEditorTest.php
@@ -200,7 +200,7 @@ class SegmentEditorTest extends IntegrationTestCase
$timestampProperties = array('ts_last_edit', 'ts_created');
foreach ($timestampProperties as $propertyName) {
if (isset($segmentInfo[$propertyName])) {
- $segmentInfo[$propertyName] = substr($segmentInfo[$propertyName], 0, strlen($segmentInfo[$propertyName] - 2));
+ $segmentInfo[$propertyName] = substr($segmentInfo[$propertyName], 0, strlen($segmentInfo[$propertyName]) - 2);
}
}
}