Skip to content
This repository has been archived by the owner on Feb 1, 2024. It is now read-only.

Commit

Permalink
Kelp UI: Edit bots part 3 (#189)
Browse files Browse the repository at this point in the history
addresses part of issue #67

* 1 - remove hardcoded error message

* 2 - bot is only editable when stopped

* 3 - disable editing names for now

* 4 - streamline frontend save logic workflow

* 5 - added /updateBotConfig endpoint and integrated to frontend

* 6 - fix checks around null values of _asyncRequests and .cancel() calls
  • Loading branch information
nikhilsaraf authored Jun 25, 2019
1 parent 258e026 commit 45c16e7
Show file tree
Hide file tree
Showing 10 changed files with 215 additions and 63 deletions.
2 changes: 1 addition & 1 deletion gui/backend/api_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ type ErrorResponse struct {
}

func (s *APIServer) writeErrorJson(w http.ResponseWriter, message string) {
log.Print(message)
log.Println(message)
w.WriteHeader(http.StatusInternalServerError)

marshalledJson, e := json.MarshalIndent(ErrorResponse{Error: message}, "", " ")
Expand Down
1 change: 1 addition & 0 deletions gui/backend/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,6 @@ func SetRoutes(r *chi.Mux, s *APIServer) {
r.Post("/getBotInfo", http.HandlerFunc(s.getBotInfo))
r.Post("/getBotConfig", http.HandlerFunc(s.getBotConfig))
r.Post("/fetchPrice", http.HandlerFunc(s.fetchPrice))
r.Post("/updateBotConfig", http.HandlerFunc(s.updateBotConfig))
})
}
73 changes: 73 additions & 0 deletions gui/backend/update_bot_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package backend

import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"

"github.com/stellar/kelp/gui/model"
"github.com/stellar/kelp/plugins"
"github.com/stellar/kelp/support/kelpos"
"github.com/stellar/kelp/support/toml"
"github.com/stellar/kelp/trader"
)

type updateBotConfigRequest struct {
Name string `json:"name"`
Strategy string `json:"strategy"`
TraderConfig trader.BotConfig `json:"trader_config"`
StrategyConfig plugins.BuySellConfig `json:"strategy_config"`
}

type updateBotConfigResponse struct {
Success bool `json:"success"`
}

func (s *APIServer) updateBotConfig(w http.ResponseWriter, r *http.Request) {
bodyBytes, e := ioutil.ReadAll(r.Body)
if e != nil {
s.writeErrorJson(w, fmt.Sprintf("error reading request input: %s", e))
return
}
log.Printf("updateBotConfig requestJson: %s\n", string(bodyBytes))

var req updateBotConfigRequest
e = json.Unmarshal(bodyBytes, &req)
if e != nil {
s.writeErrorJson(w, fmt.Sprintf("error unmarshaling json: %s; bodyString = %s", e, string(bodyBytes)))
return
}

botState, e := s.kos.QueryBotState(req.Name)
if e != nil {
s.writeErrorJson(w, fmt.Sprintf("error getting bot state for bot '%s': %s", req.Name, e))
return
}
if botState != kelpos.BotStateStopped {
s.writeErrorJson(w, fmt.Sprintf("bot state needs to be '%s' when updating bot config, but was '%s'\n", kelpos.BotStateStopped, botState))
return
}

filenamePair := model.GetBotFilenames(req.Name, req.Strategy)
traderFilePath := fmt.Sprintf("%s/%s", s.configsDir, filenamePair.Trader)
botConfig := req.TraderConfig
log.Printf("updating bot config to file: %s\n", traderFilePath)
e = toml.WriteFile(traderFilePath, &botConfig)
if e != nil {
s.writeErrorJson(w, fmt.Sprintf("error writing trader botConfig toml file for bot '%s': %s", req.Name, e))
return
}

strategyFilePath := fmt.Sprintf("%s/%s", s.configsDir, filenamePair.Strategy)
strategyConfig := req.StrategyConfig
log.Printf("updating strategy config to file: %s\n", strategyFilePath)
e = toml.WriteFile(strategyFilePath, &strategyConfig)
if e != nil {
s.writeErrorJson(w, fmt.Sprintf("error writing strategy toml file for bot '%s': %s", req.Name, e))
return
}

s.writeJson(w, updateBotConfigResponse{Success: true})
}
16 changes: 11 additions & 5 deletions gui/web/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,26 @@ class App extends Component {
this.state = {
version: ""
};

this._asyncRequests = {};
}

