Skip to content

feat(api)!: update entity output for having relations grouped#90

Open
brandPittCode wants to merge 37 commits into
mainfrom
feat/update-entity-output-for-having-relations-grouped
Open

feat(api)!: update entity output for having relations grouped#90
brandPittCode wants to merge 37 commits into
mainfrom
feat/update-entity-output-for-having-relations-grouped

Conversation

@brandPittCode

@brandPittCode brandPittCode commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

PR Description

What this PR Provides

This Pull request add the capacity for retrieving the relations in a given depth for the entities/template_identifier/identifier endpoint.

It also update the EntityDtout contract to have all the relations grouped in the relations object and dropping the relations_as_taget section.

This pull request introduces several improvements and refactorings to the entity relationship graph functionality, enhances API documentation, and clarifies the structure of relationship data in the documentation. The main focus is on supporting batch graph traversal, improving inbound relation summaries, and simplifying the graph-building logic by delegating it to a new helper class. There are also updates to the documentation to clarify the use and structure of relations in API requests and responses.

Backend Logic Refactoring:

  • The API mapping layer (EntityDtoOutMapper) is refactored to build a unified relations map, merging outbound and inbound relations under the same key and including template identifiers in related entity summaries. Obsolete mapping methods are removed

Entity Graph Traversal and Service Refactoring:

  • The EntityGraphService now supports batch graph traversal for multiple root entities, delegating recursive graph construction to a new EntityGraphHelper class, which improves modularity and performance. The service exposes a new paginated API to fetch entity graphs with both inbound and outbound relations resolved in a single transaction.
  • The repository interface method findEntityGraph is changed to accept a collection of root entity UUIDs instead of a single UUID, enabling bulk graph resolution.
  • Introduced a new internal enum FlowDirection to represent graph traversal directions, supporting more flexible and explicit traversal logic.

Documentation and API Description Improvements:

  • Documentation for entity relations is reorganized and clarified: the structure of relations in API requests and responses is more clearly explained, and the meaning of reverse relation filters is made more precise.

  • The OpenAPI/Swagger description for the "get entity by identifier" endpoint is updated to explain support for configurable relationship graph traversal, including inbound and outbound relations and direct lineage mode.

Inbound Relation Summaries:

  • The RelationAsTargetSummary record now includes the sourceTemplateIdentifier field, making inbound relation summaries template-aware for more informative and unified API responses.

Other Technical Changes:

  • Minor import cleanups and code documentation improvements for clarity and maintainability.

These changes collectively make the entity relationship graph APIs more powerful, consistent, and easier to use, while improving documentation for both developers and API consumers.

Test and Documentation Updates:

  • Integration test fixtures are updated to reflect the new unified relations structure and the inclusion of template identifiers.

These changes provide a more consistent and informative API for consumers, simplify relation handling, and enhance frontend capabilities.

Review

The reviewer must double-check these points:

  • The reviewer has tested the feature
  • The reviewer has reviewed the implementation of the feature
  • The documentation has been updated
  • The feature implementation respects the Technical Doc / ADR previously produced
  • The Pull Request title has a ! after the type/scope to identify the breaking
    change in the release note and ensure we will release a major version.

How to test

Please refer (copy/paste) the test section from the User Story. This should include

  • The initial state: Have an initial test set with entities that have outboud and inbound relations.
  • What and how to test: Make call to the /entities/template_identifier, /entities/template_identifier/entity_identifier and /entities/search endpoints.
  • Expected results: When calling any of the enpoints that output entities information in a EntityDtout contract, the relations (inbound and outbound) should be now grouped in the relations section. The relations_as_target field shoud not exists.

Breaking changes (if any)

  • API JSON schema modification (existing resource / behavior)

  • N/A

Context of the Breaking Change

The EntityDtoOut contract has change.

Result of the Breaking Change

The relation_as_target fiel have been removed.
The relations (inbound and outbound) are now grouped in the relations section.
The relation summary have now the template identifer information

@github-code-quality

github-code-quality Bot commented Jul 8, 2026

Copy link
Copy Markdown

Code Coverage Overview

Languages: Java

Java / code-coverage/jacoco

The overall coverage in commit 492a130 in the feat/update-entity-o... branch remains at 90%, unchanged from commit fb2197a in the main branch.

