diff --git a/application/pom.xml b/application/pom.xml
index 81e0b9ed6545187d17d027b84c551ca3e8edc712..3eed9228a842f06d9b2fc50ff2723f86873ffb51 100644
--- a/application/pom.xml
+++ b/application/pom.xml
@@ -12,10 +12,6 @@
1.0.9
-
- com.fasterxml.jackson.dataformat
- jackson-dataformat-csv
-
com.openhtmltopdf
diff --git a/pom.xml b/pom.xml
index 5240e0bfc950ace5cd0469f4b1aa19f5b7bf4b54..3e9de2e9c6c0ebf665450f50be5bfd4eb09dd99f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
1.4.10-SNAPSHOT
pom
- 0.6.6
+ 0.6.7
2.22.1
3.8.1
11
@@ -84,6 +84,10 @@
io.quarkus
quarkus-resteasy-jackson
+
+ com.fasterxml.jackson.dataformat
+ jackson-dataformat-csv
+
com.fasterxml.jackson.datatype
diff --git a/portal/src/main/java/org/eclipsefoundation/eclipsedb/dto/EventLogSessions.java b/portal/src/main/java/org/eclipsefoundation/eclipsedb/dto/EventLogSessions.java
new file mode 100644
index 0000000000000000000000000000000000000000..8e2c23eccdf633d0930fde19320b169a6ef7ff10
--- /dev/null
+++ b/portal/src/main/java/org/eclipsefoundation/eclipsedb/dto/EventLogSessions.java
@@ -0,0 +1,163 @@
+/**
+ * Copyright (c) 2022 Eclipse Foundation
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * Author: Martin Lowe
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipsefoundation.eclipsedb.dto;
+
+import java.sql.Date;
+import java.time.LocalDateTime;
+import java.util.Calendar;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.ws.rs.core.MultivaluedMap;
+
+import org.eclipsefoundation.eclipsedb.namespaces.EclipseDBParameterNames;
+import org.eclipsefoundation.persistence.dto.BareNode;
+import org.eclipsefoundation.persistence.dto.filter.DtoFilter;
+import org.eclipsefoundation.persistence.model.DtoTable;
+import org.eclipsefoundation.persistence.model.ParameterizedCallStatement;
+import org.eclipsefoundation.persistence.model.ParameterizedSQLStatement;
+import org.eclipsefoundation.persistence.model.ParameterizedSQLStatementBuilder;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+
+@Entity
+public class EventLogSessions extends BareNode {
+ public static final DtoTable TABLE = new DtoTable(EventLogSessions.class, "els");
+
+ @Id
+ @Column(name = "pk1")
+ private int organizationId;
+ private int uniqueHits;
+ private int totalHits;
+ private LocalDateTime earliestHit;
+ private LocalDateTime latestHit;
+
+ @JsonIgnore
+ @Override
+ public Object getId() {
+ return getOrganizationId();
+ }
+
+ /**
+ * @return the organizationId
+ */
+ public int getOrganizationId() {
+ return organizationId;
+ }
+
+ /**
+ * @param organizationId the organizationId to set
+ */
+ public void setOrganizationId(int organizationId) {
+ this.organizationId = organizationId;
+ }
+
+ /**
+ * @return the uniqueHits
+ */
+ public int getUniqueHits() {
+ return uniqueHits;
+ }
+
+ /**
+ * @param uniqueHits the uniqueHits to set
+ */
+ public void setUniqueHits(int uniqueHits) {
+ this.uniqueHits = uniqueHits;
+ }
+
+ /**
+ * @return the totalHits
+ */
+ public int getTotalHits() {
+ return totalHits;
+ }
+
+ /**
+ * @param totalHits the totalHits to set
+ */
+ public void setTotalHits(int totalHits) {
+ this.totalHits = totalHits;
+ }
+
+ /**
+ * @return the earliestHit
+ */
+ public LocalDateTime getEarliestHit() {
+ return earliestHit;
+ }
+
+ /**
+ * @param earliestHit the earliestHit to set
+ */
+ public void setEarliestHit(LocalDateTime earliestHit) {
+ this.earliestHit = earliestHit;
+ }
+
+ /**
+ * @return the latestHit
+ */
+ public LocalDateTime getLatestHit() {
+ return latestHit;
+ }
+
+ /**
+ * @param latestHit the latestHit to set
+ */
+ public void setLatestHit(LocalDateTime latestHit) {
+ this.latestHit = latestHit;
+ }
+
+ @Singleton
+ public static class SysEventLogFilter implements DtoFilter {
+ @Inject
+ ParameterizedSQLStatementBuilder builder;
+
+ @Override
+ public ParameterizedSQLStatement getFilters(MultivaluedMap params, boolean isRoot) {
+ ParameterizedCallStatement stmt = builder.buildCallStatement(TABLE);
+ stmt.setCallStatement("sp_evtlog_sessions");
+
+ // since check
+ String since = params.getFirst(EclipseDBParameterNames.SINCE.getName());
+ if (since != null) {
+ stmt.addClause(new ParameterizedSQLStatement.Clause("", new Object[] { since }));
+ } else {
+ // default to 1 year in the past
+ Calendar c = Calendar.getInstance();
+ c.set(Calendar.YEAR, c.get(Calendar.YEAR) - 1);
+ stmt
+ .addClause(new ParameterizedSQLStatement.Clause("",
+ new Object[] { new Date(c.toInstant().getNano()) }));
+ }
+
+ // servername check
+ String serverName = params.getFirst(EclipseDBParameterNames.SERVER_NAME.getName());
+ if (serverName != null) {
+ stmt.addClause(new ParameterizedSQLStatement.Clause("", new Object[] { serverName }));
+ } else {
+ stmt.addClause(new ParameterizedSQLStatement.Clause("", new Object[] { "" }));
+ }
+
+ return stmt;
+ }
+
+ @Override
+ public Class getType() {
+ return EventLogSessions.class;
+ }
+ }
+
+}
diff --git a/portal/src/main/java/org/eclipsefoundation/eclipsedb/namespaces/EclipseDBParameterNames.java b/portal/src/main/java/org/eclipsefoundation/eclipsedb/namespaces/EclipseDBParameterNames.java
index 0a5b60473691eac3120be9ebe02da25046721adc..5a7adebb9bb2249fc89dba84bd5f64cda6f7fc0e 100644
--- a/portal/src/main/java/org/eclipsefoundation/eclipsedb/namespaces/EclipseDBParameterNames.java
+++ b/portal/src/main/java/org/eclipsefoundation/eclipsedb/namespaces/EclipseDBParameterNames.java
@@ -15,9 +15,12 @@ public class EclipseDBParameterNames implements UrlParameterNamespace {
public static final UrlParameter PRODUCT_ID = new UrlParameter("product_id");
public static final UrlParameter ORGANIZATION_ID = new UrlParameter("organization_id");
public static final UrlParameter USERNAME = new UrlParameter("username");
+ public static final UrlParameter SERVER_NAME = new UrlParameter("server_name");
+ public static final UrlParameter SINCE = new UrlParameter("since");
+ public static final UrlParameter LOG_TABLE = new UrlParameter("log_table");
private static final List params = Collections
- .unmodifiableList(Arrays.asList(PRODUCT_ID, ORGANIZATION_ID, USERNAME));
+ .unmodifiableList(Arrays.asList(PRODUCT_ID, ORGANIZATION_ID, USERNAME, SERVER_NAME, SINCE, LOG_TABLE));
@Override
public List getParameters() {
diff --git a/portal/src/main/java/org/eclipsefoundation/membership/portal/model/reports/MemberAdoptionReportItem.java b/portal/src/main/java/org/eclipsefoundation/membership/portal/model/reports/MemberAdoptionReportItem.java
new file mode 100644
index 0000000000000000000000000000000000000000..92bff23cbd2007c158f246df8ef2db373f9238e0
--- /dev/null
+++ b/portal/src/main/java/org/eclipsefoundation/membership/portal/model/reports/MemberAdoptionReportItem.java
@@ -0,0 +1,44 @@
+/**
+ * Copyright (c) 2022 Eclipse Foundation
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * Author: Martin Lowe
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipsefoundation.membership.portal.model.reports;
+
+import java.time.LocalDateTime;
+
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
+import com.google.auto.value.AutoValue;
+
+@AutoValue
+@JsonDeserialize(builder = AutoValue_MemberAdoptionReportItem.Builder.class)
+public abstract class MemberAdoptionReportItem {
+
+ public abstract int getOrganizationId();
+ public abstract String getOrganizationName();
+ public abstract LocalDateTime getFirstHit();
+ public abstract int getTotalHits();
+ public abstract int getUniqueHits();
+
+ public static Builder builder() {
+ return new AutoValue_MemberAdoptionReportItem.Builder();
+ }
+
+ @AutoValue.Builder
+ @JsonPOJOBuilder(withPrefix = "set")
+ public abstract static class Builder {
+ public abstract Builder setOrganizationId(int orgId);
+ public abstract Builder setOrganizationName(String orgName);
+ public abstract Builder setFirstHit(LocalDateTime firstHit);
+ public abstract Builder setTotalHits(int totalHits);
+ public abstract Builder setUniqueHits(int uniqueHits);
+ public abstract MemberAdoptionReportItem build();
+ }
+}
diff --git a/portal/src/main/java/org/eclipsefoundation/membership/portal/resources/AbstractRESTResource.java b/portal/src/main/java/org/eclipsefoundation/membership/portal/resources/AbstractRESTResource.java
index a2c8cca90e329d2af4738dea449d6c6069021b65..f193bd1b384a29e95e654971557c1c7f11a07d54 100644
--- a/portal/src/main/java/org/eclipsefoundation/membership/portal/resources/AbstractRESTResource.java
+++ b/portal/src/main/java/org/eclipsefoundation/membership/portal/resources/AbstractRESTResource.java
@@ -65,8 +65,6 @@ public abstract class AbstractRESTResource {
@Inject
RequestWrapper wrap;
- @Inject
- ResponseHelper responseBuider;
@Inject
CSRFHelper csrfHelper;
diff --git a/portal/src/main/java/org/eclipsefoundation/membership/portal/resources/ReportsResource.java b/portal/src/main/java/org/eclipsefoundation/membership/portal/resources/ReportsResource.java
new file mode 100644
index 0000000000000000000000000000000000000000..627daae5ae5038809b2c639a65e6475abec5ce6e
--- /dev/null
+++ b/portal/src/main/java/org/eclipsefoundation/membership/portal/resources/ReportsResource.java
@@ -0,0 +1,98 @@
+/**
+ * Copyright (c) 2022 Eclipse Foundation
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * Author: Martin Lowe
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipsefoundation.membership.portal.resources;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import javax.inject.Inject;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+import org.eclipsefoundation.eclipsedb.dto.EventLogSessions;
+import org.eclipsefoundation.eclipsedb.namespaces.EclipseDBParameterNames;
+import org.eclipsefoundation.membership.portal.model.reports.MemberAdoptionReportItem;
+import org.eclipsefoundation.persistence.dao.impl.DefaultHibernateDao;
+import org.eclipsefoundation.persistence.model.RDBMSQuery;
+import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.databind.SequenceWriter;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.dataformat.csv.CsvMapper;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+
+import io.quarkus.security.Authenticated;
+
+@Path("reports")
+@Authenticated
+public class ReportsResource extends AbstractRESTResource {
+ public static final Logger LOGGER = LoggerFactory.getLogger(ReportsResource.class);
+
+ @ConfigProperty(name = "eclipse.security.reports.allowed-users")
+ List allowedUsers;
+
+ @Inject
+ DefaultHibernateDao dao;
+
+ private static final CsvMapper CSV_MAPPER = (CsvMapper) new CsvMapper()
+ .registerModule(new JavaTimeModule())
+ .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
+
+ @GET
+ @Path("adoption_rate")
+ @Produces(MediaType.TEXT_PLAIN)
+ public Response ok() throws IOException {
+ // check that the user is in the allowed user list
+ if (!allowedUsers.contains(ident.getPrincipal().getName())) {
+ return Response.status(403).build();
+ }
+ // set filters for db query, matching on events for current host
+ MultivaluedMap params = new MultivaluedMapImpl<>();
+ params.add(EclipseDBParameterNames.SERVER_NAME.getName(), request.getServerName());
+ RDBMSQuery q = new RDBMSQuery<>(wrap, filters.get(EventLogSessions.class), params);
+
+ try (StringWriter sw = new StringWriter()) {
+ // create the CSV writer for membership forms
+ SequenceWriter writer = CSV_MAPPER
+ .writer(CSV_MAPPER.schemaFor(MemberAdoptionReportItem.class).withHeader())
+ .writeValues(sw);
+ // retrieve the membership form for the current state and write to output
+ writer.writeAll(adapt(dao.get(q)));
+ return Response.ok(sw.toString()).build();
+ }
+ }
+
+ private List adapt(List origin) {
+ return origin
+ .stream()
+ .map(o -> MemberAdoptionReportItem
+ .builder()
+ .setOrganizationId(o.getOrganizationId())
+ .setFirstHit(o.getEarliestHit())
+ .setTotalHits(o.getTotalHits())
+ .setUniqueHits(o.getUniqueHits())
+ .setOrganizationName(
+ orgService.getByID(Integer.toString(o.getOrganizationId())).get().getName())
+ .build())
+ .collect(Collectors.toList());
+ }
+
+}
diff --git a/portal/src/main/resources/application.properties b/portal/src/main/resources/application.properties
index 379f529b62ec197732f1a82273fcb8aa33064a4b..bc235c6c6c8123c07141ab17672fa9cfed66abdd 100644
--- a/portal/src/main/resources/application.properties
+++ b/portal/src/main/resources/application.properties
@@ -75,6 +75,7 @@ quarkus.resteasy.path=/api
eclipse.image-store.file-path=/app/organization/imagestore
eclipse.image-store.web-root=${eclipse.app.base-url}/images/organization/
eclipse.api.application-group=eclipse
+eclipse.security.reports.allowed-users=zfazli,webdev
security.csrf.enabled=true
security.csrf.enabled.distributed-mode=true
eclipse.image-store.default-image-url=https://www.eclipse.org/eclipse.org-common/themes/solstice/public/images/logo/eclipse-foundation-white-orange.svg