componentDidMount() {
var _this = this
this._asyncRequest = version(baseUrl).then(resp => {
_this._asyncRequest = null;
this._asyncRequests["version"] = version(baseUrl).then(resp => {
if (!_this._asyncRequests["version"]) {
// if it has been deleted it means we don't want to process the result
return
}

delete _this._asyncRequests["version"];
_this.setState({version: resp});
});
}

componentWillUnmount() {
if (this._asyncRequest) {
this._asyncRequest.cancel();
this._asyncRequest = null;
if (this._asyncRequests["version"]) {
delete this._asyncRequests["version"];
}
}

Expand Down
56 changes: 38 additions & 18 deletions gui/web/src/components/molecules/BotCard/BotCard.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,15 @@ class BotCard extends Component {
};

checkState() {
if (this._asyncRequests["state"] == null) {
if (!this._asyncRequests["state"]) {
var _this = this;
this._asyncRequests["state"] = getState(this.props.baseUrl, this.props.name).then(resp => {
_this._asyncRequests["state"] = null;
if (!_this._asyncRequests["state"]) {
// if it has been deleted it means we don't want to process the result
return
}

delete _this._asyncRequests["state"];
let state = resp.trim();
if (_this.state.state !== state) {
_this.setState({
Expand All @@ -99,10 +104,15 @@ class BotCard extends Component {
}

checkBotInfo() {
if (this._asyncRequests["botInfo"] == null) {
if (!this._asyncRequests["botInfo"]) {
var _this = this;
this._asyncRequests["botInfo"] = getBotInfo(this.props.baseUrl, this.props.name).then(resp => {
_this._asyncRequests["botInfo"] = null;
if (!_this._asyncRequests["botInfo"]) {
// if it has been deleted it means we don't want to process the result
return
}

delete _this._asyncRequests["botInfo"];
if (JSON.stringify(resp) !== "{}") {
_this.setState({
botInfo: resp,
Expand Down Expand Up @@ -140,28 +150,23 @@ class BotCard extends Component {
}

if (this._asyncRequests["state"]) {
this._asyncRequests["state"].cancel();
this._asyncRequests["state"] = null;
delete this._asyncRequests["state"];
}

if (this._asyncRequests["start"]) {
this._asyncRequests["start"].cancel();
this._asyncRequests["start"] = null;
delete this._asyncRequests["start"];
}

if (this._asyncRequests["stop"]) {
this._asyncRequests["stop"].cancel();
this._asyncRequests["stop"] = null;
delete this._asyncRequests["stop"];
}

if (this._asyncRequests["delete"]) {
this._asyncRequests["delete"].cancel();
this._asyncRequests["delete"] = null;
delete this._asyncRequests["delete"];
}

if (this._asyncRequests["botInfo"]) {
this._asyncRequests["botInfo"].cancel();
this._asyncRequests["botInfo"] = null;
delete this._asyncRequests["botInfo"];
}
}

Expand All @@ -178,7 +183,12 @@ class BotCard extends Component {
startBot() {
var _this = this;
this._asyncRequests["start"] = start(this.props.baseUrl, this.props.name).then(resp => {
_this._asyncRequests["start"] = null;
if (!_this._asyncRequests["start"]) {
// if it has been deleted it means we don't want to process the result
return
}

delete _this._asyncRequests["start"];

_this.setState({
timeStarted: new Date(),
Expand All @@ -194,7 +204,12 @@ class BotCard extends Component {
stopBot() {
var _this = this;
this._asyncRequests["stop"] = stop(this.props.baseUrl, this.props.name).then(resp => {
_this._asyncRequests["stop"] = null;
if (!_this._asyncRequests["stop"]) {
// if it has been deleted it means we don't want to process the result
return
}

delete _this._asyncRequests["stop"];
_this.setState({
timeStarted: null,
});
Expand All @@ -206,7 +221,12 @@ class BotCard extends Component {
callDeleteBot() {
var _this = this;
this._asyncRequests["delete"] = deleteBot(this.props.baseUrl, this.props.name).then(resp => {
_this._asyncRequests["delete"] = null;
if (!_this._asyncRequests["delete"]) {
// if it has been deleted it means we don't want to process the result
return
}

delete _this._asyncRequests["delete"];
clearTimeout(_this._tickTimer);
_this._tickTimer = null;
// reload parent view
Expand Down Expand Up @@ -245,7 +265,7 @@ class BotCard extends Component {
<div className={styles.optionsSpacer}/>
<PopoverMenu
className={styles.optionsMenu}
enableEdit={true}
enableEdit={this.state.state === Constants.BotState.stopped}
onEdit={this.editBot}
enableCopy={false}
onCopy={this.toggleOptions}
Expand Down
32 changes: 12 additions & 20 deletions gui/web/src/components/molecules/Form/Form.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,20 @@ import FieldGroup from '../FieldGroup/FieldGroup';
import PriceFeedAsset from '../PriceFeedAsset/PriceFeedAsset';
import PriceFeedFormula from '../PriceFeedFormula/PriceFeedFormula';
import Levels from '../Levels/Levels';
import ErrorMessage from '../ErrorMessage/ErrorMessage';
// import ErrorMessage from '../ErrorMessage/ErrorMessage';

class Form extends Component {
constructor(props) {
super(props);
this.state = {
isLoading: false,
isSaving: false,
isLoadingFormula: true,
numerator: null,
denominator: null,
};
this.setLoadingFormula = this.setLoadingFormula.bind(this);
this.updateFormulaPrice = this.updateFormulaPrice.bind(this);
this.save = this.save.bind(this);
this.collectConfigData = this.collectConfigData.bind(this);
this.priceFeedAssetChangeHandler = this.priceFeedAssetChangeHandler.bind(this);
this.updateLevel = this.updateLevel.bind(this);
this.newLevel = this.newLevel.bind(this);
Expand All @@ -42,11 +41,6 @@ class Form extends Component {
this._last_fill_tracker_sleep_millis = 1000;
}

collectConfigData() {
// TODO collect config data from UI elements
return this.props.configData;
}

setLoadingFormula() {
this.setState({
isLoadingFormula: true
Expand All @@ -69,15 +63,10 @@ class Form extends Component {

save() {
this.setState({
isLoading: true,
isSaving: true,
})
let errorFields = this.props.saveFn(this.collectConfigData());
if (errorFields) {
// TODO mark errors
return
}

this.props.router.goBack();
this.props.saveFn();
// save fn will call router.goBack();
}

priceFeedAssetChangeHandler(ab, newValues) {
Expand Down Expand Up @@ -153,7 +142,7 @@ class Form extends Component {
// update levels and always set amount_of_a_base to 1.0
this.props.onChange(
"strategy_config.levels", {target: {value: newLevels}},
{ "strategy_config.amount_of_a_base": (value) => { return "1.0"; } }
{ "strategy_config.amount_of_a_base": (value) => { return 1.0; } }
)
}

Expand Down Expand Up @@ -182,6 +171,7 @@ class Form extends Component {
value={this.props.configData.name}
type="string"
onChange={(event) => { this.props.onChange("name", event) }}
disabled={!this.props.isNew}
/>

{/* Trader Settings */}
Expand Down Expand Up @@ -619,14 +609,16 @@ class Form extends Component {
</div>
</AdvancedWrapper>

{/* <ErrorMessage/> */}

<div className={grid.container}>
<ErrorMessage/>
<div className={styles.formFooter}>
<Button
icon="add"
size="large"
loading={this.state.isLoading}
onClick={this.save}>
loading={this.state.isSaving}
onClick={this.save}
>
{this.props.saveText}
</Button>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,12 @@ class PriceFeedAsset extends Component {

var _this = this;
this._asyncRequests["price"] = fetchPrice(this.props.baseUrl, this.props.type, this.props.feed_url).then(resp => {
_this._asyncRequests["price"] = null;
if (!_this._asyncRequests["price"]) {
// if it has been deleted it means we don't want to process the result
return
}

delete _this._asyncRequests["price"];
let updateStateObj = { isLoading: false };
if (!resp.error) {
updateStateObj.price = resp.price
Expand All @@ -205,8 +210,7 @@ class PriceFeedAsset extends Component {

componentWillUnmount() {
if (this._asyncRequests["price"]) {
this._asyncRequests["price"].cancel();
this._asyncRequests["price"] = null;
delete this._asyncRequests["price"];
}
}

Expand Down
Loading

0 comments on commit 45c16e7

Please sign in to comment.