Merge branch 'develop' into feature/preprocess-properties

This commit is contained in:
Armin Schrenk
2023-06-21 10:31:43 +02:00
7 changed files with 372 additions and 142 deletions

View File

@@ -32,18 +32,15 @@ public class ErrorCode {
this.rootCauseSpecificFrames = rootCauseSpecificFrames;
}
// visible for testing
String methodCode() {
public String methodCode() {
return format(traceCode(rootCause, LATEST_FRAME));
}
// visible for testing
String rootCauseCode() {
public String rootCauseCode() {
return format(traceCode(rootCause, rootCauseSpecificFrames));
}
// visible for testing
String throwableCode() {
public String throwableCode() {
return format(traceCode(throwable, ALL_FRAMES));
}

View File

@@ -1,5 +1,7 @@
package org.cryptomator.ui.error;
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import org.cryptomator.common.Environment;
import org.cryptomator.common.ErrorCode;
import org.cryptomator.common.Nullable;
@@ -9,20 +11,34 @@ import javax.inject.Inject;
import javax.inject.Named;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.binding.BooleanExpression;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.fxml.FXML;
import javafx.scene.Scene;
import javafx.scene.input.Clipboard;
import javafx.scene.input.ClipboardContent;
import javafx.stage.Stage;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.Comparator;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
public class ErrorController implements FxController {
private static final String ERROR_CODES_URL = "https://gist.githubusercontent.com/cryptobot/accba9fb9555e7192271b85606f97230/raw/errorcodes.json";
private static final String SEARCH_URL_FORMAT = "https://github.com/cryptomator/cryptomator/discussions/categories/errors?discussions_q=category:Errors+%s";
private static final String REPORT_URL_FORMAT = "https://github.com/cryptomator/cryptomator/discussions/new?category=Errors&title=Error+%s&body=%s";
private static final String SEARCH_ERRORCODE_DELIM = " OR ";
@@ -46,16 +62,28 @@ public class ErrorController implements FxController {
private final Stage window;
private final Environment environment;
private BooleanProperty copiedDetails = new SimpleBooleanProperty();
private final BooleanProperty copiedDetails = new SimpleBooleanProperty();
private final ObjectProperty<ErrorDiscussion> matchingErrorDiscussion = new SimpleObjectProperty<>();
private final BooleanExpression errorSolutionFound = matchingErrorDiscussion.isNotNull();
private final BooleanProperty isLoadingHttpResponse = new SimpleBooleanProperty();
@Inject
ErrorController(Application application, @Named("stackTrace") String stackTrace, ErrorCode errorCode, @Nullable Scene previousScene, Stage window, Environment environment) {
ErrorController(Application application, @Named("stackTrace") String stackTrace, ErrorCode errorCode, @Nullable Scene previousScene, Stage window, Environment environment, ExecutorService executorService) {
this.application = application;
this.stackTrace = stackTrace;
this.errorCode = errorCode;
this.previousScene = previousScene;
this.window = window;
this.environment = environment;
isLoadingHttpResponse.set(true);
HttpClient httpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).build();
HttpRequest httpRequest = HttpRequest.newBuilder()//
.uri(URI.create(ERROR_CODES_URL))//
.build();
httpClient.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofInputStream())//
.thenAcceptAsync(this::loadHttpResponse, executorService)//
.whenCompleteAsync((r, e) -> isLoadingHttpResponse.set(false), Platform::runLater);
}
@FXML
@@ -70,6 +98,16 @@ public class ErrorController implements FxController {
window.close();
}
@FXML
public void showSolution() {
if (matchingErrorDiscussion.isNotNull().get()) {
var discussion = matchingErrorDiscussion.get();
if (discussion != null) {
application.getHostServices().showDocument(discussion.url);
}
}
}
@FXML
public void searchError() {
var searchTerm = URLEncoder.encode(getErrorCode().replace(ErrorCode.DELIM, SEARCH_ERRORCODE_DELIM), StandardCharsets.UTF_8);
@@ -95,13 +133,119 @@ public class ErrorController implements FxController {
Clipboard.getSystemClipboard().setContent(clipboardContent);
copiedDetails.set(true);
CompletableFuture.delayedExecutor(2, TimeUnit.SECONDS, Platform::runLater).execute(() -> {
copiedDetails.set(false);
});
CompletableFuture.delayedExecutor(2, TimeUnit.SECONDS, Platform::runLater).execute(() -> copiedDetails.set(false));
}
private void loadHttpResponse(HttpResponse<InputStream> response) {
if (response.statusCode() == 200) {
Map<String, ErrorDiscussion> errorDiscussionMap = new Gson().fromJson(//
new InputStreamReader(response.body(), StandardCharsets.UTF_8),//
new TypeToken<Map<String, ErrorDiscussion>>() {
}.getType());
if (errorDiscussionMap.values().stream().anyMatch(this::containsMethodCode)) {
Comparator<ErrorDiscussion> comp = this::compareByFullErrorCode;
Optional<ErrorDiscussion> value = errorDiscussionMap.values().stream().filter(this::containsMethodCode)//
.min(comp//
.thenComparing(this::compareByRootCauseCode)//
.thenComparing(this::compareIsAnswered)//
.thenComparing(this::compareUpvoteCount));
if (value.isPresent()) {
matchingErrorDiscussion.set(value.get());
}
}
}
}
/**
* Checks if an ErrorDiscussion object's title contains the error code's method code.
*
* @param errorDiscussion The ErrorDiscussion object to be checked.
* @return A boolean value indicating if the ErrorDiscussion object's title contains the error code's method code:
* - true if the title contains the method code,
* - false otherwise.
*/
public boolean containsMethodCode(ErrorDiscussion errorDiscussion) {
return errorDiscussion.title.contains(" " + errorCode.methodCode());
}
/**
* Compares two ErrorDiscussion objects based on their upvote counts and returns the result.
*
* @param ed1 The first ErrorDiscussion object.
* @param ed2 The second ErrorDiscussion object.
* @return An integer indicating which ErrorDiscussion object has a higher upvote count:
* - A positive value if ed2 has a higher upvote count than ed1,
* - A negative value if ed1 has a higher upvote count than ed2,
* - Or 0 if both upvote counts are equal.
*/
public int compareUpvoteCount(ErrorDiscussion ed1, ErrorDiscussion ed2) {
return Integer.compare(ed2.upvoteCount, ed1.upvoteCount);
}
/**
* Compares two ErrorDiscussion objects based on their answered status and returns the result.
*
* @param ed1 The first ErrorDiscussion object.
* @param ed2 The second ErrorDiscussion object.
* @return An integer indicating the answered status of the ErrorDiscussion objects:
* - A negative value (-1) if ed1 is considered answered and ed2 is considered unanswered,
* - A positive value (1) if ed1 is considered unanswered and ed2 is considered answered,
* - Or 0 if both ErrorDiscussion objects are considered answered or unanswered.
*/
public int compareIsAnswered(ErrorDiscussion ed1, ErrorDiscussion ed2) {
if (ed1.answer != null && ed2.answer == null) {
return -1;
} else if (ed1.answer == null && ed2.answer != null) {
return 1;
} else {
return 0;
}
}
/**
* Compares two ErrorDiscussion objects based on the presence of the full error code in their titles and returns the result.
*
* @param ed1 The first ErrorDiscussion object.
* @param ed2 The second ErrorDiscussion object.
* @return An integer indicating the comparison result based on the presence of the full error code in the titles:
* - A negative value (-1) if ed1 contains the full error code in the title and ed2 does not have a match,
* - A positive value (1) if ed1 does not have a match and ed2 contains the full error code in the title,
* - Or 0 if both ErrorDiscussion objects either contain the full error code or do not have a match in the titles.
*/
public int compareByFullErrorCode(ErrorDiscussion ed1, ErrorDiscussion ed2) {
if (ed1.title.contains(getErrorCode()) && !ed2.title.contains(getErrorCode())) {
return -1;
} else if (!ed1.title.contains(getErrorCode()) && ed2.title.contains(getErrorCode())) {
return 1;
} else {
return 0;
}
}
/**
* Compares two ErrorDiscussion objects based on the presence of the root cause code in their titles and returns the result.
*
* @param ed1 The first ErrorDiscussion object.
* @param ed2 The second ErrorDiscussion object.
* @return An integer indicating the comparison result based on the presence of the root cause code in the titles:
* - A negative value (-1) if ed1 contains the root cause code in the title and ed2 does not have a match,
* - A positive value (1) if ed1 does not have a match and ed2 contains the root cause code in the title,
* - Or 0 if both ErrorDiscussion objects either contain the root cause code or do not have a match in the titles.
*/
public int compareByRootCauseCode(ErrorDiscussion ed1, ErrorDiscussion ed2) {
String value = " " + errorCode.methodCode() + ErrorCode.DELIM + errorCode.rootCauseCode();
if (ed1.title.contains(value) && !ed2.title.contains(value)) {
return -1;
} else if (!ed1.title.contains(value) && ed2.title.contains(value)) {
return 1;
} else {
return 0;
}
}
/* Getter/Setter */
public boolean isPreviousScenePresent() {
return previousScene != null;
}
@@ -125,4 +269,21 @@ public class ErrorController implements FxController {
public boolean getCopiedDetails() {
return copiedDetails.get();
}
}
public BooleanExpression errorSolutionFoundProperty() {
return errorSolutionFound;
}
public boolean getErrorSolutionFound() {
return errorSolutionFound.get();
}
public BooleanProperty isLoadingHttpResponseProperty() {
return isLoadingHttpResponse;
}
public boolean getIsLoadingHttpResponse() {
return isLoadingHttpResponse.get();
}
}

View File

@@ -0,0 +1,13 @@
package org.cryptomator.ui.error;
public class ErrorDiscussion {
int upvoteCount;
String title;
String url;
Answer answer;
static class Answer {
}
}