Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 5 additions & 5 deletions .github/workflows/maven.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ jobs:
with:
java-version: 19
distribution: 'temurin'
# Install node 20 for running e2e tests (and for maven-semantic-release).
- name: Use Node.js 20.x
# Install node 22 for running e2e tests (and for maven-semantic-release).
- name: Use Node.js 22.x
uses: actions/setup-node@v1
with:
node-version: 20.x
node-version: 22.x
- name: Start MongoDB
uses: supercharge/mongodb-github-action@1.3.0
with:
Expand Down Expand Up @@ -98,10 +98,10 @@ jobs:

# Run maven-semantic-release to potentially create a new release of datatools-server. The flag --skip-maven-deploy is
# used to avoid deploying to maven central. So essentially, this just creates a release with a changelog on github.
- name: Use Node.js 20.x
- name: Use Node.js 22.x
uses: actions/setup-node@v1
with:
node-version: 20.x
node-version: 22.x
- name: Run maven-semantic-release
if: env.SAVE_JAR_TO_S3 == 'true'
env:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.conveyal.datatools.manager.auth.Auth0UserProfile;
import com.conveyal.datatools.manager.jobs.ProcessSingleFeedJob;
import com.conveyal.datatools.manager.jobs.ValidateFeedJob;
import com.conveyal.datatools.manager.jobs.ValidateGtfsPlusFeedJob;
import com.conveyal.datatools.manager.jobs.ValidateMobilityDataFeedJob;
import com.conveyal.datatools.manager.models.Deployment;
import com.conveyal.datatools.manager.models.ExternalFeedSourceProperty;
Expand Down Expand Up @@ -365,6 +366,7 @@ public static boolean validateAll (boolean load, boolean force, String filterFee
} else {
JobUtils.heavyExecutor.execute(new ValidateFeedJob(version, systemUser, false));
JobUtils.heavyExecutor.execute(new ValidateMobilityDataFeedJob(version, systemUser, false));
JobUtils.heavyExecutor.execute(new ValidateGtfsPlusFeedJob(version, systemUser, false));
}
}
// ValidateAllFeedsJob validateAllFeedsJob = new ValidateAllFeedsJob("system", force, load);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,13 +242,22 @@ private static String publishGtfsPlusFile(Request req, Response res) {
}

