wrap DiagnosticResult in Result in order to track the state of applied fixes

This commit is contained in:
Sebastian Stenzel
2021-06-30 14:13:05 +02:00
parent 5c9c336a33
commit f3953c2fb1
7 changed files with 149 additions and 57 deletions

View File

@@ -23,7 +23,7 @@ import java.util.stream.Stream;
@HealthCheckScoped
public class CheckDetailController implements FxController {
private final EasyObservableList<DiagnosticResult> results;
private final EasyObservableList<Result> results;
private final OptionalBinding<Worker.State> taskState;
private final Binding<String> taskName;
private final Binding<String> taskDuration;
@@ -39,7 +39,7 @@ public class CheckDetailController implements FxController {
private final ResultListCellFactory resultListCellFactory;
private final ResourceBundle resourceBundle;
public ListView<DiagnosticResult> resultsListView;
public ListView<Result> resultsListView;
private Subscription resultSubscription;
@Inject
@@ -71,8 +71,8 @@ public class CheckDetailController implements FxController {
}
}
private Function<Stream<? extends DiagnosticResult>, Long> countSeverity(DiagnosticResult.Severity severity) {
return stream -> stream.filter(item -> severity.equals(item.getSeverity())).count();
private Function<Stream<? extends Result>, Long> countSeverity(DiagnosticResult.Severity severity) {
return stream -> stream.filter(item -> severity.equals(item.diagnosis().getSeverity())).count();
}
@FXML

View File

@@ -35,7 +35,7 @@ class HealthCheckTask extends Task<Void> {
private final Masterkey masterkey;
private final SecureRandom csprng;
private final HealthCheck check;
private final ObservableList<DiagnosticResult> results;
private final ObservableList<Result> results;
private final LongProperty durationInMillis;
private final BooleanProperty chosenForExecution;
@@ -45,7 +45,7 @@ class HealthCheckTask extends Task<Void> {
this.masterkey = Objects.requireNonNull(masterkey);
this.csprng = Objects.requireNonNull(csprng);
this.check = Objects.requireNonNull(check);
this.results = FXCollections.observableArrayList();
this.results = FXCollections.observableArrayList(Result::observables);
try {
updateTitle(resourceBundle.getString("health." + check.identifier()));
} catch (MissingResourceException e) {
@@ -61,11 +61,11 @@ class HealthCheckTask extends Task<Void> {
Instant start = Instant.now();
try (var masterkeyClone = masterkey.clone(); //
var cryptor = CryptorProvider.forScheme(vaultConfig.getCipherCombo()).provide(masterkeyClone, csprng)) {
check.check(vaultPath, vaultConfig, masterkeyClone, cryptor, result -> {
check.check(vaultPath, vaultConfig, masterkeyClone, cryptor, diagnosis -> {
if (isCancelled()) {
throw new CancellationException();
}
Platform.runLater(() -> results.add(result));
Platform.runLater(() -> results.add(Result.create(diagnosis)));
});
}
Platform.runLater(() -> durationInMillis.set(Duration.between(start, Instant.now()).toMillis()));
@@ -88,7 +88,7 @@ class HealthCheckTask extends Task<Void> {
return new Observable[]{results, chosenForExecution};
}
public ObservableList<DiagnosticResult> results() {
public ObservableList<Result> results() {
return results;
}

View File

@@ -72,7 +72,7 @@ public class ReportWriter {
case SUCCEEDED -> {
writer.write("STATUS: SUCCESS\nRESULTS:\n");
for (var result : task.results()) {
writer.write(REPORT_CHECK_RESULT.formatted(result.getSeverity(), result.toString()));
writer.write(REPORT_CHECK_RESULT.formatted(result.diagnosis().getSeverity(), result.getDescription()));
}
}
case CANCELLED -> writer.write("STATUS: CANCELED\n");

View File

@@ -0,0 +1,43 @@
package org.cryptomator.ui.health;
import org.cryptomator.cryptofs.health.api.DiagnosticResult;
import javafx.beans.Observable;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
record Result(DiagnosticResult diagnosis, ObjectProperty<FixState> fixState) {
enum FixState {
NOT_FIXABLE,
FIXABLE,
FIXING,
FIXED,
FIX_FAILED
}
public static Result create(DiagnosticResult diagnosis) {
FixState initialState = switch (diagnosis.getSeverity()) {
case WARN -> FixState.FIXABLE;
default -> FixState.NOT_FIXABLE;
};
return new Result(diagnosis, new SimpleObjectProperty<>(initialState));
}
public Observable[] observables() {
return new Observable[]{fixState};
}
public String getDescription() {
return diagnosis.toString();
}
public FixState getState() {
return fixState.get();
}
public void setState(FixState state) {
this.fixState.set(state);
}
}

View File

@@ -1,84 +1,82 @@
package org.cryptomator.ui.health;
import com.tobiasdiez.easybind.EasyBind;
import org.cryptomator.cryptofs.health.api.DiagnosticResult;
import com.tobiasdiez.easybind.optional.OptionalBinding;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.controls.FontAwesome5Icon;
import org.cryptomator.ui.controls.FontAwesome5IconView;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javafx.application.Platform;
import javafx.beans.binding.Binding;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.beans.value.ObservableObjectValue;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
// unscoped because each cell needs its own controller
public class ResultListCellController implements FxController {
private final ResultFixApplier fixApplier;
private final ObjectProperty<DiagnosticResult> result;
private final Logger LOG = LoggerFactory.getLogger(ResultListCellController.class);
private final ObjectProperty<Result> result;
private final Binding<String> description;
private final ResultFixApplier fixApplier;
private final OptionalBinding<Result.FixState> fixState;
private final ObjectBinding<FontAwesome5Icon> glyph;
private final BooleanBinding fixable;
private final BooleanBinding fixing;
private final BooleanBinding fixed;
public FontAwesome5IconView iconView;
public Button actionButton;
public Button fixButton;
@Inject
public ResultListCellController(ResultFixApplier fixApplier) {
this.result = new SimpleObjectProperty<>(null);
this.description = EasyBind.wrapNullable(result).map(DiagnosticResult::toString).orElse("");
this.description = EasyBind.wrapNullable(result).map(Result::getDescription).orElse("");
this.fixApplier = fixApplier;
result.addListener(this::updateCellContent);
}
private void updateCellContent(ObservableValue<? extends DiagnosticResult> observable, DiagnosticResult oldVal, DiagnosticResult newVal) {
iconView.getStyleClass().clear();
actionButton.setVisible(false);
//TODO: see comment in case WARN
actionButton.setManaged(false);
switch (newVal.getSeverity()) {
case INFO -> {
iconView.setGlyph(FontAwesome5Icon.INFO_CIRCLE);
iconView.getStyleClass().add("glyph-icon-muted");
}
case GOOD -> {
iconView.setGlyph(FontAwesome5Icon.CHECK);
iconView.getStyleClass().add("glyph-icon-primary");
}
case WARN -> {
iconView.setGlyph(FontAwesome5Icon.EXCLAMATION_TRIANGLE);
iconView.getStyleClass().add("glyph-icon-orange");
//TODO: Neither is any fix implemented, nor it is ensured, that only fix is executed at a time with good ui indication
// before both are not fix, do not show the button
//actionButton.setVisible(true);
}
case CRITICAL -> {
iconView.setGlyph(FontAwesome5Icon.TIMES);
iconView.getStyleClass().add("glyph-icon-red");
}
}
this.fixState = EasyBind.wrapNullable(result).mapObservable(Result::fixState);
this.glyph = Bindings.createObjectBinding(this::getGlyph, result);
this.fixable = Bindings.createBooleanBinding(this::isFixable, fixState);
this.fixing = Bindings.createBooleanBinding(this::isFixing, fixState);
this.fixed = Bindings.createBooleanBinding(this::isFixed, fixState);
}
@FXML
public void runResultAction() {
public void fix() {
final var realResult = result.get();
if (realResult != null) {
fixApplier.fix(realResult);
}
}
@FXML
public void initialize() {
// see getGlyph() for relevant glyphs:
EasyBind.includeWhen(iconView.getStyleClass(), "glyph-icon-muted", iconView.glyphProperty().isEqualTo(FontAwesome5Icon.INFO_CIRCLE));
EasyBind.includeWhen(iconView.getStyleClass(), "glyph-icon-primary", iconView.glyphProperty().isEqualTo(FontAwesome5Icon.CHECK));
EasyBind.includeWhen(iconView.getStyleClass(), "glyph-icon-orange", iconView.glyphProperty().isEqualTo(FontAwesome5Icon.EXCLAMATION_TRIANGLE));
EasyBind.includeWhen(iconView.getStyleClass(), "glyph-icon-red", iconView.glyphProperty().isEqualTo(FontAwesome5Icon.TIMES));
}
/* Getter & Setter */
public DiagnosticResult getResult() {
public Result getResult() {
return result.get();
}
public void setResult(DiagnosticResult result) {
public void setResult(Result result) {
this.result.set(result);
}
public ObjectProperty<DiagnosticResult> resultProperty() {
public ObjectProperty<Result> resultProperty() {
return result;
}
@@ -86,7 +84,49 @@ public class ResultListCellController implements FxController {
return description.getValue();
}
public ObjectBinding<FontAwesome5Icon> glyphProperty() {
return glyph;
}
public FontAwesome5Icon getGlyph() {
var r = result.get();
if (r == null) {
return null;
}
return switch (r.diagnosis().getSeverity()) {
case INFO -> FontAwesome5Icon.INFO_CIRCLE;
case GOOD -> FontAwesome5Icon.CHECK;
case WARN -> FontAwesome5Icon.EXCLAMATION_TRIANGLE;
case CRITICAL -> FontAwesome5Icon.TIMES;
};
}
public Binding<String> descriptionProperty() {
return description;
}
public BooleanBinding fixableProperty() {
return fixable;
}
public boolean isFixable() {
return fixState.get().map(Result.FixState.FIXABLE::equals).orElse(false);
}
public BooleanBinding fixingProperty() {
return fixing;
}
public boolean isFixing() {
return fixState.get().map(Result.FixState.FIXING::equals).orElse(false);
}
public BooleanBinding fixedProperty() {
return fixed;
}
public boolean isFixed() {
return fixState.get().map(Result.FixState.FIXED::equals).orElse(false);
}
}

View File

@@ -1,7 +1,6 @@
package org.cryptomator.ui.health;
import org.cryptomator.cryptofs.health.api.DiagnosticResult;
import org.cryptomator.ui.common.FxmlLoaderFactory;
import javax.inject.Inject;
@@ -15,7 +14,7 @@ import java.io.IOException;
import java.io.UncheckedIOException;
@HealthCheckScoped
public class ResultListCellFactory implements Callback<ListView<DiagnosticResult>, ListCell<DiagnosticResult>> {
public class ResultListCellFactory implements Callback<ListView<Result>, ListCell<Result>> {
private final FxmlLoaderFactory fxmlLoaders;
@@ -25,7 +24,7 @@ public class ResultListCellFactory implements Callback<ListView<DiagnosticResult
}
@Override
public ListCell<DiagnosticResult> call(ListView<DiagnosticResult> param) {
public ListCell<Result> call(ListView<Result> param) {
try {
FXMLLoader fxmlLoader = fxmlLoaders.load("/fxml/health_result_listcell.fxml");
return new ResultListCellFactory.Cell(fxmlLoader.getRoot(), fxmlLoader.getController());
@@ -34,7 +33,7 @@ public class ResultListCellFactory implements Callback<ListView<DiagnosticResult
}
}
private static class Cell extends ListCell<DiagnosticResult> {
private static class Cell extends ListCell<Result> {
private final Parent node;
private final ResultListCellController controller;
@@ -45,7 +44,7 @@ public class ResultListCellFactory implements Callback<ListView<DiagnosticResult
}
@Override
protected void updateItem(DiagnosticResult item, boolean empty) {
protected void updateItem(Result item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setText(null);

View File

@@ -6,6 +6,10 @@
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.control.ProgressIndicator?>
<?import javafx.scene.text.Text?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.StackPane?>
<HBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.health.ResultListCellController"
@@ -18,12 +22,18 @@
<Insets topRightBottomLeft="6"/>
</padding>
<children>
<FontAwesome5IconView fx:id="iconView" HBox.hgrow="NEVER" glyphSize="16"/>
<FontAwesome5IconView fx:id="iconView" HBox.hgrow="NEVER" glyphSize="16" glyph="${controller.glyph}"/>
<Label text="${controller.description}"/>
<Region HBox.hgrow="ALWAYS"/>
<!-- TODO: setting the minWidth of the button is just a workaround.
What we actually want to do is to prevent shrinking the button more than the text
-> own subclass of HBox is needed -->
<Button fx:id="actionButton" text="%health.check.fixBtn" onAction="#runResultAction" alignment="CENTER" visible="false" minWidth="-Infinity"/>
<StackPane HBox.hgrow="NEVER">
<children>
<Button fx:id="fixButton" text="%health.check.fixBtn" visible="${controller.fixable}" managed="${controller.fixable}" onAction="#fix" alignment="CENTER" minWidth="-Infinity"/>
<ProgressIndicator progress="-1" prefWidth="12" prefHeight="12" visible="${controller.fixing}" managed="${controller.fixing}"/>
<FontAwesome5IconView glyph="CHECK" glyphSize="16" visible="${controller.fixed}" managed="${controller.fixed}"/>
</children>
</StackPane>
</children>
</HBox>