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

Add pagination to bugsnag and generic api controller #72

Merged
merged 23 commits into from
Oct 12, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
68168ba
refactor(pagination): add pagination to bugsnag and generic api contr…
landonreed Sep 10, 2020
bdfc6a7
docs(swagger): update API docs
landonreed Sep 22, 2020
f6c361b
refactor(pagination): refactor ResponseList to simplify construction
landonreed Sep 28, 2020
767ee7e
docs(swagger): update swagger API docs
landonreed Sep 28, 2020
00dc9cd
refactor(bugsnag): clean up code to construct event summaries
landonreed Sep 28, 2020
efab948
Merge branch 'dev' into add-pagination
landonreed Sep 28, 2020
5c1bfcf
refactor(redirect): update HttpUtils refactored method name
landonreed Sep 28, 2020
7795bd1
test: fix broken e2e test
landonreed Sep 28, 2020
02ea4b8
refactor: add no-arg constructor for ResponseList
landonreed Sep 28, 2020
7288d2b
refactor: fix test and remove unused imports
landonreed Sep 28, 2020
a2146c3
refactor(api-key): replace customerId with tag for userId
landonreed Sep 29, 2020
755a900
refactor(pagination): change page param to offset
landonreed Sep 29, 2020
99d364b
refactor(pagination): change remaining page -> offset instances
landonreed Sep 29, 2020
af9ad88
docs(pagination): update swagger for pagination
landonreed Sep 29, 2020
affc874
refactor(HttpUtils): refactor getQueryParamFromRequest (params, javad…
landonreed Sep 30, 2020
530f20e
refactor(HttpUtils): fix max int value check
landonreed Sep 30, 2020
7e59e7a
refactor(pagination): update swagger descriptions; add ResponseList#c…
landonreed Oct 2, 2020
0c800e4
build(deps): use ibi-group fork of spark-swagger
landonreed Oct 2, 2020
44c6c77
Revert "build(deps): use ibi-group fork of spark-swagger"
landonreed Oct 12, 2020
7c1e133
docs(swagger): update API docs
landonreed Oct 12, 2020
d6e236d
Merge branch 'dev' into add-pagination
landonreed Oct 12, 2020
ace5772
test: fix trip history persistence compilation error
landonreed Oct 12, 2020
692c66a
refactor(ApiGatewayUtils): use fluent method for inlining timeout call
landonreed Oct 12, 2020
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
Expand Up @@ -9,6 +9,7 @@
import org.eclipse.jetty.http.HttpStatus;
import org.opentripplanner.middleware.auth.Auth0Connection;
import org.opentripplanner.middleware.auth.Auth0UserProfile;
import org.opentripplanner.middleware.controllers.response.ResponseList;
import org.opentripplanner.middleware.models.Model;
import org.opentripplanner.middleware.models.OtpUser;
import org.opentripplanner.middleware.persistence.TypedPersistence;
Expand Down Expand Up @@ -119,7 +120,7 @@ protected void buildEndpoint(ApiEndpoint baseEndpoint) {
// Note: there exists a method withResponseAsCollection, but unlike what its name suggests,
// it does exactly the same as .withResponseType and does not generate a return type array.
// See issue https://github.com/manusant/spark-swagger/issues/12.
.withResponseType(Array.newInstance(clazz, 0).getClass()),
.withResponseType(ResponseList.class),
landonreed marked this conversation as resolved.
Show resolved Hide resolved
this::getMany, JsonUtils::toJson
)

Expand Down Expand Up @@ -172,23 +173,26 @@ protected void buildEndpoint(ApiEndpoint baseEndpoint) {
*/
// FIXME Maybe better if the user check (and filtering) was done in a pre hook?
// FIXME Will require further granularity for admin
private List<T> getMany(Request req, Response res) {

private ResponseList<T> getMany(Request req, Response res) {
List<T> list;
int limit = Integer.parseInt(req.queryParamOrDefault("limit", "10"));
int page = Integer.parseInt(req.queryParamOrDefault("page", "0"));
Auth0UserProfile requestingUser = getUserFromRequest(req);
if (isUserAdmin(requestingUser)) {
// If the user is admin, the context is presumed to be the admin dashboard, so we deliver all entities for
// management or review without restriction.
return persistence.getAll();
list = persistence.getAll();
} else if (persistence.clazz == OtpUser.class) {
// If the required entity is of type 'OtpUser' the assumption is that a call is being made via the
// OtpUserController. Therefore, the request should be limited to return just the entity matching the
// requesting user.
return getObjectsFiltered("_id", requestingUser.otpUser.id);
list = getObjectsFiltered("_id", requestingUser.otpUser.id);
} else {
// For all other cases the assumption is that the request is being made by an Otp user and the requested
// entities have a 'userId' parameter. Only entities that match the requesting user id are returned.
return getObjectsFiltered("userId", requestingUser.otpUser.id);
list = getObjectsFiltered("userId", requestingUser.otpUser.id);
}
return new ResponseList<>(list, page, limit);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.mongodb.client.model.Filters;
import org.bson.conversions.Bson;
import org.opentripplanner.middleware.bugsnag.EventSummary;
import org.opentripplanner.middleware.controllers.response.ResponseList;
import org.opentripplanner.middleware.models.BugsnagEvent;
import org.opentripplanner.middleware.models.BugsnagProject;
import org.opentripplanner.middleware.persistence.Persistence;
Expand All @@ -15,7 +16,9 @@
import spark.Response;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

import static com.beerboy.ss.descriptor.EndpointDescriptor.endpointPath;
import static com.beerboy.ss.descriptor.MethodDescriptor.path;
Expand Down Expand Up @@ -56,17 +59,22 @@ public void bind(final SparkSwagger restApi) {
/**
* Get all Bugsnag events from Mongo and replace the project id with the project name and return
*/
private static List<EventSummary> getEventSummary(Request request, Response response) {
private static ResponseList<EventSummary> getEventSummary(Request req, Response res) {
List<EventSummary> eventSummaries = new ArrayList<>();
List<BugsnagEvent> events = bugsnagEvents.getAll();
int limit = Integer.parseInt(req.queryParamOrDefault("limit", "10"));
landonreed marked this conversation as resolved.
Show resolved Hide resolved
int page = Integer.parseInt(req.queryParamOrDefault("page", "0"));

// FIXME: Group by error/project type?
for (BugsnagEvent event : events) {
Bson filter = Filters.eq("projectId", event.projectId);
BugsnagProject project = bugsnagProjects.getOneFiltered(filter);
eventSummaries.add(new EventSummary(project, event));
}

return eventSummaries;
List<EventSummary> sorted = eventSummaries.stream()
.sorted(Comparator.comparing(eventSummary -> eventSummary.received))
.collect(Collectors.toList());
// For now return first 100 events. Otherwise, this list could grow to thousands of items and cause a request timeout.
return new ResponseList<>(sorted, page, limit);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.opentripplanner.middleware.controllers.response;

import java.util.Date;
import java.util.List;

/**
* Generic class for wrapping a paginated list response for a 'get all' HTTP endpoint.
*/
public class ResponseList<T> {
public final List<T> data;
public final int page;
public final int limit;
public final int total;
public final Date timestamp;

/**
* Constructor for generating a paginated response list.
* @param data - list of entities to limit
* @param page - from index to abbreviate data
* @param limit - number of entities to include in list
*/
public ResponseList(List<T> data, int page, int limit){
this.page = page;
this.limit = limit;
this.total = data.size();
this.data = data.subList(page * limit, Math.min(this.total, (page + 1) * limit));
landonreed marked this conversation as resolved.
Show resolved Hide resolved
this.timestamp = new Date();
}
}
33 changes: 22 additions & 11 deletions src/main/resources/latest-spark-swagger-output.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ paths:
"200":
description: "successful operation"
schema:
$ref: "#/definitions/AdminUser[]"
$ref: "#/definitions/ResponseList"
Copy link
Contributor

Choose a reason for hiding this comment

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

I suppose this is a little better than the AdminUser[] type since the definition for that was just an empty object. It might be out of scope for this PR, but it would be nice to have the swagger docs include refs to the proper object in an array.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@evansiroky Note that AdminUser is a subclass of AbstractUser, but spark-swagger ignores inherited fields at the time this was implemented. We insert inherited fields into the AdminUserswagger type using the PublicApiDocGenerator class.

post:
tags:
- "api/admin/user"
Expand Down Expand Up @@ -237,7 +237,7 @@ paths:
"200":
description: "successful operation"
schema:
$ref: "#/definitions/ApiUser[]"
$ref: "#/definitions/ResponseList"
post:
tags:
- "api/secure/application"
Expand Down Expand Up @@ -330,7 +330,7 @@ paths:
"200":
description: "successful operation"
schema:
$ref: "#/definitions/MonitoredTrip[]"
$ref: "#/definitions/ResponseList"
post:
tags:
- "api/secure/monitoredtrip"
Expand Down Expand Up @@ -527,7 +527,7 @@ paths:
"200":
description: "successful operation"
schema:
$ref: "#/definitions/OtpUser[]"
$ref: "#/definitions/ResponseList"
post:
tags:
- "api/secure/user"
Expand Down Expand Up @@ -684,8 +684,25 @@ definitions:
format: "date"
id:
type: "string"
AdminUser[]:
ResponseList:
type: "object"
properties:
data:
type: "array"
items:
type: "string"
Copy link
Contributor

Choose a reason for hiding this comment

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

This is going to be incorrect since at the very least the items will be an object. It might be necessary to have separate ResponseList types that include the correct reference to the subclass that they return.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@binh-dam-ibigroup, do you have any thoughts on how to ammend this swagger def?

Copy link
Collaborator

Choose a reason for hiding this comment

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

The most thorough way to handle this would be to change the document generator class to create a ResponseList_Subtype object definition for each of the ResponseList responses we are publishing. We can also try creating a separate descendant of ResponseBody per @evansiroky's suggestion above and see how spark-swagger handles that.

Copy link
Collaborator

Choose a reason for hiding this comment

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

To follow up on my comment above, you could modify class PublicApiDocsGenerator to do the following:

  • Scan all ResponseList return entries in the autogenerated swagger.
  • For each instance, find the type of the elements in the list from one of the parent elements.
  • Generate a custom return type definition that correspond to ResponseList<type>
  • Replace ResponseList with the custom type generated above.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Further discussion should be had in #85

page:
type: "integer"
format: "int32"
limit:
type: "integer"
format: "int32"
total:
type: "integer"
format: "int32"
timestamp:
type: "string"
format: "date"
ApiKey:
type: "object"
properties:
Expand Down Expand Up @@ -714,10 +731,6 @@ definitions:
type: "boolean"
name:
type: "string"
ApiUser[]:
type: "object"
MonitoredTrip[]:
type: "object"
EncodedPolyline:
type: "object"
properties:
Expand Down Expand Up @@ -1116,8 +1129,6 @@ definitions:
type: "boolean"
applicationId:
type: "string"
OtpUser[]:
type: "object"
GetUsageResult:
type: "object"
properties:
Expand Down