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

Deployed feed version summaries #516

Merged
merged 8 commits into from
Mar 16, 2023
115 changes: 40 additions & 75 deletions src/main/java/com/conveyal/datatools/manager/models/FeedSource.java
Original file line number Diff line number Diff line change
Expand Up @@ -459,108 +459,73 @@ public String latestVersionId() {
return latest != null ? latest.id : null;
}

/**
* The deployed feed version.
* This cannot be returned because of a circular reference between feed source and feed version. Instead, individual
* parameters (version id, start date and end date) are returned.
*/
@JsonIgnore
@BsonIgnore
private FeedVersionDeployed deployedFeedVersion;

/**
* This value is set to true once an attempt has been made to get the deployed feed version. This prevents subsequent
* attempts where the deployed feed version is not available.
*/
@JsonIgnore
@BsonIgnore
private boolean deployedFeedVersionDefined;
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we do this instead of a null check on deployedFeedVersion?

Copy link
Contributor Author

@br648 br648 Mar 15, 2023

Choose a reason for hiding this comment

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

This is to prevent calls to retrieveDeployedFeedVersion() where there are no deployed feed versions. Using null resulted in the following scenerio: Call to getDeployedFeedVersionId() returned null, followed by a call to getDeployedFeedVersionStartDate() returning null and a call to getDeployedFeedVersionEndDate() also returning null.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think this may just be issues with my java understanding how is this field better than those 3 methods returning null? Is it to avoid mongo queries?


@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonView(JsonViews.UserInterface.class)
@JsonProperty("deployedFeedVersionId")
@BsonIgnore
public String getDeployedFeedVersionId() {
FeedVersion feedVersion = getDeployedFeedVersion();
return feedVersion != null ? feedVersion.id : null;
deployedFeedVersion = retrieveDeployedFeedVersion();
return deployedFeedVersion != null ? deployedFeedVersion.id : null;
}

@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonView(JsonViews.UserInterface.class)
@JsonProperty("deployedFeedVersionStartDate")
@BsonIgnore
public LocalDate getDeployedFeedVersionStartDate() {
FeedVersion feedVersion = getDeployedFeedVersion();
return feedVersion != null ? feedVersion.validationSummary().startDate : null;
deployedFeedVersion = retrieveDeployedFeedVersion();
return deployedFeedVersion != null ? deployedFeedVersion.startDate : null;
}

@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonView(JsonViews.UserInterface.class)
@JsonProperty("deployedFeedVersionEndDate")
@BsonIgnore
public LocalDate getDeployedFeedVersionEndDate() {
FeedVersion feedVersion = getDeployedFeedVersion();
return feedVersion != null ? feedVersion.validationSummary().endDate : null;
deployedFeedVersion = retrieveDeployedFeedVersion();
return deployedFeedVersion != null ? deployedFeedVersion.endDate : null;
}

/**
* The deployed feed version.
* This cannot be returned because of a circular reference between feed source and feed version. Instead, individual
* parameters (version id, start date and end date) are returned.
*/
@JsonIgnore
@BsonIgnore
private FeedVersion deployedFeedVersion = null;