/**
* HTTP endpoint that validates GTFS+ tables for a specific feed version (or its saved/edited GTFS+).
* HTTP endpoint that validates GTFS+ tables for a specific feed version (or its saved/edited GTFS+). If the feed
* version already has GTFS+ validation results, those will be returned instead of re-validating.
*/
private static GtfsPlusValidation getGtfsPlusValidation(Request req, Response res) {
String feedVersionId = req.params("versionid");
GtfsPlusValidation gtfsPlusValidation = null;
try {
FeedVersion feedVersion = Persistence.feedVersions.getById(feedVersionId);
if (feedVersion != null && feedVersion.gtfsPlusValidation != null) {
return feedVersion.gtfsPlusValidation;
}
gtfsPlusValidation = GtfsPlusValidation.validate(feedVersionId);
if (feedVersion != null) {
feedVersion.gtfsPlusValidation = gtfsPlusValidation;
Persistence.feedVersions.replace(feedVersion.id, feedVersion);
}
} catch(Exception e) {
logMessageAndHalt(req, 500, "Could not read GTFS+ zip file", e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public class GtfsPlusValidation implements Serializable {
private static final String REALTIME_ROUTES_TXT = "realtime_routes.txt";

// Public fields to appear in validation JSON.
public final String feedVersionId;
public String feedVersionId;
/** Indicates whether GTFS+ validation applies to user-edited feed or original published GTFS feed */
public boolean published;
public long lastModified;
Expand All @@ -49,22 +49,36 @@ private GtfsPlusValidation (String feedVersionId) {
this.feedVersionId = feedVersionId;
}

public GtfsPlusValidation(boolean published, List<ValidationIssue> issues) {
this.published = published;
this.issues = issues;
}

public GtfsPlusValidation() {
// Empty constructor for serialization
}

/**
* Validate a GTFS+ feed and return a list of issues encountered.
* FIXME: For now this uses the MapDB-backed GTFSFeed class. Which actually suggests that this might
* should be contained within a MonitorableJob.
* Overload method to retrieve the feed version.
*/
public static GtfsPlusValidation validate(String feedVersionId) throws Exception {
GtfsPlusValidation validation = new GtfsPlusValidation(feedVersionId);
FeedVersion feedVersion = Persistence.feedVersions.getById(feedVersionId);
return validate(feedVersion);
}

/**
* Validate a GTFS+ feed and return a list of issues encountered.
*/
public static GtfsPlusValidation validate(FeedVersion feedVersion) throws Exception {
if (!DataManager.isModuleEnabled("gtfsplus")) {
throw new IllegalStateException("GTFS+ module must be enabled in server.yml to run GTFS+ validation.");
}
LOG.info("Validating GTFS+ for {}", feedVersionId);
GtfsPlusValidation validation = new GtfsPlusValidation(feedVersion.id);
LOG.info("Validating GTFS+ for {}", feedVersion.id);

FeedVersion feedVersion = Persistence.feedVersions.getById(feedVersionId);
// Load the main GTFS file.
// FIXME: Swap MapDB-backed GTFSFeed for use of SQL data?
File gtfsFeedDbFile = gtfsPlusStore.getFeedFile(feedVersionId + ".db");
File gtfsFeedDbFile = gtfsPlusStore.getFeedFile(feedVersion.id + ".db");
String gtfsFeedDbFilePath = gtfsFeedDbFile.getAbsolutePath();
GTFSFeed gtfsFeed;
try {
Expand All @@ -87,7 +101,7 @@ public static GtfsPlusValidation validate(String feedVersionId) throws Exception
}

// check for saved GTFS+ data
File file = gtfsPlusStore.getFeed(feedVersionId);
File file = gtfsPlusStore.getFeed(feedVersion.id);
if (file == null) {
validation.published = true;
LOG.warn("GTFS+ Validation -- Modified GTFS+ file not found, loading from main version GTFS.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ public class ValidationIssue implements Serializable {
public int rowIndex;
public String description;

public ValidationIssue() {
// Empty constructor for serialization
}

public ValidationIssue(String tableId, String fieldName, int rowIndex, String description) {
this.tableId = tableId;
this.fieldName = fieldName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public class ProcessSingleFeedJob extends FeedVersionJob {
public static boolean ENABLE_MTC_TRANSFORMATIONS = true;

// Used in testing to skip validation and speed up response times.
public static boolean VALIDATE_MOBILITY_DATA = true;
public static boolean ENABLE_ADDITIONAL_VALIDATION = true;

/**
* Create a job for the given feed version.
Expand Down Expand Up @@ -131,8 +131,9 @@ public void jobLogic() {

// Next, validate the feed.
addNextJob(new ValidateFeedJob(feedVersion, owner, isNewVersion));
if (VALIDATE_MOBILITY_DATA) {
if (ENABLE_ADDITIONAL_VALIDATION) {
addNextJob(new ValidateMobilityDataFeedJob(feedVersion, owner, isNewVersion));
addNextJob(new ValidateGtfsPlusFeedJob(feedVersion, owner, isNewVersion));
}

// We only need to snapshot the feed if there are transformations at the database level. In the case that there
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.conveyal.datatools.manager.jobs;

import com.conveyal.datatools.common.status.FeedVersionJob;
import com.conveyal.datatools.manager.auth.Auth0UserProfile;
import com.conveyal.datatools.manager.models.FeedVersion;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* This job handles the Gtfs+ validation of a given feed version. If the version is not new, it will simply
* replace the existing version with the version object that has updated validation info.
*/
public class ValidateGtfsPlusFeedJob extends FeedVersionJob {
public static final Logger LOG = LoggerFactory.getLogger(ValidateGtfsPlusFeedJob.class);

private final FeedVersion feedVersion;
private final boolean isNewVersion;

public ValidateGtfsPlusFeedJob(FeedVersion version, Auth0UserProfile owner, boolean isNewVersion) {
super(owner, "Validating Gtfs+", JobType.VALIDATE_FEED);
feedVersion = version;
this.isNewVersion = isNewVersion;
status.update("Waiting to begin Gtfs+ validation...", 0);
}

@Override
public void jobLogic () {
LOG.info("Running ValidateGtfsPlusFeedJob for {}", feedVersion.id);
feedVersion.validateGtfsPlus(status);
}

@Override
public void jobFinished () {
if (!status.error) {
if (parentJobId != null && JobType.PROCESS_FEED.equals(parentJobType)) {
// Validate stage is happening as part of an overall process feed job.
// At this point all GTFS data has been loaded and validated, so we record
// the FeedVersion into mongo.
// This happens here because otherwise we would have to wait for other jobs,
// such as BuildTransportNetwork, to finish. If those subsequent jobs fail,
// the version won't get loaded into MongoDB (even though it exists in postgres).
feedVersion.persistFeedVersionAfterValidation(isNewVersion);
}
status.completeSuccessfully("Gtfs+ validation finished!");
} else {
// If the version was not stored successfully, call FeedVersion#delete to reset things to before the version
// was uploaded/fetched. Note: delete calls made to MongoDB on the version ID will not succeed, but that is
// expected.
feedVersion.delete();
}
}

/**
* Getter that allows a client to know the ID of the feed version that will be created as soon as the upload is
* initiated; however, we will not store the FeedVersion in the mongo application database until the upload and
* processing is completed. This prevents clients from manipulating GTFS data before it is entirely imported.
*/
@JsonProperty
public String getFeedVersionId () {
return feedVersion.id;
}

@JsonProperty
public String getFeedSourceId () {
return feedVersion.parentFeedSource().id;
}


}
Loading
Loading