-
Notifications
You must be signed in to change notification settings - Fork 0
/
push.go
247 lines (232 loc) · 7.49 KB
/
push.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
// Package cyfe implements the basic Push API for Cyfe in Go according to https://www.cyfe.com/api
// It is important to remember that no calls will actually be made if CYFE_ENV is not set to production. See Push() for more information
package cyfe
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"strings"
"time"
"github.com/go-resty/resty"
)
// PushOptions are a set of options for a specific Push request. Sane defaults are used (hence why the names differ than in the Cyfe API docs) so that, for
// example, false equates to the default behavior for instantiation
type PushOptions struct {
// ReplaceInstead toggles whether a new push adds to the value (false, default) or replaces the value (true)
ReplaceInstead bool
Color string
Type string
IsCumulative bool
DisplayAverages bool
OverwriteTotal bool
OverwriteComparison bool
// IsBad specifies if a higher value for this metric is "bad" (see reverse in docs)
IsBad bool
IsUpsideDownGraph bool
UnsyncYAxis bool
YAxisMin string
YAxisMax string
YAxisShow bool
ShowLabel bool
// Token is available to override a token read from the end point
Token string
}
// PushSendRequest is the formatted request to be sent to the Cyfe server
type PushSendRequest struct {
Data []map[string]string `json:"data,omitempty"`
OnDuplicate *map[string]string `json:"onduplicate,omitempty"`
Color *map[string]string `json:"color,omitempty"`
Type *map[string]string `json:"type,omitempty"`
Cumulative *map[string]string `json:"cumulative,omitempty"`
Average *map[string]string `json:"average,omitempty"`
Total *map[string]string `json:"total,omitempty"`
Comparison *map[string]string `json:"comparison,omitempty"`
Reverse *map[string]string `json:"reverse,omitempty"`
ReverseGraph *map[string]string `json:"reversegraph,omitempty"`
YAxis *map[string]string `json:"yaxis,omitempty"`
YAxisMin *map[string]string `json:"yaxismin,omitempty"`
YAxisMax *map[string]string `json:"yaxismax,omitempty"`
YAxisShow *map[string]string `json:"yaxisshow,omitempty"`
LabelShow *map[string]string `json:"labelshow,omitempty"`
ChartToken string `json:"-"`
}
// APIReturn is the success return from a successful push
type APIReturn struct {
StatusCode int `json:"statusCode"`
Status string `json:"status"`
Message string `json:"message"`
}
// Prepare prepares a metric to be sent by filling out all of the options and formatting the data. Currently, you can
// only push one metric per call. A future improvement would be to allow multiple metricLabel/metricValue pairs. If keyLabel is empty or keyLabel is date
// AND keyValue is empty, the keyLabel will be set to Date (intentional capitalization as per the docs) and the current UTC timestamp
func Prepare(metricLabel, metricValue, keyLabel, keyValue string, options *PushOptions) (request *PushSendRequest, err error) {
request = CreateDefaultSendRequest(metricLabel)
if keyLabel == "" || (keyLabel == "date" && keyValue == "") {
// make the key the current date
keyLabel = "Date"
keyValue = time.Now().In(config.timezone).Format("20060102")
}
// lookup the chart token; if we can't find it, error
chartToken := ""
// first, check if there is an override
if options != nil && options.Token != "" {
chartToken = options.Token
} else {
for i := range config.metricLookups {
if config.metricLookups[i].Metric == metricLabel {
chartToken = config.metricLookups[i].Token
break
}
}
}
if chartToken == "" {
err = errors.New("chart token not found for metric " + metricLabel)
return
}
request.ChartToken = chartToken
// build out the data structure
request.Data = []map[string]string{
map[string]string{
keyLabel: keyValue,
metricLabel: metricValue,
},
}
// now we loop over the options to build the map to send
if options != nil {
if options.ReplaceInstead {
request.OnDuplicate = &map[string]string{
metricLabel: "replace",
}
}
if options.Color != "" {
request.Color = &map[string]string{
metricLabel: options.Color,
}
}
if options.Type != "" {
request.Type = &map[string]string{
metricLabel: options.Type,
}
}
if options.IsCumulative {
request.Cumulative = &map[string]string{
metricLabel: "1",
}
}
if options.DisplayAverages {
request.Average = &map[string]string{
metricLabel: "1",
}
}
if options.OverwriteTotal {
request.Total = &map[string]string{
metricLabel: "1",
}
}
if options.OverwriteComparison {
request.Comparison = &map[string]string{
metricLabel: "1",
}
}
if options.IsBad {
request.Reverse = &map[string]string{
metricLabel: "1",
}
}
if options.IsUpsideDownGraph {
request.ReverseGraph = &map[string]string{
metricLabel: "1",
}
}
if options.UnsyncYAxis {
request.YAxis = &map[string]string{
metricLabel: "1",
}
}
if options.YAxisMin != "" {
request.YAxisMin = &map[string]string{
metricLabel: options.YAxisMin,
}
}
if options.YAxisMax != "" {
request.YAxisMax = &map[string]string{
metricLabel: options.YAxisMax,
}
}
if options.YAxisShow {
request.YAxisShow = &map[string]string{
metricLabel: "1",
}
}
if options.ShowLabel {
request.LabelShow = &map[string]string{
metricLabel: "1",
}
}
}
return
}
// JustPush is a simpler Push implementation which uses just the defaults. Useful if you don't like typing and just want to get
// a metric to the server
func JustPush(metricLabel, metricValue string) (request *PushSendRequest, ret APIReturn, err error) {
request, err = Prepare(metricLabel, metricValue, "", "", nil)
if err != nil {
return
}
ret, err = Push(request)
return
}
// Push actually makes the push request. NOTE: If the CYFE_ENV environment variable is not set to production, the request is
// NOT actually sent. This is to prevent accidentally sending metrics in test or development environments.
func Push(request *PushSendRequest) (ret APIReturn, err error) {
if request == nil {
err = errors.New("request cannot be nil")
}
if strings.HasPrefix(request.ChartToken, "/") {
request.ChartToken = request.ChartToken[1:]
}
// is the environment isn't production, we don't make the call
if !isProd() {
// return as if it was a code call
// if the token is set to badtoken, we are likely in a test and want to return an error
if request.ChartToken == "badtoken" {
ret = APIReturn{
Status: "error",
StatusCode: http.StatusBadRequest,
Message: "Invalid widget key",
}
err = errors.New(ret.Message)
} else {
ret = APIReturn{
Status: "ok",
StatusCode: http.StatusOK,
Message: "Data pushed",
}
}
return
}
httpRequest, err := resty.R().
SetHeader("Accept", "application/json").
SetBody(request).
Post(fmt.Sprintf("%s%s", config.cyfeRoot, request.ChartToken))
if err != nil {
return
}
err = json.Unmarshal(httpRequest.Body(), &ret)
if err != nil {
err = errors.New("could not unmarshal the JSON response; check the API")
return
}
ret.StatusCode = httpRequest.StatusCode()
if httpRequest.StatusCode() != http.StatusOK {
// the err should be the parsed message
err = errors.New(ret.Message)
}
return
}
// CreateDefaultSendRequest initializes sane defaults for the send request. Right now it does nothing, but
// could be a place to override basics if needed
func CreateDefaultSendRequest(metricLabel string) (send *PushSendRequest) {
return &PushSendRequest{}
}