
view.reporting.ReportingDialog Maven / Gradle / Ivy
package view.reporting;
/*-
* #%L
* FOKProjects Common
* %%
* Copyright (C) 2016 - 2017 Frederik Kammel
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import com.amazonaws.AmazonClientException;
import com.amazonaws.AmazonServiceException;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.s3.model.PutObjectRequest;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import common.AWSS3Utils;
import common.Common;
import common.internet.Internet;
import javafx.application.Platform;
import javafx.collections.ObservableList;
import javafx.embed.swing.SwingFXUtils;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.image.WritableImage;
import javafx.scene.input.InputEvent;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
import logging.FOKLogger;
import reporting.GitHubIssue;
import javax.imageio.ImageIO;
import java.awt.*;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Collections;
import java.util.ResourceBundle;
import java.util.logging.Level;
/**
* A web view that shows a reporting page of GitReports, a website to submit anonymous GitHub issues.
* Please note: This class is *NOT* thread safe.
*
* @author frede
* @since 0.0.22
*/
@SuppressWarnings({"SameParameterValue", "ConstantConditions"})
public class ReportingDialog {
private static final String s3BucketName = "vatbubissuelogs2";
private static final ResourceBundle bundle = ResourceBundle.getBundle("view.reporting.ReportingDialog");
/**
* The color in which a required text box appears in case it is not filled in
*/
private static final String errorColor = "#dd4444";
/**
* The default color of a text box
*/
private static final String defaultColor = "inherit";
@SuppressWarnings("CanBeFinal")
private static URL defaultGitReportsURL = null;
private static Stage stage;
private static URL logInfoURL;
private static URL privacyInfoURL;
private static Scene screenshotScene;
@SuppressWarnings("CanBeFinal")
private static URL defaultLogInfoURL;
@SuppressWarnings("CanBeFinal")
private static URL defaultPrivacyURL;
private static URL gitReportsBaseURL;
private static GitHubIssue gitHubIssue;
static {
try {
defaultGitReportsURL = new URL("https://vatbubgitreports.herokuapp.com/");
defaultLogInfoURL = new URL("https://github.com/vatbub/common/wiki/Log-and-screenshot-info-and-privacy-policy-for-submitting-issues#logs");
defaultPrivacyURL = new URL("https://github.com/vatbub/common/wiki/Log-and-screenshot-info-and-privacy-policy-for-submitting-issues#privacy-policy");
} catch (MalformedURLException e) {
// will not happen
FOKLogger.log(ReportingDialog.class.getName(), Level.SEVERE, "An error occurred due to a typo in a constant", e);
}
}
private boolean submitButtonWasPressed;
@FXML
private AnchorPane anchorPane;
@FXML
private TextField name;
@FXML
private TextField mail;
@FXML
private TextField title;
@FXML
private TextArea message;
@FXML
private CheckBox uploadLogsCheckbox;
@FXML
private Button logInfoButton;
@FXML
private Button sendButton;
@FXML
private Button screenshotInfoButton;
@FXML
private CheckBox uploadScreenshot;
/**
* Creates a new {@code ReportingDialog} but keeps previous settings. Keep in mind that this class is *NOT* thread safe.
*/
@SuppressWarnings("unused")
public ReportingDialog() {
}
/**
* Creates a new {@code ReportingDialog} with the specified settings. Screenshot submission will be disabled. Keep in mind that this class is *NOT* thread safe.
*
* @param logInfoURL The url that is opened in the browser if the user clicks the {@code Get more info}-button next to the {@code uploadLogs}-Checkbox
* @param privacyURL The url that is opened in the browser if the user clicks the {@code Privace statement}-button.
*/
@SuppressWarnings("unused")
public ReportingDialog(URL logInfoURL, URL privacyURL) {
this(logInfoURL, privacyURL, null);
}
/**
* Creates a new {@code ReportingDialog} but keeps previous settings. The possibility to attach a screenshot to the issue is offered to the user. If the user selects that option, a screenshot from the specified {@code Scene} is taken and attached to the issue.
*
Keep in mind that this class is *NOT* thread safe.
*
* @param screenshotScene The {@code Scene} to take a screenshot from if the user selects that option
*/
@SuppressWarnings("unused")
public ReportingDialog(Scene screenshotScene) {
this(defaultLogInfoURL, defaultPrivacyURL, screenshotScene);
}
/**
* Creates a new {@code ReportingDialog} with the specified settings. The possibility to attach a screenshot to the issue is offered to the user. If the user selects that option, a screenshot from the specified {@code Scene} is taken and attached to the issue.
*
Keep in mind that this class is *NOT* thread safe.
*
* @param logInfoURL The url that is opened in the browser if the user clicks the {@code Get more info}-button next to the {@code uploadLogs}-Checkbox
* @param privacyURL The url that is opened in the browser if the user clicks the {@code Privace statement}-button.
* @param screenshotScene The {@code Scene} to take a screenshot from if the user selects that option
*/
@SuppressWarnings("unused")
public ReportingDialog(URL logInfoURL, URL privacyURL, Scene screenshotScene) {
ReportingDialog.logInfoURL = logInfoURL;
ReportingDialog.screenshotScene = screenshotScene;
ReportingDialog.privacyInfoURL = privacyURL;
}
/**
* Shows the dialog and submits the issue to the specified repository if the {@code vatbubgitreports}-server has write access to it.
*
* @param userName The repo owner of the repo to send the issue to
* @param repoName The name of the repo to send the issue to
*/
@SuppressWarnings("unused")
public void show(String userName, String repoName) {
String windowTitle = null;
show(windowTitle, userName, repoName, null);
}
/**
* Shows the dialog and submits the issue to the specified repository if the {@code vatbubgitreports}-server has write access to it. A exception is attached to the issue.
*
* @param userName The repo owner of the repo to send the issue to
* @param repoName The name of the repo to send the issue to
* @param e The exception to attach to the issue
*/
@SuppressWarnings("unused")
public void show(String userName, String repoName, Throwable e) {
show(defaultGitReportsURL, userName, repoName, e);
}
/**
* Shows the dialog and submits the issue to the specified repository if the specified {@code vatbubgitreports}-server has write access to it.
*
* @param gitReportsBaseURL The url of the {@code vatbubgitreports}-server to use to submit the issue
* @param userName The repo owner of the repo to send the issue to
* @param repoName The name of the repo to send the issue to
*/
@SuppressWarnings("unused")
public void show(URL gitReportsBaseURL, String userName, String repoName) {
String windowTitle = null;
show(windowTitle, gitReportsBaseURL, userName, repoName);
}
/**
* Shows the dialog and submits the issue to the specified repository if the specified {@code vatbubgitreports}-server has write access to it. A exception is attached to the issue.
*
* @param gitReportsBaseURL The url of the {@code vatbubgitreports}-server to use to submit the issue
* @param userName The repo owner of the repo to send the issue to
* @param repoName The name of the repo to send the issue to
* @param e The exception to attach to the issue
*/
public void show(URL gitReportsBaseURL, String userName, String repoName, Throwable e) {
String windowTitle = null;
show(windowTitle, gitReportsBaseURL, userName, repoName, e);
}
/**
* Shows the dialog and submits the issue to the specified repository if the {@code vatbubgitreports}-server has write access to it.
*
* @param windowTitle The title of the window
* @param userName The repo owner of the repo to send the issue to
* @param repoName The name of the repo to send the issue to
*/
@SuppressWarnings("unused")
public void show(String windowTitle, String userName, String repoName) {
show(windowTitle, defaultGitReportsURL, userName, repoName);
}
/**
* Shows the dialog and submits the issue to the specified repository if the {@code vatbubgitreports}-server has write access to it. A exception is attached to the issue.
*
* @param windowTitle The title of the window
* @param userName The repo owner of the repo to send the issue to
* @param repoName The name of the repo to send the issue to
* @param e The exception to attach to the issue
*/
public void show(String windowTitle, String userName, String repoName, Throwable e) {
show(windowTitle, defaultGitReportsURL, userName, repoName, e);
}
/**
* Shows the dialog and submits the issue to the specified repository if the specified {@code vatbubgitreports}-server has write access to it.
*
* @param windowTitle The title of the window
* @param gitReportsBaseURL The url of the {@code vatbubgitreports}-server to use to submit the issue
* @param userName The repo owner of the repo to send the issue to
* @param repoName The name of the repo to send the issue to
*/
public void show(String windowTitle, URL gitReportsBaseURL, String userName, String repoName) {
show(windowTitle, gitReportsBaseURL, userName, repoName, null);
}
/**
* Shows the dialog and submits the issue to the specified repository if the specified {@code vatbubgitreports}-server has write access to it. A exception is attached to the issue.
*
* @param windowTitle The title of the window
* @param gitReportsBaseURL The url of the {@code vatbubgitreports}-server to use to submit the issue
* @param userName The repo owner of the repo to send the issue to
* @param repoName The name of the repo to send the issue to
* @param e The exception to attach to the issue
*/
public void show(String windowTitle, URL gitReportsBaseURL, String userName, String repoName, Throwable e) {
FOKLogger.info(ReportingDialog.class.getName(), "Showing the ReportingDialog...");
stage = new Stage();
Parent root;
try {
gitHubIssue = new GitHubIssue();
gitHubIssue.setThrowable(e);
gitHubIssue.setToRepo_Owner(userName);
gitHubIssue.setToRepo_RepoName(repoName);
ReportingDialog.gitReportsBaseURL = gitReportsBaseURL;
root = FXMLLoader.load(ReportingDialog.class.getResource("/view/reporting/ReportingDialog.fxml"), bundle);
Scene scene = new Scene(root);
scene.getStylesheets().add(this.getClass().getResource("/view/reporting/ReportingDialog.css").toExternalForm());
stage.setMinWidth(scene.getRoot().minWidth(0) + 70);
stage.setMinHeight(scene.getRoot().minHeight(0) + 70);
stage.setScene(scene);
stage.setTitle(windowTitle);
stage.show();
} catch (IOException e2) {
FOKLogger.log(ReportingDialog.class.getName(), Level.SEVERE, "An error occurred", e2);
}
}
@FXML
void initialize() {
anchorPane.prefHeightProperty().addListener((observableValue, number, number2) -> stage.setHeight((double) number2 + 85));
// disable screenshot upload if no screenshotScene was given
if (screenshotScene == null) {
uploadScreenshot.setDisable(true);
screenshotInfoButton.setDisable(true);
}
// check log upload if an exception is submitted
if (gitHubIssue.getThrowable() != null) {
uploadLogsCheckbox.setSelected(true);
if (screenshotScene != null) {
uploadScreenshot.setSelected(true);
}
}
}
@FXML
void uploadLogsCheckboxOnACtion(ActionEvent event) {
if (gitHubIssue.getThrowable() != null && !uploadLogsCheckbox.isSelected()) {
(new Alert(Alert.AlertType.WARNING, bundle.getString("uploadLogsExceptionInfo"), ButtonType.OK)).show();
}
}
@FXML
void logInfoButtonOnAction(ActionEvent event) {
try {
Desktop.getDesktop().browse(logInfoURL.toURI());
} catch (IOException | URISyntaxException e) {
FOKLogger.log(ReportingDialog.class.getName(), Level.SEVERE, "An error occurred", e);
}
}
@FXML
void screenshotInfoButtonOnAction(ActionEvent event) {
try {
Desktop.getDesktop().browse(privacyInfoURL.toURI());
} catch (IOException | URISyntaxException e) {
FOKLogger.log(ReportingDialog.class.getName(), Level.SEVERE, "An error occurred", e);
}
}
@FXML
void titleKeyReleased(InputEvent event) {
if (submitButtonWasPressed) {
validate(title);
}
}
@FXML
void messageKeyReleased(InputEvent event) {
if (submitButtonWasPressed) {
validate(message);
}
}
@FXML
void sendButtonOnAction(ActionEvent event) {
submitButtonWasPressed = true;
boolean validationErrors = false;
if (validate(message)) {
validationErrors = true;
}
if (validate(title)) {
validationErrors = true;
}
if (validationErrors) {
// something is not ok
new Alert(Alert.AlertType.ERROR, bundle.getString("error")).show();
return;
}
// Everything ok
// disable all of the controls
anchorPane.setDisable(true);
// show the progress dialog
ReportingDialogUploadProgress.show();
// take a screenshot
WritableImage imageTemp = null;
if (uploadScreenshot.isSelected()) {
imageTemp = screenshotScene.snapshot(null);
}
final WritableImage image = imageTemp;
Thread issueUploadThread = new Thread(() -> {
if (uploadLogsCheckbox.isSelected()) {
// upload the logs to aws
Platform.runLater(() -> ReportingDialogUploadProgress.getStatusLabel().setText(bundle.getString("uploadingLogs")));
String awsFileName = Common.getAppName() + "/logs/" + FOKLogger.getLogFileName();
gitHubIssue.setLogLocation(awsFileName);
// upload the logs to s3
try {
AmazonS3 s3Client = AmazonS3ClientBuilder.standard().withCredentials(new AWSStaticCredentialsProvider(Common.getAWSCredentials())).build();
if (!AWSS3Utils.doesBucketExist(s3Client, s3BucketName)) {
// create bucket
FOKLogger.info(ReportingDialog.class.getName(), "Creating aws s3 bucket " + s3BucketName);
s3Client.createBucket(s3BucketName);
}
// Upload the log file
FOKLogger.info(ReportingDialog.class.getName(), "Uploading log file to aws: " + awsFileName);
s3Client.putObject(new PutObjectRequest(s3BucketName, awsFileName, new File(FOKLogger.getLogFilePathAndName())));
FOKLogger.info(ReportingDialog.class.getName(), "Upload completed");
} catch (AmazonServiceException ase) {
FOKLogger.log(ReportingDialog.class.getName(), Level.SEVERE, "Caught AmazonServiceException which means that the request made it to S3, but was rejected with an error response", ase);
} catch (AmazonClientException ace) {
FOKLogger.log(ReportingDialog.class.getName(), Level.SEVERE, "Caught an AmazonClientException, which means the client encountered an internal error while trying to communicate with S3, such as not being able to access the network.", ace);
}
}
if (uploadScreenshot.isSelected()) {
try {
// save it to the disk
File screenshotFile = new File(Common.getAndCreateAppDataPath() + "screenshotForIssueUpload.png");
ImageIO.write(SwingFXUtils.fromFXImage(image, null), "png", screenshotFile);
// upload the screenshot to aws
Platform.runLater(() -> ReportingDialogUploadProgress.getStatusLabel().setText(bundle.getString("uploadingScreenshot")));
String awsFileName = Common.getAppName() + "/screenshots/screenshot_" + Common.getAppName() + "_" + Common.getLaunchTimeStamp() + ".png";
gitHubIssue.setScreenshotLocation(awsFileName);
// upload the logs to s3
AmazonS3Client s3Client = new AmazonS3Client(Common.getAWSCredentials());
if (!AWSS3Utils.doesBucketExist(s3Client, s3BucketName)) {
// create bucket
FOKLogger.info(ReportingDialog.class.getName(), "Creating aws s3 bucket " + s3BucketName);
s3Client.createBucket(s3BucketName);
}
// Upload the screenshot file
FOKLogger.info(ReportingDialog.class.getName(), "Uploading screenshot to aws: " + awsFileName);
s3Client.putObject(new PutObjectRequest(s3BucketName, awsFileName, screenshotFile));
FOKLogger.info(ReportingDialog.class.getName(), "Upload completed");
} catch (AmazonServiceException ase) {
FOKLogger.log(ReportingDialog.class.getName(), Level.SEVERE, "Caught AmazonServiceException which means that the request made it to S3, but was rejected with an error response", ase);
} catch (AmazonClientException ace) {
FOKLogger.log(ReportingDialog.class.getName(), Level.SEVERE, "Caught an AmazonClientException, which means the client encountered an internal error while trying to communicate with S3, such as not being able to access the network.", ace);
} catch (IOException e) {
FOKLogger.log(ReportingDialog.class.getName(), Level.SEVERE, "Could not write temporary screenshot file, screenshot will not be uploaded", e);
}
}
// post the issue
Platform.runLater(() -> ReportingDialogUploadProgress.getStatusLabel().setText(bundle.getString("uploadingIssue")));
gitHubIssue.setBody(message.getText());
gitHubIssue.setTitle(title.getText());
gitHubIssue.setReporterEmail(mail.getText());
gitHubIssue.setReporterName(name.getText());
try {
HttpURLConnection connection = (HttpURLConnection) gitReportsBaseURL.openConnection();
// build the request body
Gson gson = new GsonBuilder().setPrettyPrinting().create();
String query = gson.toJson(gitHubIssue);
// request header
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/json");
connection.setRequestProperty("Content-Encoding", "UTF-8");
connection.setRequestProperty("Content-Length", Integer.toString(query.length()));
connection.setDoOutput(true);
connection.getOutputStream().write(query.getBytes("UTF8"));
connection.getOutputStream().flush();
connection.getOutputStream().close();
Platform.runLater(() -> {
ReportingDialogUploadProgress.hide();
stage.hide();
// check the server response
int responseCode = 0;
StringBuilder responseBody = new StringBuilder();
try {
responseCode = connection.getResponseCode();
BufferedReader br;
if (200 <= connection.getResponseCode() && connection.getResponseCode() <= 299) {
br = new BufferedReader(new InputStreamReader((connection.getInputStream())));
} else {
br = new BufferedReader(new InputStreamReader((connection.getErrorStream())));
}
String line;
while ((line = br.readLine()) != null) {
responseBody.append(line);
}
} catch (IOException e) {
FOKLogger.log(ReportingDialog.class.getName(), Level.SEVERE, "An error occurred", e);
}
FOKLogger.info(ReportingDialog.class.getName(), "Submitted GitHub issue, response code from VatbubGitReports-Server: " + responseCode);
FOKLogger.info(ReportingDialog.class.getName(), "Response from Server:\n" + responseBody);
if (responseCode >= 400) {
// something went wrong
FOKLogger.log(ReportingDialog.class.getName(), Level.SEVERE, "Something went wrong when trying to upload the issue: " + responseCode + " " + Internet.getReasonForHTTPCode(responseCode));
new Alert(Alert.AlertType.ERROR, "Something went wrong when trying to upload the issue: " + responseCode + " " + Internet.getReasonForHTTPCode(responseCode)).show();
} else {
// everything worked
new Alert(Alert.AlertType.CONFIRMATION, bundle.getString("thanks")).show();
}
});
} catch (IOException e) {
FOKLogger.log(ReportingDialog.class.getName(), Level.SEVERE, "An error occurred", e);
}
});
issueUploadThread.setName("issueUploadThread");
issueUploadThread.start();
}
/**
* Validates the input of the given {@code TextField}
*
* @param tf The {@code TextField} to validate
* @return {@code true} if {@code tf} is NOT ok, {@code false} if it IS ok.
*/
private boolean validate(TextInputControl tf) {
boolean res = tf.getText().trim().length() == 0;
ObservableList styleClass = tf.getStyleClass();
if (res) {
if (!styleClass.contains("error")) {
tf.setStyle("-text-area-background: " + errorColor + ";");
styleClass.add("error");
}
} else {
// remove all occurrences:
tf.setStyle("-text-area-background: " + defaultColor + ";");
styleClass.removeAll(Collections.singleton("error"));
}
return res;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy