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

O3-1494: Added REST APIs for O3 Report Admin pages #33

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

pwargulak
Copy link

Summary

This change adds REST APIs used on O3 Admin pages, including:

  • ReportDesign resource
  • ScheduledReport pseudo, read-only resource which returns scheduled ReportRequests
  • extends ReportingRestController with endpoints used to request a report execution, schedule report, cancel, preserve and download

See the related issue.

Issue

https://openmrs.atlassian.net/browse/O3-1494

omod/pom.xml Outdated
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.openmrs.module</groupId>
<artifactId>uiframework-api</artifactId>
Copy link
Member

@dkayiwa dkayiwa Feb 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you intentionally forcing O3 distributions to have to load the uiframework module? It would have been better if we can avoid this. 😊

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, not sure why you need this?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll remove it, it was needed for FileDownload class

@@ -13,11 +13,40 @@
*/
package org.openmrs.module.reportingrest.web.controller;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.commons.logging.Log;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}
}

@RequestMapping(value = "/preserveReport", method = RequestMethod.POST)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How different is preserveReport from saveReport?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this not be done as an operation on the existing reportRequest endpoint, to preserve that report request (eg. changing the status)?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dkayiwa I'll change it to saveReport, preserve came from UI.

@mseaton Generally, this is a quick migration of legacy UI's org.openmrs.module.reportingui.fragment.controller.ReportStatusFragmentController, this directly relates to actions on UI.

The existing ReportRequestResource is a CrudResource - should I just add an endpoint method there?
Or do you mean, that if there is an update call, which changes request's status from COMPLETED to SAVED, then it should run the ReportService.savereport()?

