diff --git a/projects/mercury/src/constants.js b/projects/mercury/src/constants.js
index b0cbada34..4d2b9e43e 100644
--- a/projects/mercury/src/constants.js
+++ b/projects/mercury/src/constants.js
@@ -22,6 +22,8 @@ export const DATE_FORMAT = 'dd-MM-yyyy';
// The maximum number of items in a list in the right panel, for performance reasons.
// If you change this, also change it in 'MetadataService.java'
export const MAX_LIST_LENGTH = 100;
+// Max length of URL in the browser
+export const MAX_URL_LENGTH = 2000;
// Metadata schemas
export const SHACL_NS = 'http://www.w3.org/ns/shacl#';
diff --git a/projects/mercury/src/metadata/common/LinkedDataEntityForm.js b/projects/mercury/src/metadata/common/LinkedDataEntityForm.js
index 2f0b32d45..54ae6f7ab 100644
--- a/projects/mercury/src/metadata/common/LinkedDataEntityForm.js
+++ b/projects/mercury/src/metadata/common/LinkedDataEntityForm.js
@@ -97,7 +97,7 @@ export const LinkedDataEntityForm = ({
labelFirst,
descriptionFirst,
systemPropertiesLast,
- compareBy(p => (typeof p.order === 'number' ? p.order : Number.MAX_SAFE_INTEGER)),
+ compareBy(p => (Number.isNaN(Number(p.order)) ? Number.MAX_SAFE_INTEGER : Number(p.order))),
compareBy('label')
)
)
diff --git a/projects/mercury/src/metadata/common/values/ReferringValue.js b/projects/mercury/src/metadata/common/values/ReferringValue.js
index 7f2719a45..f1893c82a 100644
--- a/projects/mercury/src/metadata/common/values/ReferringValue.js
+++ b/projects/mercury/src/metadata/common/values/ReferringValue.js
@@ -34,7 +34,9 @@ export const ReferringValue = ({property, entry}) => {
// External links should be represented by a direct link to the URI itself
// Other iri entities should be opened in the metadata editor
return property.isExternalLink ? (
- {entry.id}
+
+ {entry.id}
+
) : (
{displayValue}
);
diff --git a/projects/mercury/src/metadata/common/values/__tests__/ReferringValue.js b/projects/mercury/src/metadata/common/values/__tests__/ReferringValue.js
index 29d4c7586..784e7add7 100644
--- a/projects/mercury/src/metadata/common/values/__tests__/ReferringValue.js
+++ b/projects/mercury/src/metadata/common/values/__tests__/ReferringValue.js
@@ -18,7 +18,11 @@ describe('ReferringValue', () => {
property,
entry
})
- ).toEqual(https://thehyve.nl);
+ ).toEqual(
+
+ https://thehyve.nl
+
+ );
});
it('should render a generic iri resource as link to editor', () => {
diff --git a/projects/mercury/src/metadata/views/MetadataView.js b/projects/mercury/src/metadata/views/MetadataView.js
index 5bf908525..da519ad0e 100644
--- a/projects/mercury/src/metadata/views/MetadataView.js
+++ b/projects/mercury/src/metadata/views/MetadataView.js
@@ -3,9 +3,11 @@ import _ from 'lodash';
import {useHistory} from 'react-router-dom';
import {Button, Grid, Typography} from '@mui/material';
import withStyles from '@mui/styles/withStyles';
-import {Assignment, Close} from '@mui/icons-material';
+import {Assignment, Close, ContentCopy} from '@mui/icons-material';
import queryString from 'query-string';
+import qs from 'qs';
+import {SnackbarProvider, useSnackbar} from 'notistack';
import styles from './MetadataView.styles';
import type {MetadataViewFacet, MetadataViewFilter, MetadataViewOptions, ValueType} from './MetadataViewAPI';
import BreadCrumbs from '../../common/components/BreadCrumbs';
@@ -34,6 +36,7 @@ import usePageTitleUpdater from '../../common/hooks/UsePageTitleUpdater';
import MetadataViewFacetsContext from './MetadataViewFacetsContext';
import {accessLevelForCollection} from '../../collections/collectionUtils';
import InternalMetadataSourceContext from '../metadata-sources/InternalMetadataSourceContext';
+import {MAX_URL_LENGTH} from '../../constants';
type ContextualMetadataViewProperties = {
classes: any
@@ -70,6 +73,7 @@ export const MetadataView = (props: MetadataViewProperties) => {
const {collections} = useContext(CollectionsContext);
const {toggle, selected} = useSingleSelection();
+ const {enqueueSnackbar} = useSnackbar();
const [filterCandidates, setFilterCandidates] = useState([]);
const [textFiltersObject, setTextFiltersObject] = useState({});
@@ -199,7 +203,9 @@ export const MetadataView = (props: MetadataViewProperties) => {
};
const areFacetFiltersNonEmpty = useMemo(
- () => filters && filters.some(filter => facetsEx.some(facet => facet.name === filter.field)),
+ () =>
+ filters &&
+ filters.some(filter => facetsEx.some(facet => facet.name.toLowerCase() === filter.field.toLowerCase())),
[filters, facetsEx]
);
const areTextFiltersNonEmpty = useMemo(
@@ -218,6 +224,48 @@ export const MetadataView = (props: MetadataViewProperties) => {
return `${window.location.host}${pathPrefix}?${prefilteringQueryString}`;
};
+ const copyFiltersUrl = () => {
+ const queryParams = filters.reduce((acc, filter) => {
+ acc[filter.field.toLowerCase()] = filter.values.join(',');
+ return acc;
+ }, {});
+ const queryStringFilters = queryString.stringify(queryParams);
+ const url = `${window.location.protocol}//${window.location.host}/metadata-views?view=${currentView.name}&${queryStringFilters}`;
+
+ if (url.length > MAX_URL_LENGTH) {
+ enqueueSnackbar('Failed to copy metadata view filters URL to clipboard: URL too long');
+ return;
+ }
+ navigator.clipboard
+ .writeText(url)
+ .then(() => enqueueSnackbar('Metadata view filters URL copied to clipboard'))
+ .catch(() => enqueueSnackbar('Failed to copy metadata view filters URL to clipboard'));
+ };
+
+ useEffect(() => {
+ const queryStringFilters = qs.parse(window.location.search, {ignoreQueryPrefix: true});
+ if (queryStringFilters && Object.keys(queryStringFilters).length > 0) {
+ const idTextFilter = queryStringFilters[currentViewIdColumn.name.toLowerCase()];
+ if (idTextFilter && (!areTextFiltersNonEmpty || !textFiltersObject.keys.includes(currentViewIdColumn))) {
+ setTextFiltersObject({...textFiltersObject, [currentViewIdColumn.name]: idTextFilter});
+ }
+ if (!areFacetFiltersNonEmpty) {
+ const facetNames = facets.map(f => f.name.toLowerCase());
+ const newFilters = Object.keys(queryStringFilters)
+ .filter(k => facetNames.includes(k.toLowerCase()))
+ .reduce((arr, key) => {
+ arr.push({
+ field: key,
+ values: queryStringFilters[key].split(',')
+ });
+ return arr;
+ }, []);
+ updateFilters(newFilters);
+ }
+ }
+ // eslint-disable-next-line
+ }, []);
+
return (
{
{(areFacetFiltersNonEmpty || areTextFiltersNonEmpty) && (
-
+
}
- onClick={handleClearAllFilters}
+ className={classes.filterButtons}
+ startIcon={}
+ onClick={() => copyFiltersUrl(currentView, filters)}
>
+ Copy filters
+
+ } onClick={handleClearAllFilters}>
Clear all filters
@@ -348,19 +399,21 @@ export const ContextualMetadataView = (props: ContextualMetadataViewProperties)
};
return (
-
+
+
+
);
};
diff --git a/projects/mercury/src/metadata/views/MetadataView.styles.js b/projects/mercury/src/metadata/views/MetadataView.styles.js
index 52f96d12a..da55f9c43 100644
--- a/projects/mercury/src/metadata/views/MetadataView.styles.js
+++ b/projects/mercury/src/metadata/views/MetadataView.styles.js
@@ -29,9 +29,11 @@ const styles = theme => ({
clearAllButtonContainer: {
textAlign: 'end'
},
- clearAllButton: {
+ filterButtons: {
color: theme.palette.primary.contrastText,
- background: theme.palette.primary.main
+ background: theme.palette.primary.main,
+ marginLeft: 1,
+ marginBottom: 1
},
activeFilters: {
marginBottom: 10
diff --git a/projects/mercury/src/metadata/views/MetadataViewActiveFacetFilters.js b/projects/mercury/src/metadata/views/MetadataViewActiveFacetFilters.js
index fc6d8632a..8343f9014 100644
--- a/projects/mercury/src/metadata/views/MetadataViewActiveFacetFilters.js
+++ b/projects/mercury/src/metadata/views/MetadataViewActiveFacetFilters.js
@@ -64,7 +64,7 @@ export const MetadataViewActiveFacetFilters = (props: MetadataViewActiveFacetFil
);
}
return filter.values.map(valueIri => {
- const value = facet.values.find(val => val.value === valueIri);
+ const value = facet.values.find(val => val.value.toLowerCase() === valueIri.toLowerCase());
return (
value && (
f.name === filter.field);
+ const facet = facets.find(f => f.name.toLowerCase() === filter.field.toLowerCase());
if (facet) {
return (
diff --git a/projects/mercury/src/metadata/views/MetadataViewContext.js b/projects/mercury/src/metadata/views/MetadataViewContext.js
index 7a366df34..4d80968a4 100644
--- a/projects/mercury/src/metadata/views/MetadataViewContext.js
+++ b/projects/mercury/src/metadata/views/MetadataViewContext.js
@@ -11,7 +11,6 @@ const SESSION_STORAGE_METADATA_FILTERS_KEY = 'FAIRSPACE_METADATA_FILTERS';
export const MetadataViewProvider = ({children, metadataViewAPI = MetadataViewAPI, sourceName = ''}) => {
const {data = {}, error, loading, refresh} = useAsync(() => metadataViewAPI.getViews(), []);
-
const [filters: MetadataViewFilter[], setFilters] = useStateWithSessionStorage(
`${SESSION_STORAGE_METADATA_FILTERS_KEY}_${sourceName}`,
[]
@@ -23,11 +22,14 @@ export const MetadataViewProvider = ({children, metadataViewAPI = MetadataViewAP
const clearAllFilters = () => {
setFilters([]);
+ const queryParams = new URLSearchParams(window.location.search);
+ const viewParam = queryParams.get('view');
+ window.history.replaceState(null, '', `${window.location.pathname}?${viewParam ? `view=${viewParam}` : ''}`);
};
const updateFilters = (filterCandidates: MetadataViewFilter[]) => {
setFilters([
- ...filters.filter(f => !filterCandidates.some(u => u.field === f.field)),
+ ...filters.filter(f => !filterCandidates.some(u => u.field.toLowerCase() === f.field.toLowerCase())),
...filterCandidates.filter(
f =>
(f.values && f.values.length > 0) ||
diff --git a/projects/mercury/src/metadata/views/MetadataViewFacets.js b/projects/mercury/src/metadata/views/MetadataViewFacets.js
index d31d08eec..a339309f1 100644
--- a/projects/mercury/src/metadata/views/MetadataViewFacets.js
+++ b/projects/mercury/src/metadata/views/MetadataViewFacets.js
@@ -79,7 +79,9 @@ export const MetadataViewFacets = (props: MetadataViewFacetsProperties) => {
const renderSingleFacet = (facet: MetadataViewFacet) => {
const facetOptions = getFilterValues(facet.type, facet);
- const activeFilter = [...filterCandidates, ...filters].find(filter => filter.field === facet.name);
+ const activeFilter = [...filterCandidates, ...filters].find(
+ filter => filter.field.toLowerCase() === facet.name.toLowerCase()
+ );
let activeFilterValues = [];
if (activeFilter) {
activeFilterValues = getFilterValues(facet.type, activeFilter);
diff --git a/projects/mercury/src/metadata/views/MetadataViewTable.js b/projects/mercury/src/metadata/views/MetadataViewTable.js
index 3472b5074..66f557640 100644
--- a/projects/mercury/src/metadata/views/MetadataViewTable.js
+++ b/projects/mercury/src/metadata/views/MetadataViewTable.js
@@ -1,15 +1,14 @@
-import React, {useCallback, useEffect} from 'react';
+import React, {useCallback} from 'react';
import {Checkbox, Link, Table, TableBody, TableCell, TableHead, TableRow} from '@mui/material';
import {Check, Close} from '@mui/icons-material';
import makeStyles from '@mui/styles/makeStyles';
import {Link as RouterLink} from 'react-router-dom';
-import qs from 'qs';
import useDeepCompareEffect from 'use-deep-compare-effect';
import type {MetadataViewColumn, MetadataViewData} from './MetadataViewAPI';
import {TextualValueTypes} from './MetadataViewAPI';
import type {MetadataViewEntity, MetadataViewEntityWithLinkedFiles} from './metadataViewUtils';
import {RESOURCES_VIEW} from './metadataViewUtils';
-import {stringToBooleanValueOrNull, formatDate} from '../../common/utils/genericUtils';
+import {formatDate, stringToBooleanValueOrNull} from '../../common/utils/genericUtils';
import type {Collection} from '../../collections/CollectionAPI';
import {collectionAccessIcon} from '../../collections/collectionUtils';
import {getPathFromIri, redirectLink} from '../../file/fileUtils';
@@ -52,12 +51,12 @@ const RESOURCE_TYPE_COLUMN = `${RESOURCES_VIEW}_type`;
export const MetadataViewTable = (props: MetadataViewTableProperties) => {
const {columns, visibleColumnNames, loading, data, toggleRow, selected, view, idColumn, history, collections} =
props;
- const classes = useStyles();
const {textFiltersObject, setTextFiltersObject} = props;
+ const {checkboxes, setCheckboxState} = props;
+ const classes = useStyles();
const visibleColumns = columns.filter(column => visibleColumnNames.includes(column.name));
const dataLinkColumn = columns.find(c => c.type === 'dataLink');
const isResourcesView = view === RESOURCES_VIEW;
- const {checkboxes, setCheckboxState} = props;
const isCustomResourceColumn = (column: MetadataViewColumn) =>
isResourcesView && CUSTOM_RESOURCE_COLUMNS.includes(column.name) && column.type === 'Custom';
@@ -67,11 +66,6 @@ export const MetadataViewTable = (props: MetadataViewTableProperties) => {
return col ? col.access : 'None';
};
- const getIdColumnFilterFromSearchParams = () => {
- const idColumnName = idColumn.name.toLowerCase();
- return qs.parse(window.location.search, {ignoreQueryPrefix: true})[idColumnName];
- };
-
const getResourceType = (row: Map) =>
row[RESOURCE_TYPE_COLUMN] && row[RESOURCE_TYPE_COLUMN][0] && row[RESOURCE_TYPE_COLUMN][0].value;
@@ -82,15 +76,6 @@ export const MetadataViewTable = (props: MetadataViewTableProperties) => {
toggleRow({label, iri, linkedFiles: linkedFiles || []});
}
};
- useEffect(() => {
- if (!textFiltersObject || !textFiltersObject.keys || !textFiltersObject.keys.includes(idColumn)) {
- const idColumnTextFilter = getIdColumnFilterFromSearchParams();
- if (idColumnTextFilter) {
- setTextFiltersObject({...textFiltersObject, [idColumn.name]: idColumnTextFilter});
- }
- }
- // eslint-disable-next-line
- }, []);
const initializeCheckboxes = useCallback(() => {
if (idColumn && data && data.rows) {
diff --git a/projects/mercury/src/metadata/views/MetadataViewTableContainer.js b/projects/mercury/src/metadata/views/MetadataViewTableContainer.js
index 64fed29f2..0eb4267cb 100644
--- a/projects/mercury/src/metadata/views/MetadataViewTableContainer.js
+++ b/projects/mercury/src/metadata/views/MetadataViewTableContainer.js
@@ -19,7 +19,7 @@ import CheckBoxOutlineBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank';
import CheckBoxIcon from '@mui/icons-material/CheckBox';
import GetAppIcon from '@mui/icons-material/GetApp';
import FormGroup from '@mui/material/FormGroup';
-import useDeepCompareEffect from 'use-deep-compare-effect';
+import useDeepCompareEffect, {useDeepCompareEffectNoCheck} from 'use-deep-compare-effect';
import {useTheme} from '@mui/material/styles';
import type {MetadataViewColumn, MetadataViewFilter} from './MetadataViewAPI';
@@ -376,7 +376,7 @@ export const MetadataViewTableContainer = (props: MetadataViewTableContainerProp
setPage(0);
}, [filters]);
- useDeepCompareEffect(() => {
+ useDeepCompareEffectNoCheck(() => {
resetRowCheckboxes();
}, [data]);
diff --git a/projects/saturn/src/main/java/io/fairspace/saturn/services/metadata/validation/UniqueLabelValidator.java b/projects/saturn/src/main/java/io/fairspace/saturn/services/metadata/validation/UniqueLabelValidator.java
index 859673e39..f7e66df11 100644
--- a/projects/saturn/src/main/java/io/fairspace/saturn/services/metadata/validation/UniqueLabelValidator.java
+++ b/projects/saturn/src/main/java/io/fairspace/saturn/services/metadata/validation/UniqueLabelValidator.java
@@ -7,6 +7,8 @@
import io.fairspace.saturn.vocabulary.FS;
+import static org.apache.jena.rdf.model.ResourceFactory.createPlainLiteral;
+
@Component
public class UniqueLabelValidator implements MetadataRequestValidator {
@Override
@@ -21,7 +23,7 @@ public void validate(Model before, Model after, Model removed, Model added, Viol
.filterDrop(res -> res.hasProperty(FS.dateDeleted))
.hasNext();
if (conflictingResourceExists) {
- violationHandler.onViolation("Duplicate label", resource, RDFS.label, null);
+ violationHandler.onViolation("Duplicate label", resource, RDFS.label, createPlainLiteral(label));
}
});
}
diff --git a/projects/saturn/src/main/java/io/fairspace/saturn/services/metadata/validation/ValidationException.java b/projects/saturn/src/main/java/io/fairspace/saturn/services/metadata/validation/ValidationException.java
index 746b2f925..9b16a32e0 100644
--- a/projects/saturn/src/main/java/io/fairspace/saturn/services/metadata/validation/ValidationException.java
+++ b/projects/saturn/src/main/java/io/fairspace/saturn/services/metadata/validation/ValidationException.java
@@ -1,6 +1,7 @@
package io.fairspace.saturn.services.metadata.validation;
import java.util.Set;
+import java.util.stream.Collectors;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@@ -9,4 +10,13 @@
@Getter
public class ValidationException extends RuntimeException {
private final Set violations;
+
+ @Override
+ public String getMessage() {
+ if (violations == null || violations.isEmpty()) {
+ return "Validation failed with no specific violations.";
+ }
+ return "Validation failed with the following violations: "
+ + violations.stream().map(Violation::toString).collect(Collectors.joining(", "));
+ }
}
diff --git a/projects/saturn/src/main/java/io/fairspace/saturn/services/views/MaterializedViewService.java b/projects/saturn/src/main/java/io/fairspace/saturn/services/views/MaterializedViewService.java
index 3eacc95b0..4d44774fa 100644
--- a/projects/saturn/src/main/java/io/fairspace/saturn/services/views/MaterializedViewService.java
+++ b/projects/saturn/src/main/java/io/fairspace/saturn/services/views/MaterializedViewService.java
@@ -5,7 +5,6 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
-import java.util.Map;
import javax.sql.DataSource;
import lombok.extern.slf4j.Slf4j;
@@ -250,10 +249,8 @@ private void createJoinMaterializedViews(
if ("id".equalsIgnoreCase(attr)) {
continue;
}
- var isOfSetType = configuration
- .propertyTables
- .getOrDefault(joinView.view, Map.of())
- .containsKey(attr);
+ var propTable = configuration.propertyTables.get(joinView.view);
+ var isOfSetType = propTable != null && propTable.containsKey(attr);
tableAliases.put(joinedTable + "_" + attr.toLowerCase(), isOfSetType ? "jt_" + (i + 1) : "jt_0");
}
for (int i = 0; i < joinView.include.size(); i++) {
@@ -292,10 +289,8 @@ private void createJoinMaterializedViews(
for (int i = 0; i < joinView.include.size(); i++) {
var attr = joinView.include.get(i);
- var isOfSetType = configuration
- .propertyTables
- .getOrDefault(joinView.view, Map.of())
- .containsKey(attr);
+ var propTable = configuration.propertyTables.get(joinView.view);
+ var isOfSetType = propTable != null && propTable.containsKey(attr);
if (!isOfSetType) {
continue;
}
diff --git a/projects/saturn/src/main/java/io/fairspace/saturn/services/views/ViewStoreClient.java b/projects/saturn/src/main/java/io/fairspace/saturn/services/views/ViewStoreClient.java
index 112259331..b0668466b 100644
--- a/projects/saturn/src/main/java/io/fairspace/saturn/services/views/ViewStoreClient.java
+++ b/projects/saturn/src/main/java/io/fairspace/saturn/services/views/ViewStoreClient.java
@@ -10,8 +10,6 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -23,6 +21,7 @@
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
+import org.springframework.util.LinkedCaseInsensitiveMap;
import io.fairspace.saturn.config.properties.ViewsProperties;
import io.fairspace.saturn.services.views.Table.ColumnDefinition;
@@ -34,14 +33,16 @@
public class ViewStoreClient implements AutoCloseable {
public static class ViewStoreConfiguration {
- final Map viewConfig;
- final Map viewTables = new HashMap<>();
- final Map> propertyTables = new HashMap<>();
- final Map> joinTables = new HashMap<>();
+ final LinkedCaseInsensitiveMap viewConfig;
+ final LinkedCaseInsensitiveMap viewTables = new LinkedCaseInsensitiveMap<>();
+ final LinkedCaseInsensitiveMap> propertyTables =
+ new LinkedCaseInsensitiveMap<>();
+ final LinkedCaseInsensitiveMap> joinTables = new LinkedCaseInsensitiveMap<>();
public ViewStoreConfiguration(ViewsProperties viewsProperties) {
- viewConfig =
- viewsProperties.views.stream().collect(Collectors.toMap(view -> view.name, Function.identity()));
+ viewConfig = new LinkedCaseInsensitiveMap<>();
+ viewConfig.putAll(
+ viewsProperties.views.stream().collect(Collectors.toMap(view -> view.name, Function.identity())));
}
}
@@ -272,7 +273,7 @@ public void truncateViewTables(String view) throws SQLException {
tables.add(configuration.viewTables.get(view));
tables.addAll(configuration
.propertyTables
- .getOrDefault(view, Collections.emptyMap())
+ .getOrDefault(view, new LinkedCaseInsensitiveMap<>())
.values());
var joins = configuration.viewConfig.get(view).join;
if (joins != null) {
diff --git a/projects/saturn/src/main/java/io/fairspace/saturn/services/views/ViewStoreClientFactory.java b/projects/saturn/src/main/java/io/fairspace/saturn/services/views/ViewStoreClientFactory.java
index ace6e6332..7c9d2ca97 100644
--- a/projects/saturn/src/main/java/io/fairspace/saturn/services/views/ViewStoreClientFactory.java
+++ b/projects/saturn/src/main/java/io/fairspace/saturn/services/views/ViewStoreClientFactory.java
@@ -4,7 +4,6 @@
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -19,6 +18,7 @@
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
+import org.springframework.util.LinkedCaseInsensitiveMap;
import io.fairspace.saturn.config.properties.ViewDatabaseProperties;
import io.fairspace.saturn.config.properties.ViewsProperties;
@@ -252,7 +252,7 @@ void createOrUpdateView(ViewsProperties.View view) throws SQLException {
var name = String.format("%s_%s", view.name.toLowerCase(), column.name.toLowerCase());
var propertyTable = new Table(name, propertyTableColumns);
createOrUpdateTable(propertyTable);
- configuration.propertyTables.putIfAbsent(view.name, new HashMap<>());
+ configuration.propertyTables.putIfAbsent(view.name, new LinkedCaseInsensitiveMap<>());
configuration.propertyTables.get(view.name).put(column.name, propertyTable);
}
if (view.join != null) {
@@ -260,9 +260,8 @@ void createOrUpdateView(ViewsProperties.View view) throws SQLException {
for (ViewsProperties.View.JoinView join : view.join) {
var joinTable = getJoinTable(join, view);
createOrUpdateJoinTable(joinTable);
- configuration.joinTables.putIfAbsent(view.name, new HashMap<>());
+ configuration.joinTables.putIfAbsent(view.name, new LinkedCaseInsensitiveMap<>());
configuration.joinTables.get(view.name).put(join.view, joinTable);
- var joinView = configuration.viewConfig.get(join.view);
}
}
}
diff --git a/projects/saturn/src/test/java/io/fairspace/saturn/services/views/JdbcQueryServiceTest.java b/projects/saturn/src/test/java/io/fairspace/saturn/services/views/JdbcQueryServiceTest.java
index 4ab572640..3ba60ffb4 100644
--- a/projects/saturn/src/test/java/io/fairspace/saturn/services/views/JdbcQueryServiceTest.java
+++ b/projects/saturn/src/test/java/io/fairspace/saturn/services/views/JdbcQueryServiceTest.java
@@ -253,6 +253,16 @@ public void testRetrieveSamplePage() {
0.01);
}
+ @Test
+ public void retrieveSamplePageIsCaseInsensitive() {
+ var request = new ViewRequest();
+ request.setView("sample");
+ request.setPage(1);
+ request.setSize(10);
+ var page = sut.retrieveViewPage(request);
+ Assert.assertEquals(2, page.getRows().size());
+ }
+
@Test
public void testRetrieveSamplePageAfterReindexing() {
maintenanceService.recreateIndex();
@@ -356,6 +366,29 @@ public void testRetrieveSamplePageIncludeJoin() {
row2.get("Resource_analysisType").stream().map(ValueDto::label).collect(Collectors.toSet()));
}
+ @Test
+ public void retrieveSamplePageIncludeJoinIsCaseInsensitive() {
+ var request = new ViewRequest();
+ request.setView("sample");
+ request.setPage(1);
+ request.setSize(10);
+ request.setIncludeJoinedViews(true);
+ var page = sut.retrieveViewPage(request);
+ Assert.assertEquals(2, page.getRows().size());
+ var row1 = page.getRows().getFirst();
+ Assert.assertEquals(
+ "Sample A for subject 1",
+ row1.get("Sample").stream().findFirst().orElseThrow().label());
+ Assert.assertEquals(1, row1.get("Subject").size());
+ var row2 = page.getRows().get(1);
+ Assert.assertEquals(
+ "Sample B for subject 2",
+ row2.get("Sample").stream().findFirst().orElseThrow().label());
+ Assert.assertEquals(
+ Set.of("RNA-seq", "Whole genome sequencing"),
+ row2.get("Resource_analysisType").stream().map(ValueDto::label).collect(Collectors.toSet()));
+ }
+
@Test
public void testRetrieveSamplePageIncludeJoinAfterReindexing() {
maintenanceService.recreateIndex();
@@ -389,6 +422,15 @@ public void testCountSamplesWithoutMaxDisplayCount() {
assertEquals(2, result.count());
}
+ @Test
+ public void countSamplesIsCaseInsensitive() {
+ selectRegularUser();
+ var requestParams = new CountRequest();
+ requestParams.setView("sample");
+ var result = sut.count(requestParams);
+ assertEquals(2, result.count());
+ }
+
@Test
public void testCountSubjectWithMaxDisplayCountLimitLessThanTotalCount() {
var request = new CountRequest();