-
Notifications
You must be signed in to change notification settings - Fork 3.7k
feat: Adds time_format param for httpd #26596
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -594,6 +594,12 @@ func (h *Handler) serveQuery(w http.ResponseWriter, r *http.Request, user meta.U | |
|
|
||
| epoch := strings.TrimSpace(r.FormValue("epoch")) | ||
|
|
||
| // timeFormat should default to "epoch" | ||
| timeFormat := strings.TrimSpace(r.FormValue("time_format")) | ||
| if timeFormat == "" { | ||
| timeFormat = "epoch" | ||
| } | ||
|
|
||
| p := influxql.NewParser(qr) | ||
| db := r.FormValue("db") | ||
|
|
||
|
|
@@ -747,10 +753,14 @@ func (h *Handler) serveQuery(w http.ResponseWriter, r *http.Request, user meta.U | |
| } | ||
|
|
||
| // if requested, convert result timestamps to epoch | ||
| if epoch != "" { | ||
| if epoch != "" && timeFormat == "epoch" { | ||
| convertToEpoch(r, epoch) | ||
| } | ||
|
|
||
| if timeFormat == "rfc3339" { | ||
| convertToRfc3339(r) | ||
| } | ||
|
||
|
|
||
| // Write out result immediately if chunked. | ||
| if chunked { | ||
| n, _ := rw.WriteResponse(Response{ | ||
|
|
@@ -1812,6 +1822,16 @@ func convertToEpoch(r *query.Result, epoch string) { | |
| } | ||
| } | ||
|
|
||
| func convertToRfc3339(r *query.Result) { | ||
| for _, s := range r.Series { | ||
| for _, v := range s.Values { | ||
| if ts, ok := v[0].(time.Time); ok { | ||
| v[0] = ts.Format(time.RFC3339Nano) | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // servePromWrite receives data in the Prometheus remote write protocol and writes it | ||
| // to the database | ||
| func (h *Handler) servePromWrite(w http.ResponseWriter, r *http.Request, user meta.User) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,7 @@ import ( | |
| "encoding/json" | ||
| "errors" | ||
| "fmt" | ||
| "github.com/stretchr/testify/require" | ||
| "io" | ||
| "log" | ||
| "math" | ||
|
|
@@ -596,6 +597,108 @@ func TestHandler_Query_CloseNotify(t *testing.T) { | |
| } | ||
| } | ||
|
|
||
| // Ensure the handler returns results with RFC3339 timestamp format when requested. | ||
| func TestHandler_Query_RFC3339(t *testing.T) { | ||
| h := NewHandler(false) | ||
| testTime := time.Date(2021, 1, 1, 12, 0, 0, 0, time.UTC) | ||
|
|
||
| h.StatementExecutor.ExecuteStatementFn = func(stmt influxql.Statement, ctx *query.ExecutionContext) error { | ||
| ctx.Results <- &query.Result{ | ||
| StatementID: 1, | ||
| Series: []*models.Row{{ | ||
| Name: "series0", | ||
| Columns: []string{"time", "value"}, | ||
| Values: [][]interface{}{ | ||
| {testTime, 42}, | ||
| }, | ||
| }}, | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| w := httptest.NewRecorder() | ||
| h.ServeHTTP(w, MustNewJSONRequest("GET", "/query?db=foo&q=SELECT+*+FROM+bar&time_format=rfc3339", nil)) | ||
| require.Equal(t, w.Code, http.StatusOK, "response status") | ||
|
|
||
| expectedRFC3339 := testTime.Format(time.RFC3339Nano) | ||
| expectedBody := fmt.Sprintf(`{"results":[{"statement_id":1,"series":[{"name":"series0","columns":["time","value"],"values":[["%s",42]]}]}]}`, expectedRFC3339) | ||
|
|
||
| body := strings.TrimSpace(w.Body.String()) | ||
| require.Equal(t, expectedBody, body, "response body") | ||
| } | ||
|
|
||
| // Ensure the handler returns results with RFC3339 timestamp format for multiple series. | ||
| func TestHandler_Query_RFC3339_MultipleSeries(t *testing.T) { | ||
|
||
| h := NewHandler(false) | ||
| testTime1 := time.Date(2021, 1, 1, 12, 0, 0, 0, time.UTC) | ||
| testTime2 := time.Date(2021, 1, 2, 12, 0, 0, 0, time.UTC) | ||
|
|
||
| h.StatementExecutor.ExecuteStatementFn = func(stmt influxql.Statement, ctx *query.ExecutionContext) error { | ||
| ctx.Results <- &query.Result{ | ||
| StatementID: 1, | ||
| Series: []*models.Row{ | ||
| { | ||
| Name: "series0", | ||
| Columns: []string{"time", "value"}, | ||
| Values: [][]interface{}{ | ||
| {testTime1, 42}, | ||
| {testTime2, 43}, | ||
| }, | ||
| }, | ||
| { | ||
| Name: "series1", | ||
| Columns: []string{"time", "value"}, | ||
| Values: [][]interface{}{ | ||
| {testTime1, 100}, | ||
| }, | ||
| }, | ||
| }, | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| w := httptest.NewRecorder() | ||
| h.ServeHTTP(w, MustNewJSONRequest("GET", "/query?db=foo&q=SELECT+*+FROM+bar&time_format=rfc3339", nil)) | ||
| require.Equal(t, w.Code, http.StatusOK, "response code") | ||
|
|
||
| firstExpectedRFC3339 := testTime1.Format(time.RFC3339Nano) | ||
| secondExpectedRFC3339 := testTime2.Format(time.RFC3339Nano) | ||
| expectedBody := fmt.Sprintf(`{"results":[{"statement_id":1,"series":[{"name":"series0","columns":["time","value"],"values":[["%s",42],["%s",43]]},{"name":"series1","columns":["time","value"],"values":[["%s",100]]}]}]}`, firstExpectedRFC3339, secondExpectedRFC3339, firstExpectedRFC3339) | ||
|
|
||
| body := strings.TrimSpace(w.Body.String()) | ||
| require.Equal(t, expectedBody, body, "response body") | ||
| } | ||
|
|
||
| // Ensure the handler returns results with RFC3339 timestamp format using nanosecond precision. | ||
| func TestHandler_Query_RFC3339_Nanoseconds(t *testing.T) { | ||
| h := NewHandler(false) | ||
| testTime := time.Date(2021, 1, 1, 12, 0, 0, 123456789, time.UTC) | ||
|
|
||
| h.StatementExecutor.ExecuteStatementFn = func(stmt influxql.Statement, ctx *query.ExecutionContext) error { | ||
| ctx.Results <- &query.Result{ | ||
| StatementID: 1, | ||
| Series: []*models.Row{{ | ||
| Name: "series0", | ||
| Columns: []string{"time", "value"}, | ||
| Values: [][]interface{}{ | ||
| {testTime, 42}, | ||
| }, | ||
| }}, | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| w := httptest.NewRecorder() | ||
| h.ServeHTTP(w, MustNewJSONRequest("GET", "/query?db=foo&q=SELECT+*+FROM+bar&time_format=rfc3339", nil)) | ||
| require.Equal(t, w.Code, http.StatusOK, "response code") | ||
|
|
||
| expectedRFC3339 := testTime.Format(time.RFC3339Nano) | ||
| expectedBody := fmt.Sprintf(`{"results":[{"statement_id":1,"series":[{"name":"series0","columns":["time","value"],"values":[["%s",42]]}]}]}`, expectedRFC3339) | ||
|
|
||
| body := strings.TrimSpace(w.Body.String()) | ||
| require.Equal(t, expectedBody, body, "response body") | ||
| } | ||
|
|
||
| // Ensure the handler returns an appropriate 401 status when authentication | ||
| // fails on ping endpoints. | ||
| func TestHandler_Ping_ErrAuthorize(t *testing.T) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it should return an error if an invalid value is given for
time_format. This will save a lot of support time.