Added Response Visualizer

This commit is contained in:
Rohit Awate 2018-04-27 17:29:55 +05:30
parent 35a21f0df0
commit 09ea27f699
7 changed files with 181 additions and 14 deletions

View file

@ -40,6 +40,7 @@ import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.CacheHint;
import javafx.scene.Parent;
import javafx.scene.control.*;
import javafx.scene.input.KeyCode;
@ -55,6 +56,7 @@ import java.net.MalformedURLException;
import java.net.URL;
import java.time.LocalDateTime;
import java.util.*;
import java.util.Map.Entry;
public class DashboardController implements Initializable {
@FXML
@ -78,6 +80,10 @@ public class DashboardController implements Initializable {
TabPane requestOptionsTab;
@FXML
Tab paramsTab, authTab, headersTab, bodyTab;
@FXML
private Tab visualizerTab;
@FXML
private ScrollPane visualizer;
private JFXSnackbar snackbar;
private final String[] httpMethods = {"GET", "POST", "PUT", "DELETE", "PATCH"};
@ -87,6 +93,7 @@ public class DashboardController implements Initializable {
private HeaderTabController headerTabController;
private BodyTabController bodyTabController;
private IntegerProperty paramsCountProperty;
private Accordion accordion;
@Override
public void initialize(URL url, ResourceBundle rb) {
@ -138,6 +145,8 @@ public class DashboardController implements Initializable {
errorTitle.setText("Oops... That's embarrassing!");
errorDetails.setText("Something went wrong. Try to make another request.\nRestart Everest if that doesn't work.");
setupVisualizer();
}
@FXML
@ -320,11 +329,15 @@ public class DashboardController implements Initializable {
// Selects only the part preceding the ';', skipping the character encoding
type = type.split(";")[0];
visualizerTab.setDisable(true);
switch (type.toLowerCase()) {
case "application/json":
responseType.setText("JSON");
JsonNode node = EverestUtilities.mapper.readTree(responseBody);
responseArea.setText(EverestUtilities.mapper.writeValueAsString(node));
accordion.getPanes().clear();
visualizerTab.setDisable(false);
populateVisualizer(accordion, "root", node);
break;
case "application/xml":
responseType.setText("XML");
@ -351,6 +364,84 @@ public class DashboardController implements Initializable {
}
}
private void setupVisualizer() {
accordion = new Accordion();
accordion.setCache(true);
accordion.setCacheHint(CacheHint.SPEED);
visualizer.setContent(accordion);
}
private void populateVisualizer(Accordion rootAccordion, String rootName, JsonNode root) {
JsonNode currentNode;
VBox container = new VBox();
container.setStyle("-fx-padding: 3px 30px");
Label valueLabel;
TitledPane pane = new TitledPane(rootName, container);
Tooltip valueTooltip;
if (root.isArray()) {
Iterator<JsonNode> iterator = root.elements();
while (iterator.hasNext()) {
currentNode = iterator.next();
if (currentNode.isValueNode()) {
valueLabel = new Label(currentNode.toString());
valueLabel.getStyleClass().add("visualizerValueLabel");
valueLabel.setWrapText(true);
valueTooltip = new Tooltip(currentNode.toString());
valueLabel.setTooltip(valueTooltip);
container.getChildren().add(valueLabel);
} else if (currentNode.isObject()) {
Accordion arrayAccordion = new Accordion();
container.getChildren().add(arrayAccordion);
populateVisualizer(arrayAccordion, "", currentNode);
}
}
rootAccordion.getPanes().add(pane);
} else {
Iterator<Entry<String, JsonNode>> iterator = root.fields();
Entry<String, JsonNode> currentEntry;
HBox valueContainer;
Label keyLabel;
Tooltip keyTooltip;
while (iterator.hasNext()) {
currentEntry = iterator.next();
currentNode = currentEntry.getValue();
if (currentNode.isValueNode()) {
keyLabel = new Label(currentEntry.getKey() + ": ");
keyLabel.setStyle("-fx-font-weight: bold");
keyLabel.getStyleClass().add("visualizerKeyLabel");
keyTooltip = new Tooltip(currentEntry.getKey());
keyLabel.setTooltip(keyTooltip);
valueLabel = new Label(currentNode.toString());
valueLabel.getStyleClass().add("visualizerValueLabel");
valueLabel.setWrapText(true);
valueTooltip = new Tooltip(currentNode.toString());
valueLabel.setTooltip(valueTooltip);
valueContainer = new HBox(keyLabel, valueLabel);
container.getChildren().add(valueContainer);
} else if (currentNode.isArray() || currentNode.isObject()) {
Accordion arrayAccordion = new Accordion();
container.getChildren().add(arrayAccordion);
populateVisualizer(arrayAccordion, currentEntry.getKey(), currentNode);
}
}
rootAccordion.getPanes().add(pane);
}
if (!rootName.equals("root")) {
pane.getStyleClass().add("nonRootTitledPane"); // Special CSS class to set padding for non-root panes only
} else {
rootAccordion.setExpandedPane(pane);
}
}
private void applyDashboardSettings() {
String responseAreaCSS = "-fx-font-family: " + Settings.responseAreaFont + ";" +
"-fx-font-size: " + Settings.responseAreaFontSize;
@ -482,11 +573,11 @@ public class DashboardController implements Initializable {
httpMethodBox.getSelectionModel().select(dashboardState.getHttpMethod());
if (dashboardState.getHeaders() != null)
for (Map.Entry entry : dashboardState.getHeaders().entrySet())
for (Entry entry : dashboardState.getHeaders().entrySet())
headerTabController.addHeader(entry.getKey().toString(), entry.getValue().toString());
if (dashboardState.getParams() != null)
for (Map.Entry entry : dashboardState.getParams().entrySet())
for (Entry entry : dashboardState.getParams().entrySet())
addParamField(entry.getKey().toString(), entry.getValue().toString());
if (!(httpMethodBox.getValue().equals("GET") || httpMethodBox.getValue().equals("DELETE")))

View file

@ -260,7 +260,7 @@ public class HomeWindowController implements Initializable {
objectStream.writeObject(dashboardStates);
objectStream.close();
fileStream.close();
Services.loggingService.logInfo("Application state saved successfully.", LocalDateTime.now());
Services.loggingService.logInfo("Application state was saved successfully.", LocalDateTime.now());
} catch (IOException e) {
Services.loggingService.logSevere("Failed to save the application's state.", e, LocalDateTime.now());
}
@ -338,7 +338,7 @@ public class HomeWindowController implements Initializable {
for (HistoryItemController controller : historyItemControllers) {
int relativityIndex = 0;
int relativityIndex = controller.getRelativityIndex(searchString);
// Split the string into words and get total relativity index as sum of individual indices.
String words[] = searchString.split("\\s");

View file

@ -213,7 +213,7 @@ public class HistoryManager {
public synchronized List<DashboardState> getHistory() {
List<DashboardState> history = new ArrayList<>();
try {
// Loads the requests from the last x number of days, x being stored in Settings.showHistoryRange
// Loads the requests from the last x number of days, x being Settings.showHistoryRange
statement = conn.prepareStatement(EverestUtilities.trimString(queries.get("selectRecentRequests").toString()));
String historyStartDate = LocalDate.now().minusDays(Settings.showHistoryRange).toString();
statement.setString(1, historyStartDate);

View file

@ -31,5 +31,5 @@ public class Settings {
public static int connectionReadTimeOut = 30000;
public static String theme = "Adreana";
public static long showHistoryRange = 7;
public static int showHistoryRange = 3;
}

View file

@ -59,6 +59,7 @@ public class SettingsLoader implements Runnable {
Settings.connectionReadTimeOut = setIntegerSetting(Settings.connectionReadTimeOut, "connectionReadTimeOut");
Settings.theme = EverestUtilities.trimString(setStringSetting(Settings.theme, "theme"));
Settings.showHistoryRange = setIntegerSetting(Settings.showHistoryRange, "showHistoryRange");
} catch (IOException IOE) {
Services.loggingService.logInfo("Settings file not found. Using defaults", LocalDateTime.now());
}

View file

@ -76,7 +76,7 @@
}
.text-area {
-fx-font-family: "Liberation Mono";
-fx-font-family: "Liberation Mono", monospace;
-fx-text-fill: #a1a1a1;
-fx-background-radius: 0;
-fx-background-insets: 0;
@ -118,7 +118,7 @@
-fx-background-color: #505050;
}
.tab-pane:top .tab-header-area {
.tab-pane .tab-header-area {
-fx-padding: 0;
}
@ -187,7 +187,6 @@
-fx-background-color: #3d3d3d;
}
.scroll-pane .scroll-bar .thumb {
-fx-background-color: #808080;
}
@ -290,6 +289,66 @@
-fx-font-size: 15px;
}
/* Response Visualizer */
#responseTabPane:bottom .tab-header-area .tab-header-background,
#responseTabPane:bottom .tab-header-area .headers-region .tab:bottom {
-fx-background-color: #282828;
}
#responseTabPane:bottom .tab-header-area .headers-region .tab:bottom .tab-container .tab-label .text {
-fx-fill: white;
}
#responseTabPane:bottom .tab-header-area .headers-region .tab:hover:bottom {
-fx-background-color: purple;
}
#responseTabPane:bottom .tab-header-area .headers-region .tab:selected:bottom {
-fx-background-color: #3d3d3d;
}
#responseTabPane .tab-content-area,
#responseTabPane .scroll-pane .viewport {
-fx-background-color: #2a2a2a;
}
.nonRootTitledPane {
-fx-padding: 3px 0px;
}
.titled-pane .title {
-fx-font-family: "Liberation Mono", monospace;
-fx-background-color: #404040;
}
.titled-pane .content {
-fx-background-color: #282828;
-fx-border-width: 0px;
}
.titled-pane .content {
-fx-font-family: "Liberation Mono", monospace;
-fx-padding: 3px 0px;
}
.visualizerKeyLabel {
-fx-font-size: 18px;
-fx-text-fill: #bababa;
-fx-text-alignment: left;
}
.visualizerValueLabel {
-fx-font-size: 18px;
-fx-text-fill: #959595;
-fx-text-alignment: left;
}
.titled-pane .title .text,
.titled-pane .title .arrow-button .arrow {
-fx-background-color: #dadada;
-fx-fill: #dadada;
}
/* SnackBar */
.jfx-snackbar-content {
-fx-background-color: black;

View file

@ -28,7 +28,7 @@
<children>
<VBox prefHeight="200.0" prefWidth="100.0">
<children>
<HBox alignment="CENTER" maxHeight="100.0" prefWidth="100.0" spacing="20.0" VBox.vgrow="ALWAYS">
<HBox alignment="CENTER" maxHeight="100.0" minHeight="100.0" spacing="20.0" VBox.vgrow="ALWAYS">
<children>
<ImageView fitHeight="50.0" fitWidth="50.0" pickOnBounds="true" preserveRatio="true"
HBox.hgrow="ALWAYS">
@ -76,7 +76,7 @@
</HBox>
<SplitPane dividerPositions="0.1" orientation="VERTICAL" VBox.vgrow="ALWAYS">
<items>
<AnchorPane maxHeight="250.0">
<AnchorPane maxHeight="300.0">
<children>
<TabPane fx:id="requestOptionsTab" minHeight="190.0" tabClosingPolicy="UNAVAILABLE"
tabMinWidth="150.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
@ -198,9 +198,25 @@
<Insets bottom="15.0" left="15.0" right="15.0" top="15.0" />
</padding>
</HBox>
<StackPane VBox.vgrow="ALWAYS">
<StackPane VBox.vgrow="SOMETIMES">
<children>
<TextArea fx:id="responseArea" editable="false" wrapText="true"/>
<TabPane fx:id="responseTabPane" side="BOTTOM" tabMinWidth="100.0">
<tabs>
<Tab closable="false" text="BODY">
<content>
<TextArea fx:id="responseArea" editable="false"
wrapText="true"/>
</content>
</Tab>
<Tab fx:id="visualizerTab" closable="false" text="VISUALIZER">
<content>
<ScrollPane fx:id="visualizer" fitToHeight="true"
fitToWidth="true" hbarPolicy="NEVER"/>
</content>
</Tab>
<Tab closable="false" text="HEADERS"/>
</tabs>
</TabPane>
<VBox fx:id="loadingLayer" alignment="CENTER" visible="false">
<children>
<HBox alignment="CENTER">
@ -228,7 +244,7 @@
</JFXButton>
</children>
</VBox>
<VBox fx:id="promptLayer" alignment="CENTER">
<VBox fx:id="promptLayer" alignment="CENTER" visible="false">
<children>
<Label text="Enter an address, select a method and hit send."
textFill="WHITE">