9
9
"net/http"
10
10
"time"
11
11
12
+ "github.com/grafana/dataplane/sdata/timeseries"
13
+
12
14
"github.com/grafana/grafana-plugin-sdk-go/backend"
13
15
"github.com/grafana/grafana-plugin-sdk-go/data"
14
16
"github.com/grafana/grafana-plugin-sdk-go/data/sqlutil"
@@ -18,14 +20,16 @@ import (
18
20
type FormatQueryOption uint32
19
21
20
22
const (
21
- // FormatOptionTimeSeries formats the query results as a timeseries using "WideToLong "
23
+ // FormatOptionTimeSeries formats the query results as a timeseries using "LongToWide "
22
24
FormatOptionTimeSeries FormatQueryOption = iota
23
- // FormatOptionTable formats the query results as a table using "LongToWide"
25
+ // FormatOptionTable sets the preferred visualization to table
24
26
FormatOptionTable
25
27
// FormatOptionLogs sets the preferred visualization to logs
26
28
FormatOptionLogs
27
29
// FormatOptionsTrace sets the preferred visualization to trace
28
30
FormatOptionTrace
31
+ // FormatOptionMulti formats the query results as a timeseries using "LongToMulti"
32
+ FormatOptionMulti
29
33
)
30
34
31
35
// Query is the model that represents the query that users submit from the panel / queryeditor.
@@ -155,43 +159,84 @@ func getFrames(rows *sql.Rows, limit int64, converters []sqlutil.Converter, fill
155
159
frame .Meta = & data.FrameMeta {}
156
160
}
157
161
162
+ count , err := frame .RowLen ()
163
+ if err != nil {
164
+ return nil , err
165
+ }
166
+ if count == 0 {
167
+ return nil , ErrorNoResults
168
+ }
169
+
158
170
frame .Meta .ExecutedQueryString = query .RawSQL
159
171
frame .Meta .PreferredVisualization = data .VisTypeGraph
160
172
161
- if query .Format == FormatOptionTable {
162
- frame .Meta .PreferredVisualization = data .VisTypeTable
163
- return data.Frames {frame }, nil
164
- }
173
+ switch query .Format {
174
+ case FormatOptionMulti :
175
+ if frame .TimeSeriesSchema ().Type == data .TimeSeriesTypeLong {
165
176
166
- if query . Format == FormatOptionLogs {
167
- frame . Meta . PreferredVisualization = data . VisTypeLogs
168
- return data. Frames { frame }, nil
169
- }
177
+ err = fixFrameForLongToMulti ( frame )
178
+ if err != nil {
179
+ return nil , err
180
+ }
170
181
171
- if query .Format == FormatOptionTrace {
182
+ frames , err := timeseries .LongToMulti (& timeseries.LongFrame {frame })
183
+ if err != nil {
184
+ return nil , err
185
+ }
186
+ return frames .Frames (), nil
187
+ }
188
+ case FormatOptionTable :
189
+ frame .Meta .PreferredVisualization = data .VisTypeTable
190
+ case FormatOptionLogs :
191
+ frame .Meta .PreferredVisualization = data .VisTypeLogs
192
+ case FormatOptionTrace :
172
193
frame .Meta .PreferredVisualization = data .VisTypeTrace
173
- return data.Frames {frame }, nil
194
+ // Format as timeSeries
195
+ default :
196
+ if frame .TimeSeriesSchema ().Type == data .TimeSeriesTypeLong {
197
+ frame , err = data .LongToWide (frame , fillMode )
198
+ if err != nil {
199
+ return nil , err
200
+ }
201
+ }
174
202
}
203
+ return data.Frames {frame }, nil
204
+ }
175
205
176
- count , err := frame . RowLen ()
177
-
178
- if err ! = nil {
179
- return nil , err
206
+ // fixFrameForLongToMulti edits the passed in frame so that it's first time field isn't nullable and has the correct meta
207
+ func fixFrameForLongToMulti ( frame * data. Frame ) error {
208
+ if frame = = nil {
209
+ return fmt . Errorf ( "can not convert to wide series, input is nil" )
180
210
}
181
211
182
- if count == 0 {
183
- return nil , ErrorNoResults
212
+ timeFields := frame .TypeIndices (data .FieldTypeTime , data .FieldTypeNullableTime )
213
+ if len (timeFields ) == 0 {
214
+ return fmt .Errorf ("can not convert to wide series, input is missing a time field" )
184
215
}
185
216
186
- if frame .TimeSeriesSchema ().Type == data .TimeSeriesTypeLong {
187
- frame , err := data .LongToWide (frame , fillMode )
188
- if err != nil {
189
- return nil , err
217
+ // the timeseries package expects the first time field in the frame to be non-nullable and ignores the rest
218
+ timeField := frame .Fields [timeFields [0 ]]
219
+ if timeField .Type () == data .FieldTypeNullableTime {
220
+ newValues := []time.Time {}
221
+ for i := 0 ; i < timeField .Len (); i ++ {
222
+ val , ok := timeField .ConcreteAt (i )
223
+ if ! ok {
224
+ return fmt .Errorf ("can not convert to wide series, input has null time values" )
225
+ }
226
+ newValues = append (newValues , val .(time.Time ))
190
227
}
191
- return data.Frames {frame }, nil
192
- }
228
+ newField := data .NewField (timeField .Name , timeField .Labels , newValues )
229
+ newField .Config = timeField .Config
230
+ frame .Fields [timeFields [0 ]] = newField
193
231
194
- return data.Frames {frame }, nil
232
+ // LongToMulti requires the meta to be set for the frame
233
+ if frame .Meta == nil {
234
+ frame .Meta = & data.FrameMeta {}
235
+ }
236
+ frame .Meta .Type = data .FrameTypeTimeSeriesLong
237
+ frame .Meta .TypeVersion = data.FrameTypeVersion {0 , 1 }
238
+ }
239
+ return nil
195
240
}
196
241
197
242
func applyHeaders (query * Query , headers http.Header ) * Query {
0 commit comments