This is an automated email from the ASF dual-hosted git repository. kbhatt pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/atlas.git
commit e04c4f5c332322e191e1d8abdadc250bf03e2840 Author: kevalbhatt <[email protected]> AuthorDate: Tue Jan 28 16:11:00 2020 +0530 ATLAS-3596: UI: Improved System attribute presentation for user friendliness --- dashboardv2/public/css/scss/override.scss | 31 ++- dashboardv2/public/css/scss/search.scss | 47 ++++- .../js/templates/search/UserDefine_tmpl.html | 44 ++++ dashboardv2/public/js/utils/Enums.js | 4 +- .../public/js/views/entity/EntityUserDefineView.js | 4 +- .../public/js/views/search/QueryBuilderView.js | 228 +++++++++++++++++++- .../public/js/views/search/SearchQueryView.js | 3 +- .../js/views/search/SearchResultLayoutView.js | 25 +++ dashboardv3/public/css/scss/override.scss | 31 ++- dashboardv3/public/css/scss/search.scss | 53 ++++- .../js/templates/search/UserDefine_tmpl.html | 44 ++++ dashboardv3/public/js/utils/Enums.js | 8 +- .../public/js/views/entity/EntityUserDefineView.js | 2 +- .../public/js/views/search/QueryBuilderView.js | 229 ++++++++++++++++++++- .../js/views/search/SearchDefaultLayoutView.js | 125 +++++------ .../js/views/search/SearchResultLayoutView.js | 25 +++ 16 files changed, 800 insertions(+), 103 deletions(-) diff --git a/dashboardv2/public/css/scss/override.scss b/dashboardv2/public/css/scss/override.scss index d918f66..b6c42f6 100644 --- a/dashboardv2/public/css/scss/override.scss +++ b/dashboardv2/public/css/scss/override.scss @@ -294,6 +294,21 @@ td { .query-builder { .rule-container { margin: 6px 0; + display: flex; + flex-wrap: wrap; + + .values-box { + display: flex; + flex-grow: 1; + width: 100%; + padding-right: 26px; + } + + .action-box { + position: absolute; + right: 4px; + top: calc(50% - 18px); + } .rule-header { .rule-actions { @@ -301,24 +316,34 @@ td { } } + .error-container { + color: $color_trinidad_approx; + } + .rule-value-container { display: inline-block !important; + border-left: none; + width: calc(65% - 105px); .form-control { - width: 220px !important; + width: 100% !important; padding: 6px 12px !important; } } .rule-filter-container { + width: 35%; + .form-control { - width: 200px !important; + width: 100% !important; } } .rule-operator-container { + width: 105px; + .form-control { - width: auto !important; + width: 100% !important; } } } diff --git a/dashboardv2/public/css/scss/search.scss b/dashboardv2/public/css/scss/search.scss index 11d7144..fdb4d69 100644 --- a/dashboardv2/public/css/scss/search.scss +++ b/dashboardv2/public/css/scss/search.scss @@ -57,7 +57,7 @@ $color_celeste_approx: #1D1F2B; background-color: $color_jungle_green_approx; border-radius: 10px; box-shadow: inset 0 1px rgba(black, .02); - @include transition(inherit) + @include transition(inherit); } .switch-input:checked~& { @@ -240,6 +240,47 @@ hr.hr-filter { } } -.filter-btn-wrapper { - padding-left: 0; +.query-builder { + .rule-container { + &.user-define { + .values-box { + display: flex; + flex-wrap: wrap; + + .rule-filter-container { + width: 200px; + } + + .rule-value-container { + width: 100%; + padding: 7px 0px 0px 0px; + } + } + } + + .rule-value-container { + &>table.custom-table { + tr { + display: none; + + &.custom-tr { + display: table-row; + + td.custom-col-1 { + width: 48%; + + .errorMsg { + display: none; + } + } + } + } + + input, + textarea { + width: 100% !important; + } + } + } + } } \ No newline at end of file diff --git a/dashboardv2/public/js/templates/search/UserDefine_tmpl.html b/dashboardv2/public/js/templates/search/UserDefine_tmpl.html new file mode 100644 index 0000000..8868e90 --- /dev/null +++ b/dashboardv2/public/js/templates/search/UserDefine_tmpl.html @@ -0,0 +1,44 @@ +<!-- + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +--> +<table class="custom-table"> + <tbody> + <tr class="custom-tr"> + <td class="custom-col-1"> + <input placeholder="key" type="text" data-type="key" data-index="0" class="form-control" value=""> + <p class="errorMsg"></p> + </td> + <td class="custom-col-0"> : </td> + <td class="custom-col-1"> + <textarea placeholder="value" data-type="value" data-index="0" class="form-control"></textarea> + <p class="errorMsg"></p> + </td> + <!-- <td class="custom-col-2"> + <button class="btn btn-default btn-sm" title="" data-index="0" data-id="deleteUserDefineItem"> + <i class="fa fa-minus"> </i> + </button> + <button class="btn btn-default btn-sm" title="" data-index="0" data-id="addUserDefineItem"> + <i class="fa fa-plus"> </i> + </button> + </td> --> + </tr> + <tr> + <td colspan="4"> + <p class="errorMsg" data-id="charSupportMsg"></p> + </td> + </tr> + </tbody> +</table> \ No newline at end of file diff --git a/dashboardv2/public/js/utils/Enums.js b/dashboardv2/public/js/utils/Enums.js index f5d1ba6..82e11ca 100644 --- a/dashboardv2/public/js/utils/Enums.js +++ b/dashboardv2/public/js/utils/Enums.js @@ -194,8 +194,8 @@ define(['require'], function(require) { }; Enums.systemAttributes = { "__classificationNames": "Classification(s)", - "__createdBy": "Created By", - "__customAttributes": "User-defined attributes", + "__createdBy": "Created By User", + "__customAttributes": "User-defined Properties", "__guid": "Guid", "__isIncomplete": "IsIncomplete", "__labels": "Label(s)", diff --git a/dashboardv2/public/js/views/entity/EntityUserDefineView.js b/dashboardv2/public/js/views/entity/EntityUserDefineView.js index e206d55..1cb0d8c 100644 --- a/dashboardv2/public/js/views/entity/EntityUserDefineView.js +++ b/dashboardv2/public/js/views/entity/EntityUserDefineView.js @@ -54,7 +54,7 @@ define(['require', }, initialize: function(options) { _.extend(this, _.pick(options, 'entity', 'customFilter')); - this.userDefineAttr = this.entity.customAttributes || []; + this.userDefineAttr = this.entity && this.entity.customAttributes || []; this.initialCall = false; this.swapItem = false, this.saveAttrItems = false; this.readOnlyEntity = this.customFilter === undefined ? Enums.entityStateReadOnly[this.entity.status] : this.customFilter; @@ -123,7 +123,7 @@ define(['require', data: JSON.stringify(payload), type: 'POST', success: function() { - var msg = _.isEmpty(that.customAttibutes) ? 'addSuccessMessage' : 'editSuccessMessage', + var msg = that.initialCall ? 'addSuccessMessage' : 'editSuccessMessage', caption = "One or more user-defined propertie"; // 's' will be added in abbreviation function that.customAttibutes = list; if (list.length === 0) { diff --git a/dashboardv2/public/js/views/search/QueryBuilderView.js b/dashboardv2/public/js/views/search/QueryBuilderView.js index 5cbbbc3..cca7992 100644 --- a/dashboardv2/public/js/views/search/QueryBuilderView.js +++ b/dashboardv2/public/js/views/search/QueryBuilderView.js @@ -19,19 +19,20 @@ define(['require', 'backbone', 'hbs!tmpl/search/QueryBuilder_tmpl', + 'hbs!tmpl/search/UserDefine_tmpl', 'utils/Utils', 'utils/CommonViewFunction', 'utils/Enums', 'query-builder', 'daterangepicker' -], function(require, Backbone, QueryBuilder_Tmpl, Utils, CommonViewFunction, Enums) { +], function(require, Backbone, QueryBuilderTmpl, UserDefineTmpl, Utils, CommonViewFunction, Enums) { var QueryBuilderView = Backbone.Marionette.LayoutView.extend( /** @lends QueryBuilderView */ { _viewName: 'QueryBuilderView', - template: QueryBuilder_Tmpl, + template: QueryBuilderTmpl, @@ -53,13 +54,22 @@ define(['require', * @constructs */ initialize: function(options) { - _.extend(this, _.pick(options, 'attrObj', 'value', 'typeHeaders', 'entityDefCollection', 'enumDefCollection', 'tag', 'searchTableFilters', 'systemAttrArr')); + _.extend(this, _.pick(options, + 'attrObj', + 'value', + 'typeHeaders', + 'entityDefCollection', + 'enumDefCollection', + 'classificationDefCollection', + 'tag', + 'searchTableFilters', + 'systemAttrArr')); this.attrObj = _.sortBy(this.attrObj, 'name'); - this.systemAttrArr = _.sortBy(this.systemAttrArr, 'name'); + //this.systemAttrArr = _.sortBy(this.systemAttrArr, 'name'); this.filterType = this.tag ? 'tagFilters' : 'entityFilters'; }, bindEvents: function() {}, - getOperator: function(type) { + getOperator: function(type, skipDefault) { var obj = { operators: null } @@ -72,7 +82,7 @@ define(['require', if (type === "enum" || type === "boolean") { obj.operators = ['=', '!=']; } - if (obj.operators) { + if (_.isEmpty(skipDefault) && obj.operators) { obj.operators = obj.operators.concat(['is_null', 'not_null']); } return obj; @@ -83,34 +93,180 @@ define(['require', } return false; }, + getUserDefineInput: function() { + return UserDefineTmpl(); + }, getObjDef: function(attrObj, rules, isGroup, groupType, isSystemAttr) { + var that = this; if (attrObj.name === "__classificationsText" || attrObj.name === "__historicalGuids") { return; } + var getLableWithType = function(label, name) { + if (name === "__classificationNames" || name === "__customAttributes" || name === "__labels" || name === "__propagatedClassificationNames") { + return label; + } else { + return label + " (" + attrObj.typeName + ")"; + } + + } + var label = (Enums.systemAttributes[attrObj.name] ? Enums.systemAttributes[attrObj.name] : _.escape(attrObj.name.capitalize())); var obj = { id: attrObj.name, - label: (Enums.systemAttributes[attrObj.name] ? Enums.systemAttributes[attrObj.name] : _.escape(attrObj.name.capitalize())) + " (" + attrObj.typeName + ")", - type: _.escape(attrObj.typeName) + label: getLableWithType(label, attrObj.name), + plainLabel: label, + type: _.escape(attrObj.typeName), + validation: { + callback: function(value, rule) { + if (rule.operator.nb_inputs === false || !_.isEmpty(value) || !value instanceof Error) { + return true; + } else { + if (value instanceof Error) { + return value.message; // with params + } else { + return rule.filter.plainLabel + ' is required'; // with params + } + } + } + } }; if (isGroup) { obj.optgroup = groupType; } + /* __isIncomplete / IsIncomplete */ if (isSystemAttr && attrObj.name === "__isIncomplete" || isSystemAttr && attrObj.name === "IsIncomplete") { obj.type = "boolean"; obj.label = (Enums.systemAttributes[attrObj.name] ? Enums.systemAttributes[attrObj.name] : _.escape(attrObj.name.capitalize())) + " (boolean)"; obj['input'] = 'select'; obj['values'] = [{ 1: 'true' }, { 0: 'false' }]; - obj.operators = ['=', '!=', 'is_null', 'not_null']; + _.extend(obj, this.getOperator("boolean")); return obj; } + /* Status / __state */ if (isSystemAttr && attrObj.name === "Status" || isSystemAttr && attrObj.name === "__state") { obj.label = (Enums.systemAttributes[attrObj.name] ? Enums.systemAttributes[attrObj.name] : _.escape(attrObj.name.capitalize())) + " (enum)"; obj['input'] = 'select'; obj['values'] = ['ACTIVE', 'DELETED']; - obj.operators = ['=', '!=']; + _.extend(obj, this.getOperator("boolean", true)); + return obj; + } + /* __classificationNames / __propagatedClassificationNames */ + if (isSystemAttr && attrObj.name === "__classificationNames" || attrObj.name === "__propagatedClassificationNames") { + obj["plugin"] = "select2"; + obj["input"] = 'select'; + obj["plugin_config"] = { + placeholder: "Select classfication", + tags: true, + multiple: false, + data: this.classificationDefCollection.fullCollection.models.map(function(o) { return { "id": o.get("name"), "text": o.get("name") } }) + }; + obj["valueSetter"] = function(rule) { + if (rule && !_.isEmpty(rule.value)) { + var selectEl = rule.$el.find('.rule-value-container select') + var valFound = that.classificationDefCollection.fullCollection.find(function(o) { + return o.get("name") === rule.value + }) + if (valFound) { + selectEl.val(rule.value).trigger("change"); + } else { + var newOption = new Option(rule.value, rule.value, false, false); + selectEl.append(newOption).val(rule.value); + } + } + }; + _.extend(obj, this.getOperator("string")); return obj; } + /* __customAttributes */ + if (isSystemAttr && attrObj.name === "__customAttributes") { + obj["input"] = function(rule) { + return rule.operator.nb_inputs ? that.getUserDefineInput() : null + } + obj["valueGetter"] = function(rule) { + if (rule.operator.type === "contains") { + var $el = rule.$el.find('.rule-value-container'), + key = $el.find("[data-type='key']").val(), + val = $el.find("[data-type='value']").val(); + if (!_.isEmpty(key) && !_.isEmpty(val)) { + return key + "=" + val; + } else { + return new Error("Key & Value is Required"); + } + } + } + obj["valueSetter"] = function(rule) { + if (!rule.$el.hasClass("user-define")) { + rule.$el.addClass("user-define"); + } + if (rule.value && !(rule.value instanceof Error)) { + var $el = rule.$el.find('.rule-value-container'), + value = rule.value.split("="); + if (value) { + $el.find("[data-type='key']").val(value[0]), + $el.find("[data-type='value']").val(value[1]); + } + } + } + obj.operators = ['contains', 'is_null', 'not_null']; + return obj; + } + /* __labels */ + if (isSystemAttr && attrObj.name === "__labels") { + obj["plugin"] = "select2"; + obj["input"] = 'select'; + obj["plugin_config"] = { + placeholder: "Enter Label(s)", + tags: true, + "language": { + "noResults": function() { return ''; } + }, + multiple: false + }; + obj["valueSetter"] = function(rule) { + if (rule && !_.isEmpty(rule.value)) { + var newOption = new Option(rule.value, rule.value, true, false); + return rule.$el.find('.rule-value-container select').append(newOption); + } + } + _.extend(obj, this.getOperator("string")); + return obj; + } + /* __typeName */ + if (isSystemAttr && attrObj.name === "__typeName") { + var entityType = []; + that.typeHeaders.fullCollection.each(function(model) { + if (model.get('category') == 'ENTITY') { + entityType.push({ + "id": model.get("name"), + "text": model.get("name") + }) + } + }); + obj["plugin"] = "select2"; + obj["input"] = 'select'; + obj["plugin_config"] = { + placeholder: "Select type", + tags: true, + multiple: false, + data: entityType + }; + obj["valueSetter"] = function(rule) { + if (rule && !_.isEmpty(rule.value)) { + var selectEl = rule.$el.find('.rule-value-container select') + var valFound = that.typeHeaders.fullCollection.find(function(o) { + return o.get("name") === rule.value + }) + if (valFound) { + selectEl.val(rule.value).trigger("change"); + } else { + var newOption = new Option(rule.value, rule.value, false, false); + selectEl.append(newOption).val(rule.value); + } + } + }; + _.extend(obj, this.getOperator("string")); + return obj; + } if (obj.type === "date") { obj['plugin'] = 'daterangepicker'; obj['plugin_config'] = { @@ -185,6 +341,23 @@ define(['require', filters.push(returnObj); } }); + var sortMap = { + "__guid": 1, + "__typeName": 2, + "__timestamp": 3, + "__modificationTimestamp": 4, + "__createdBy": 5, + "__modifiedBy": 6, + "__isIncomplete": 7, + "__state": 8, + "__classificationNames": 9, + "__propagatedClassificationNames": 10, + "__labels": 11, + "__customAttributes": 12, + } + this.systemAttrArr = _.sortBy(this.systemAttrArr, function(obj) { + return sortMap[obj.name] + }) _.each(this.systemAttrArr, function(obj) { var returnObj = that.getObjDef(obj, rules_widgets, isGroupView, 'Select System Attribute', true); if (returnObj) { @@ -201,6 +374,23 @@ define(['require', conditions: ['AND', 'OR'], allow_groups: true, allow_empty: true, + templates: { + rule: '<div id="{{= it.rule_id }}" class="rule-container"> \ + <div class="values-box"><div class="rule-filter-container"></div> \ + <div class="rule-operator-container"></div> \ + <div class="rule-value-container"></div></div> \ + <div class="action-box"><div class="rule-header"> \ + <div class="btn-group rule-actions"> \ + <button type="button" class="btn btn-xs btn-danger" data-delete="rule"> \ + <i class="{{= it.icons.remove_rule }}"></i> \ + </button> \ + </div> \ + </div> </div>\ + {{? it.settings.display_errors }} \ + <div class="error-container"><i class="{{= it.icons.error }}"></i> <span></span></div> \ + {{?}} \ + </div>' + }, operators: [ { type: '=', nb_inputs: 1, multiple: false, apply_to: ['number', 'string', 'boolean', 'enum'] }, { type: '!=', nb_inputs: 1, multiple: false, apply_to: ['number', 'string', 'boolean', 'enum'] }, @@ -227,7 +417,25 @@ define(['require', error: 'fa fa-exclamation-triangle' }, rules: rules_widgets + }).on("afterCreateRuleInput.queryBuilder", function(e, rule) { + rule.error = null; + if (rule.operator.nb_inputs && rule.filter.id === "__customAttributes") { + rule.$el.addClass("user-define"); + } else if (rule.$el.hasClass("user-define")) { + rule.$el.removeClass("user-define"); + } + }).on('validationError.queryBuilder', function(e, rule, error, value) { + // never display error for my custom filter + var errorMsg = error[0]; + if (that.queryBuilderLang && that.queryBuilderLang.errors && that.queryBuilderLang.errors[errorMsg]) { + errorMsg = that.queryBuilderLang.errors[errorMsg]; + } + rule.$el.find(".error-container span").html(errorMsg); }); + var queryBuilderEl = that.ui.builder.data("queryBuilder"); + if (queryBuilderEl && queryBuilderEl.lang) { + this.queryBuilderLang = queryBuilderEl.lang; + } this.$('.rules-group-header .btn-group.pull-right.group-actions').toggleClass('pull-left'); } else { this.ui.builder.html('<h4>No Attributes are available !</h4>') diff --git a/dashboardv2/public/js/views/search/SearchQueryView.js b/dashboardv2/public/js/views/search/SearchQueryView.js index 6e5f1a4..692a9df 100644 --- a/dashboardv2/public/js/views/search/SearchQueryView.js +++ b/dashboardv2/public/js/views/search/SearchQueryView.js @@ -89,7 +89,8 @@ define(['require', entityDefCollection: this.entityDefCollection, enumDefCollection: this.enumDefCollection, classificationDefCollection: this.classificationDefCollection, - searchTableFilters: this.searchTableFilters + searchTableFilters: this.searchTableFilters, + typeHeaders: this.typeHeaders } if (this.tag) { diff --git a/dashboardv2/public/js/views/search/SearchResultLayoutView.js b/dashboardv2/public/js/views/search/SearchResultLayoutView.js index e69c7c4..a94a0f7 100644 --- a/dashboardv2/public/js/views/search/SearchResultLayoutView.js +++ b/dashboardv2/public/js/views/search/SearchResultLayoutView.js @@ -807,6 +807,9 @@ define(['require', } return; } + if (key == "__historicalGuids" || key == "__classificationsText" || key == "__classificationNames" || key == "__propagatedClassificationNames") { + return; + } col[obj.name] = { label: Enums.systemAttributes[obj.name] ? Enums.systemAttributes[obj.name] : _.escape(obj.name).capitalize(), cell: "Html", @@ -826,6 +829,28 @@ define(['require', 'valueObject': {}, 'isTable': false }; + if (key == "__labels") { + var values = modelObj.attributes[key] ? modelObj.attributes[key].split("|") : null, + valueOfArray = []; + if (values) { + if (values[values.length - 1] === "") { values.pop(); } + if (values[0] === "") { values.shift(); } + _.each(values, function(names) { + valueOfArray.push('<span class="json-string"><a class="btn btn-action btn-sm btn-blue btn-icon" ><span title="" data-original-title="' + names + '" >' + names + '</span></a></span>'); + }); + return valueOfArray.join(' '); + } + } + if (key == "__customAttributes") { + var customAttributes = modelObj.attributes[key] ? JSON.parse(modelObj.attributes[key]) : null, + valueOfArray = []; + if (customAttributes) { + _.each(Object.keys(customAttributes), function(value, index) { + valueOfArray.push('<span class="json-string"><a class="btn btn-action btn-sm btn-blue btn-icon" ><span title="" data-original-title="' + value + ' : ' + Object.values(customAttributes)[index] + '" ><span>' + value + '</span> : <span>' + Object.values(customAttributes)[index] + '</span></span></a></span>'); + }); + return valueOfArray.join(' '); + } + } tempObj.valueObject[key] = modelObj.attributes[key]; var tablecolumn = CommonViewFunction.propertyTable(tempObj); if (_.isArray(modelObj.attributes[key])) { diff --git a/dashboardv3/public/css/scss/override.scss b/dashboardv3/public/css/scss/override.scss index ab7d423..ab4d601 100644 --- a/dashboardv3/public/css/scss/override.scss +++ b/dashboardv3/public/css/scss/override.scss @@ -246,6 +246,21 @@ .query-builder { .rule-container { margin: 6px 0; + display: flex; + flex-wrap: wrap; + + .values-box { + display: flex; + flex-grow: 1; + width: 100%; + padding-right: 26px; + } + + .action-box { + position: absolute; + right: 4px; + top: calc(50% - 18px); + } .rule-header { .rule-actions { @@ -253,24 +268,34 @@ } } + .error-container { + color: $color_trinidad_approx; + } + .rule-value-container { display: inline-block !important; + border-left: none; + width: calc(65% - 105px); .form-control { - width: 220px !important; + width: 100% !important; padding: 6px 12px !important; } } .rule-filter-container { + width: 35%; + .form-control { - width: 200px !important; + width: 100% !important; } } .rule-operator-container { + width: 105px; + .form-control { - width: auto !important; + width: 100% !important; } } } diff --git a/dashboardv3/public/css/scss/search.scss b/dashboardv3/public/css/scss/search.scss index a995ad5..3ac2769 100644 --- a/dashboardv3/public/css/scss/search.scss +++ b/dashboardv3/public/css/scss/search.scss @@ -425,10 +425,10 @@ hr.hr-filter { padding: 0px 15px; } - .attribute-result-footer, - .attribute-edit-footer { - display: inline-block; - } + // .attribute-result-footer, + // .attribute-edit-footer { + // display: inline-block; + // } } .filter-box { @@ -477,4 +477,49 @@ hr.hr-filter { .attributePopOver { min-height: 190px; } +} + +.query-builder { + .rule-container { + &.user-define { + .values-box { + display: flex; + flex-wrap: wrap; + + .rule-filter-container { + width: 200px; + } + + .rule-value-container { + width: 100%; + padding: 7px 0px 0px 0px; + } + } + } + + .rule-value-container { + &>table.custom-table { + tr { + display: none; + + &.custom-tr { + display: table-row; + + td.custom-col-1 { + width: 48%; + + .errorMsg { + display: none; + } + } + } + } + + input, + textarea { + width: 100% !important; + } + } + } + } } \ No newline at end of file diff --git a/dashboardv3/public/js/templates/search/UserDefine_tmpl.html b/dashboardv3/public/js/templates/search/UserDefine_tmpl.html new file mode 100644 index 0000000..8868e90 --- /dev/null +++ b/dashboardv3/public/js/templates/search/UserDefine_tmpl.html @@ -0,0 +1,44 @@ +<!-- + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +--> +<table class="custom-table"> + <tbody> + <tr class="custom-tr"> + <td class="custom-col-1"> + <input placeholder="key" type="text" data-type="key" data-index="0" class="form-control" value=""> + <p class="errorMsg"></p> + </td> + <td class="custom-col-0"> : </td> + <td class="custom-col-1"> + <textarea placeholder="value" data-type="value" data-index="0" class="form-control"></textarea> + <p class="errorMsg"></p> + </td> + <!-- <td class="custom-col-2"> + <button class="btn btn-default btn-sm" title="" data-index="0" data-id="deleteUserDefineItem"> + <i class="fa fa-minus"> </i> + </button> + <button class="btn btn-default btn-sm" title="" data-index="0" data-id="addUserDefineItem"> + <i class="fa fa-plus"> </i> + </button> + </td> --> + </tr> + <tr> + <td colspan="4"> + <p class="errorMsg" data-id="charSupportMsg"></p> + </td> + </tr> + </tbody> +</table> \ No newline at end of file diff --git a/dashboardv3/public/js/utils/Enums.js b/dashboardv3/public/js/utils/Enums.js index e0486d3..82e11ca 100644 --- a/dashboardv3/public/js/utils/Enums.js +++ b/dashboardv3/public/js/utils/Enums.js @@ -194,8 +194,8 @@ define(['require'], function(require) { }; Enums.systemAttributes = { "__classificationNames": "Classification(s)", - "__createdBy": "Created By", - "__customAttributes": "User-defined attributes", + "__createdBy": "Created By User", + "__customAttributes": "User-defined Properties", "__guid": "Guid", "__isIncomplete": "IsIncomplete", "__labels": "Label(s)", @@ -208,7 +208,7 @@ define(['require'], function(require) { }; Enums.__isIncomplete = { 0: "false", - 1: "rue" - } + 1: "true" + }; return Enums; }); \ No newline at end of file diff --git a/dashboardv3/public/js/views/entity/EntityUserDefineView.js b/dashboardv3/public/js/views/entity/EntityUserDefineView.js index 61e603e..1cb0d8c 100644 --- a/dashboardv3/public/js/views/entity/EntityUserDefineView.js +++ b/dashboardv3/public/js/views/entity/EntityUserDefineView.js @@ -54,7 +54,7 @@ define(['require', }, initialize: function(options) { _.extend(this, _.pick(options, 'entity', 'customFilter')); - this.userDefineAttr = this.entity.customAttributes || []; + this.userDefineAttr = this.entity && this.entity.customAttributes || []; this.initialCall = false; this.swapItem = false, this.saveAttrItems = false; this.readOnlyEntity = this.customFilter === undefined ? Enums.entityStateReadOnly[this.entity.status] : this.customFilter; diff --git a/dashboardv3/public/js/views/search/QueryBuilderView.js b/dashboardv3/public/js/views/search/QueryBuilderView.js index 39bb242..cca7992 100644 --- a/dashboardv3/public/js/views/search/QueryBuilderView.js +++ b/dashboardv3/public/js/views/search/QueryBuilderView.js @@ -19,19 +19,20 @@ define(['require', 'backbone', 'hbs!tmpl/search/QueryBuilder_tmpl', + 'hbs!tmpl/search/UserDefine_tmpl', 'utils/Utils', 'utils/CommonViewFunction', 'utils/Enums', 'query-builder', 'daterangepicker' -], function(require, Backbone, QueryBuilder_Tmpl, Utils, CommonViewFunction, Enums) { +], function(require, Backbone, QueryBuilderTmpl, UserDefineTmpl, Utils, CommonViewFunction, Enums) { var QueryBuilderView = Backbone.Marionette.LayoutView.extend( /** @lends QueryBuilderView */ { _viewName: 'QueryBuilderView', - template: QueryBuilder_Tmpl, + template: QueryBuilderTmpl, @@ -53,13 +54,22 @@ define(['require', * @constructs */ initialize: function(options) { - _.extend(this, _.pick(options, 'attrObj', 'value', 'typeHeaders', 'entityDefCollection', 'enumDefCollection', 'tag', 'searchTableFilters', 'systemAttrArr')); + _.extend(this, _.pick(options, + 'attrObj', + 'value', + 'typeHeaders', + 'entityDefCollection', + 'enumDefCollection', + 'classificationDefCollection', + 'tag', + 'searchTableFilters', + 'systemAttrArr')); this.attrObj = _.sortBy(this.attrObj, 'name'); - this.systemAttrArr = _.sortBy(this.systemAttrArr, 'name'); + //this.systemAttrArr = _.sortBy(this.systemAttrArr, 'name'); this.filterType = this.tag ? 'tagFilters' : 'entityFilters'; }, bindEvents: function() {}, - getOperator: function(type) { + getOperator: function(type, skipDefault) { var obj = { operators: null } @@ -72,7 +82,7 @@ define(['require', if (type === "enum" || type === "boolean") { obj.operators = ['=', '!=']; } - if (obj.operators) { + if (_.isEmpty(skipDefault) && obj.operators) { obj.operators = obj.operators.concat(['is_null', 'not_null']); } return obj; @@ -83,31 +93,178 @@ define(['require', } return false; }, + getUserDefineInput: function() { + return UserDefineTmpl(); + }, getObjDef: function(attrObj, rules, isGroup, groupType, isSystemAttr) { + var that = this; if (attrObj.name === "__classificationsText" || attrObj.name === "__historicalGuids") { return; } + var getLableWithType = function(label, name) { + if (name === "__classificationNames" || name === "__customAttributes" || name === "__labels" || name === "__propagatedClassificationNames") { + return label; + } else { + return label + " (" + attrObj.typeName + ")"; + } + + } + var label = (Enums.systemAttributes[attrObj.name] ? Enums.systemAttributes[attrObj.name] : _.escape(attrObj.name.capitalize())); var obj = { id: attrObj.name, - label: (Enums.systemAttributes[attrObj.name] ? Enums.systemAttributes[attrObj.name] : _.escape(attrObj.name.capitalize())) + " (" + attrObj.typeName + ")", - type: _.escape(attrObj.typeName) + label: getLableWithType(label, attrObj.name), + plainLabel: label, + type: _.escape(attrObj.typeName), + validation: { + callback: function(value, rule) { + if (rule.operator.nb_inputs === false || !_.isEmpty(value) || !value instanceof Error) { + return true; + } else { + if (value instanceof Error) { + return value.message; // with params + } else { + return rule.filter.plainLabel + ' is required'; // with params + } + } + } + } }; if (isGroup) { obj.optgroup = groupType; } + /* __isIncomplete / IsIncomplete */ if (isSystemAttr && attrObj.name === "__isIncomplete" || isSystemAttr && attrObj.name === "IsIncomplete") { obj.type = "boolean"; obj.label = (Enums.systemAttributes[attrObj.name] ? Enums.systemAttributes[attrObj.name] : _.escape(attrObj.name.capitalize())) + " (boolean)"; obj['input'] = 'select'; obj['values'] = [{ 1: 'true' }, { 0: 'false' }]; - obj.operators = ['=', '!=', 'is_null', 'not_null']; + _.extend(obj, this.getOperator("boolean")); return obj; } + /* Status / __state */ if (isSystemAttr && attrObj.name === "Status" || isSystemAttr && attrObj.name === "__state") { obj.label = (Enums.systemAttributes[attrObj.name] ? Enums.systemAttributes[attrObj.name] : _.escape(attrObj.name.capitalize())) + " (enum)"; obj['input'] = 'select'; obj['values'] = ['ACTIVE', 'DELETED']; - obj.operators = ['=', '!=']; + _.extend(obj, this.getOperator("boolean", true)); + return obj; + } + /* __classificationNames / __propagatedClassificationNames */ + if (isSystemAttr && attrObj.name === "__classificationNames" || attrObj.name === "__propagatedClassificationNames") { + obj["plugin"] = "select2"; + obj["input"] = 'select'; + obj["plugin_config"] = { + placeholder: "Select classfication", + tags: true, + multiple: false, + data: this.classificationDefCollection.fullCollection.models.map(function(o) { return { "id": o.get("name"), "text": o.get("name") } }) + }; + obj["valueSetter"] = function(rule) { + if (rule && !_.isEmpty(rule.value)) { + var selectEl = rule.$el.find('.rule-value-container select') + var valFound = that.classificationDefCollection.fullCollection.find(function(o) { + return o.get("name") === rule.value + }) + if (valFound) { + selectEl.val(rule.value).trigger("change"); + } else { + var newOption = new Option(rule.value, rule.value, false, false); + selectEl.append(newOption).val(rule.value); + } + } + }; + _.extend(obj, this.getOperator("string")); + return obj; + } + /* __customAttributes */ + if (isSystemAttr && attrObj.name === "__customAttributes") { + obj["input"] = function(rule) { + return rule.operator.nb_inputs ? that.getUserDefineInput() : null + } + obj["valueGetter"] = function(rule) { + if (rule.operator.type === "contains") { + var $el = rule.$el.find('.rule-value-container'), + key = $el.find("[data-type='key']").val(), + val = $el.find("[data-type='value']").val(); + if (!_.isEmpty(key) && !_.isEmpty(val)) { + return key + "=" + val; + } else { + return new Error("Key & Value is Required"); + } + } + } + obj["valueSetter"] = function(rule) { + if (!rule.$el.hasClass("user-define")) { + rule.$el.addClass("user-define"); + } + if (rule.value && !(rule.value instanceof Error)) { + var $el = rule.$el.find('.rule-value-container'), + value = rule.value.split("="); + if (value) { + $el.find("[data-type='key']").val(value[0]), + $el.find("[data-type='value']").val(value[1]); + } + } + } + + obj.operators = ['contains', 'is_null', 'not_null']; + return obj; + } + /* __labels */ + if (isSystemAttr && attrObj.name === "__labels") { + obj["plugin"] = "select2"; + obj["input"] = 'select'; + obj["plugin_config"] = { + placeholder: "Enter Label(s)", + tags: true, + "language": { + "noResults": function() { return ''; } + }, + multiple: false + }; + obj["valueSetter"] = function(rule) { + if (rule && !_.isEmpty(rule.value)) { + var newOption = new Option(rule.value, rule.value, true, false); + return rule.$el.find('.rule-value-container select').append(newOption); + } + } + _.extend(obj, this.getOperator("string")); + return obj; + } + /* __typeName */ + if (isSystemAttr && attrObj.name === "__typeName") { + var entityType = []; + that.typeHeaders.fullCollection.each(function(model) { + if (model.get('category') == 'ENTITY') { + entityType.push({ + "id": model.get("name"), + "text": model.get("name") + }) + } + }); + obj["plugin"] = "select2"; + obj["input"] = 'select'; + obj["plugin_config"] = { + placeholder: "Select type", + tags: true, + multiple: false, + data: entityType + }; + obj["valueSetter"] = function(rule) { + if (rule && !_.isEmpty(rule.value)) { + var selectEl = rule.$el.find('.rule-value-container select') + var valFound = that.typeHeaders.fullCollection.find(function(o) { + return o.get("name") === rule.value + }) + if (valFound) { + selectEl.val(rule.value).trigger("change"); + } else { + var newOption = new Option(rule.value, rule.value, false, false); + selectEl.append(newOption).val(rule.value); + } + } + }; + _.extend(obj, this.getOperator("string")); return obj; } if (obj.type === "date") { @@ -184,6 +341,23 @@ define(['require', filters.push(returnObj); } }); + var sortMap = { + "__guid": 1, + "__typeName": 2, + "__timestamp": 3, + "__modificationTimestamp": 4, + "__createdBy": 5, + "__modifiedBy": 6, + "__isIncomplete": 7, + "__state": 8, + "__classificationNames": 9, + "__propagatedClassificationNames": 10, + "__labels": 11, + "__customAttributes": 12, + } + this.systemAttrArr = _.sortBy(this.systemAttrArr, function(obj) { + return sortMap[obj.name] + }) _.each(this.systemAttrArr, function(obj) { var returnObj = that.getObjDef(obj, rules_widgets, isGroupView, 'Select System Attribute', true); if (returnObj) { @@ -200,6 +374,23 @@ define(['require', conditions: ['AND', 'OR'], allow_groups: true, allow_empty: true, + templates: { + rule: '<div id="{{= it.rule_id }}" class="rule-container"> \ + <div class="values-box"><div class="rule-filter-container"></div> \ + <div class="rule-operator-container"></div> \ + <div class="rule-value-container"></div></div> \ + <div class="action-box"><div class="rule-header"> \ + <div class="btn-group rule-actions"> \ + <button type="button" class="btn btn-xs btn-danger" data-delete="rule"> \ + <i class="{{= it.icons.remove_rule }}"></i> \ + </button> \ + </div> \ + </div> </div>\ + {{? it.settings.display_errors }} \ + <div class="error-container"><i class="{{= it.icons.error }}"></i> <span></span></div> \ + {{?}} \ + </div>' + }, operators: [ { type: '=', nb_inputs: 1, multiple: false, apply_to: ['number', 'string', 'boolean', 'enum'] }, { type: '!=', nb_inputs: 1, multiple: false, apply_to: ['number', 'string', 'boolean', 'enum'] }, @@ -226,7 +417,25 @@ define(['require', error: 'fa fa-exclamation-triangle' }, rules: rules_widgets + }).on("afterCreateRuleInput.queryBuilder", function(e, rule) { + rule.error = null; + if (rule.operator.nb_inputs && rule.filter.id === "__customAttributes") { + rule.$el.addClass("user-define"); + } else if (rule.$el.hasClass("user-define")) { + rule.$el.removeClass("user-define"); + } + }).on('validationError.queryBuilder', function(e, rule, error, value) { + // never display error for my custom filter + var errorMsg = error[0]; + if (that.queryBuilderLang && that.queryBuilderLang.errors && that.queryBuilderLang.errors[errorMsg]) { + errorMsg = that.queryBuilderLang.errors[errorMsg]; + } + rule.$el.find(".error-container span").html(errorMsg); }); + var queryBuilderEl = that.ui.builder.data("queryBuilder"); + if (queryBuilderEl && queryBuilderEl.lang) { + this.queryBuilderLang = queryBuilderEl.lang; + } this.$('.rules-group-header .btn-group.pull-right.group-actions').toggleClass('pull-left'); } else { this.ui.builder.html('<h4>No Attributes are available !</h4>') diff --git a/dashboardv3/public/js/views/search/SearchDefaultLayoutView.js b/dashboardv3/public/js/views/search/SearchDefaultLayoutView.js index a831d34..707ff14 100644 --- a/dashboardv3/public/js/views/search/SearchDefaultLayoutView.js +++ b/dashboardv3/public/js/views/search/SearchDefaultLayoutView.js @@ -55,12 +55,12 @@ define(["require", "backbone", "utils/Globals", "hbs!tmpl/search/SearchDefaultLa var events = {}, that = this; events["click " + this.ui.attrFilter] = function(e) { - // this.$('.fa-chevron-right').toggleClass('fa-chevron-down'); + if (this.$('.attribute-filter-container').hasClass("hide")) { + this.onClickAttrFilter(); + } this.$('.fa-angle-right').toggleClass('fa-angle-down'); this.$('.attributeResultContainer').addClass("overlay"); this.$('.attribute-filter-container, .attr-filter-overlay').toggleClass('hide'); - // this.$('.attribute-filter-container').toggleClass('attribute-filter-container') - this.onClickAttrFilter(); }; events["click " + this.ui.attrApply] = function(e) { @@ -201,6 +201,7 @@ define(["require", "backbone", "utils/Globals", "hbs!tmpl/search/SearchDefaultLa manualRender: function(options) { _.extend(this.options, options); this.updateView(); + this.onClickAttrFilter(); this.renderSearchResult(); }, renderGlobalSearch: function() { @@ -226,77 +227,81 @@ define(["require", "backbone", "utils/Globals", "hbs!tmpl/search/SearchDefaultLa } return options.searchTableFilters; }, - onClickAttrFilter: function(filterType) { + onClickAttrFilter: function() { var that = this, obj = { value: that.options.value, searchVent: that.options.searchVent, entityDefCollection: that.options.entityDefCollection, enumDefCollection: that.options.enumDefCollection, + typeHeaders: that.options.typeHeaders, classificationDefCollection: that.options.classificationDefCollection, searchTableFilters: that.checkEntityFilter(that.options) }; - this.ui.checkDeletedEntity.prop('checked', this.options.value.includeDE ? this.options.value.includeDE : false); - this.ui.checkSubClassification.prop('checked', this.options.value.excludeSC ? this.options.value.excludeSC : false); - this.ui.checkSubType.prop('checked', this.options.value.excludeST ? this.options.value.excludeST : false); + if (that.options.value) { + this.ui.checkDeletedEntity.prop('checked', this.options.value.includeDE ? this.options.value.includeDE : false); + this.ui.checkSubClassification.prop('checked', this.options.value.excludeSC ? this.options.value.excludeSC : false); + this.ui.checkSubType.prop('checked', this.options.value.excludeST ? this.options.value.excludeST : false); - if (that.options.value.tag && that.options.value.type) { - this.$('.attribute-filter-container').removeClass('no-attr'); - this.ui.classificationRegion.show(); - this.ui.entityRegion.show(); - } else { - if (!that.options.value.tag && !that.options.value.type) { - this.$('.attribute-filter-container').addClass('no-attr'); + if (that.options.value.tag && that.options.value.type) { + this.$('.attribute-filter-container').removeClass('no-attr'); + this.ui.classificationRegion.show(); + this.ui.entityRegion.show(); + } else { + if (!that.options.value.tag && !that.options.value.type) { + this.$('.attribute-filter-container').addClass('no-attr'); + } + this.ui.entityRegion.hide(); + this.ui.classificationRegion.hide(); } - this.ui.entityRegion.hide(); - this.ui.classificationRegion.hide(); - } - if (that.options.value.tag) { - this.ui.classificationRegion.show(); - // this.ui.entityRegion.hide(); - var attrTagObj = that.options.classificationDefCollection.fullCollection.find({ name: that.options.value.tag }); - if (attrTagObj) { - attrTagObj = Utils.getNestedSuperTypeObj({ - data: attrTagObj.toJSON(), - collection: that.options.classificationDefCollection, - attrMerge: true, - }); - this.tagAttributeLength = attrTagObj.length; + if (that.options.value.tag) { + this.ui.classificationRegion.show(); + // this.ui.entityRegion.hide(); + var attrTagObj = that.options.classificationDefCollection.fullCollection.find({ name: that.options.value.tag }); + if (attrTagObj) { + attrTagObj = Utils.getNestedSuperTypeObj({ + data: attrTagObj.toJSON(), + collection: that.options.classificationDefCollection, + attrMerge: true, + }); + this.tagAttributeLength = attrTagObj.length; + } + if (Globals[that.options.value.tag] || Globals[Enums.addOnClassification[0]]) { + obj.systemAttrArr = (Globals[that.options.value.tag] || Globals[Enums.addOnClassification[0]]).attributeDefs; + this.tagAttributeLength = obj.systemAttrArr.length; + } + this.renderQueryBuilder(_.extend({}, obj, { + tag: true, + type: false, + attrObj: attrTagObj + }), this.RQueryBuilderClassification); + this.ui.classificationName.html(that.options.value.tag); } - if (Globals[that.options.value.tag] || Globals[Enums.addOnClassification[0]]) { - obj.systemAttrArr = (Globals[that.options.value.tag] || Globals[Enums.addOnClassification[0]]).attributeDefs; - this.tagAttributeLength = obj.systemAttrArr.length; + if (that.options.value.type) { + this.ui.entityRegion.show(); + var attrTypeObj = that.options.entityDefCollection.fullCollection.find({ name: that.options.value.type }); + if (attrTypeObj) { + attrTypeObj = Utils.getNestedSuperTypeObj({ + data: attrTypeObj.toJSON(), + collection: that.options.entityDefCollection, + attrMerge: true + }); + this.entityAttributeLength = attrTypeObj.length; + } + if (Globals[that.options.value.type] || Globals[Enums.addOnEntities[0]]) { + obj.systemAttrArr = (Globals[that.options.value.type] || Globals[Enums.addOnEntities[0]]).attributeDefs; + this.entityAttributeLength = obj.systemAttrArr.length; + } + this.renderQueryBuilder(_.extend({}, obj, { + tag: false, + type: true, + attrObj: attrTypeObj + }), this.RQueryBuilderEntity); + + this.ui.entityName.html(that.options.value.type); } - this.renderQueryBuilder(_.extend({}, obj, { - tag: true, - type: false, - attrObj: attrTagObj - }), this.RQueryBuilderClassification); - this.ui.classificationName.html(that.options.value.tag); } - if (that.options.value.type) { - this.ui.entityRegion.show(); - var attrTypeObj = that.options.entityDefCollection.fullCollection.find({ name: that.options.value.type }); - if (attrTypeObj) { - attrTypeObj = Utils.getNestedSuperTypeObj({ - data: attrTypeObj.toJSON(), - collection: that.options.entityDefCollection, - attrMerge: true - }); - this.entityAttributeLength = attrTypeObj.length; - } - if (Globals[that.options.value.type] || Globals[Enums.addOnEntities[0]]) { - obj.systemAttrArr = (Globals[that.options.value.type] || Globals[Enums.addOnEntities[0]]).attributeDefs; - this.entityAttributeLength = obj.systemAttrArr.length; - } - this.renderQueryBuilder(_.extend({}, obj, { - tag: false, - type: true, - attrObj: attrTypeObj - }), this.RQueryBuilderEntity); - this.ui.entityName.html(that.options.value.type); - } }, okAttrFilterButton: function(e) { var isTag, @@ -349,7 +354,7 @@ define(["require", "backbone", "utils/Globals", "hbs!tmpl/search/SearchDefaultLa that.options.value.includeDE = (obj.value === "ACTIVE" && obj.operator === "=") || (obj.value === "DELETED" && obj.operator === "!=") ? false : true; } if (_.has(obj, "condition")) { - return this.getIdFromRuleObj(obj); + return that.getIdFromRuleObj(obj); } else { return col.push(obj.id); } diff --git a/dashboardv3/public/js/views/search/SearchResultLayoutView.js b/dashboardv3/public/js/views/search/SearchResultLayoutView.js index 4d58fad..f11d903 100644 --- a/dashboardv3/public/js/views/search/SearchResultLayoutView.js +++ b/dashboardv3/public/js/views/search/SearchResultLayoutView.js @@ -819,6 +819,9 @@ define(['require', } return; } + if (key == "__historicalGuids" || key == "__classificationsText" || key == "__classificationNames" || key == "__propagatedClassificationNames") { + return; + } col[obj.name] = { label: Enums.systemAttributes[obj.name] ? Enums.systemAttributes[obj.name] : _.escape(obj.name).capitalize(), cell: "Html", @@ -838,6 +841,28 @@ define(['require', 'valueObject': {}, 'isTable': false }; + if (key == "__labels") { + var values = modelObj.attributes[key] ? modelObj.attributes[key].split("|") : null, + valueOfArray = []; + if (values) { + if (values[values.length - 1] === "") { values.pop(); } + if (values[0] === "") { values.shift(); } + _.each(values, function(names) { + valueOfArray.push('<span class="json-string"><a class="btn btn-action btn-sm btn-blue btn-icon" ><span title="" data-original-title="' + names + '" >' + names + '</span></a></span>'); + }); + return valueOfArray.join(' '); + } + } + if (key == "__customAttributes") { + var customAttributes = modelObj.attributes[key] ? JSON.parse(modelObj.attributes[key]) : null, + valueOfArray = []; + if (customAttributes) { + _.each(Object.keys(customAttributes), function(value, index) { + valueOfArray.push('<span class="json-string"><a class="btn btn-action btn-sm btn-blue btn-icon" ><span title="" data-original-title="' + value + ' : ' + Object.values(customAttributes)[index] + '" ><span>' + value + '</span> : <span>' + Object.values(customAttributes)[index] + '</span></span></a></span>'); + }); + return valueOfArray.join(' '); + } + } tempObj.valueObject[key] = modelObj.attributes[key]; var tablecolumn = CommonViewFunction.propertyTable(tempObj); if (_.isArray(modelObj.attributes[key])) {
