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:
authordizzy <diosmosis@users.noreply.github.com>2022-03-31 16:20:54 +0300
committerGitHub <noreply@github.com>2022-03-31 16:20:54 +0300
commit19489d103c6b4e91a745492e7ccffd957c3dcb4f (patch)
tree5bfcf0bbb6d2725876a01935f35500e7380e1595
parent09e1412dcd0e142c760eb5447af254a817b7b056 (diff)
[Vue] migrate segment generator directive to Vue (#18993)
* start migrating segment generator directive * get to build * remove some TODO * rebuilt * get UI tests to pass * fix ng-model handling * remote todo * built vue files
-rw-r--r--plugins/PrivacyManager/tests/UI/PrivacyManager_spec.js2
-rw-r--r--plugins/SegmentEditor/SegmentEditor.php5
-rw-r--r--plugins/SegmentEditor/angularjs/segment-generator/segmentgenerator.controller.js112
-rw-r--r--plugins/SegmentEditor/angularjs/segment-generator/segmentgenerator.model.js (renamed from plugins/SegmentEditor/angularjs/segment-generator/segmentgenerator-model.js)0
-rw-r--r--plugins/SegmentEditor/tests/UI/SegmentSelectorEditor_spec.js15
-rw-r--r--plugins/SegmentEditor/vue/dist/SegmentEditor.umd.js957
-rw-r--r--plugins/SegmentEditor/vue/dist/SegmentEditor.umd.min.js14
-rw-r--r--plugins/SegmentEditor/vue/dist/umd.metadata.json6
-rw-r--r--plugins/SegmentEditor/vue/src/SegmentGenerator/SegmentGenerator.adapter.ts91
-rw-r--r--plugins/SegmentEditor/vue/src/SegmentGenerator/SegmentGenerator.less (renamed from plugins/SegmentEditor/angularjs/segment-generator/segmentgenerator.directive.less)0
-rw-r--r--plugins/SegmentEditor/vue/src/SegmentGenerator/SegmentGenerator.store.ts94
-rw-r--r--plugins/SegmentEditor/vue/src/SegmentGenerator/SegmentGenerator.vue523
-rw-r--r--plugins/SegmentEditor/vue/src/SegmentGenerator/ValueInput.vue38
-rw-r--r--plugins/SegmentEditor/vue/src/index.ts12
-rw-r--r--plugins/SegmentEditor/vue/src/types.ts30
15 files changed, 1781 insertions, 118 deletions
diff --git a/plugins/PrivacyManager/tests/UI/PrivacyManager_spec.js b/plugins/PrivacyManager/tests/UI/PrivacyManager_spec.js
index a13e37bf35..2f43775a4c 100644
--- a/plugins/PrivacyManager/tests/UI/PrivacyManager_spec.js
+++ b/plugins/PrivacyManager/tests/UI/PrivacyManager_spec.js
@@ -80,7 +80,7 @@ describe("PrivacyManager", function () {
$(this).val(theVal).change();
});
}, value);
- await page.waitForTimeout(100);
+ await page.waitForTimeout(200);
}
async function selectVisitColumn(title)
diff --git a/plugins/SegmentEditor/SegmentEditor.php b/plugins/SegmentEditor/SegmentEditor.php
index c22ba33bda..e3c9e16ea9 100644
--- a/plugins/SegmentEditor/SegmentEditor.php
+++ b/plugins/SegmentEditor/SegmentEditor.php
@@ -294,15 +294,12 @@ 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";
+ $stylesheets[] = "plugins/SegmentEditor/vue/src/SegmentGenerator/SegmentGenerator.less";
}
/**
diff --git a/plugins/SegmentEditor/angularjs/segment-generator/segmentgenerator.controller.js b/plugins/SegmentEditor/angularjs/segment-generator/segmentgenerator.controller.js
index b3bcca5e68..1726d44c38 100644
--- a/plugins/SegmentEditor/angularjs/segment-generator/segmentgenerator.controller.js
+++ b/plugins/SegmentEditor/angularjs/segment-generator/segmentgenerator.controller.js
@@ -9,71 +9,8 @@
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 = decodeURIComponent(metric.substr(minPos+1));
- } else {
- newMetric.segment = metric.substr(0,minPos);
- newMetric.matches = metric.substr(minPos,2);
- newMetric.value = decodeURIComponent(metric.substr(minPos+2));
- }
- // if value is only "" -> change to empty string
- if(newMetric.value === '""')
- {
- newMetric.value = "";
- }
- }
-
- try {
- // Decode again to deal with double-encoded segments in database
- newMetric.value = decodeURIComponent(newMetric.value);
- } catch (e) {
- // Expected if the segment was not double-encoded
- }
- 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');
@@ -149,13 +86,13 @@
orCondition.isLoading = true;
this.updateSegmentDefinition();
-
+
var inputElement = $('.orCondId' + orCondition.id + " .metricValueBlock input");
inputElement.autocomplete({
source: [],
minLength: 0
});
-
+
var resolved = false;
var promise = piwikApi.fetch({
@@ -230,51 +167,6 @@
};
this.getSegmentString = function () {
- var segmentStr = '';
-
- angular.forEach(this.conditions, function (conditions) {
- var subSegmentStr = '';
-
- angular.forEach(conditions.orConditions, function (orCondition){
- if (subSegmentStr !== ''){
- subSegmentStr += ","; // OR operator
- }
-
- // one encode for urldecode on value, one encode for urldecode on condition
- subSegmentStr += orCondition.segment + orCondition.matches + encodeURIComponent(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++) {
- 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 () {
diff --git a/plugins/SegmentEditor/angularjs/segment-generator/segmentgenerator-model.js b/plugins/SegmentEditor/angularjs/segment-generator/segmentgenerator.model.js
index 5db3eeab64..5db3eeab64 100644
--- a/plugins/SegmentEditor/angularjs/segment-generator/segmentgenerator-model.js
+++ b/plugins/SegmentEditor/angularjs/segment-generator/segmentgenerator.model.js
diff --git a/plugins/SegmentEditor/tests/UI/SegmentSelectorEditor_spec.js b/plugins/SegmentEditor/tests/UI/SegmentSelectorEditor_spec.js
index cb183b952c..e5a05fcf4a 100644
--- a/plugins/SegmentEditor/tests/UI/SegmentSelectorEditor_spec.js
+++ b/plugins/SegmentEditor/tests/UI/SegmentSelectorEditor_spec.js
@@ -114,14 +114,16 @@ describe("SegmentSelectorEditorTest", function () {
it("should save a new segment and add it to the segment list when the form is filled out and the save button is clicked", async function() {
await page.evaluate(function () {
- $('.metricValueBlock input').each(function (index) {
- $(this).val('value ' + index).change();
+ $('.metricValueBlock input').each(function (index, elem) {
+ $(elem).val('value ' + index).change();
});
});
await page.type('input.edit_segment_name', 'new segment');
await page.click('.segmentRow0 .segment-or'); // click somewhere else to save new name
+ await page.waitForTimeout(200);
+
await page.evaluate(function () {
$('button.saveAndApply').click();
});
@@ -164,8 +166,10 @@ describe("SegmentSelectorEditorTest", function () {
});
});
+ await page.waitFor(200);
+
await page.evaluate(function () {
- $('button.saveAndApply').click();
+ $('button.saveAndApply').click();
});
await page.waitForSelector('.modal.open');
await page.waitForTimeout(500); // animation to show confirm
@@ -289,6 +293,9 @@ describe("SegmentSelectorEditorTest", function () {
var complexValue = 's#2&#--_*+?# #5"\'&<>.22,3';
$('.segmentRow1 .metricValueBlock input').val(complexValue).change();
});
+
+ await page.waitFor(200);
+
await page.evaluate(function () {
$('button.saveAndApply').click();
});
@@ -331,6 +338,8 @@ describe("SegmentSelectorEditorTest", function () {
console.log(dialog.message());
});
+ await page.waitFor(200);
+
await page.evaluate(function () {
$('button.saveAndApply').click();
});
diff --git a/plugins/SegmentEditor/vue/dist/SegmentEditor.umd.js b/plugins/SegmentEditor/vue/dist/SegmentEditor.umd.js
new file mode 100644
index 0000000000..25b9c0f751
--- /dev/null
+++ b/plugins/SegmentEditor/vue/dist/SegmentEditor.umd.js
@@ -0,0 +1,957 @@
+(function webpackUniversalModuleDefinition(root, factory) {
+ if(typeof exports === 'object' && typeof module === 'object')
+ module.exports = factory(require("CoreHome"), require("vue"), require("CorePluginsAdmin"));
+ else if(typeof define === 'function' && define.amd)
+ define(["CoreHome", , "CorePluginsAdmin"], factory);
+ else if(typeof exports === 'object')
+ exports["SegmentEditor"] = factory(require("CoreHome"), require("vue"), require("CorePluginsAdmin"));
+ else
+ root["SegmentEditor"] = factory(root["CoreHome"], root["Vue"], root["CorePluginsAdmin"]);
+})((typeof self !== 'undefined' ? self : this), function(__WEBPACK_EXTERNAL_MODULE__19dc__, __WEBPACK_EXTERNAL_MODULE__8bbf__, __WEBPACK_EXTERNAL_MODULE_a5a2__) {
+return /******/ (function(modules) { // webpackBootstrap
+/******/ // The module cache
+/******/ var installedModules = {};
+/******/
+/******/ // The require function
+/******/ function __webpack_require__(moduleId) {
+/******/
+/******/ // Check if module is in cache
+/******/ if(installedModules[moduleId]) {
+/******/ return installedModules[moduleId].exports;
+/******/ }
+/******/ // Create a new module (and put it into the cache)
+/******/ var module = installedModules[moduleId] = {
+/******/ i: moduleId,
+/******/ l: false,
+/******/ exports: {}
+/******/ };
+/******/
+/******/ // Execute the module function
+/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+/******/
+/******/ // Flag the module as loaded
+/******/ module.l = true;
+/******/
+/******/ // Return the exports of the module
+/******/ return module.exports;
+/******/ }
+/******/
+/******/
+/******/ // expose the modules object (__webpack_modules__)
+/******/ __webpack_require__.m = modules;
+/******/
+/******/ // expose the module cache
+/******/ __webpack_require__.c = installedModules;
+/******/
+/******/ // define getter function for harmony exports
+/******/ __webpack_require__.d = function(exports, name, getter) {
+/******/ if(!__webpack_require__.o(exports, name)) {
+/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
+/******/ }
+/******/ };
+/******/
+/******/ // define __esModule on exports
+/******/ __webpack_require__.r = function(exports) {
+/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
+/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
+/******/ }
+/******/ Object.defineProperty(exports, '__esModule', { value: true });
+/******/ };
+/******/
+/******/ // create a fake namespace object
+/******/ // mode & 1: value is a module id, require it
+/******/ // mode & 2: merge all properties of value into the ns
+/******/ // mode & 4: return value when already ns object
+/******/ // mode & 8|1: behave like require
+/******/ __webpack_require__.t = function(value, mode) {
+/******/ if(mode & 1) value = __webpack_require__(value);
+/******/ if(mode & 8) return value;
+/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
+/******/ var ns = Object.create(null);
+/******/ __webpack_require__.r(ns);
+/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
+/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
+/******/ return ns;
+/******/ };
+/******/
+/******/ // getDefaultExport function for compatibility with non-harmony modules
+/******/ __webpack_require__.n = function(module) {
+/******/ var getter = module && module.__esModule ?
+/******/ function getDefault() { return module['default']; } :
+/******/ function getModuleExports() { return module; };
+/******/ __webpack_require__.d(getter, 'a', getter);
+/******/ return getter;
+/******/ };
+/******/
+/******/ // Object.prototype.hasOwnProperty.call
+/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
+/******/
+/******/ // __webpack_public_path__
+/******/ __webpack_require__.p = "plugins/SegmentEditor/vue/dist/";
+/******/
+/******/
+/******/ // Load entry module and return exports
+/******/ return __webpack_require__(__webpack_require__.s = "fae3");
+/******/ })
+/************************************************************************/
+/******/ ({
+
+/***/ "19dc":
+/***/ (function(module, exports) {
+
+module.exports = __WEBPACK_EXTERNAL_MODULE__19dc__;
+
+/***/ }),
+
+/***/ "8bbf":
+/***/ (function(module, exports) {
+
+module.exports = __WEBPACK_EXTERNAL_MODULE__8bbf__;
+
+/***/ }),
+
+/***/ "a5a2":
+/***/ (function(module, exports) {
+
+module.exports = __WEBPACK_EXTERNAL_MODULE_a5a2__;
+
+/***/ }),
+
+/***/ "fae3":
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+// ESM COMPAT FLAG
+__webpack_require__.r(__webpack_exports__);
+
+// EXPORTS
+__webpack_require__.d(__webpack_exports__, "SegmentGeneratorStore", function() { return /* reexport */ SegmentGenerator_store; });
+__webpack_require__.d(__webpack_exports__, "SegmentGenerator", function() { return /* reexport */ SegmentGenerator; });
+
+// CONCATENATED MODULE: ./node_modules/@vue/cli-service/lib/commands/build/setPublicPath.js
+// This file is imported into lib/wc client bundles.
+
+if (typeof window !== 'undefined') {
+ var currentScript = window.document.currentScript
+ if (false) { var getCurrentScript; }
+
+ var src = currentScript && currentScript.src.match(/(.+\/)[^/]+\.js(\?.*)?$/)
+ if (src) {
+ __webpack_require__.p = src[1] // eslint-disable-line
+ }
+}
+
+// Indicate to webpack that this file can be concatenated
+/* harmony default export */ var setPublicPath = (null);
+
+// EXTERNAL MODULE: external {"commonjs":"vue","commonjs2":"vue","root":"Vue"}
+var external_commonjs_vue_commonjs2_vue_root_Vue_ = __webpack_require__("8bbf");
+
+// EXTERNAL MODULE: external "CoreHome"
+var external_CoreHome_ = __webpack_require__("19dc");
+
+// CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-babel/node_modules/cache-loader/dist/cjs.js??ref--12-0!./node_modules/@vue/cli-plugin-babel/node_modules/thread-loader/dist/cjs.js!./node_modules/babel-loader/lib!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/templateLoader.js??ref--6!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--0-1!./plugins/SegmentEditor/vue/src/SegmentGenerator/SegmentGenerator.vue?vue&type=template&id=3e4a5f7a
+
+var _hoisted_1 = {
+ class: "segment-generator",
+ ref: "root"
+};
+var _hoisted_2 = {
+ class: "segment-rows"
+};
+var _hoisted_3 = {
+ class: "segment-row"
+};
+var _hoisted_4 = ["onClick"];
+var _hoisted_5 = {
+ href: "#",
+ class: "segment-loading"
+};
+var _hoisted_6 = {
+ class: "segment-row-inputs valign-wrapper"
+};
+var _hoisted_7 = {
+ class: "segment-input metricListBlock valign-wrapper"
+};
+var _hoisted_8 = {
+ style: {
+ "width": "100%"
+ }
+};
+var _hoisted_9 = {
+ class: "segment-input metricMatchBlock valign-wrapper"
+};
+var _hoisted_10 = {
+ style: {
+ "display": "inline-block"
+ }
+};
+var _hoisted_11 = {
+ class: "segment-input metricValueBlock valign-wrapper"
+};
+var _hoisted_12 = {
+ class: "form-group row",
+ style: {
+ "width": "100%"
+ }
+};
+var _hoisted_13 = {
+ class: "input-field col s12"
+};
+
+var _hoisted_14 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", {
+ role: "status",
+ "aria-live": "polite",
+ class: "ui-helper-hidden-accessible"
+}, null, -1);
+
+var _hoisted_15 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", {
+ class: "clear"
+}, null, -1);
+
+var _hoisted_16 = {
+ class: "segment-or"
+};
+var _hoisted_17 = ["onClick"];
+var _hoisted_18 = ["innerHTML"];
+var _hoisted_19 = {
+ class: "segment-and"
+};
+var _hoisted_20 = ["innerHTML"];
+function render(_ctx, _cache, $props, $setup, $data, $options) {
+ var _this = this;
+
+ var _component_ActivityIndicator = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["resolveComponent"])("ActivityIndicator");
+
+ var _component_Field = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["resolveComponent"])("Field");
+
+ var _component_ValueInput = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["resolveComponent"])("ValueInput");
+
+ return Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("div", _hoisted_1, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_ActivityIndicator, {
+ loading: _ctx.isLoading
+ }, null, 8, ["loading"]), (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(true), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])(external_commonjs_vue_commonjs2_vue_root_Vue_["Fragment"], null, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["renderList"])(_ctx.conditions, function (condition, conditionIndex) {
+ return Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("div", {
+ class: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["normalizeClass"])("segmentRow".concat(conditionIndex)),
+ key: conditionIndex
+ }, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_2, [(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(true), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])(external_commonjs_vue_commonjs2_vue_root_Vue_["Fragment"], null, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["renderList"])(condition.orConditions, function (orCondition, orConditionIndex) {
+ var _ctx$segments$orCondi, _ctx$segments$orCondi2;
+
+ return Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("div", {
+ class: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["normalizeClass"])("orCondId".concat(orCondition.id)),
+ key: orConditionIndex
+ }, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_3, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("a", {
+ class: "segment-close",
+ onClick: function onClick($event) {
+ return _ctx.removeOrCondition(condition, orCondition);
+ }
+ }, null, 8, _hoisted_4), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("a", _hoisted_5, null, 512), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.conditionValuesLoading[orCondition.id]]]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_6, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_7, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_8, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_Field, {
+ uicontrol: "expandable-select",
+ name: "segments",
+ "model-value": orCondition.segment,
+ "onUpdate:modelValue": function onUpdateModelValue($event) {
+ orCondition.segment = $event;
+
+ _ctx.updateAutocomplete(orCondition);
+
+ _ctx.computeSegmentDefinition();
+ },
+ title: (_ctx$segments$orCondi = _ctx.segments[orCondition.segment]) === null || _ctx$segments$orCondi === void 0 ? void 0 : _ctx$segments$orCondi.name,
+ "full-width": true,
+ options: _ctx.segmentList
+ }, null, 8, ["model-value", "onUpdate:modelValue", "title", "options"])])]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_9, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_10, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_Field, {
+ uicontrol: "select",
+ name: "matchType",
+ "model-value": orCondition.matches,
+ "onUpdate:modelValue": function onUpdateModelValue($event) {
+ orCondition.matches = $event;
+
+ _ctx.computeSegmentDefinition();
+ },
+ "full-width": true,
+ options: _ctx.matches[(_ctx$segments$orCondi2 = _ctx.segments[orCondition.segment]) === null || _ctx$segments$orCondi2 === void 0 ? void 0 : _ctx$segments$orCondi2.type]
+ }, null, 8, ["model-value", "onUpdate:modelValue", "options"])])]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_11, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_12, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_13, [_hoisted_14, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_ValueInput, {
+ or: orCondition,
+ onUpdate: function onUpdate($event) {
+ orCondition.value = $event; // deep watch doesn't catch this change
+
+ _this.computeSegmentDefinition();
+ }
+ }, null, 8, ["or", "onUpdate"])])])]), _hoisted_15])]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_16, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('SegmentEditor_OperatorOR')), 1)], 2);
+ }), 128)), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", {
+ class: "segment-add-or",
+ onClick: function onClick($event) {
+ return _ctx.addNewOrCondition(condition);
+ }
+ }, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", null, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("a", {
+ innerHTML: _ctx.$sanitize(_ctx.addNewOrConditionLinkText)
+ }, null, 8, _hoisted_18)])], 8, _hoisted_17)]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_19, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('SegmentEditor_OperatorAND')), 1)], 2);
+ }), 128)), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", {
+ class: "segment-add-row initial",
+ onClick: _cache[0] || (_cache[0] = function ($event) {
+ return _ctx.addNewAndCondition();
+ })
+ }, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", null, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("a", {
+ innerHTML: _ctx.$sanitize(_ctx.addNewAndConditionLinkText)
+ }, null, 8, _hoisted_20)])])], 512);
+}
+// CONCATENATED MODULE: ./plugins/SegmentEditor/vue/src/SegmentGenerator/SegmentGenerator.vue?vue&type=template&id=3e4a5f7a
+
+// EXTERNAL MODULE: external "CorePluginsAdmin"
+var external_CorePluginsAdmin_ = __webpack_require__("a5a2");
+
+// CONCATENATED MODULE: ./plugins/SegmentEditor/vue/src/SegmentGenerator/SegmentGenerator.store.ts
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
+
+function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
+
+function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
+
+/*!
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+
+
+var SegmentGenerator_store_SegmentGeneratorStore = /*#__PURE__*/function () {
+ function SegmentGeneratorStore() {
+ var _this = this;
+
+ _classCallCheck(this, SegmentGeneratorStore);
+
+ _defineProperty(this, "privateState", Object(external_commonjs_vue_commonjs2_vue_root_Vue_["reactive"])({
+ isLoading: false,
+ segments: []
+ }));
+
+ _defineProperty(this, "state", Object(external_commonjs_vue_commonjs2_vue_root_Vue_["computed"])(function () {
+ return Object(external_commonjs_vue_commonjs2_vue_root_Vue_["readonly"])(_this.privateState);
+ }));
+
+ _defineProperty(this, "loadSegmentsAbort", void 0);
+
+ _defineProperty(this, "loadSegmentsPromise", void 0);
+
+ _defineProperty(this, "fetchedSiteId", void 0);
+ }
+
+ _createClass(SegmentGeneratorStore, [{
+ key: "loadSegments",
+ value: function loadSegments(siteId, visitSegmentsOnly) {
+ var _this2 = this;
+
+ if (this.loadSegmentsAbort) {
+ this.loadSegmentsAbort.abort();
+ this.loadSegmentsAbort = undefined;
+ }
+
+ this.privateState.isLoading = true;
+
+ if (this.fetchedSiteId !== siteId) {
+ this.loadSegmentsAbort = undefined;
+ this.fetchedSiteId = siteId;
+ }
+
+ if (!this.loadSegmentsPromise) {
+ var idSites = undefined;
+ var idSite = undefined;
+
+ if (siteId === 'all' || !siteId) {
+ idSites = 'all';
+ idSite = 'all';
+ } else if (siteId) {
+ idSites = siteId;
+ idSite = siteId;
+ }
+
+ this.loadSegmentsAbort = new AbortController();
+ this.loadSegmentsPromise = external_CoreHome_["AjaxHelper"].fetch({
+ method: 'API.getSegmentsMetadata',
+ filter_limit: '-1',
+ _hideImplementationData: 0,
+ idSites: idSites,
+ idSite: idSite
+ });
+ }
+
+ return this.loadSegmentsPromise.then(function (response) {
+ _this2.privateState.isLoading = false;
+
+ if (response) {
+ if (visitSegmentsOnly) {
+ _this2.privateState.segments = response.filter(function (s) {
+ return s.sqlSegment && s.sqlSegment.match(/log_visit\./);
+ });
+ } else {
+ _this2.privateState.segments = response;
+ }
+ }
+
+ return _this2.state.value.segments;
+ }).finally(function () {
+ _this2.privateState.isLoading = false;
+ });
+ }
+ }]);
+
+ return SegmentGeneratorStore;
+}();
+
+/* harmony default export */ var SegmentGenerator_store = (new SegmentGenerator_store_SegmentGeneratorStore());
+// CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-babel/node_modules/cache-loader/dist/cjs.js??ref--12-0!./node_modules/@vue/cli-plugin-babel/node_modules/thread-loader/dist/cjs.js!./node_modules/babel-loader/lib!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/templateLoader.js??ref--6!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--0-1!./plugins/SegmentEditor/vue/src/SegmentGenerator/ValueInput.vue?vue&type=template&id=17b64c0b
+
+var ValueInputvue_type_template_id_17b64c0b_hoisted_1 = ["placeholder", "title", "value"];
+function ValueInputvue_type_template_id_17b64c0b_render(_ctx, _cache, $props, $setup, $data, $options) {
+ return Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("input", {
+ placeholder: _ctx.translate('General_Value'),
+ type: "text",
+ class: "autocomplete",
+ title: _ctx.translate('General_Value'),
+ autocomplete: "off",
+ value: _ctx.or.value,
+ onKeydown: _cache[0] || (_cache[0] = function ($event) {
+ return _ctx.onKeydownOrConditionValue($event);
+ }),
+ onChange: _cache[1] || (_cache[1] = function ($event) {
+ return _ctx.onKeydownOrConditionValue($event);
+ })
+ }, null, 40, ValueInputvue_type_template_id_17b64c0b_hoisted_1);
+}
+// CONCATENATED MODULE: ./plugins/SegmentEditor/vue/src/SegmentGenerator/ValueInput.vue?vue&type=template&id=17b64c0b
+
+// CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-typescript/node_modules/cache-loader/dist/cjs.js??ref--14-0!./node_modules/babel-loader/lib!./node_modules/@vue/cli-plugin-typescript/node_modules/ts-loader??ref--14-2!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--0-1!./plugins/SegmentEditor/vue/src/SegmentGenerator/ValueInput.vue?vue&type=script&lang=ts
+
+
+/* harmony default export */ var ValueInputvue_type_script_lang_ts = (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["defineComponent"])({
+ props: {
+ or: Object
+ },
+ created: function created() {
+ this.onKeydownOrConditionValue = Object(external_CoreHome_["debounce"])(this.onKeydownOrConditionValue, 50);
+ },
+ emits: ['update'],
+ methods: {
+ onKeydownOrConditionValue: function onKeydownOrConditionValue(event) {
+ this.$emit('update', event.target.value);
+ }
+ }
+}));
+// CONCATENATED MODULE: ./plugins/SegmentEditor/vue/src/SegmentGenerator/ValueInput.vue?vue&type=script&lang=ts
+
+// CONCATENATED MODULE: ./plugins/SegmentEditor/vue/src/SegmentGenerator/ValueInput.vue
+
+
+
+ValueInputvue_type_script_lang_ts.render = ValueInputvue_type_template_id_17b64c0b_render
+
+/* harmony default export */ var ValueInput = (ValueInputvue_type_script_lang_ts);
+// CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-typescript/node_modules/cache-loader/dist/cjs.js??ref--14-0!./node_modules/babel-loader/lib!./node_modules/@vue/cli-plugin-typescript/node_modules/ts-loader??ref--14-2!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--0-1!./plugins/SegmentEditor/vue/src/SegmentGenerator/SegmentGenerator.vue?vue&type=script&lang=ts
+
+
+
+
+
+
+function initialMatches() {
+ return {
+ metric: [{
+ key: '==',
+ value: Object(external_CoreHome_["translate"])('General_OperationEquals')
+ }, {
+ key: '!=',
+ value: Object(external_CoreHome_["translate"])('General_OperationNotEquals')
+ }, {
+ key: '<=',
+ value: Object(external_CoreHome_["translate"])('General_OperationAtMost')
+ }, {
+ key: '>=',
+ value: Object(external_CoreHome_["translate"])('General_OperationAtLeast')
+ }, {
+ key: '<',
+ value: Object(external_CoreHome_["translate"])('General_OperationLessThan')
+ }, {
+ key: '>',
+ value: Object(external_CoreHome_["translate"])('General_OperationGreaterThan')
+ }],
+ dimension: [{
+ key: '==',
+ value: Object(external_CoreHome_["translate"])('General_OperationIs')
+ }, {
+ key: '!=',
+ value: Object(external_CoreHome_["translate"])('General_OperationIsNot')
+ }, {
+ key: '=@',
+ value: Object(external_CoreHome_["translate"])('General_OperationContains')
+ }, {
+ key: '!@',
+ value: Object(external_CoreHome_["translate"])('General_OperationDoesNotContain')
+ }, {
+ key: '=^',
+ value: Object(external_CoreHome_["translate"])('General_OperationStartsWith')
+ }, {
+ key: '=$',
+ value: Object(external_CoreHome_["translate"])('General_OperationEndsWith')
+ }]
+ };
+}
+
+function generateUniqueId() {
+ var id = '';
+ var chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
+
+ for (var i = 1; i <= 10; i += 1) {
+ id += chars.charAt(Math.floor(Math.random() * chars.length));
+ }
+
+ return id;
+}
+
+function findAndExplodeByMatch(metric) {
+ var matches = ['==', '!=', '<=', '>=', '=@', '!@', '<', '>', '=^', '=$'];
+ var newMetric = {};
+ var minPos = metric.length;
+ var match;
+ var index;
+ var singleChar = false;
+
+ for (var key = 0; key < matches.length; key += 1) {
+ 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 = decodeURIComponent(metric.substr(minPos + 1));
+ } else {
+ newMetric.segment = metric.substr(0, minPos);
+ newMetric.matches = metric.substr(minPos, 2);
+ newMetric.value = decodeURIComponent(metric.substr(minPos + 2));
+ } // if value is only '' -> change to empty string
+
+
+ if (newMetric.value === '""') {
+ newMetric.value = '';
+ }
+ }
+
+ try {
+ // Decode again to deal with double-encoded segments in database
+ newMetric.value = decodeURIComponent(newMetric.value);
+ } catch (e) {// Expected if the segment was not double-encoded
+ }
+
+ return newMetric;
+}
+
+function stripTags(text) {
+ return text ? "".concat(text).replace(/(<([^>]+)>)/ig, '') : text;
+}
+
+var _window = window,
+ $ = _window.$;
+/* harmony default export */ var SegmentGeneratorvue_type_script_lang_ts = (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["defineComponent"])({
+ props: {
+ addInitialCondition: Boolean,
+ visitSegmentsOnly: Boolean,
+ idsite: [String, Number],
+ modelValue: {
+ type: String,
+ default: ''
+ }
+ },
+ components: {
+ ActivityIndicator: external_CoreHome_["ActivityIndicator"],
+ Field: external_CorePluginsAdmin_["Field"],
+ ValueInput: ValueInput
+ },
+ data: function data() {
+ return {
+ conditions: [],
+ queriedSegments: [],
+ matches: initialMatches(),
+ conditionValuesLoading: {},
+ segmentDefinition: ''
+ };
+ },
+ emits: ['update:modelValue'],
+ watch: {
+ modelValue: function modelValue(newVal) {
+ if (newVal !== this.segmentDefinition) {
+ this.setSegmentString(newVal);
+ }
+ },
+ conditions: {
+ deep: true,
+ handler: function handler() {
+ this.computeSegmentDefinition();
+ }
+ },
+ segmentDefinition: function segmentDefinition(newVal) {
+ if (newVal !== this.modelValue) {
+ this.$emit('update:modelValue', newVal);
+ }
+ },
+ idsite: function idsite(newVal) {
+ this.reloadSegments(newVal, this.visitSegmentsOnly);
+ }
+ },
+ created: function created() {
+ this.matches[''] = this.matches.dimension;
+ this.setSegmentString(this.modelValue);
+ this.segmentDefinition = this.modelValue;
+ this.reloadSegments(this.idsite, this.visitSegmentsOnly);
+ },
+ methods: {
+ reloadSegments: function reloadSegments(idsite, visitSegmentsOnly) {
+ var _this = this;
+
+ SegmentGenerator_store.loadSegments(idsite, visitSegmentsOnly).then(function (segments) {
+ _this.queriedSegments = segments.map(function (s) {
+ return Object.assign(Object.assign({}, s), {}, {
+ category: s.category || 'Others'
+ });
+ });
+
+ if (_this.addInitialCondition && _this.conditions.length === 0) {
+ _this.addNewAndCondition();
+ }
+ });
+ },
+ addAndCondition: function addAndCondition(condition) {
+ this.conditions.push(condition);
+ },
+ addNewOrCondition: function addNewOrCondition(condition) {
+ var orCondition = {
+ segment: this.firstSegment,
+ matches: this.firstMatch,
+ value: ''
+ };
+ this.addOrCondition(condition, orCondition);
+ },
+ addOrCondition: function addOrCondition(condition, orCondition) {
+ var _this2 = this;
+
+ this.conditionValuesLoading[orCondition.id] = false;
+ orCondition.id = generateUniqueId();
+ condition.orConditions.push(orCondition);
+ Object(external_commonjs_vue_commonjs2_vue_root_Vue_["nextTick"])(function () {
+ _this2.updateAutocomplete(orCondition);
+ });
+ },
+ updateAutocomplete: function updateAutocomplete(orCondition) {
+ var _this3 = this;
+
+ this.conditionValuesLoading[orCondition.id] = true;
+ $(".orCondId".concat(orCondition.id, " .metricValueBlock input"), this.$refs.root).autocomplete({
+ source: [],
+ minLength: 0
+ });
+ var abortController = new AbortController();
+ var resolved = false;
+ external_CoreHome_["AjaxHelper"].fetch({
+ module: 'API',
+ format: 'json',
+ method: 'API.getSuggestedValuesForSegment',
+ segmentName: orCondition.segment
+ }).then(function (response) {
+ _this3.conditionValuesLoading[orCondition.id] = false;
+ resolved = true;
+ var inputElement = $(".orCondId".concat(orCondition.id, " .metricValueBlock input")).autocomplete({
+ source: response,
+ minLength: 0,
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ select: function select(event, ui) {
+ event.preventDefault();
+ orCondition.value = ui.item.value;
+
+ _this3.computeSegmentDefinition(); // deep watch doesn't catch this change
+
+
+ _this3.$forceUpdate();
+ }
+ }).off('click').click(function () {
+ $(inputElement).autocomplete('search', orCondition.value);
+ });
+ }).catch(function () {
+ resolved = true;
+ _this3.conditionValuesLoading[orCondition.id] = false;
+ $(".orCondId".concat(orCondition.id, " .metricValueBlock input")).autocomplete({
+ source: [],
+ minLength: 0
+ }).autocomplete('search', orCondition.value);
+ });
+ setTimeout(function () {
+ if (!resolved) {
+ abortController.abort();
+ }
+ }, 20000);
+ },
+ removeOrCondition: function removeOrCondition(condition, orCondition) {
+ var index = condition.orConditions.indexOf(orCondition);
+
+ if (index > -1) {
+ condition.orConditions.splice(index, 1);
+ }
+
+ if (condition.orConditions.length === 0) {
+ var andCondIndex = this.conditions.indexOf(condition);
+
+ if (index > -1) {
+ this.conditions.splice(andCondIndex, 1);
+ }
+ }
+ },
+ setSegmentString: function setSegmentString(segmentStr) {
+ var _this4 = this;
+
+ this.conditions = [];
+
+ if (!segmentStr) {
+ return;
+ }
+
+ var blocks = segmentStr.split(';').map(function (b) {
+ return b.split(',');
+ });
+ this.conditions = blocks.map(function (block) {
+ var condition = {
+ orConditions: []
+ };
+ block.forEach(function (innerBlock) {
+ var orCondition = findAndExplodeByMatch(innerBlock);
+
+ _this4.addOrCondition(condition, orCondition);
+ });
+ return condition;
+ });
+ },
+ addNewAndCondition: function addNewAndCondition() {
+ var condition = {
+ orConditions: []
+ };
+ this.addAndCondition(condition);
+ this.addNewOrCondition(condition);
+ return condition;
+ },
+ // NOTE: can't use a computed property since we need to recompute on changes inside the
+ // structure. don't have to if we don't do in-place changes, but with nested structures,
+ // that's complicated.
+ computeSegmentDefinition: function computeSegmentDefinition() {
+ var segmentStr = '';
+ this.conditions.forEach(function (condition) {
+ if (!condition.orConditions.length) {
+ return;
+ }
+
+ var subSegmentStr = '';
+ condition.orConditions.forEach(function (orCondition) {
+ if (!orCondition.value && !orCondition.segment && !orCondition.matches) {
+ return;
+ }
+
+ if (subSegmentStr !== '') {
+ subSegmentStr += ','; // OR operator
+ } // one encode for urldecode on value, one encode for urldecode on condition
+
+
+ var value = encodeURIComponent(encodeURIComponent(orCondition.value));
+ subSegmentStr += "".concat(orCondition.segment).concat(orCondition.matches).concat(value);
+ });
+
+ if (segmentStr !== '') {
+ segmentStr += ';'; // add AND operator between segment blocks
+ }
+
+ segmentStr += subSegmentStr;
+ });
+ this.segmentDefinition = segmentStr;
+ }
+ },
+ computed: {
+ firstSegment: function firstSegment() {
+ return this.queriedSegments[0].segment;
+ },
+ firstMatch: function firstMatch() {
+ var segment = this.queriedSegments[0];
+
+ if (!segment) {
+ return null;
+ }
+
+ if (segment.type && this.matches[segment.type]) {
+ return this.matches[segment.type][0].key;
+ }
+
+ return this.matches[''][0].key;
+ },
+ segments: function segments() {
+ var result = {};
+ this.queriedSegments.forEach(function (s) {
+ result[s.segment] = s;
+ });
+ return result;
+ },
+ segmentList: function segmentList() {
+ return this.queriedSegments.map(function (s) {
+ return {
+ group: s.category,
+ key: s.segment,
+ value: s.name,
+ tooltip: s.acceptedValues ? stripTags(s.acceptedValues) : undefined
+ };
+ });
+ },
+ addNewOrConditionLinkText: function addNewOrConditionLinkText() {
+ return "+".concat(Object(external_CoreHome_["translate"])('SegmentEditor_AddANDorORCondition', "<span>".concat(Object(external_CoreHome_["translate"])('SegmentEditor_OperatorOR'), "</span>")));
+ },
+ andConditionLabel: function andConditionLabel() {
+ return this.conditions.length ? Object(external_CoreHome_["translate"])('SegmentEditor_OperatorAND') : '';
+ },
+ addNewAndConditionLinkText: function addNewAndConditionLinkText() {
+ return "+".concat(Object(external_CoreHome_["translate"])('SegmentEditor_AddANDorORCondition', "<span>".concat(this.andConditionLabel, "</span>")));
+ },
+ isLoading: function isLoading() {
+ return SegmentGenerator_store.state.value.isLoading;
+ }
+ }
+}));
+// CONCATENATED MODULE: ./plugins/SegmentEditor/vue/src/SegmentGenerator/SegmentGenerator.vue?vue&type=script&lang=ts
+
+// CONCATENATED MODULE: ./plugins/SegmentEditor/vue/src/SegmentGenerator/SegmentGenerator.vue
+
+
+
+SegmentGeneratorvue_type_script_lang_ts.render = render
+
+/* harmony default export */ var SegmentGenerator = (SegmentGeneratorvue_type_script_lang_ts);
+// CONCATENATED MODULE: ./plugins/SegmentEditor/vue/src/SegmentGenerator/SegmentGenerator.adapter.ts
+/*!
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+
+
+/* harmony default export */ var SegmentGenerator_adapter = (Object(external_CoreHome_["createAngularJsAdapter"])({
+ component: SegmentGenerator,
+ require: '?ngModel',
+ scope: {
+ segmentDefinition: {
+ angularJsBind: '@',
+ vue: 'modelValue'
+ },
+ addInitialCondition: {
+ angularJsBind: '=',
+ transform: external_CoreHome_["transformAngularJsBoolAttr"]
+ },
+ visitSegmentsOnly: {
+ angularJsBind: '=',
+ transform: external_CoreHome_["transformAngularJsBoolAttr"]
+ },
+ idsite: {
+ angularJsBind: '='
+ }
+ },
+ directiveName: 'piwikSegmentGenerator',
+ $inject: ['$timeout'],
+ events: {
+ 'update:modelValue': function updateModelValue(newValue, vm, scope, element, attrs, ngModel, $timeout) {
+ var currentValue = ngModel ? ngModel.$viewValue : scope.segmentDefinition;
+
+ if (newValue !== currentValue) {
+ $timeout(function () {
+ if (!ngModel) {
+ scope.segmentDefinition = newValue;
+ return;
+ } // ngModel being used
+
+
+ ngModel.$setViewValue(newValue);
+ ngModel.$render(); // not detected by the watch for some reason
+ });
+ }
+ }
+ },
+ postCreate: function postCreate(vm, scope, element, attrs, controller) {
+ // methods to forward for BC
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ element.scope().segmentGenerator = {
+ getSegmentString: function getSegmentString() {
+ return vm.modelValue;
+ }
+ };
+ var ngModel = controller;
+
+ if (!ngModel) {
+ scope.$watch('segmentDefinition', function (newVal) {
+ if (newVal !== vm.modelValue) {
+ Object(external_commonjs_vue_commonjs2_vue_root_Vue_["nextTick"])(function () {
+ vm.modelValue = newVal;
+ });
+ }
+ });
+ return;
+ } // ngModel being used
+
+
+ ngModel.$render = function () {
+ Object(external_commonjs_vue_commonjs2_vue_root_Vue_["nextTick"])(function () {
+ vm.modelValue = Object(external_CoreHome_["removeAngularJsSpecificProperties"])(ngModel.$viewValue);
+ });
+ };
+
+ if (typeof scope.segmentDefinition !== 'undefined') {
+ ngModel.$setViewValue(scope.segmentDefinition);
+ } else {
+ ngModel.$setViewValue(vm.modelValue);
+ }
+ }
+}));
+// CONCATENATED MODULE: ./plugins/SegmentEditor/vue/src/types.ts
+/*!
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+// CONCATENATED MODULE: ./plugins/SegmentEditor/vue/src/index.ts
+/*!
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+
+
+
+// CONCATENATED MODULE: ./node_modules/@vue/cli-service/lib/commands/build/entry-lib-no-default.js
+
+
+
+
+/***/ })
+
+/******/ });
+});
+//# sourceMappingURL=SegmentEditor.umd.js.map \ No newline at end of file
diff --git a/plugins/SegmentEditor/vue/dist/SegmentEditor.umd.min.js b/plugins/SegmentEditor/vue/dist/SegmentEditor.umd.min.js
new file mode 100644
index 0000000000..38e66de7f3
--- /dev/null
+++ b/plugins/SegmentEditor/vue/dist/SegmentEditor.umd.min.js
@@ -0,0 +1,14 @@
+(function(e,t){"object"===typeof exports&&"object"===typeof module?module.exports=t(require("CoreHome"),require("vue"),require("CorePluginsAdmin")):"function"===typeof define&&define.amd?define(["CoreHome",,"CorePluginsAdmin"],t):"object"===typeof exports?exports["SegmentEditor"]=t(require("CoreHome"),require("vue"),require("CorePluginsAdmin")):e["SegmentEditor"]=t(e["CoreHome"],e["Vue"],e["CorePluginsAdmin"])})("undefined"!==typeof self?self:this,(function(e,t,n){return function(e){var t={};function n(o){if(t[o])return t[o].exports;var i=t[o]={i:o,l:!1,exports:{}};return e[o].call(i.exports,i,i.exports,n),i.l=!0,i.exports}return n.m=e,n.c=t,n.d=function(e,t,o){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:o})},n.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"===typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var i in e)n.d(o,i,function(t){return e[t]}.bind(null,i));return o},n.n=function(e){var t=e&&e.__esModule?function(){return e["default"]}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="plugins/SegmentEditor/vue/dist/",n(n.s="fae3")}({"19dc":function(t,n){t.exports=e},"8bbf":function(e,n){e.exports=t},a5a2:function(e,t){e.exports=n},fae3:function(e,t,n){"use strict";if(n.r(t),n.d(t,"SegmentGeneratorStore",(function(){return B})),n.d(t,"SegmentGenerator",(function(){return F})),"undefined"!==typeof window){var o=window.document.currentScript,i=o&&o.src.match(/(.+\/)[^/]+\.js(\?.*)?$/);i&&(n.p=i[1])}var a=n("8bbf"),r=n("19dc"),c={class:"segment-generator",ref:"root"},l={class:"segment-rows"},s={class:"segment-row"},d=["onClick"],u={href:"#",class:"segment-loading"},m={class:"segment-row-inputs valign-wrapper"},f={class:"segment-input metricListBlock valign-wrapper"},p={style:{width:"100%"}},g={class:"segment-input metricMatchBlock valign-wrapper"},v={style:{display:"inline-block"}},h={class:"segment-input metricValueBlock valign-wrapper"},b={class:"form-group row",style:{width:"100%"}},O={class:"input-field col s12"},j=Object(a["createElementVNode"])("span",{role:"status","aria-live":"polite",class:"ui-helper-hidden-accessible"},null,-1),S=Object(a["createElementVNode"])("div",{class:"clear"},null,-1),V={class:"segment-or"},y=["onClick"],C=["innerHTML"],w={class:"segment-and"},k=["innerHTML"];function A(e,t,n,o,i,r){var A=this,N=Object(a["resolveComponent"])("ActivityIndicator"),E=Object(a["resolveComponent"])("Field"),L=Object(a["resolveComponent"])("ValueInput");return Object(a["openBlock"])(),Object(a["createElementBlock"])("div",c,[Object(a["createVNode"])(N,{loading:e.isLoading},null,8,["loading"]),(Object(a["openBlock"])(!0),Object(a["createElementBlock"])(a["Fragment"],null,Object(a["renderList"])(e.conditions,(function(t,n){return Object(a["openBlock"])(),Object(a["createElementBlock"])("div",{class:Object(a["normalizeClass"])("segmentRow".concat(n)),key:n},[Object(a["createElementVNode"])("div",l,[(Object(a["openBlock"])(!0),Object(a["createElementBlock"])(a["Fragment"],null,Object(a["renderList"])(t.orConditions,(function(n,o){var i,r;return Object(a["openBlock"])(),Object(a["createElementBlock"])("div",{class:Object(a["normalizeClass"])("orCondId".concat(n.id)),key:o},[Object(a["createElementVNode"])("div",s,[Object(a["createElementVNode"])("a",{class:"segment-close",onClick:function(o){return e.removeOrCondition(t,n)}},null,8,d),Object(a["withDirectives"])(Object(a["createElementVNode"])("a",u,null,512),[[a["vShow"],e.conditionValuesLoading[n.id]]]),Object(a["createElementVNode"])("div",m,[Object(a["createElementVNode"])("div",f,[Object(a["createElementVNode"])("div",p,[Object(a["createVNode"])(E,{uicontrol:"expandable-select",name:"segments","model-value":n.segment,"onUpdate:modelValue":function(t){n.segment=t,e.updateAutocomplete(n),e.computeSegmentDefinition()},title:null===(i=e.segments[n.segment])||void 0===i?void 0:i.name,"full-width":!0,options:e.segmentList},null,8,["model-value","onUpdate:modelValue","title","options"])])]),Object(a["createElementVNode"])("div",g,[Object(a["createElementVNode"])("div",v,[Object(a["createVNode"])(E,{uicontrol:"select",name:"matchType","model-value":n.matches,"onUpdate:modelValue":function(t){n.matches=t,e.computeSegmentDefinition()},"full-width":!0,options:e.matches[null===(r=e.segments[n.segment])||void 0===r?void 0:r.type]},null,8,["model-value","onUpdate:modelValue","options"])])]),Object(a["createElementVNode"])("div",h,[Object(a["createElementVNode"])("div",b,[Object(a["createElementVNode"])("div",O,[j,Object(a["createVNode"])(L,{or:n,onUpdate:function(e){n.value=e,A.computeSegmentDefinition()}},null,8,["or","onUpdate"])])])]),S])]),Object(a["createElementVNode"])("div",V,Object(a["toDisplayString"])(e.translate("SegmentEditor_OperatorOR")),1)],2)})),128)),Object(a["createElementVNode"])("div",{class:"segment-add-or",onClick:function(n){return e.addNewOrCondition(t)}},[Object(a["createElementVNode"])("div",null,[Object(a["createElementVNode"])("a",{innerHTML:e.$sanitize(e.addNewOrConditionLinkText)},null,8,C)])],8,y)]),Object(a["createElementVNode"])("div",w,Object(a["toDisplayString"])(e.translate("SegmentEditor_OperatorAND")),1)],2)})),128)),Object(a["createElementVNode"])("div",{class:"segment-add-row initial",onClick:t[0]||(t[0]=function(t){return e.addNewAndCondition()})},[Object(a["createElementVNode"])("div",null,[Object(a["createElementVNode"])("a",{innerHTML:e.$sanitize(e.addNewAndConditionLinkText)},null,8,k)])])],512)}var N=n("a5a2");function E(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function L(e,t){for(var n=0;n<t.length;n++){var o=t[n];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(e,o.key,o)}}function _(e,t,n){return t&&L(e.prototype,t),n&&L(e,n),e}function D(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}
+/*!
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */var x=function(){function e(){var t=this;E(this,e),D(this,"privateState",Object(a["reactive"])({isLoading:!1,segments:[]})),D(this,"state",Object(a["computed"])((function(){return Object(a["readonly"])(t.privateState)}))),D(this,"loadSegmentsAbort",void 0),D(this,"loadSegmentsPromise",void 0),D(this,"fetchedSiteId",void 0)}return _(e,[{key:"loadSegments",value:function(e,t){var n=this;if(this.loadSegmentsAbort&&(this.loadSegmentsAbort.abort(),this.loadSegmentsAbort=void 0),this.privateState.isLoading=!0,this.fetchedSiteId!==e&&(this.loadSegmentsAbort=void 0,this.fetchedSiteId=e),!this.loadSegmentsPromise){var o=void 0,i=void 0;"all"!==e&&e?e&&(o=e,i=e):(o="all",i="all"),this.loadSegmentsAbort=new AbortController,this.loadSegmentsPromise=r["AjaxHelper"].fetch({method:"API.getSegmentsMetadata",filter_limit:"-1",_hideImplementationData:0,idSites:o,idSite:i})}return this.loadSegmentsPromise.then((function(e){return n.privateState.isLoading=!1,e&&(n.privateState.segments=t?e.filter((function(e){return e.sqlSegment&&e.sqlSegment.match(/log_visit\./)})):e),n.state.value.segments})).finally((function(){n.privateState.isLoading=!1}))}}]),e}(),B=new x,I=["placeholder","title","value"];function G(e,t,n,o,i,r){return Object(a["openBlock"])(),Object(a["createElementBlock"])("input",{placeholder:e.translate("General_Value"),type:"text",class:"autocomplete",title:e.translate("General_Value"),autocomplete:"off",value:e.or.value,onKeydown:t[0]||(t[0]=function(t){return e.onKeydownOrConditionValue(t)}),onChange:t[1]||(t[1]=function(t){return e.onKeydownOrConditionValue(t)})},null,40,I)}var P=Object(a["defineComponent"])({props:{or:Object},created:function(){this.onKeydownOrConditionValue=Object(r["debounce"])(this.onKeydownOrConditionValue,50)},emits:["update"],methods:{onKeydownOrConditionValue:function(e){this.$emit("update",e.target.value)}}});P.render=G;var $=P;function T(){return{metric:[{key:"==",value:Object(r["translate"])("General_OperationEquals")},{key:"!=",value:Object(r["translate"])("General_OperationNotEquals")},{key:"<=",value:Object(r["translate"])("General_OperationAtMost")},{key:">=",value:Object(r["translate"])("General_OperationAtLeast")},{key:"<",value:Object(r["translate"])("General_OperationLessThan")},{key:">",value:Object(r["translate"])("General_OperationGreaterThan")}],dimension:[{key:"==",value:Object(r["translate"])("General_OperationIs")},{key:"!=",value:Object(r["translate"])("General_OperationIsNot")},{key:"=@",value:Object(r["translate"])("General_OperationContains")},{key:"!@",value:Object(r["translate"])("General_OperationDoesNotContain")},{key:"=^",value:Object(r["translate"])("General_OperationStartsWith")},{key:"=$",value:Object(r["translate"])("General_OperationEndsWith")}]}}function q(){for(var e="",t="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",n=1;n<=10;n+=1)e+=t.charAt(Math.floor(Math.random()*t.length));return e}function M(e){for(var t,n,o=["==","!=","<=",">=","=@","!@","<",">","=^","=$"],i={},a=e.length,r=!1,c=0;c<o.length;c+=1)t=o[c],n=e.indexOf(t),-1!==n&&n<a&&(a=n,1===t.length&&(r=!0));a<e.length&&(!0===r?(i.segment=e.substr(0,a),i.matches=e.substr(a,1),i.value=decodeURIComponent(e.substr(a+1))):(i.segment=e.substr(0,a),i.matches=e.substr(a,2),i.value=decodeURIComponent(e.substr(a+2))),'""'===i.value&&(i.value=""));try{i.value=decodeURIComponent(i.value)}catch(l){}return i}function U(e){return e?"".concat(e).replace(/(<([^>]+)>)/gi,""):e}var H=window,R=H.$,J=Object(a["defineComponent"])({props:{addInitialCondition:Boolean,visitSegmentsOnly:Boolean,idsite:[String,Number],modelValue:{type:String,default:""}},components:{ActivityIndicator:r["ActivityIndicator"],Field:N["Field"],ValueInput:$},data:function(){return{conditions:[],queriedSegments:[],matches:T(),conditionValuesLoading:{},segmentDefinition:""}},emits:["update:modelValue"],watch:{modelValue:function(e){e!==this.segmentDefinition&&this.setSegmentString(e)},conditions:{deep:!0,handler:function(){this.computeSegmentDefinition()}},segmentDefinition:function(e){e!==this.modelValue&&this.$emit("update:modelValue",e)},idsite:function(e){this.reloadSegments(e,this.visitSegmentsOnly)}},created:function(){this.matches[""]=this.matches.dimension,this.setSegmentString(this.modelValue),this.segmentDefinition=this.modelValue,this.reloadSegments(this.idsite,this.visitSegmentsOnly)},methods:{reloadSegments:function(e,t){var n=this;B.loadSegments(e,t).then((function(e){n.queriedSegments=e.map((function(e){return Object.assign(Object.assign({},e),{},{category:e.category||"Others"})})),n.addInitialCondition&&0===n.conditions.length&&n.addNewAndCondition()}))},addAndCondition:function(e){this.conditions.push(e)},addNewOrCondition:function(e){var t={segment:this.firstSegment,matches:this.firstMatch,value:""};this.addOrCondition(e,t)},addOrCondition:function(e,t){var n=this;this.conditionValuesLoading[t.id]=!1,t.id=q(),e.orConditions.push(t),Object(a["nextTick"])((function(){n.updateAutocomplete(t)}))},updateAutocomplete:function(e){var t=this;this.conditionValuesLoading[e.id]=!0,R(".orCondId".concat(e.id," .metricValueBlock input"),this.$refs.root).autocomplete({source:[],minLength:0});var n=new AbortController,o=!1;r["AjaxHelper"].fetch({module:"API",format:"json",method:"API.getSuggestedValuesForSegment",segmentName:e.segment}).then((function(n){t.conditionValuesLoading[e.id]=!1,o=!0;var i=R(".orCondId".concat(e.id," .metricValueBlock input")).autocomplete({source:n,minLength:0,select:function(n,o){n.preventDefault(),e.value=o.item.value,t.computeSegmentDefinition(),t.$forceUpdate()}}).off("click").click((function(){R(i).autocomplete("search",e.value)}))})).catch((function(){o=!0,t.conditionValuesLoading[e.id]=!1,R(".orCondId".concat(e.id," .metricValueBlock input")).autocomplete({source:[],minLength:0}).autocomplete("search",e.value)})),setTimeout((function(){o||n.abort()}),2e4)},removeOrCondition:function(e,t){var n=e.orConditions.indexOf(t);if(n>-1&&e.orConditions.splice(n,1),0===e.orConditions.length){var o=this.conditions.indexOf(e);n>-1&&this.conditions.splice(o,1)}},setSegmentString:function(e){var t=this;if(this.conditions=[],e){var n=e.split(";").map((function(e){return e.split(",")}));this.conditions=n.map((function(e){var n={orConditions:[]};return e.forEach((function(e){var o=M(e);t.addOrCondition(n,o)})),n}))}},addNewAndCondition:function(){var e={orConditions:[]};return this.addAndCondition(e),this.addNewOrCondition(e),e},computeSegmentDefinition:function(){var e="";this.conditions.forEach((function(t){if(t.orConditions.length){var n="";t.orConditions.forEach((function(e){if(e.value||e.segment||e.matches){""!==n&&(n+=",");var t=encodeURIComponent(encodeURIComponent(e.value));n+="".concat(e.segment).concat(e.matches).concat(t)}})),""!==e&&(e+=";"),e+=n}})),this.segmentDefinition=e}},computed:{firstSegment:function(){return this.queriedSegments[0].segment},firstMatch:function(){var e=this.queriedSegments[0];return e?e.type&&this.matches[e.type]?this.matches[e.type][0].key:this.matches[""][0].key:null},segments:function(){var e={};return this.queriedSegments.forEach((function(t){e[t.segment]=t})),e},segmentList:function(){return this.queriedSegments.map((function(e){return{group:e.category,key:e.segment,value:e.name,tooltip:e.acceptedValues?U(e.acceptedValues):void 0}}))},addNewOrConditionLinkText:function(){return"+".concat(Object(r["translate"])("SegmentEditor_AddANDorORCondition","<span>".concat(Object(r["translate"])("SegmentEditor_OperatorOR"),"</span>")))},andConditionLabel:function(){return this.conditions.length?Object(r["translate"])("SegmentEditor_OperatorAND"):""},addNewAndConditionLinkText:function(){return"+".concat(Object(r["translate"])("SegmentEditor_AddANDorORCondition","<span>".concat(this.andConditionLabel,"</span>")))},isLoading:function(){return B.state.value.isLoading}}});J.render=A;var F=J;
+/*!
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */Object(r["createAngularJsAdapter"])({component:F,require:"?ngModel",scope:{segmentDefinition:{angularJsBind:"@",vue:"modelValue"},addInitialCondition:{angularJsBind:"=",transform:r["transformAngularJsBoolAttr"]},visitSegmentsOnly:{angularJsBind:"=",transform:r["transformAngularJsBoolAttr"]},idsite:{angularJsBind:"="}},directiveName:"piwikSegmentGenerator",$inject:["$timeout"],events:{"update:modelValue":function(e,t,n,o,i,a,r){var c=a?a.$viewValue:n.segmentDefinition;e!==c&&r((function(){a?(a.$setViewValue(e),a.$render()):n.segmentDefinition=e}))}},postCreate:function(e,t,n,o,i){n.scope().segmentGenerator={getSegmentString:function(){return e.modelValue}};var c=i;c?(c.$render=function(){Object(a["nextTick"])((function(){e.modelValue=Object(r["removeAngularJsSpecificProperties"])(c.$viewValue)}))},"undefined"!==typeof t.segmentDefinition?c.$setViewValue(t.segmentDefinition):c.$setViewValue(e.modelValue)):t.$watch("segmentDefinition",(function(t){t!==e.modelValue&&Object(a["nextTick"])((function(){e.modelValue=t}))}))}})}})}));
+//# sourceMappingURL=SegmentEditor.umd.min.js.map \ No newline at end of file
diff --git a/plugins/SegmentEditor/vue/dist/umd.metadata.json b/plugins/SegmentEditor/vue/dist/umd.metadata.json
new file mode 100644
index 0000000000..dce4477a3c
--- /dev/null
+++ b/plugins/SegmentEditor/vue/dist/umd.metadata.json
@@ -0,0 +1,6 @@
+{
+ "dependsOn": [
+ "CoreHome",
+ "CorePluginsAdmin"
+ ]
+} \ No newline at end of file
diff --git a/plugins/SegmentEditor/vue/src/SegmentGenerator/SegmentGenerator.adapter.ts b/plugins/SegmentEditor/vue/src/SegmentGenerator/SegmentGenerator.adapter.ts
new file mode 100644
index 0000000000..49642797f3
--- /dev/null
+++ b/plugins/SegmentEditor/vue/src/SegmentGenerator/SegmentGenerator.adapter.ts
@@ -0,0 +1,91 @@
+/*!
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+import { INgModelController, ITimeoutService } from 'angular';
+import { nextTick } from 'vue';
+import {
+ createAngularJsAdapter,
+ removeAngularJsSpecificProperties,
+ transformAngularJsBoolAttr,
+} from 'CoreHome';
+import SegmentGenerator from './SegmentGenerator.vue';
+
+export default createAngularJsAdapter<[ITimeoutService]>({
+ component: SegmentGenerator,
+ require: '?ngModel',
+ scope: {
+ segmentDefinition: {
+ angularJsBind: '@',
+ vue: 'modelValue',
+ },
+ addInitialCondition: {
+ angularJsBind: '=',
+ transform: transformAngularJsBoolAttr,
+ },
+ visitSegmentsOnly: {
+ angularJsBind: '=',
+ transform: transformAngularJsBoolAttr,
+ },
+ idsite: {
+ angularJsBind: '=',
+ },
+ },
+ directiveName: 'piwikSegmentGenerator',
+ $inject: ['$timeout'],
+ events: {
+ 'update:modelValue': (newValue, vm, scope, element, attrs, ngModel, $timeout) => {
+ const currentValue = ngModel ? ngModel.$viewValue : scope.segmentDefinition;
+ if (newValue !== currentValue) {
+ $timeout(() => {
+ if (!ngModel) {
+ scope.segmentDefinition = newValue;
+ return;
+ }
+
+ // ngModel being used
+ (ngModel as INgModelController).$setViewValue(newValue);
+ (ngModel as INgModelController).$render(); // not detected by the watch for some reason
+ });
+ }
+ },
+ },
+ postCreate(vm, scope, element, attrs, controller) {
+ // methods to forward for BC
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ (element.scope() as any).segmentGenerator = {
+ getSegmentString(): string {
+ return vm.modelValue;
+ },
+ };
+
+ const ngModel = controller as INgModelController;
+ if (!ngModel) {
+ scope.$watch('segmentDefinition', (newVal: unknown) => {
+ if (newVal !== vm.modelValue) {
+ nextTick(() => {
+ vm.modelValue = newVal;
+ });
+ }
+ });
+
+ return;
+ }
+
+ // ngModel being used
+ ngModel.$render = () => {
+ nextTick(() => {
+ vm.modelValue = removeAngularJsSpecificProperties(ngModel.$viewValue);
+ });
+ };
+
+ if (typeof scope.segmentDefinition !== 'undefined') {
+ (ngModel as INgModelController).$setViewValue(scope.segmentDefinition);
+ } else {
+ ngModel.$setViewValue(vm.modelValue);
+ }
+ },
+});
diff --git a/plugins/SegmentEditor/angularjs/segment-generator/segmentgenerator.directive.less b/plugins/SegmentEditor/vue/src/SegmentGenerator/SegmentGenerator.less
index 778021f446..778021f446 100644
--- a/plugins/SegmentEditor/angularjs/segment-generator/segmentgenerator.directive.less
+++ b/plugins/SegmentEditor/vue/src/SegmentGenerator/SegmentGenerator.less
diff --git a/plugins/SegmentEditor/vue/src/SegmentGenerator/SegmentGenerator.store.ts b/plugins/SegmentEditor/vue/src/SegmentGenerator/SegmentGenerator.store.ts
new file mode 100644
index 0000000000..46d68274d4
--- /dev/null
+++ b/plugins/SegmentEditor/vue/src/SegmentGenerator/SegmentGenerator.store.ts
@@ -0,0 +1,94 @@
+/*!
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+import {
+ reactive,
+ computed,
+ readonly,
+ DeepReadonly,
+} from 'vue';
+import { AjaxHelper } from 'CoreHome';
+import { SegmentMetadata } from '../types';
+
+interface SegmentGeneratorStoreState {
+ isLoading: boolean;
+ segments: SegmentMetadata[];
+}
+
+class SegmentGeneratorStore {
+ private privateState: SegmentGeneratorStoreState = reactive<SegmentGeneratorStoreState>({
+ isLoading: false,
+ segments: [],
+ });
+
+ readonly state = computed(() => readonly(this.privateState));
+
+ private loadSegmentsAbort?: AbortController;
+
+ private loadSegmentsPromise?: Promise<SegmentMetadata[]>;
+
+ private fetchedSiteId?: string|number;
+
+ loadSegments(
+ siteId?: string|number,
+ visitSegmentsOnly?: boolean,
+ ): Promise<DeepReadonly<SegmentMetadata[]>> {
+ if (this.loadSegmentsAbort) {
+ this.loadSegmentsAbort.abort();
+ this.loadSegmentsAbort = undefined;
+ }
+
+ this.privateState.isLoading = true;
+
+ if (this.fetchedSiteId !== siteId) {
+ this.loadSegmentsAbort = undefined;
+ this.fetchedSiteId = siteId;
+ }
+
+ if (!this.loadSegmentsPromise) {
+ let idSites: string|number|undefined = undefined;
+ let idSite: string|number|undefined = undefined;
+
+ if (siteId === 'all' || !siteId) {
+ idSites = 'all';
+ idSite = 'all';
+ } else if (siteId) {
+ idSites = siteId;
+ idSite = siteId;
+ }
+
+ this.loadSegmentsAbort = new AbortController();
+ this.loadSegmentsPromise = AjaxHelper.fetch<SegmentMetadata[]>({
+ method: 'API.getSegmentsMetadata',
+ filter_limit: '-1',
+ _hideImplementationData: 0,
+ idSites,
+ idSite,
+ });
+ }
+
+ return this.loadSegmentsPromise.then((response) => {
+ this.privateState.isLoading = false;
+
+ if (response) {
+ if (visitSegmentsOnly) {
+ this.privateState.segments = response.filter(
+ (s) => s.sqlSegment && s.sqlSegment.match(/log_visit\./),
+ );
+ } else {
+ this.privateState.segments = response;
+ }
+ }
+
+ return this.state.value.segments;
+ }).finally(() => {
+ this.privateState.isLoading = false;
+ });
+ }
+}
+
+export default new SegmentGeneratorStore();
diff --git a/plugins/SegmentEditor/vue/src/SegmentGenerator/SegmentGenerator.vue b/plugins/SegmentEditor/vue/src/SegmentGenerator/SegmentGenerator.vue
new file mode 100644
index 0000000000..7fd9b5db91
--- /dev/null
+++ b/plugins/SegmentEditor/vue/src/SegmentGenerator/SegmentGenerator.vue
@@ -0,0 +1,523 @@
+<!--
+ Matomo - free/libre analytics platform
+ @link https://matomo.org
+ @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+-->
+
+<template>
+ <div class="segment-generator" ref="root">
+ <ActivityIndicator :loading="isLoading" />
+ <div
+ :class="`segmentRow${conditionIndex}`"
+ v-for="(condition, conditionIndex) in conditions"
+ :key="conditionIndex"
+ >
+ <div class="segment-rows">
+ <div
+ :class="`orCondId${orCondition.id}`"
+ v-for="(orCondition, orConditionIndex) in condition.orConditions"
+ :key="orConditionIndex"
+ >
+ <div class="segment-row">
+ <a
+ class="segment-close"
+ @click="removeOrCondition(condition, orCondition)"
+ />
+ <a
+ href="#"
+ class="segment-loading"
+ v-show="conditionValuesLoading[orCondition.id]"
+ />
+ <div class="segment-row-inputs valign-wrapper">
+ <div class="segment-input metricListBlock valign-wrapper">
+ <div style="width: 100%;">
+ <Field
+ uicontrol="expandable-select"
+ name="segments"
+ :model-value="orCondition.segment"
+ @update:model-value="orCondition.segment = $event;
+ updateAutocomplete(orCondition); computeSegmentDefinition();"
+ :title="segments[orCondition.segment]?.name"
+ :full-width="true"
+ :options="segmentList"
+ >
+ </Field>
+ </div>
+ </div>
+ <div class="segment-input metricMatchBlock valign-wrapper">
+ <div style="display: inline-block">
+ <Field
+ uicontrol="select"
+ name="matchType"
+ :model-value="orCondition.matches"
+ @update:model-value="orCondition.matches = $event; computeSegmentDefinition();"
+ :full-width="true"
+ :options="matches[segments[orCondition.segment]?.type]"
+ >
+ </Field>
+ </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"
+ />
+ <ValueInput
+ :or="orCondition"
+ @update="orCondition.value = $event;
+ // deep watch doesn't catch this change
+ this.computeSegmentDefinition();"
+ />
+ </div>
+ </div>
+ </div>
+ <div class="clear" />
+ </div>
+ </div>
+ <div class="segment-or">{{ translate('SegmentEditor_OperatorOR') }}</div>
+ </div>
+ <div
+ class="segment-add-or"
+ @click="addNewOrCondition(condition)"
+ >
+ <div>
+ <a v-html="$sanitize(addNewOrConditionLinkText)" />
+ </div>
+ </div>
+ </div>
+ <div class="segment-and">{{ translate('SegmentEditor_OperatorAND') }}</div>
+ </div>
+ <div
+ class="segment-add-row initial"
+ @click="addNewAndCondition()"
+ >
+ <div>
+ <a v-html="$sanitize(addNewAndConditionLinkText)" />
+ </div>
+ </div>
+ </div>
+</template>
+
+<script lang="ts">
+import { DeepReadonly, defineComponent, nextTick } from 'vue';
+import {
+ translate,
+ AjaxHelper,
+ ActivityIndicator,
+} from 'CoreHome';
+import { Field } from 'CorePluginsAdmin';
+import SegmentGeneratorStore from './SegmentGenerator.store';
+import { SegmentAndCondition, SegmentMetadata, SegmentOrCondition } from '../types';
+import ValueInput from './ValueInput.vue';
+
+interface SegmentGeneratorState {
+ conditions: SegmentAndCondition[];
+ matches: Record<string, { key: string, value: string }[]>;
+ queriedSegments: DeepReadonly<SegmentMetadata[]>;
+ conditionValuesLoading: Record<string, boolean>;
+ segmentDefinition: string;
+}
+
+function initialMatches() {
+ return {
+ 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'),
+ },
+ ],
+ };
+}
+
+function generateUniqueId() {
+ let id = '';
+ const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
+
+ for (let i = 1; i <= 10; i += 1) {
+ id += chars.charAt(Math.floor(Math.random() * chars.length));
+ }
+
+ return id;
+}
+
+function findAndExplodeByMatch(metric: string) {
+ const matches = ['==', '!=', '<=', '>=', '=@', '!@', '<', '>', '=^', '=$'];
+ const newMetric: SegmentOrCondition = {} as unknown as SegmentOrCondition;
+ let minPos = metric.length;
+ let match;
+ let index: number;
+ let singleChar = false;
+
+ for (let key = 0; key < matches.length; key += 1) {
+ 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 = decodeURIComponent(metric.substr(minPos + 1));
+ } else {
+ newMetric.segment = metric.substr(0, minPos);
+ newMetric.matches = metric.substr(minPos, 2);
+ newMetric.value = decodeURIComponent(metric.substr(minPos + 2));
+ }
+
+ // if value is only '' -> change to empty string
+ if (newMetric.value === '""') {
+ newMetric.value = '';
+ }
+ }
+
+ try {
+ // Decode again to deal with double-encoded segments in database
+ newMetric.value = decodeURIComponent(newMetric.value);
+ } catch (e) {
+ // Expected if the segment was not double-encoded
+ }
+
+ return newMetric;
+}
+
+function stripTags(text?: unknown) {
+ return text ? `${text}`.replace(/(<([^>]+)>)/ig, '') : text;
+}
+
+const { $ } = window;
+
+export default defineComponent({
+ props: {
+ addInitialCondition: Boolean,
+ visitSegmentsOnly: Boolean,
+ idsite: [String, Number],
+ modelValue: {
+ type: String,
+ default: '',
+ },
+ },
+ components: {
+ ActivityIndicator,
+ Field,
+ ValueInput,
+ },
+ data(): SegmentGeneratorState {
+ return {
+ conditions: [],
+ queriedSegments: [],
+ matches: initialMatches(),
+ conditionValuesLoading: {},
+ segmentDefinition: '',
+ };
+ },
+ emits: ['update:modelValue'],
+ watch: {
+ modelValue(newVal) {
+ if (newVal !== this.segmentDefinition) {
+ this.setSegmentString(newVal);
+ }
+ },
+ conditions: {
+ deep: true,
+ handler() {
+ this.computeSegmentDefinition();
+ },
+ },
+ segmentDefinition(newVal) {
+ if (newVal !== this.modelValue) {
+ this.$emit('update:modelValue', newVal);
+ }
+ },
+ idsite(newVal) {
+ this.reloadSegments(newVal, this.visitSegmentsOnly);
+ },
+ },
+ created() {
+ this.matches[''] = this.matches.dimension;
+ this.setSegmentString(this.modelValue);
+ this.segmentDefinition = this.modelValue;
+
+ this.reloadSegments(this.idsite, this.visitSegmentsOnly);
+ },
+ methods: {
+ reloadSegments(idsite?: string|number, visitSegmentsOnly?: boolean) {
+ SegmentGeneratorStore.loadSegments(idsite, visitSegmentsOnly).then((segments) => {
+ this.queriedSegments = segments.map((s) => ({
+ ...s,
+ category: s.category || 'Others',
+ }));
+
+ if (this.addInitialCondition && this.conditions.length === 0) {
+ this.addNewAndCondition();
+ }
+ });
+ },
+ addAndCondition(condition: SegmentAndCondition) {
+ this.conditions.push(condition);
+ },
+ addNewOrCondition(condition: SegmentAndCondition) {
+ const orCondition = {
+ segment: this.firstSegment,
+ matches: this.firstMatch!,
+ value: '',
+ };
+
+ this.addOrCondition(condition, orCondition);
+ },
+ addOrCondition(condition: SegmentAndCondition, orCondition: SegmentOrCondition) {
+ this.conditionValuesLoading[orCondition.id!] = false;
+ orCondition.id = generateUniqueId();
+
+ condition.orConditions.push(orCondition);
+
+ nextTick(() => {
+ this.updateAutocomplete(orCondition);
+ });
+ },
+ updateAutocomplete(orCondition: SegmentOrCondition) {
+ this.conditionValuesLoading[orCondition.id!] = true;
+
+ $(`.orCondId${orCondition.id} .metricValueBlock input`, this.$refs.root as HTMLElement)
+ .autocomplete({
+ source: [],
+ minLength: 0,
+ });
+
+ const abortController = new AbortController();
+
+ let resolved = false;
+ AjaxHelper.fetch<string[]>(
+ {
+ module: 'API',
+ format: 'json',
+ method: 'API.getSuggestedValuesForSegment',
+ segmentName: orCondition.segment,
+ },
+ ).then((response) => {
+ this.conditionValuesLoading[orCondition.id!] = false;
+ resolved = true;
+
+ const inputElement = $(`.orCondId${orCondition.id} .metricValueBlock input`)
+ .autocomplete({
+ source: response,
+ minLength: 0,
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ select: (event: Event, ui: any) => {
+ event.preventDefault();
+
+ orCondition.value = ui.item.value;
+ this.computeSegmentDefinition(); // deep watch doesn't catch this change
+ this.$forceUpdate();
+ },
+ })
+ .off('click')
+ .click(() => {
+ $(inputElement).autocomplete('search', orCondition.value);
+ });
+ }).catch(() => {
+ resolved = true;
+
+ this.conditionValuesLoading[orCondition.id!] = false;
+
+ $(`.orCondId${orCondition.id} .metricValueBlock input`)
+ .autocomplete({
+ source: [],
+ minLength: 0,
+ })
+ .autocomplete('search', orCondition.value);
+ });
+
+ setTimeout(() => {
+ if (!resolved) {
+ abortController.abort();
+ }
+ }, 20000);
+ },
+ removeOrCondition(condition: SegmentAndCondition, orCondition: SegmentOrCondition) {
+ const index = condition.orConditions.indexOf(orCondition);
+
+ if (index > -1) {
+ condition.orConditions.splice(index, 1);
+ }
+
+ if (condition.orConditions.length === 0) {
+ const andCondIndex = this.conditions.indexOf(condition);
+
+ if (index > -1) {
+ this.conditions.splice(andCondIndex, 1);
+ }
+ }
+ },
+ setSegmentString(segmentStr: string) {
+ this.conditions = [];
+
+ if (!segmentStr) {
+ return;
+ }
+
+ const blocks = segmentStr.split(';').map((b) => b.split(','));
+ this.conditions = blocks.map((block) => {
+ const condition: SegmentAndCondition = { orConditions: [] };
+
+ block.forEach((innerBlock) => {
+ const orCondition: SegmentOrCondition = findAndExplodeByMatch(innerBlock);
+ this.addOrCondition(condition, orCondition);
+ });
+
+ return condition;
+ });
+ },
+ addNewAndCondition() {
+ const condition = { orConditions: [] };
+
+ this.addAndCondition(condition);
+ this.addNewOrCondition(condition);
+
+ return condition;
+ },
+ // NOTE: can't use a computed property since we need to recompute on changes inside the
+ // structure. don't have to if we don't do in-place changes, but with nested structures,
+ // that's complicated.
+ computeSegmentDefinition() {
+ let segmentStr = '';
+
+ this.conditions.forEach((condition) => {
+ if (!condition.orConditions.length) {
+ return;
+ }
+
+ let subSegmentStr = '';
+ condition.orConditions.forEach((orCondition) => {
+ if (!orCondition.value && !orCondition.segment && !orCondition.matches) {
+ return;
+ }
+
+ if (subSegmentStr !== '') {
+ subSegmentStr += ','; // OR operator
+ }
+
+ // one encode for urldecode on value, one encode for urldecode on condition
+ const value = encodeURIComponent(encodeURIComponent(orCondition.value));
+ subSegmentStr += `${orCondition.segment}${orCondition.matches}${value}`;
+ });
+
+ if (segmentStr !== '') {
+ segmentStr += ';'; // add AND operator between segment blocks
+ }
+
+ segmentStr += subSegmentStr;
+ });
+
+ this.segmentDefinition = segmentStr;
+ },
+ },
+ computed: {
+ firstSegment() {
+ return this.queriedSegments[0].segment;
+ },
+ firstMatch() {
+ const segment = this.queriedSegments[0];
+ if (!segment) {
+ return null;
+ }
+
+ if (segment.type && this.matches[segment.type]) {
+ return this.matches[segment.type][0].key;
+ }
+
+ return this.matches[''][0].key;
+ },
+ segments() {
+ const result: Record<string, SegmentMetadata> = {};
+ this.queriedSegments.forEach((s) => {
+ result[s.segment] = s;
+ });
+ return result;
+ },
+ segmentList() {
+ return this.queriedSegments.map((s) => ({
+ group: s.category,
+ key: s.segment,
+ value: s.name,
+ tooltip: s.acceptedValues ? stripTags(s.acceptedValues) : undefined,
+ }));
+ },
+ addNewOrConditionLinkText() {
+ return `+${translate(
+ 'SegmentEditor_AddANDorORCondition',
+ `<span>${translate('SegmentEditor_OperatorOR')}</span>`,
+ )}`;
+ },
+ andConditionLabel() {
+ return this.conditions.length ? translate('SegmentEditor_OperatorAND') : '';
+ },
+ addNewAndConditionLinkText() {
+ return `+${translate('SegmentEditor_AddANDorORCondition', `<span>${this.andConditionLabel}</span>`)}`;
+ },
+ isLoading() {
+ return SegmentGeneratorStore.state.value.isLoading;
+ },
+ },
+});
+</script>
diff --git a/plugins/SegmentEditor/vue/src/SegmentGenerator/ValueInput.vue b/plugins/SegmentEditor/vue/src/SegmentGenerator/ValueInput.vue
new file mode 100644
index 0000000000..15880bb0db
--- /dev/null
+++ b/plugins/SegmentEditor/vue/src/SegmentGenerator/ValueInput.vue
@@ -0,0 +1,38 @@
+<!--
+ Matomo - free/libre analytics platform
+ @link https://matomo.org
+ @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+-->
+
+<template>
+ <input
+ :placeholder="translate('General_Value')"
+ type="text"
+ class="autocomplete"
+ :title="translate('General_Value')"
+ autocomplete="off"
+ :value="or.value"
+ @keydown="onKeydownOrConditionValue($event)"
+ @change="onKeydownOrConditionValue($event)"
+ />
+</template>
+
+<script lang="ts">
+import { defineComponent } from 'vue';
+import { debounce } from 'CoreHome';
+
+export default defineComponent({
+ props: {
+ or: Object,
+ },
+ created() {
+ this.onKeydownOrConditionValue = debounce(this.onKeydownOrConditionValue, 50);
+ },
+ emits: ['update'],
+ methods: {
+ onKeydownOrConditionValue(event: Event) {
+ this.$emit('update', (event.target as HTMLInputElement).value);
+ },
+ },
+});
+</script>
diff --git a/plugins/SegmentEditor/vue/src/index.ts b/plugins/SegmentEditor/vue/src/index.ts
new file mode 100644
index 0000000000..5258822104
--- /dev/null
+++ b/plugins/SegmentEditor/vue/src/index.ts
@@ -0,0 +1,12 @@
+/*!
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+import './SegmentGenerator/SegmentGenerator.adapter';
+
+export * from './types';
+export { default as SegmentGeneratorStore } from './SegmentGenerator/SegmentGenerator.store';
+export { default as SegmentGenerator } from './SegmentGenerator/SegmentGenerator.vue';
diff --git a/plugins/SegmentEditor/vue/src/types.ts b/plugins/SegmentEditor/vue/src/types.ts
new file mode 100644
index 0000000000..62aab9d1f5
--- /dev/null
+++ b/plugins/SegmentEditor/vue/src/types.ts
@@ -0,0 +1,30 @@
+/*!
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+export interface SegmentMetadata {
+ acceptedValues: string;
+ category: string;
+ name: string;
+ needsMostFrequentValues: boolean;
+ segment: string;
+ sqlFilterValue: unknown;
+ sqlSegment: string;
+ type: string;
+}
+
+export interface SegmentOrCondition {
+ segment: string;
+ matches: string;
+ value: string;
+
+ id?: string;
+ isLoading?: boolean;
+}
+
+export interface SegmentAndCondition {
+ orConditions: SegmentOrCondition[];
+}