[SYNCOPE-1246] Replaced 'property' dropdown field with an autocomplete textfield...
authorskylark17 <matteo.alessandroni@tirasa.net>
Thu, 14 Dec 2017 15:29:59 +0000 (16:29 +0100)
committerskylark17 <matteo.alessandroni@tirasa.net>
Thu, 14 Dec 2017 15:41:05 +0000 (16:41 +0100)
client/console/src/main/java/org/apache/syncope/client/console/panels/search/AbstractSearchPanel.java
client/console/src/main/java/org/apache/syncope/client/console/panels/search/AnyObjectSearchPanel.java
client/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchClausePanel.java
client/console/src/main/java/org/apache/syncope/client/console/rest/GroupRestClient.java
client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxTextFieldPanel.java
client/console/src/main/resources/META-INF/resources/css/syncopeConsole.css

index f96da97..933defc 100644 (file)
@@ -23,7 +23,9 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
+import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.client.console.rest.AnyTypeRestClient;
+import org.apache.syncope.client.console.rest.GroupRestClient;
 import org.apache.syncope.client.console.rest.ResourceRestClient;
 import org.apache.syncope.client.console.rest.SchemaRestClient;
 import org.apache.syncope.client.console.wicket.markup.html.form.MultiFieldPanel;
@@ -33,6 +35,7 @@ import org.apache.syncope.common.lib.to.PlainSchemaTO;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.SchemaType;
 import org.apache.wicket.event.IEventSink;
+import org.apache.wicket.extensions.markup.html.repeater.util.SortParam;
 import org.apache.wicket.markup.html.WebMarkupContainer;
 import org.apache.wicket.markup.html.panel.Panel;
 import org.apache.wicket.model.IModel;
