diff --git a/chronograf.go b/chronograf.go index 28b5e6942..6a766d63f 100644 --- a/chronograf.go +++ b/chronograf.go @@ -535,6 +535,7 @@ var annotationTagsBlacklist = map[string]bool{ "endTime": true, "modified_time_ns": true, "text": true, + "color": true, "type": true, "id": true, } @@ -566,6 +567,7 @@ type Annotation struct { StartTime time.Time // StartTime starts the annotation EndTime time.Time // EndTime ends the annotation Text string // Text is the associated user-facing text describing the annotation + Color string // Color associated with the annotation Tags AnnotationTags // Tags is a collection of user defined key/value pairs that contextualize the annotation } diff --git a/influx/annotations.go b/influx/annotations.go index 60855e56c..3ffff23ed 100644 --- a/influx/annotations.go +++ b/influx/annotations.go @@ -162,6 +162,7 @@ func toPoint(anno *chronograf.Annotation, now time.Time) chronograf.Point { "start_time": anno.StartTime.UnixNano(), "modified_time_ns": int64(now.UnixNano()), "text": anno.Text, + "color": anno.Color, }, } } @@ -302,6 +303,9 @@ func (r *influxResults) Annotations() (res []chronograf.Annotation, err error) { if anno.ID, err = v.String(i); err != nil { return } + if colorIndex, found := columnIndex["color"]; found { + anno.Color, _ = v.String(colorIndex) + } anno.Tags = chronograf.AnnotationTags{} diff --git a/influx/annotations_test.go b/influx/annotations_test.go index 6e14c66f0..374914acb 100644 --- a/influx/annotations_test.go +++ b/influx/annotations_test.go @@ -40,6 +40,7 @@ func Test_toPoint(t *testing.T) { "start_time": time.Time{}.UnixNano(), "modified_time_ns": int64(time.Unix(0, 0).UnixNano()), "text": "mytext", + "color": "", }, }, }, @@ -50,6 +51,7 @@ func Test_toPoint(t *testing.T) { Text: "mytext", StartTime: time.Unix(100, 0), EndTime: time.Unix(200, 0), + Color: "red", }, now: time.Unix(0, 0), want: chronograf.Point{ @@ -65,6 +67,7 @@ func Test_toPoint(t *testing.T) { "start_time": time.Unix(100, 0).UnixNano(), "modified_time_ns": int64(time.Unix(0, 0).UnixNano()), "text": "mytext", + "color": "red", }, }, }, diff --git a/server/annotations.go b/server/annotations.go index 3a6898a77..82e149c9c 100644 --- a/server/annotations.go +++ b/server/annotations.go @@ -28,6 +28,7 @@ type annotationResponse struct { StartTime string `json:"startTime"` // StartTime in RFC3339 of the start of the annotation EndTime string `json:"endTime"` // EndTime in RFC3339 of the end of the annotation Text string `json:"text"` // Text is the associated user-facing text describing the annotation + Color string `json:"color"` // Optional annotation color Tags chronograf.AnnotationTags `json:"tags"` // Tags is a collection of user defined key/value pairs that contextualize the annotation Links annotationLinks `json:"links"` } @@ -39,6 +40,7 @@ func newAnnotationResponse(src chronograf.Source, a *chronograf.Annotation) anno StartTime: a.StartTime.UTC().Format(timeMilliFormat), EndTime: a.EndTime.UTC().Format(timeMilliFormat), Text: a.Text, + Color: a.Color, Tags: a.Tags, Links: annotationLinks{ Self: fmt.Sprintf("%s/%d/annotations/%s", base, src.ID, a.ID), @@ -227,7 +229,8 @@ func (s *Service) Annotation(w http.ResponseWriter, r *http.Request) { type newAnnotationRequest struct { StartTime time.Time EndTime time.Time - Text string `json:"text,omitempty"` // Text is the associated user-facing text describing the annotation + Text string `json:"text,omitempty"` // Text is the associated user-facing text describing the annotation + Color string `json:"color,omitempty"` // Optional annotation color Tags chronograf.AnnotationTags `json:"tags"` } @@ -267,6 +270,7 @@ func (ar *newAnnotationRequest) Annotation() *chronograf.Annotation { StartTime: ar.StartTime, EndTime: ar.EndTime, Text: ar.Text, + Color: ar.Color, Tags: ar.Tags, } } @@ -379,6 +383,7 @@ type updateAnnotationRequest struct { StartTime *time.Time `json:"startTime,omitempty"` // StartTime is the time in rfc3339 milliseconds EndTime *time.Time `json:"endTime,omitempty"` // EndTime is the time in rfc3339 milliseconds Text *string `json:"text,omitempty"` // Text is the associated user-facing text describing the annotation + Color *string `json:"color,omitempty"` // Annotation color Tags chronograf.AnnotationTags `json:"tags"` } @@ -479,6 +484,10 @@ func (s *Service) UpdateAnnotation(w http.ResponseWriter, r *http.Request) { if req.Text != nil { cur.Text = *req.Text } + + if req.Color != nil { + cur.Color = *req.Color + } if req.Tags != nil { if err = req.Tags.Valid(); err != nil { Error(w, http.StatusBadRequest, err.Error(), s.Logger) diff --git a/server/annotations_test.go b/server/annotations_test.go index 6d412fbe7..749ccbe30 100644 --- a/server/annotations_test.go +++ b/server/annotations_test.go @@ -153,7 +153,52 @@ func TestService_Annotations(t *testing.T) { ID: "1", w: httptest.NewRecorder(), r: httptest.NewRequest("GET", "/chronograf/v1/sources/1/annotations?since=1985-04-12T23:20:50.52Z", bytes.NewReader([]byte(`howdy`))), - want: `{"annotations":[{"id":"ea0aa94b-969a-4cd5-912a-5db61d502268","startTime":"1970-01-01T00:00:00Z","endTime":"2018-01-25T22:42:57.345Z","text":"mytext","tags":{},"links":{"self":"/chronograf/v1/sources/1/annotations/ea0aa94b-969a-4cd5-912a-5db61d502268"}}]} + want: `{"annotations":[{"id":"ea0aa94b-969a-4cd5-912a-5db61d502268","startTime":"1970-01-01T00:00:00Z","endTime":"2018-01-25T22:42:57.345Z","text":"mytext","color":"","tags":{},"links":{"self":"/chronograf/v1/sources/1/annotations/ea0aa94b-969a-4cd5-912a-5db61d502268"}}]} +`, + }, + { + name: "returns annotations with color in store", + fields: fields{ + Store: mockStore, + TimeSeriesClient: &mocks.TimeSeries{ + ConnectF: func(context.Context, *chronograf.Source) error { + return nil + }, + QueryF: func(context.Context, chronograf.Query) (chronograf.Response, error) { + return mocks.NewResponse(`[ + { + "series": [ + { + "name": "annotations", + "columns": [ + "time", + "start_time", + "modified_time_ns", + "text", + "color", + "id" + ], + "values": [ + [ + 1516920177345000000, + 0, + 1516989242129417403, + "mytext", + "red", + "ea0aa94b-969a-4cd5-912a-5db61d502268" + ] + ] + } + ] + } + ]`, nil), nil + }, + }, + }, + ID: "1", + w: httptest.NewRecorder(), + r: httptest.NewRequest("GET", "/chronograf/v1/sources/1/annotations?since=1985-04-12T23:20:50.52Z", bytes.NewReader([]byte(`howdy`))), + want: `{"annotations":[{"id":"ea0aa94b-969a-4cd5-912a-5db61d502268","startTime":"1970-01-01T00:00:00Z","endTime":"2018-01-25T22:42:57.345Z","text":"mytext","color":"red","tags":{},"links":{"self":"/chronograf/v1/sources/1/annotations/ea0aa94b-969a-4cd5-912a-5db61d502268"}}]} `, }, { @@ -218,6 +263,7 @@ func TestService_UpdateAnnotation(t *testing.T) { "start_time", "modified_time_ns", "text", + "color", "id" ], "values": [ @@ -226,6 +272,7 @@ func TestService_UpdateAnnotation(t *testing.T) { 0, 1516989242129417403, "mytext", + "red", "1" ] ] @@ -249,9 +296,14 @@ func TestService_UpdateAnnotation(t *testing.T) { body string want string }{ + { + body: `{"id":"1","text":"newtext","color":"blue","tags":{"foo":"bar"}}`, + want: `{"id":"1","startTime":"1970-01-01T00:00:00Z","endTime":"2018-01-25T22:42:57.345Z","text":"newtext","color":"blue","tags":{"foo":"bar"},"links":{"self":"/chronograf/v1/sources/1/annotations/1"}} +`, + }, { body: `{"id":"1","text":"newtext","tags":{"foo":"bar"}}`, - want: `{"id":"1","startTime":"1970-01-01T00:00:00Z","endTime":"2018-01-25T22:42:57.345Z","text":"newtext","tags":{"foo":"bar"},"links":{"self":"/chronograf/v1/sources/1/annotations/1"}} + want: `{"id":"1","startTime":"1970-01-01T00:00:00Z","endTime":"2018-01-25T22:42:57.345Z","text":"newtext","color":"red","tags":{"foo":"bar"},"links":{"self":"/chronograf/v1/sources/1/annotations/1"}} `, }, {