Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Metrics API: show number of user accounts #10260

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### New Accounts Metrics API

Users can retrieve new types of metrics related to user accounts. The new capabilities are [described](https://guides.dataverse.org/en/6.2/api/metrics.html) in the guides.
14 changes: 9 additions & 5 deletions doc/sphinx-guides/source/api/metrics.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Metrics API
===========

The Metrics API provides counts of downloads, datasets created, files uploaded, and more, as described below. The Dataverse Software also includes aggregate counts of Make Data Count metrics (described in the :doc:`/admin/make-data-count` section of the Admin Guide and available per-Dataset through the :doc:`/api/native-api`). A table of all the endpoints is listed below.
The Metrics API provides counts of downloads, datasets created, files uploaded, user accounts created, and more, as described below. The Dataverse Software also includes aggregate counts of Make Data Count metrics (described in the :doc:`/admin/make-data-count` section of the Admin Guide and available per-Dataset through the :doc:`/api/native-api`). A table of all the endpoints is listed below.

.. contents:: |toctitle|
:local:
Expand All @@ -21,7 +21,7 @@ The Metrics API includes several categories of endpoints that provide different

* Form: GET https://$SERVER/api/info/metrics/$type

* where ``$type`` can be set, for example, to ``dataverses`` (Dataverse collections), ``datasets``, ``files`` or ``downloads``.
* where ``$type`` can be set, for example, to ``dataverses`` (Dataverse collections), ``datasets``, ``files``, ``downloads`` or ``accounts``.

* Example: ``curl https://demo.dataverse.org/api/info/metrics/downloads``

Expand All @@ -31,7 +31,7 @@ The Metrics API includes several categories of endpoints that provide different

* Form: GET https://$SERVER/api/info/metrics/$type/toMonth/$YYYY-DD

* where ``$type`` can be set, for example, to ``dataverses`` (Dataverse collections), ``datasets``, ``files`` or ``downloads``.
* where ``$type`` can be set, for example, to ``dataverses`` (Dataverse collections), ``datasets``, ``files``, ``downloads`` or ``accounts``.

* Example: ``curl https://demo.dataverse.org/api/info/metrics/dataverses/toMonth/2018-01``

Expand All @@ -41,7 +41,7 @@ The Metrics API includes several categories of endpoints that provide different

* Form: GET https://$SERVER/api/info/metrics/$type/pastDays/$days

* where ``$type`` can be set, for example, to ``dataverses`` (Dataverse collections), ``datasets``, ``files`` or ``downloads``.
* where ``$type`` can be set, for example, to ``dataverses`` (Dataverse collections), ``datasets``, ``files``, ``downloads`` or ``accounts``.

* Example: ``curl https://demo.dataverse.org/api/info/metrics/datasets/pastDays/30``

Expand All @@ -51,7 +51,7 @@ The Metrics API includes several categories of endpoints that provide different

* Form: GET https://$SERVER/api/info/metrics/$type/monthly

* where ``$type`` can be set, for example, to ``dataverses`` (Dataverse collections), ``datasets``, ``files`` or ``downloads``.
* where ``$type`` can be set, for example, to ``dataverses`` (Dataverse collections), ``datasets``, ``files``, ``downloads`` or ``accounts``.

* Example: ``curl https://demo.dataverse.org/api/info/metrics/downloads/monthly``

Expand Down Expand Up @@ -163,6 +163,10 @@ The following table lists the available metrics endpoints (not including the Mak
/api/info/metrics/uniquefiledownloads/toMonth/{yyyy-MM},"count by id, pid","json, csv",collection subtree,published,y,cumulative up to month specified,unique download counts per file id to the specified month. PIDs are also included in output if they exist
/api/info/metrics/tree,"id, ownerId, alias, depth, name, children",json,collection subtree,published,y,"tree of dataverses starting at the root or a specified parentAlias with their id, owner id, alias, name, a computed depth, and array of children dataverses","underlying code can also include draft dataverses, this is not currently accessible via api, depth starts at 0"
/api/info/metrics/tree/toMonth/{yyyy-MM},"id, ownerId, alias, depth, name, children",json,collection subtree,published,y,"tree of dataverses in existence as of specified date starting at the root or a specified parentAlias with their id, owner id, alias, name, a computed depth, and array of children dataverses","underlying code can also include draft dataverses, this is not currently accessible via api, depth starts at 0"
/api/info/metrics/accounts,count,json,Dataverse installation,all,y,as of now/totals,
/api/info/metrics/accounts/toMonth/{yyyy-MM},count,json,Dataverse installation,all,y,cumulative up to month specified,
/api/info/metrics/accounts/pastDays/{n},count,json,Dataverse installation,all,y,aggregate count for past n days,
/api/info/metrics/accounts/monthly,"date, count","json, csv",Dataverse installation,all,y,monthly cumulative timeseries from first date of first entry to now,

Related API Endpoints
---------------------
Expand Down
92 changes: 92 additions & 0 deletions src/main/java/edu/harvard/iq/dataverse/api/Metrics.java
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,98 @@ public Response getDownloadsPastDays(@Context UriInfo uriInfo, @PathParam("days"
return ok(jsonObj);
}

/** Accounts */

@GET
@Path("accounts")
public Response getAccountsAllTime(@Context UriInfo uriInfo) {
return getAccountsToMonth(uriInfo, MetricsUtil.getCurrentMonth());
}

@GET
@Path("accounts/toMonth/{yyyymm}")
public Response getAccountsToMonth(@Context UriInfo uriInfo, @PathParam("yyyymm") String yyyymm) {

try {
errorIfUnrecongizedQueryParamPassed(uriInfo, new String[] { });
} catch (IllegalArgumentException ia) {
return error(BAD_REQUEST, ia.getLocalizedMessage());
}

String metricName = "accountsToMonth";
String sanitizedyyyymm = MetricsUtil.sanitizeYearMonthUserInput(yyyymm);
JsonObject jsonObj = MetricsUtil.stringToJsonObject(metricsSvc.returnUnexpiredCacheMonthly(metricName, sanitizedyyyymm, null, null));

if (null == jsonObj) { // run query and save
Long count;
try {
count = metricsSvc.accountsToMonth(sanitizedyyyymm);
} catch (ParseException e) {
return error(BAD_REQUEST, "Unable to parse supplied date: " + e.getLocalizedMessage());
}
jsonObj = MetricsUtil.countToJson(count).build();
metricsSvc.save(new Metric(metricName, sanitizedyyyymm, null, null, jsonObj.toString()));
}

return ok(jsonObj);
}

@GET
@Path("accounts/pastDays/{days}")
public Response getAccountsPastDays(@Context UriInfo uriInfo, @PathParam("days") int days) {

try {
errorIfUnrecongizedQueryParamPassed(uriInfo, new String[] { });
} catch (IllegalArgumentException ia) {
return error(BAD_REQUEST, ia.getLocalizedMessage());
}

String metricName = "accountsPastDays";

if (days < 1) {
return error(BAD_REQUEST, "Invalid parameter for number of days.");
}

JsonObject jsonObj = MetricsUtil.stringToJsonObject(metricsSvc.returnUnexpiredCacheDayBased(metricName, String.valueOf(days), null, null));

if (null == jsonObj) { // run query and save
Long count = metricsSvc.accountsPastDays(days);
jsonObj = MetricsUtil.countToJson(count).build();
metricsSvc.save(new Metric(metricName, String.valueOf(days), null, null, jsonObj.toString()));
}

return ok(jsonObj);
}

@GET
@Path("accounts/monthly")
@Produces("text/csv, application/json")
public Response getAccountsTimeSeries(@Context Request req, @Context UriInfo uriInfo) {

try {
errorIfUnrecongizedQueryParamPassed(uriInfo, new String[] { });
} catch (IllegalArgumentException ia) {
return error(BAD_REQUEST, ia.getLocalizedMessage());
}

String metricName = "accounts";
JsonArray jsonArray = MetricsUtil.stringToJsonArray(metricsSvc.returnUnexpiredCacheAllTime(metricName, null, null));

if (null == jsonArray) { // run query and save
// Only handling published right now
jsonArray = metricsSvc.accountsTimeSeries();
metricsSvc.save(new Metric(metricName, null, null, null, jsonArray.toString()));
}

MediaType requestedType = getVariant(req, MediaType.valueOf(FileUtil.MIME_TYPE_CSV), MediaType.APPLICATION_JSON_TYPE);
if ((requestedType != null) && (requestedType.equals(MediaType.APPLICATION_JSON_TYPE))) {
return ok(jsonArray);
}
return ok(FileUtil.jsonArrayOfObjectsToCSV(jsonArray, MetricsUtil.DATE, MetricsUtil.COUNT), MediaType.valueOf(FileUtil.MIME_TYPE_CSV), "accounts.timeseries.csv");
}

/** MakeDataCount */

@GET
@Path("makeDataCount/{metric}")
public Response getMakeDataCountMetricCurrentMonth(@Context UriInfo uriInfo, @PathParam("metric") String metricSupplied, @QueryParam("country") String country, @QueryParam("parentAlias") String parentAlias) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,54 @@ public JsonArray uniqueDatasetDownloads(String yyyymm, Dataverse d) {

}

//Accounts

/*
*
* @param yyyymm Month in YYYY-MM format.
*/
public long accountsToMonth(String yyyymm) throws ParseException {
Query query = em.createNativeQuery(""
+ "select count(authenticateduser.id)\n"
+ "from authenticateduser\n"
+ "where authenticateduser.createdtime is not null\n"
+ "and date_trunc('month', createdtime) <= to_date('" + yyyymm + "','YYYY-MM');"
);
logger.log(Level.FINE, "Metric query: {0}", query);

return (long) query.getSingleResult();
}

/*
*
* @param days interval since the current date to list
* the number of user accounts created
*/
public long accountsPastDays(int days) {
Query query = em.createNativeQuery(""
+ "select count(id)\n"
+ "from authenticateduser\n"
+ "where authenticateduser.createdtime is not null\n"
+ "and authenticateduser.createdtime > current_date - interval '" + days + "' day;"
);
logger.log(Level.FINE, "Metric query: {0}", query);

return (long) query.getSingleResult();
}

public JsonArray accountsTimeSeries() {
Query query = em.createNativeQuery(""
+ "select distinct to_char(au.createdtime, 'YYYY-MM'), count(id)\n"
+ "from authenticateduser as au\n"
+ "where au.createdtime is not null\n"
+ "group by to_char(au.createdtime, 'YYYY-MM')\n"
+ "order by to_char(au.createdtime, 'YYYY-MM');");

logger.log(Level.FINE, "Metric query: {0}", query);
List<Object[]> results = query.getResultList();
return MetricsUtil.timeSeriesToJson(results);
}

//MDC


Expand Down
83 changes: 78 additions & 5 deletions src/test/java/edu/harvard/iq/dataverse/api/MetricsIT.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
package edu.harvard.iq.dataverse.api;

import io.restassured.RestAssured;
import io.restassured.response.Response;
import edu.harvard.iq.dataverse.metrics.MetricsUtil;
import static jakarta.ws.rs.core.Response.Status.BAD_REQUEST;
import static jakarta.ws.rs.core.Response.Status.OK;
import org.junit.jupiter.api.AfterAll;
import static org.junit.jupiter.api.Assertions.assertEquals;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import edu.harvard.iq.dataverse.metrics.MetricsUtil;
import edu.harvard.iq.dataverse.util.FileUtil;
import io.restassured.RestAssured;
import io.restassured.response.Response;
import jakarta.ws.rs.core.MediaType;

//TODO: These tests are fairly flawed as they don't actually add data to compare on.
//To improve these tests we should try adding data and see if the number DOESN'T
Expand Down Expand Up @@ -120,6 +122,54 @@ public void testGetDownloadsToMonth() {
response.then().assertThat()
.statusCode(BAD_REQUEST.getStatusCode());
}

@Test
public void testGetAccountsToMonth() {
String thismonth = MetricsUtil.getCurrentMonth();

Response response = UtilIT.metricsAccountsToMonth(thismonth, null);
String precache = response.prettyPrint();
response.then().assertThat()
.statusCode(OK.getStatusCode());

//Run each query twice and compare results to tests caching
response = UtilIT.metricsAccountsToMonth(thismonth, null);
String postcache = response.prettyPrint();
response.then().assertThat()
.statusCode(OK.getStatusCode());

assertEquals(precache, postcache);

//Test error when passing extra query params
response = UtilIT.metricsAccountsToMonth(thismonth, "dataLocation=local");
response.then().assertThat()
.statusCode(BAD_REQUEST.getStatusCode());
}

@Test
public void testGetAccountsTimeSeries() {
Response response = UtilIT.metricsAccountsTimeSeries(MediaType.APPLICATION_JSON, null);
String precache = response.prettyPrint();
response.then().assertThat()
.statusCode(OK.getStatusCode());

//Run each query twice and compare results to tests caching
response = UtilIT.metricsAccountsTimeSeries(MediaType.APPLICATION_JSON, null);
String postcache = response.prettyPrint();
response.then().assertThat()
.statusCode(OK.getStatusCode());

assertEquals(precache, postcache);

response = UtilIT.metricsAccountsTimeSeries(FileUtil.MIME_TYPE_CSV, null);
response.then().assertThat()
.statusCode(OK.getStatusCode());

//Test error when passing extra query params
response = UtilIT.metricsAccountsTimeSeries(MediaType.APPLICATION_JSON, "dataLocation=local");
response.then().assertThat()
.statusCode(BAD_REQUEST.getStatusCode());
}


@Test
Expand Down Expand Up @@ -214,6 +264,29 @@ public void testGetDownloadsPastDays() {
response.then().assertThat()
.statusCode(BAD_REQUEST.getStatusCode());
}

@Test
public void testGetAccountsPastDays() {
String days = "30";

Response response = UtilIT.metricsAccountsPastDays(days, null);
String precache = response.prettyPrint();
response.then().assertThat()
.statusCode(OK.getStatusCode());

//Run each query twice and compare results to tests caching
response = UtilIT.metricsAccountsPastDays(days, null);
String postcache = response.prettyPrint();
response.then().assertThat()
.statusCode(OK.getStatusCode());

assertEquals(precache, postcache);

//Test error when passing extra query params
response = UtilIT.metricsAccountsPastDays(days, "dataLocation=local");
response.then().assertThat()
.statusCode(BAD_REQUEST.getStatusCode());
}


@Test
Expand Down
30 changes: 29 additions & 1 deletion src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
import edu.harvard.iq.dataverse.util.StringUtil;

import java.util.Collections;
import static org.junit.jupiter.api.Assertions.assertEquals;

import static org.junit.jupiter.api.Assertions.*;

public class UtilIT {
Expand Down Expand Up @@ -2512,6 +2512,25 @@ static Response metricsDownloadsToMonth(String yyyymm, String queryParams) {
RequestSpecification requestSpecification = given();
return requestSpecification.get("/api/info/metrics/downloads/toMonth" + optionalYyyyMm + optionalQueryParams);
}

static Response metricsAccountsToMonth(String yyyymm, String queryParams) {
String optionalQueryParams = "";
if (queryParams != null) {
optionalQueryParams = "?" + queryParams;
}
RequestSpecification requestSpecification = given();
return requestSpecification.get("/api/info/metrics/accounts/toMonth/" + yyyymm + optionalQueryParams);
}

static Response metricsAccountsTimeSeries(String mediaType, String queryParams) {
String optionalQueryParams = "";
if (queryParams != null) {
optionalQueryParams = "?" + queryParams;
}
RequestSpecification requestSpecification = given();
requestSpecification.contentType(mediaType);
return requestSpecification.get("/api/info/metrics/accounts/monthly" + optionalQueryParams);
}

static Response metricsDataversesPastDays(String days, String queryParams) {
String optionalQueryParams = "";
Expand Down Expand Up @@ -2549,6 +2568,15 @@ static Response metricsDownloadsPastDays(String days, String queryParams) {
return requestSpecification.get("/api/info/metrics/downloads/pastDays/" + days + optionalQueryParams);
}

static Response metricsAccountsPastDays(String days, String queryParams) {
String optionalQueryParams = "";
if (queryParams != null) {
optionalQueryParams = "?" + queryParams;
}
RequestSpecification requestSpecification = given();
return requestSpecification.get("/api/info/metrics/accounts/pastDays/" + days + optionalQueryParams);
}

static Response metricsDataversesByCategory(String queryParams) {
String optionalQueryParams = "";
if (queryParams != null) {
Expand Down
Loading