Skip to content

Commit c256508

Browse files
alex-odysseusssuvorov-flshernaldourbinaoleg-odysseus
authored
Application usage statistics (#2387)
--------- Co-authored-by: sergey-suvorov <[email protected]> Co-authored-by: Hernaldo Urbina <[email protected]> Co-authored-by: oleg-odysseus <[email protected]>
1 parent 4ce6b3e commit c256508

11 files changed

+655
-2
lines changed

pom.xml

+1
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,7 @@
301301

302302
<audit.trail.enabled>false</audit.trail.enabled>
303303
<audit.trail.log.file>/tmp/atlas/audit/audit.log</audit.trail.log.file>
304+
<audit.trail.log.file.pattern>/tmp/atlas/audit/audit-%d{yyyy-MM-dd}-%i.log</audit.trail.log.file.pattern>
304305
<audit.trail.log.extraFile>/tmp/atlas/audit/audit-extra.log</audit.trail.log.extraFile>
305306
</properties>
306307

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
package org.ohdsi.webapi.statistic.controller;
2+
3+
import com.opencsv.CSVWriter;
4+
5+
import org.ohdsi.webapi.statistic.dto.AccessTrendDto;
6+
import org.ohdsi.webapi.statistic.dto.AccessTrendsDto;
7+
import org.ohdsi.webapi.statistic.dto.EndpointDto;
8+
import org.ohdsi.webapi.statistic.dto.SourceExecutionDto;
9+
import org.ohdsi.webapi.statistic.dto.SourceExecutionsDto;
10+
import org.ohdsi.webapi.statistic.service.StatisticService;
11+
import org.slf4j.Logger;
12+
import org.slf4j.LoggerFactory;
13+
import org.springframework.beans.factory.annotation.Autowired;
14+
import org.springframework.beans.factory.annotation.Value;
15+
import org.springframework.stereotype.Controller;
16+
17+
import javax.ws.rs.Consumes;
18+
import javax.ws.rs.InternalServerErrorException;
19+
import javax.ws.rs.POST;
20+
import javax.ws.rs.Path;
21+
import javax.ws.rs.Produces;
22+
import javax.ws.rs.core.MediaType;
23+
import javax.ws.rs.core.Response;
24+
25+
import java.io.ByteArrayOutputStream;
26+
import java.io.StringWriter;
27+
import java.time.LocalDate;
28+
import java.time.format.DateTimeFormatter;
29+
import java.util.ArrayList;
30+
import java.util.List;
31+
import java.util.stream.Collectors;
32+
33+
@Controller
34+
@Path("/statistic/")
35+
public class StatisticController {
36+
37+
private static final Logger log = LoggerFactory.getLogger(StatisticController.class);
38+
39+
private StatisticService service;
40+
41+
@Value("${audit.trail.enabled}")
42+
private boolean auditTrailEnabled;
43+
44+
public enum ResponseFormat {
45+
CSV, JSON
46+
}
47+
48+
private static final List<String[]> EXECUTION_STATISTICS_CSV_RESULT_HEADER = new ArrayList<String[]>() {{
49+
add(new String[]{"Date", "Source", "Execution Type"});
50+
}};
51+
52+
private static final List<String[]> EXECUTION_STATISTICS_CSV_RESULT_HEADER_WITH_USER_ID = new ArrayList<String[]>() {{
53+
add(new String[]{"Date", "Source", "Execution Type", "User ID"});
54+
}};
55+
56+
private static final List<String[]> ACCESS_TRENDS_CSV_RESULT_HEADER = new ArrayList<String[]>() {{
57+
add(new String[]{"Date", "Endpoint"});
58+
}};
59+
60+
private static final List<String[]> ACCESS_TRENDS_CSV_RESULT_HEADER_WITH_USER_ID = new ArrayList<String[]>() {{
61+
add(new String[]{"Date", "Endpoint", "User ID"});
62+
}};
63+
64+
@Autowired
65+
public StatisticController(StatisticService service) {
66+
this.service = service;
67+
}
68+
69+
/**
70+
* Returns execution statistics
71+
* @param executionStatisticsRequest - filter settings for statistics
72+
*/
73+
@POST
74+
@Path("/executions")
75+
@Produces(MediaType.APPLICATION_JSON)
76+
@Consumes(MediaType.APPLICATION_JSON)
77+
public Response executionStatistics(ExecutionStatisticsRequest executionStatisticsRequest) {
78+
if (!auditTrailEnabled) {
79+
throw new InternalServerErrorException("Audit Trail functionality should be enabled (audit.trail.enabled) to serve this endpoint");
80+
}
81+
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
82+
boolean showUserInformation = executionStatisticsRequest.isShowUserInformation();
83+
84+
SourceExecutionsDto sourceExecutions = service.getSourceExecutions(LocalDate.parse(executionStatisticsRequest.getStartDate(), formatter),
85+
LocalDate.parse(executionStatisticsRequest.getEndDate(), formatter), executionStatisticsRequest.getSourceKey(), showUserInformation);
86+
87+
if (ResponseFormat.CSV.equals(executionStatisticsRequest.getResponseFormat())) {
88+
return prepareExecutionResultResponse(sourceExecutions.getExecutions(), "execution_statistics.zip", showUserInformation);
89+
} else {
90+
return Response.ok(sourceExecutions).build();
91+
}
92+
}
93+
94+
/**
95+
* Returns access trends statistics
96+
* @param accessTrendsStatisticsRequest - filter settings for statistics
97+
*/
98+
@POST
99+
@Path("/accesstrends")
100+
@Produces(MediaType.APPLICATION_JSON)
101+
@Consumes(MediaType.APPLICATION_JSON)
102+
public Response accessStatistics(AccessTrendsStatisticsRequest accessTrendsStatisticsRequest) {
103+
if (!auditTrailEnabled) {
104+
throw new InternalServerErrorException("Audit Trail functionality should be enabled (audit.trail.enabled) to serve this endpoint");
105+
}
106+
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
107+
boolean showUserInformation = accessTrendsStatisticsRequest.isShowUserInformation();
108+
109+
AccessTrendsDto trends = service.getAccessTrends(LocalDate.parse(accessTrendsStatisticsRequest.getStartDate(), formatter),
110+
LocalDate.parse(accessTrendsStatisticsRequest.getEndDate(), formatter), accessTrendsStatisticsRequest.getEndpoints(), showUserInformation);
111+
112+
if (ResponseFormat.CSV.equals(accessTrendsStatisticsRequest.getResponseFormat())) {
113+
return prepareAccessTrendsResponse(trends.getTrends(), "execution_trends.zip", showUserInformation);
114+
} else {
115+
return Response.ok(trends).build();
116+
}
117+
}
118+
119+
private Response prepareExecutionResultResponse(List<SourceExecutionDto> executions, String filename, boolean showUserInformation) {
120+
List<String[]> data = executions.stream()
121+
.map(execution -> showUserInformation
122+
? new String[]{execution.getExecutionDate(), execution.getSourceName(), execution.getExecutionName(), execution.getUserId()}
123+
: new String[]{execution.getExecutionDate(), execution.getSourceName(), execution.getExecutionName()}
124+
)
125+
.collect(Collectors.toList());
126+
return prepareResponse(data, filename, showUserInformation ? EXECUTION_STATISTICS_CSV_RESULT_HEADER_WITH_USER_ID : EXECUTION_STATISTICS_CSV_RESULT_HEADER);
127+
}
128+
129+
private Response prepareAccessTrendsResponse(List<AccessTrendDto> trends, String filename, boolean showUserInformation) {
130+
List<String[]> data = trends.stream()
131+
.map(trend -> showUserInformation
132+
? new String[]{trend.getExecutionDate().toString(), trend.getEndpointName(), trend.getUserID()}
133+
: new String[]{trend.getExecutionDate().toString(), trend.getEndpointName()}
134+
)
135+
.collect(Collectors.toList());
136+
return prepareResponse(data, filename, showUserInformation ? ACCESS_TRENDS_CSV_RESULT_HEADER_WITH_USER_ID : ACCESS_TRENDS_CSV_RESULT_HEADER);
137+
}
138+
139+
private Response prepareResponse(List<String[]> data, String filename, List<String[]> header) {
140+
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
141+
StringWriter sw = new StringWriter();
142+
CSVWriter csvWriter = new CSVWriter(sw, ',', CSVWriter.DEFAULT_QUOTE_CHARACTER, CSVWriter.DEFAULT_ESCAPE_CHARACTER)) {
143+
144+
csvWriter.writeAll(header);
145+
csvWriter.writeAll(data);
146+
csvWriter.flush();
147+
baos.write(sw.getBuffer().toString().getBytes());
148+
149+
return Response
150+
.ok(baos)
151+
.type(MediaType.APPLICATION_OCTET_STREAM)
152+
.header("Content-Disposition", String.format("attachment; filename=\"%s\"", filename))
153+
.build();
154+
} catch (Exception ex) {
155+
log.error("An error occurred while building a response");
156+
throw new RuntimeException(ex);
157+
}
158+
}
159+
160+
public static final class ExecutionStatisticsRequest {
161+
// Format - yyyy-MM-dd
162+
String startDate;
163+
// Format - yyyy-MM-dd
164+
String endDate;
165+
String sourceKey;
166+
ResponseFormat responseFormat;
167+
boolean showUserInformation;
168+
169+
public String getStartDate() {
170+
return startDate;
171+
}
172+
173+
public void setStartDate(String startDate) {
174+
this.startDate = startDate;
175+
}
176+
177+
public String getEndDate() {
178+
return endDate;
179+
}
180+
181+
public void setEndDate(String endDate) {
182+
this.endDate = endDate;
183+
}
184+
185+
public String getSourceKey() {
186+
return sourceKey;
187+
}
188+
189+
public void setSourceKey(String sourceKey) {
190+
this.sourceKey = sourceKey;
191+
}
192+
193+
public ResponseFormat getResponseFormat() {
194+
return responseFormat;
195+
}
196+
197+
public void setResponseFormat(ResponseFormat responseFormat) {
198+
this.responseFormat = responseFormat;
199+
}
200+
201+
public boolean isShowUserInformation() {
202+
return showUserInformation;
203+
}
204+
205+
public void setShowUserInformation(boolean showUserInformation) {
206+
this.showUserInformation = showUserInformation;
207+
}
208+
}
209+
210+
public static final class AccessTrendsStatisticsRequest {
211+
// Format - yyyy-MM-dd
212+
String startDate;
213+
// Format - yyyy-MM-dd
214+
String endDate;
215+
// Key - method (POST, GET)
216+
// Value - endpoint ("{}" can be used as a placeholder, will be converted to ".*" in regular expression)
217+
List<EndpointDto> endpoints;
218+
ResponseFormat responseFormat;
219+
boolean showUserInformation;
220+
221+
public String getStartDate() {
222+
return startDate;
223+
}
224+
225+
public void setStartDate(String startDate) {
226+
this.startDate = startDate;
227+
}
228+
229+
public String getEndDate() {
230+
return endDate;
231+
}
232+
233+
public void setEndDate(String endDate) {
234+
this.endDate = endDate;
235+
}
236+
237+
public List<EndpointDto> getEndpoints() {
238+
return endpoints;
239+
}
240+
241+
public void setEndpoints(List<EndpointDto> endpoints) {
242+
this.endpoints = endpoints;
243+
}
244+
245+
public ResponseFormat getResponseFormat() {
246+
return responseFormat;
247+
}
248+
249+
public void setResponseFormat(ResponseFormat responseFormat) {
250+
this.responseFormat = responseFormat;
251+
}
252+
253+
public boolean isShowUserInformation() {
254+
return showUserInformation;
255+
}
256+
257+
public void setShowUserInformation(boolean showUserInformation) {
258+
this.showUserInformation = showUserInformation;
259+
}
260+
}
261+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package org.ohdsi.webapi.statistic.dto;
2+
3+
public class AccessTrendDto {
4+
private String endpointName;
5+
private String executionDate;
6+
private String userID;
7+
8+
public AccessTrendDto(String endpointName, String executionDate, String userID) {
9+
this.endpointName = endpointName;
10+
this.executionDate = executionDate;
11+
this.userID = userID;
12+
}
13+
14+
public String getEndpointName() {
15+
return endpointName;
16+
}
17+
18+
public void setEndpointName(String endpointName) {
19+
this.endpointName = endpointName;
20+
}
21+
22+
public String getExecutionDate() {
23+
return executionDate;
24+
}
25+
26+
public void setExecutionDate(String executionDate) {
27+
this.executionDate = executionDate;
28+
}
29+
30+
public String getUserID() {
31+
return userID;
32+
}
33+
34+
public void setUserID(String userID) {
35+
this.userID = userID;
36+
}
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package org.ohdsi.webapi.statistic.dto;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
6+
public class AccessTrendsDto {
7+
private List<AccessTrendDto> trends = new ArrayList<>();
8+
9+
public AccessTrendsDto(List<AccessTrendDto> trends) {
10+
this.trends = trends;
11+
}
12+
13+
public List<AccessTrendDto> getTrends() {
14+
return trends;
15+
}
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package org.ohdsi.webapi.statistic.dto;
2+
3+
public class EndpointDto {
4+
String method;
5+
String urlPattern;
6+
String userId;
7+
8+
public String getMethod() {
9+
return method;
10+
}
11+
12+
public void setMethod(String method) {
13+
this.method = method;
14+
}
15+
16+
public String getUrlPattern() {
17+
return urlPattern;
18+
}
19+
20+
public void setUrlPattern(String urlPattern) {
21+
this.urlPattern = urlPattern;
22+
}
23+
24+
public String getUserId() {
25+
return userId;
26+
}
27+
28+
public void setUserId(String userId) {
29+
this.userId = userId;
30+
}
31+
}
32+

0 commit comments

Comments
 (0)