From 88a5dc4e18a0886938d79d6a617352529e0d1543 Mon Sep 17 00:00:00 2001 From: Kamil Czaja <46053356+kamilczaja@users.noreply.github.com> Date: Mon, 12 Aug 2024 15:53:27 +0200 Subject: [PATCH] feat: consolidated catalog filters (#263) Co-authored-by: Richard Treier --- CHANGELOG.md | 2 + .../api/model/catalog/CnfFilterAttribute.kt | 2 + .../catalog/CnfFilterAttributeDisplayType.kt} | 15 +- .../api/model/catalog/CnfFilterItem.kt | 4 +- .../CatalogQueryAvailableFilterFetcher.kt | 25 +- .../catalog/CatalogQueryDataOfferFetcher.kt | 6 +- .../catalog/CatalogQueryFilterService.kt | 6 +- .../dao/pages/catalog/CatalogQueryService.kt | 4 +- .../models/AvailableFilterValuesQuery.kt | 22 -- .../dao/utils/JsonDeserializationUtils.kt | 6 +- .../api/filtering/AttributeFilterQuery.kt | 22 -- .../CatalogFilterAttributeDefinition.kt | 31 -- ...CatalogFilterAttributeDefinitionService.kt | 35 +- .../api/filtering/CatalogFilterService.kt | 112 +++---- .../filtering/model/FilterAttributeApplied.kt | 26 ++ .../model/FilterAttributeDefinition.kt | 34 ++ .../api/filtering/model/FilterCondition.kt | 23 ++ .../model/FilterConditionFactory.kt} | 6 +- .../api/filtering/model/FilterValueFn.kt | 21 ++ .../authorityportal/seeds/DevScenario.kt | 22 +- .../seeds/utils/ScenarioData.kt | 37 +- .../CatalogDataspaceConfigService.kt | 6 + .../tests/services/catalog/CatalogApiTest.kt | 317 +++++------------- .../catalog/DataOfferDetailApiTest.kt | 32 +- .../OrganizationInfoApiServiceTest.kt | 18 +- .../fake-backend/impl/catalog-fake-impl.ts | 15 +- .../catalog-page/state/catalog-page-state.ts | 9 +- .../filter-box/filter-box-model.ts | 29 +- .../filter-box/filter-box.component.html | 8 +- .../src/app/shared/shared.module.ts | 2 + 30 files changed, 429 insertions(+), 468 deletions(-) rename authority-portal-backend/{authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/dao/pages/catalog/models/CatalogQueryFilter.kt => authority-portal-api/src/main/java/de/sovity/authorityportal/api/model/catalog/CnfFilterAttributeDisplayType.kt} (56%) delete mode 100644 authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/dao/pages/catalog/models/AvailableFilterValuesQuery.kt delete mode 100644 authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/services/api/filtering/AttributeFilterQuery.kt delete mode 100644 authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/services/api/filtering/CatalogFilterAttributeDefinition.kt create mode 100644 authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/services/api/filtering/model/FilterAttributeApplied.kt create mode 100644 authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/services/api/filtering/model/FilterAttributeDefinition.kt create mode 100644 authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/services/api/filtering/model/FilterCondition.kt rename authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/{dao/pages/catalog/models/CatalogQuerySelectedFilterQuery.kt => services/api/filtering/model/FilterConditionFactory.kt} (64%) create mode 100644 authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/services/api/filtering/model/FilterValueFn.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a494ee6d..f474e96b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,8 @@ please see [changelog_updates.md](docs/dev/changelog_updates.md). - Already registered connectors will be updated automatically, this process can take up to 24 hours - Added a message when the CaaS request feature is not available - Catalog: Removed dataspace filter when only one dataspace is known +- Catalog: Organization filter is no longer split into ID and name +- Catalog: Connector filter is no longer split into ID and endpoint - Organization list: Data offer and connector counts now show the correct numbers according to the active environment ### Known issues diff --git a/authority-portal-backend/authority-portal-api/src/main/java/de/sovity/authorityportal/api/model/catalog/CnfFilterAttribute.kt b/authority-portal-backend/authority-portal-api/src/main/java/de/sovity/authorityportal/api/model/catalog/CnfFilterAttribute.kt index 9ac1fb1b6..c248ec8dd 100644 --- a/authority-portal-backend/authority-portal-api/src/main/java/de/sovity/authorityportal/api/model/catalog/CnfFilterAttribute.kt +++ b/authority-portal-backend/authority-portal-api/src/main/java/de/sovity/authorityportal/api/model/catalog/CnfFilterAttribute.kt @@ -21,6 +21,8 @@ data class CnfFilterAttribute( val id: String, @field:Schema(description = "Attribute Title", example = "Language", requiredMode = Schema.RequiredMode.REQUIRED) val title: String, + @field:Schema(description = "How should items of this filter be rendered in the UI", requiredMode = Schema.RequiredMode.REQUIRED) + val displayType: CnfFilterAttributeDisplayType, @field:Schema(description = "Available values.", requiredMode = Schema.RequiredMode.REQUIRED) val values: List, ) diff --git a/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/dao/pages/catalog/models/CatalogQueryFilter.kt b/authority-portal-backend/authority-portal-api/src/main/java/de/sovity/authorityportal/api/model/catalog/CnfFilterAttributeDisplayType.kt similarity index 56% rename from authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/dao/pages/catalog/models/CatalogQueryFilter.kt rename to authority-portal-backend/authority-portal-api/src/main/java/de/sovity/authorityportal/api/model/catalog/CnfFilterAttributeDisplayType.kt index 7305b2b49..99b9b90e7 100644 --- a/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/dao/pages/catalog/models/CatalogQueryFilter.kt +++ b/authority-portal-backend/authority-portal-api/src/main/java/de/sovity/authorityportal/api/model/catalog/CnfFilterAttributeDisplayType.kt @@ -11,10 +11,13 @@ * sovity GmbH - initial API and implementation * */ -package de.sovity.authorityportal.broker.dao.pages.catalog.models +package de.sovity.authorityportal.api.model.catalog + +import io.swagger.v3.oas.annotations.media.Schema + +@Schema(description = "How should filter items be rendered in the UI", enumAsRef = true) +enum class CnfFilterAttributeDisplayType { + TITLE_ONLY, + ID_AND_TITLE +} -data class CatalogQueryFilter( - val name: String, - val valueQuery: AvailableFilterValuesQuery, - val queryFilterClauseOrNull: CatalogQuerySelectedFilterQuery? -) diff --git a/authority-portal-backend/authority-portal-api/src/main/java/de/sovity/authorityportal/api/model/catalog/CnfFilterItem.kt b/authority-portal-backend/authority-portal-api/src/main/java/de/sovity/authorityportal/api/model/catalog/CnfFilterItem.kt index e3db98c50..ea6e809d3 100644 --- a/authority-portal-backend/authority-portal-api/src/main/java/de/sovity/authorityportal/api/model/catalog/CnfFilterItem.kt +++ b/authority-portal-backend/authority-portal-api/src/main/java/de/sovity/authorityportal/api/model/catalog/CnfFilterItem.kt @@ -19,11 +19,11 @@ import io.swagger.v3.oas.annotations.media.Schema data class CnfFilterItem( @field:Schema( description = "Value ID", - example = "https://w3id.org/idsa/code/EN", + example = "MDSLXXXXX1", requiredMode = Schema.RequiredMode.REQUIRED ) val id: String, - @field:Schema(description = "Value Title", example = "English", requiredMode = Schema.RequiredMode.REQUIRED) + @field:Schema(description = "Value Title", example = "Example Organization", requiredMode = Schema.RequiredMode.REQUIRED) val title: String, ) diff --git a/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/dao/pages/catalog/CatalogQueryAvailableFilterFetcher.kt b/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/dao/pages/catalog/CatalogQueryAvailableFilterFetcher.kt index e510e9cc1..a8c9338b9 100644 --- a/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/dao/pages/catalog/CatalogQueryAvailableFilterFetcher.kt +++ b/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/dao/pages/catalog/CatalogQueryAvailableFilterFetcher.kt @@ -13,7 +13,7 @@ */ package de.sovity.authorityportal.broker.dao.pages.catalog -import de.sovity.authorityportal.broker.dao.pages.catalog.models.CatalogQueryFilter +import de.sovity.authorityportal.broker.services.api.filtering.model.FilterAttributeApplied import jakarta.enterprise.context.ApplicationScoped import org.jooq.Field import org.jooq.JSON @@ -36,10 +36,10 @@ class CatalogQueryAvailableFilterFetcher( environment: String, fields: CatalogQueryFields, searchQuery: String?, - filters: List + filters: List ): Field { val resultFields = filters.mapIndexed { i, currentFilter -> - // When querying a filter's values we apply all filters except for the current filter's values + // When querying a filter's values we apply all filters except for the current filter val otherFilters = filters.filterIndexed { j, _ -> i != j } queryFilterValues(environment, fields, currentFilter, searchQuery, otherFilters) } @@ -49,22 +49,31 @@ class CatalogQueryAvailableFilterFetcher( private fun queryFilterValues( environment: String, parentQueryFields: CatalogQueryFields, - currentFilter: CatalogQueryFilter, + currentFilter: FilterAttributeApplied, searchQuery: String?, - otherFilters: List + otherFilters: List ): Field { val fields = parentQueryFields.withSuffix("filter_" + currentFilter.name) - val value = currentFilter.valueQuery(fields) + val idField: Field = currentFilter.idField(fields) + val nameField: Field? = currentFilter.nameField?.invoke(fields) + + val idNameArray = if (nameField == null) { + DSL.array(idField) + } else { + DSL.array(idField, nameField) + } return DSL.select( DSL.coalesce( - DSL.arrayAggDistinct(value), - DSL.value(arrayOf()).cast>(SQLDataType.VARCHAR.array()) + DSL.arrayAggDistinct(idNameArray), + emptyStringArray() ) ) .fromCatalogQueryTables(fields) .where(catalogQueryFilterService.filterDbQuery(environment, fields, searchQuery, otherFilters)) .asField() } + + private fun emptyStringArray() = DSL.value(arrayOf()).cast>(SQLDataType.VARCHAR.array()) } diff --git a/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/dao/pages/catalog/CatalogQueryDataOfferFetcher.kt b/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/dao/pages/catalog/CatalogQueryDataOfferFetcher.kt index 72c475d1c..b95a4fd88 100644 --- a/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/dao/pages/catalog/CatalogQueryDataOfferFetcher.kt +++ b/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/dao/pages/catalog/CatalogQueryDataOfferFetcher.kt @@ -14,7 +14,7 @@ package de.sovity.authorityportal.broker.dao.pages.catalog import de.sovity.authorityportal.api.model.catalog.CatalogPageSortingType -import de.sovity.authorityportal.broker.dao.pages.catalog.models.CatalogQueryFilter +import de.sovity.authorityportal.broker.services.api.filtering.model.FilterAttributeApplied import de.sovity.authorityportal.broker.dao.pages.catalog.models.DataOfferListEntryRs import de.sovity.authorityportal.broker.dao.pages.catalog.models.PageQuery import de.sovity.authorityportal.broker.dao.utils.MultisetUtils @@ -42,7 +42,7 @@ class CatalogQueryDataOfferFetcher( environment: String, fields: CatalogQueryFields, searchQuery: String?, - filters: List, + filters: List, sorting: CatalogPageSortingType, pageQuery: PageQuery ): Field> { @@ -86,7 +86,7 @@ class CatalogQueryDataOfferFetcher( environment: String, fields: CatalogQueryFields, searchQuery: String?, - filters: List + filters: List ): Field { val query = DSL.select(DSL.count()) .fromCatalogQueryTables(fields) diff --git a/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/dao/pages/catalog/CatalogQueryFilterService.kt b/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/dao/pages/catalog/CatalogQueryFilterService.kt index 0808bb02b..060cdba67 100644 --- a/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/dao/pages/catalog/CatalogQueryFilterService.kt +++ b/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/dao/pages/catalog/CatalogQueryFilterService.kt @@ -13,7 +13,7 @@ */ package de.sovity.authorityportal.broker.dao.pages.catalog -import de.sovity.authorityportal.broker.dao.pages.catalog.models.CatalogQueryFilter +import de.sovity.authorityportal.broker.services.api.filtering.model.FilterAttributeApplied import de.sovity.authorityportal.broker.services.api.filtering.CatalogSearchService import de.sovity.authorityportal.db.jooq.enums.ConnectorOnlineStatus import de.sovity.authorityportal.db.jooq.tables.Connector @@ -33,13 +33,13 @@ class CatalogQueryFilterService( environment: String, fields: CatalogQueryFields, searchQuery: String?, - filters: List + filters: List ): Condition { val conditions = ArrayList() conditions.add(fields.connectorTable.ENVIRONMENT.eq(environment)) conditions.add(visibleConnectorsOfEnvironment(environment, fields.connectorTable)) conditions.add(catalogSearchService.filterBySearch(fields, searchQuery)) - conditions.addAll(filters.mapNotNull { it.queryFilterClauseOrNull }.map { it(fields) }) + conditions.addAll(filters.mapNotNull { it.filterConditionOrNull }.map { it(fields) }) return DSL.and(conditions) } diff --git a/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/dao/pages/catalog/CatalogQueryService.kt b/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/dao/pages/catalog/CatalogQueryService.kt index 1fe73e506..44c7c9f13 100644 --- a/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/dao/pages/catalog/CatalogQueryService.kt +++ b/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/dao/pages/catalog/CatalogQueryService.kt @@ -15,7 +15,7 @@ package de.sovity.authorityportal.broker.dao.pages.catalog import de.sovity.authorityportal.api.model.catalog.CatalogPageSortingType import de.sovity.authorityportal.broker.dao.pages.catalog.models.CatalogPageRs -import de.sovity.authorityportal.broker.dao.pages.catalog.models.CatalogQueryFilter +import de.sovity.authorityportal.broker.services.api.filtering.model.FilterAttributeApplied import de.sovity.authorityportal.broker.dao.pages.catalog.models.PageQuery import de.sovity.authorityportal.db.jooq.Tables import de.sovity.authorityportal.web.environment.CatalogDataspaceConfigService @@ -42,7 +42,7 @@ class CatalogQueryService( fun queryCatalogPage( environment: String, searchQuery: String?, - filters: List, + filters: List, sorting: CatalogPageSortingType, pageQuery: PageQuery ): CatalogPageRs { diff --git a/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/dao/pages/catalog/models/AvailableFilterValuesQuery.kt b/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/dao/pages/catalog/models/AvailableFilterValuesQuery.kt deleted file mode 100644 index c458510ff..000000000 --- a/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/dao/pages/catalog/models/AvailableFilterValuesQuery.kt +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (c) 2023 sovity GmbH - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * sovity GmbH - initial API and implementation - * - */ -package de.sovity.authorityportal.broker.dao.pages.catalog.models - -import de.sovity.authorityportal.broker.dao.pages.catalog.CatalogQueryFields -import org.jooq.Field - -/** - * Gets the values for a given filter attribute from a list of data offers. - */ -typealias AvailableFilterValuesQuery = (CatalogQueryFields) -> Field diff --git a/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/dao/utils/JsonDeserializationUtils.kt b/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/dao/utils/JsonDeserializationUtils.kt index ba221fc2d..8635d23fc 100644 --- a/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/dao/utils/JsonDeserializationUtils.kt +++ b/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/dao/utils/JsonDeserializationUtils.kt @@ -36,9 +36,9 @@ import com.fasterxml.jackson.databind.ObjectMapper */ object JsonDeserializationUtils { private val objectMapper: ObjectMapper = ObjectMapper() - private val TYPE_STRING_LIST_2: TypeReference>> = object : TypeReference>>() {} + private val TYPE_STRING_LIST_3: TypeReference>>> = object : TypeReference>>>() {} - fun read2dStringList(json: String): List> { - return objectMapper.readValue(json, TYPE_STRING_LIST_2) + fun read3dStringList(json: String): List>> { + return objectMapper.readValue(json, TYPE_STRING_LIST_3) } } diff --git a/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/services/api/filtering/AttributeFilterQuery.kt b/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/services/api/filtering/AttributeFilterQuery.kt deleted file mode 100644 index 5446c5d9e..000000000 --- a/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/services/api/filtering/AttributeFilterQuery.kt +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (c) 2023 sovity GmbH - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * sovity GmbH - initial API and implementation - * - */ -package de.sovity.authorityportal.broker.services.api.filtering - -import de.sovity.authorityportal.broker.dao.pages.catalog.CatalogQueryFields -import org.jooq.Condition - -/** - * Filters a Catalog DB Query for a given Filter Attribute with selected values - */ -typealias AttributeFilterQuery = (CatalogQueryFields, Collection) -> Condition diff --git a/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/services/api/filtering/CatalogFilterAttributeDefinition.kt b/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/services/api/filtering/CatalogFilterAttributeDefinition.kt deleted file mode 100644 index d4704edc4..000000000 --- a/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/services/api/filtering/CatalogFilterAttributeDefinition.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2023 sovity GmbH - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * sovity GmbH - initial API and implementation - * - */ -package de.sovity.authorityportal.broker.services.api.filtering - -import de.sovity.authorityportal.broker.dao.pages.catalog.models.AvailableFilterValuesQuery - -/** - * Implementation of a filter attribute definition for the catalog. - * - * @param name technical id of the attribute - * @param label UI showing label for the attribute - * @param valueGetter query existing values from DB - * @param filterApplier apply a filter to a data offer query - */ -data class CatalogFilterAttributeDefinition( - val name: String, - val label: String, - val valueGetter: AvailableFilterValuesQuery, - val filterApplier: AttributeFilterQuery -) diff --git a/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/services/api/filtering/CatalogFilterAttributeDefinitionService.kt b/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/services/api/filtering/CatalogFilterAttributeDefinitionService.kt index be5eea451..395ce7e7b 100644 --- a/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/services/api/filtering/CatalogFilterAttributeDefinitionService.kt +++ b/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/services/api/filtering/CatalogFilterAttributeDefinitionService.kt @@ -13,30 +13,51 @@ */ package de.sovity.authorityportal.broker.services.api.filtering +import de.sovity.authorityportal.api.model.catalog.CnfFilterAttributeDisplayType import de.sovity.authorityportal.broker.dao.pages.catalog.CatalogQueryFields import de.sovity.authorityportal.broker.dao.utils.eqAny +import de.sovity.authorityportal.broker.services.api.filtering.model.FilterAttributeDefinition import jakarta.enterprise.context.ApplicationScoped import org.jooq.Field @ApplicationScoped class CatalogFilterAttributeDefinitionService { - fun forField( + fun forIdOnlyField( fieldExtractor: (CatalogQueryFields) -> Field, name: String, label: String - ): CatalogFilterAttributeDefinition { - return CatalogFilterAttributeDefinition( + ): FilterAttributeDefinition { + return FilterAttributeDefinition( name = name, label = label, - valueGetter = fieldExtractor + displayType = CnfFilterAttributeDisplayType.TITLE_ONLY, + idField = fieldExtractor, + nameField = null ) { fields, values -> fieldExtractor(fields).eqAny(values) } } - fun buildDataSpaceFilter(): CatalogFilterAttributeDefinition { - return CatalogFilterAttributeDefinition( + fun forIdNameProperty( + idFieldExtractor: (CatalogQueryFields) -> Field, + nameFieldExtractor: (CatalogQueryFields) -> Field, + name: String, + label: String + ): FilterAttributeDefinition { + return FilterAttributeDefinition( + name = name, + label = label, + displayType = CnfFilterAttributeDisplayType.ID_AND_TITLE, + idField = idFieldExtractor, + nameField = nameFieldExtractor + ) { fields, values -> idFieldExtractor(fields).eqAny(values) } + } + + fun buildDataSpaceFilter(): FilterAttributeDefinition { + return FilterAttributeDefinition( name = "dataSpace", label = "Data Space", - valueGetter = CatalogQueryFields::dataSpace + displayType = CnfFilterAttributeDisplayType.TITLE_ONLY, + idField = CatalogQueryFields::dataSpace, + nameField = null ) { fields, values -> fields.dataSpace.eqAny(values) } } } diff --git a/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/services/api/filtering/CatalogFilterService.kt b/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/services/api/filtering/CatalogFilterService.kt index f7bcacc91..df8a2d354 100644 --- a/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/services/api/filtering/CatalogFilterService.kt +++ b/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/services/api/filtering/CatalogFilterService.kt @@ -18,18 +18,17 @@ import de.sovity.authorityportal.api.model.catalog.CnfFilterAttribute import de.sovity.authorityportal.api.model.catalog.CnfFilterItem import de.sovity.authorityportal.api.model.catalog.CnfFilterValue import de.sovity.authorityportal.broker.dao.pages.catalog.CatalogQueryFields -import de.sovity.authorityportal.broker.dao.pages.catalog.models.CatalogQueryFilter -import de.sovity.authorityportal.broker.dao.pages.catalog.models.CatalogQuerySelectedFilterQuery -import de.sovity.authorityportal.broker.dao.utils.JsonDeserializationUtils.read2dStringList +import de.sovity.authorityportal.broker.dao.utils.JsonDeserializationUtils.read3dStringList +import de.sovity.authorityportal.broker.services.api.filtering.model.FilterAttributeApplied +import de.sovity.authorityportal.broker.services.api.filtering.model.FilterAttributeDefinition +import de.sovity.authorityportal.broker.services.api.filtering.model.FilterCondition import de.sovity.authorityportal.web.environment.CatalogDataspaceConfigService -import de.sovity.authorityportal.web.environment.DeploymentEnvironmentService import jakarta.enterprise.context.ApplicationScoped -import org.eclipse.microprofile.config.inject.ConfigProperty +import org.jooq.impl.DSL @ApplicationScoped class CatalogFilterService( val catalogFilterAttributeDefinitionService: CatalogFilterAttributeDefinitionService, - val deploymentEnvironmentService: DeploymentEnvironmentService, val catalogDataspaceConfigService: CatalogDataspaceConfigService ) { @@ -41,114 +40,118 @@ class CatalogFilterService( } } - private val availableFilters: List + private val availableFilters: List /** * Currently supported filters for the catalog page. * * @return attribute definitions */ get() = listOfNotNull( - catalogFilterAttributeDefinitionService.forField( + catalogFilterAttributeDefinitionService.forIdOnlyField( { fields: CatalogQueryFields -> fields.dataSourceAvailabilityLabel }, "dataSourceAvailability", "Data Offer Type" ), - catalogFilterAttributeDefinitionService.buildDataSpaceFilter().takeIf { this.shouldSupportMultipleDataspaces() }, - catalogFilterAttributeDefinitionService.forField( + catalogFilterAttributeDefinitionService.buildDataSpaceFilter() + .takeIf { catalogDataspaceConfigService.hasMultipleDataspaces }, + catalogFilterAttributeDefinitionService.forIdOnlyField( { fields: CatalogQueryFields -> fields.dataOfferTable.DATA_CATEGORY }, "dataCategory", "Data Category" ), - catalogFilterAttributeDefinitionService.forField( + catalogFilterAttributeDefinitionService.forIdOnlyField( { fields: CatalogQueryFields -> fields.dataOfferTable.DATA_SUBCATEGORY }, "dataSubcategory", "Data Subcategory" ), - catalogFilterAttributeDefinitionService.forField( + catalogFilterAttributeDefinitionService.forIdOnlyField( { fields: CatalogQueryFields -> fields.dataOfferTable.DATA_MODEL }, "dataModel", "Data Model" ), - catalogFilterAttributeDefinitionService.forField( + catalogFilterAttributeDefinitionService.forIdOnlyField( { fields: CatalogQueryFields -> fields.dataOfferTable.TRANSPORT_MODE }, "transportMode", "Transport Mode" ), - catalogFilterAttributeDefinitionService.forField( + catalogFilterAttributeDefinitionService.forIdOnlyField( { fields: CatalogQueryFields -> fields.dataOfferTable.GEO_REFERENCE_METHOD }, "geoReferenceMethod", "Geo Reference Method" ), - catalogFilterAttributeDefinitionService.forField( + catalogFilterAttributeDefinitionService.forIdNameProperty( + { fields: CatalogQueryFields -> fields.organizationTable.ID }, { fields: CatalogQueryFields -> fields.organizationTable.NAME }, - "organizationName", - "Organization Name" + "organization", + "Organization" ), - catalogFilterAttributeDefinitionService.forField( - { fields: CatalogQueryFields -> fields.connectorTable.ORGANIZATION_ID }, - "organizationId", - "Organization ID" - ), - catalogFilterAttributeDefinitionService.forField( + catalogFilterAttributeDefinitionService.forIdNameProperty( { fields: CatalogQueryFields -> fields.connectorTable.CONNECTOR_ID }, + { fields: CatalogQueryFields -> + DSL.concat( + fields.connectorTable.NAME, + DSL.`val`(" - "), + fields.organizationTable.NAME + ) + }, "connectorId", - "Connector ID" + "Connector" ), - catalogFilterAttributeDefinitionService.forField( - { fields: CatalogQueryFields -> fields.connectorTable.ENDPOINT_URL }, - "connectorEndpoint", - "Connector Endpoint" - ) ) - fun getCatalogQueryFilters(cnfFilterValue: CnfFilterValue?): List { + fun getCatalogQueryFilters(cnfFilterValue: CnfFilterValue?): List { val values = getCnfFilterValuesMap(cnfFilterValue) return availableFilters - .map { filter: CatalogFilterAttributeDefinition -> + .map { filter: FilterAttributeDefinition -> val queryFilter = getQueryFilter(filter, values[filter.name]) - CatalogQueryFilter( - filter.name, - filter.valueGetter, - queryFilter + FilterAttributeApplied( + name = filter.name, + idField = filter.idField, + nameField = filter.nameField, + filterConditionOrNull = queryFilter ) } .toList() } private fun getQueryFilter( - filter: CatalogFilterAttributeDefinition, + filter: FilterAttributeDefinition, values: List? - ): CatalogQuerySelectedFilterQuery? { + ): FilterCondition? { if (values.isNullOrEmpty()) { return null } - return { fields: CatalogQueryFields -> filter.filterApplier(fields, values) } + return { fields: CatalogQueryFields -> filter.filterConditionFactory(fields, values) } } fun buildAvailableFilters(filterValuesJson: String): CnfFilter { - val filterValues = read2dStringList(filterValuesJson) + val filterValues = read3dStringList(filterValuesJson) val filterAttributes = zipAvailableFilters(availableFilters, filterValues) .map { availableFilter: AvailableFilter -> CnfFilterAttribute( - availableFilter.definition.name, - availableFilter.definition.label, - buildAvailableFilterValues(availableFilter) + id = availableFilter.definition.name, + title = availableFilter.definition.label, + values = buildAvailableFilterValues(availableFilter), + displayType = availableFilter.definition.displayType ) } - .toList() return CnfFilter(filterAttributes) } private fun buildAvailableFilterValues(availableFilter: AvailableFilter): List { return availableFilter.availableValues - .sortedWith(caseInsensitiveEmptyStringLast) - .map { CnfFilterItem(it, it) } - .toList() + .map { + CnfFilterItem( + id = it.first(), + title = it.last(), + ) + } + .sortedWith(java.util.Comparator.comparing({ it.title }, caseInsensitiveEmptyStringLast)) } private fun zipAvailableFilters( - availableFilters: List, - filterValues: List> + availableFilters: List, + filterValues: List>> ): List { require(availableFilters.size == filterValues.size) { "Number of available filters and filter values must match: ${availableFilters.size} != ${filterValues.size}" @@ -159,8 +162,8 @@ class CatalogFilterService( } private data class AvailableFilter( - val definition: CatalogFilterAttributeDefinition, - val availableValues: List + val definition: FilterAttributeDefinition, + val availableValues: List> ) private fun getCnfFilterValuesMap(cnfFilterValue: CnfFilterValue?): Map> { @@ -171,13 +174,4 @@ class CatalogFilterService( .filter { it.selectedIds.isNotEmpty() } .associate { it.id to it.selectedIds } } - - private fun shouldSupportMultipleDataspaces(): Boolean { - deploymentEnvironmentService.findAll().forEach { - if (catalogDataspaceConfigService.forEnvironment(it.key).namesByConnectorId.isNotEmpty()) { - return true - } - } - return false - } } diff --git a/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/services/api/filtering/model/FilterAttributeApplied.kt b/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/services/api/filtering/model/FilterAttributeApplied.kt new file mode 100644 index 000000000..3a58e83ec --- /dev/null +++ b/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/services/api/filtering/model/FilterAttributeApplied.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2024 sovity GmbH + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * sovity GmbH - initial implementation + */ +package de.sovity.authorityportal.broker.services.api.filtering.model + +/** + * Abbreviated [FilterAttributeDefinition] with the actual filter condition applied for selected items + * that came in specifically for this request. + * + * Contains the information as required by the JooQ query + */ +data class FilterAttributeApplied( + val name: String, + val idField: FilterValueFn, + val nameField: FilterValueFn?, + val filterConditionOrNull: FilterCondition? +) diff --git a/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/services/api/filtering/model/FilterAttributeDefinition.kt b/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/services/api/filtering/model/FilterAttributeDefinition.kt new file mode 100644 index 000000000..26d4ef2a8 --- /dev/null +++ b/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/services/api/filtering/model/FilterAttributeDefinition.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2024 sovity GmbH + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * sovity GmbH - initial implementation + */ +package de.sovity.authorityportal.broker.services.api.filtering.model + +import de.sovity.authorityportal.api.model.catalog.CnfFilterAttributeDisplayType + +/** + * Implementation of a filter attribute for the catalog. + * + * @param name technical id of the attribute + * @param label attribute label is shown as the title of the filter-box in the UI + * @param displayType how to display the available values in the UI + * @param idField get available value's id in the JooQ query + * @param nameField get available value's name in the JooQ query (optional) + * @param filterConditionFactory apply the filter + */ +data class FilterAttributeDefinition( + val name: String, + val label: String, + val displayType: CnfFilterAttributeDisplayType, + val idField: FilterValueFn, + val nameField: FilterValueFn?, + val filterConditionFactory: FilterConditionFactory +) diff --git a/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/services/api/filtering/model/FilterCondition.kt b/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/services/api/filtering/model/FilterCondition.kt new file mode 100644 index 000000000..55eb85c19 --- /dev/null +++ b/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/services/api/filtering/model/FilterCondition.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2024 sovity GmbH + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * sovity GmbH - initial implementation + */ +package de.sovity.authorityportal.broker.services.api.filtering.model + +import de.sovity.authorityportal.broker.dao.pages.catalog.CatalogQueryFields +import org.jooq.Condition + +/** + * Non-null actual condition to be added to the query + * + * Already knows its list of values, so it was created for one filter attribute of one request + */ +typealias FilterCondition = (CatalogQueryFields) -> Condition diff --git a/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/dao/pages/catalog/models/CatalogQuerySelectedFilterQuery.kt b/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/services/api/filtering/model/FilterConditionFactory.kt similarity index 64% rename from authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/dao/pages/catalog/models/CatalogQuerySelectedFilterQuery.kt rename to authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/services/api/filtering/model/FilterConditionFactory.kt index 57ef2a839..83e5fc35b 100644 --- a/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/dao/pages/catalog/models/CatalogQuerySelectedFilterQuery.kt +++ b/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/services/api/filtering/model/FilterConditionFactory.kt @@ -11,12 +11,12 @@ * sovity GmbH - initial API and implementation * */ -package de.sovity.authorityportal.broker.dao.pages.catalog.models +package de.sovity.authorityportal.broker.services.api.filtering.model import de.sovity.authorityportal.broker.dao.pages.catalog.CatalogQueryFields import org.jooq.Condition /** - * Adds a filter to a Catalog Query. + * Given a list of selected values for this filter, can build the Condition for filtering for it */ -typealias CatalogQuerySelectedFilterQuery = (CatalogQueryFields) -> Condition +typealias FilterConditionFactory = (CatalogQueryFields, Collection) -> Condition diff --git a/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/services/api/filtering/model/FilterValueFn.kt b/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/services/api/filtering/model/FilterValueFn.kt new file mode 100644 index 000000000..cd6853079 --- /dev/null +++ b/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/broker/services/api/filtering/model/FilterValueFn.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2024 sovity GmbH + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * sovity GmbH - initial implementation + */ +package de.sovity.authorityportal.broker.services.api.filtering.model + +import de.sovity.authorityportal.broker.dao.pages.catalog.CatalogQueryFields +import org.jooq.Field + +/** + * Gets the values for a given filter attribute from a list of data offers. + */ +typealias FilterValueFn = (CatalogQueryFields) -> Field diff --git a/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/seeds/DevScenario.kt b/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/seeds/DevScenario.kt index c541b0155..0cdfc0f67 100644 --- a/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/seeds/DevScenario.kt +++ b/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/seeds/DevScenario.kt @@ -95,7 +95,10 @@ class DevScenario( } // Catalog test data - val asset1 = UiAsset().also { + val objectMapper = ObjectMapper() + + connector(1, 1, 1) + dataOffer(1, 1, 1, assetApplier = { it.assetId = dummyDevAssetId(1) it.title = "Asset Title" it.connectorEndpoint = "https://test-connector/dsp" @@ -105,8 +108,8 @@ class DevScenario( it.description = "Long description" it.descriptionShortText = "Short description" it.dataSourceAvailability = DataSourceAvailability.LIVE - } - val asset2 = UiAsset().also { + }) + dataOffer(1, 1, 2, assetApplier = { it.assetId = dummyDevAssetId(2) it.title = "OnDemand Asset" it.connectorEndpoint = null @@ -116,18 +119,7 @@ class DevScenario( it.description = "Long description" it.descriptionShortText = "Short description" it.dataSourceAvailability = DataSourceAvailability.ON_REQUEST - } - val objectMapper = ObjectMapper() - - connector(1, 1, 1) - dataOffer(1, 1, 1) { - it.uiAssetJson = JSONB.valueOf(objectMapper.writeValueAsString(asset1)) - it.assetTitle = "Asset Title" - } - dataOffer(1, 1, 2) { - it.uiAssetJson = JSONB.valueOf(objectMapper.writeValueAsString(asset2)) - it.assetTitle = "OnDemand Asset" - } + }) contractOffer(1, 1, 1, 1) contractOffer(1, 1, 2, 2) } diff --git a/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/seeds/utils/ScenarioData.kt b/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/seeds/utils/ScenarioData.kt index f6b8b48c9..2ee0ca53a 100644 --- a/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/seeds/utils/ScenarioData.kt +++ b/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/seeds/utils/ScenarioData.kt @@ -179,34 +179,43 @@ class ScenarioData { orgId: Int, assetId: Int, viewCount: Int = 0, - applyer: (DataOfferRecord) -> Unit = {} + dataOfferApplier: (DataOfferRecord) -> Unit = {}, + assetApplier: (UiAsset) -> Unit = {}, ) { val fullConnectorId = dummyDevConnectorId(orgId, connectorId) val objectMapper = ObjectMapper() val uiAsset = UiAsset().also { - it.title = "Title" - it.description = "# Long Description" - it.descriptionShortText = "shortDescription" it.dataSourceAvailability = DataSourceAvailability.LIVE + + it.assetId = dummyDevAssetId(assetId) + it.title = "Asset $assetId" + it.description = "Asset description" + it.descriptionShortText = "shortDescription" + it.dataCategory = "dataCategory" + it.keywords = listOf("keyword") + assetApplier(it) } DataOfferRecord().also { it.connectorId = fullConnectorId - it.assetId = dummyDevAssetId(assetId) it.uiAssetJson = JSONB.valueOf(objectMapper.writeValueAsString(uiAsset)) it.createdAt = OffsetDateTime.now() it.updatedAt = OffsetDateTime.now() + + + it.assetId = uiAsset.assetId it.assetTitle = uiAsset.title - it.descriptionNoMarkdown = "Long Description" - it.shortDescriptionNoMarkdown = "shortDescription" - it.dataCategory = "Data Category" - it.dataSubcategory = "Data Subcategory" - it.transportMode = "Transport Mode" - it.geoReferenceMethod = "Geo Reference Method" - it.keywords = emptyList() - it.keywordsCommaJoined = "" - applyer(it) + it.descriptionNoMarkdown = uiAsset.description?.let{ d -> "$d no markdown" } ?: "" + it.shortDescriptionNoMarkdown = uiAsset.descriptionShortText ?: "" + it.dataCategory = uiAsset.dataCategory ?: "" + it.dataSubcategory = uiAsset.dataSubcategory ?: "" + it.transportMode = uiAsset.transportMode ?: "" + it.geoReferenceMethod = uiAsset.geoReferenceMethod ?: "" + it.keywords = uiAsset.keywords ?: emptyList() + it.keywordsCommaJoined = uiAsset.keywords?.joinToString(",") ?: "" + it.dataModel = uiAsset.dataModel ?: "" + dataOfferApplier(it) dataOffers.add(it) } diff --git a/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/web/environment/CatalogDataspaceConfigService.kt b/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/web/environment/CatalogDataspaceConfigService.kt index 882f1d770..13c500695 100644 --- a/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/web/environment/CatalogDataspaceConfigService.kt +++ b/authority-portal-backend/authority-portal-quarkus/src/main/kotlin/de/sovity/authorityportal/web/environment/CatalogDataspaceConfigService.kt @@ -19,6 +19,12 @@ import jakarta.enterprise.context.ApplicationScoped class CatalogDataspaceConfigService( val deploymentEnvironmentService: DeploymentEnvironmentService ) { + + val hasMultipleDataspaces: Boolean by lazy { + deploymentEnvironmentService.findAll().values + .any { it.dataCatalog().dataspaceNames().connectorIds().isNotEmpty() } + } + fun forEnvironment(envId: String): CatalogDataspaceConfig { val env = deploymentEnvironmentService.findByIdOrThrow(envId) return CatalogDataspaceConfig( diff --git a/authority-portal-backend/authority-portal-quarkus/src/test/kotlin/de/sovity/authorityportal/web/tests/services/catalog/CatalogApiTest.kt b/authority-portal-backend/authority-portal-quarkus/src/test/kotlin/de/sovity/authorityportal/web/tests/services/catalog/CatalogApiTest.kt index 713df0128..5a06a9cf4 100644 --- a/authority-portal-backend/authority-portal-quarkus/src/test/kotlin/de/sovity/authorityportal/web/tests/services/catalog/CatalogApiTest.kt +++ b/authority-portal-backend/authority-portal-quarkus/src/test/kotlin/de/sovity/authorityportal/web/tests/services/catalog/CatalogApiTest.kt @@ -13,7 +13,6 @@ package de.sovity.authorityportal.web.tests.services.catalog -import com.fasterxml.jackson.databind.ObjectMapper import de.sovity.authorityportal.api.CatalogResource import de.sovity.authorityportal.api.model.catalog.CatalogPageQuery import de.sovity.authorityportal.api.model.catalog.CatalogPageResult @@ -36,13 +35,11 @@ import de.sovity.authorityportal.web.environment.CatalogDataspaceConfigService import de.sovity.authorityportal.web.tests.useDevUser import de.sovity.authorityportal.web.tests.useMockNow import de.sovity.edc.ext.wrapper.api.common.model.DataSourceAvailability -import de.sovity.edc.ext.wrapper.api.common.model.UiAsset import io.quarkus.test.InjectMock import io.quarkus.test.TestTransaction import io.quarkus.test.junit.QuarkusTest import jakarta.inject.Inject import org.assertj.core.api.Assertions.assertThat -import org.jooq.JSONB import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.mockito.junit.jupiter.MockitoExtension @@ -60,9 +57,6 @@ class CatalogApiTest { @Inject lateinit var scenarioInstaller: ScenarioInstaller - @Inject - lateinit var objectMapper: ObjectMapper - @InjectMock lateinit var catalogDataspaceConfigService: CatalogDataspaceConfigService @@ -94,6 +88,7 @@ class CatalogApiTest { defaultName = "MDS" ) ) + whenever(catalogDataspaceConfigService.hasMultipleDataspaces).thenReturn(true) val query = CatalogPageQuery( filter = CnfFilterValue( @@ -113,57 +108,6 @@ class CatalogApiTest { assertThat(result.dataOffers.first().connectorId).isEqualTo(dummyDevConnectorId(0, 1)) } - @Test - @TestTransaction - fun `test connector endpoint filter - two connectors and filter for one`() { - // arrange - useDevUser(0, 0) - - ScenarioData().apply { - organization(0, 0) - user(0, 0) - - connector(0, 0, 0) { - it.endpointUrl = "https://connector-0/dsp" - } - dataOffer(0, 0, 0) - - connector(1, 0, 0) { - it.endpointUrl = "https://connector-1/dsp" - } - dataOffer(1, 0, 0) - - scenarioInstaller.install(this) - } - - whenever(catalogDataspaceConfigService.forEnvironment(any())).thenReturn( - CatalogDataspaceConfig( - namesByConnectorId = mapOf( - dummyDevConnectorId(0, 1) to "Dataspace 1", - dummyDevConnectorId(0, 2) to "Dataspace 2" - ), - defaultName = "MDS" - ) - ) - - val query = CatalogPageQuery( - filter = CnfFilterValue( - listOf( - CnfFilterValueAttribute("connectorEndpoint", listOf("https://connector-0/dsp")) - ) - ), - searchQuery = null, - sorting = null - ) - - // act - val result = catalogResource.catalogPage("test", query) - - // assert - assertThat(result.dataOffers).hasSize(1) - assertThat(result.dataOffers.first().connectorId).isEqualTo(dummyDevConnectorId(0, 0)) - } - @Test @TestTransaction fun `test available filter values to filter by`() { @@ -182,6 +126,7 @@ class CatalogApiTest { defaultName = "MDS" ) ) + whenever(catalogDataspaceConfigService.hasMultipleDataspaces).thenReturn(true) ScenarioData().apply { organization(0, 0) @@ -233,13 +178,6 @@ class CatalogApiTest { ) ) - val uiAsset = UiAsset().also { - it.assetId = dummyDevAssetId(0) - it.title = "Data Offer 0" - it.description = "Data Offer 0 Description" - it.dataSourceAvailability = DataSourceAvailability.ON_REQUEST - } - ScenarioData().apply { organization(0, 0) user(0, 0) @@ -249,11 +187,11 @@ class CatalogApiTest { it.endpointUrl = "https://connector-0/dsp" } - dataOffer(0, 0, 0) { - it.assetTitle = "Data Offer 0" - it.uiAssetJson = JSONB.valueOf(objectMapper.writeValueAsString(uiAsset)) - it.descriptionNoMarkdown = "Data Offer Description 0" - } + dataOffer(0, 0, 0, assetApplier = { + it.title = "Data Offer 0" + it.description = "Data Offer 0 Description" + it.dataSourceAvailability = DataSourceAvailability.ON_REQUEST + }) scenarioInstaller.install(this) } @@ -289,43 +227,6 @@ class CatalogApiTest { ) ) - val uiAsset1 = UiAsset().also { - it.assetId = dummyDevAssetId(0) - it.title = "Data Offer 0" - it.description = "Data Offer 0 Description" - it.transportMode = "Transport Mode 1" - it.dataSubcategory = "Data Subcategory 1" - it.dataModel = "Data Model 1" - it.geoReferenceMethod = "Geo Reference Method 1" - it.dataSourceAvailability = DataSourceAvailability.LIVE - } - val uiAsset2 = UiAsset().also { - it.assetId = dummyDevAssetId(1) - it.title = "Data Offer 1" - it.description = "Data Offer 1 Description" - it.dataCategory = "Data Category 1" - it.transportMode = "Transport Mode 1" - it.dataSubcategory = "Data Subcategory 1" - it.dataSourceAvailability = DataSourceAvailability.LIVE - } - val uiAsset3 = UiAsset().also { - it.assetId = dummyDevAssetId(2) - it.title = "Data Offer 2" - it.description = "Data Offer 2 Description" - it.dataCategory = "Data Category 1" - it.transportMode = "Transport Mode 2" - it.dataSubcategory = "Data Subcategory 2" - it.dataSourceAvailability = DataSourceAvailability.LIVE - } - val uiAsset4 = UiAsset().also { - it.assetId = dummyDevAssetId(3) - it.title = "Data Offer 3" - it.description = "Data Offer 3 Description" - it.dataCategory = "Data Category 1" - it.transportMode = "" - it.dataSourceAvailability = DataSourceAvailability.LIVE - } - ScenarioData().apply { organization(0, 0) user(0, 0) @@ -335,25 +236,38 @@ class CatalogApiTest { it.endpointUrl = "https://connector-0/dsp" } - dataOffer(0, 0, 0) { - it.assetTitle = "Data Offer 0" - } - dataOffer(0, 0, 1) { - it.assetTitle = "Data Offer 1" - it.uiAssetJson = JSONB.valueOf(objectMapper.writeValueAsString(uiAsset1)) - } - dataOffer(0, 0, 2) { - it.assetTitle = "Data Offer 2" - it.uiAssetJson = JSONB.valueOf(objectMapper.writeValueAsString(uiAsset2)) - } - dataOffer(0, 0, 3) { - it.assetTitle = "Data Offer 3" - it.uiAssetJson = JSONB.valueOf(objectMapper.writeValueAsString(uiAsset3)) - } - dataOffer(0, 0, 4) { - it.assetTitle = "Data Offer 4" - it.uiAssetJson = JSONB.valueOf(objectMapper.writeValueAsString(uiAsset4)) - } + dataOffer(0, 0, 0, assetApplier = { + it.title = "Data Offer 0" + it.description = "Data Offer 0 Description" + it.transportMode = "Transport Mode 1" + it.dataSubcategory = "Data Subcategory 1" + it.dataModel = "Data Model 1" + it.geoReferenceMethod = "Geo Reference Method 1" + it.dataSourceAvailability = DataSourceAvailability.LIVE + }) + dataOffer(0, 0, 1, assetApplier = { + it.title = "Data Offer 1" + it.description = "Data Offer 1 Description" + it.dataCategory = "Data Category 1" + it.transportMode = "Transport Mode 1" + it.dataSubcategory = "Data Subcategory 1" + it.dataSourceAvailability = DataSourceAvailability.LIVE + }) + dataOffer(0, 0, 2, assetApplier = { + it.title = "Data Offer 2" + it.description = "Data Offer 2 Description" + it.dataCategory = "Data Category 1" + it.transportMode = "Transport Mode 2" + it.dataSubcategory = "Data Subcategory 2" + it.dataSourceAvailability = DataSourceAvailability.LIVE + }) + dataOffer(0, 0, 3, assetApplier = { + it.title = "Data Offer 3" + it.description = "Data Offer 3 Description" + it.dataCategory = "Data Category 1" + it.transportMode = "" + it.dataSourceAvailability = DataSourceAvailability.LIVE + }) scenarioInstaller.install(this) } @@ -394,32 +308,46 @@ class CatalogApiTest { } val dataCategory = getAvailableFilter(result, "dataCategory") - assertThat(dataCategory.values).allSatisfy { it.id in setOf("Data Category 1") } + assertThat(dataCategory.values).usingRecursiveFieldByFieldElementComparator().containsExactly( + CnfFilterItem("Data Category 1", "Data Category 1"), + CnfFilterItem("dataCategory", "dataCategory"), + ) val dataSubcategory = getAvailableFilter(result, "dataSubcategory") - assertThat(dataSubcategory.values).allSatisfy { it.id in setOf("Data Subcategory 1", "Data Subcategory 2", "") } + assertThat(dataSubcategory.values).usingRecursiveFieldByFieldElementComparator().containsExactly( + CnfFilterItem("Data Subcategory 1", "Data Subcategory 1"), + CnfFilterItem("Data Subcategory 2", "Data Subcategory 2"), + CnfFilterItem("", ""), + ) val dataModel = getAvailableFilter(result, "dataModel") - assertThat(dataModel.values).allSatisfy { it.id in setOf("Data Model 1", "") } + assertThat(dataModel.values).usingRecursiveFieldByFieldElementComparator().containsExactly( + CnfFilterItem("Data Model 1", "Data Model 1"), + CnfFilterItem("", "") + ) val transportMode = getAvailableFilter(result, "transportMode") - assertThat(transportMode.values).allSatisfy { it.id in setOf("Transport Mode 1", "Transport Mode 2", "") } + assertThat(transportMode.values).usingRecursiveFieldByFieldElementComparator().containsExactly( + CnfFilterItem("Transport Mode 1", "Transport Mode 1"), + CnfFilterItem("Transport Mode 2", "Transport Mode 2"), + CnfFilterItem("", "") + ) val geoReferenceMethod = getAvailableFilter(result, "geoReferenceMethod") - assertThat(geoReferenceMethod.values).allSatisfy { it.id in setOf("Geo Reference Method 1", "") } - - val curatorOrganizationName = getAvailableFilter(result, "organizationName") - assertThat(curatorOrganizationName.values).allSatisfy { it.id in setOf("Organization 0") } - - val curatorOrganizationId = getAvailableFilter(result, "organizationId") - assertThat(curatorOrganizationId.values).allSatisfy { it.id in setOf(dummyDevOrganizationId(0)) } - - val connectorId = getAvailableFilter(result, "connectorId") - assertThat(connectorId.values).allSatisfy { it.id in setOf(dummyDevConnectorId(0, 0)) } + assertThat(geoReferenceMethod.values).usingRecursiveFieldByFieldElementComparator().containsExactly( + CnfFilterItem("Geo Reference Method 1", "Geo Reference Method 1"), + CnfFilterItem("", "") + ) - val connectorEndpoint = getAvailableFilter(result, "connectorEndpoint") - assertThat(connectorEndpoint.values).allSatisfy { it.id in setOf("https://connector-0/dsp") } + val organization = getAvailableFilter(result, "organization") + assertThat(organization.values).usingRecursiveFieldByFieldElementComparator().containsExactly( + CnfFilterItem(dummyDevOrganizationId(0), "Organization 0"), + ) + val connector = getAvailableFilter(result, "connectorId") + assertThat(connector.values).usingRecursiveFieldByFieldElementComparator().containsExactly( + CnfFilterItem(dummyDevConnectorId(0, 0), "Connector - Organization 0"), + ) } /** @@ -446,13 +374,6 @@ class CatalogApiTest { ) ) - val uiAsset = UiAsset().also { - it.assetId = dummyDevAssetId(0) - it.title = "Hello" - it.description = "Data Offer 0 Description" - it.dataSourceAvailability = DataSourceAvailability.LIVE - } - ScenarioData().apply { organization(0, 0) user(0, 0) @@ -462,10 +383,11 @@ class CatalogApiTest { it.endpointUrl = "https://connector-0/dsp" } - dataOffer(0, 0, 0) { - it.assetTitle = "Hello" - it.uiAssetJson = JSONB.valueOf(objectMapper.writeValueAsString(uiAsset)) - } + dataOffer(0, 0, 0, assetApplier = { + it.title = "Hello" + it.description = "Data Offer 0 Description" + it.dataSourceAvailability = DataSourceAvailability.LIVE + }) scenarioInstaller.install(this) } @@ -503,20 +425,6 @@ class CatalogApiTest { ) ) - val uiAsset1 = UiAsset().also { - it.assetId = dummyDevAssetId(0) - it.title = "Data Offer 0" - it.description = "Data Offer 0 Description" - it.dataCategory = "Data Category 0" - it.dataSubcategory = "Data Subcategory 0" - } - val uiAsset2 = UiAsset().also { - it.assetId = dummyDevAssetId(1) - it.title = "Data Offer 1" - it.description = "Data Offer 1 Description" - it.dataSubcategory = "Data Subcategory 1" - } - ScenarioData().apply { organization(0, 0) user(0, 0) @@ -526,14 +434,17 @@ class CatalogApiTest { it.endpointUrl = "https://connector-0/dsp" } - dataOffer(0, 0, 0) { - it.assetTitle = "Data Offer 0" - it.uiAssetJson = JSONB.valueOf(objectMapper.writeValueAsString(uiAsset1)) - } - dataOffer(0, 0, 1) { - it.assetTitle = "Data Offer 1" - it.uiAssetJson = JSONB.valueOf(objectMapper.writeValueAsString(uiAsset2)) - } + dataOffer(0, 0, 0, assetApplier = { + it.title = "Data Offer 0" + it.description = "Data Offer 0 Description" + it.dataCategory = "Data Category 0" + it.dataSubcategory = "Data Subcategory 0" + }) + dataOffer(0, 0, 1, assetApplier = { + it.title = "Data Offer 1" + it.description = "Data Offer 1 Description" + it.dataSubcategory = "Data Subcategory 1" + }) scenarioInstaller.install(this) } @@ -584,8 +495,8 @@ class CatalogApiTest { user(0, 0) connector(0, 0, 0) - repeat(15) { index -> dataOffer(0, 0, index) { it.assetId = "my-asset-$index" } } - repeat(15) { index -> dataOffer(0, 0, index) { it.assetId = "other-asset-$index" } } + repeat(15) { index -> dataOffer(0, 0, index, assetApplier = { it.assetId = "my-asset-$index" }) } + repeat(15) { index -> dataOffer(0, 0, index, assetApplier = { it.assetId = "other-asset-$index" }) } scenarioInstaller.install(this) } @@ -633,8 +544,8 @@ class CatalogApiTest { user(0, 0) connector(0, 0, 0) - repeat(15) { index -> dataOffer(0, 0, index) { it.assetId = "my-asset-$index" } } - repeat(15) { index -> dataOffer(0, 0, index) { it.assetId = "other-asset-$index" } } + repeat(15) { index -> dataOffer(0, 0, index, assetApplier = { it.assetId = "my-asset-$index" }) } + repeat(15) { index -> dataOffer(0, 0, index, assetApplier = { it.assetId = "other-asset-$index" }) } scenarioInstaller.install(this) } @@ -720,52 +631,6 @@ class CatalogApiTest { return catalogResource.dataOfferDetailPage("test", query) } - @Test - @TestTransaction - fun `test filter by org name`() { - // arrange - useDevUser(0, 0) - - whenever(catalogDataspaceConfigService.forEnvironment(any())).thenReturn( - CatalogDataspaceConfig( - namesByConnectorId = emptyMap(), - defaultName = "MDS" - ) - ) - - ScenarioData().apply { - organization(0, 0) - user(0, 0) - connector(0, 0, 0) - dataOffer(0, 0, 0) - - organization(1, 1) - user(1, 1) - connector(1, 1, 1) - dataOffer(1, 1, 1) - - scenarioInstaller.install(this) - } - - // act - val result = catalogResource.catalogPage( - environmentId = "test", - query = CatalogPageQuery( - filter = CnfFilterValue( - listOf( - CnfFilterValueAttribute("organizationName", listOf("Organization 0")), - ) - ), - searchQuery = null, - sorting = null - ) - ) - - // assert - assertThat(result.dataOffers).hasSize(1) - assertThat(result.dataOffers.first().connectorId).isEqualTo(dummyDevConnectorId(0, 0)) - } - @Test @TestTransaction fun `test search for org name`() { @@ -812,7 +677,7 @@ class CatalogApiTest { @Test @TestTransaction - fun `test filter by organizationId`() { + fun `test filter by organization`() { // arrange useDevUser(0, 0) @@ -843,7 +708,7 @@ class CatalogApiTest { query = CatalogPageQuery( filter = CnfFilterValue( listOf( - CnfFilterValueAttribute("organizationId", listOf(dummyDevOrganizationId(0))), + CnfFilterValueAttribute("organization", listOf(dummyDevOrganizationId(0))), ) ), searchQuery = null, diff --git a/authority-portal-backend/authority-portal-quarkus/src/test/kotlin/de/sovity/authorityportal/web/tests/services/catalog/DataOfferDetailApiTest.kt b/authority-portal-backend/authority-portal-quarkus/src/test/kotlin/de/sovity/authorityportal/web/tests/services/catalog/DataOfferDetailApiTest.kt index dca6ec5e9..aafc0ca18 100644 --- a/authority-portal-backend/authority-portal-quarkus/src/test/kotlin/de/sovity/authorityportal/web/tests/services/catalog/DataOfferDetailApiTest.kt +++ b/authority-portal-backend/authority-portal-quarkus/src/test/kotlin/de/sovity/authorityportal/web/tests/services/catalog/DataOfferDetailApiTest.kt @@ -66,20 +66,6 @@ class DataOfferDetailApiTest { // arrange useDevUser(0, 0) - val uiAsset1 = UiAsset().also { - it.assetId = dummyDevAssetId(0) - it.title = "Data Offer 0" - it.dataCategory = "Data Category 0" - it.description = "Data Offer 0 Description" - } - - val uiAsset2 = UiAsset().also { - it.assetId = dummyDevAssetId(1) - it.title = "Data Offer 1" - it.dataCategory = "Data Category 1" - it.description = "Data Offer 1 Description" - } - ScenarioData().apply { organization(0, 0) user(0, 0) @@ -87,15 +73,21 @@ class DataOfferDetailApiTest { connector(0, 0, 0) { it.endpointUrl = "https://connector/dsp" } - dataOffer(0, 0, 0) { - it.uiAssetJson = JSONB.valueOf(objectMapper.writeValueAsString(uiAsset1)) - } + dataOffer(0, 0, 0, assetApplier = { + it.assetId = dummyDevAssetId(0) + it.title = "Data Offer 0" + it.dataCategory = "Data Category 0" + it.description = "Data Offer 0 Description" + }) contractOffer(0, 0, 0, 0) connector(1, 0, 0) - dataOffer(1, 0, 1) { - it.uiAssetJson = JSONB.valueOf(objectMapper.writeValueAsString(uiAsset2)) - } + dataOffer(1, 0, 1, assetApplier = { + it.assetId = dummyDevAssetId(1) + it.title = "Data Offer 1" + it.dataCategory = "Data Category 1" + it.description = "Data Offer 1 Description" + }) contractOffer(1, 0, 1, 1) scenarioInstaller.install(this) diff --git a/authority-portal-backend/authority-portal-quarkus/src/test/kotlin/de/sovity/authorityportal/web/tests/services/organization/OrganizationInfoApiServiceTest.kt b/authority-portal-backend/authority-portal-quarkus/src/test/kotlin/de/sovity/authorityportal/web/tests/services/organization/OrganizationInfoApiServiceTest.kt index fae4ae203..28308b872 100644 --- a/authority-portal-backend/authority-portal-quarkus/src/test/kotlin/de/sovity/authorityportal/web/tests/services/organization/OrganizationInfoApiServiceTest.kt +++ b/authority-portal-backend/authority-portal-quarkus/src/test/kotlin/de/sovity/authorityportal/web/tests/services/organization/OrganizationInfoApiServiceTest.kt @@ -267,18 +267,15 @@ class OrganizationInfoApiServiceTest { user(0, 0) connector(0, 0, 0) - val uiAsset1 = UiAsset().also { + // On request + dataOffer(0, 0, 0, 0, assetApplier = { it.assetId = dummyDevAssetId(0) it.title = "Data Offer 0" it.dataCategory = "Data Category 0" it.description = "Data Offer 0 Description" it.dataSourceAvailability = DataSourceAvailability.ON_REQUEST - } + }) - // On request - dataOffer(0, 0, 0, 0) { - it.uiAssetJson = JSONB.valueOf(objectMapper.writeValueAsString(uiAsset1)) - } // Live offer dataOffer(0, 0, 1, 0) @@ -311,18 +308,15 @@ class OrganizationInfoApiServiceTest { user(1, 1) connector(1, 1, 1) - val uiAsset1 = UiAsset().also { + // On request + dataOffer(0, 0, 0, 0, assetApplier = { it.assetId = dummyDevAssetId(0) it.title = "Data Offer 0" it.dataCategory = "Data Category 0" it.description = "Data Offer 0 Description" it.dataSourceAvailability = DataSourceAvailability.ON_REQUEST - } + }) - // On request - dataOffer(0, 0, 0, 0) { - it.uiAssetJson = JSONB.valueOf(objectMapper.writeValueAsString(uiAsset1)) - } // Live offer dataOffer(0, 0, 1, 0) dataOffer(1, 1, 2, 0) diff --git a/authority-portal-frontend/src/app/core/api/fake-backend/impl/catalog-fake-impl.ts b/authority-portal-frontend/src/app/core/api/fake-backend/impl/catalog-fake-impl.ts index 965a67347..5c0043c13 100644 --- a/authority-portal-frontend/src/app/core/api/fake-backend/impl/catalog-fake-impl.ts +++ b/authority-portal-frontend/src/app/core/api/fake-backend/impl/catalog-fake-impl.ts @@ -145,6 +145,7 @@ export const getCatalogPage = ( { id: 'example-filter', title: 'Example Filter', + displayType: 'TITLE_ONLY', values: [ {id: 'example-value', title: 'Example Value'}, {id: 'other-value', title: 'Other Value'}, @@ -154,6 +155,7 @@ export const getCatalogPage = ( { id: 'other-filter', title: 'Other Filter', + displayType: 'TITLE_ONLY', values: [ {id: 'example-value', title: 'Example Value'}, {id: 'other-value', title: 'Other Value'}, @@ -161,16 +163,21 @@ export const getCatalogPage = ( ], }, { - id: 'organizationId', - title: 'Organization ID', + id: 'organization', + title: 'Organization', + displayType: 'ID_AND_TITLE', values: [ { id: 'MDSL1111AA', - title: 'MDSL1111AA', + title: 'Example Organization', }, { id: 'MDSL2222BB', - title: 'MDSL2222BB', + title: 'Other Organization', + }, + { + id: '', + title: '', }, ], }, diff --git a/authority-portal-frontend/src/app/pages/catalog-page/catalog-page/state/catalog-page-state.ts b/authority-portal-frontend/src/app/pages/catalog-page/catalog-page/state/catalog-page-state.ts index 242608666..d72ec5270 100644 --- a/authority-portal-frontend/src/app/pages/catalog-page/catalog-page/state/catalog-page-state.ts +++ b/authority-portal-frontend/src/app/pages/catalog-page/catalog-page/state/catalog-page-state.ts @@ -187,11 +187,12 @@ export class CatalogPageState implements OnDestroy { label: x, })); return { - id: 'organizationId', - title: 'Organization ID', + id: 'organization', + title: 'Organization', selectedItems: items, availableItems: items, searchText: '', + displayType: 'TITLE_ONLY', }; } @@ -275,7 +276,7 @@ export class CatalogPageState implements OnDestroy { state.filters, ); - return { + state = { ...state, isPageReady: true, paginationDisabled: false, @@ -284,6 +285,8 @@ export class CatalogPageState implements OnDestroy { activeSorting: state.activeSorting ?? data.availableSortings[0] ?? null, filters, }; + + return this._recalculateActiveFilterItems(state); } private buildFiltersWithNewData( diff --git a/authority-portal-frontend/src/app/pages/catalog-page/filter-box/filter-box-model.ts b/authority-portal-frontend/src/app/pages/catalog-page/filter-box/filter-box-model.ts index a880ba987..4fa227a89 100644 --- a/authority-portal-frontend/src/app/pages/catalog-page/filter-box/filter-box-model.ts +++ b/authority-portal-frontend/src/app/pages/catalog-page/filter-box/filter-box-model.ts @@ -10,7 +10,10 @@ * Contributors: * sovity GmbH - initial implementation */ -import {CnfFilterAttribute} from '@sovity.de/authority-portal-client'; +import { + CnfFilterAttribute, + CnfFilterAttributeDisplayType, +} from '@sovity.de/authority-portal-client'; import {FilterBoxItem, buildFilterBoxItems} from './filter-box-item'; /** @@ -22,6 +25,7 @@ export interface FilterBoxModel { selectedItems: FilterBoxItem[]; availableItems: FilterBoxItem[]; searchText: string; + displayType: CnfFilterAttributeDisplayType; } export function buildFilterBoxModelWithNewData( @@ -34,6 +38,27 @@ export function buildFilterBoxModelWithNewData( title: fetched.title, availableItems, searchText: old?.searchText ?? '', - selectedItems: old?.selectedItems ?? [], + selectedItems: withUpdatedTitles(old?.selectedItems ?? [], availableItems), + displayType: fetched.displayType, }; } + +function withUpdatedTitles( + selectedItems: FilterBoxItem[], + availableItems: FilterBoxItem[], +): FilterBoxItem[] { + const fetchedById = new Map( + availableItems.map((item) => [item.id, item]), + ); + + const isSame = (a: FilterBoxItem, b: FilterBoxItem) => a.label === b.label; + + return selectedItems.map((oldItem) => { + const newItem = fetchedById.get(oldItem.id); + if (newItem == null || isSame(newItem, oldItem)) { + return oldItem; + } + + return newItem; + }); +} diff --git a/authority-portal-frontend/src/app/pages/catalog-page/filter-box/filter-box.component.html b/authority-portal-frontend/src/app/pages/catalog-page/filter-box/filter-box.component.html index 15d079f76..b4dc1462d 100644 --- a/authority-portal-frontend/src/app/pages/catalog-page/filter-box/filter-box.component.html +++ b/authority-portal-frontend/src/app/pages/catalog-page/filter-box/filter-box.component.html @@ -11,7 +11,13 @@ [class.italic]="item.type === 'NO_VALUE'" [value]="item" [attr.title]="item.label"> - {{ item.label }} +
{{ item.label }}
+
+ {{ item.id }} +