Show a code coverage summary of the most impacted files.
File main fb2197a feat/update-entity-o... 492a130 +/-
com/decathlon/i...aphAdapter.java 82% 73% -9%
com/decathlon/i...aphService.java 92% 88% -4%
com/decathlon/i...ityService.java 98% 98% 0%
com/decathlon/i...mpositeKey.java 0% 36% +36%
com/decathlon/i...eConverter.java 0% 73% +73%
com/decathlon/i...NodeMapper.java 0% 92% +92%
com/decathlon/i...raphHelper.java 0% 95% +95%
com/decathlon/i...wDirection.java 0% 100% +100%
com/decathlon/i...tityDtoOut.java 0% 100% +100%
com/decathlon/i...SummaryDto.java 0% 100% +100%

Updated July 24, 2026 08:59 UTC

@brandPittCode brandPittCode changed the title Feat(entity): update entity output for having relations grouped feat(entity): update entity output for having relations grouped Jul 8, 2026
@brandPittCode brandPittCode changed the title feat(entity): update entity output for having relations grouped feat(api): update entity output for having relations grouped Jul 8, 2026
@brandPittCode
brandPittCode requested a review from Copilot July 8, 2026 12:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the Entities API response contract to provide a unified relations structure that combines outbound and inbound relations, and enriches related entity summaries with template_identifier so clients can identify related entity types without additional lookups.

Changes:

  • Reworked entity output DTOs (EntityDtoOut, EntitySummaryDto) to support unified relations and include template_identifier in relation summaries.
  • Updated relation-summary persistence query and mapping to provide inbound relation source template identifiers.
  • Updated OpenAPI (swagger.yaml) and integration-test JSON fixtures to reflect the new response schema (including removal of relations_as_target from responses).

Reviewed changes

Copilot reviewed 14 out of 16 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/mapper/entity/EntityDtoOutMapper.java Builds unified relations map and injects template identifiers into relation summaries
src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/dto/out/entity/EntityDtoOut.java Refactors entity response DTO to a unified relations-only shape
src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/dto/out/entity/EntitySummaryDto.java Adds templateIdentifier to related entity summaries and converts to record
src/main/java/com/decathlon/idp_core/infrastructure/adapters/persistence/repository/JpaRelationRepository.java Extends native query projection to include source template identifier for inbound relation summaries
src/main/java/com/decathlon/idp_core/domain/model/entity/RelationAsTargetSummary.java Extends domain summary model to carry sourceTemplateIdentifier
docs/src/static/swagger.yaml Updates OpenAPI schemas to remove relations_as_target from responses and add template_identifier to entity summaries
src/test/resources/integration_test/json/entity/v1/searchEntities_200_startsWith.json Updates expected entity search response fixture for unified relations + template identifiers
src/test/resources/integration_test/json/entity/v1/searchEntities_200_orTemplates.json Updates expected entity search response fixture for unified relations + template identifiers
src/test/resources/integration_test/json/entity/v1/searchEntities_200_neq.json Updates expected entity search response fixture for unified relations
src/test/resources/integration_test/json/entity/v1/searchEntities_200_byTemplateAndProperty.json Updates expected entity search response fixture for unified relations + template identifiers
src/test/resources/integration_test/json/entity/v1/searchEntities_200_byRelationsAsTargetPresence.json Updates expected entity search response fixture reflecting inbound relations now in relations
src/test/resources/integration_test/json/entity/v1/searchEntities_200_byRelationsAsTarget.json Updates expected entity search response fixture reflecting inbound relations now in relations
src/test/resources/integration_test/json/entity/v1/searchEntities_200_byRelationNameEq.json Updates expected entity search response fixture for unified relations + template identifiers
src/test/resources/integration_test/json/entity/v1/searchEntities_200_byRelationNameContains.json Updates expected entity search response fixture for unified relations + template identifiers
src/test/resources/integration_test/json/entity/v1/getEntities_200_relationsAsTargetIdentifier.json Updates expected get-entities response fixture reflecting inbound relations now in relations
src/test/resources/integration_test/json/entity/v1/getEntities_200_identifierEquals.json Updates expected get-entities response fixture for unified relations + template identifiers

@evebrnd evebrnd left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are many mentions of relations_as_target in tests and the documentation that should be cleaned / updated.
PR title is missing a '!' to declare the breaking change

@brandPittCode brandPittCode changed the title feat(api): update entity output for having relations grouped feat(api)!: update entity output for having relations grouped Jul 16, 2026
@brandPittCode
brandPittCode requested a review from Copilot July 16, 2026 12:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 25 out of 28 changed files in this pull request and generated 10 comments.

