This is an automated email from the ASF dual-hosted git repository. mdisabatino pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/syncope.git
The following commit(s) were added to refs/heads/master by this push: new 7326c25 [SYNCOPE-1619] SearchPanel improvement (#243) 7326c25 is described below commit 7326c254f1e3ed1519ccdb7a1e5f26fe23d28d7d Author: mdisabatino <marco.disabat...@tirasa.net> AuthorDate: Fri Feb 26 14:29:31 2021 +0100 [SYNCOPE-1619] SearchPanel improvement (#243) --- .../panels/search/ConnObjectSearchPanel.java | 9 +- .../META-INF/resources/ui-commons/css/search.scss | 12 +- .../panels/DisplayAttributesModalPanel.java | 4 +- .../console/panels/search/AbstractSearchPanel.java | 15 +- .../console/panels/search/SearchClausePanel.java | 197 ++++++++++++++++++--- .../client/console/panels/search/SearchUtils.java | 8 +- .../common/lib/search/SearchableFields.java | 19 +- .../syncope/common/lib/types/AttrSchemaType.java | 7 + 8 files changed, 219 insertions(+), 52 deletions(-) diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/search/ConnObjectSearchPanel.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/search/ConnObjectSearchPanel.java index 833baf1..b078a40 100644 --- a/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/search/ConnObjectSearchPanel.java +++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/search/ConnObjectSearchPanel.java @@ -18,6 +18,7 @@ */ package org.apache.syncope.client.console.panels.search; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.function.Function; @@ -84,17 +85,17 @@ public class ConnObjectSearchPanel extends AbstractSearchPanel { @Override protected List<SearchClause.Type> load() { - return List.of(SearchClause.Type.ATTRIBUTE); + return Collections.singletonList(SearchClause.Type.ATTRIBUTE); } }; - this.dnames = new LoadableDetachableModel<List<String>>() { + this.dnames = new LoadableDetachableModel<Map<String, PlainSchemaTO>>() { private static final long serialVersionUID = 2989042618372L; @Override - protected List<String> load() { - return List.of(); + protected Map<String, PlainSchemaTO> load() { + return Collections.emptyMap(); } }; diff --git a/client/idrepo/common-ui/src/main/resources/META-INF/resources/ui-commons/css/search.scss b/client/idrepo/common-ui/src/main/resources/META-INF/resources/ui-commons/css/search.scss index 453f5c7..493c7f4 100644 --- a/client/idrepo/common-ui/src/main/resources/META-INF/resources/ui-commons/css/search.scss +++ b/client/idrepo/common-ui/src/main/resources/META-INF/resources/ui-commons/css/search.scss @@ -31,7 +31,7 @@ } } .field { - line-height: 34px; + line-height: 30px; float: left; padding: 0 3px 0px 0px; display: inline-block !important; @@ -63,7 +63,7 @@ } } .value { - width: 250px; + width: 320px; } .date { width: 160px; @@ -93,3 +93,11 @@ .custom-autocomplete-box li.selected { background-color: #eee; } + +.search-spinner { + line-height: 23px !important; +} + +.search-spinner span{ + width: 70% !important; +} diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DisplayAttributesModalPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DisplayAttributesModalPanel.java index 19d95fe..78f611b 100644 --- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DisplayAttributesModalPanel.java +++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DisplayAttributesModalPanel.java @@ -23,6 +23,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import org.apache.syncope.client.console.PreferenceManager; import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.syncope.client.ui.commons.Constants; @@ -75,7 +76,8 @@ public abstract class DisplayAttributesModalPanel<T extends Serializable> extend super(modal, pageRef); this.type = type; - final List<String> detailslList = SearchableFields.get(DisplayAttributesModalPanel.getTOClass(type)); + final List<String> detailslList = SearchableFields.get(DisplayAttributesModalPanel.getTOClass(type)) + .keySet().stream().collect(Collectors.toList()); Collections.sort(detailslList); Collections.sort(pSchemaNames); Collections.sort(dSchemaNames); diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/search/AbstractSearchPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/search/AbstractSearchPanel.java index 1ff07ae..5dff765 100644 --- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/search/AbstractSearchPanel.java +++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/search/AbstractSearchPanel.java @@ -19,6 +19,7 @@ package org.apache.syncope.client.console.panels.search; import java.io.Serializable; +import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.lang3.tuple.Pair; @@ -46,7 +47,7 @@ public abstract class AbstractSearchPanel extends Panel { protected static final Logger LOG = LoggerFactory.getLogger(AbstractSearchPanel.class); - protected IModel<List<String>> dnames; + protected IModel<Map<String, PlainSchemaTO>> dnames; protected IModel<Map<String, PlainSchemaTO>> anames; @@ -178,13 +179,19 @@ public abstract class AbstractSearchPanel extends Panel { } protected void populate() { - dnames = new LoadableDetachableModel<List<String>>() { + dnames = new LoadableDetachableModel<Map<String, PlainSchemaTO>>() { private static final long serialVersionUID = 5275935387613157437L; @Override - protected List<String> load() { - return SearchableFields.get(typeKind.getTOClass()); + protected Map<String, PlainSchemaTO> load() { + Map<String, PlainSchemaTO> dSchemaNames = new HashMap<>(); + SearchableFields.get(typeKind.getTOClass()).forEach((key, type) -> { + PlainSchemaTO plain = new PlainSchemaTO(); + plain.setType(type); + dSchemaNames.put(key, plain); + }); + return dSchemaNames; } }; diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchClausePanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchClausePanel.java index 7e613a6..a9bfc71 100644 --- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchClausePanel.java +++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchClausePanel.java @@ -21,7 +21,11 @@ package org.apache.syncope.client.console.panels.search; import de.agilecoders.wicket.extensions.markup.html.bootstrap.form.checkbox.bootstraptoggle.BootstrapToggle; import de.agilecoders.wicket.extensions.markup.html.bootstrap.form.checkbox.bootstraptoggle.BootstrapToggleConfig; import java.io.Serializable; +import java.text.ParseException; +import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; import java.util.List; import java.util.Map; import java.util.Optional; @@ -29,6 +33,7 @@ import java.util.function.Consumer; import java.util.stream.Collectors; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.time.FastDateFormat; import org.apache.commons.lang3.tuple.Pair; import org.apache.syncope.client.ui.commons.Constants; import org.apache.syncope.client.console.panels.search.SearchClause.Comparator; @@ -42,10 +47,14 @@ import org.apache.syncope.client.ui.commons.markup.html.form.AjaxDropDownChoiceP import org.apache.syncope.client.ui.commons.markup.html.form.AjaxTextFieldPanel; import org.apache.syncope.client.ui.commons.markup.html.form.FieldPanel; import org.apache.syncope.client.lib.SyncopeClient; +import org.apache.syncope.client.ui.commons.SchemaUtils; +import org.apache.syncope.client.ui.commons.markup.html.form.AjaxDateTimeFieldPanel; +import org.apache.syncope.client.ui.commons.markup.html.form.AjaxSpinnerFieldPanel; import org.apache.syncope.common.lib.SyncopeConstants; 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.syncope.common.lib.types.AttrSchemaType; import org.apache.wicket.AttributeModifier; import org.apache.wicket.Component; import org.apache.wicket.MarkupContainer; @@ -73,6 +82,8 @@ public class SearchClausePanel extends FieldPanel<SearchClause> { private static final long serialVersionUID = -527351923968737757L; + private static final ThreadLocal<SimpleDateFormat> DATE_FORMAT = ThreadLocal.withInitial(SimpleDateFormat::new); + protected static final AttributeModifier PREVENT_DEFAULT_RETURN = AttributeModifier.replace( "onkeydown", Model.of("if (event.keyCode == 13) { event.preventDefault(); }")); @@ -111,7 +122,7 @@ public class SearchClausePanel extends FieldPanel<SearchClause> { } default void setFieldAccess( - AjaxTextFieldPanel value, + FieldPanel value, AjaxTextFieldPanel property, LoadableDetachableModel<List<String>> properties) { @@ -133,7 +144,7 @@ public class SearchClausePanel extends FieldPanel<SearchClause> { private final IModel<Map<String, PlainSchemaTO>> anames; - private final IModel<List<String>> dnames; + private final IModel<Map<String, PlainSchemaTO>> dnames; private final Pair<IModel<List<String>>, IModel<Integer>> groupInfo; @@ -157,6 +168,9 @@ public class SearchClausePanel extends FieldPanel<SearchClause> { private IEventSink resultContainer; + @SuppressWarnings({ "rawtypes", "unchecked" }) + private FieldPanel value; + private final GroupRestClient groupRestClient = new GroupRestClient(); public SearchClausePanel( @@ -167,7 +181,7 @@ public class SearchClausePanel extends FieldPanel<SearchClause> { final IModel<List<SearchClause.Type>> types, final Customizer customizer, final IModel<Map<String, PlainSchemaTO>> anames, - final IModel<List<String>> dnames, + final IModel<Map<String, PlainSchemaTO>> dnames, final Pair<IModel<List<String>>, IModel<Integer>> groupInfo, final IModel<List<String>> roleNames, final IModel<List<String>> privilegeNames, @@ -264,7 +278,7 @@ public class SearchClausePanel extends FieldPanel<SearchClause> { switch (field.getModel().getObject().getType()) { case ATTRIBUTE: - List<String> names = new ArrayList<>(dnames.getObject()); + List<String> names = new ArrayList<>(dnames.getObject().keySet()); if (anames != null && anames.getObject() != null && !anames.getObject().isEmpty()) { names.addAll(anames.getObject().keySet()); } @@ -356,6 +370,7 @@ public class SearchClausePanel extends FieldPanel<SearchClause> { } @Override + @SuppressWarnings({ "rawtypes", "unchecked" }) public FieldPanel<SearchClause> settingsDependingComponents() { SearchClause searchClause = this.clause.getObject(); @@ -488,36 +503,25 @@ public class SearchClausePanel extends FieldPanel<SearchClause> { comparator.setChoiceRenderer(getComparatorRender(field.getModel())); field.add(comparator); - AjaxTextFieldPanel value = new AjaxTextFieldPanel( - "value", "value", new PropertyModel<>(searchClause, "value"), true); - value.hideLabel().setOutputMarkupId(true); - field.add(value); - - value.getField().add(PREVENT_DEFAULT_RETURN); - value.getField().add(new IndicatorAjaxEventBehavior(Constants.ON_KEYDOWN) { + renderSearchValueField(searchClause, property); + field.addOrReplace(value); - private static final long serialVersionUID = -7133385027739964990L; + property.getField().add(new IndicatorAjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) { - @Override - protected void onEvent(final AjaxRequestTarget target) { - target.focusComponent(null); - value.getField().inputChanged(); - value.getField().validate(); - if (value.getField().isValid()) { - value.getField().valid(); - value.getField().updateModel(); - } - } + private static final long serialVersionUID = -1107858522700306810L; @Override - protected void updateAjaxAttributes(final AjaxRequestAttributes attributes) { - super.updateAjaxAttributes(attributes); - AJAX_SUBMIT_ON_RETURN.accept(attributes); + protected void onUpdate(final AjaxRequestTarget target) { + renderSearchValueField(searchClause, property); + field.addOrReplace(value); + target.add(value); } - }); + } + ); AjaxDropDownChoicePanel<SearchClause.Type> type = new AjaxDropDownChoicePanel<>( "type", "type", new PropertyModel<>(searchClause, "type")); + type.setChoices(types).setChoiceRenderer(customizer.typeRenderer()). hideLabel().setRequired(required).setOutputMarkupId(true); type.setNullValid(false); @@ -583,11 +587,12 @@ public class SearchClausePanel extends FieldPanel<SearchClause> { return this; } + @SuppressWarnings({ "rawtypes", "unchecked" }) private void setFieldAccess( final Type type, final AjaxTextFieldPanel property, final FieldPanel<Comparator> comparator, - final AjaxTextFieldPanel value) { + final FieldPanel value) { if (type != null) { property.setEnabled(true); @@ -879,6 +884,144 @@ public class SearchClausePanel extends FieldPanel<SearchClause> { }; } + @SuppressWarnings({ "rawtypes", "unchecked" }) + private void renderSearchValueField( + final SearchClause searchClause, + final AjaxTextFieldPanel property) { + + PlainSchemaTO plainSchemaTO = anames.getObject().get(property.getModelObject()); + if (plainSchemaTO == null) { + PlainSchemaTO defaultPlainTO = new PlainSchemaTO(); + defaultPlainTO.setType(AttrSchemaType.String); + plainSchemaTO = dnames.getObject().getOrDefault(property.getModelObject(), defaultPlainTO); + } + + switch (plainSchemaTO.getType()) { + case Boolean: + value = new AjaxTextFieldPanel( + "value", + "value", + new PropertyModel<>(searchClause, "value"), + true); + ((AjaxTextFieldPanel) value).setChoices(Arrays.asList("true", "false")); + + break; + case Date: + SimpleDateFormat df = DATE_FORMAT.get(); + df.applyPattern(SyncopeConstants.DEFAULT_DATE_PATTERN); + + value = new AjaxDateTimeFieldPanel( + "value", + "value", + new PropertyModel(searchClause, "value") { + + private static final long serialVersionUID = 1177692285167186690L; + + @Override + public Object getObject() { + String date = (String) super.getObject(); + try { + return date != null ? df.parse(date) : null; + } catch (ParseException ex) { + LOG.error("Date parse error {}", date, ex); + } + return null; + } + + @Override + public void setObject(final Object object) { + if (object instanceof Date) { + String valueDate = df.format(object); + super.setObject(valueDate); + } else { + super.setObject(object); + } + } + }, FastDateFormat.getInstance(SyncopeConstants.DEFAULT_DATE_PATTERN)); + break; + + case Enum: + value = new AjaxDropDownChoicePanel<>( + "value", + "value", + new PropertyModel(searchClause, "value"), + true); + ((AjaxDropDownChoicePanel<String>) value).setChoices(SchemaUtils.getEnumeratedValues(plainSchemaTO)); + + if (StringUtils.isNotBlank(plainSchemaTO.getEnumerationKeys())) { + Map<String, String> valueMap = SchemaUtils.getEnumeratedKeyValues(plainSchemaTO); + ((AjaxDropDownChoicePanel) value).setChoiceRenderer(new IChoiceRenderer<String>() { + + private static final long serialVersionUID = -3724971416312135885L; + + @Override + public String getDisplayValue(final String value) { + return valueMap.get(value) == null ? value : valueMap.get(value); + } + + @Override + public String getIdValue(final String value, final int i) { + return value; + } + + @Override + public String getObject( + final String id, final IModel<? extends List<? extends String>> choices) { + return id; + } + }); + } + break; + case Long: + value = new AjaxSpinnerFieldPanel.Builder<Long>().enableOnChange().build( + "value", + "Value", + Long.class, + new PropertyModel(searchClause, "value")); + + value.add(new AttributeModifier("class", "field value search-spinner")); + break; + + case Double: + value = new AjaxSpinnerFieldPanel.Builder<Double>().enableOnChange().step(0.1).build( + "value", + "value", + Double.class, + new PropertyModel(searchClause, "value")); + value.add(new AttributeModifier("class", "field value search-spinner")); + break; + + default: + value = new AjaxTextFieldPanel( + "value", "value", new PropertyModel<>(searchClause, "value"), true); + break; + } + + value.hideLabel().setOutputMarkupId(true); + value.getField().add(PREVENT_DEFAULT_RETURN); + value.getField().add(new IndicatorAjaxEventBehavior(Constants.ON_KEYDOWN) { + + private static final long serialVersionUID = -7133385027739964990L; + + @Override + protected void onEvent(final AjaxRequestTarget target) { + target.focusComponent(null); + value.getField().inputChanged(); + value.getField().validate(); + if (value.getField().isValid()) { + value.getField().valid(); + value.getField().updateModel(); + } + } + + @Override + protected void updateAjaxAttributes(final AjaxRequestAttributes attributes) { + super.updateAjaxAttributes(attributes); + AJAX_SUBMIT_ON_RETURN.accept(attributes); + } + }); + } + @Override public FieldPanel<SearchClause> clone() { SearchClausePanel panel = new SearchClausePanel( diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchUtils.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchUtils.java index 5c0539b..18c4f42 100644 --- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchUtils.java +++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchUtils.java @@ -20,9 +20,9 @@ package org.apache.syncope.client.console.panels.search; import java.io.Serializable; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.function.Function; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -220,7 +220,7 @@ public final class SearchUtils implements Serializable { final List<SearchClause> clauses, final AbstractFiqlSearchConditionBuilder<?, ?, ?> builder) { - return buildFIQL(clauses, builder, Map.of(), NO_CUSTOM_CONDITION); + return buildFIQL(clauses, builder, Collections.emptyMap(), NO_CUSTOM_CONDITION); } public static String buildFIQL( @@ -243,7 +243,7 @@ public final class SearchUtils implements Serializable { String value = clause.getValue() == null ? null : ENCODINGS.keySet().stream(). - reduce(clause.getValue(), (s, k) -> s.replace(k, ENCODINGS.get(k))); + reduce(clause.getValue().toString(), (s, k) -> s.replace(k, ENCODINGS.get(k))); switch (clause.getType()) { case GROUP_MEMBER: @@ -487,7 +487,7 @@ public final class SearchUtils implements Serializable { notTheFirst = true; } - String fiql = Optional.ofNullable(condition).map(CompleteCondition::query).orElse(null); + String fiql = condition == null ? null : condition.query(); LOG.debug("Generated FIQL: {}", fiql); return fiql; diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/search/SearchableFields.java b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/search/SearchableFields.java index c5102a4..62a9fc9 100644 --- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/search/SearchableFields.java +++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/search/SearchableFields.java @@ -19,18 +19,18 @@ package org.apache.syncope.common.lib.search; import java.lang.reflect.Field; -import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Set; +import java.util.TreeMap; import org.apache.commons.lang3.ArrayUtils; import org.apache.syncope.common.lib.to.AnyObjectTO; import org.apache.syncope.common.lib.to.AnyTO; import org.apache.syncope.common.lib.to.GroupTO; import org.apache.syncope.common.lib.to.UserTO; +import org.apache.syncope.common.lib.types.AttrSchemaType; public final class SearchableFields { @@ -41,17 +41,17 @@ public final class SearchableFields { private static final Set<String> ANY_FIELDS = new HashSet<>(); static { - ANY_FIELDS.addAll(get(UserTO.class)); - ANY_FIELDS.addAll(get(GroupTO.class)); - ANY_FIELDS.addAll(get(AnyObjectTO.class)); + ANY_FIELDS.addAll(get(UserTO.class).keySet()); + ANY_FIELDS.addAll(get(GroupTO.class).keySet()); + ANY_FIELDS.addAll(get(AnyObjectTO.class).keySet()); } public static boolean contains(final String schema) { return ANY_FIELDS.contains(schema); } - public static List<String> get(final Class<? extends AnyTO> anyRef) { - final List<String> fieldNames = new ArrayList<>(); + public static Map<String, AttrSchemaType> get(final Class<? extends AnyTO> anyRef) { + final Map<String, AttrSchemaType> fields = new TreeMap<>(Collections.reverseOrder()); // loop on class and all superclasses searching for field Class<?> clazz = anyRef; @@ -61,14 +61,13 @@ public final class SearchableFields { && !Collection.class.isAssignableFrom(field.getType()) && !Map.class.isAssignableFrom(field.getType())) { - fieldNames.add(field.getName()); + fields.put(field.getName(), AttrSchemaType.getAttrSchemaTypeByClass(field.getType())); } } clazz = clazz.getSuperclass(); } - Collections.reverse(fieldNames); - return fieldNames; + return fields; } private SearchableFields() { diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/AttrSchemaType.java b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/AttrSchemaType.java index b8e162d..cf93418 100644 --- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/AttrSchemaType.java +++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/AttrSchemaType.java @@ -19,6 +19,7 @@ package org.apache.syncope.common.lib.types; import java.util.Date; +import java.util.stream.Stream; public enum AttrSchemaType { @@ -46,4 +47,10 @@ public enum AttrSchemaType { || this == AttrSchemaType.Double || this == AttrSchemaType.Long; } + + public static AttrSchemaType getAttrSchemaTypeByClass(final Class<?> type) { + return Stream.of(AttrSchemaType.values()) + .filter(item -> type == item.getType()).findFirst().orElse(AttrSchemaType.String); + } + }