From a4045e0de7e8922fbe8cb2a9c48e7f780c81a796 Mon Sep 17 00:00:00 2001 From: Simone Carletti Date: Fri, 18 Sep 2026 14:24:38 +0200 Subject: [PATCH 1/3] Add DNS analytics query --- CHANGELOG.md | 1 + src/main/java/com/dnsimple/Client.java | 5 +- .../com/dnsimple/data/DnsAnalyticsEntry.java | 33 ++++++++ .../com/dnsimple/data/DnsAnalyticsQuery.java | 51 +++++++++++++ .../com/dnsimple/endpoints/DnsAnalytics.java | 71 +++++++++++++++++ .../response/PaginatedResponseWithQuery.java | 16 ++++ .../dnsimple/endpoints/DnsAnalyticsTest.java | 76 +++++++++++++++++++ 7 files changed, 252 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/dnsimple/data/DnsAnalyticsEntry.java create mode 100644 src/main/java/com/dnsimple/data/DnsAnalyticsQuery.java create mode 100644 src/main/java/com/dnsimple/endpoints/DnsAnalytics.java create mode 100644 src/main/java/com/dnsimple/response/PaginatedResponseWithQuery.java create mode 100644 src/test/java/com/dnsimple/endpoints/DnsAnalyticsTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 62510038..82a7ec6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ This project uses [Semantic Versioning 2.0.0](http://semver.org/), the format is - Added `restoreDomain` to restore a domain. (#267) - Added `getDomainRestore` to retrieve the details of a domain restore. (#267) - Added `batchChangeZoneRecords` to create, update, and delete records in a zone in one atomic request. (#273) +- Added `DnsAnalytics.query` to query DNS Analytics data. The API is in Public Beta. ### Changed diff --git a/src/main/java/com/dnsimple/Client.java b/src/main/java/com/dnsimple/Client.java index 3d28a90d..212a0348 100644 --- a/src/main/java/com/dnsimple/Client.java +++ b/src/main/java/com/dnsimple/Client.java @@ -25,6 +25,7 @@ public class Client { public final Billing billing; public final Certificates certificates; public final Contacts contacts; + public final DnsAnalytics dnsAnalytics; public final Domains domains; public final Identity identity; public final Oauth oauth; @@ -36,12 +37,13 @@ public class Client { public final Webhooks webhooks; public final Zones zones; - private Client(HttpEndpointClient endpointClient, Accounts accounts, Billing billing, Certificates certificates, Contacts contacts, Domains domains, Identity identity, Oauth oauth, Registrar registrar, Services services, Templates templates, Tlds tlds, VanityNameServers vanityNameServers, Webhooks webhooks, Zones zones) { + private Client(HttpEndpointClient endpointClient, Accounts accounts, Billing billing, Certificates certificates, Contacts contacts, DnsAnalytics dnsAnalytics, Domains domains, Identity identity, Oauth oauth, Registrar registrar, Services services, Templates templates, Tlds tlds, VanityNameServers vanityNameServers, Webhooks webhooks, Zones zones) { this.endpointClient = endpointClient; this.accounts = accounts; this.billing = billing; this.certificates = certificates; this.contacts = contacts; + this.dnsAnalytics = dnsAnalytics; this.domains = domains; this.identity = identity; this.oauth = oauth; @@ -62,6 +64,7 @@ private static Client of(HttpRequestFactory httpRequestFactory, URL apiBase, Str new Billing(endpointClient), new Certificates(endpointClient), new Contacts(endpointClient), + new DnsAnalytics(endpointClient), new Domains(endpointClient), new Identity(endpointClient), new Oauth(endpointClient), diff --git a/src/main/java/com/dnsimple/data/DnsAnalyticsEntry.java b/src/main/java/com/dnsimple/data/DnsAnalyticsEntry.java new file mode 100644 index 00000000..b0af2542 --- /dev/null +++ b/src/main/java/com/dnsimple/data/DnsAnalyticsEntry.java @@ -0,0 +1,33 @@ +package com.dnsimple.data; + +import java.time.LocalDate; + +public class DnsAnalyticsEntry { + private final String zoneName; + private final LocalDate date; + private final Long volume; + + public DnsAnalyticsEntry(String zoneName, LocalDate date, Long volume) { + this.zoneName = zoneName; + this.date = date; + this.volume = volume; + } + + /** + * @return The zone name, or null when the query does not group by zone name + */ + public String getZoneName() { + return zoneName; + } + + /** + * @return The date, or null when the query does not group by date + */ + public LocalDate getDate() { + return date; + } + + public Long getVolume() { + return volume; + } +} diff --git a/src/main/java/com/dnsimple/data/DnsAnalyticsQuery.java b/src/main/java/com/dnsimple/data/DnsAnalyticsQuery.java new file mode 100644 index 00000000..a32c9912 --- /dev/null +++ b/src/main/java/com/dnsimple/data/DnsAnalyticsQuery.java @@ -0,0 +1,51 @@ +package com.dnsimple.data; + +import java.time.LocalDate; + +public class DnsAnalyticsQuery { + private final Long accountId; + private final LocalDate startDate; + private final LocalDate endDate; + private final String sort; + private final Integer page; + private final Integer perPage; + private final String groupings; + + public DnsAnalyticsQuery(Long accountId, LocalDate startDate, LocalDate endDate, String sort, Integer page, Integer perPage, String groupings) { + this.accountId = accountId; + this.startDate = startDate; + this.endDate = endDate; + this.sort = sort; + this.page = page; + this.perPage = perPage; + this.groupings = groupings; + } + + public Long getAccountId() { + return accountId; + } + + public LocalDate getStartDate() { + return startDate; + } + + public LocalDate getEndDate() { + return endDate; + } + + public String getSort() { + return sort; + } + + public Integer getPage() { + return page; + } + + public Integer getPerPage() { + return perPage; + } + + public String getGroupings() { + return groupings; + } +} diff --git a/src/main/java/com/dnsimple/endpoints/DnsAnalytics.java b/src/main/java/com/dnsimple/endpoints/DnsAnalytics.java new file mode 100644 index 00000000..024aa45a --- /dev/null +++ b/src/main/java/com/dnsimple/endpoints/DnsAnalytics.java @@ -0,0 +1,71 @@ +package com.dnsimple.endpoints; + +import com.dnsimple.data.DnsAnalyticsEntry; +import com.dnsimple.data.DnsAnalyticsQuery; +import com.dnsimple.http.HttpEndpointClient; +import com.dnsimple.request.ListOptions; +import com.dnsimple.response.PaginatedResponseWithQuery; +import com.dnsimple.response.Pagination; + +import java.time.LocalDate; +import java.util.List; + +import static com.dnsimple.http.HttpMethod.GET; +import static java.util.stream.Collectors.toList; + +/** + * Provides access to the DNSimple DNS Analytics API. + * + * @see https://developer.dnsimple.com/v2/dns-analytics + */ +public class DnsAnalytics { + private final HttpEndpointClient client; + + public DnsAnalytics(HttpEndpointClient client) { + this.client = client; + } + + /** + * Queries DNS Analytics data for the account. This API is in Public Beta. + *

+ * Use the filters start_date, end_date, and groupings, and sort by date, volume, or zone_name. + * + * @param account The account ID + * @param options The options for the query request + * @return The DNS Analytics query response + * @see https://developer.dnsimple.com/v2/dns-analytics/#queryDnsAnalytics + */ + public PaginatedResponseWithQuery query(Number account, ListOptions options) { + QueryResponse response = client.raw(GET, account + "/dns_analytics", options, null, QueryResponse.class); + List entries = response.data.rows.stream() + .map(row -> toEntry(response.data.headers, row)) + .collect(toList()); + return new PaginatedResponseWithQuery<>(entries, response.pagination, response.query); + } + + private static DnsAnalyticsEntry toEntry(List headers, List row) { + String date = (String) valueOf("date", headers, row); + Number volume = (Number) valueOf("volume", headers, row); + return new DnsAnalyticsEntry( + (String) valueOf("zone_name", headers, row), + date == null ? null : LocalDate.parse(date), + volume == null ? null : volume.longValue() + ); + } + + private static Object valueOf(String header, List headers, List row) { + int index = headers.indexOf(header); + return index < 0 ? null : row.get(index); + } + + private static class QueryResponse { + private QueryData data; + private DnsAnalyticsQuery query; + private Pagination pagination; + } + + private static class QueryData { + private List headers; + private List> rows; + } +} diff --git a/src/main/java/com/dnsimple/response/PaginatedResponseWithQuery.java b/src/main/java/com/dnsimple/response/PaginatedResponseWithQuery.java new file mode 100644 index 00000000..4ce4d23c --- /dev/null +++ b/src/main/java/com/dnsimple/response/PaginatedResponseWithQuery.java @@ -0,0 +1,16 @@ +package com.dnsimple.response; + +import java.util.List; + +public class PaginatedResponseWithQuery extends PaginatedResponse { + private final Q query; + + public PaginatedResponseWithQuery(List data, Pagination pagination, Q query) { + super(data, pagination); + this.query = query; + } + + public Q getQuery() { + return query; + } +} diff --git a/src/test/java/com/dnsimple/endpoints/DnsAnalyticsTest.java b/src/test/java/com/dnsimple/endpoints/DnsAnalyticsTest.java new file mode 100644 index 00000000..5fda80eb --- /dev/null +++ b/src/test/java/com/dnsimple/endpoints/DnsAnalyticsTest.java @@ -0,0 +1,76 @@ +package com.dnsimple.endpoints; + +import com.dnsimple.data.DnsAnalyticsEntry; +import com.dnsimple.data.DnsAnalyticsQuery; +import com.dnsimple.request.ListOptions; +import com.dnsimple.response.PaginatedResponseWithQuery; +import com.dnsimple.tools.DnsimpleTestBase; +import org.junit.Test; + +import java.time.LocalDate; +import java.util.List; + +import static com.dnsimple.http.HttpMethod.GET; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; + +public class DnsAnalyticsTest extends DnsimpleTestBase { + @Test + public void testQuery() { + server.stubFixtureAt("dnsAnalytics/success.http"); + client.dnsAnalytics.query(1, ListOptions.empty()); + assertThat(server.getRecordedRequest().getMethod(), is(GET)); + assertThat(server.getRecordedRequest().getPath(), is("/v2/1/dns_analytics")); + } + + @Test + public void testQuerySupportsFilteringSortingAndPagination() { + server.stubFixtureAt("dnsAnalytics/success.http"); + ListOptions options = ListOptions.empty() + .page(0, 100) + .filter("start_date", "2023-12-08") + .filter("end_date", "2024-01-08") + .filter("groupings", "zone_name,date") + .sortAsc("zone_name") + .sortAsc("date"); + client.dnsAnalytics.query(1, options); + assertThat(server.getRecordedRequest().getPath(), is("/v2/1/dns_analytics?page=0&per_page=100&start_date=2023-12-08&end_date=2024-01-08&groupings=zone_name%2Cdate&sort=zone_name%3Aasc,date%3Aasc")); + } + + @Test + public void testQueryProducesEntryList() { + server.stubFixtureAt("dnsAnalytics/success.http"); + List entries = client.dnsAnalytics.query(1, ListOptions.empty()).getData(); + assertThat(entries, hasSize(12)); + assertThat(entries.get(0).getZoneName(), is("bar.com")); + assertThat(entries.get(0).getDate(), is(LocalDate.of(2023, 12, 8))); + assertThat(entries.get(0).getVolume(), is(1200L)); + assertThat(entries.get(11).getZoneName(), is("foo.com")); + assertThat(entries.get(11).getDate(), is(LocalDate.of(2024, 1, 8))); + assertThat(entries.get(11).getVolume(), is(1200L)); + } + + @Test + public void testQueryExposesPaginationInfo() { + server.stubFixtureAt("dnsAnalytics/success.http"); + PaginatedResponseWithQuery response = client.dnsAnalytics.query(1, ListOptions.empty()); + assertThat(response.getPagination().getCurrentPage(), is(0)); + assertThat(response.getPagination().getPerPage(), is(100)); + assertThat(response.getPagination().getTotalEntries(), is(93)); + assertThat(response.getPagination().getTotalPages(), is(1)); + } + + @Test + public void testQueryExposesQuery() { + server.stubFixtureAt("dnsAnalytics/success.http"); + DnsAnalyticsQuery query = client.dnsAnalytics.query(1, ListOptions.empty()).getQuery(); + assertThat(query.getAccountId(), is(1L)); + assertThat(query.getStartDate(), is(LocalDate.of(2023, 12, 8))); + assertThat(query.getEndDate(), is(LocalDate.of(2024, 1, 8))); + assertThat(query.getSort(), is("zone_name:asc,date:asc")); + assertThat(query.getPage(), is(0)); + assertThat(query.getPerPage(), is(100)); + assertThat(query.getGroupings(), is("zone_name,date")); + } +} From 67c3ee6f5c41f129a7bdb751c8c1e073f3e75e76 Mon Sep 17 00:00:00 2001 From: Simone Carletti Date: Fri, 18 Sep 2026 14:25:39 +0200 Subject: [PATCH 2/3] Reference the PR in the changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 82a7ec6a..c9a17cdf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ This project uses [Semantic Versioning 2.0.0](http://semver.org/), the format is - Added `restoreDomain` to restore a domain. (#267) - Added `getDomainRestore` to retrieve the details of a domain restore. (#267) - Added `batchChangeZoneRecords` to create, update, and delete records in a zone in one atomic request. (#273) -- Added `DnsAnalytics.query` to query DNS Analytics data. The API is in Public Beta. +- Added `DnsAnalytics.query` to query DNS Analytics data. The API is in Public Beta. (#274) ### Changed From d8f2eba4d4e381001ffa29994ee69070fca41e02 Mon Sep 17 00:00:00 2001 From: Simone Carletti Date: Fri, 18 Sep 2026 17:23:48 +0200 Subject: [PATCH 3/3] Trim the DNS analytics Javadoc --- src/main/java/com/dnsimple/data/DnsAnalyticsEntry.java | 6 ------ src/main/java/com/dnsimple/endpoints/DnsAnalytics.java | 4 +--- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/src/main/java/com/dnsimple/data/DnsAnalyticsEntry.java b/src/main/java/com/dnsimple/data/DnsAnalyticsEntry.java index b0af2542..2e606f80 100644 --- a/src/main/java/com/dnsimple/data/DnsAnalyticsEntry.java +++ b/src/main/java/com/dnsimple/data/DnsAnalyticsEntry.java @@ -13,16 +13,10 @@ public DnsAnalyticsEntry(String zoneName, LocalDate date, Long volume) { this.volume = volume; } - /** - * @return The zone name, or null when the query does not group by zone name - */ public String getZoneName() { return zoneName; } - /** - * @return The date, or null when the query does not group by date - */ public LocalDate getDate() { return date; } diff --git a/src/main/java/com/dnsimple/endpoints/DnsAnalytics.java b/src/main/java/com/dnsimple/endpoints/DnsAnalytics.java index 024aa45a..7baf9fcc 100644 --- a/src/main/java/com/dnsimple/endpoints/DnsAnalytics.java +++ b/src/main/java/com/dnsimple/endpoints/DnsAnalytics.java @@ -27,12 +27,10 @@ public DnsAnalytics(HttpEndpointClient client) { /** * Queries DNS Analytics data for the account. This API is in Public Beta. - *

- * Use the filters start_date, end_date, and groupings, and sort by date, volume, or zone_name. * * @param account The account ID * @param options The options for the query request - * @return The DNS Analytics query response + * @return The DNS Analytics query response. An entry field is null when the query does not group by it. * @see https://developer.dnsimple.com/v2/dns-analytics/#queryDnsAnalytics */ public PaginatedResponseWithQuery query(Number account, ListOptions options) {