Sorted autocomplete (#1918)

Users, groups, repositories and repository roles have been sorted in the rest layer by default if no other sort option was given. In the layers "below" (aka the manager classes or the dao), the collections have been unsorted. This led to the effect, that the autocomplete resource, which did not sort all values beforehand, returned unsorted results. As a sideeffect, direct matches for an input could occur at a random position or not at all (as reported in #1695), when there were enough other matches.

With this pull request the databases for users, groups, repositories and repository roles will use instances of TreeMap instead of LinkedHashMap internally, so that these values are sorted implicitly (by id respectively name for users, groups and repository roles and namespace/name for repositories).

Due to this change the default sort applied in the rest layer could be removed.
This commit is contained in:
René Pfeuffer
2022-01-18 09:46:10 +01:00
committed by GitHub
parent 6ca88e6772
commit f2a1effc77
16 changed files with 204 additions and 162 deletions

View File

@@ -31,6 +31,7 @@ import java.util.Collection;
import java.util.Optional;
import java.util.function.Function;
import static java.util.Optional.empty;
import static java.util.Optional.ofNullable;
public abstract class GenericDisplayManager<D, T extends ReducedModelObject> implements DisplayManager<T> {
@@ -60,6 +61,9 @@ public abstract class GenericDisplayManager<D, T extends ReducedModelObject> imp
@Override
public Optional<T> get(String id) {
if (id == null) {
return empty();
}
return ofNullable(dao.get(id)).map(transform);
}
}

View File

@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.api.v2.resources;
import de.otto.edison.hal.HalRepresentation;
@@ -77,12 +77,12 @@ class CollectionResourceManagerAdapter<MODEL_OBJECT extends ModelObject,
AssertUtil.assertPositive(pageNumber);
AssertUtil.assertPositive(pageSize);
if (Util.isEmpty(sortBy)) {
// replace with something useful
sortBy = "id";
Comparator<MODEL_OBJECT> comparator = null;
if (!Util.isEmpty(sortBy)) {
comparator = createComparator(sortBy, desc);
}
return manager.getPage(filter, createComparator(sortBy, desc), pageNumber, pageSize);
return manager.getPage(filter, comparator, pageNumber, pageSize);
}
private Comparator<MODEL_OBJECT> createComparator(String sortBy, boolean desc) {

View File

@@ -41,7 +41,6 @@ import sonia.scm.event.ScmEventBus;
import sonia.scm.security.AuthorizationChangedEvent;
import sonia.scm.security.KeyGenerator;
import sonia.scm.util.AssertUtil;
import sonia.scm.util.CollectionAppender;
import sonia.scm.util.IOUtil;
import sonia.scm.util.Util;
@@ -51,13 +50,19 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collector;
import static java.util.stream.Collectors.toSet;
import static java.util.Collections.emptySet;
import static sonia.scm.AlreadyExistsException.alreadyExists;
import static sonia.scm.ContextEntry.ContextBuilder.entity;
import static sonia.scm.NotFoundException.notFound;
@@ -70,6 +75,33 @@ import static sonia.scm.NotFoundException.notFound;
@Singleton
public class DefaultRepositoryManager extends AbstractRepositoryManager {
@SuppressWarnings("unchecked")
public static final Collector<String, Object, Collection<String>> LINKED_HASH_SET_COLLECTOR = new Collector<String, Object, Collection<String>>() {
@Override
public Supplier<Object> supplier() {
return LinkedHashSet::new;
}
@Override
public BiConsumer<Object, String> accumulator() {
return (collection, value) -> ((Collection<String>) collection).add(value);
}
@Override
public BinaryOperator<Object> combiner() {
return (c1, c2) -> ((Collection<String>) c1).addAll((Collection<String>) c2);
}
@Override
public Function<Object, Collection<String>> finisher() {
return collection -> (Collection<String>) collection;
}
@Override
public Set<Characteristics> characteristics() {
return emptySet();
}
};
private static final Logger logger = LoggerFactory.getLogger(DefaultRepositoryManager.class);
private final Map<String, RepositoryHandler> handlerMap;
private final KeyGenerator keyGenerator;
@@ -340,12 +372,9 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
int start, int limit) {
return Util.createSubCollection(repositoryDAO.getAll(), comparator,
new CollectionAppender<Repository>() {
@Override
public void append(Collection<Repository> collection, Repository item) {
if (RepositoryPermissions.read().isPermitted(item)) {
collection.add(postProcess(item));
}
(collection, item) -> {
if (RepositoryPermissions.read().isPermitted(item)) {
collection.add(postProcess(item));
}
}, start, limit);
}
@@ -363,7 +392,7 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
public Collection<String> getAllNamespaces() {
return getAll().stream()
.map(Repository::getNamespace)
.collect(toSet());
.collect(LINKED_HASH_SET_COLLECTOR);
}
@Override