Skip to content

Commit

Permalink
refactor(Fixed merge conflicts):
Browse files Browse the repository at this point in the history
  • Loading branch information
br648 committed Oct 25, 2023
2 parents 7361e0f + a7e5143 commit 85f57c9
Show file tree
Hide file tree
Showing 27 changed files with 1,142 additions and 382 deletions.
10 changes: 1 addition & 9 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -194,14 +194,6 @@
<snapshots><enabled>false</enabled></snapshots>
<releases><enabled>true</enabled></releases>
</repository>
<repository> <!--Add the snapshot repository here-->
<snapshots>
<enabled>true</enabled>
</snapshots>
<id>opengeo</id>
<name>OpenGeo Maven Repository</name>
<url>https://repo.opengeo.org</url>
</repository>
<repository>
<id>sonatype</id>
<name>sonatype snapshots</name>
Expand Down Expand Up @@ -298,7 +290,7 @@
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.0-jre</version>
<version>32.0.0-jre</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import com.conveyal.datatools.manager.models.ExternalFeedSourceProperty;
import com.conveyal.datatools.manager.models.FeedRetrievalMethod;
import com.conveyal.datatools.manager.models.FeedSource;
import com.conveyal.datatools.manager.models.FeedSourceSummary;
import com.conveyal.datatools.manager.models.JsonViews;
import com.conveyal.datatools.manager.models.Project;
import com.conveyal.datatools.manager.models.transform.NormalizeFieldTransformation;
Expand Down Expand Up @@ -85,7 +86,7 @@ private static Collection<FeedSource> getProjectFeedSources(Request req, Respons
boolean isAdmin = user.canAdministerProject(project);

Collection<FeedSource> projectFeedSources = project.retrieveProjectFeedSources();
for (FeedSource source: projectFeedSources) {
for (FeedSource source : projectFeedSources) {
String orgId = source.organizationId();
// If user can view or manage feed, add to list of feeds to return. NOTE: By default most users with access
// to a project should be able to view all feed sources. Custom privileges would need to be provided to
Expand All @@ -101,6 +102,31 @@ private static Collection<FeedSource> getProjectFeedSources(Request req, Respons
return feedSourcesToReturn;
}

private static Collection<FeedSourceSummary> getAllFeedSourceSummaries(Request req, Response res) {
Collection<FeedSourceSummary> feedSourcesToReturn = new ArrayList<>();
Auth0UserProfile user = req.attribute("user");
String projectId = req.queryParams("projectId");

Project project = Persistence.projects.getById(projectId);

if (project == null) {
logMessageAndHalt(req, 400, "Must provide valid projectId value.");
} else {
boolean isAdmin = user.canAdministerProject(project);
Collection<FeedSourceSummary> feedSourceSummaries = project.retrieveFeedSourceSummaries();
for (FeedSourceSummary feedSourceSummary : feedSourceSummaries) {
// If user can view or manage feed, add to list of feeds to return. NOTE: By default most users with access
// to a project should be able to view all feed sources. Custom privileges would need to be provided to
// override this behavior.
if (user.canManageOrViewFeed(project.organizationId, feedSourceSummary.projectId, feedSourceSummary.id)) {
// Remove labels user can't view, then add to list of feeds to return.
feedSourcesToReturn.add(cleanFeedSourceSummaryForNonAdmins(feedSourceSummary, isAdmin));
}
}
}
return feedSourcesToReturn;
}

/**
* HTTP endpoint to create a new feed source.
*/
Expand Down Expand Up @@ -295,7 +321,7 @@ private static FeedSource deleteFeedSource(Request req, Response res) {
/**
* Re-fetch this feed from the feed source URL.
*/
private static String fetch (Request req, Response res) {
private static String fetch(Request req, Response res) {
FeedSource s = requestFeedSourceById(req, Actions.MANAGE);
if (s.url == null) {
logMessageAndHalt(req, HttpStatus.BAD_REQUEST_400, "Cannot fetch feed source with null URL.");
Expand All @@ -313,7 +339,8 @@ private static String fetch (Request req, Response res) {

/**
* Helper function returns feed source if user has permission for specified action.
* @param req spark Request object from API request
*
* @param req spark Request object from API request
* @param action action type (either "view" or Permission.MANAGE)
* @return feedsource object for ID
*/
Expand Down Expand Up @@ -364,45 +391,88 @@ public static FeedSource checkFeedSourcePermissions(Request req, FeedSource feed
return cleanFeedSourceForNonAdmins(feedSource, isProjectAdmin);
}

/** Determines whether a change to a feed source is significant enough that it warrants sending a notification
/**
* Determines whether a change to a feed source is significant enough that it warrants sending a notification
*
* @param formerFeedSource A feed source object, without new changes
* @param updatedFeedSource A feed source object, with new changes
* @return A boolean value indicating if the updated feed source is changed enough to warrant sending a notification.
* @return A boolean value indicating if the updated feed source is changed enough to warrant sending a notification.
*/
private static boolean shouldNotifyUsersOnFeedUpdated(FeedSource formerFeedSource, FeedSource updatedFeedSource) {
return
// If only labels have changed, don't send out an email
formerFeedSource.equalsExceptLabels(updatedFeedSource);
// If only labels have changed, don't send out an email.
return formerFeedSource.equalsExceptLabels(updatedFeedSource);
}

/**
* Removes labels and notes from a feed that a user is not allowed to view. Returns cleaned feed source.
* @param feedSource The feed source to clean
* @param isAdmin Is the user an admin? Changes what is returned.
* @return A feed source containing only labels/notes the user is allowed to see
*
* @param feedSource The feed source to clean.
* @param isAdmin Is the user an admin? Changes what is returned.
* @return A feed source containing only labels/notes the user is allowed to see.
*/
protected static FeedSource cleanFeedSourceForNonAdmins(FeedSource feedSource, boolean isAdmin) {
// Admin can view all feed labels, but a non-admin should only see those with adminOnly=false
feedSource.labelIds = Persistence.labels
.getFiltered(PersistenceUtils.applyAdminFilter(in("_id", feedSource.labelIds), isAdmin)).stream()
feedSource.labelIds = cleanFeedSourceLabelIdsForNonAdmins(feedSource.labelIds, isAdmin);
feedSource.noteIds = cleanFeedSourceNotesForNonAdmins(feedSource.noteIds, isAdmin);
return feedSource;
}

/**
* Removes labels and notes from a feed that a user is not allowed to view. Returns cleaned feed source summary.
*
* @param feedSourceSummary The feed source to clean.
* @param isAdmin Is the user an admin? Changes what is returned.
* @return A feed source summary containing only labels/notes the user is allowed to see.
*/
protected static FeedSourceSummary cleanFeedSourceSummaryForNonAdmins(FeedSourceSummary feedSourceSummary, boolean isAdmin) {
// Admin can view all feed labels, but a non-admin should only see those with adminOnly=false
feedSourceSummary.labelIds = cleanFeedSourceLabelIdsForNonAdmins(feedSourceSummary.labelIds, isAdmin);
feedSourceSummary.noteIds = cleanFeedSourceNotesForNonAdmins(feedSourceSummary.noteIds, isAdmin);
return feedSourceSummary;
}

/**
* Removes labels from a feed that a user is not allowed to view. Returns cleaned notes.
*
* @param labelIds The labels to clean.
* @param isAdmin Is the user an admin? Changes what is returned.
* @return Labels the user is allowed to see.
*/
protected static List<String> cleanFeedSourceLabelIdsForNonAdmins(List<String> labelIds, boolean isAdmin) {
// Admin can view all feed labels, but a non-admin should only see those with adminOnly=false.
return Persistence.labels
.getFiltered(PersistenceUtils.applyAdminFilter(in("_id", labelIds), isAdmin))
.stream()
.map(label -> label.id)
.collect(Collectors.toList());
feedSource.noteIds = Persistence.notes
.getFiltered(PersistenceUtils.applyAdminFilter(in("_id", feedSource.noteIds), isAdmin)).stream()
}

/**
* Removes notes from a feed that a user is not allowed to view. Returns cleaned notes.
*
* @param noteIds The notes to clean.
* @param isAdmin Is the user an admin? Changes what is returned.
* @return Notes the user is allowed to see.
*/
protected static List<String> cleanFeedSourceNotesForNonAdmins(List<String> noteIds, boolean isAdmin) {
// Admin can view all feed notes, but a non-admin should only see those with adminOnly=false.
return Persistence.notes
.getFiltered(PersistenceUtils.applyAdminFilter(in("_id", noteIds), isAdmin))
.stream()
.map(note -> note.id)
.collect(Collectors.toList());
return feedSource;
}


// FIXME: use generic API controller and return JSON documents via BSON/Mongo
public static void register (String apiPrefix) {
public static void register(String apiPrefix) {
get(apiPrefix + "secure/feedsource/:id", FeedSourceController::getFeedSource, json::write);
get(apiPrefix + "secure/feedsource", FeedSourceController::getProjectFeedSources, json::write);
post(apiPrefix + "secure/feedsource", FeedSourceController::createFeedSource, json::write);
put(apiPrefix + "secure/feedsource/:id", FeedSourceController::updateFeedSource, json::write);
put(apiPrefix + "secure/feedsource/:id/updateExternal", FeedSourceController::updateExternalFeedResource, json::write);
delete(apiPrefix + "secure/feedsource/:id", FeedSourceController::deleteFeedSource, json::write);
post(apiPrefix + "secure/feedsource/:id/fetch", FeedSourceController::fetch, json::write);
get(apiPrefix + "secure/feedsourceSummaries", FeedSourceController::getAllFeedSourceSummaries, json::write);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

Expand Down Expand Up @@ -122,6 +124,13 @@ private static void validateTable(
GTFSFeed gtfsFeed
) throws IOException {
String tableId = specTable.get("id").asText();
boolean tableIsDirections = tableId.equals("directions");

Set<String> gtfsRoutes = new HashSet<>();
if (tableIsDirections) {
// Copy the gtfs routes into a set so that we can "check them off" (remove them).
gtfsRoutes.addAll(gtfsFeed.routes.keySet());
}

// Read in table data from input stream.
CsvReader csvReader = new CsvReader(inputStreamToValidate, ',', StandardCharsets.UTF_8);
Expand Down Expand Up @@ -167,15 +176,20 @@ private static void validateTable(
// Validate each value in row. Note: we iterate over the fields and not values because a row may be missing
// columns, but we still want to validate that missing value (e.g., if it is missing a required field).
for (int f = 0; f < fieldsFound.length; f++) {
JsonNode specField = fieldsFound[f];
// If value exists for index, use that. Otherwise, default to null to avoid out of bounds exception.
String val = f < recordColumnCount ? rowValues[f] : null;
validateTableValue(issues, tableId, rowIndex, rowValues, val, fieldsFound, fieldsFound[f], gtfsFeed);
validateTableValue(issues, tableId, rowIndex, rowValues, val, fieldsFound, specField, gtfsFeed, gtfsRoutes, tableIsDirections);
}
}
rowIndex++;
}
csvReader.close();

if (tableIsDirections && !gtfsRoutes.isEmpty()) {
// After we're done validating all the table values, check if every route was checked off in directions.txt
issues.add(new ValidationIssue(tableId, null, -1, "Directions file doesn't define directions for all routes listed in routes.txt"));
}
// Add issues for wrong number of columns and for empty rows after processing all rows.
// Note: We considered adding an issue for each row, but opted for the single error approach because there's no
// concept of a row-level issue in the UI right now. So we would potentially need to add that to the UI
Expand Down Expand Up @@ -205,7 +219,9 @@ private static void validateTableValue(
String value,
JsonNode[] specFieldsFound,
JsonNode specField,
GTFSFeed gtfsFeed
GTFSFeed gtfsFeed,
Set<String> gtfsRoutes,
boolean tableIsDirections
) {
if (specField == null) return;
String fieldName = specField.get("name").asText();
Expand Down Expand Up @@ -300,6 +316,8 @@ private static void validateTableValue(
break;
}

// "Check off" the route_id in directions.txt from the list to verify every route id has a direction
if (tableIsDirections && fieldName.equals("route_id")) gtfsRoutes.remove(value);
}

/** Construct missing ID text for validation issue description. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1292,6 +1292,10 @@ public OtpRunnerManifest createAndUploadManifestAndConfigs(boolean graphAlreadyB
}
}

// upload shared stops file
String sharedStopsConfig = this.deployment.parentProject().sharedStopsConfig;
if (sharedStopsConfig != null) addStringContentsAsBaseFolderDownload(manifest, "shared_stops.csv", sharedStopsConfig);

// upload otp-runner manifest to s3
try {
ObjectMapper mapper = new ObjectMapper();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package com.conveyal.datatools.manager.jobs.validation;

import com.conveyal.datatools.manager.models.Project;
import com.conveyal.gtfs.error.NewGTFSError;
import com.conveyal.gtfs.error.NewGTFSErrorType;
import com.conveyal.gtfs.error.SQLErrorStorage;
import com.conveyal.gtfs.loader.Feed;
import com.conveyal.gtfs.model.Stop;
import com.conveyal.gtfs.validator.FeedValidator;
import com.csvreader.CsvReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;


public class SharedStopsValidator extends FeedValidator {
private static final Logger LOG = LoggerFactory.getLogger(SharedStopsValidator.class);

Feed feed;
String feedId;
Project project;

public SharedStopsValidator(Project project, String feedId) {
super(null, null);
this.project = project;
this.feedId = feedId;
}

// This method can only be run on a SharedStopsValidator that has been set up with a project only
public SharedStopsValidator buildSharedStopsValidator(Feed feed, SQLErrorStorage errorStorage) {
if (this.project == null) {
throw new RuntimeException("Shared stops validator can not be called because no project has been set!");
}
if (this.feedId == null) {
throw new RuntimeException("Shared stops validator can not be called because no feed ID has been set!");
}
return new SharedStopsValidator(feed, errorStorage, this.project, this.feedId);
}
public SharedStopsValidator(Feed feed, SQLErrorStorage errorStorage, Project project, String feedId) {
super(feed, errorStorage);
this.feed = feed;
this.project = project;
this.feedId = feedId;
}

@Override
public void validate() {
String config = project.sharedStopsConfig;
if (config == null) {
return;
}

CsvReader configReader = CsvReader.parse(config);

// TODO: pull indicies from the CSV header
final int STOP_GROUP_ID_INDEX = 0;
final int STOP_ID_INDEX = 2;
final int IS_PRIMARY_INDEX = 3;
final int FEED_ID_INDEX = 1;

// Build list of stop Ids.
List<String> stopIds = new ArrayList<>();
List<Stop> stops = new ArrayList<>();

for (Stop stop : feed.stops) {
stops.add(stop);
stopIds.add(stop.stop_id);
}

// Initialize hashmaps to hold duplication info
HashMap<String, Set<String>> stopGroupStops = new HashMap<>();
HashSet<String> stopGroupsWithPrimaryStops = new HashSet<>();

try {
while (configReader.readRecord()) {
String stopGroupId = configReader.get(STOP_GROUP_ID_INDEX);
String stopId = configReader.get(STOP_ID_INDEX);
String sharedStopFeedId = configReader.get(FEED_ID_INDEX);

if (stopId.equals("stop_id")) {
// Swallow header row
continue;
}

stopGroupStops.putIfAbsent(stopGroupId, new HashSet<>());
Set<String> seenStopIds = stopGroupStops.get(stopGroupId);

// Check for SS_01 (stop id appearing in multiple stop groups)
if (seenStopIds.contains(stopId)) {
registerError(stops
.stream()
.filter(stop -> stop.stop_id.equals(stopId))
.findFirst()
.orElse(stops.get(0)), NewGTFSErrorType.MULTIPLE_SHARED_STOPS_GROUPS
);
} else {
seenStopIds.add(stopId);
}

// Check for SS_02 (multiple primary stops per stop group)
if (stopGroupsWithPrimaryStops.contains(stopGroupId)) {
registerError(NewGTFSError.forFeed(NewGTFSErrorType.SHARED_STOP_GROUP_MUTLIPLE_PRIMARY_STOPS, stopGroupId));
} else if (configReader.get(IS_PRIMARY_INDEX).equals("true")) {
stopGroupsWithPrimaryStops.add(stopGroupId);
}

// Check for SS_03 (stop_id referenced doesn't exist)
// Make sure this error is only returned if we are inside the feed that is being checked
if (feedId.equals(sharedStopFeedId) && !stopIds.contains(stopId)) {
registerError(NewGTFSError.forFeed(NewGTFSErrorType.SHARED_STOP_GROUP_ENTITY_DOES_NOT_EXIST, stopId));
}
}
} catch (IOException e) { LOG.error(e.toString()); }
finally {
configReader.close();
}


}
}
Loading

0 comments on commit 85f57c9

Please sign in to comment.