Comments suppressed due to low confidence (1)

docs/src/concepts/entities.md:247

  • The response example under "Relations in Responses" is now missing template_identifier in relation summaries, which the PR introduces as part of the unified relations API. This makes the docs inconsistent with the updated contract.
In API responses, relations are grouped by name and include summary information about each target entity:

```json
{
  "relations": {

Comment on lines +166 to +172
// Load entity graphs in batch
Map<UUID, Entity> entityGraphs = entityGraphRepositoryPort.findEntityGraph(entityUuids, 1, true,
EntityGraphTraversalMode.DIRECT_LINEAGE);

private static record GraphTraversalContext(Map<UUID, Entity> entityMap,
Map<EntityCompositeKey, UUID> textToUuidLookup,
Map<String, Map<String, List<UUID>>> inboundIndex, boolean includeProperties,
Set<String> propertyFilter, Set<String> relationFilter, Set<String> activeStack,
Map<String, EntityGraphNode> memoCache, // High-speed in-memory reuse cache
EntityGraphTraversalMode mode) {
}
// Call the helper to build graph nodes
Map<UUID, EntityGraphNode> graphsByUuid = entityGraphHelper.buildGraphNodesForEntityIds(
entityGraphs, true, Set.of(), Set.of(), EntityGraphTraversalMode.DIRECT_LINEAGE, 1);
brandPittCode and others added 16 commits July 17, 2026 12:25
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Andrés Brand <andresbrand@gmail.com>
Unify graph retrieving methods
Unify graph retrieving methods
Unify graph retrieving methods
Unify graph retrieving methods
Unify graph retrieving methods

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Andrés Brand <andresbrand@gmail.com>
Unify graph retrieving methods

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Andrés Brand <andresbrand@gmail.com>
Unify graph retrieving methods

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 31 out of 34 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (1)

src/main/java/com/decathlon/idp_core/domain/service/entity_graph/EntityGraphService.java:15

  • Unused import EntityTemplateNotFoundException will fail compilation (Javadoc references don’t count as usage). Remove it, or reference it in code if you intend to throw it explicitly from this class.
import com.decathlon.idp_core.domain.exception.entity_template.EntityTemplateNotFoundException;

Comment on lines +9 to +10
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We will migrate this for all service that use this Spring Data (Page/Pageable)

Comment thread src/main/resources/application-local.yml
Comment thread docs/src/concepts/entities.md
Copilot AI review requested due to automatic review settings July 22, 2026 16:07

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 31 out of 34 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (3)

src/main/java/com/decathlon/idp_core/domain/service/entity_graph/EntityGraphHelper.java:60

  • buildGraphNodesForEntityIds computes a reachable footprint + localized indices for every entry in entityGraphs (which includes neighbors, not just requested roots). For the paginated list path, this can multiply work significantly (O(N) BFS/DFS builds) even though only the page roots are actually returned.
    IndexBundle globalIndices = buildIndices(entityGraphs, mode);
    Map<UUID, EntityGraphNode> result = new HashMap<>();

    for (Map.Entry<UUID, Entity> entry : entityGraphs.entrySet()) {
      Entity entity = entry.getValue();
      if (entity != null) {

        Set<UUID> reachableFootprint = computeReachableSubGraph(entry.getKey(), entityGraphs,
            globalIndices, depth, mode);

src/main/resources/application-local.yml:39

  • This PR comments out app.security.mock-enabled in the local profile, which changes local runtime behavior (likely re-enabling real OAuth2). This operational/config change isn’t described in the PR description and may break existing local workflows.
app:
  # security:
  #   mock-enabled: true
  full-refresh-at-startup: true

docs/src/concepts/entities.md:57

  • The JSON example is malformed (missing closing braces/brackets for the relation item and array), which will confuse readers and breaks copy/paste.
  "relations": {
    "depends-on": [
      {
        "identifier": "web-api-1",
        "name": "Web API 1",
        "template_identifier": "web-service"
    ]
  }

Comment on lines +264 to +269
private Map<String, EntitySummaryDto> buildEntitiesSummariesMap(List<String> targetIdentifiers) {
return targetIdentifiers.isEmpty()
? Collections.emptyMap()
: entityService.getEntitiesSummariesByIdentifiers(targetIdentifiers).stream()
.collect(Collectors.toMap(EntitySummary::identifier,
es -> new EntitySummaryDto(es.identifier(), es.name())));
es -> new EntitySummaryDto(es.identifier(), es.name(), es.templateIdentifier())));
Comment on lines +82 to +85

var state = new TraversalState(root.identifier(), new HashMap<>(), new HashMap<>(),
new HashMap<>(), new HashSet<>());

Comment on lines +167 to 175
// Load entity graphs in batch (includes roots + neighbors for relation
// resolution)
Map<UUID, Entity> entityGraphs = entityGraphRepositoryPort.findEntityGraph(entityUuids, depth,
true, EntityGraphTraversalMode.DIRECT_LINEAGE);

private static record GraphTraversalContext(Map<UUID, Entity> entityMap,
Map<EntityCompositeKey, UUID> textToUuidLookup,
Map<String, Map<String, List<UUID>>> inboundIndex, boolean includeProperties,
Set<String> propertyFilter, Set<String> relationFilter, Set<String> activeStack,
Map<String, EntityGraphNode> memoCache, // High-speed in-memory reuse cache
EntityGraphTraversalMode mode) {
}
// Call the helper to build graph nodes
Map<UUID, EntityGraphNode> graphsByUuid = entityGraphHelper.buildGraphNodesForEntityIds(
entityGraphs, true, Set.of(), Set.of(), EntityGraphTraversalMode.DIRECT_LINEAGE, depth);

/// callers that already own a transaction can reuse the logic directly without
/// triggering a new transaction via the Spring proxy.
///
/// @param entityIds UUIDs of the root entities to build graphs for

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/// @param entityGraphs map of entity UUIDs to their corresponding Entity objects

Copilot AI review requested due to automatic review settings July 23, 2026 08:32

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 31 out of 34 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (3)

src/main/java/com/decathlon/idp_core/domain/service/entity_graph/EntityGraphHelper.java:58

  • buildGraphNodesForEntityIds(...) iterates over every entry in entityGraphs and builds a full graph for each UUID (computeReachableSubGraph(...) + buildGraphNode(...)). But entityGraphs comes from findEntityGraph(...) and includes roots and all discovered neighbors, so for typical calls you only need graphs for the root UUID(s), not for every discovered node. This can turn a single graph build into O(N) graph builds and become very expensive at higher depths / larger graphs. Consider changing the API to accept an explicit rootIds collection and only build graphs for those roots (neighbors remain available in the indexed map).
    for (Map.Entry<UUID, Entity> entry : entityGraphs.entrySet()) {
      Entity entity = entry.getValue();
      if (entity != null) {

        Set<UUID> reachableFootprint = computeReachableSubGraph(entry.getKey(), entityGraphs,

src/main/java/com/decathlon/idp_core/domain/service/entity_graph/EntityGraphService.java:12

  • The Domain layer now depends on Spring Data types (org.springframework.data.domain.Page / Pageable). Per the project’s Domain-layer constraints, Domain code should not depend on Spring Data (keep pagination/transport concerns in Infrastructure/Application). Consider moving getEntityGraphPageByTemplate(...) out of the Domain service (or exposing a port that uses domain pagination models like PaginatedResult / PaginationCriteria) so the Domain remains framework-agnostic.
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

src/main/resources/application-local.yml:38

  • application-local.yml no longer enables app.security.mock-enabled: true. Because SecurityConfiguration uses matchIfMissing=true for the real JWT resource server, commenting this out makes the local profile require a real JWKS/JWT setup, which is a significant local-dev regression unless intentionally changed. If the goal is to keep local development working out-of-the-box, restore the mock security flag (or document the new local requirements).
app:
  # security:
  #   mock-enabled: true

Comment on lines +326 to +330
schema:
type: array
example:
- depends-on
- relates-to

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolved

Comment thread docs/src/concepts/entities.md Outdated
Copilot AI review requested due to automatic review settings July 23, 2026 15:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 37 out of 40 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (6)

src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/mapper/entity/EntityDtoOutFromEntityNodeMapper.java:85

  • TraversalState stores rootIdentifier as the root’s identifier only. Since identifiers are not globally unique across templates (this PR introduces composite keys for that), the traversal can incorrectly treat a different-template node with the same identifier as the root and drop it from the output.
    var state = new TraversalState(root.identifier(), new HashMap<>(), new HashMap<>(),
        new HashMap<>(), new HashSet<>());

src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/mapper/entity/EntityDtoOutFromEntityNodeMapper.java:162

  • Root-exclusion logic compares only identifier against the stored root value. With composite IDs, this should compare the full templateIdentifier:identifier to avoid excluding unrelated entities that share an identifier across templates.
        String targetId = nodeId(target.templateIdentifier(), target.identifier());
        String signature = nodeId + "|" + targetId + "|" + relation.name();

        boolean isNotRoot = !target.identifier().equalsIgnoreCase(state.rootIdentifier());

        if (shouldCollectRelations && isNotRoot && state.emittedEdgeSignatures().add(signature)) {
          state.relationsMap().computeIfAbsent(relation.name(), k -> new LinkedHashSet<>())
              .add(new EntitySummaryDto(target.identifier(), target.name(),
                  target.templateIdentifier()));

src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/mapper/entity/EntityDtoOutFromEntityNodeMapper.java:206

  • Inbound root-exclusion logic should also compare composite IDs (template + identifier). Otherwise a source entity from another template that happens to share the root identifier will be dropped incorrectly.
    for (EntityGraphRelation relation : node.relationsAsTarget()) {
      for (EntityGraphNode source : relation.targets()) {
        String sourceId = nodeId(source.templateIdentifier(), source.identifier());
        String signature = sourceId + "|" + nodeId + "|" + relation.name();

        boolean isNotRoot = !source.identifier().equalsIgnoreCase(state.rootIdentifier());

        if (shouldCollectRelations && isNotRoot && state.emittedEdgeSignatures().add(signature)) {
          state.relationsMap().computeIfAbsent(relation.name(), k -> new LinkedHashSet<>())
              .add(new EntitySummaryDto(source.identifier(), source.name(),
                  source.templateIdentifier()));
        }

src/main/java/com/decathlon/idp_core/domain/service/entity_graph/EntityGraphHelper.java:77

  • buildGraphNodesForEntityIds currently iterates over every entry in entityGraphs and builds a full reachable footprint + localized indices for each one. Since entityGraphs contains the root(s) and all discovered neighbor nodes, this can turn a single-root graph build into O(N²) work and make paginated list endpoints unexpectedly expensive.
    IndexBundle globalIndices = buildIndices(entityGraphs, mode);
    Map<UUID, EntityGraphNode> result = new HashMap<>();

    for (Map.Entry<UUID, Entity> entry : entityGraphs.entrySet()) {
      Entity entity = entry.getValue();
      if (entity != null) {

        Set<UUID> reachableFootprint = computeReachableSubGraph(entry.getKey(), entityGraphs,
            globalIndices, depth, mode);

        Map<UUID, Entity> localizedEntityMap = entityGraphs.entrySet().stream()
            .filter(e -> reachableFootprint.contains(e.getKey()))
            .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

        IndexBundle localizedIndices = buildIndices(localizedEntityMap, mode);

        Set<UUID> isolatedStack = new HashSet<>();
        Map<UUID, EntityGraphNode> isolatedCache = new HashMap<>();

        GraphTraversalContext localizedCtx = new GraphTraversalContext(localizedEntityMap,
            localizedIndices.textToUuidLookup(), localizedIndices.inboundIndex(), includeProperties,
            propertyFilter, relationFilter, isolatedStack, isolatedCache, mode);

        EntityGraphNode node = buildGraphNode(entry.getKey(), localizedCtx);
        result.put(entry.getKey(), node);
      }

src/main/resources/application-local.yml:38

  • Removing app.security.mock-enabled: true changes the local profile’s security behavior: because SecurityConfiguration uses matchIfMissing = true, local will now require real JWT authentication unless the property is explicitly set elsewhere. If this is intentional, consider setting app.security.mock-enabled: false explicitly (or documenting the new local setup) to avoid surprises for developers.
app:
  full-refresh-at-startup: true
  idp-core-prefix-url: http://localhost:8084

src/main/java/com/decathlon/idp_core/infrastructure/adapters/persistence/repository/JpaRelationRepository.java:47

  • Inbound relation summaries are fetched by filtering only on rte.target_entity_identifier. Since entity identity is (template_identifier, identifier) and this PR explicitly adds composite-key handling elsewhere, this query can conflate inbound relations for two different entities that share the same identifier across templates.
  @Query(value = """
      SELECT
          rte.target_entity_identifier AS targetEntityIdentifier,
          r.name AS relationName,
          e.identifier AS sourceEntityIdentifier,
          e.name AS sourceEntityName,
          e.template_identifier AS sourceTemplateIdentifier
      FROM idp_core.entity e
      JOIN idp_core.entity_relations er ON er.entity_id = e.id
      JOIN idp_core.relation r ON r.id = er.relation_id
      JOIN idp_core.relation_target_entities rte ON rte.relation_id = r.id
      WHERE rte.target_entity_identifier IN :targetEntityIdentifiers
      """, nativeQuery = true)
  List<RelationAsTargetSummary> findRelationsSummariesByTargetEntityIdentifiers(
      @Param("targetEntityIdentifiers") List<String> targetEntityIdentifiers);

Copilot AI review requested due to automatic review settings July 23, 2026 16:40

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 38 out of 41 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (8)

src/main/resources/application-local.yml:38

  • app.security.mock-enabled is no longer set for the local profile. Because SecurityConfiguration is enabled when app.security.mock-enabled=false or missing (matchIfMissing=true), this changes local runs to require real JWT auth instead of using MockSecurityConfiguration (which is explicitly documented as the local dev setup). If the intent is to keep mock auth for local dev, re-enable it in application-local.yml (or update local run docs accordingly).
app:
  full-refresh-at-startup: true
  idp-core-prefix-url: http://localhost:8084

src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/mapper/entity/EntityDtoOutFromEntityNodeMapper.java:84

  • TraversalState stores rootIdentifier as only the entity identifier. If different templates can share the same identifier, nodes from other templates with the same identifier will be treated as the root and wrongly excluded from collected relations.
    var state = new TraversalState(root.identifier(), new HashMap<>(), new HashMap<>(),
        new HashMap<>(), new HashSet<>());

src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/mapper/entity/EntityDtoOutFromEntityNodeMapper.java:157

  • Root-node exclusion compares only identifier (target.identifier() vs state.rootIdentifier()). This can incorrectly exclude non-root nodes that share the same identifier across templates, and it will also stop working correctly if rootIdentifier is made composite. Compare using the already-built composite targetId instead.
        boolean isNotRoot = !target.identifier().equalsIgnoreCase(state.rootIdentifier());

src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/mapper/entity/EntityDtoOutFromEntityNodeMapper.java:200

  • Root-node exclusion compares only identifier (source.identifier() vs state.rootIdentifier()). In multi-template scenarios this can exclude the wrong node, and it will also stop working correctly if rootIdentifier is made composite. Compare using the already-built composite sourceId instead.
        boolean isNotRoot = !source.identifier().equalsIgnoreCase(state.rootIdentifier());

src/main/java/com/decathlon/idp_core/domain/service/entity_graph/EntityGraphHelper.java:59

  • buildGraphNodesForEntityIds(...) iterates over every entry in entityGraphs and recomputes a reachable footprint + localized indices for each one. In current call sites, entityGraphs is the entire discovered subgraph (roots + neighbors), so this makes graph building O(N²) and does much more work than needed (especially for single-root calls). Consider changing the API to accept explicit root IDs and only build graphs for those roots, reusing the global indices where possible.
    for (Map.Entry<UUID, Entity> entry : entityGraphs.entrySet()) {
      Entity entity = entry.getValue();
      if (entity != null) {

        Set<UUID> reachableFootprint = computeReachableSubGraph(entry.getKey(), entityGraphs,

src/main/java/com/decathlon/idp_core/domain/service/entity_graph/EntityGraphHelper.java:303

  • Inbound lookup is keyed only by entity.identifier(), which is ambiguous if identifiers are only unique per template. This can attach inbound relations from an entity in a different template that happens to share the same identifier. Use a composite key (template + identifier) consistently for inbound indexing and lookup.
    // Process inbound relationships
    List<EntityGraphRelation> inboundRelations = buildRelationsAsTargetFromIndex(
        entity.identifier(), ctx);

src/main/java/com/decathlon/idp_core/domain/service/entity_graph/EntityGraphHelper.java:407

  • inboundIndex is built using only targetId as the key. If identifiers are not globally unique, this will mix inbound edges for different templates. Use relation.targetTemplateIdentifier() + ":" + targetId (normalized) as the inbound index key so inbound lookups can be template-aware.
        if (targetId == null)
          continue;
        String normalizedTargetId = targetId.trim().toLowerCase();
        inboundIndex.computeIfAbsent(normalizedTargetId, k -> new HashMap<>())
            .computeIfAbsent(relation.name(), k -> new ArrayList<>()).add(sourceUuid);

src/main/java/com/decathlon/idp_core/domain/service/entity_graph/EntityGraphService.java:10

  • This introduces Spring Data types (Page/Pageable) into the Domain layer. Per the project’s Domain layer constraints, Domain code should not depend on Spring Data; pagination concerns belong in Infrastructure/Application and the Domain API should use domain-friendly abstractions (e.g., criteria + List/Slice-like domain model) instead.
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

Comment on lines +229 to +233
String normalizedId = entity.identifier() == null
? ""
: entity.identifier().trim().toLowerCase();
Map<String, List<UUID>> sourcesByRelation = globalIndices.inboundIndex()
.getOrDefault(normalizedId, Map.of());
Copilot AI review requested due to automatic review settings July 24, 2026 08:55
@sonarqubecloud

Copy link
Copy Markdown

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 38 out of 41 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (5)

src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/mapper/entity/EntityDtoOutFromEntityNodeMapper.java:84

  • TraversalState tracks the root using only root.identifier(). If identifiers are only unique within a template (as implied by the new composite-key work), this can wrongly treat a different-template entity with the same identifier as the root during traversal/dedup, leading to missing relations in the flattened map.
    var state = new TraversalState(root.identifier(), new HashMap<>(), new HashMap<>(),
        new HashMap<>(), new HashSet<>());

src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/mapper/entity/EntityDtoOutFromEntityNodeMapper.java:158

  • Root-exclusion logic compares only target.identifier() to state.rootIdentifier(). Once rootIdentifier is made template-aware (needed to avoid cross-template collisions), this check should compare the same composite id (templateIdentifier:identifier) to reliably exclude only the true root node.
        String targetId = nodeId(target.templateIdentifier(), target.identifier());
        String signature = nodeId + "|" + targetId + "|" + relation.name();

        boolean isNotRoot = !target.identifier().equalsIgnoreCase(state.rootIdentifier());

src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/mapper/entity/EntityDtoOutFromEntityNodeMapper.java:201

  • Inbound root-exclusion uses source.identifier() only. For consistency with template-aware IDs (and to avoid excluding a different-template entity that happens to share the root identifier), compare the composite sourceId (templateIdentifier:identifier) against the stored root id.
        String sourceId = nodeId(source.templateIdentifier(), source.identifier());
        String signature = sourceId + "|" + nodeId + "|" + relation.name();

        boolean isNotRoot = !source.identifier().equalsIgnoreCase(state.rootIdentifier());

src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/mapper/entity/EntityDtoOutMapper.java:176

  • Inbound relations are looked up/grouped by entity.identifier() only (relationTargetOwnershipsMap.get(entity.identifier())). If identifiers are not globally unique, this can merge inbound relations across templates and expose unrelated entities in responses. This should be keyed by an unambiguous identifier (preferably target_entity_uuid) rather than the bare identifier string.
    // Add inbound relations (entity is target) — merge if key exists
    List<RelationAsTargetSummary> inboundRelations = relationTargetOwnershipsMap
        .get(entity.identifier());
    if (inboundRelations != null) {

src/main/java/com/decathlon/idp_core/domain/service/entity_graph/EntityGraphHelper.java:56

  • buildGraphNodesForEntityIds(...) iterates over every entry in entityGraphs. Callers pass the full discovered graph footprint (roots + neighbors), so this builds a full rooted graph for each discovered node even though only the requested root(s) are needed. This can turn a single graph request into O(N) graph-builds and will not scale for larger graphs/pages.
    IndexBundle globalIndices = buildIndices(entityGraphs, mode);
    Map<UUID, EntityGraphNode> result = new HashMap<>();

    for (Map.Entry<UUID, Entity> entry : entityGraphs.entrySet()) {
      Entity entity = entry.getValue();

Comment on lines 34 to +38
SELECT
rte.target_entity_identifier AS targetEntityIdentifier,
r.name AS relationName,
e.identifier AS sourceEntityIdentifier,
e.name AS sourceEntityName
e.name AS sourceEntityName,
Comment on lines +721 to +725
"Optional filter query using a simple expression language. See
more details in the API documentation. Example: `name:idp` for entities
with names containing 'idp'.

"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants