Skip to content

Commit

Permalink
[4352] Add support to range column filters in table
Browse files Browse the repository at this point in the history
Bug: eclipse-sirius#4352
Signed-off-by: Florian ROUËNÉ <[email protected]>
  • Loading branch information
frouene authored and sbegaudeau committed Jan 24, 2025
1 parent 9d0106d commit 17c005e
Show file tree
Hide file tree
Showing 20 changed files with 328 additions and 81 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ Note that you may need to encode special characters like `[`(by `%5B`) and `]` (
- https://github.com/eclipse-sirius/sirius-web/issues/4426[#4426] [deck] Remove last usages of mui `styled` pattern.
- https://github.com/eclipse-sirius/sirius-web/issues/4346[#4346] [query] Leverage the session storage to keep expressions entered in the query view even if the view is unmounted.
- https://github.com/eclipse-sirius/sirius-web/issues/4345[#4345] [table] Make table cells independently selectable
- https://github.com/eclipse-sirius/sirius-web/issues/4352[#4352] [table] Add support to range column filters in table


== v2025.1.0
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2021, 2024 Obeo.
* Copyright (c) 2021, 2025 Obeo.
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
Expand All @@ -16,7 +16,6 @@
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -56,10 +55,30 @@ public Map<String, List<String>> getParameterValues(String kind) {
@Override
public List<String> getParameterEntries(String value) {
if (value.startsWith("[") && value.endsWith("]") && value.length() >= 3) {
var rawValue = value.substring(1);
rawValue = rawValue.substring(0, rawValue.indexOf("]"));
return Arrays.stream(rawValue.split(",")).toList();
var rawValue = value.substring(1, value.length() - 1);
return this.parseEntries(rawValue);
}
return List.of();
}

private List<String> parseEntries(String rawValue) {
List<String> entries = new ArrayList<>();
int start = 0;
int level = 0;

for (int i = 0; i < rawValue.length(); i++) {
char c = rawValue.charAt(i);
if (c == '[') {
level++;
} else if (c == ']') {
level--;
} else if (c == ',' && level == 0) {
entries.add(rawValue.substring(start, i).trim());
start = i + 1;
}
}
entries.add(rawValue.substring(start).trim());

return entries;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*******************************************************************************
* Copyright (c) 2025 CEA LIST.
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Obeo - initial API and implementation
*******************************************************************************/
package org.eclipse.sirius.components.core;

import static org.assertj.core.api.Assertions.assertThat;

import java.util.List;

import org.junit.jupiter.api.Test;

/**
* Tests of the URLParser.
*
* @author frouene
*/
public class URLParserTests {

@Test
public void testGetParameterEntriesWithNestedLists() {
URLParser parser = new URLParser();
String value = "[467ec1c3-8ba7-32dc-9d72-71a2ccad161b:[\"1\",\"\"],4752c1c3-8ba7-32dc-9d72-71a2ccad161b:[\"1\",\"2\"]]";
List<String> expected = List.of(
"467ec1c3-8ba7-32dc-9d72-71a2ccad161b:[\"1\",\"\"]",
"4752c1c3-8ba7-32dc-9d72-71a2ccad161b:[\"1\",\"2\"]"
);
List<String> result = parser.getParameterEntries(value);
assertThat(expected).isEqualTo(result);
}

@Test
public void testGetParameterEntriesWithEmptyList() {
URLParser parser = new URLParser();
String value = "[]";
List<String> expected = List.of();
List<String> result = parser.getParameterEntries(value);
assertThat(expected).isEqualTo(result);
}

@Test
public void testGetParameterEntriesWithSingleElement() {
URLParser parser = new URLParser();
String value = "[467ec1c3-8ba7-32dc-9d72-71a2ccad161b:[\"1\",\"\"]]";
List<String> expected = List.of("467ec1c3-8ba7-32dc-9d72-71a2ccad161b:[\"1\",\"\"]");
List<String> result = parser.getParameterEntries(value);
assertThat(expected).isEqualTo(result);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
*******************************************************************************/
package org.eclipse.sirius.web.papaya.representations.table;

import com.fasterxml.jackson.databind.ObjectMapper;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
Expand All @@ -29,6 +31,7 @@
import org.eclipse.sirius.components.core.api.IEditingContextRepresentationDescriptionProvider;
import org.eclipse.sirius.components.core.api.IIdentityService;
import org.eclipse.sirius.components.core.api.ILabelService;
import org.eclipse.sirius.components.papaya.AnnotableElement;
import org.eclipse.sirius.components.papaya.PapayaFactory;
import org.eclipse.sirius.components.papaya.PapayaPackage;
import org.eclipse.sirius.components.papaya.Type;
Expand Down Expand Up @@ -65,10 +68,13 @@ public class PackageTableRepresentationDescriptionProvider implements IEditingCo

private final ComposedAdapterFactory composedAdapterFactory;

public PackageTableRepresentationDescriptionProvider(IIdentityService identityService, ILabelService labelService, ComposedAdapterFactory composedAdapterFactory) {
private final ObjectMapper objectMapper;

public PackageTableRepresentationDescriptionProvider(IIdentityService identityService, ILabelService labelService, ComposedAdapterFactory composedAdapterFactory, ObjectMapper objectMapper) {
this.identityService = Objects.requireNonNull(identityService);
this.labelService = Objects.requireNonNull(labelService);
this.composedAdapterFactory = Objects.requireNonNull(composedAdapterFactory);
this.objectMapper = Objects.requireNonNull(objectMapper);
}

@Override
Expand Down Expand Up @@ -137,13 +143,7 @@ private PaginatedData getSemanticElements(VariableManager variableManager) {
isValidCandidate = isValidCandidate || type.getVisibility() != null && type.getVisibility().getLiteral().contains(globalFilter);
isValidCandidate = isValidCandidate || type.getAnnotations().stream().anyMatch(annotation -> annotation.getName().contains(globalFilter));
}
isValidCandidate = isValidCandidate && columnFilters.stream().allMatch(columnFilter -> {
if (columnFilter.id().equals("papaya.NamedElement#name")) {
return type.getName() != null && type.getName().contains(columnFilter.value());
}
return true;

});
isValidCandidate = isValidCandidate && columnFilters.stream().allMatch(new PapayaColumnFilterPredicate(this.objectMapper, type));
}
return isValidCandidate;
};
Expand All @@ -158,15 +158,27 @@ private List<ColumnDescription> getColumnDescriptions() {
ColumnDescription iconColumnDescription = ColumnDescription.newColumnDescription(UUID.nameUUIDFromBytes("icon".getBytes()).toString())
.semanticElementsProvider(variableManager -> List.of("IconColumn"))
.headerLabelProvider(variableManager -> "Icon")
.headerIconURLsProvider(variableManager -> List.of("/icons/svg/Default.svg"))
.headerIndexLabelProvider(variableManager -> "")
.headerIconURLsProvider(variableManager -> List.of())
.headerIndexLabelProvider(variableManager -> "A")
.targetObjectIdProvider(new ColumnTargetObjectIdProvider())
.targetObjectKindProvider(variableManager -> "")
.initialWidthProvider(variableManager -> 130)
.isResizablePredicate(variableManager -> false)
.filterVariantProvider(variableManager -> "text")
.build();

ColumnDescription nbAnnotationsColumn = ColumnDescription.newColumnDescription(UUID.nameUUIDFromBytes("nbAnnotation".getBytes()).toString())
.semanticElementsProvider(variableManager -> List.of("NbAnnotationColumn"))
.headerLabelProvider(variableManager -> "Annotation number")
.headerIconURLsProvider(variableManager -> List.of())
.headerIndexLabelProvider(variableManager -> "F")
.targetObjectIdProvider(variableManager -> "papaya.Type#nbAnnotation")
.targetObjectKindProvider(variableManager -> "")
.initialWidthProvider(variableManager -> 250)
.isResizablePredicate(variableManager -> false)
.filterVariantProvider(variableManager -> "range")
.build();

Function<VariableManager, String> headerLabelProvider = variableManager -> variableManager.get(VariableManager.SELF, EStructuralFeature.class)
.map(featureToDisplayName::get)
.orElse("");
Expand All @@ -176,7 +188,7 @@ private List<ColumnDescription> getColumnDescriptions() {
.orElse(List.of());

Function<VariableManager, String> headerIndexLabelProvider = variableManager -> variableManager.get("columnIndex", Integer.class)
.map(index -> String.valueOf((char) (index + 'A')))
.map(index -> String.valueOf((char) (index + 1 + 'A')))
.orElse("");

ColumnDescription columnDescription = ColumnDescription.newColumnDescription(UUID.nameUUIDFromBytes("features".getBytes()).toString())
Expand All @@ -190,7 +202,7 @@ private List<ColumnDescription> getColumnDescriptions() {
.isResizablePredicate(variableManager -> true)
.filterVariantProvider(variableManager -> "text")
.build();
return List.of(iconColumnDescription, columnDescription);
return List.of(iconColumnDescription, columnDescription, nbAnnotationsColumn);
}

private List<ICellDescription> getCellDescriptions() {
Expand Down Expand Up @@ -248,6 +260,25 @@ private List<ICellDescription> getCellDescriptions() {
.cellValueProvider((variableManager, columnTargetObject) -> "")
.cellIconURLsProvider(iconLabelCellIconURLsProvider)
.build());

Predicate<VariableManager> nbAnnotationCellPredicate = variableManager -> variableManager.get(ColumnDescription.COLUMN_TARGET_OBJECT, Object.class)
.filter(String.class::isInstance)
.map(String.class::cast)
.filter(value -> value.equals("NbAnnotationColumn"))
.isPresent();

cellDescriptions.add(IconLabelCellDescription.newIconLabelCellDescription("nbAnnotationCells")
.canCreatePredicate(nbAnnotationCellPredicate)
.targetObjectIdProvider(variableManager -> "")
.targetObjectKindProvider(variableManager -> "")
.cellValueProvider((variableManager, columnTargetObject) -> variableManager.get(VariableManager.SELF, Object.class)
.filter(AnnotableElement.class::isInstance)
.map(AnnotableElement.class::cast)
.map(annotableElement -> annotableElement.getAnnotations().size())
.map(String::valueOf)
.orElse("NA"))
.cellIconURLsProvider((variableManager, object) -> List.of())
.build());
return cellDescriptions;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*******************************************************************************
* Copyright (c) 2025 Obeo.
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Obeo - initial API and implementation
*******************************************************************************/
package org.eclipse.sirius.web.papaya.representations.table;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;

import org.eclipse.sirius.components.papaya.Type;
import org.eclipse.sirius.components.tables.ColumnFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Used to indicate if a column should be filtered or not.
*
* @author sbegaudeau
*/
public class PapayaColumnFilterPredicate implements Predicate<ColumnFilter> {

private final ObjectMapper objectMapper;

private final Type type;

private final Logger logger = LoggerFactory.getLogger(PapayaColumnFilterPredicate.class);

public PapayaColumnFilterPredicate(ObjectMapper objectMapper, Type type) {
this.objectMapper = Objects.requireNonNull(objectMapper);
this.type = Objects.requireNonNull(type);
}

@Override
public boolean test(ColumnFilter columnFilter) {
boolean isValidColumFilterCandidate = true;
if (columnFilter.id().equals("papaya.NamedElement#name")) {
isValidColumFilterCandidate = this.isValidNameColumnFilterCandidate(columnFilter);
} else if (columnFilter.id().equals("papaya.Type#nbAnnotation")) {
isValidColumFilterCandidate = this.isValidAnnotationCountFilterCandidate(columnFilter);
}
return isValidColumFilterCandidate;
}

private boolean isValidNameColumnFilterCandidate(ColumnFilter columnFilter) {
var isValid = true;
try {
String filterValue = this.objectMapper.readValue(columnFilter.value(), new TypeReference<>() { });
isValid = this.type.getName() != null && this.type.getName().contains(filterValue);
} catch (JsonProcessingException exception) {
this.logger.warn(exception.getMessage(), exception);
}
return isValid;
}

private boolean isValidAnnotationCountFilterCandidate(ColumnFilter columnFilter) {
var isValid = true;
try {
List<String> filterValues = objectMapper.readValue(columnFilter.value(), new TypeReference<>() { });
int nbAnnotation = this.type.getAnnotations().size();
if (filterValues.size() == 2) {
if (filterValues.get(0) != null && !filterValues.get(0).isBlank()) {
try {
int minValue = Integer.parseInt(filterValues.get(0));
isValid = minValue <= nbAnnotation;
} catch (NumberFormatException exception) {
this.logger.warn(exception.getMessage(), exception);
}
}
if (filterValues.get(1) != null && !filterValues.get(1).isBlank()) {
try {
int maxValue = Integer.parseInt(filterValues.get(1));
isValid = isValid && maxValue >= nbAnnotation;
} catch (NumberFormatException exception) {
this.logger.warn(exception.getMessage(), exception);
isValid = true;
}
}
}
} catch (JsonProcessingException exception) {
this.logger.warn(exception.getMessage(), exception);
}
return isValid;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ public void givenTableWhenEditTextareaMutationTriggeredThenTheRepresentationIsRe
Consumer<Object> initialTableContentConsumer = this.getTableSubscriptionConsumer(table -> {
assertThat(table).isNotNull();
assertThat(table.getLines()).hasSize(2);
assertThat(table.getLines().get(0).getCells()).hasSize(5);
assertThat(table.getLines().get(0).getCells()).hasSize(6);
assertThat(table.getLines().get(0).getCells().get(1)).isInstanceOf(TextareaCell.class);
assertThat(table.getLines().get(0).getCells().get(1)).isInstanceOf(TextareaCell.class);
assertThat(((TextareaCell) table.getLines().get(0).getCells().get(1)).getValue()).isEqualTo("");
Expand All @@ -121,7 +121,7 @@ public void givenTableWhenEditTextareaMutationTriggeredThenTheRepresentationIsRe
Consumer<Object> updatedTableContentConsumer = this.getTableSubscriptionConsumer(table -> {
assertThat(table).isNotNull();
assertThat(table.getLines()).hasSize(2);
assertThat(table.getLines().get(0).getCells()).hasSize(5);
assertThat(table.getLines().get(0).getCells()).hasSize(6);
assertThat(table.getLines().get(0).getCells().get(1)).isInstanceOf(TextareaCell.class);
assertThat(((TextareaCell) table.getLines().get(0).getCells().get(1)).getValue()).isEqualTo("new description");
});
Expand Down
Loading

0 comments on commit 17c005e

Please sign in to comment.