-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathgraph.go
372 lines (319 loc) · 14.5 KB
/
graph.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
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
// Copyright 2016 Circonus, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Graph API support - Fetch, Create, Update, Delete, and Search
// See: https://login.circonus.com/resources/api/calls/graph
package apiclient
import (
"encoding/json"
"fmt"
"net/url"
"regexp"
"strings"
"github.com/circonus-labs/go-apiclient/config"
"github.com/pkg/errors"
)
// GraphAccessKey defines an access key for a graph
type GraphAccessKey struct {
Key string `json:"key,omitempty"` // string
LockMode string `json:"lock_mode,omitempty"` // string
LockZoom string `json:"lock_zoom,omitempty"` // string
Nickname string `json:"nickname,omitempty"` // string
Height uint `json:"height,omitempty"` // uint
LockRangeEnd uint `json:"lock_range_end,omitempty"` // uint
LockRangeStart uint `json:"lock_range_start,omitempty"` // uint
Width uint `json:"width,omitempty"` // uint
Active bool `json:"active,omitempty"` // boolean
Legend bool `json:"legend,omitempty"` // boolean
LockDate bool `json:"lock_date,omitempty"` // boolean
LockShowTimes bool `json:"lock_show_times,omitempty"` // boolean
Title bool `json:"title,omitempty"` // boolean
XLabels bool `json:"x_labels,omitempty"` // boolean
YLabels bool `json:"y_labels,omitempty"` // boolean
}
// GraphComposite defines a composite
type GraphComposite struct {
Stack *uint `json:"stack"` // uint or null
DataFormula *string `json:"data_formula"` // string or null
LegendFormula *string `json:"legend_formula"` // string or null
Axis string `json:"axis"` // string
Color string `json:"color"` // string
Name string `json:"name"` // string
Hidden bool `json:"hidden"` // boolean
}
// GraphDatapoint defines a datapoint
type GraphDatapoint struct {
Derive interface{} `json:"derive,omitempty"` // BUG doc: string, api: string or boolean(for caql statements)
Search *string `json:"search"` // string or null
Alpha *string `json:"alpha,omitempty"` // BUG: doc: floating point number, api: string
CAQL *string `json:"caql,omitempty"` // string or null
Color *string `json:"color,omitempty"` // string
DataFormula *string `json:"data_formula"` // string or null
LegendFormula *string `json:"legend_formula"` // string or null
Stack *uint `json:"stack"` // uint or null
Axis string `json:"axis,omitempty"` // string
MetricName string `json:"metric_name,omitempty"` // string
MetricType string `json:"metric_type,omitempty"` // string
Name string `json:"name"` // string
CheckID uint `json:"check_id,omitempty"` // uint
Hidden bool `json:"hidden"` // boolean
}
// GraphGuide defines a guide
type GraphGuide struct {
DataFormula *string `json:"data_formula"` // string or null
LegendFormula *string `json:"legend_formula"` // string or null
Color string `json:"color"` // string
Name string `json:"name"` // string
Hidden bool `json:"hidden"` // boolean
}
// GraphMetricCluster defines a metric cluster
type GraphMetricCluster struct {
Color *string `json:"color,omitempty"` // string
DataFormula *string `json:"data_formula"` // string or null
LegendFormula *string `json:"legend_formula"` // string or null
Stack *uint `json:"stack"` // uint or null
MetricCluster string `json:"metric_cluster,omitempty"` // string
Name string `json:"name,omitempty"` // string
AggregateFunc string `json:"aggregate_function,omitempty"` // string
Axis string `json:"axis,omitempty"` // string
Hidden bool `json:"hidden"` // boolean
}
// OverlaySet defines an overlay set for a graph
type GraphOverlaySet struct {
Overlays map[string]GraphOverlay `json:"overlays"`
Title string `json:"title"`
}
// GraphOverlay defines a single overlay in an overlay set
type GraphOverlay struct {
DataOpts OverlayDataOptions `json:"data_opts,omitempty"` // OverlayDataOptions
ID string `json:"id,omitempty"` // string
Title string `json:"title,omitempty"` // string
UISpecs OverlayUISpecs `json:"ui_specs,omitempty"` // OverlayUISpecs
}
// OverlayUISpecs defines UI specs for overlay
type OverlayUISpecs struct {
ID string `json:"id,omitempty"` // string
Label string `json:"label,omitempty"` // string
Type string `json:"type,omitempty"` // string
Z string `json:"z,omitempty"` // int encoded as string BUG doc: numeric, api: string
Decouple bool `json:"decouple,omitempty"` // boolean
}
// OverlayDataOptions defines overlay options for data. Note, each overlay type requires
// a _subset_ of the options. See Graph API documentation (URL above) for details.
type OverlayDataOptions struct {
Alerts string `json:"alerts,omitempty"` // int encoded as string BUG doc: numeric, api: string
ArrayOutput string `json:"array_output,omitempty"` // int encoded as string BUG doc: numeric, api: string
BasePeriod string `json:"base_period,omitempty"` // int encoded as string BUG doc: numeric, api: string
Delay string `json:"delay,omitempty"` // int encoded as string BUG doc: numeric, api: string
Extension string `json:"extension,omitempty"` // string
GraphTitle string `json:"graph_title,omitempty"` // string
GraphUUID string `json:"graph_id,omitempty"` // string
InPercent string `json:"in_percent,omitempty"` // boolean encoded as string BUG doc: boolean, api: string
Inverse string `json:"inverse,omitempty"` // int encoded as string BUG doc: numeric, api: string
Method string `json:"method,omitempty"` // string
Model string `json:"model,omitempty"` // string
ModelEnd string `json:"model_end,omitempty"` // string
ModelPeriod string `json:"model_period,omitempty"` // string
ModelRelative string `json:"model_relative,omitempty"` // int encoded as string BUG doc: numeric, api: string
Out string `json:"out,omitempty"` // string
Prequel string `json:"prequel,omitempty"` // int
Presets string `json:"presets,omitempty"` // string
Quantiles string `json:"quantiles,omitempty"` // string
SeasonLength string `json:"season_length,omitempty"` // int encoded as string BUG doc: numeric, api: string
Sensitivity string `json:"sensitivity,omitempty"` // int encoded as string BUG doc: numeric, api: string
SingleValue string `json:"single_value,omitempty"` // int encoded as string BUG doc: numeric, api: string
TargetPeriod string `json:"target_period,omitempty"` // string
TimeOffset string `json:"time_offset,omitempty"` // string
TimeShift string `json:"time_shift,omitempty"` // int encoded as string BUG doc: numeric, api: string
Transform string `json:"transform,omitempty"` // string
Version string `json:"version,omitempty"` // int encoded as string BUG doc: numeric, api: string
Window string `json:"window,omitempty"` // int encoded as string BUG doc: numeric, api: string
XShift string `json:"x_shift,omitempty"` // string
}
// Graph defines a graph. See https://login.circonus.com/resources/api/calls/graph for more information.
type Graph struct {
LineStyle *string `json:"line_style"` // string or null
Style *string `json:"style"` // string or null
Notes *string `json:"notes"` // string or null
OverlaySets *map[string]GraphOverlaySet `json:"overlay_sets"` // GroupOverLaySets or null
CID string `json:"_cid"` // string
Description string `json:"description"` // string
Title string `json:"title"` // string
LogLeftY interface{} `json:"logarithmic_left_y"` // string that must parse to an int, empty string "", or null BUG doc: number (not string)
LogRightY interface{} `json:"logarithmic_right_y"` // string that must parse to a float, empty string "", or null BUG doc: number (not string)
MaxLeftY interface{} `json:"max_left_y"` // string that must parse to a float, empty string "", or null BUG doc: number (not string)
MaxRightY interface{} `json:"max_right_y"` // string that must parse to a float, empty string "", or null BUG doc: number (not string)
MinLeftY interface{} `json:"min_left_y"` // string that must parse to a float, empty string "", or null BUG doc: number (not string)
MinRightY interface{} `json:"min_right_y"` // string that must parse to a float, empty string "", or null BUG doc: number (not string)
Tags []string `json:"tags"` // [] len >= 0
AccessKeys []GraphAccessKey `json:"access_keys"` // [] len >= 0
Composites []GraphComposite `json:"composites"` // [] len >= 0
Datapoints []GraphDatapoint `json:"datapoints"` // [] len >= 0
Guides []GraphGuide `json:"guides"` // [] len >= 0
MetricClusters []GraphMetricCluster `json:"metric_clusters"` // [] len >= 0
}
// NewGraph returns a Graph (with defaults, if applicable)
func NewGraph() *Graph {
return &Graph{}
}
// FetchGraph retrieves graph with passed cid.
func (a *API) FetchGraph(cid CIDType) (*Graph, error) {
if cid == nil || *cid == "" {
return nil, errors.New("invalid graph CID (none)")
}
var graphCID string
if !strings.HasPrefix(*cid, config.GraphPrefix) {
graphCID = fmt.Sprintf("%s/%s", config.GraphPrefix, *cid)
} else {
graphCID = *cid
}
matched, err := regexp.MatchString(config.GraphCIDRegex, graphCID)
if err != nil {
return nil, err
}
if !matched {
return nil, errors.Errorf("invalid graph CID (%s)", graphCID)
}
result, err := a.Get(graphCID)
if err != nil {
return nil, errors.Wrap(err, "fetching graph")
}
if a.Debug {
a.Log.Printf("fetch graph, received JSON: %s", string(result))
}
graph := new(Graph)
if err := json.Unmarshal(result, graph); err != nil {
return nil, errors.Wrap(err, "parsing graph")
}
return graph, nil
}
// FetchGraphs retrieves all graphs available to the API Token.
func (a *API) FetchGraphs() (*[]Graph, error) {
result, err := a.Get(config.GraphPrefix)
if err != nil {
return nil, errors.Wrap(err, "fetching graphs")
}
var graphs []Graph
if err := json.Unmarshal(result, &graphs); err != nil {
return nil, errors.Wrap(err, "parsing graphs")
}
return &graphs, nil
}
// UpdateGraph updates passed graph.
func (a *API) UpdateGraph(cfg *Graph) (*Graph, error) {
if cfg == nil {
return nil, errors.New("invalid graph config (nil)")
}
graphCID := cfg.CID
matched, err := regexp.MatchString(config.GraphCIDRegex, graphCID)
if err != nil {
return nil, err
}
if !matched {
return nil, errors.Errorf("invalid graph CID (%s)", graphCID)
}
jsonCfg, err := json.Marshal(cfg)
if err != nil {
return nil, err
}
if a.Debug {
a.Log.Printf("update graph, sending JSON: %s", string(jsonCfg))
}
result, err := a.Put(graphCID, jsonCfg)
if err != nil {
return nil, errors.Wrap(err, "updating graph")
}
graph := &Graph{}
if err := json.Unmarshal(result, graph); err != nil {
return nil, errors.Wrap(err, "parsing graph")
}
return graph, nil
}
// CreateGraph creates a new graph.
func (a *API) CreateGraph(cfg *Graph) (*Graph, error) {
if cfg == nil {
return nil, errors.New("invalid graph config (nil)")
}
jsonCfg, err := json.Marshal(cfg)
if err != nil {
return nil, err
}
if a.Debug {
a.Log.Printf("create graph, sending JSON: %s", string(jsonCfg))
}
result, err := a.Post(config.GraphPrefix, jsonCfg)
if err != nil {
return nil, errors.Wrap(err, "creating graph")
}
if a.Debug {
a.Log.Printf("create graph, received JSON: %s", string(result))
}
graph := &Graph{}
if err := json.Unmarshal(result, graph); err != nil {
return nil, errors.Wrap(err, "parsing graph")
}
return graph, nil
}
// DeleteGraph deletes passed graph.
func (a *API) DeleteGraph(cfg *Graph) (bool, error) {
if cfg == nil {
return false, errors.New("invalid graph config (nil)")
}
return a.DeleteGraphByCID(CIDType(&cfg.CID))
}
// DeleteGraphByCID deletes graph with passed cid.
func (a *API) DeleteGraphByCID(cid CIDType) (bool, error) {
if cid == nil || *cid == "" {
return false, errors.New("invalid graph CID (none)")
}
var graphCID string
if !strings.HasPrefix(*cid, config.GraphPrefix) {
graphCID = fmt.Sprintf("%s/%s", config.GraphPrefix, *cid)
} else {
graphCID = *cid
}
matched, err := regexp.MatchString(config.GraphCIDRegex, graphCID)
if err != nil {
return false, err
}
if !matched {
return false, errors.Errorf("invalid graph CID (%s)", graphCID)
}
_, err = a.Delete(graphCID)
if err != nil {
return false, errors.Wrap(err, "deleting graph")
}
return true, nil
}
// SearchGraphs returns graphs matching the specified search query
// and/or filter. If nil is passed for both parameters all graphs
// will be returned.
func (a *API) SearchGraphs(searchCriteria *SearchQueryType, filterCriteria *SearchFilterType) (*[]Graph, error) {
q := url.Values{}
if searchCriteria != nil && *searchCriteria != "" {
q.Set("search", string(*searchCriteria))
}
if filterCriteria != nil && len(*filterCriteria) > 0 {
for filter, criteria := range *filterCriteria {
for _, val := range criteria {
q.Add(filter, val)
}
}
}
if q.Encode() == "" {
return a.FetchGraphs()
}
reqURL := url.URL{
Path: config.GraphPrefix,
RawQuery: q.Encode(),
}
result, err := a.Get(reqURL.String())
if err != nil {
return nil, errors.Wrap(err, "searching graphs")
}
var graphs []Graph
if err := json.Unmarshal(result, &graphs); err != nil {
return nil, errors.Wrap(err, "parsing graphs")
}
return &graphs, nil
}