/**
* {@link Resource} for {@link ReportDesign}s, supporting standard CRUD operations
*/
@Resource(name = RestConstants.VERSION_1 + ReportingRestController.REPORTING_REST_NAMESPACE + "/designs",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about reportDesign instead of designs?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. Please follow existing naming conventions. reportDesign is appropriate.

@@ -0,0 +1,111 @@
/**
* The contents of this file are subject to the OpenMRS Public License
* Version 1.0 (the "License"); you may not use this file except in
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

private FileDownload processAndDownloadReport(String reportRequestUuid, ReportService reportService) {
ReportRequest reportRequest = reportService.getReportRequestByUuid(reportRequestUuid);
if (reportRequest == null) {
throw new IllegalArgumentException("Report request not found");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It helps to include the report request uuid in the error message.

try {
convertedObject = DateUtils.parseDate((String) value, "MM/dd/yyyy");
} catch (ParseException e) {
LOGGER.error("Error while parsing date");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we pass the exception as the second parameter of the above?

throw new IllegalArgumentException("reportDefinitionUuid parameter is required");
}

ReportDefinition reportDefinition = getReportDefinitionService().getDefinitionByUuid(reportDefinitionUuid);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens when no report definition is found with the given uuid?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've changed it to return an empty result, as "no report definition found, then there are no report requests related to that not existing report definition in the system", previously it would return all requests.
But I guess, an exception/404 would be an option too.


if (type.equals(Date.class)) {
try {
convertedObject = DateUtils.parseDate((String) value, "MM/dd/yyyy");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use org.openmrs.module.webservices.rest.web.ConversionUtil.convert(value, Date.class)

}
}

if (type.equals(Integer.class)) {
Copy link
Member

@dkayiwa dkayiwa Feb 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use ConversionUtil.convert?

convertedObject = Integer.valueOf((String) value);
}

if (type.equals(Location.class)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use ConversionUtil.convert?

@Override
public DelegatingResourceDescription getRepresentationDescription(Representation representation) {
final DelegatingResourceDescription description = new DelegatingResourceDescription();
description.addProperty("reportDefinition", Representation.FULL);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the only representation that you support?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we didn't need anything else to support the O3 UI.

@pwargulak pwargulak marked this pull request as draft February 21, 2024 16:32
@pwargulak
Copy link
Author

Changed to DRAFT, becuase it's not adapted to review changes in openmrs/openmrs-module-reporting#249

omod/pom.xml Outdated
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.openmrs.module</groupId>
<artifactId>uiframework-api</artifactId>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, not sure why you need this?


@RequestMapping(value = "/runReport", method = RequestMethod.POST)
@ResponseStatus(HttpStatus.OK)
public void runReport(@RequestBody RunReportRequest runReportRequest) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain what this endpoint does that the existing reportRequest endpoint does not do? Can we improve what we have if needed or do we really need this new endpoint?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Current ReportRequestResource just saves a ReportRequest in DB, as-is.
The runReport creates ReportRequest and adds it to execution queue - maybe better name would be queueReport as from ReportService.queueReport.

Generally, this is migration of org.openmrs.module.reportingui.page.controller.RunReportPageController.

Would it make sense for ReportRequestResource to queue every new ReportRequest?
We were hesitant to modify anything already in use.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pwargulak - It's been a long time since I've looked at this, but my first reaction is that it definitely would make the most sense for the ReportRequestResource to queue every new ReportRequest. So basically:

  • If the report request does not already exist with the given uuid, call reportService.queueReport(ReportRequest), otherwise calls reportService.saveReportRequest(ReportRequest);


@RequestMapping(value = "/cancelReport", method = RequestMethod.DELETE)
@ResponseStatus(HttpStatus.OK)
public void cancelReport(@RequestParam String reportRequestUuid) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be handled by the DELETE operation on the existing reportRequest endpoint, maybe based on certain parameters that dictate whether the goal is to cancel or purge the report request? Or maybe as a POST or PUT operation on the reportRequest endpoint that aims to change the status to CANCELLED?

}
}

@RequestMapping(value = "/preserveReport", method = RequestMethod.POST)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this not be done as an operation on the existing reportRequest endpoint, to preserve that report request (eg. changing the status)?

reportService.saveReport(report, StringUtils.EMPTY);
}
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of these operations that take in a reportRequestUuid seem like they might be better done within the reportRequest resource / endpoint

@Override
protected PageableResult doSearch(RequestContext context) {
String statusesGroup = context.getParameter("statusesGroup");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't love this hard-coding like this with these statusGroups. Why is this needed?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It adds groups to ReportRequest.Status:

  • ran - all statuses which mean a report was executed
  • processing - all statuses which mean report is being executed
  • scheduled - all statuses which mean it's an execution according to schedule

We could change the statusesGroup parameter to status and allow comma-separated list here, moving the grouping of Status to the FE.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pwargulak - I think that would be preferable. Basically, allow the endpoint to accept multiple values for status and interpret this as an OR condition. I wouldn't accept a csv input, rather I would just get the array of parameter values out of the request for the status parameter name, and treat this as an "or".

}

return new AlreadyPaged<ReportRequest>(context, reportRequests, reportRequestsDTO.getReportRequestCount() > (long) pageNumber * pageSize, reportRequestsDTO.getReportRequestCount());
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand the placement of this if/then block. Let's just do things consistently and always use the paging endpoint. If no paging parameters are supplied, it should default to returning all like it currently does.

import java.util.Comparator;
import java.util.List;

@Resource(name = RestConstants.VERSION_1 + ReportingRestController.REPORTING_REST_NAMESPACE + "/scheduledReport",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't this class be handled by hitting the reportRequest endpoint with appropriate parameters to only return scheduled reports? Or can't we make it do so?

Copy link
Author

@pwargulak pwargulak Feb 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically, this is reportDefinition left join reportRequest - one request.
With existing APIs, we would need to REST GET reportDefinition and reportRequest then join them on FE side.
Used on a page which shows a table of all Reports with their related execution schedule.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. OK. Couldn't we accomplish this without adding the ScheduledReport class, and just return a List where each simple object contains a reportDefinition -> ReportDefinition and scheduledReports -> List<ReportRequest> object?

Then, in the frontend, you'd hit this endpoint and it would give you all available report definitions, and any scheduled reports for them, or an empty list if none.

@@ -15,6 +15,7 @@
<require_module version="${serializationxstreamVersion}">org.openmrs.module.serialization.xstream</require_module>
<require_module version="${reportingVersion}">org.openmrs.module.reporting</require_module>
<require_module version="${webservicesRestVersion}">org.openmrs.module.webservices.rest</require_module>
<require_module version="${uiframeworkModuleVersion}">org.openmrs.module.uiframework</require_module>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, this should not require uiframework. This needs to be removed.

pom.xml Outdated
<artifactId>uiframework-api</artifactId>
<version>${uiframeworkModuleVersion}</version>
<scope>provided</scope>
</dependency>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This dependency needs to be removed.

@pwargulak
Copy link
Author

@mseaton Just want to confirm with you, that the ReportRequest REST API should perform some additional actions based on ReportRequest's status change, instead having separate endpoints for invoking the actions.

For example,
preserveReport should be removed and the functionality should be invoked when ReportRequest's status is updated to SAVED via REST API.

@mseaton
Copy link
Member

mseaton commented Mar 14, 2024

@mseaton Just want to confirm with you, that the ReportRequest REST API should perform some additional actions based on ReportRequest's status change, instead having separate endpoints for invoking the actions.

For example, preserveReport should be removed and the functionality should be invoked when ReportRequest's status is updated to SAVED via REST API.

I'm fine either way, but would be good if we could make our existing endpoints behave as we think they should.

@druchniewicz
Copy link

@mseaton @dkayiwa @ibacher @djazayeri

we would like to get back to this and make the code ready for merging. We applied previous remarks from code review. Can you take a look at this once again?
This PR and openmrs/openmrs-module-reporting#251 are related to each other and together constitute backend for Reports page in O3 UI - openmrs/openmrs-esm-admin-tools#45 (this UI PR has to be adapted after merging backend PRs)
CC: @pwargulak

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants