Skip to content

Commit

Permalink
IQSS#10177 API Metrics : add user accounts number
Browse files Browse the repository at this point in the history
  • Loading branch information
stevenferey committed Jan 5, 2024
1 parent 5cbb895 commit 9926a8b
Show file tree
Hide file tree
Showing 6 changed files with 259 additions and 11 deletions.
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 instalation,all,y,as of now/totals,
/api/info/metrics/accounts/toMonth/{yyyy-MM},count,json,Dataverse instalation,all,y,cumulative up to month specified,
/api/info/metrics/accounts/pastDays/{n},count,json,Dataverse instalation,all,y,aggregate count for past n days,
/api/info/metrics/accounts/monthly,"date, count","json, csv",Dataverse instalation,all,y,monthly cumulative timeseries from first date of first entry to now,

This comment has been minimized.

Copy link
@jeromeroucou

jeromeroucou Jan 17, 2024

Typo : instalation need to be replace by installation and delete , at the end of line


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 @@ -2491,6 +2491,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 @@ -2528,6 +2547,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

0 comments on commit 9926a8b

Please sign in to comment.