diff --git a/core/src/main/java/google/registry/ui/server/console/ConsoleDomainListAction.java b/core/src/main/java/google/registry/ui/server/console/ConsoleDomainListAction.java index 95651092275..789c21a4326 100644 --- a/core/src/main/java/google/registry/ui/server/console/ConsoleDomainListAction.java +++ b/core/src/main/java/google/registry/ui/server/console/ConsoleDomainListAction.java @@ -21,10 +21,13 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Ascii; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import com.google.gson.annotations.Expose; import google.registry.model.CreateAutoTimestamp; import google.registry.model.console.User; import google.registry.model.domain.Domain; +import google.registry.model.eppcommon.StatusValue; import google.registry.request.Action; import google.registry.request.Action.Service; import google.registry.request.Parameter; @@ -33,7 +36,10 @@ import jakarta.persistence.TypedQuery; import java.time.Instant; import java.util.List; +import java.util.Map; import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; /** Returns a (paginated) list of domains for a particular registrar. */ @Action( @@ -46,11 +52,11 @@ public class ConsoleDomainListAction extends ConsoleApiAction { public static final String PATH = "/console-api/domain-list"; private static final int DEFAULT_RESULTS_PER_PAGE = 50; - private static final String DOMAIN_QUERY_TEMPLATE = - "FROM Domain WHERE currentSponsorRegistrarId = :registrarId AND deletionTime >" - + " :deletedAfterTime AND creationTime <= :createdBeforeTime"; - private static final String SEARCH_TERM_QUERY = " AND LOWER(domainName) LIKE :searchTerm"; - private static final String ORDER_BY_STATEMENT = " ORDER BY creationTime DESC"; + private static final String DOMAIN_QUERY_FILTER = + " WHERE d.currentSponsorRegistrarId = :registrarId AND d.deletionTime >" + + " :deletedAfterTime AND d.creationTime <= :createdBeforeTime"; + private static final String SEARCH_TERM_QUERY = " AND LOWER(d.domainName) LIKE :searchTerm"; + private static final String ORDER_BY_STATEMENT = " ORDER BY d.creationTime DESC"; private final String registrarId; private final Optional checkpointTime; @@ -112,18 +118,46 @@ private void runInTransaction() { .setMaxResults(resultsPerPage) .getResultList(); + ImmutableList domainInfos; + if (domains.isEmpty()) { + domainInfos = ImmutableList.of(); + } else { + ImmutableList repoIds = + domains.stream().map(Domain::getRepoId).collect(ImmutableList.toImmutableList()); + List domainsWithStatuses = + tm().query( + "SELECT DISTINCT d FROM Domain d " + + "LEFT JOIN FETCH d.statuses " + + "WHERE d.repoId IN (:repoIds)", + Domain.class) + .setParameter("repoIds", repoIds) + .getResultList(); + + Map statusMap = + domainsWithStatuses.stream().collect(Collectors.toMap(Domain::getRepoId, d -> d)); + + domainInfos = + domains.stream() + .map( + d -> { + Domain domainWithStatuses = statusMap.getOrDefault(d.getRepoId(), d); + return ConsoleDomainInfo.fromDomain(domainWithStatuses); + }) + .collect(ImmutableList.toImmutableList()); + } + consoleApiParams .response() .setPayload( consoleApiParams .gson() - .toJson(new DomainListResult(domains, checkpoint, actualTotalResults))); + .toJson(new DomainListResult(domainInfos, checkpoint, actualTotalResults))); consoleApiParams.response().setStatus(SC_OK); } /** Creates the query to get the total number of matching domains, interpolating as necessary. */ private TypedQuery createCountQuery() { - String queryString = "SELECT COUNT(*) " + DOMAIN_QUERY_TEMPLATE; + String queryString = "SELECT COUNT(d) FROM Domain d" + DOMAIN_QUERY_FILTER; if (searchTerm.isPresent() && !searchTerm.get().isEmpty()) { return tm().query(queryString + SEARCH_TERM_QUERY, Long.class) .setParameter("searchTerm", String.format("%%%s%%", Ascii.toLowerCase(searchTerm.get()))); @@ -133,22 +167,64 @@ private TypedQuery createCountQuery() { /** Creates the query to retrieve the matching domains themselves, interpolating as necessary. */ private TypedQuery createDomainQuery() { + String query = "SELECT d FROM Domain d" + DOMAIN_QUERY_FILTER; if (searchTerm.isPresent() && !searchTerm.get().isEmpty()) { - return tm().query( - DOMAIN_QUERY_TEMPLATE + SEARCH_TERM_QUERY + ORDER_BY_STATEMENT, Domain.class) + return tm().query(query + SEARCH_TERM_QUERY + ORDER_BY_STATEMENT, Domain.class) .setParameter("searchTerm", String.format("%%%s%%", Ascii.toLowerCase(searchTerm.get()))); } - return tm().query(DOMAIN_QUERY_TEMPLATE + ORDER_BY_STATEMENT, Domain.class); + return tm().query(query + ORDER_BY_STATEMENT, Domain.class); + } + + public static final class ConsoleDomainInfo { + @Expose String domainName; + @Expose CreateAutoTimestamp creationTime; + @Expose Instant registrationExpirationTime; + @Expose String currentSponsorRegistrarId; + @Expose Set statuses; + + public String getDomainName() { + return domainName; + } + + public CreateAutoTimestamp getCreationTime() { + return creationTime; + } + + public Instant getRegistrationExpirationTime() { + return registrationExpirationTime; + } + + public String getCurrentSponsorRegistrarId() { + return currentSponsorRegistrarId; + } + + public Set getStatuses() { + return statuses; + } + + static ConsoleDomainInfo fromDomain(Domain domain) { + ConsoleDomainInfo info = new ConsoleDomainInfo(); + info.domainName = domain.getDomainName(); + info.creationTime = CreateAutoTimestamp.create(domain.getCreationTime()); + info.registrationExpirationTime = domain.getRegistrationExpirationTime(); + info.currentSponsorRegistrarId = domain.getCurrentSponsorRegistrarId(); + info.statuses = + domain.getStatusValues().stream() + .map(StatusValue::name) + .collect(ImmutableSet.toImmutableSet()); + return info; + } } /** Container result class that allows for pagination. */ @VisibleForTesting static final class DomainListResult { - @Expose List domains; + @Expose List domains; @Expose Instant checkpointTime; @Expose long totalResults; - private DomainListResult(List domains, Instant checkpointTime, long totalResults) { + private DomainListResult( + List domains, Instant checkpointTime, long totalResults) { this.domains = domains; this.checkpointTime = checkpointTime; this.totalResults = totalResults; diff --git a/core/src/test/java/google/registry/ui/server/console/ConsoleDomainListActionTest.java b/core/src/test/java/google/registry/ui/server/console/ConsoleDomainListActionTest.java index 664718aa01f..72e5b41d9d0 100644 --- a/core/src/test/java/google/registry/ui/server/console/ConsoleDomainListActionTest.java +++ b/core/src/test/java/google/registry/ui/server/console/ConsoleDomainListActionTest.java @@ -30,6 +30,7 @@ import google.registry.testing.ConsoleApiParamsUtils; import google.registry.testing.DatabaseHelper; import google.registry.testing.FakeResponse; +import google.registry.ui.server.console.ConsoleDomainListAction.ConsoleDomainInfo; import google.registry.ui.server.console.ConsoleDomainListAction.DomainListResult; import java.time.Instant; import java.util.Optional; @@ -77,7 +78,10 @@ void testSuccess_pages() { ConsoleDomainListAction action = createAction("TheRegistrar", null, 0, 5, null, null); action.run(); DomainListResult result = GSON.fromJson(response.getPayload(), DomainListResult.class); - assertThat(result.domains.stream().map(Domain::getDomainName).collect(toImmutableList())) + assertThat( + result.domains.stream() + .map(ConsoleDomainInfo::getDomainName) + .collect(toImmutableList())) .containsExactly("9exists.tld", "8exists.tld", "7exists.tld", "6exists.tld", "5exists.tld"); assertThat(result.totalResults).isEqualTo(10); @@ -85,7 +89,10 @@ void testSuccess_pages() { action = createAction("TheRegistrar", result.checkpointTime, 1, 5, 10L, null); action.run(); result = GSON.fromJson(response.getPayload(), DomainListResult.class); - assertThat(result.domains.stream().map(Domain::getDomainName).collect(toImmutableList())) + assertThat( + result.domains.stream() + .map(ConsoleDomainInfo::getDomainName) + .collect(toImmutableList())) .containsExactly("4exists.tld", "3exists.tld", "2exists.tld", "1exists.tld", "0exists.tld"); } @@ -94,7 +101,10 @@ void testSuccess_partialPage() { ConsoleDomainListAction action = createAction("TheRegistrar", null, 1, 8, null, null); action.run(); DomainListResult result = GSON.fromJson(response.getPayload(), DomainListResult.class); - assertThat(result.domains.stream().map(Domain::getDomainName).collect(toImmutableList())) + assertThat( + result.domains.stream() + .map(ConsoleDomainInfo::getDomainName) + .collect(toImmutableList())) .containsExactly("1exists.tld", "0exists.tld"); } @@ -132,7 +142,10 @@ void testSuccess_checkpointTime_deletion() { action = createAction("TheRegistrar", result.checkpointTime, 1, 5, null, null); action.run(); result = GSON.fromJson(response.getPayload(), DomainListResult.class); - assertThat(result.domains.stream().map(Domain::getDomainName).collect(toImmutableList())) + assertThat( + result.domains.stream() + .map(ConsoleDomainInfo::getDomainName) + .collect(toImmutableList())) .containsExactly("4exists.tld", "3exists.tld", "2exists.tld", "1exists.tld", "0exists.tld"); }