Skip to content

Commit

Permalink
feat: Check for new version of app during validation (#1176)
Browse files Browse the repository at this point in the history
* Add logic to resolve the current app version and notify if a new version is available.

* Bump test timeout.

* Try again on that unit test.

* Various fixes in response to PR comments.

* Update to point to Releases page for new downloads.

* Format code.
  • Loading branch information
bdferris-v2 authored Jun 3, 2022
1 parent 187be96 commit cec914e
Show file tree
Hide file tree
Showing 16 changed files with 504 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,15 @@
package org.mobilitydata.gtfsvalidator.app.gui;

import com.google.common.flogger.FluentLogger;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
Expand Down Expand Up @@ -53,12 +57,15 @@ public class GtfsValidatorApp extends JFrame {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();

private static final Dimension VERTICAL_GAP = new Dimension(0, 40);
private static final Dimension TEXT_GAP = new Dimension(0, 10);

private static final Font BOLD_FONT = createBoldFont();

private final JTextField gtfsInputField = new JTextField();
private final JTextField outputDirectoryField = new JTextField();

private final JPanel newVersionAvailablePanel = new JPanel();

private final JButton validateButton = new JButton();

private final JPanel advancedOptionsPanel = new JPanel();
Expand Down Expand Up @@ -120,6 +127,11 @@ void addPreValidationCallback(Runnable callback) {
preValidationCallbacks.add(callback);
}

public void showNewVersionAvailable() {
newVersionAvailablePanel.setVisible(true);
pack();
}

void constructUI() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

Expand All @@ -133,6 +145,7 @@ void constructUI() {
constructOutputDirectorySection(panel);
panel.add(Box.createRigidArea(VERTICAL_GAP));
constructAdvancedOptionsPanel(panel);
constructNewVersionAvailablePanel(panel);
constructValidateButton(panel);

// Ensure everything is left-aligned in the main application panel.
Expand Down Expand Up @@ -247,6 +260,32 @@ private void constructAdvancedOptionsPanel(JPanel parent) {
advancedOptionsPanel.setVisible(false);
}

private void constructNewVersionAvailablePanel(JPanel parent) {
// Panel is initially not shown.
newVersionAvailablePanel.setVisible(false);
newVersionAvailablePanel.setLayout(
new BoxLayout(newVersionAvailablePanel, BoxLayout.PAGE_AXIS));
parent.add(newVersionAvailablePanel);

newVersionAvailablePanel.add(
createLabelWithFont(bundle.getString("new_version_available"), BOLD_FONT));
newVersionAvailablePanel.add(Box.createRigidArea(TEXT_GAP));

JLabel download_link = new JLabel(bundle.getString("download_here"));
download_link.setForeground(Color.BLUE.darker());
download_link.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
download_link.addMouseListener(
new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
validationDisplay.handleBrowseToHomepage();
}
});
newVersionAvailablePanel.add(download_link);

newVersionAvailablePanel.add(Box.createRigidArea(VERTICAL_GAP));
}

private void constructValidateButton(JPanel panel) {
JPanel validateButtonPanel = new JPanel();
validateButtonPanel.setLayout(new BoxLayout(validateButtonPanel, BoxLayout.LINE_AXIS));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import org.mobilitydata.gtfsvalidator.runner.ValidationRunner;
import org.mobilitydata.gtfsvalidator.util.VersionResolver;

/**
* The main entry point for the GUI application.
Expand Down Expand Up @@ -74,9 +75,10 @@ private static void createAndShowGUI(String[] args) {
logger.atSevere().withCause(e).log("Error setting system look-and-feel");
}

VersionResolver resolver = new VersionResolver();
ValidationDisplay display = new ValidationDisplay();
MonitoredValidationRunner runner =
new MonitoredValidationRunner(new ValidationRunner(), display);
new MonitoredValidationRunner(new ValidationRunner(resolver), display);
GtfsValidatorApp app = new GtfsValidatorApp(runner, display);
app.constructUI();

Expand All @@ -88,6 +90,14 @@ private static void createAndShowGUI(String[] args) {
prefs.savePreferences(app);
});

// Check to see if there is a new version of the app available.
resolver.addCallback(
(versionInfo) -> {
if (versionInfo.updateAvailable()) {
SwingUtilities.invokeLater(() -> app.showNewVersionAvailable());
}
});

// On Windows, if you drag a file onto the application shortcut, it will
// execute the app with the file as the first command-line argument. This
// doesn't appear to work on Mac OS.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.google.common.flogger.FluentLogger;
import java.awt.Desktop;
import java.io.IOException;
import java.net.URI;
import java.nio.file.Path;
import javax.swing.JOptionPane;
import org.mobilitydata.gtfsvalidator.runner.ValidationRunner;
Expand Down Expand Up @@ -43,4 +44,13 @@ void handleError() {
JOptionPane.ERROR_MESSAGE);
System.exit(-1);
}

void handleBrowseToHomepage() {
try {
Desktop.getDesktop()
.browse(URI.create("https://github.com/MobilityData/gtfs-validator/releases"));
} catch (IOException ex) {
logger.atSevere().withCause(ex).log("Error opening webpage");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ output_directory=Output Directory:
choose_output_directory=Choose Output Directory...
output_directory_description=The validation report will be written here.

new_version_available=A new version of the Canonical GTFS Schedule validator is available!
download_here=Download here to get the latest/best validation results.

advanced=Advanced
advanced_options=Advanced Options
number_of_threads=Number of threads used to run the validator:
Expand Down
7 changes: 7 additions & 0 deletions core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@ dependencies {
testImplementation 'com.google.truth.extensions:truth-java8-extension:1.0.1'
}

jar {
manifest {
attributes('Implementation-Title': 'gtfs-validator-core',
'Implementation-Version': project.version)
}
}

publishing {
publications {
mavenJava(MavenPublication) {
Expand Down
5 changes: 4 additions & 1 deletion main/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,11 @@ dependencies {
implementation 'com.univocity:univocity-parsers:2.9.0'
implementation 'com.google.geometry:s2-geometry:2.0.0'
implementation 'org.thymeleaf:thymeleaf:3.0.15.RELEASE'
implementation 'com.jcabi:jcabi-manifests:1.1'
implementation 'io.github.classgraph:classgraph:4.8.146'
testImplementation group: 'junit', name: 'junit', version: '4.13'
testImplementation 'com.google.truth:truth:1.0.1'
testImplementation 'com.google.truth.extensions:truth-java8-extension:1.0.1'
testImplementation 'org.mockito:mockito-core:4.5.1'
}

test {
Expand All @@ -91,6 +92,8 @@ test {
testLogging {
events "passed", "skipped", "failed"
}

systemProperty 'gtfsValidatorVersionForTest', project.version
}

// Share the test report data to be aggregated for the whole project
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import java.nio.file.Paths;
import org.mobilitydata.gtfsvalidator.notice.NoticeSchemaGenerator;
import org.mobilitydata.gtfsvalidator.runner.ValidationRunner;
import org.mobilitydata.gtfsvalidator.util.VersionResolver;

/** The main entry point for GTFS Validator CLI. */
public class Main {
Expand All @@ -41,14 +42,16 @@ public static void main(String[] argv) {
}

try {
ValidationRunner runner = new ValidationRunner();
ValidationRunner runner = new ValidationRunner(new VersionResolver());
if (runner.run(args.toConfig()) != ValidationRunner.Status.SUCCESS) {
System.exit(-1);
}
} catch (Exception ex) {
logger.atSevere().withCause(ex).log("Error running validation");
System.exit(-1);
}

System.exit(0);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

package org.mobilitydata.gtfsvalidator.report;

import com.jcabi.manifests.Manifests;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Path;
Expand All @@ -25,6 +24,7 @@
import org.mobilitydata.gtfsvalidator.notice.NoticeContainer;
import org.mobilitydata.gtfsvalidator.report.model.ReportSummary;
import org.mobilitydata.gtfsvalidator.runner.ValidationRunnerConfig;
import org.mobilitydata.gtfsvalidator.util.VersionInfo;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import org.thymeleaf.templatemode.TemplateMode;
Expand All @@ -35,15 +35,17 @@ public class HtmlReportGenerator {

/** Generate the HTML report using the class ReportSummary and the notice container. */
public void generateReport(
NoticeContainer noticeContainer, ValidationRunnerConfig config, Path reportPath)
NoticeContainer noticeContainer,
ValidationRunnerConfig config,
VersionInfo versionInfo,
Path reportPath)
throws IOException {
TemplateEngine templateEngine = new TemplateEngine();
ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver();
templateResolver.setTemplateMode(TemplateMode.HTML);
templateEngine.setTemplateResolver(templateResolver);

ReportSummary summary = new ReportSummary(noticeContainer);
String version = Manifests.read("Implementation-Version");
ReportSummary summary = new ReportSummary(noticeContainer, versionInfo);

SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd 'at' HH:mm:ss z");
Date now = new Date(System.currentTimeMillis());
Expand All @@ -52,7 +54,6 @@ public void generateReport(
Context context = new Context();
context.setVariable("summary", summary);
context.setVariable("config", config);
context.setVariable("version", version);
context.setVariable("date", date);

try (FileWriter writer = new FileWriter(reportPath.toFile())) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,16 @@
import org.mobilitydata.gtfsvalidator.notice.Notice;
import org.mobilitydata.gtfsvalidator.notice.NoticeContainer;
import org.mobilitydata.gtfsvalidator.notice.SeverityLevel;
import org.mobilitydata.gtfsvalidator.util.VersionInfo;

/** ReportSummary is the class containing the summary methods for the HTML report. */
public class ReportSummary {
private final NoticeContainer container;
private final Map<SeverityLevel, Long> severityCounts;
private final Map<SeverityLevel, Map<String, List<NoticeView>>> noticesMap;
private final VersionInfo versionInfo;

public ReportSummary(NoticeContainer container) {
public ReportSummary(NoticeContainer container, VersionInfo versionInfo) {
this.container = container;
this.severityCounts =
container.getValidationNotices().stream()
Expand All @@ -44,6 +46,7 @@ public ReportSummary(NoticeContainer container) {
NoticeView::getSeverityLevel,
LinkedHashMap::new,
Collectors.groupingBy(NoticeView::getCode, TreeMap::new, Collectors.toList())));
this.versionInfo = versionInfo;
}

/**
Expand Down Expand Up @@ -93,4 +96,12 @@ public long getWarningCount() {
public long getInfoCount() {
return severityCounts.getOrDefault(SeverityLevel.INFO, 0L);
}

public String getVersion() {
return versionInfo.currentVersion().orElse(null);
}

public boolean isNewVersionOfValidatorAvailable() {
return versionInfo.updateAvailable();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.Duration;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import org.mobilitydata.gtfsvalidator.input.CurrentDateTime;
Expand All @@ -35,6 +36,8 @@
import org.mobilitydata.gtfsvalidator.report.HtmlReportGenerator;
import org.mobilitydata.gtfsvalidator.table.GtfsFeedContainer;
import org.mobilitydata.gtfsvalidator.table.GtfsFeedLoader;
import org.mobilitydata.gtfsvalidator.util.VersionInfo;
import org.mobilitydata.gtfsvalidator.util.VersionResolver;
import org.mobilitydata.gtfsvalidator.validator.DefaultValidatorProvider;
import org.mobilitydata.gtfsvalidator.validator.ValidationContext;
import org.mobilitydata.gtfsvalidator.validator.ValidatorLoader;
Expand All @@ -46,6 +49,8 @@ public class ValidationRunner {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private static final String GTFS_ZIP_FILENAME = "gtfs.zip";

private final VersionResolver versionResolver;

public enum Status {
// Indicates validation successfully completed, but doesn't imply the
// feed itself is valid.
Expand All @@ -58,7 +63,17 @@ public enum Status {
EXCEPTION
}

public ValidationRunner(VersionResolver versionResolver) {
this.versionResolver = versionResolver;
}

public Status run(ValidationRunnerConfig config) {
VersionInfo versionInfo = versionResolver.getVersionInfoWithTimeout(Duration.ofSeconds(5));
logger.atInfo().log("VersionInfo: %s", versionInfo);
if (versionInfo.updateAvailable()) {
logger.atInfo().log("A new version of the validator is available!");
}

ValidatorLoader validatorLoader = null;
try {
validatorLoader = new ValidatorLoader();
Expand Down Expand Up @@ -87,7 +102,7 @@ public Status run(ValidationRunnerConfig config) {
noticeContainer.addSystemError(new URISyntaxError(e));
}
if (gtfsInput == null) {
exportReport(noticeContainer, config);
exportReport(noticeContainer, config, versionInfo);
if (!noticeContainer.getSystemErrors().isEmpty()) {
return Status.SYSTEM_ERRORS;
} else {
Expand All @@ -110,7 +125,7 @@ public Status run(ValidationRunnerConfig config) {
closeGtfsInput(gtfsInput, noticeContainer);

// Output
exportReport(noticeContainer, config);
exportReport(noticeContainer, config, versionInfo);
printSummary(startNanos, feedContainer);
return Status.SUCCESS;
}
Expand Down Expand Up @@ -191,7 +206,7 @@ private static Gson createGson(boolean pretty) {

/** Generates and exports reports for both validation notices and system errors reports. */
public static void exportReport(
final NoticeContainer noticeContainer, final ValidationRunnerConfig config) {
NoticeContainer noticeContainer, ValidationRunnerConfig config, VersionInfo versionInfo) {
if (!Files.exists(config.outputDirectory())) {
try {
Files.createDirectories(config.outputDirectory());
Expand All @@ -207,7 +222,10 @@ public static void exportReport(
config.outputDirectory().resolve(config.validationReportFileName()),
gson.toJson(noticeContainer.exportValidationNotices()).getBytes(StandardCharsets.UTF_8));
generator.generateReport(
noticeContainer, config, config.outputDirectory().resolve(config.htmlReportFileName()));
noticeContainer,
config,
versionInfo,
config.outputDirectory().resolve(config.htmlReportFileName()));
Files.write(
config.outputDirectory().resolve(config.systemErrorsReportFileName()),
gson.toJson(noticeContainer.exportSystemErrors()).getBytes(StandardCharsets.UTF_8));
Expand Down
Loading

0 comments on commit cec914e

Please sign in to comment.