diff options
author | Thomas Steur <tsteur@users.noreply.github.com> | 2017-10-03 23:22:01 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-10-03 23:22:01 +0300 |
commit | 9af4e95aa976f3a6533e95b776b5298f73e5f916 (patch) | |
tree | d612cd4d32019e9e52ce1398b8bf214ec06a8e0f /plugins/SegmentEditor | |
parent | 359c3ec875b554c7b71a933b26d18cdde0bb8f4e (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')
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:'<span>' + ('SegmentEditor_OperatorOR'|translate) + '</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:'<span>' + segmentGenerator.andConditionLabel + '</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, '"')+'" 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); } } } |