@@ -77,6 +80,8 @@ public abstract class AbstractSearchPanel extends Panel {
 
     protected final boolean enableSearch;
 
+    protected final GroupRestClient groupRestClient = new GroupRestClient();
+
     public abstract static class Builder<T extends AbstractSearchPanel> implements Serializable {
 
         private static final long serialVersionUID = 6308997285778809578L;
@@ -120,6 +125,12 @@ public abstract class AbstractSearchPanel extends Panel {
 
         super(id);
         populate();
+        Pair<IModel<Map<String, String>>, Integer> groupInfo =
+                Pair.of(groupNames, groupRestClient.search("/",
+                        null,
+                        1,
+                        1,
+                        new SortParam<>("name", true)).getTotalCount());
 
         this.model = builder.model;
         this.typeKind = kind;
@@ -136,7 +147,7 @@ public abstract class AbstractSearchPanel extends Panel {
         final SearchClausePanel searchClausePanel = new SearchClausePanel("panel", "panel",
                 Model.of(new SearchClause()),
                 required,
-                types, anames, dnames, groupNames, roleNames, resourceNames);
+                types, anames, dnames, groupInfo, roleNames, resourceNames);
 
         if (enableSearch) {
             searchClausePanel.enableSearch(builder.resultContainer);
index caa3f8a..0a94110 100644 (file)
@@ -22,7 +22,6 @@ import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import org.apache.syncope.client.console.rest.GroupRestClient;
 import org.apache.syncope.common.lib.to.GroupTO;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.wicket.extensions.markup.html.repeater.util.SortParam;
@@ -33,7 +32,7 @@ public class AnyObjectSearchPanel extends AbstractSearchPanel {
 
     private static final long serialVersionUID = -1769527800450203738L;
 
-    private final GroupRestClient groupRestClient = new GroupRestClient();
+    public static final int MAX_GROUP_LIST_CARDINALITY = 30;
 
     public static class Builder extends AbstractSearchPanel.Builder<AnyObjectSearchPanel> {
 
@@ -80,10 +79,15 @@ public class AnyObjectSearchPanel extends AbstractSearchPanel {
 
             @Override
             protected Map<String, String> load() {
-                List<GroupTO> groupTOs = groupRestClient.search("/", null, -1, -1, new SortParam<>("name", true), null);
-
-                final Map<String, String> result = new HashMap<>(groupTOs.size());
-                for (GroupTO group : groupTOs) {
+                List<GroupTO> res = groupRestClient.search("/",
+                        null,
+                        1,
+                        MAX_GROUP_LIST_CARDINALITY,
+                        new SortParam<>("name", true),
+                        null);
+
+                final Map<String, String> result = new HashMap<>(res.size());
+                for (GroupTO group : res) {
                     result.put(group.getKey(), group.getName());
                 }
 
index cc66d40..6b92e12 100644 (file)
@@ -23,21 +23,28 @@ import de.agilecoders.wicket.extensions.markup.html.bootstrap.form.checkbox.boot
 import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.collections4.Transformer;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.client.console.commons.Constants;
 import org.apache.syncope.client.console.panels.search.SearchClause.Comparator;
 import org.apache.syncope.client.console.panels.search.SearchClause.Operator;
 import org.apache.syncope.client.console.panels.search.SearchClause.Type;
+import org.apache.syncope.client.console.rest.GroupRestClient;
 import org.apache.syncope.client.console.rest.RelationshipTypeRestClient;
 import org.apache.syncope.client.console.wicket.ajax.form.IndicatorAjaxEventBehavior;
 import org.apache.syncope.client.console.wicket.ajax.form.IndicatorAjaxFormComponentUpdatingBehavior;
 import org.apache.syncope.client.console.wicket.markup.html.form.AjaxDropDownChoicePanel;
 import org.apache.syncope.client.console.wicket.markup.html.form.AjaxTextFieldPanel;
 import org.apache.syncope.client.console.wicket.markup.html.form.FieldPanel;
+import org.apache.syncope.client.lib.SyncopeClient;
+import org.apache.syncope.common.lib.to.GroupTO;
 import org.apache.syncope.common.lib.to.PlainSchemaTO;
 import org.apache.syncope.common.lib.to.RelationshipTypeTO;
 import org.apache.wicket.AttributeModifier;
@@ -50,6 +57,7 @@ import org.apache.wicket.ajax.attributes.AjaxRequestAttributes;
 import org.apache.wicket.ajax.markup.html.form.AjaxSubmitLink;
 import org.apache.wicket.event.Broadcast;
 import org.apache.wicket.event.IEventSink;
+import org.apache.wicket.extensions.markup.html.repeater.util.SortParam;
 import org.apache.wicket.markup.html.WebMarkupContainer;
 import org.apache.wicket.markup.html.form.CheckBox;
 import org.apache.wicket.markup.html.form.Form;
@@ -74,7 +82,7 @@ public class SearchClausePanel extends FieldPanel<SearchClause> {
 
     private final IModel<List<String>> dnames;
 
-    private final IModel<Map<String, String>> groupNames;
+    private final Pair<IModel<Map<String, String>>, Integer> groupInfo;
 
     private final IModel<List<String>> roleNames;
 
@@ -94,6 +102,8 @@ public class SearchClausePanel extends FieldPanel<SearchClause> {
 
     private IEventSink resultContainer;
 
+    private final GroupRestClient groupRestClient = new GroupRestClient();
+
     public SearchClausePanel(
             final String id,
             final String name,
@@ -102,7 +112,7 @@ public class SearchClausePanel extends FieldPanel<SearchClause> {
             final IModel<List<SearchClause.Type>> types,
             final IModel<Map<String, PlainSchemaTO>> anames,
             final IModel<List<String>> dnames,
-            final IModel<Map<String, String>> groupNames,
+            final Pair<IModel<Map<String, String>>, Integer> groupInfo,
             final IModel<List<String>> roleNames,
             final IModel<List<String>> resourceNames) {
 
@@ -114,7 +124,7 @@ public class SearchClausePanel extends FieldPanel<SearchClause> {
         this.types = types;
         this.anames = anames;
         this.dnames = dnames;
-        this.groupNames = groupNames;
+        this.groupInfo = groupInfo;
         this.roleNames = roleNames;
         this.resourceNames = resourceNames;
 
@@ -133,7 +143,7 @@ public class SearchClausePanel extends FieldPanel<SearchClause> {
         };
 
         searchButtonFragment = new Fragment("operator", "searchButtonFragment", this);
-        searchButtonFragment.add(searchButton.setEnabled(false));
+        searchButtonFragment.add(searchButton.setEnabled(false).setVisible(false));
 
         operatorFragment = new Fragment("operator", "operatorFragment", this);
 
@@ -199,7 +209,7 @@ public class SearchClausePanel extends FieldPanel<SearchClause> {
                         return names;
 
                     case GROUP_MEMBERSHIP:
-                        final List<String> groups = groupNames.getObject().keySet().
+                        final List<String> groups = groupInfo.getLeft().getObject().values().
                                 stream().collect(Collectors.toList());
                         Collections.sort(groups);
                         return groups;
@@ -229,6 +239,7 @@ public class SearchClausePanel extends FieldPanel<SearchClause> {
     public void enableSearch(final IEventSink resultContainer) {
         this.resultContainer = resultContainer;
         this.searchButton.setEnabled(true);
+        this.searchButton.setVisible(true);
 
         field.add(AttributeModifier.replace(
                 "onkeydown",
@@ -354,19 +365,88 @@ public class SearchClausePanel extends FieldPanel<SearchClause> {
             operatorContainer.add(searchButtonFragment);
         }
 
-        final AjaxDropDownChoicePanel<String> property = new AjaxDropDownChoicePanel<>(
-                "property", "property", new PropertyModel<>(searchClause, "property"));
-        property.hideLabel().setRequired(required).setOutputMarkupId(true);
-        property.setChoices(properties);
-        property.getField().add(new IndicatorAjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
+        final AjaxTextFieldPanel property = new AjaxTextFieldPanel(
+                "property",
+                "property",
+                new PropertyModel<String>(searchClause, "property"),
+                false);
+        property.hideLabel().setOutputMarkupId(true).setEnabled(true);
+        property.setChoices(properties.getObject());
+        field.add(property);
 
-            private static final long serialVersionUID = -1107858522700306810L;
+        property.getField().add(AttributeModifier.replace(
+                "onkeydown",
+                Model.of("if(event.keyCode == 13) { event.preventDefault(); }")));
+
+        property.getField().add(new IndicatorAjaxEventBehavior("onkeyup") {
+
+            private static final long serialVersionUID = -7866120562087857309L;
 
             @Override
-            protected void onUpdate(final AjaxRequestTarget target) {
+            protected void onEvent(final AjaxRequestTarget target) {
+                if (field.getModel().getObject() == null || field.getModel().getObject().getType() == null) {
+                    return;
+                }
+
+                if (field.getModel().getObject().getType() == Type.GROUP_MEMBERSHIP) {
+                    target.focusComponent(null);
+                    property.getField().inputChanged();
+                    property.getField().validate();
+                    if (property.getField().isValid()) {
+                        property.getField().valid();
+                        property.getField().updateModel();
+                        String[] inputAsArray = property.getField().getInputAsArray();
+
+                        if (StringUtils.isBlank(property.getField().getInput())
+                                || inputAsArray.length == 0) {
+                            property.setChoices(properties.getObject());
+                        } else {
+                            String inputValue = (inputAsArray.length > 1 && inputAsArray[1] != null)
+                                    ? inputAsArray[1]
+                                    : property.getField().getInput();
+                            inputValue = (inputValue.startsWith("*") && !inputValue.endsWith("*"))
+                                    ? inputValue + "*"
+                                    : (!inputValue.startsWith("*") && inputValue.endsWith("*"))
+                                    ? "*" + inputValue
+                                    : (inputValue.startsWith("*") && inputValue.endsWith("*")
+                                    ? inputValue : "*" + inputValue + "*");
+
+                            if (groupInfo.getRight() > AnyObjectSearchPanel.MAX_GROUP_LIST_CARDINALITY) {
+                                List<GroupTO> filteredGroups =
+                                        groupRestClient.search("/",
+                                                SyncopeClient.getGroupSearchConditionBuilder().
+                                                        is("name").equalToIgnoreCase(inputValue).
+                                                        query(),
+                                                1,
+                                                AnyObjectSearchPanel.MAX_GROUP_LIST_CARDINALITY,
+                                                new SortParam<>("name", true),
+                                                null);
+                                Collection<String> newList =
+                                        CollectionUtils.collect(filteredGroups,
+                                                new Transformer<GroupTO, String>() {
+
+                                            @Override
+                                            public String transform(final GroupTO input) {
+                                                return input.getName();
+                                            }
+                                        });
+
+                                final List<String> names = new ArrayList<>(newList);
+                                Collections.sort(names);
+                                property.setChoices(names);
+                            }
+                        }
+                    }
+                }
+            }
+
+            @Override
+            protected void updateAjaxAttributes(final AjaxRequestAttributes attributes) {
+                super.updateAjaxAttributes(attributes);
+
+                attributes.getAjaxCallListeners().clear();
             }
         });
-        field.add(property);
 
         final AjaxDropDownChoicePanel<SearchClause.Comparator> comparator = new AjaxDropDownChoicePanel<>(
                 "comparator", "comparator", new PropertyModel<>(searchClause, "comparator"));
@@ -459,12 +539,7 @@ public class SearchClausePanel extends FieldPanel<SearchClause> {
                 }
 
                 if (type.getModelObject() == SearchClause.Type.RELATIONSHIP) {
-                    if (comparator.getModelObject() == SearchClause.Comparator.EQUALS
-                            || comparator.getModelObject() == SearchClause.Comparator.NOT_EQUALS) {
-                        property.setEnabled(false);
-                    } else {
-                        property.setEnabled(true);
-                    }
+                    property.setEnabled(true);
 
                     final SearchClause searchClause = new SearchClause();
                     searchClause.setType(Type.valueOf(type.getDefaultModelObjectAsString()));
@@ -483,7 +558,7 @@ public class SearchClausePanel extends FieldPanel<SearchClause> {
 
     private void setFieldAccess(
             final Type type,
-            final AjaxDropDownChoicePanel<String> property,
+            final AjaxTextFieldPanel property,
             final FieldPanel<Comparator> comparator,
             final FieldPanel<String> value) {
 
@@ -504,19 +579,28 @@ public class SearchClausePanel extends FieldPanel<SearchClause> {
                         value.setEnabled(false);
                         value.setModelObject(StringUtils.EMPTY);
                     }
-                    property.setChoiceRenderer(new DefaultChoiceRender());
+
+                    // reload properties list
+                    properties.detach();
+                    property.setChoices(properties.getObject());
                     break;
 
                 case ROLE_MEMBERSHIP:
-                    property.setChoiceRenderer(new DefaultChoiceRender());
                     value.setEnabled(false);
                     value.setModelObject(StringUtils.EMPTY);
+
+                    // reload properties list
+                    properties.detach();
+                    property.setChoices(properties.getObject());
                     break;
 
                 case GROUP_MEMBERSHIP:
-                    property.setChoiceRenderer(new GroupChoiceRender());
                     value.setEnabled(false);
                     value.setModelObject(StringUtils.EMPTY);
+
+                    // reload properties list
+                    properties.detach();
+                    property.setChoices(properties.getObject());
                     break;
 
                 case GROUP_MEMBER:
@@ -526,23 +610,22 @@ public class SearchClausePanel extends FieldPanel<SearchClause> {
                     break;
 
                 case RESOURCE:
-                    property.setChoiceRenderer(new DefaultChoiceRender());
                     value.setEnabled(false);
                     value.setModelObject(StringUtils.EMPTY);
+
+                    // reload properties list
+                    properties.detach();
+                    property.setChoices(properties.getObject());
                     break;
 
                 case RELATIONSHIP:
-                    property.setChoiceRenderer(new DefaultChoiceRender());
-                    if (comparator.getModelObject() == SearchClause.Comparator.IS_NULL
-                            || comparator.getModelObject() == SearchClause.Comparator.IS_NOT_NULL) {
-                        value.setEnabled(false);
-                        value.setModelObject(StringUtils.EMPTY);
-                        property.setEnabled(true);
-                    } else {
-                        value.setEnabled(true);
-                        property.setEnabled(false);
-                        property.setModelObject(null);
-                    }
+                    value.setEnabled(true);
+                    value.setModelObject(StringUtils.EMPTY);
+                    property.setEnabled(true);
+
+                    // reload properties list
+                    properties.detach();
+                    property.setChoices(properties.getObject());
                     break;
 
                 default:
@@ -625,7 +708,7 @@ public class SearchClausePanel extends FieldPanel<SearchClause> {
                                 break;
 
                             case NOT_EQUALS:
-                                display = "NOT WITH";
+                                display = "WITHOUT";
                                 break;
 
                             default:
@@ -662,7 +745,7 @@ public class SearchClausePanel extends FieldPanel<SearchClause> {
                                 break;
 
                             case NOT_EQUALS:
-                                display = "NOT WITH";
+                                display = "WITHOUT";
                                 break;
 
                             default:
@@ -697,7 +780,7 @@ public class SearchClausePanel extends FieldPanel<SearchClause> {
                         break;
                     case "HAS NOT":
                     case "NOT IN":
-                    case "NOT WITH":
+                    case "WITHOUT":
                         res = SearchClause.Comparator.NOT_EQUALS;
                         break;
                     case "NULL":
@@ -739,7 +822,7 @@ public class SearchClausePanel extends FieldPanel<SearchClause> {
     @Override
     public FieldPanel<SearchClause> clone() {
         final SearchClausePanel panel = new SearchClausePanel(
-                getId(), name, null, required, types, anames, dnames, groupNames, roleNames, resourceNames);
+                getId(), name, null, required, types, anames, dnames, groupInfo, roleNames, resourceNames);
         panel.setReadOnly(this.isReadOnly());
         panel.setRequired(this.isRequired());
         if (searchButton.isEnabled()) {
@@ -784,7 +867,7 @@ public class SearchClausePanel extends FieldPanel<SearchClause> {
 
         @Override
         public Object getDisplayValue(final String object) {
-            return groupNames.getObject().get(object);
+            return groupInfo.getLeft().getObject().get(object);
         }
     }
 
index 6f3d0d0..60ec367 100644 (file)
@@ -69,6 +69,23 @@ public class GroupRestClient extends AbstractAnyRestClient<GroupTO, GroupPatch>
         return result;
     }
 
+    public PagedResult<GroupTO> search(
+            final String realm,
+            final String fiql,
+            final int page,
+            final int size,
+            final SortParam<String> sort) {
+
+        PagedResult<GroupTO> res;
+        do {
+            res = getService(GroupService.class).
+                    search(new AnyQuery.Builder().realm(realm).fiql(fiql).page(page).size(size).
+                            orderBy(toOrderBy(sort)).details(false).build());
+        } while (page == -1 && size == -1 && res.getNext() != null);
+
+        return res;
+    }
+
     public void bulkMembersAction(final String key, final BulkMembersActionType actionType) {
         getService(GroupService.class).bulkMembersAction(key, actionType);
     }
index 4b85f66..16b7db5 100644 (file)
@@ -59,6 +59,7 @@ public class AjaxTextFieldPanel extends FieldPanel<String> implements Cloneable
         final AutoCompleteSettings settings = new AutoCompleteSettings();
         settings.setShowCompleteListOnFocusGain(true);
         settings.setShowListOnEmptyInput(true);
+        settings.setCssClassName("custom-autocomplete-box");
 
         field = new AutoCompleteTextField<String>("textField", model, settings) {
 
index 8763e9a..294c978 100644 (file)
@@ -805,19 +805,19 @@ START - Search - AjaxDateTimePicker
 }
 
 .clause .type{
-  width: 120px !important;
+  width: 170px !important;
 }
 
 .clause .type button{
-  width: 120px !important;
+  width: 170px !important;
 }
 
 .clause .property{
-  width: 190px;
+  width: 300px;
 }
 
 .clause .property button{
-  width: 190px;
+  width: 300px;
 }
 
 .clause .comparator{
@@ -857,6 +857,11 @@ START - Search - AjaxDateTimePicker
 .searchBox .input-group{
   margin-top: 1px;
 }
+
+.custom-autocomplete-box li.selected {
+  background-color: #eee;
+}
+
 /**
 END - Search - AjaxDateTimePicker
 */