Skip to content

EIN-4841: Implement indexable DownloadCount entity#645

Open
gne wants to merge 6 commits intomainfrom
EIN-4841-klikk-statistikk
Open

EIN-4841: Implement indexable DownloadCount entity#645
gne wants to merge 6 commits intomainfrom
EIN-4841-klikk-statistikk

Conversation

@gne
Copy link
Copy Markdown
Collaborator

@gne gne commented Mar 30, 2026

No description provided.

Copilot AI review requested due to automatic review settings March 30, 2026 13:49
import no.einnsyn.backend.entities.base.models.Base;

@Getter
@Setter

// lastIndexed should not be updated through JPA
@Column(insertable = false, updatable = false)
private Instant lastIndexed;
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Implements persistent, indexable download-count tracking for Dokumentobjekt downloads and exposes aggregated download totals via the existing Elasticsearch-backed statistics endpoint.

Changes:

  • Add DownloadCount DB entity + migration, with hourly bucketing and Elasticsearch indexing as a join-child (statRelation name download).
  • Record downloads from DokumentobjektService.download(...) and extend StatisticsService to aggregate downloads via a sum(count) children aggregation.
  • Wire DownloadCount into indexing/reindexing/stale-removal schedulers and add/extend tests for parent resolution and statistics counts.

Reviewed changes

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

Show a summary per file
File Description
src/test/java/no/einnsyn/backend/entities/downloadcount/DownloadCountServiceTest.java Adds unit tests for resolving ES parent routing for DownloadCount.
src/test/java/no/einnsyn/backend/entities/dokumentobjekt/DokumentobjektControllerTest.java Adds integration tests asserting downloads are reflected in /statistics and helper methods for asserting totals.
src/main/resources/elasticsearch/indexMappings.json Adds count field mapping used for download sum aggregations.
src/main/resources/db/migration/V20260323_1200__Dokumentobjekt_download_statistics.sql Creates dokumentobjekt_download_stat table and indexes for bucketing + indexing state.
src/main/resources/application.yml Adds downloadCountSchemaTimestamp for the reindexer.
src/main/java/no/einnsyn/backend/utils/id/IdPrefix.java Registers DownloadCount id prefix (dc_).
src/main/java/no/einnsyn/backend/tasks/handlers/reindex/ElasticsearchRemoveStaleScheduler.java Adds stale-removal pass for DownloadCount documents.
src/main/java/no/einnsyn/backend/tasks/handlers/reindex/ElasticsearchReindexScheduler.java Adds reindex scheduling for DownloadCount.
src/main/java/no/einnsyn/backend/tasks/handlers/index/ElasticsearchHandlerInterceptor.java Enables request-end indexing for DownloadCount ids.
src/main/java/no/einnsyn/backend/entities/moetesak/MoetesakRepository.java Adds queries to resolve Moetesak parents via Utredning/Vedtak documents.
src/main/java/no/einnsyn/backend/entities/downloadcount/models/DownloadCountES.java Defines ES document shape incl. count + join relation payload.
src/main/java/no/einnsyn/backend/entities/downloadcount/models/DownloadCountDTO.java Introduces DTO type for the new service.
src/main/java/no/einnsyn/backend/entities/downloadcount/models/DownloadCount.java Introduces JPA entity backing hourly buckets and indexing metadata.
src/main/java/no/einnsyn/backend/entities/downloadcount/DownloadCountService.java Implements bucketing, parent lookup, and ES conversion for indexing.
src/main/java/no/einnsyn/backend/entities/downloadcount/DownloadCountRepository.java Adds lookup + indexability queries for reindex/stale detection.
src/main/java/no/einnsyn/backend/entities/dokumentobjekt/DokumentobjektService.java Records downloads and deletes associated DownloadCount rows on Dokumentobjekt deletion.
src/main/java/no/einnsyn/backend/common/statistics/StatisticsService.java Adds download sum aggregation + time-series collection for download counts.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +257 to +261
removeForEntity(
"DownloadCount",
List.of("id", "created"),
downloadCountService.getRepository(),
downloadCountService.getElasticsearchIndex());
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

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

DownloadCount documents are indexed as join-children (statRelation name="download") and therefore use custom routing (the parent registrering id). removeStaleDocuments() deletes stale ES docs via deleteDocumentList(...) which issues bulk deletes without routing, so routed DownloadCount documents may not actually be removed from Elasticsearch. Consider extending stale removal to delete with the correct routing (e.g., carry Hit.routing() from the iterator into the bulk delete ops, or switch to a delete-by-query strategy) before enabling stale cleanup for DownloadCount.

Suggested change
removeForEntity(
"DownloadCount",
List.of("id", "created"),
downloadCountService.getRepository(),
downloadCountService.getElasticsearchIndex());
// NOTE: DownloadCount documents are indexed as routed join-children and require
// explicit routing for deletion. Generic stale removal via removeForEntity
// does bulk deletes without routing, so it is disabled here until a
// routing-aware cleanup implementation is added.

Copilot uses AI. Check for mistakes.
Comment on lines +119 to +130
// Try to get the parent from the ES index (needed when parent is deleted before child)
try {
esClient.indices().refresh(r -> r.index(elasticsearchIndex));
var esResponse =
esClient.search(
sr -> sr.index(elasticsearchIndex).query(q -> q.ids(ids -> ids.values(List.of(id)))),
Void.class);
return esResponse.hits().hits().getFirst().routing();
} catch (Exception e) {
log.error("Failed to get parent for DownloadCount {}", id, e);
}
return null;
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

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

getESParent(...) falls back to an Elasticsearch ids-search and then unconditionally calls esResponse.hits().hits().getFirst(). If the document is not present in ES (e.g., a never-indexed DownloadCount that still can't resolve a DB parent), this throws and is caught, producing an error log for a non-exceptional case. Consider checking whether hits() is empty (and whether routing() is present) before accessing the first hit, and avoid logging at error level when ES simply has no matching document.

Copilot uses AI. Check for mistakes.
@coveralls
Copy link
Copy Markdown
Collaborator

coveralls commented Mar 30, 2026

Pull Request Test Coverage Report for Build 23749164808

Details

  • 0 of 0 changed or added relevant lines in 0 files are covered.
  • 57 unchanged lines in 6 files lost coverage.
  • Overall coverage decreased (-0.06%) to 85.096%

Files with Coverage Reduction New Missed Lines %
no/einnsyn/backend/entities/innsynskrav/models/Innsynskrav.java 2 82.14%
no/einnsyn/backend/tasks/handlers/index/ElasticsearchHandlerInterceptor.java 5 80.0%
no/einnsyn/backend/tasks/handlers/reindex/ElasticsearchReindexScheduler.java 6 88.89%
no/einnsyn/backend/entities/dokumentobjekt/DokumentobjektService.java 13 82.22%
no/einnsyn/backend/tasks/handlers/reindex/ElasticsearchRemoveStaleScheduler.java 15 79.31%
no/einnsyn/backend/common/statistics/StatisticsService.java 16 83.17%
Totals Coverage Status
Change from base Build 23586825993: -0.06%
Covered Lines: 8037
Relevant Lines: 8977

💛 - Coveralls

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.

3 participants