Added saved queries to the SQL Console page.
This commit is contained in:
parent
feda2e1897
commit
ec6bc83353
|
@ -1,13 +1,15 @@
|
|||
package com.andrewlalis.perfin.control;
|
||||
|
||||
import com.andrewlalis.javafx_scene_router.RouteSelectionListener;
|
||||
import com.andrewlalis.perfin.data.SavedQueryRepository;
|
||||
import com.andrewlalis.perfin.data.impl.JdbcDataSource;
|
||||
import com.andrewlalis.perfin.model.Profile;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.control.Dialog;
|
||||
import javafx.scene.control.DialogPane;
|
||||
import javafx.scene.control.TextArea;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.StageStyle;
|
||||
import javafx.stage.Window;
|
||||
|
@ -18,6 +20,7 @@ import java.sql.ResultSet;
|
|||
import java.sql.SQLException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Controller for the SQL Console View, in which the user can write and execute
|
||||
|
@ -29,14 +32,16 @@ public class SqlConsoleViewController implements RouteSelectionListener {
|
|||
|
||||
@FXML public TextArea sqlEditorTextArea;
|
||||
@FXML public TextArea outputTextArea;
|
||||
@FXML public VBox savedQueriesVBox;
|
||||
|
||||
@Override
|
||||
public void onRouteSelected(Object context) {
|
||||
sqlEditorTextArea.clear();
|
||||
outputTextArea.clear();
|
||||
refreshSavedQueries();
|
||||
}
|
||||
|
||||
public void executeQuery() {
|
||||
@FXML public void executeQuery() {
|
||||
String queryText = sqlEditorTextArea.getText().strip();
|
||||
String[] rawQueries = queryText.split("\\s*;\\s*");
|
||||
List<String> queries = Arrays.stream(rawQueries)
|
||||
|
@ -78,6 +83,70 @@ public class SqlConsoleViewController implements RouteSelectionListener {
|
|||
}
|
||||
}
|
||||
|
||||
@FXML public void saveQuery() {
|
||||
if (sqlEditorTextArea.getText().isBlank()) {
|
||||
Popups.message(sqlEditorTextArea, "Cannot save an empty query.");
|
||||
return;
|
||||
}
|
||||
TextInputDialog dialog = new TextInputDialog();
|
||||
dialog.setTitle("Save Query");
|
||||
dialog.setContentText("Enter a name for this query.");
|
||||
Optional<String> result = dialog.showAndWait();
|
||||
if (result.isPresent()) {
|
||||
SavedQueryRepository repo = Profile.getCurrent().dataSource().getSavedQueryRepository();
|
||||
String name = result.get().strip();
|
||||
if (
|
||||
repo.getSavedQueries().contains(name) &&
|
||||
!Popups.confirm(sqlEditorTextArea, "Are you sure you want to overwrite this saved query?")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
String content = sqlEditorTextArea.getText().strip();
|
||||
repo.createSavedQuery(name, content);
|
||||
refreshSavedQueries();
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshSavedQueries() {
|
||||
savedQueriesVBox.getChildren().clear();
|
||||
List<String> savedQueries = Profile.getCurrent().dataSource()
|
||||
.getSavedQueryRepository().getSavedQueries();
|
||||
savedQueriesVBox.getChildren().addAll(savedQueries.stream().map(this::makeQueryTile).toList());
|
||||
}
|
||||
|
||||
private Node makeQueryTile(String name) {
|
||||
AnchorPane pane = new AnchorPane();
|
||||
pane.getStyleClass().addAll("tile");
|
||||
Label nameLabel = new Label(name);
|
||||
AnchorPane.setLeftAnchor(nameLabel, 0.0);
|
||||
AnchorPane.setTopAnchor(nameLabel, 0.0);
|
||||
AnchorPane.setBottomAnchor(nameLabel, 0.0);
|
||||
pane.getChildren().add(nameLabel);
|
||||
|
||||
HBox buttonsBox = new HBox();
|
||||
buttonsBox.getStyleClass().addAll("std-spacing", "small-font");
|
||||
Button loadButton = new Button("Load");
|
||||
loadButton.setOnAction(event -> sqlEditorTextArea.setText(
|
||||
Profile.getCurrent().dataSource().getSavedQueryRepository()
|
||||
.getSavedQueryContent(name)
|
||||
));
|
||||
buttonsBox.getChildren().add(loadButton);
|
||||
Button deleteButton = new Button("Delete");
|
||||
deleteButton.setOnAction(event -> {
|
||||
if (Popups.confirm(pane, "Are you sure you want to delete this query?")) {
|
||||
Profile.getCurrent().dataSource().getSavedQueryRepository().deleteSavedQuery(name);
|
||||
refreshSavedQueries();
|
||||
}
|
||||
});
|
||||
buttonsBox.getChildren().add(deleteButton);
|
||||
|
||||
AnchorPane.setRightAnchor(buttonsBox, 0.0);
|
||||
AnchorPane.setTopAnchor(buttonsBox, 0.0);
|
||||
AnchorPane.setBottomAnchor(buttonsBox, 0.0);
|
||||
pane.getChildren().add(buttonsBox);
|
||||
return pane;
|
||||
}
|
||||
|
||||
@FXML public void showSchema() {
|
||||
SchemaDialog dialog = new SchemaDialog(sqlEditorTextArea.getScene().getWindow());
|
||||
dialog.show();
|
||||
|
|
|
@ -36,6 +36,7 @@ public interface DataSource {
|
|||
TransactionLineItemRepository getTransactionLineItemRepository();
|
||||
AttachmentRepository getAttachmentRepository();
|
||||
HistoryRepository getHistoryRepository();
|
||||
SavedQueryRepository getSavedQueryRepository();
|
||||
|
||||
AnalyticsRepository getAnalyticsRepository();
|
||||
|
||||
|
@ -92,6 +93,7 @@ public interface DataSource {
|
|||
TransactionLineItemRepository.class, this::getTransactionLineItemRepository,
|
||||
AttachmentRepository.class, this::getAttachmentRepository,
|
||||
HistoryRepository.class, this::getHistoryRepository,
|
||||
SavedQueryRepository.class, this::getSavedQueryRepository,
|
||||
AnalyticsRepository.class, this::getAnalyticsRepository
|
||||
);
|
||||
return (Supplier<R>) repoSuppliers.get(type);
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
package com.andrewlalis.perfin.data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface SavedQueryRepository extends Repository {
|
||||
List<String> getSavedQueries();
|
||||
String getSavedQueryContent(String name);
|
||||
void createSavedQuery(String name, String content);
|
||||
void deleteSavedQuery(String name);
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
package com.andrewlalis.perfin.data.impl;
|
||||
|
||||
import com.andrewlalis.perfin.data.SavedQueryRepository;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public record FileSystemSavedQueryRepository(Path contentDir) implements SavedQueryRepository {
|
||||
private static final Logger log = LoggerFactory.getLogger(FileSystemSavedQueryRepository.class);
|
||||
|
||||
private Path queriesDir() {
|
||||
return contentDir.resolve("saved-queries");
|
||||
}
|
||||
|
||||
private Path queryFile(String name) {
|
||||
return queriesDir().resolve(name + ".sql");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getSavedQueries() {
|
||||
Path dir = queriesDir();
|
||||
if (Files.notExists(dir)) return Collections.emptyList();
|
||||
try (var stream = Files.list(dir)) {
|
||||
return stream.filter(p ->
|
||||
Files.isRegularFile(p) &&
|
||||
p.getFileName().toString().toLowerCase().endsWith(".sql")
|
||||
)
|
||||
.map(p -> {
|
||||
var s = p.getFileName().toString();
|
||||
int idx = s.lastIndexOf('.');
|
||||
return s.substring(0, idx);
|
||||
})
|
||||
.sorted()
|
||||
.toList();
|
||||
} catch (IOException e) {
|
||||
log.error("Failed to list files", e);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSavedQueryContent(String name) {
|
||||
Path file = queryFile(name);
|
||||
if (Files.notExists(file)) return null;
|
||||
try {
|
||||
return Files.readString(file);
|
||||
} catch (IOException e) {
|
||||
log.error("Failed to read saved query content", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createSavedQuery(String name, String content) {
|
||||
try {
|
||||
if (Files.notExists(queriesDir())) {
|
||||
Files.createDirectory(queriesDir());
|
||||
}
|
||||
Path file = queryFile(name);
|
||||
Files.writeString(file, content);
|
||||
} catch (IOException e) {
|
||||
log.error("Failed to create saved query.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteSavedQuery(String name) {
|
||||
try {
|
||||
Files.deleteIfExists(queryFile(name));
|
||||
} catch (IOException e) {
|
||||
log.error("Failed to delete saved query.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -74,6 +74,11 @@ public class JdbcDataSource implements DataSource {
|
|||
return new JdbcHistoryRepository(getConnection());
|
||||
}
|
||||
|
||||
@Override
|
||||
public SavedQueryRepository getSavedQueryRepository() {
|
||||
return new FileSystemSavedQueryRepository(contentDir);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnalyticsRepository getAnalyticsRepository() {
|
||||
return new JdbcAnalyticsRepository(getConnection());
|
||||
|
|
|
@ -27,5 +27,11 @@
|
|||
Prepend your query with `#` or `//` to exclude it from execution. Each
|
||||
comment character disables all query syntax up until the next semicolon,
|
||||
(`;`).
|
||||
|
||||
# Saved Queries #
|
||||
Click on the **Save Query** button to save the contents of the editor
|
||||
into a *saved query*, which can then be loaded again at any time. You
|
||||
can find the saved query files under `/content/saved-queries`, in your
|
||||
profile's directory.
|
||||
</StyledText>
|
||||
</VBox>
|
||||
|
|
|
@ -3,11 +3,13 @@
|
|||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<VBox
|
||||
<HBox
|
||||
xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
fx:controller="com.andrewlalis.perfin.control.SqlConsoleViewController"
|
||||
styleClass="std-spacing"
|
||||
>
|
||||
<VBox HBox.hgrow="ALWAYS">
|
||||
<ScrollPane fitToWidth="true" fitToHeight="true" VBox.vgrow="ALWAYS">
|
||||
<TextArea
|
||||
fx:id="sqlEditorTextArea"
|
||||
|
@ -18,6 +20,7 @@
|
|||
<HBox styleClass="std-padding,std-spacing">
|
||||
<Button text="Execute Query" onAction="#executeQuery"/>
|
||||
<Button text="View Schema" onAction="#showSchema"/>
|
||||
<Button text="Save Query" onAction="#saveQuery"/>
|
||||
</HBox>
|
||||
<ScrollPane fitToHeight="true" fitToWidth="true">
|
||||
<TextArea
|
||||
|
@ -26,4 +29,12 @@
|
|||
maxHeight="800"
|
||||
/>
|
||||
</ScrollPane>
|
||||
</VBox>
|
||||
</VBox>
|
||||
|
||||
<VBox HBox.hgrow="SOMETIMES" minWidth="300" prefWidth="300">
|
||||
<Label text="Saved Queries"/>
|
||||
<ScrollPane styleClass="tile-container-scroll" VBox.vgrow="ALWAYS">
|
||||
<VBox fx:id="savedQueriesVBox" styleClass="tile-container"/>
|
||||
</ScrollPane>
|
||||
</VBox>
|
||||
</HBox>
|
||||
|
|
Loading…
Reference in New Issue