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

Commit 07e8b1e

Browse files
authored
Add GUI metrics to Button.js and refactor metrics tracker and frontend metric architecture (part of #508) (#642)
* 1 - changes from existing PR 582 * 2 - changes from my comments on PR * 3 - pass in correct baseURL to sendMetricEvent from Button.js * 4 - fix check for metrics when in local dev mode (no metrics tracker) * 5 - metrics tracker should throw when amplitude returns invalid response * 6 - fix API usage of /sendMetricEvent * 7 - warn when no event name on button * 8 - use correct event name type and data across the frontend, API, backend * 9 - simplify metrics tracker event props naming * 10 - refactor and simplify metricsTracker factory methods to use a single factory method this ensures we do not send cli props for gui actions etc since they are separated * 11 - prefix gui specific event properties as gui_ * 12 - isTestnet should be hardcoded to true by default * 13 - change warning when no event name to an error * 14 - set button names for welcome screen * 15 - set button names for all remaining buttons by threading eventPrefix * 16 - rename event_name to gui_event_name * 17 - fix warnings in Button.js
1 parent b70c22c commit 07e8b1e

23 files changed

Lines changed: 357 additions & 279 deletions

File tree

cmd/server_amd64.go

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -298,24 +298,27 @@ func init() {
298298
if e != nil {
299299
panic(fmt.Errorf("could not generate machine id: %s", e))
300300
}
301-
302-
httpClient := &http.Client{}
303-
metricsTracker, e = plugins.MakeMetricsTrackerGui(
304-
deviceID,
305-
deviceID,
301+
userID := deviceID // reuse for now
302+
metricsTracker, e = plugins.MakeMetricsTracker(
303+
http.DefaultClient,
306304
amplitudeAPIKey,
307-
httpClient,
308-
time.Now(), // TODO: Find proper time.
309-
version,
310-
gitHash,
311-
env,
312-
runtime.GOOS,
313-
runtime.GOARCH,
314-
"unknown_todo", // TODO DS Determine how to get GOARM.
315-
runtime.Version(),
316-
guiVersion,
305+
userID,
306+
deviceID,
307+
time.Now(), // TODO: Find proper time.
317308
*options.noHeaders, // disable metrics if the CLI specified no headers
318-
309+
plugins.MakeCommonProps(
310+
version,
311+
gitHash,
312+
env,
313+
runtime.GOOS,
314+
runtime.GOARCH,
315+
"unknown_todo", // TODO DS Determine how to get GOARM.
316+
runtime.Version(),
317+
0,
318+
true, // isTestnet hardcoded to true for now, but once we allow it on the GUI via enablePubnetBots then this should be set accordingly
319+
guiVersion,
320+
),
321+
nil,
319322
)
320323
if e != nil {
321324
panic(e)

cmd/trade.go

Lines changed: 52 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ func makeFeeFn(l logger.Logger, botConfig trader.BotConfig, newClient *horizoncl
243243
return feeFn
244244
}
245245

246-
func readBotConfig(l logger.Logger, options inputs, botStart time.Time) trader.BotConfig {
246+
func readBotConfig(l logger.Logger, options inputs, botStartTime time.Time) trader.BotConfig {
247247
var botConfig trader.BotConfig
248248
e := config.Read(*options.botConfigPath, &botConfig)
249249
utils.CheckConfigError(botConfig, e, *options.botConfigPath)
@@ -253,7 +253,7 @@ func readBotConfig(l logger.Logger, options inputs, botStart time.Time) trader.B
253253
}
254254

255255
if *options.logPrefix != "" {
256-
logFilename := makeLogFilename(*options.logPrefix, botConfig, botStart)
256+
logFilename := makeLogFilename(*options.logPrefix, botConfig, botStartTime)
257257
setLogFile(l, logFilename)
258258
}
259259

@@ -427,7 +427,7 @@ func makeBot(
427427
threadTracker *multithreading.ThreadTracker,
428428
options inputs,
429429
metricsTracker *plugins.MetricsTracker,
430-
botStart time.Time,
430+
botStartTime time.Time,
431431
) *trader.Trader {
432432
timeController := plugins.MakeIntervalTimeController(
433433
time.Duration(botConfig.TickIntervalMillis)*time.Millisecond,
@@ -530,7 +530,7 @@ func makeBot(
530530
dataKey,
531531
alert,
532532
metricsTracker,
533-
botStart,
533+
botStartTime,
534534
)
535535
}
536536

@@ -558,66 +558,67 @@ func convertDeprecatedBotConfigValues(l logger.Logger, botConfig trader.BotConfi
558558

559559
func runTradeCmd(options inputs) {
560560
l := logger.MakeBasicLogger()
561-
botStart := time.Now()
562-
botConfig := readBotConfig(l, options, botStart)
561+
botStartTime := time.Now()
562+
botConfig := readBotConfig(l, options, botStartTime)
563563
botConfig = convertDeprecatedBotConfigValues(l, botConfig)
564564
l.Infof("Trading %s:%s for %s:%s\n", botConfig.AssetCodeA, botConfig.IssuerA, botConfig.AssetCodeB, botConfig.IssuerB)
565565

566-
userID, e := getUserID(l, botConfig)
567-
if e != nil {
568-
logger.Fatal(l, fmt.Errorf("could not get user id: %s", e))
569-
}
570-
571-
httpClient := &http.Client{}
572566
var guiVersionFlag string
573567
if *options.ui {
574568
guiVersionFlag = guiVersion
575569
}
576570

571+
userID, e := getUserID(l, botConfig)
572+
if e != nil {
573+
logger.Fatal(l, fmt.Errorf("could not get user id: %s", e))
574+
}
577575
deviceID, e := machineid.ID()
578576
if e != nil {
579577
logger.Fatal(l, fmt.Errorf("could not generate machine id: %s", e))
580578
}
581-
582579
isTestnet := strings.Contains(botConfig.HorizonURL, "test") && botConfig.IsTradingSdex()
583-
584-
metricsTracker, e := plugins.MakeMetricsTrackerCli(
580+
metricsTracker, e := plugins.MakeMetricsTracker(
581+
http.DefaultClient,
582+
amplitudeAPIKey,
585583
userID,
586584
deviceID,
587-
amplitudeAPIKey,
588-
httpClient,
589-
botStart,
590-
version,
591-
gitHash,
592-
env,
593-
runtime.GOOS,
594-
runtime.GOARCH,
595-
goarm,
596-
runtime.Version(),
597-
guiVersionFlag,
598-
*options.strategy,
599-
float64(botConfig.TickIntervalMillis)/1000,
600-
botConfig.TradingExchange,
601-
botConfig.TradingPair(),
585+
botStartTime,
602586
*options.noHeaders, // disable metrics if the CLI specified no headers
603-
isTestnet,
604-
botConfig.MaxTickDelayMillis,
605-
botConfig.SubmitMode,
606-
botConfig.DeleteCyclesThreshold,
607-
botConfig.FillTrackerSleepMillis,
608-
botConfig.FillTrackerDeleteCyclesThreshold,
609-
botConfig.SynchronizeStateLoadEnable,
610-
botConfig.SynchronizeStateLoadMaxRetries,
611-
botConfig.DollarValueFeedBaseAsset != "" && botConfig.DollarValueFeedQuoteAsset != "",
612-
botConfig.AlertType,
613-
int(botConfig.MonitoringPort) != 0,
614-
len(botConfig.Filters) > 0,
615-
botConfig.PostgresDbConfig != nil,
616-
*options.logPrefix != "",
617-
*options.operationalBuffer,
618-
*options.operationalBufferNonNativePct,
619-
*options.simMode,
620-
*options.fixedIterations,
587+
plugins.MakeCommonProps(
588+
version,
589+
gitHash,
590+
env,
591+
runtime.GOOS,
592+
runtime.GOARCH,
593+
goarm,
594+
runtime.Version(),
595+
0,
596+
isTestnet,
597+
guiVersionFlag,
598+
),
599+
plugins.MakeCliProps(
600+
*options.strategy,
601+
float64(botConfig.TickIntervalMillis)/1000,
602+
botConfig.TradingExchange,
603+
botConfig.TradingPair(),
604+
botConfig.MaxTickDelayMillis,
605+
botConfig.SubmitMode,
606+
botConfig.DeleteCyclesThreshold,
607+
botConfig.FillTrackerSleepMillis,
608+
botConfig.FillTrackerDeleteCyclesThreshold,
609+
botConfig.SynchronizeStateLoadEnable,
610+
botConfig.SynchronizeStateLoadMaxRetries,
611+
botConfig.DollarValueFeedBaseAsset != "" && botConfig.DollarValueFeedQuoteAsset != "",
612+
botConfig.AlertType,
613+
int(botConfig.MonitoringPort) != 0,
614+
len(botConfig.Filters) > 0,
615+
botConfig.PostgresDbConfig != nil,
616+
*options.logPrefix != "",
617+
*options.operationalBuffer,
618+
*options.operationalBufferNonNativePct,
619+
*options.simMode,
620+
*options.fixedIterations,
621+
),
621622
)
622623
if e != nil {
623624
logger.Fatal(l, fmt.Errorf("could not generate metrics tracker: %s", e))
@@ -774,7 +775,7 @@ func runTradeCmd(options inputs) {
774775
threadTracker,
775776
options,
776777
metricsTracker,
777-
botStart,
778+
botStartTime,
778779
)
779780
// --- end initialization of objects ---
780781
// --- start initialization of services ---
@@ -1048,8 +1049,8 @@ func setLogFile(l logger.Logger, filename string) {
10481049
defer logPanic(l, false)
10491050
}
10501051

1051-
func makeLogFilename(logPrefix string, botConfig trader.BotConfig, botStart time.Time) string {
1052-
botStartStr := botStart.Format("20060102T150405MST")
1052+
func makeLogFilename(logPrefix string, botConfig trader.BotConfig, botStartTime time.Time) string {
1053+
botStartStr := botStartTime.Format("20060102T150405MST")
10531054
if botConfig.IsTradingSdex() {
10541055
return fmt.Sprintf("%s_%s_%s_%s_%s_%s.log", logPrefix, botConfig.AssetCodeA, botConfig.IssuerA, botConfig.AssetCodeB, botConfig.IssuerB, botStartStr)
10551056
}

gui/backend/send_metric_event.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010
)
1111

1212
type sendMetricEventRequest struct {
13-
EventName string `json:"event_name"`
13+
EventType string `json:"event_type"`
1414
EventData map[string]interface{} `json:"event_data"`
1515
}
1616

@@ -34,9 +34,9 @@ func (s *APIServer) sendMetricEvent(w http.ResponseWriter, r *http.Request) {
3434
}
3535

3636
// TODO DS Properly extract and compute time for SendEvent
37-
e = s.metricsTracker.SendEvent(req.EventName, req.EventData, time.Now())
37+
e = s.metricsTracker.SendEvent(req.EventType, req.EventData, time.Now())
3838
if e != nil {
39-
s.writeErrorJson(w, fmt.Sprintf("error sending gui event %s: %s", req.EventName, e))
39+
s.writeErrorJson(w, fmt.Sprintf("error sending gui event %s: %s", string(bodyBytes), e))
4040
return
4141
}
4242

gui/web/src/App.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,14 @@ import removeKelpErrors from './kelp-ops-api/removeKelpErrors';
1313
import Welcome from './components/molecules/Welcome/Welcome';
1414

1515
let baseUrl = function () {
16-
let origin = window.location.origin
16+
let base_url = window.location.origin;
1717
if (process.env.REACT_APP_API_PORT) {
18-
let parts = origin.split(":")
19-
return parts[0] + ":" + parts[1] + ":" + process.env.REACT_APP_API_PORT;
18+
let parts = origin.split(":");
19+
base_url = parts[0] + ":" + parts[1] + ":" + process.env.REACT_APP_API_PORT;
2020
}
21-
return origin;
22-
}()
21+
Constants.setGlobalBaseURL(base_url);
22+
return base_url;
23+
}();
2324

2425
class App extends Component {
2526
constructor(props) {
@@ -296,6 +297,7 @@ class App extends Component {
296297

297298
let banner = (<div className={styles.banner}>
298299
<Button
300+
eventName="main-quit"
299301
className={styles.quit}
300302
size="small"
301303
onClick={this.quit}

gui/web/src/Constants.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export default {
1+
const constants = {
22
BotState: {
33
initializing: "initializing",
44
stopped: "stopped",
@@ -15,4 +15,12 @@ export default {
1515
error: "error",
1616
warning: "warning",
1717
},
18-
}
18+
19+
BaseURL: "",
20+
21+
setGlobalBaseURL: (baseUrl) => {
22+
constants.BaseURL = baseUrl;
23+
},
24+
};
25+
26+
export default constants;

gui/web/src/components/atoms/Button/Button.js

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import React, { Component } from 'react';
22
import PropTypes from 'prop-types';
33
import classNames from 'classnames';
4+
import Constants from '../../../Constants';
45
import styles from './Button.module.scss';
56
import Icon from '../Icon/Icon';
67
import LoadingAnimation from '../LoadingAnimation/LoadingAnimation';
8+
import sendMetricEvent from '../../../kelp-ops-api/sendMetricEvent';
79

810
const iconSizes = {
911
small:'10px',
@@ -18,6 +20,14 @@ const iconSizesRound = {
1820
}
1921

2022
class Button extends Component {
23+
constructor(props) {
24+
super(props);
25+
this.trackOnClick = this.trackOnClick.bind(this);
26+
this.sendMetricEvent = this.sendMetricEvent.bind(this);
27+
28+
this._asyncRequests = {};
29+
}
30+
2131
static defaultProps = {
2232
icon: null,
2333
size: 'medium',
@@ -34,9 +44,53 @@ class Button extends Component {
3444
variant: PropTypes.string,
3545
onClick: PropTypes.func,
3646
loading: PropTypes.bool,
37-
disabled: PropTypes.bool
47+
disabled: PropTypes.bool,
48+
// we specify a custom validator. It should return an Error object if the validation fails
49+
// don't `console.warn` or throw, as this won't work inside `oneOfType`.
50+
eventName: function(props, propName, componentName) {
51+
// "-" needs to be first or last character to be used literally
52+
// source: https://stackoverflow.com/questions/8833963/allow-dash-in-regular-expression
53+
if (!/^[-a-zA-Z0-9]+$/.test(props[propName])) {
54+
return new Error('Invalid prop `' + propName + '` supplied to `' + componentName + '`. Validation failed.');
55+
}
56+
},
3857
};
3958

59+
sendMetricEvent() {
60+
if (this._asyncRequests["sendMetricEvent"]) {
61+
return
62+
}
63+
64+
if (!this.props.eventName || this.props.eventName === "") {
65+
console.error("programmer error: no event name provided for this Button, not sending button click event!");
66+
return
67+
}
68+
69+
const _this = this;
70+
const eventData = {
71+
gui_event_name: this.props.eventName,
72+
gui_category: "generic",
73+
gui_component: "button"
74+
};
75+
this._asyncRequests["sendMetricEvent"] = sendMetricEvent(Constants.BaseURL, "gui-button", eventData).then(resp => {
76+
if (!_this._asyncRequests["sendMetricEvent"]) {
77+
// if it has been deleted it means we don't want to process the result
78+
return
79+
}
80+
delete _this._asyncRequests["sendMetricEvent"];
81+
82+
if (!resp.success) {
83+
console.log(resp.error);
84+
}
85+
// else do nothing on success
86+
});
87+
}
88+
89+
trackOnClick() {
90+
this.sendMetricEvent();
91+
this.props.onClick();
92+
}
93+
4094
render() {
4195
const iconOnly = this.props.children ? null : styles.iconOnly;
4296
const isLoading = this.props.loading ? styles.isLoading : null;
@@ -57,7 +111,7 @@ class Button extends Component {
57111
<button
58112
className={classNameList}
59113
disabled={this.props.disabled || this.props.loading }
60-
onClick= {this.props.onClick}
114+
onClick= {this.trackOnClick}
61115
>
62116
{this.props.loading &&
63117
<span className={styles.loader}>

gui/web/src/components/atoms/StartStop/StartStop.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,22 @@ class StartStop extends Component {
1717
render() {
1818
let icon = "";
1919
let variant = "";
20-
let text = ""
20+
let text = "";
21+
let eventName = "";
2122
if (this.props.state === Constants.BotState.running) {
2223
icon = "stop";
2324
variant = "stop";
2425
text = "Stop";
26+
eventName = "bot-stop";
2527
} else {
2628
icon = "start";
2729
variant = "start";
2830
text = "Start";
31+
eventName = "bot-start";
2932
}
3033
let disabled = this.props.state === Constants.BotState.initializing || this.props.state === Constants.BotState.stopping;
3134

32-
return (<Button icon={icon} size="small" variant={variant} onClick={this.props.onClick} disabled={disabled}>{text}</Button>);
35+
return (<Button eventName={eventName} icon={icon} size="small" variant={variant} onClick={this.props.onClick} disabled={disabled}>{text}</Button>);
3336
}
3437
}
3538

0 commit comments

Comments
 (0)