diff --git a/src/wirecloud/catalogue/searchers.py b/src/wirecloud/catalogue/searchers.py index 62a0d18bbc..774c89a3cb 100644 --- a/src/wirecloud/catalogue/searchers.py +++ b/src/wirecloud/catalogue/searchers.py @@ -52,6 +52,8 @@ class CatalogueResourceSchema(fields.SchemaClass): users = fields.KEYWORD(commas=True) groups = fields.KEYWORD(commas=True) content = fields.NGRAMWORDS() + input_friendcodes = fields.KEYWORD() + output_friendcodes = fields.KEYWORD() class CatalogueResourceSearcher(BaseSearcher): @@ -66,12 +68,16 @@ def build_compatible_fields(self, resource): resource_info = resource.get_processed_info(process_urls=False) endpoint_descriptions = '' + input_friendcodes = [] + output_friendcodes = [] for endpoint in resource_info['wiring']['inputs']: endpoint_descriptions += endpoint['description'] + ' ' + input_friendcodes.extend(endpoint['friendcode'].split(' ')) for endpoint in resource_info['wiring']['outputs']: endpoint_descriptions += endpoint['description'] + ' ' + output_friendcodes.extend(endpoint['friendcode'].split(' ')) fields = { 'pk': '%s' % resource.pk, @@ -90,6 +96,8 @@ def build_compatible_fields(self, resource): 'smartphoneimage': resource_info['smartphoneimage'], 'users': ', '.join(resource.users.all().values_list('username', flat=True)), 'groups': ', '.join(resource.groups.all().values_list('name', flat=True)), + 'input_friendcodes': ' '.join(set(input_friendcodes)), + 'output_friendcodes': ' '.join(set(output_friendcodes)), } return fields diff --git a/src/wirecloud/commons/static/js/wirecloud/ui/MACSearch.js b/src/wirecloud/commons/static/js/wirecloud/ui/MACSearch.js index 8260e7b64f..701d4295f3 100644 --- a/src/wirecloud/commons/static/js/wirecloud/ui/MACSearch.js +++ b/src/wirecloud/commons/static/js/wirecloud/ui/MACSearch.js @@ -78,6 +78,10 @@ }; utils.inherit(MACSearch, StyledElements.StyledElement); + MACSearch.prototype.search = function search(keywords) { + this.inputField.setValue(keywords); + }; + MACSearch.prototype.paintInfo = function paintInfo(message, context) { if (context != null) { message = builder.parse(builder.DEFAULT_OPENING + message + builder.DEFAULT_CLOSING, context); diff --git a/src/wirecloud/commons/test-data/Wirecloud_TestOperator_FriendcodeList_2.1.zip b/src/wirecloud/commons/test-data/Wirecloud_TestOperator_FriendcodeList_2.1.zip new file mode 100644 index 0000000000..b52a14b477 Binary files /dev/null and b/src/wirecloud/commons/test-data/Wirecloud_TestOperator_FriendcodeList_2.1.zip differ diff --git a/src/wirecloud/commons/test-data/Wirecloud_Test_FriendcodeList_3.1.wgt b/src/wirecloud/commons/test-data/Wirecloud_Test_FriendcodeList_3.1.wgt new file mode 100644 index 0000000000..b4d0e7f585 Binary files /dev/null and b/src/wirecloud/commons/test-data/Wirecloud_Test_FriendcodeList_3.1.wgt differ diff --git a/src/wirecloud/commons/utils/remote.py b/src/wirecloud/commons/utils/remote.py index 1732c1b06a..1c8ef067f5 100644 --- a/src/wirecloud/commons/utils/remote.py +++ b/src/wirecloud/commons/utils/remote.py @@ -1075,6 +1075,10 @@ def name(self): def title(self): return self.find_element(".endpoint-title").text + @property + def btn_preferences(self): + return ButtonTester(self.testcase, self.find_element(".we-prefs-btn")) + def change_position(self, endpoint): new_index = endpoint.index actions = ActionChains(self.testcase.driver).click_and_hold(self.element) @@ -1117,6 +1121,10 @@ def mouse_over(self, must_recommend=()): self.testcase.assertTrue(endpoint.is_active) return self + def show_preferences(self): + button = self.btn_preferences.click() + return PopupMenuTester(self.testcase, self.testcase.wait_element_visible(".se-popup-menu"), button) + class WiringBehaviourTester(WebElementTester): @@ -1881,13 +1889,15 @@ def has_behaviours(self): class WiringComponentSidebarTester(BaseWiringViewTester): def __enter__(self): - self.btn_components.click() + if not self.btn_components.is_active: + self.btn_components.click() self.element = self.testcase.driver.find_element_by_css_selector(".wc-workspace-wiring .we-panel-components") WebDriverWait(self.testcase.driver, timeout=5).until(WEC.element_be_still(self.element)) return self def __exit__(self, type, value, traceback): - self.btn_components.click() + if self.btn_components.is_active: + self.btn_components.click() WebDriverWait(self.testcase.driver, timeout=5).until(WEC.element_be_still(self.element)) @property diff --git a/src/wirecloud/defaulttheme/static/css/wiring/components.scss b/src/wirecloud/defaulttheme/static/css/wiring/components.scss index 73d50b027f..7610b07c2e 100644 --- a/src/wirecloud/defaulttheme/static/css/wiring/components.scss +++ b/src/wirecloud/defaulttheme/static/css/wiring/components.scss @@ -264,6 +264,24 @@ padding: ($endpoint-text-height / 2) ($endpoint-anchor-width / 2); } + .endpoint-actions { + position: absolute; + top: -1px; + + .se-btn { + padding: 0; + height: 20px; + width: 20px; + line-height: 20px; + font-size: 12px; + margin: 0; + background-image: none; + box-shadow: none; + border-color: rgba(0, 0, 0, 0); + background-color: transparent; + } + } + &:first-child { margin-top: $endpoint-margin-vertical; } @@ -275,6 +293,17 @@ left, $source-endpoint-anchor-bg ); + + .endpoint { + + .endpoint-title { + padding-left: 20px; + } + + .endpoint-actions { + left: 0; + } + } } &.target-endpoints { @@ -283,6 +312,17 @@ right, $target-endpoint-anchor-bg ); + + .endpoint { + + .endpoint-title { + padding-right: 20px; + } + + .endpoint-actions { + right: 0; + } + } } &:empty { @@ -302,6 +342,15 @@ .endpoint-anchor { background-color: $endpoint-active-bg; } + + .endpoint-actions > .se-btn { + color: rgb(255, 255 ,255); + + &:hover, + &:focus { + background-color: darken($endpoint-active-bg, 10%); + } + } } .endpoint.missing .endpoint-anchor { diff --git a/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor.js b/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor.js index d441ab3cb6..1e1c2e87cf 100644 --- a/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor.js +++ b/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor.js @@ -124,6 +124,34 @@ Wirecloud.ui = Wirecloud.ui || {}; component.forEachEndpoint(bindEndpoint.bind(this)); this.initialMessage.hide(); + var subMenuItem = new se.SubMenuItem(utils.gettext("Search component...")); + subMenuItem.menuItem.addIconClass("fa fa-search"); + + var menuItem1 = new se.MenuItem(utils.gettext("For both endpoints"), function () { + if (!this.btnFindComponents.active) { + this.btnFindComponents.click(); + } + this.componentManager.searchComponents.search("input_friendcodes:(" + component.sourceFriendcodes.join(" OR ") + ") OR " + "output_friendcodes:(" + component.targetFriendcodes.join(" OR ") + ") NOT (vendor:" + component._component.meta.vendor + " AND name:" + component._component.meta.name + ")"); + }.bind(this)); + var menuItem2 = new se.MenuItem(utils.gettext("For input endpoints"), function () { + if (!this.btnFindComponents.active) { + this.btnFindComponents.click(); + } + this.componentManager.searchComponents.search("output_friendcodes:(" + component.targetFriendcodes.join(" OR ") + ") NOT (vendor:" + component._component.meta.vendor + " AND name:" + component._component.meta.name + ")"); + }.bind(this)); + var menuItem3 = new se.MenuItem(utils.gettext("For output endpoints"), function () { + if (!this.btnFindComponents.active) { + this.btnFindComponents.click(); + } + this.componentManager.searchComponents.search("input_friendcodes:(" + component.sourceFriendcodes.join(" OR ") + ") NOT (vendor:" + component._component.meta.vendor + " AND name:" + component._component.meta.name + ")"); + }.bind(this)); + + subMenuItem.append(menuItem1); + subMenuItem.append(menuItem2); + subMenuItem.append(menuItem3); + + component.btnPrefs.popup_menu.append(subMenuItem); + if (options.commit) { this.layout.content.appendChild(component); this.behaviourEngine.updateComponent(component, component.toJSON()); @@ -227,6 +255,18 @@ Wirecloud.ui = Wirecloud.ui || {}; this.connectionEngine.appendEndpoint(endpoint); this.suggestionManager.appendEndpoint(endpoint); + var menuItem = new se.MenuItem(utils.gettext("Search components"), function () { + + if (!this.btnFindComponents.active) { + this.btnFindComponents.click(); + } + + this.componentManager.searchComponents.search((endpoint.type === "source" ? "input" : "output") + "_friendcodes:(" + endpoint.keywords.join(" OR ") + ") NOT (vendor:" + endpoint.component._component.meta.vendor + " AND name:" + endpoint.component._component.meta.name + ")"); + }.bind(this)); + menuItem.addIconClass("fa fa-search"); + + endpoint.btnPrefs.popup_menu.append(menuItem); + endpoint .addEventListener('mouseenter', function () { if (!this.connectionEngine.temporalConnection) { diff --git a/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor/ComponentDraggable.js b/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor/ComponentDraggable.js index e36aab7549..7938120e27 100644 --- a/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor/ComponentDraggable.js +++ b/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor/ComponentDraggable.js @@ -122,6 +122,12 @@ get: function get() {return this.endpoints.source.endpoints;} }, + sourceFriendcodes: { + get: function get() { + return find_friendcodes_by_type.call(this, 'source'); + } + }, + sourceList: { get: function get() {return this.endpoints.source.children;} }, @@ -130,6 +136,12 @@ get: function get() {return this.endpoints.target.endpoints;} }, + targetFriendcodes: { + get: function get() { + return find_friendcodes_by_type.call(this, 'target'); + } + }, + targetList: { get: function get() {return this.endpoints.target.children;} }, @@ -786,4 +798,19 @@ } }; + var find_friendcodes_by_type = function find_friendcodes_by_type(endpoint_type) { + /* jshint validthis: true */ + var friendcodes = []; + + this.endpoints[endpoint_type].children.forEach(function (endpoint) { + endpoint.keywords.forEach(function (friendcode) { + if (friendcodes.indexOf(friendcode) < 0) { + friendcodes.push(friendcode); + } + }); + }); + + return friendcodes; + }; + })(Wirecloud.ui.WiringEditor, StyledElements, StyledElements.Utils); diff --git a/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor/Endpoint.js b/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor/Endpoint.js index 301b531009..d536902e44 100644 --- a/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor/Endpoint.js +++ b/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor/Endpoint.js @@ -56,6 +56,17 @@ this.anchorElement.className = "endpoint-anchor"; this.wrapperElement.appendChild(this.anchorElement); + this.optionsElement = document.createElement('span'); + this.optionsElement.className = "endpoint-actions"; + this.wrapperElement.appendChild(this.optionsElement); + + this.btnPrefs = new se.PopupButton({ + title: utils.gettext("More options"), + class: "we-prefs-btn", + iconClass: "fa fa-ellipsis-v" + }); + this.btnPrefs.appendTo(this.optionsElement); + this._endpoint = wiringEndpoint; this.component = component; diff --git a/src/wirecloud/platform/wiring/tests.py b/src/wirecloud/platform/wiring/tests.py index b9c65c86da..941d1c1b4f 100644 --- a/src/wirecloud/platform/wiring/tests.py +++ b/src/wirecloud/platform/wiring/tests.py @@ -1772,6 +1772,76 @@ def test_ordering_widget_endpoints(self): self.assertEqual(targets_count, len(widget.find_endpoints('target'))) self.assertEqual(sources_count, len(widget.find_endpoints('source'))) + @uses_extra_resources(('Wirecloud_TestOperator_FriendcodeList_2.1.zip','Wirecloud_Test_FriendcodeList_3.1.wgt'), shared=True) + def test_search_components_by_component_both_endpoints(self): + self.login(username='user_with_workspaces', next='/user_with_workspaces/ExistingWorkspace') + + with self.wiring_view as wiring: + with wiring.component_sidebar as sidebar: + operator = sidebar.add_component('operator', "Wirecloud/TestOperator") + + operator.show_preferences().click_entry(("Search component...", "For both endpoints")) + + with wiring.component_sidebar as sidebar: + self.assertEqual(len(sidebar.find_component_groups('operator')), 0) + self.assertEqual(len(sidebar.find_component_groups('widget')), 1) + + @uses_extra_resources(('Wirecloud_TestOperator_FriendcodeList_2.1.zip','Wirecloud_Test_FriendcodeList_3.1.wgt'), shared=True) + def test_search_components_by_component_input_endpoints(self): + self.login(username='user_with_workspaces', next='/user_with_workspaces/ExistingWorkspace') + + with self.wiring_view as wiring: + with wiring.component_sidebar as sidebar: + operator = sidebar.add_component('operator', "Wirecloud/TestOperator") + + operator.show_preferences().click_entry(("Search component...", "For input endpoints")) + + with wiring.component_sidebar as sidebar: + self.assertEqual(len(sidebar.find_component_groups('operator')), 0) + self.assertEqual(len(sidebar.find_component_groups('widget')), 1) + + @uses_extra_resources(('Wirecloud_TestOperator_FriendcodeList_2.1.zip','Wirecloud_Test_FriendcodeList_3.1.wgt'), shared=True) + def test_search_components_by_component_output_endpoints(self): + self.login(username='user_with_workspaces', next='/user_with_workspaces/ExistingWorkspace') + + with self.wiring_view as wiring: + with wiring.component_sidebar as sidebar: + operator = sidebar.add_component('operator', "Wirecloud/TestOperator") + + operator.show_preferences().click_entry(("Search component...", "For output endpoints")) + + with wiring.component_sidebar as sidebar: + self.assertEqual(len(sidebar.find_component_groups('operator')), 0) + self.assertEqual(len(sidebar.find_component_groups('widget')), 1) + + @uses_extra_resources(('Wirecloud_TestOperator_FriendcodeList_2.1.zip','Wirecloud_Test_FriendcodeList_3.1.wgt'), shared=True) + def test_search_components_by_component_specific_input_endpoint(self): + self.login(username='user_with_workspaces', next='/user_with_workspaces/ExistingWorkspace') + + with self.wiring_view as wiring: + with wiring.component_sidebar as sidebar: + operator = sidebar.add_component('operator', "Wirecloud/TestOperator") + + operator.find_endpoint('target', name="input").show_preferences().click_entry("Search components") + + with wiring.component_sidebar as sidebar: + self.assertEqual(len(sidebar.find_component_groups('operator')), 0) + self.assertEqual(len(sidebar.find_component_groups('widget')), 1) + + @uses_extra_resources(('Wirecloud_TestOperator_FriendcodeList_2.1.zip','Wirecloud_Test_FriendcodeList_3.1.wgt'), shared=True) + def test_search_components_by_component_specific_output_endpoint(self): + self.login(username='user_with_workspaces', next='/user_with_workspaces/ExistingWorkspace') + + with self.wiring_view as wiring: + with wiring.component_sidebar as sidebar: + operator = sidebar.add_component('operator', "Wirecloud/TestOperator") + + operator.find_endpoint('source', name="output-test").show_preferences().click_entry("Search components") + + with wiring.component_sidebar as sidebar: + self.assertEqual(len(sidebar.find_component_groups('operator')), 0) + self.assertEqual(len(sidebar.find_component_groups('widget')), 1) + @wirecloud_selenium_test_case class BehaviourManagementTestCase(WirecloudSeleniumTestCase):