diff --git a/docs/user_guide.md b/docs/user_guide.md index c4850d3f7a..9e6a27e784 100644 --- a/docs/user_guide.md +++ b/docs/user_guide.md @@ -1505,8 +1505,9 @@ optional arguments: -i COMPONENT_FILE, --import COMPONENT_FILE Path to the source component file which contains multiple file paths. Each file path start with a '+' - (path should be filtered) or '-' (path should not be - filtered) sign. E.g.: + (results from this path should be listed) or '-' + (results from this path should not be listed) sign. + E.g.: +/a/b/x.cpp -/a/b/ Please see the User guide for more information. diff --git a/libcodechecker/libhandlers/cmd.py b/libcodechecker/libhandlers/cmd.py index 2eacba2006..bb31e90a5f 100644 --- a/libcodechecker/libhandlers/cmd.py +++ b/libcodechecker/libhandlers/cmd.py @@ -742,9 +742,10 @@ def __register_add(parser): required=True, help="Path to the source component file which " "contains multiple file paths. Each file " - "path start with a '+' (path should be " - "filtered) or '-' (path should not be " - "filtered) sign. E.g.: \n" + "path start with a '+' (results from this " + "path should be listed) or '-' (results from " + "this path should not be listed) sign. " + "E.g.: \n" " +/a/b/x.cpp\n" " -/a/b/\n" "Please see the User guide for more " diff --git a/www/scripts/codecheckerviewer/SourceComponentManager.js b/www/scripts/codecheckerviewer/SourceComponentManager.js new file mode 100644 index 0000000000..05fa24d4f6 --- /dev/null +++ b/www/scripts/codecheckerviewer/SourceComponentManager.js @@ -0,0 +1,365 @@ +// ------------------------------------------------------------------------- +// The CodeChecker Infrastructure +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// ------------------------------------------------------------------------- + +define([ + 'dojo/dom-construct', + 'dojo/dom-attr', + 'dojo/dom-class', + 'dojo/_base/declare', + 'dojo/data/ItemFileWriteStore', + 'dojox/grid/DataGrid', + 'dijit/ConfirmDialog', + 'dijit/form/Button', + 'dijit/form/SimpleTextarea', + 'dijit/form/TextBox', + 'dijit/layout/BorderContainer', + 'dijit/layout/ContentPane'], +function (dom, domAttr, domClass, declare, ItemFileWriteStore, DataGrid, + ConfirmDialog, Button, SimpleTextarea, TextBox, BorderContainer, + ContentPane) { + + var DeleteComponentDialog = declare(ConfirmDialog, { + constructor : function () { + this._confirmLabel = new ContentPane({ + class : 'delete-confirm-text', + innerHTML : 'You have selected to ' + + 'delete a source component!' + }); + }, + + postCreate : function () { + this.inherited(arguments); + this.addChild(this._confirmLabel); + }, + + onExecute : function () { + CC_SERVICE.removeSourceComponent(this.componentName); + this.listSourceComponent.refreshGrid(); + } + }); + + var EditSourceComponent = declare(ContentPane, { + postCreate : function () { + var that = this; + + this._errorMessage = dom.create('div', {class : 'mbox mbox-error hide'}); + dom.place(this._errorMessage, this.domNode); + + this._componentName = new TextBox({ + class : 'component-name', + placeHolder : 'Name of the source component...' + }); + this._placeFormElement(this._componentName, 'name', 'Name*'); + + this._componentValue = new SimpleTextarea({ + class : 'component-value', + rows : 10, + placeholder : 'Value of the source component. Each line must start ' + + 'with a "+" (results from this path should be listed) or ' + + '"-" (results from this path should not be listed) sign. ' + + 'E.g.: +/a/b/x.cpp or -/a/b/' + }); + this._placeFormElement(this._componentValue, 'value', 'Value*'); + + this._componentDescription = new TextBox({ + class : 'component-description', + placeHolder : 'Description of the source component...' + }); + this._placeFormElement(this._componentDescription, 'description', + 'Description'); + + this._btnCreate = new Button({ + class : 'btn-save', + label : 'Save', + onClick : function () { + var componentName = that._componentName.get('value'); + if (!componentName) { + return that.showError('Component name can not be empty!'); + } + + var componentValue = that._componentValue.get('value'); + if (!componentValue) { + return that.showError('Component value can not be empty!'); + } + + if (!that.isValidComponentValue(componentValue)) { + return that.showError('Component value format is invalid! Every' + + 'line should start with + or - sign followed by one or more ' + + 'character.'); + } + + var componentDescription = that._componentDescription.get('value'); + + // Remove the original component because the user would like to change + // the name. + var origComponentName = that._origComponent + ? that._origComponent.name[0] + : null; + + if (origComponentName && origComponentName !== componentName) { + CC_SERVICE.removeSourceComponent(origComponentName); + } + + CC_SERVICE.addSourceComponent(componentName, componentValue, + componentDescription, function (success) { + if (success) { + that.showSuccess('The component has been successfully ' + + 'created/edited!'); + that.sourceComponentManager.updateNeeded = true; + + // Reset items of the filter tooltip. + that.sourceComponentFilter._filterTooltip.reset(); + } else { + that.showError('Failed to create/edit component!'); + } + }).fail(function (jsReq, status, exc) { + if (status === 'parsererror') { + that.showError(exc.message); + } + }); + } + }); + this.addChild(this._btnCreate); + }, + + isValidComponentValue : function (value) { + var lines = value.split(/\r|\n/); + for (var i = 0; i < lines.length; ++i) { + if (!lines[i].startsWith('+') && !lines[i].startsWith('-') || + lines[i].trim().length < 2 + ) { + return false; + } + } + return true; + }, + + hideError : function () { + domClass.add(this._errorMessage, 'hide'); + }, + + showSuccess : function (msg) { + this._errorMessage.innerHTML = msg; + domClass.remove(this._errorMessage, 'hide'); + domClass.remove(this._errorMessage, 'mbox-error'); + domClass.add(this._errorMessage, 'mbox-success'); + }, + + showError : function (msg) { + this._errorMessage.innerHTML = msg; + domClass.remove(this._errorMessage, 'hide'); + domClass.remove(this._errorMessage, 'mbox-success'); + domClass.add(this._errorMessage, 'mbox-error'); + }, + + _placeFormElement : function (element, key, label) { + this.addChild(element); + + var container = dom.create('div', { + class : 'formElement' + }, this.containerNode); + + if (label) { + var labelNode = dom.create('label', { + class : 'formLabel bold', + innerHTML : label + ': ' + }, container); + + if (key) { + domAttr.set(labelNode, 'for', key); + } + } + + dom.place(element.domNode, container); + }, + + init : function (component) { + if (component) { + this._componentName.set('value', component.name[0]); + this._componentValue.set('value', component.value[0]); + this._componentDescription.set('value', component.description[0]); + this._origComponent = component; + } else { + this._componentName.set('value', null); + this._componentValue.set('value', null); + this._componentDescription.set('value', null); + this._origComponent = null; + } + + this.hideError(); + } + }); + + function componentValueFormatter(value) { + return value.split(/\r|\n/).map(function (line) { + if (line.startsWith('+')) { + return '' + line + ''; + } else { + return '' + line + ''; + } + }).join('
'); + } + + var ListSourceComponent = declare(DataGrid, { + constructor : function () { + this.store = new ItemFileWriteStore({ + data : { identifier : 'id', items : [] } + }); + + this.structure = [ + { name : 'Name', field : 'name', styles : 'text-align: left;', width : '100%' }, + { name : 'Value', field : 'value', styles : 'text-align: left;', width : '100%', formatter: componentValueFormatter }, + { name : 'Description', field : 'description', styles : 'text-align: left;', width : '100%' }, + { name : ' ', field : 'editIcon', cellClasses : 'status', width : '20px', noresize : true }, + { name : ' ', field : 'deleteIcon', cellClasses : 'status', width : '20px', noresize : true } + ]; + + this.focused = true; + this.selectable = true; + this.keepSelection = true; + this.escapeHTMLInData = false; + this.autoHeight = false; + }, + + postCreate : function () { + this.inherited(arguments); + + this._confirmDeleteDialog = new DeleteComponentDialog({ + title : 'Confirm deletion of component', + listSourceComponent : this + }); + }, + + onRowClick : function (evt) { + var item = this.getItem(evt.rowIndex); + switch (evt.cell.field) { + case 'editIcon': + this.editComponent(item); + break; + case 'deleteIcon': + this.removeComponent(item.name[0]); + break; + } + }, + + removeComponent : function (name) { + this._confirmDeleteDialog.componentName = name; + this._confirmDeleteDialog.show(); + }, + + editComponent : function (component) { + this.sourceComponentManager.showNewComponentPage(component); + }, + + refreshGrid : function () { + var that = this; + + this.store.fetch({ + onComplete : function (sourceComponents) { + sourceComponents.forEach(function (component) { + that.store.deleteItem(component); + }); + that.store.save(); + } + }); + + CC_SERVICE.getSourceComponents(null, function (sourceComponents) { + sourceComponents.forEach(function (item) { + that._addSourceComponent(item); + }); + }); + }, + + _addSourceComponent : function (component) { + this.store.newItem({ + id : component.name, + name : component.name, + value : component.value, + description : component.description, + editIcon : '', + deleteIcon : '' + }); + }, + + onShow : function () { + if (this.sourceComponentManager.updateNeeded) { + this.refreshGrid(); + this.sourceComponentManager.updateNeeded = false; + } + } + }); + + return declare(ContentPane, { + updateNeeded : true, + + postCreate : function () { + var that = this; + + this._wrapper = new BorderContainer({ + class : 'component-manager-wrapper' + }); + + this.addChild(this._wrapper); + + this._btnNew = new Button({ + class : 'btn-new', + region : 'top', + label : 'New', + onClick : function () { + that.showNewComponentPage(); + } + }); + + this._btnBack = new Button({ + class : 'btn-back', + region : 'top', + label : 'Back', + onClick : function () { + that.showListOfComponentPage(); + } + }); + + this._editSourceComponent = new EditSourceComponent({ + region : 'center', + sourceComponentManager : this, + sourceComponentFilter : this.sourceComponentFilter + }); + + this._listSourceComponent = new ListSourceComponent({ + region : 'center', + editSourceComponent : this._editSourceComponent, + sourceComponentManager : this + }); + }, + + showNewComponentPage : function (component) { + this._clearDom(); + + this._editSourceComponent.init(component); + + this._wrapper.addChild(this._btnBack); + this._wrapper.addChild(this._editSourceComponent); + }, + + showListOfComponentPage : function () { + this._clearDom(); + + this._wrapper.addChild(this._btnNew); + this._wrapper.addChild(this._listSourceComponent); + this._listSourceComponent.onShow(); + }, + + refreshGrid : function () { + this._listSourceComponent.refreshGrid(); + }, + + _clearDom : function () { + this._wrapper.getChildren().forEach(function (child) { + this.removeChild(child); + }, this); + } + }); +}); diff --git a/www/scripts/codecheckerviewer/codecheckerviewer.js b/www/scripts/codecheckerviewer/codecheckerviewer.js index 67ad1edf4f..2d37185b18 100644 --- a/www/scripts/codecheckerviewer/codecheckerviewer.js +++ b/www/scripts/codecheckerviewer/codecheckerviewer.js @@ -106,6 +106,8 @@ function (declare, cookie, topic, Lightbox, Dialog, Button, BorderContainer, var currentProductName = util.atou(CURRENT_PRODUCT.displayedName_b64); document.title = currentProductName + ' - CodeChecker'; + IS_ADMIN_OF_ANY_PRODUCT = CC_PROD_SERVICE.isAdministratorOfAnyProduct(); + //--- Back button to product list ---// var productListButton = new Button({ diff --git a/www/scripts/codecheckerviewer/filter/SourceComponentFilter.js b/www/scripts/codecheckerviewer/filter/SourceComponentFilter.js index 9809dc22f8..ee8a1615b6 100644 --- a/www/scripts/codecheckerviewer/filter/SourceComponentFilter.js +++ b/www/scripts/codecheckerviewer/filter/SourceComponentFilter.js @@ -5,17 +5,52 @@ // ------------------------------------------------------------------------- define([ - 'dojo/_base/declare', 'dojo/dom-construct', + 'dojo/_base/declare', 'dojo/Deferred', - 'codechecker/filter/SelectFilter'], -function (declare, dom, Deferred, SelectFilter) { + 'dijit/Dialog', + 'codechecker/filter/SelectFilter', + 'codechecker/SourceComponentManager'], +function (dom, declare, Deferred, Dialog, SelectFilter, + SourceComponentManager) { + return declare(SelectFilter, { search : { enable : true, placeHolder : 'Search for source components...' }, + postCreate : function () { + this.inherited(arguments); + var that = this; + + // Source components can be managed only by administrators. + if (IS_ADMIN_OF_ANY_PRODUCT) { + this._edit = dom.create('i', { + class : 'customIcon edit', + onclick : function () { + that._manageDialog.show(); + } + }, this._clean, 'before'); + + // Source component manager. + this._manageDialog = new Dialog({ + title : 'Manage source components', + style : 'width: 50%;', + onShow : function () { + that._sourceComponentManager.showListOfComponentPage(); + } + }); + + this._sourceComponentManager = new SourceComponentManager({ + class : 'source-component-manager', + style : 'height: 450px;', + sourceComponentFilter : this + }); + this._manageDialog.addChild(this._sourceComponentManager); + } + }, + formatDescription : function (value) { var list = dom.create('ul', { class : 'component-description'}); value.split('\n').forEach(function (item) { diff --git a/www/style/bugfilter.css b/www/style/bugfilter.css index cb20e91cbb..3057814670 100644 --- a/www/style/bugfilter.css +++ b/www/style/bugfilter.css @@ -115,8 +115,10 @@ } .bug-filters .clean, -.bug-filters .cogwheel { +.bug-filters .cogwheel, +.bug-filters .edit { cursor: pointer; + color: #878d96; } .bug-filters .clean::before { @@ -322,3 +324,7 @@ width: 100%; margin: 5px 0px; } + +.component-manager-wrapper .dijitBorderContainer-child { + border: 0px; +} diff --git a/www/style/codecheckerviewer.css b/www/style/codecheckerviewer.css index 9dbc402ce5..d8f7ffb122 100644 --- a/www/style/codecheckerviewer.css +++ b/www/style/codecheckerviewer.css @@ -418,6 +418,7 @@ html, body { .mbox.mbox-error { border-color: #e92625; + padding: 5px; } .mbox.mbox-error .heading { @@ -658,3 +659,45 @@ html, body { .tab-new-features-label { font-weight: bold; } + +/* Form labels */ + +.formElement { + display: block; + margin: 2px; + padding: 4px; +} + +.formElement .formLabel { + display: inline-block; + margin-left: 5px; + padding-right: 6px; + width: 200px; +} + +.formElement .formLabel.bold { + font-weight: bold; +} + +/* Source Component Manager */ +.source-component-manager .component-name, +.source-component-manager .component-value, +.source-component-manager .component-description { + width: 50%; +} + +.component-dialog .formLabel { + width: 100px; +} + +.source-component-manager .edit, +.source-component-manager .delete { + cursor: pointer; +} + +.source-component-manager .edit { + color: #5b6169; +} +.source-component-manager .delete { + color: #d63434; +} diff --git a/www/style/productlist.css b/www/style/productlist.css index 26069e5126..486e0324fa 100644 --- a/www/style/productlist.css +++ b/www/style/productlist.css @@ -116,23 +116,6 @@ span.product-avatar { /*** Product configuration dialog ***/ -.formElement { - display: block; - margin: 2px; - padding: 4px; -} - -.formElement .formLabel { - display: inline-block; - margin-left: 5px; - padding-right: 6px; - width: 200px; -} - -.formElement .formLabel.bold { - font-weight: bold; -} - .formElement .form-input { width: 350px; }