/**
* If a project has a "pinned" deployment it's feed versions take precedence over the latest feed version for this
* feed source. In this case, return either the latest "pinned" feed version or just the feed version, which ever
* is available.
* Get deployed feed version for this feed source.
*
* If a project does not have a "pinned" deployment (or the above is not available), return the latest feed version
* for this feed source from the newest deployment.
*/
private FeedVersion getDeployedFeedVersion() {
if (deployedFeedVersion == null) {
Project project = Persistence.projects.getById(this.projectId);
if (project.pinnedDeploymentId != null) {
Deployment deployment = Persistence.deployments.getByIdLimitedFields(
project.pinnedDeploymentId,
"pinnedfeedVersionIds", "feedVersionIds"
);
FeedVersion feedVersion = getLatestDeployedFeedVersionForFeedSource(deployment.pinnedfeedVersionIds);
if (feedVersion != null) {
// This feed version will be the latest pinned version for this feed source, if available.
deployedFeedVersion = feedVersion;
} else {
feedVersion = getLatestDeployedFeedVersionForFeedSource(deployment.feedVersionIds);
if (feedVersion != null) {
// This feed version will be the latest version for this feed source, if available.
deployedFeedVersion = feedVersion;
}
}
}

// If there is no pinned deployment or none of the deployment's feed versions are related to this feed source,
// find the latest feed version for this feed source.
if (deployedFeedVersion == null) {
// Get all deployments for this project.
List<Deployment> deployments = Persistence.deployments.getFilteredLimitedFields(
eq("projectId", this.projectId),
Sorts.descending("lastUpdated"),
"feedVersionIds"
);
// Iterate through deployments newest to oldest.
for (Deployment deployment : deployments) {
FeedVersion feedVersion = getLatestDeployedFeedVersionForFeedSource(deployment.feedVersionIds);
if (feedVersion != null) {
// This feed version will be the latest feed version for this feed source from the newest
// deployment.
deployedFeedVersion = feedVersion;
break;
}
}
}
* If a project has a "pinned" deployment, return the feed version from this pinned deployment. If it is not
* available return null and don't attempt to get the feed version from the latest deployment.
*
* If a project does not have a "pinned" deployment, return the latest deployment's feed versions for this feed
* source, if available.
*/
public FeedVersionDeployed retrieveDeployedFeedVersion() {
if (deployedFeedVersionDefined) {
return deployedFeedVersion;
}
Project project = Persistence.projects.getById(projectId);
if (project.pinnedDeploymentId != null && !project.pinnedDeploymentId.isEmpty()) {
miles-grant-ibigroup marked this conversation as resolved.
Show resolved Hide resolved
deployedFeedVersion = FeedVersionDeployed.getFeedVersionFromPinnedDeployment(projectId, id);
} else {
deployedFeedVersion = FeedVersionDeployed.getFeedVersionFromLatestDeployment(projectId, id);
}
deployedFeedVersionDefined = true;
return deployedFeedVersion;
}

/**
* Get the latest deployed feed version for this feed source, if available.
*/
private FeedVersion getLatestDeployedFeedVersionForFeedSource(Collection<String> feedVersionIds) {
return Persistence.feedVersions.getOneFiltered(
and(
eq("feedSourceId", this.id),
in("_id", feedVersionIds)
),
Sorts.descending("updated")
);
}

/**
* Number of {@link FeedVersion}s that exist for the feed source.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
package com.conveyal.datatools.manager.models;

import com.conveyal.datatools.editor.utils.JacksonSerializers;
import com.conveyal.datatools.manager.persistence.Persistence;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.google.common.collect.Lists;
import com.mongodb.client.model.Sorts;
import org.bson.Document;
import org.bson.conversions.Bson;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.List;

import static com.mongodb.client.model.Aggregates.limit;
import static com.mongodb.client.model.Aggregates.lookup;
import static com.mongodb.client.model.Aggregates.match;
import static com.mongodb.client.model.Aggregates.replaceRoot;
import static com.mongodb.client.model.Aggregates.sort;
import static com.mongodb.client.model.Aggregates.unwind;
import static com.mongodb.client.model.Filters.in;

public class FeedVersionDeployed {
public String id;

@JsonSerialize(using = JacksonSerializers.LocalDateIsoSerializer.class)
@JsonDeserialize(using = JacksonSerializers.LocalDateIsoDeserializer.class)
public LocalDate startDate;

@JsonSerialize(using = JacksonSerializers.LocalDateIsoSerializer.class)
@JsonDeserialize(using = JacksonSerializers.LocalDateIsoDeserializer.class)
public LocalDate endDate;

public FeedVersionDeployed() {
}

public FeedVersionDeployed(Document feedVersionDocument) {
this.id = feedVersionDocument.getString("_id");
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
Document validationResult = (Document) feedVersionDocument.get("validationResult");
if (validationResult != null) {
String first = validationResult.getString("firstCalendarDate");
String last = validationResult.getString("lastCalendarDate");
this.startDate = (first == null) ? null : LocalDate.parse(first, formatter);
this.endDate = (last == null) ? null : LocalDate.parse(last, formatter);
}
}

/**
* Get the deployed feed version from the pinned deployment for this feed source.
*/
public static FeedVersionDeployed getFeedVersionFromPinnedDeployment(String projectId, String feedSourceId) {
/*
Note: To test this script:
1) Comment out the call to tearDownDeployedFeedVersion() in FeedSourceControllerTest -> tearDown().
2) Run FeedSourceControllerTest to created required objects referenced here.
3) Once complete, delete documents via MongoDB.
4) Uncomment the call to tearDownDeployedFeedVersion() in FeedSourceControllerTest -> tearDown().
5) Re-run FeedSourceControllerTest to confirm deletion of objects.

db.getCollection('Project').aggregate([
{
$match: {
_id: "project-with-pinned-deployment"
}
},
{
$lookup:{
from:"Deployment",
localField:"pinnedDeploymentId",
foreignField:"_id",
as:"deployment"
}
},
{
$lookup:{
from:"FeedVersion",
localField:"deployment.feedVersionIds",
foreignField:"_id",
as:"feedVersions"
}
},
{
$unwind: "$feedVersions"
},
{
"$replaceRoot": {
"newRoot": "$feedVersions"
}
},
{
$match: {
feedSourceId: "feed-source-with-pinned-deployment-feed-version"
}
},
{
$sort: {
lastUpdated : -1
}
},
{
$limit: 1
}
])
*/
List<Bson> stages = Lists.newArrayList(
match(
in("_id", projectId)
),
lookup("Deployment", "pinnedDeploymentId", "_id", "deployment"),
miles-grant-ibigroup marked this conversation as resolved.
Show resolved Hide resolved
lookup("FeedVersion", "deployment.feedVersionIds", "_id", "feedVersions"),
unwind("$feedVersions"),
replaceRoot("$feedVersions"),
match(
in("feedSourceId", feedSourceId)
),
// If more than one feed version for a feed source is held against a deployment the latest is used.
sort(Sorts.descending("lastUpdated")),
limit(1)
);
return getFeedVersionDeployed(stages);
}

/**
* Get the deployed feed version from the latest deployment for this feed source.
*/
public static FeedVersionDeployed getFeedVersionFromLatestDeployment(String projectId, String feedSourceId) {
/*
Note: To test this script:
1) Comment out the call to tearDownDeployedFeedVersion() in FeedSourceControllerTest -> tearDown().
2) Run FeedSourceControllerTest to created required objects referenced here.
3) Once complete, delete documents via MongoDB.
4) Uncomment the call to tearDownDeployedFeedVersion() in FeedSourceControllerTest -> tearDown().
5) Re-run FeedSourceControllerTest to confirm deletion of objects.

db.getCollection('Project').aggregate([
{
// Match provided project id.
$match: {
_id: "project-with-latest-deployment"
}
},
{
// Get all deployments for this project.
$lookup:{
from:"Deployment",
localField:"_id",
foreignField:"projectId",
as:"deployments"
}
},
{
// Deconstruct deployments array to a document for each element.
$unwind: "$deployments"
},
{
// Make the deployment documents the input/root document.
"$replaceRoot": {
"newRoot": "$deployments"
}
},
{
// Sort descending.
$sort: {
lastUpdated : -1
}
},
{
// At this point we will have the latest deployment for a project.
$limit: 1
},
{
// Get all feed versions that have been deployed as part of the latest deployment.
$lookup:{
from:"FeedVersion",
localField:"feedVersionIds",
foreignField:"_id",
as:"feedVersions"
}
},
{
// Deconstruct feedVersions array to a document for each element.
$unwind: "$feedVersions"
},
{
// Make the feed version documents the input/root document.
"$replaceRoot": {
"newRoot": "$feedVersions"
}
},
{
// Match the required feed source.
$match: {
feedSourceId: "feed-source-with-latest-deployment-feed-version"
}
},
{
$sort: {
lastUpdated : -1
}
},
{
// At this point we will have the latest feed version from the latest deployment for a feed source.
$limit: 1
}
])
*/
List<Bson> stages = Lists.newArrayList(
match(
in("_id", projectId)
),
lookup("Deployment", "_id", "projectId", "deployments"),
unwind("$deployments"),
replaceRoot("$deployments"),
sort(Sorts.descending("lastUpdated")),
limit(1),
lookup("FeedVersion", "feedVersionIds", "_id", "feedVersions"),
unwind("$feedVersions"),
replaceRoot("$feedVersions"),
match(
in("feedSourceId", feedSourceId)
),
// If more than one feed version for a feed source is held against a deployment the latest is used.
sort(Sorts.descending("lastUpdated")),
limit(1)
);
return getFeedVersionDeployed(stages);
}

private static FeedVersionDeployed getFeedVersionDeployed(List<Bson> stages) {
Document feedVersionDocument = Persistence
.getMongoDatabase()
.getCollection("Project")
.aggregate(stages)
.first();
return (feedVersionDocument == null) ? null : new FeedVersionDeployed(feedVersionDocument);
}
}
Loading