Added csv export for tx history and trade history

This commit is contained in:
Manfred Karrer 2016-07-04 00:02:05 +02:00
parent 925f59a9ea
commit 37bd2fa5f3
8 changed files with 149 additions and 31 deletions

View file

@ -7,5 +7,5 @@ public class DevFlags {
private static final Logger log = LoggerFactory.getLogger(DevFlags.class);
public static final boolean STRESS_TEST_MODE = false;
public static final boolean DEV_MODE = STRESS_TEST_MODE || false;
public static final boolean DEV_MODE = STRESS_TEST_MODE || true;
}

View file

@ -142,19 +142,11 @@
<version>0.4.1</version>
</dependency>-->
<dependency>
<groupId>com.opencsv</groupId>
<artifactId>opencsv</artifactId>
<version>3.8</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.googlecode.jcsv/jcsv -->
<dependency>
<groupId>com.googlecode.jcsv</groupId>
<artifactId>jcsv</artifactId>
<version>1.4.0</version>
</dependency>
</dependencies>
</project>

View file

@ -52,6 +52,15 @@ public class TransactionsListItem {
private boolean detailsAvailable;
private Coin amountAsCoin = Coin.ZERO;
private BSFormatter formatter;
private int confirmations = 0;
public TransactionsListItem() {
date = null;
walletService = null;
txConfidenceIndicator = null;
tooltip = null;
txId = null;
}
public TransactionsListItem(Transaction transaction, WalletService walletService, Optional<Tradable> tradableOptional, BSFormatter formatter) {
this.formatter = formatter;
@ -176,6 +185,7 @@ public class TransactionsListItem {
}
private void updateConfidence(TransactionConfidence confidence) {
confirmations = confidence.getDepthInBlocks();
if (confidence != null) {
switch (confidence.getConfidenceType()) {
case UNKNOWN:
@ -251,5 +261,9 @@ public class TransactionsListItem {
public Tradable getTradable() {
return tradable;
}
public String getNumConfirmations() {
return String.valueOf(confirmations);
}
}

View file

@ -21,7 +21,7 @@
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.VBox?>
<VBox fx:id="root" fx:controller="io.bitsquare.gui.main.funds.transactions.TransactionsView"
spacing="10" xmlns:fx="http://javafx.com/fxml">
spacing="10" alignment="CENTER_RIGHT" xmlns:fx="http://javafx.com/fxml">
<padding>
<Insets bottom="0.0" left="10.0" right="10.0" top="10.0"/>
</padding>
@ -30,11 +30,12 @@
<TableColumn text="Date/Time" fx:id="dateColumn" minWidth="180" maxWidth="180"/>
<TableColumn text="Details" fx:id="detailsColumn" minWidth="220" maxWidth="220"/>
<TableColumn text="Address" fx:id="addressColumn" minWidth="260"/>
<TableColumn text="Transaction" fx:id="transactionColumn" minWidth="180"/>
<TableColumn text="Transaction ID" fx:id="transactionColumn" minWidth="180"/>
<TableColumn text="Amount (BTC)" fx:id="amountColumn" minWidth="130" maxWidth="130"/>
<TableColumn text="Confirmations" fx:id="confidenceColumn" minWidth="130" maxWidth="130"/>
<TableColumn text="Revert Tx" fx:id="revertTxColumn" sortable="false" minWidth="110" maxWidth="110"
visible="false"/>
</columns>
</TableView>
<Button fx:id="exportButton"/>
</VBox>

View file

@ -17,6 +17,7 @@
package io.bitsquare.gui.main.funds.transactions;
import com.googlecode.jcsv.writer.CSVEntryConverter;
import de.jensd.fx.fontawesome.AwesomeIcon;
import io.bitsquare.arbitration.DisputeManager;
import io.bitsquare.btc.FeePolicy;
@ -32,6 +33,7 @@ import io.bitsquare.gui.main.overlays.popups.Popup;
import io.bitsquare.gui.main.overlays.windows.OfferDetailsWindow;
import io.bitsquare.gui.main.overlays.windows.TradeDetailsWindow;
import io.bitsquare.gui.util.BSFormatter;
import io.bitsquare.gui.util.GUIUtil;
import io.bitsquare.trade.Tradable;
import io.bitsquare.trade.Trade;
import io.bitsquare.trade.TradeManager;
@ -53,6 +55,7 @@ import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Callback;
import org.bitcoinj.core.*;
import org.bitcoinj.script.Script;
@ -71,6 +74,8 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
TableView<TransactionsListItem> tableView;
@FXML
TableColumn<TransactionsListItem, TransactionsListItem> dateColumn, detailsColumn, addressColumn, transactionColumn, amountColumn, confidenceColumn, revertTxColumn;
@FXML
Button exportButton;
private final ObservableList<TransactionsListItem> observableList = FXCollections.observableArrayList();
private final SortedList<TransactionsListItem> sortedList = new SortedList<>(observableList);
@ -84,6 +89,7 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
private final Preferences preferences;
private final TradeDetailsWindow tradeDetailsWindow;
private final DisputeManager disputeManager;
private Stage stage;
private final OfferDetailsWindow offerDetailsWindow;
private WalletEventListener walletEventListener;
private EventHandler<KeyEvent> keyEventEventHandler;
@ -97,7 +103,7 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
private TransactionsView(WalletService walletService, TradeManager tradeManager, OpenOfferManager openOfferManager,
ClosedTradableManager closedTradableManager, FailedTradesManager failedTradesManager,
BSFormatter formatter, Preferences preferences, TradeDetailsWindow tradeDetailsWindow,
DisputeManager disputeManager,
DisputeManager disputeManager, Stage stage,
OfferDetailsWindow offerDetailsWindow) {
this.walletService = walletService;
this.tradeManager = tradeManager;
@ -108,6 +114,7 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
this.preferences = preferences;
this.tradeDetailsWindow = tradeDetailsWindow;
this.disputeManager = disputeManager;
this.stage = stage;
this.offerDetailsWindow = offerDetailsWindow;
}
@ -182,6 +189,8 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
else if (new KeyCodeCombination(KeyCode.A, KeyCombination.SHORTCUT_DOWN).match(event))
showStatisticsPopup();
};
exportButton.setText("Export to csv");
}
@Override
@ -195,6 +204,30 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
scene = root.getScene();
if (scene != null)
scene.addEventHandler(KeyEvent.KEY_RELEASED, keyEventEventHandler);
exportButton.setOnAction(event -> {
final ObservableList<TableColumn<TransactionsListItem, ?>> tableColumns = tableView.getColumns();
CSVEntryConverter<TransactionsListItem> headerConverter = transactionsListItem -> {
String[] columns = new String[6];
for (int i = 0; i < columns.length; i++)
columns[i] = tableColumns.get(i).getText();
return columns;
};
CSVEntryConverter<TransactionsListItem> contentConverter = item -> {
String[] columns = new String[6];
columns[0] = item.getDateString();
columns[1] = item.getDetails();
columns[2] = item.getDirection() + " " + item.getAddressString();
columns[3] = item.getTxId();
columns[4] = item.getAmount();
columns[5] = item.getNumConfirmations();
return columns;
};
GUIUtil.exportCSV("transactions.csv", headerConverter, contentConverter,
new TransactionsListItem(), sortedList, stage);
});
}
@Override
@ -205,6 +238,8 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
if (scene != null)
scene.removeEventHandler(KeyEvent.KEY_RELEASED, keyEventEventHandler);
exportButton.setOnAction(null);
}
@ -528,7 +563,6 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
walletService.doubleSpendTransaction(txId, () -> {
if (tradable != null)
walletService.swapAnyTradeEntryContextToAvailableEntry(tradable.getId());
new Popup().information("Transaction successfully sent to a new address in the local Bitsquare wallet.").show();
}, errorMessage -> {

View file

@ -21,7 +21,7 @@
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.VBox?>
<VBox fx:id="root" fx:controller="io.bitsquare.gui.main.portfolio.closedtrades.ClosedTradesView"
spacing="10" xmlns:fx="http://javafx.com/fxml">
spacing="10" alignment="CENTER_RIGHT" xmlns:fx="http://javafx.com/fxml">
<padding>
<Insets bottom="0.0" left="10.0" right="10.0" top="10.0"/>
</padding>
@ -38,5 +38,5 @@
<TableColumn text="" fx:id="avatarColumn" minWidth="40" maxWidth="40"/>
</columns>
</TableView>
<Button fx:id="exportButton"/>
</VBox>

View file

@ -17,6 +17,7 @@
package io.bitsquare.gui.main.portfolio.closedtrades;
import com.googlecode.jcsv.writer.CSVEntryConverter;
import io.bitsquare.alert.PrivateNotificationManager;
import io.bitsquare.gui.common.view.ActivatableViewAndModel;
import io.bitsquare.gui.common.view.FxmlView;
@ -25,17 +26,20 @@ import io.bitsquare.gui.components.PeerInfoIcon;
import io.bitsquare.gui.main.overlays.windows.OfferDetailsWindow;
import io.bitsquare.gui.main.overlays.windows.TradeDetailsWindow;
import io.bitsquare.gui.util.BSFormatter;
import io.bitsquare.gui.util.GUIUtil;
import io.bitsquare.p2p.NodeAddress;
import io.bitsquare.trade.Tradable;
import io.bitsquare.trade.Trade;
import io.bitsquare.trade.offer.OpenOffer;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.collections.ObservableList;
import javafx.collections.transformation.SortedList;
import javafx.fxml.FXML;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Callback;
import org.bitcoinj.core.Coin;
import org.bitcoinj.utils.Fiat;
@ -50,19 +54,24 @@ public class ClosedTradesView extends ActivatableViewAndModel<VBox, ClosedTrades
@FXML
TableColumn<ClosedTradableListItem, ClosedTradableListItem> priceColumn, amountColumn, volumeColumn,
directionColumn, dateColumn, tradeIdColumn, stateColumn, avatarColumn;
@FXML
Button exportButton;
private final BSFormatter formatter;
private final OfferDetailsWindow offerDetailsWindow;
private final TradeDetailsWindow tradeDetailsWindow;
private PrivateNotificationManager privateNotificationManager;
private Stage stage;
private SortedList<ClosedTradableListItem> sortedList;
@Inject
public ClosedTradesView(ClosedTradesViewModel model, BSFormatter formatter, OfferDetailsWindow offerDetailsWindow, TradeDetailsWindow tradeDetailsWindow, PrivateNotificationManager privateNotificationManager) {
public ClosedTradesView(ClosedTradesViewModel model, BSFormatter formatter, OfferDetailsWindow offerDetailsWindow,
TradeDetailsWindow tradeDetailsWindow, PrivateNotificationManager privateNotificationManager, Stage stage) {
super(model);
this.formatter = formatter;
this.offerDetailsWindow = offerDetailsWindow;
this.tradeDetailsWindow = tradeDetailsWindow;
this.privateNotificationManager = privateNotificationManager;
this.stage = stage;
}
@Override
@ -79,9 +88,6 @@ public class ClosedTradesView extends ActivatableViewAndModel<VBox, ClosedTrades
setStateColumnCellFactory();
setAvatarColumnCellFactory();
/* , , ,
, , , , avatarColumn;
*/
tradeIdColumn.setComparator((o1, o2) -> o1.getTradable().getId().compareTo(o2.getTradable().getId()));
dateColumn.setComparator((o1, o2) -> o1.getTradable().getDate().compareTo(o2.getTradable().getDate()));
directionColumn.setComparator((o1, o2) -> o1.getTradable().getOffer().getDirection().compareTo(o2.getTradable().getOffer().getDirection()));
@ -125,6 +131,7 @@ public class ClosedTradesView extends ActivatableViewAndModel<VBox, ClosedTrades
dateColumn.setSortType(TableColumn.SortType.DESCENDING);
tableView.getSortOrder().add(dateColumn);
exportButton.setText("Export to csv");
}
@Override
@ -132,11 +139,37 @@ public class ClosedTradesView extends ActivatableViewAndModel<VBox, ClosedTrades
sortedList = new SortedList<>(model.getList());
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
tableView.setItems(sortedList);
exportButton.setOnAction(event -> {
final ObservableList<TableColumn<ClosedTradableListItem, ?>> tableColumns = tableView.getColumns();
CSVEntryConverter<ClosedTradableListItem> headerConverter = transactionsListItem -> {
String[] columns = new String[7];
for (int i = 0; i < columns.length; i++)
columns[i] = tableColumns.get(i).getText();
return columns;
};
CSVEntryConverter<ClosedTradableListItem> contentConverter = item -> {
String[] columns = new String[7];
columns[0] = model.getTradeId(item);
columns[1] = model.getDate(item);
columns[2] = model.getAmount(item);
columns[3] = model.getPrice(item);
columns[4] = model.getVolume(item);
columns[5] = model.getDirectionLabel(item);
columns[6] = model.getState(item);
return columns;
};
GUIUtil.exportCSV("tradeHistory.csv", headerConverter, contentConverter,
new ClosedTradableListItem(null), sortedList, stage);
});
}
@Override
protected void deactivate() {
sortedList.comparatorProperty().unbind();
exportButton.setOnAction(null);
}

View file

@ -17,6 +17,11 @@
package io.bitsquare.gui.util;
import com.google.common.base.Charsets;
import com.googlecode.jcsv.CSVStrategy;
import com.googlecode.jcsv.writer.CSVEntryConverter;
import com.googlecode.jcsv.writer.CSVWriter;
import com.googlecode.jcsv.writer.internal.CSVWriterBuilder;
import io.bitsquare.app.DevFlags;
import io.bitsquare.gui.main.overlays.popups.Popup;
import io.bitsquare.payment.PaymentAccount;
@ -33,8 +38,12 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
public class GUIUtil {
private static final Logger log = LoggerFactory.getLogger(GUIUtil.class);
@ -69,18 +78,11 @@ public class GUIUtil {
public static void exportAccounts(ArrayList<PaymentAccount> accounts, String fileName, Preferences preferences, Stage stage) {
if (!accounts.isEmpty()) {
DirectoryChooser directoryChooser = new DirectoryChooser();
directoryChooser.setInitialDirectory(new File(preferences.getDefaultPath()));
directoryChooser.setTitle("Select export path");
File dir = directoryChooser.showDialog(stage);
if (dir != null) {
String directory = dir.getAbsolutePath();
preferences.setDefaultPath(directory);
Storage<ArrayList<PaymentAccount>> paymentAccountsStorage = new Storage<>(new File(directory));
paymentAccountsStorage.initAndGetPersisted(accounts, fileName);
paymentAccountsStorage.queueUpForSave(20);
new Popup<>().feedback("Payment accounts saved to path:\n" + Paths.get(directory, fileName).toAbsolutePath()).show();
}
String directory = getDirectoryFormChooser(preferences, stage);
Storage<ArrayList<PaymentAccount>> paymentAccountsStorage = new Storage<>(new File(directory));
paymentAccountsStorage.initAndGetPersisted(accounts, fileName);
paymentAccountsStorage.queueUpForSave(20);
new Popup<>().feedback("Payment accounts saved to path:\n" + Paths.get(directory, fileName).toAbsolutePath()).show();
} else {
new Popup<>().warning("You don't have payment accounts set up for exporting.").show();
}
@ -119,4 +121,46 @@ public class GUIUtil {
}
}
}
public static <T> void exportCSV(String fileName, CSVEntryConverter<T> headerConverter,
CSVEntryConverter<T> contentConverter, T emptyItem,
List<T> list, Stage stage) {
FileChooser fileChooser = new FileChooser();
fileChooser.setInitialFileName(fileName);
File file = fileChooser.showSaveDialog(stage);
try (OutputStreamWriter outputStreamWriter = new OutputStreamWriter(new FileOutputStream(file, false), Charsets.UTF_8)) {
CSVWriter<T> headerWriter = new CSVWriterBuilder<T>(outputStreamWriter)
.strategy(CSVStrategy.UK_DEFAULT)
.entryConverter(headerConverter)
.build();
headerWriter.write(emptyItem);
CSVWriter<T> contentWriter = new CSVWriterBuilder<T>(outputStreamWriter)
.strategy(CSVStrategy.UK_DEFAULT)
.entryConverter(contentConverter)
.build();
contentWriter.writeAll(list);
} catch (RuntimeException | IOException e) {
e.printStackTrace();
log.error(e.getMessage());
new Popup().error("Exporting to CSV failed because of an error.\n" +
"Error = " + e.getMessage());
}
}
public static String getDirectoryFormChooser(Preferences preferences, Stage stage) {
DirectoryChooser directoryChooser = new DirectoryChooser();
directoryChooser.setInitialDirectory(new File(preferences.getDefaultPath()));
directoryChooser.setTitle("Select export path");
File dir = directoryChooser.showDialog(stage);
if (dir != null) {
String directory = dir.getAbsolutePath();
preferences.setDefaultPath(directory);
return directory;
} else {
return "";
}
}
}