{
return (
-
+
{contextIsOpen && context && (
{
@@ -143,7 +150,10 @@ class UnThemedLogRowMessage extends PureComponent {
{renderLogMessage(hasAnsi, restructuredEntry, highlights, highlightClassName)}
{showContextToggle?.(row) && (
-
+
{contextIsOpen ? 'Hide' : 'Show'} context
)}
diff --git a/packages/grafana-ui/src/components/Logs/getLogRowStyles.ts b/packages/grafana-ui/src/components/Logs/getLogRowStyles.ts
index 9fd2b6d2539..0a0b1ebabb7 100644
--- a/packages/grafana-ui/src/components/Logs/getLogRowStyles.ts
+++ b/packages/grafana-ui/src/components/Logs/getLogRowStyles.ts
@@ -48,13 +48,13 @@ export const getLogRowStyles = stylesFactory((theme: GrafanaTheme, logLevel?: Lo
font-family: ${theme.typography.fontFamily.monospace};
font-size: ${theme.typography.size.sm};
width: 100%;
- overflow: hidden;
`,
context: css`
label: context;
visibility: hidden;
white-space: nowrap;
position: relative;
+ margin-left: 10px;
`,
logsRow: css`
label: logs-row;
@@ -66,7 +66,6 @@ export const getLogRowStyles = stylesFactory((theme: GrafanaTheme, logLevel?: Lo
.log-row-context {
visibility: visible;
z-index: 1;
- margin-left: 10px;
text-decoration: underline;
&:hover {
color: ${theme.palette.yellow};
diff --git a/packages/grafana-ui/src/components/uPlot/models.cue b/packages/grafana-ui/src/components/uPlot/models.cue
index a7394e56206..71c80392870 100644
--- a/packages/grafana-ui/src/components/uPlot/models.cue
+++ b/packages/grafana-ui/src/components/uPlot/models.cue
@@ -50,9 +50,12 @@ AxisConfig: {
HideSeriesConfig: {
tooltip: bool
legend: bool
- graph: bool
+ viz: bool
} @cuetsy(targetType="interface")
+// TODO This is the same composition as what's used in the timeseries panel's
+// PanelFieldConfig. If that's the only place it's used, it probably shouldn't
+// be assembled here, too
GraphFieldConfig: LineConfig & FillConfig & PointsConfig & AxisConfig & {
drawStyle?: DrawStyle
gradientMode?: GraphGradientMode
diff --git a/pkg/api/avatar/avatar.go b/pkg/api/avatar/avatar.go
index 2c3952684c0..7ef583e0fe1 100644
--- a/pkg/api/avatar/avatar.go
+++ b/pkg/api/avatar/avatar.go
@@ -118,7 +118,7 @@ func (a *CacheServer) Handler(ctx *models.ReqContext) {
if err := avatar.Encode(ctx.Resp); err != nil {
log.Warnf("avatar encode error: %v", err)
- ctx.WriteHeader(500)
+ ctx.Resp.WriteHeader(500)
}
}
diff --git a/pkg/api/common_test.go b/pkg/api/common_test.go
index db083c30e26..e5a95a0525e 100644
--- a/pkg/api/common_test.go
+++ b/pkg/api/common_test.go
@@ -217,10 +217,7 @@ func setupScenarioContext(t *testing.T, url string) *scenarioContext {
require.Truef(t, exists, "Views should be in %q", viewsPath)
sc.m = macaron.New()
- sc.m.Use(macaron.Renderer(macaron.RenderOptions{
- Directory: viewsPath,
- Delims: macaron.Delims{Left: "[[", Right: "]]"},
- }))
+ sc.m.UseMiddleware(macaron.Renderer(viewsPath, "[[", "]]"))
sc.m.Use(getContextHandler(t, cfg).Middleware)
return sc
diff --git a/pkg/api/frontendsettings_test.go b/pkg/api/frontendsettings_test.go
index bd9b4dfcbea..11c858be3a8 100644
--- a/pkg/api/frontendsettings_test.go
+++ b/pkg/api/frontendsettings_test.go
@@ -58,11 +58,7 @@ func setupTestEnvironment(t *testing.T, cfg *setting.Cfg) (*macaron.Macaron, *HT
m := macaron.New()
m.Use(getContextHandler(t, cfg).Middleware)
- m.Use(macaron.Renderer(macaron.RenderOptions{
- Directory: filepath.Join(setting.StaticRootPath, "views"),
- IndentJSON: true,
- Delims: macaron.Delims{Left: "[[", Right: "]]"},
- }))
+ m.UseMiddleware(macaron.Renderer(filepath.Join(setting.StaticRootPath, "views"), "[[", "]]"))
m.Get("/api/frontend/settings/", hs.GetFrontendSettings)
return m, hs
diff --git a/pkg/api/http_server.go b/pkg/api/http_server.go
index 2fd0a608118..4e8be016ffa 100644
--- a/pkg/api/http_server.go
+++ b/pkg/api/http_server.go
@@ -335,7 +335,7 @@ func (hs *HTTPServer) addMiddlewaresAndStaticRoutes() {
m.Use(middleware.Logger(hs.Cfg))
if hs.Cfg.EnableGzip {
- m.Use(middleware.Gziper())
+ m.UseMiddleware(middleware.Gziper())
}
m.Use(middleware.Recovery(hs.Cfg))
@@ -354,11 +354,7 @@ func (hs *HTTPServer) addMiddlewaresAndStaticRoutes() {
m.SetURLPrefix(hs.Cfg.AppSubURL)
}
- m.Use(macaron.Renderer(macaron.RenderOptions{
- Directory: filepath.Join(hs.Cfg.StaticRootPath, "views"),
- IndentJSON: macaron.Env != macaron.PROD,
- Delims: macaron.Delims{Left: "[[", Right: "]]"},
- }))
+ m.UseMiddleware(macaron.Renderer(filepath.Join(hs.Cfg.StaticRootPath, "views"), "[[", "]]"))
// These endpoints are used for monitoring the Grafana instance
// and should not be redirected or rejected.
@@ -375,7 +371,7 @@ func (hs *HTTPServer) addMiddlewaresAndStaticRoutes() {
}
m.Use(middleware.HandleNoCacheHeader)
- m.Use(middleware.AddCSPHeader(hs.Cfg, hs.log))
+ m.UseMiddleware(middleware.AddCSPHeader(hs.Cfg, hs.log))
for _, mw := range hs.middlewares {
m.Use(mw)
@@ -408,7 +404,7 @@ func (hs *HTTPServer) healthzHandler(ctx *macaron.Context) {
return
}
- ctx.WriteHeader(200)
+ ctx.Resp.WriteHeader(200)
_, err := ctx.Resp.Write([]byte("Ok"))
if err != nil {
hs.log.Error("could not write to response", "err", err)
diff --git a/pkg/api/plugins.go b/pkg/api/plugins.go
index 5f44bf939e3..b7bc2e2b47f 100644
--- a/pkg/api/plugins.go
+++ b/pkg/api/plugins.go
@@ -21,6 +21,14 @@ import (
"github.com/grafana/grafana/pkg/setting"
)
+var permittedFileExts = []string{
+ ".html", ".xhtml", ".css", ".js", ".json", ".jsonld", ".map", ".mjs",
+ ".jpeg", ".jpg", ".png", ".gif", ".svg", ".webp", ".ico",
+ ".woff", ".woff2", ".eot", ".ttf", ".otf",
+ ".wav", ".mp3",
+ ".md", ".pdf", ".txt",
+}
+
func (hs *HTTPServer) GetPluginList(c *models.ReqContext) response.Response {
typeFilter := c.Query("type")
enabledFilter := c.Query("enabled")
@@ -292,9 +300,9 @@ func (hs *HTTPServer) GetPluginAssets(c *models.ReqContext) {
return
}
- if shouldExclude(fi) {
+ if accessForbidden(fi.Name()) {
c.JsonApiErr(403, "Plugin file access forbidden",
- fmt.Errorf("access is forbidden to executable plugin file %s", pluginFilePath))
+ fmt.Errorf("access is forbidden to plugin file %s", pluginFilePath))
return
}
@@ -441,12 +449,13 @@ func translatePluginRequestErrorToAPIError(err error) response.Response {
return response.Error(500, "Plugin request failed", err)
}
-func shouldExclude(fi os.FileInfo) bool {
- normalizedFilename := strings.ToLower(fi.Name())
+func accessForbidden(pluginFilename string) bool {
+ ext := filepath.Ext(pluginFilename)
- isUnixExecutable := fi.Mode()&0111 == 0111
- isWindowsExecutable := strings.HasSuffix(normalizedFilename, ".exe")
- isScript := strings.HasSuffix(normalizedFilename, ".sh")
-
- return isUnixExecutable || isWindowsExecutable || isScript
+ for _, permittedExt := range permittedFileExts {
+ if strings.EqualFold(permittedExt, ext) {
+ return false
+ }
+ }
+ return true
}
diff --git a/pkg/api/plugins_test.go b/pkg/api/plugins_test.go
new file mode 100644
index 00000000000..47ea607cda6
--- /dev/null
+++ b/pkg/api/plugins_test.go
@@ -0,0 +1,89 @@
+package api
+
+import (
+ "testing"
+)
+
+func Test_accessForbidden(t *testing.T) {
+ type testCase struct {
+ filename string
+ }
+ tests := []struct {
+ name string
+ t testCase
+ accessForbidden bool
+ }{
+ {
+ name: ".exe files are forbidden",
+ t: testCase{
+ filename: "test.exe",
+ },
+ accessForbidden: true,
+ },
+ {
+ name: ".sh files are forbidden",
+ t: testCase{
+ filename: "test.sh",
+ },
+ accessForbidden: true,
+ },
+ {
+ name: "js is not forbidden",
+ t: testCase{
+
+ filename: "module.js",
+ },
+ accessForbidden: false,
+ },
+ {
+ name: "logos are not forbidden",
+ t: testCase{
+
+ filename: "logo.svg",
+ },
+ accessForbidden: false,
+ },
+ {
+ name: "JPGs are not forbidden",
+ t: testCase{
+ filename: "img/test.jpg",
+ },
+ accessForbidden: false,
+ },
+ {
+ name: "JPEGs are not forbidden",
+ t: testCase{
+ filename: "img/test.jpeg",
+ },
+ accessForbidden: false,
+ },
+ {
+ name: "ext case is ignored",
+ t: testCase{
+ filename: "scripts/runThis.SH",
+ },
+ accessForbidden: true,
+ },
+ {
+ name: "no file ext is forbidden",
+ t: testCase{
+ filename: "scripts/runThis",
+ },
+ accessForbidden: true,
+ },
+ {
+ name: "empty file ext is forbidden",
+ t: testCase{
+ filename: "scripts/runThis.",
+ },
+ accessForbidden: true,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := accessForbidden(tt.t.filename); got != tt.accessForbidden {
+ t.Errorf("accessForbidden() = %v, accessForbidden %v", got, tt.accessForbidden)
+ }
+ })
+ }
+}
diff --git a/pkg/expr/nodes.go b/pkg/expr/nodes.go
index f88d054e0a9..e8914c0e7ff 100644
--- a/pkg/expr/nodes.go
+++ b/pkg/expr/nodes.go
@@ -186,7 +186,7 @@ func (s *Service) buildDSNode(dp *simple.DirectedGraph, rn *rawNode, req *Reques
}
var floatIntervalMS float64
- if rawIntervalMS := rn.Query["intervalMs"]; ok {
+ if rawIntervalMS, ok := rn.Query["intervalMs"]; ok {
if floatIntervalMS, ok = rawIntervalMS.(float64); !ok {
return nil, fmt.Errorf("expected intervalMs to be an float64, got type %T for refId %v", rawIntervalMS, rn.RefID)
}
@@ -194,7 +194,7 @@ func (s *Service) buildDSNode(dp *simple.DirectedGraph, rn *rawNode, req *Reques
}
var floatMaxDP float64
- if rawMaxDP := rn.Query["maxDataPoints"]; ok {
+ if rawMaxDP, ok := rn.Query["maxDataPoints"]; ok {
if floatMaxDP, ok = rawMaxDP.(float64); !ok {
return nil, fmt.Errorf("expected maxDataPoints to be an float64, got type %T for refId %v", rawMaxDP, rn.RefID)
}
diff --git a/pkg/macaron/context.go b/pkg/macaron/context.go
index 2920f834af8..a26b37aafdb 100644
--- a/pkg/macaron/context.go
+++ b/pkg/macaron/context.go
@@ -15,6 +15,8 @@
package macaron
import (
+ "encoding/json"
+ "html/template"
"net/http"
"net/url"
"reflect"
@@ -44,11 +46,11 @@ type Context struct {
index int
*Router
- Req Request
- Resp ResponseWriter
- params Params
- Render
- Data map[string]interface{}
+ Req Request
+ Resp ResponseWriter
+ params Params
+ template *template.Template
+ Data map[string]interface{}
}
func (ctx *Context) handler() Handler {
@@ -108,19 +110,31 @@ func (ctx *Context) RemoteAddr() string {
return addr
}
-func (ctx *Context) renderHTML(status int, setName, tplName string, data ...interface{}) {
- if len(data) <= 0 {
- ctx.Render.HTMLSet(status, setName, tplName, ctx.Data)
- } else if len(data) == 1 {
- ctx.Render.HTMLSet(status, setName, tplName, data[0])
- } else {
- ctx.Render.HTMLSet(status, setName, tplName, data[0], data[1].(HTMLOptions))
+const (
+ headerContentType = "Content-Type"
+ contentTypeJSON = "application/json; charset=UTF-8"
+ contentTypeHTML = "text/html; charset=UTF-8"
+)
+
+// HTML renders the HTML with default template set.
+func (ctx *Context) HTML(status int, name string, data interface{}) {
+ ctx.Resp.Header().Set(headerContentType, contentTypeHTML)
+ ctx.Resp.WriteHeader(status)
+ if err := ctx.template.ExecuteTemplate(ctx.Resp, name, data); err != nil {
+ panic("Context.HTML:" + err.Error())
}
}
-// HTML renders the HTML with default template set.
-func (ctx *Context) HTML(status int, name string, data ...interface{}) {
- ctx.renderHTML(status, DEFAULT_TPL_SET_NAME, name, data...)
+func (ctx *Context) JSON(status int, data interface{}) {
+ ctx.Resp.Header().Set(headerContentType, contentTypeJSON)
+ ctx.Resp.WriteHeader(status)
+ enc := json.NewEncoder(ctx.Resp)
+ if Env != PROD {
+ enc.SetIndent("", " ")
+ }
+ if err := enc.Encode(data); err != nil {
+ panic("Context.JSON: " + err.Error())
+ }
}
// Redirect sends a redirect response
@@ -142,7 +156,7 @@ func (ctx *Context) parseForm() {
return
}
- contentType := ctx.Req.Header.Get(_CONTENT_TYPE)
+ contentType := ctx.Req.Header.Get(headerContentType)
if (ctx.Req.Method == "POST" || ctx.Req.Method == "PUT") &&
len(contentType) > 0 && strings.Contains(contentType, "multipart/form-data") {
_ = ctx.Req.ParseMultipartForm(MaxMemory)
diff --git a/pkg/macaron/macaron.go b/pkg/macaron/macaron.go
index 40701d7bfcd..ce44a6ebf26 100644
--- a/pkg/macaron/macaron.go
+++ b/pkg/macaron/macaron.go
@@ -182,7 +182,6 @@ func (m *Macaron) createContext(rw http.ResponseWriter, req *http.Request) *Cont
index: 0,
Router: m.Router,
Resp: NewResponseWriter(req.Method, rw),
- Render: &DummyRender{rw},
Data: make(map[string]interface{}),
}
req = req.WithContext(context.WithValue(req.Context(), macaronContextKey{}, c))
diff --git a/pkg/macaron/render.go b/pkg/macaron/render.go
index 8aec82bee59..c6acbee5bb0 100644
--- a/pkg/macaron/render.go
+++ b/pkg/macaron/render.go
@@ -16,708 +16,57 @@
package macaron
import (
- "bytes"
- "encoding/json"
- "encoding/xml"
- "fmt"
"html/template"
- "io"
- "io/ioutil"
+ "io/fs"
"net/http"
"os"
- "path"
"path/filepath"
- "strings"
- "sync"
- "time"
)
-const (
- _CONTENT_TYPE = "Content-Type"
- _CONTENT_BINARY = "application/octet-stream"
- _CONTENT_JSON = "application/json"
- _CONTENT_HTML = "text/html"
- _CONTENT_PLAIN = "text/plain"
- _CONTENT_XHTML = "application/xhtml+xml"
- _CONTENT_XML = "text/xml"
- _DEFAULT_CHARSET = "UTF-8"
-)
-
-var (
- // Provides a temporary buffer to execute templates into and catch errors.
- bufpool = sync.Pool{
- New: func() interface{} { return new(bytes.Buffer) },
+// Renderer is a Middleware that injects a template renderer into the macaron context, enabling ctx.HTML calls in the handlers.
+// If MACARON_ENV is set to "development" then templates will be recompiled on every request. For more performance, set the
+// MACARON_ENV environment variable to "production".
+func Renderer(dir, leftDelim, rightDelim string) func(http.Handler) http.Handler {
+ fs := os.DirFS(dir)
+ t, err := compileTemplates(fs, leftDelim, rightDelim)
+ if err != nil {
+ panic("Renderer: " + err.Error())
}
-
- // Included helper functions for use when rendering html
- helperFuncs = template.FuncMap{
- "yield": func() (string, error) {
- return "", fmt.Errorf("yield called with no layout defined")
- },
- "current": func() (string, error) {
- return "", nil
- },
+ return func(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
+ ctx := FromContext(req.Context())
+ if Env == DEV {
+ if t, err = compileTemplates(fs, leftDelim, rightDelim); err != nil {
+ panic("Context.HTML:" + err.Error())
+ }
+ }
+ ctx.template = t
+ next.ServeHTTP(rw, req)
+ })
}
-)
-
-type (
- // TemplateFile represents a interface of template file that has name and can be read.
- TemplateFile interface {
- Name() string
- Data() []byte
- Ext() string
- }
- // TemplateFileSystem represents a interface of template file system that able to list all files.
- TemplateFileSystem interface {
- ListFiles() []TemplateFile
- Get(string) (io.Reader, error)
- }
-
- // Delims represents a set of Left and Right delimiters for HTML template rendering
- Delims struct {
- // Left delimiter, defaults to {{
- Left string
- // Right delimiter, defaults to }}
- Right string
- }
-
- // RenderOptions represents a struct for specifying configuration options for the Render middleware.
- RenderOptions struct {
- // Directory to load templates. Default is "templates".
- Directory string
- // Addtional directories to overwite templates.
- AppendDirectories []string
- // Layout template name. Will not render a layout if "". Default is to "".
- Layout string
- // Extensions to parse template files from. Defaults are [".tmpl", ".html"].
- Extensions []string
- // Funcs is a slice of FuncMaps to apply to the template upon compilation. This is useful for helper functions. Default is [].
- Funcs []template.FuncMap
- // Delims sets the action delimiters to the specified strings in the Delims struct.
- Delims Delims
- // Appends the given charset to the Content-Type header. Default is "UTF-8".
- Charset string
- // Outputs human readable JSON.
- IndentJSON bool
- // Outputs human readable XML.
- IndentXML bool
- // Prefixes the JSON output with the given bytes.
- PrefixJSON []byte
- // Prefixes the XML output with the given bytes.
- PrefixXML []byte
- // Allows changing of output to XHTML instead of HTML. Default is "text/html"
- HTMLContentType string
- // TemplateFileSystem is the interface for supporting any implmentation of template file system.
- TemplateFileSystem
- }
-
- // HTMLOptions is a struct for overriding some rendering Options for specific HTML call
- HTMLOptions struct {
- // Layout template name. Overrides Options.Layout.
- Layout string
- }
-
- Render interface {
- http.ResponseWriter
- SetResponseWriter(http.ResponseWriter)
-
- JSON(int, interface{})
- JSONString(interface{}) (string, error)
- RawData(int, []byte) // Serve content as binary
- PlainText(int, []byte) // Serve content as plain text
- HTML(int, string, interface{}, ...HTMLOptions)
- HTMLSet(int, string, string, interface{}, ...HTMLOptions)
- HTMLSetString(string, string, interface{}, ...HTMLOptions) (string, error)
- HTMLString(string, interface{}, ...HTMLOptions) (string, error)
- HTMLSetBytes(string, string, interface{}, ...HTMLOptions) ([]byte, error)
- HTMLBytes(string, interface{}, ...HTMLOptions) ([]byte, error)
- XML(int, interface{})
- Error(int, ...string)
- Status(int)
- SetTemplatePath(string, string)
- HasTemplateSet(string) bool
- }
-)
-
-// TplFile implements TemplateFile interface.
-type TplFile struct {
- name string
- data []byte
- ext string
}
-// NewTplFile cerates new template file with given name and data.
-func NewTplFile(name string, data []byte, ext string) *TplFile {
- return &TplFile{name, data, ext}
-}
-
-func (f *TplFile) Name() string {
- return f.name
-}
-
-func (f *TplFile) Data() []byte {
- return f.data
-}
-
-func (f *TplFile) Ext() string {
- return f.ext
-}
-
-// TplFileSystem implements TemplateFileSystem interface.
-type TplFileSystem struct {
- files []TemplateFile
-}
-
-// NewTemplateFileSystem creates new template file system with given options.
-func NewTemplateFileSystem(opt RenderOptions, omitData bool) TplFileSystem {
- fs := TplFileSystem{}
- fs.files = make([]TemplateFile, 0, 10)
-
- // Directories are composed in reverse order because later one overwrites previous ones,
- // so once found, we can directly jump out of the loop.
- dirs := make([]string, 0, len(opt.AppendDirectories)+1)
- for i := len(opt.AppendDirectories) - 1; i >= 0; i-- {
- dirs = append(dirs, opt.AppendDirectories[i])
- }
- dirs = append(dirs, opt.Directory)
-
- var err error
- for i := range dirs {
- // Skip ones that does not exists for symlink test,
- // but allow non-symlink ones added after start.
- if _, err := os.Stat(dirs[i]); err != nil && os.IsNotExist(err) {
- continue
+func compileTemplates(filesystem fs.FS, leftDelim, rightDelim string) (*template.Template, error) {
+ t := template.New("")
+ t.Delims(leftDelim, rightDelim)
+ err := fs.WalkDir(filesystem, ".", func(path string, d fs.DirEntry, e error) error {
+ if e != nil {
+ return nil // skip unreadable or erroneous filesystem items
}
-
- dirs[i], err = filepath.EvalSymlinks(dirs[i])
- if err != nil {
- panic("EvalSymlinks(" + dirs[i] + "): " + err.Error())
+ if d.IsDir() {
+ return nil
}
- }
- lastDir := dirs[len(dirs)-1]
-
- // We still walk the last (original) directory because it's non-sense we load templates not exist in original directory.
- if err = filepath.Walk(lastDir, func(path string, info os.FileInfo, _ error) error {
- r, err := filepath.Rel(lastDir, path)
+ ext := filepath.Ext(path)
+ if ext != ".html" && ext != ".tmpl" {
+ return nil
+ }
+ data, err := fs.ReadFile(filesystem, path)
if err != nil {
return err
}
-
- ext := GetExt(r)
-
- for _, extension := range opt.Extensions {
- if ext != extension {
- continue
- }
-
- var data []byte
- if !omitData {
- // Loop over candidates of directory, break out once found.
- // The file always exists because it's inside the walk function,
- // and read original file is the worst case.
- for i := range dirs {
- path = filepath.Join(dirs[i], r)
- if f, err := os.Stat(path); err != nil || f.IsDir() {
- continue
- }
-
- data, err = ioutil.ReadFile(path)
- if err != nil {
- return err
- }
- break
- }
- }
-
- name := filepath.ToSlash((r[0 : len(r)-len(ext)]))
- fs.files = append(fs.files, NewTplFile(name, data, ext))
- }
-
- return nil
- }); err != nil {
- panic("NewTemplateFileSystem: " + err.Error())
- }
-
- return fs
-}
-
-func (fs TplFileSystem) ListFiles() []TemplateFile {
- return fs.files
-}
-
-func (fs TplFileSystem) Get(name string) (io.Reader, error) {
- for i := range fs.files {
- if fs.files[i].Name()+fs.files[i].Ext() == name {
- return bytes.NewReader(fs.files[i].Data()), nil
- }
- }
- return nil, fmt.Errorf("file '%s' not found", name)
-}
-
-func PrepareCharset(charset string) string {
- if len(charset) != 0 {
- return "; charset=" + charset
- }
-
- return "; charset=" + _DEFAULT_CHARSET
-}
-
-func GetExt(s string) string {
- index := strings.Index(s, ".")
- if index == -1 {
- return ""
- }
- return s[index:]
-}
-
-func compile(opt RenderOptions) *template.Template {
- t := template.New(opt.Directory)
- t.Delims(opt.Delims.Left, opt.Delims.Right)
- // Parse an initial template in case we don't have any.
- template.Must(t.Parse("Macaron"))
-
- if opt.TemplateFileSystem == nil {
- opt.TemplateFileSystem = NewTemplateFileSystem(opt, false)
- }
-
- for _, f := range opt.TemplateFileSystem.ListFiles() {
- tmpl := t.New(f.Name())
- for _, funcs := range opt.Funcs {
- tmpl.Funcs(funcs)
- }
- // Bomb out if parse fails. We don't want any silent server starts.
- template.Must(tmpl.Funcs(helperFuncs).Parse(string(f.Data())))
- }
-
- return t
-}
-
-const (
- DEFAULT_TPL_SET_NAME = "DEFAULT"
-)
-
-// TemplateSet represents a template set of type *template.Template.
-type TemplateSet struct {
- lock sync.RWMutex
- sets map[string]*template.Template
- dirs map[string]string
-}
-
-// NewTemplateSet initializes a new empty template set.
-func NewTemplateSet() *TemplateSet {
- return &TemplateSet{
- sets: make(map[string]*template.Template),
- dirs: make(map[string]string),
- }
-}
-
-func (ts *TemplateSet) Set(name string, opt *RenderOptions) *template.Template {
- t := compile(*opt)
-
- ts.lock.Lock()
- defer ts.lock.Unlock()
-
- ts.sets[name] = t
- ts.dirs[name] = opt.Directory
- return t
-}
-
-func (ts *TemplateSet) Get(name string) *template.Template {
- ts.lock.RLock()
- defer ts.lock.RUnlock()
-
- return ts.sets[name]
-}
-
-func (ts *TemplateSet) GetDir(name string) string {
- ts.lock.RLock()
- defer ts.lock.RUnlock()
-
- return ts.dirs[name]
-}
-
-func prepareRenderOptions(options []RenderOptions) RenderOptions {
- var opt RenderOptions
- if len(options) > 0 {
- opt = options[0]
- }
-
- // Defaults.
- if len(opt.Directory) == 0 {
- opt.Directory = "templates"
- }
- if len(opt.Extensions) == 0 {
- opt.Extensions = []string{".tmpl", ".html"}
- }
- if len(opt.HTMLContentType) == 0 {
- opt.HTMLContentType = _CONTENT_HTML
- }
-
- return opt
-}
-
-func ParseTplSet(tplSet string) (tplName string, tplDir string) {
- tplSet = strings.TrimSpace(tplSet)
- if len(tplSet) == 0 {
- panic("empty template set argument")
- }
- infos := strings.Split(tplSet, ":")
- if len(infos) == 1 {
- tplDir = infos[0]
- tplName = path.Base(tplDir)
- } else {
- tplName = infos[0]
- tplDir = infos[1]
- }
-
- dir, err := os.Stat(tplDir)
- if err != nil || !dir.IsDir() {
- panic("template set path does not exist or is not a directory")
- }
- return tplName, tplDir
-}
-
-func renderHandler(opt RenderOptions, tplSets []string) Handler {
- cs := PrepareCharset(opt.Charset)
- ts := NewTemplateSet()
- ts.Set(DEFAULT_TPL_SET_NAME, &opt)
-
- var tmpOpt RenderOptions
- for _, tplSet := range tplSets {
- tplName, tplDir := ParseTplSet(tplSet)
- tmpOpt = opt
- tmpOpt.Directory = tplDir
- ts.Set(tplName, &tmpOpt)
- }
-
- return func(ctx *Context) {
- r := &TplRender{
- ResponseWriter: ctx.Resp,
- TemplateSet: ts,
- Opt: &opt,
- CompiledCharset: cs,
- }
- ctx.Data["TmplLoadTimes"] = func() string {
- if r.startTime.IsZero() {
- return ""
- }
- return fmt.Sprint(time.Since(r.startTime).Nanoseconds()/1e6) + "ms"
- }
-
- ctx.Render = r
- ctx.MapTo(r, (*Render)(nil))
- }
-}
-
-// Renderer is a Middleware that maps a macaron.Render service into the Macaron handler chain.
-// An single variadic macaron.RenderOptions struct can be optionally provided to configure
-// HTML rendering. The default directory for templates is "templates" and the default
-// file extension is ".tmpl" and ".html".
-//
-// If MACARON_ENV is set to "" or "development" then templates will be recompiled on every request. For more performance, set the
-// MACARON_ENV environment variable to "production".
-func Renderer(options ...RenderOptions) Handler {
- return renderHandler(prepareRenderOptions(options), []string{})
-}
-
-func Renderers(options RenderOptions, tplSets ...string) Handler {
- return renderHandler(prepareRenderOptions([]RenderOptions{options}), tplSets)
-}
-
-type TplRender struct {
- http.ResponseWriter
- *TemplateSet
- Opt *RenderOptions
- CompiledCharset string
-
- startTime time.Time
-}
-
-func (r *TplRender) SetResponseWriter(rw http.ResponseWriter) {
- r.ResponseWriter = rw
-}
-
-func (r *TplRender) JSON(status int, v interface{}) {
- var (
- result []byte
- err error
- )
- if r.Opt.IndentJSON {
- result, err = json.MarshalIndent(v, "", " ")
- } else {
- result, err = json.Marshal(v)
- }
- if err != nil {
- http.Error(r, err.Error(), 500)
- return
- }
-
- // json rendered fine, write out the result
- r.Header().Set(_CONTENT_TYPE, _CONTENT_JSON+r.CompiledCharset)
- r.WriteHeader(status)
- if len(r.Opt.PrefixJSON) > 0 {
- _, _ = r.Write(r.Opt.PrefixJSON)
- }
- _, _ = r.Write(result)
-}
-
-func (r *TplRender) JSONString(v interface{}) (string, error) {
- var result []byte
- var err error
- if r.Opt.IndentJSON {
- result, err = json.MarshalIndent(v, "", " ")
- } else {
- result, err = json.Marshal(v)
- }
- if err != nil {
- return "", err
- }
- return string(result), nil
-}
-
-func (r *TplRender) XML(status int, v interface{}) {
- var result []byte
- var err error
- if r.Opt.IndentXML {
- result, err = xml.MarshalIndent(v, "", " ")
- } else {
- result, err = xml.Marshal(v)
- }
- if err != nil {
- http.Error(r, err.Error(), 500)
- return
- }
-
- // XML rendered fine, write out the result
- r.Header().Set(_CONTENT_TYPE, _CONTENT_XML+r.CompiledCharset)
- r.WriteHeader(status)
- if len(r.Opt.PrefixXML) > 0 {
- _, _ = r.Write(r.Opt.PrefixXML)
- }
- _, _ = r.Write(result)
-}
-
-func (r *TplRender) data(status int, contentType string, v []byte) {
- if r.Header().Get(_CONTENT_TYPE) == "" {
- r.Header().Set(_CONTENT_TYPE, contentType)
- }
- r.WriteHeader(status)
- _, _ = r.Write(v)
-}
-
-func (r *TplRender) RawData(status int, v []byte) {
- r.data(status, _CONTENT_BINARY, v)
-}
-
-func (r *TplRender) PlainText(status int, v []byte) {
- r.data(status, _CONTENT_PLAIN, v)
-}
-
-func (r *TplRender) execute(t *template.Template, name string, data interface{}) (*bytes.Buffer, error) {
- buf := bufpool.Get().(*bytes.Buffer)
- return buf, t.ExecuteTemplate(buf, name, data)
-}
-
-func (r *TplRender) addYield(t *template.Template, tplName string, data interface{}) {
- funcs := template.FuncMap{
- "yield": func() (template.HTML, error) {
- buf, err := r.execute(t, tplName, data)
- // return safe html here since we are rendering our own template
- return template.HTML(buf.String()), err
- },
- "current": func() (string, error) {
- return tplName, nil
- },
- }
- t.Funcs(funcs)
-}
-
-func (r *TplRender) renderBytes(setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) (*bytes.Buffer, error) {
- t := r.TemplateSet.Get(setName)
- if Env == DEV {
- opt := *r.Opt
- opt.Directory = r.TemplateSet.GetDir(setName)
- t = r.TemplateSet.Set(setName, &opt)
- }
- if t == nil {
- return nil, fmt.Errorf("html/template: template \"%s\" is undefined", tplName)
- }
-
- opt := r.prepareHTMLOptions(htmlOpt)
-
- if len(opt.Layout) > 0 {
- r.addYield(t, tplName, data)
- tplName = opt.Layout
- }
-
- out, err := r.execute(t, tplName, data)
- if err != nil {
- return nil, err
- }
-
- return out, nil
-}
-
-func (r *TplRender) renderHTML(status int, setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) {
- r.startTime = time.Now()
-
- out, err := r.renderBytes(setName, tplName, data, htmlOpt...)
- if err != nil {
- http.Error(r, err.Error(), http.StatusInternalServerError)
- return
- }
-
- r.Header().Set(_CONTENT_TYPE, r.Opt.HTMLContentType+r.CompiledCharset)
- r.WriteHeader(status)
-
- if _, err := out.WriteTo(r); err != nil {
- out.Reset()
- }
- bufpool.Put(out)
-}
-
-func (r *TplRender) HTML(status int, name string, data interface{}, htmlOpt ...HTMLOptions) {
- r.renderHTML(status, DEFAULT_TPL_SET_NAME, name, data, htmlOpt...)
-}
-
-func (r *TplRender) HTMLSet(status int, setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) {
- r.renderHTML(status, setName, tplName, data, htmlOpt...)
-}
-
-func (r *TplRender) HTMLSetBytes(setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) ([]byte, error) {
- out, err := r.renderBytes(setName, tplName, data, htmlOpt...)
- if err != nil {
- return []byte(""), err
- }
- return out.Bytes(), nil
-}
-
-func (r *TplRender) HTMLBytes(name string, data interface{}, htmlOpt ...HTMLOptions) ([]byte, error) {
- return r.HTMLSetBytes(DEFAULT_TPL_SET_NAME, name, data, htmlOpt...)
-}
-
-func (r *TplRender) HTMLSetString(setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) (string, error) {
- p, err := r.HTMLSetBytes(setName, tplName, data, htmlOpt...)
- return string(p), err
-}
-
-func (r *TplRender) HTMLString(name string, data interface{}, htmlOpt ...HTMLOptions) (string, error) {
- p, err := r.HTMLBytes(name, data, htmlOpt...)
- return string(p), err
-}
-
-// Error writes the given HTTP status to the current ResponseWriter
-func (r *TplRender) Error(status int, message ...string) {
- r.WriteHeader(status)
- if len(message) > 0 {
- _, _ = r.Write([]byte(message[0]))
- }
-}
-
-func (r *TplRender) Status(status int) {
- r.WriteHeader(status)
-}
-
-func (r *TplRender) prepareHTMLOptions(htmlOpt []HTMLOptions) HTMLOptions {
- if len(htmlOpt) > 0 {
- return htmlOpt[0]
- }
-
- return HTMLOptions{
- Layout: r.Opt.Layout,
- }
-}
-
-func (r *TplRender) SetTemplatePath(setName, dir string) {
- if len(setName) == 0 {
- setName = DEFAULT_TPL_SET_NAME
- }
- opt := *r.Opt
- opt.Directory = dir
- r.TemplateSet.Set(setName, &opt)
-}
-
-func (r *TplRender) HasTemplateSet(name string) bool {
- return r.TemplateSet.Get(name) != nil
-}
-
-// DummyRender is used when user does not choose any real render to use.
-// This way, we can print out friendly message which asks them to register one,
-// instead of ugly and confusing 'nil pointer' panic.
-type DummyRender struct {
- http.ResponseWriter
-}
-
-func renderNotRegistered() {
- panic("middleware render hasn't been registered")
-}
-
-func (r *DummyRender) SetResponseWriter(http.ResponseWriter) {
- renderNotRegistered()
-}
-
-func (r *DummyRender) JSON(int, interface{}) {
- renderNotRegistered()
-}
-
-func (r *DummyRender) JSONString(interface{}) (string, error) {
- renderNotRegistered()
- return "", nil
-}
-
-func (r *DummyRender) RawData(int, []byte) {
- renderNotRegistered()
-}
-
-func (r *DummyRender) PlainText(int, []byte) {
- renderNotRegistered()
-}
-
-func (r *DummyRender) HTML(int, string, interface{}, ...HTMLOptions) {
- renderNotRegistered()
-}
-
-func (r *DummyRender) HTMLSet(int, string, string, interface{}, ...HTMLOptions) {
- renderNotRegistered()
-}
-
-func (r *DummyRender) HTMLSetString(string, string, interface{}, ...HTMLOptions) (string, error) {
- renderNotRegistered()
- return "", nil
-}
-
-func (r *DummyRender) HTMLString(string, interface{}, ...HTMLOptions) (string, error) {
- renderNotRegistered()
- return "", nil
-}
-
-func (r *DummyRender) HTMLSetBytes(string, string, interface{}, ...HTMLOptions) ([]byte, error) {
- renderNotRegistered()
- return nil, nil
-}
-
-func (r *DummyRender) HTMLBytes(string, interface{}, ...HTMLOptions) ([]byte, error) {
- renderNotRegistered()
- return nil, nil
-}
-
-func (r *DummyRender) XML(int, interface{}) {
- renderNotRegistered()
-}
-
-func (r *DummyRender) Error(int, ...string) {
- renderNotRegistered()
-}
-
-func (r *DummyRender) Status(int) {
- renderNotRegistered()
-}
-
-func (r *DummyRender) SetTemplatePath(string, string) {
- renderNotRegistered()
-}
-
-func (r *DummyRender) HasTemplateSet(string) bool {
- renderNotRegistered()
- return false
+ basename := path[:len(path)-len(ext)]
+ _, err = t.New(basename).Parse(string(data))
+ return err
+ })
+ return t, err
}
diff --git a/pkg/middleware/csp.go b/pkg/middleware/csp.go
index 3fff0cbe04f..2ea9614dfe0 100644
--- a/pkg/middleware/csp.go
+++ b/pkg/middleware/csp.go
@@ -10,45 +10,44 @@ import (
"strings"
"github.com/grafana/grafana/pkg/infra/log"
- "github.com/grafana/grafana/pkg/models"
+ "github.com/grafana/grafana/pkg/services/contexthandler"
"github.com/grafana/grafana/pkg/setting"
- macaron "gopkg.in/macaron.v1"
)
// AddCSPHeader adds the Content Security Policy header.
-func AddCSPHeader(cfg *setting.Cfg, logger log.Logger) macaron.Handler {
- return func(w http.ResponseWriter, req *http.Request, c *macaron.Context) {
- if !cfg.CSPEnabled {
- return
- }
+func AddCSPHeader(cfg *setting.Cfg, logger log.Logger) func(http.Handler) http.Handler {
+ return func(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
+ if !cfg.CSPEnabled {
+ next.ServeHTTP(rw, req)
+ return
+ }
- logger.Debug("Adding CSP header to response", "cfg", fmt.Sprintf("%p", cfg))
+ logger.Debug("Adding CSP header to response", "cfg", fmt.Sprintf("%p", cfg))
- ctx, ok := c.Data["ctx"].(*models.ReqContext)
- if !ok {
- panic("Failed to convert context into models.ReqContext")
- }
+ ctx := contexthandler.FromContext(req.Context())
+ if cfg.CSPTemplate == "" {
+ logger.Debug("CSP template not configured, so returning 500")
+ ctx.JsonApiErr(500, "CSP template has to be configured", nil)
+ return
+ }
- if cfg.CSPTemplate == "" {
- logger.Debug("CSP template not configured, so returning 500")
- ctx.JsonApiErr(500, "CSP template has to be configured", nil)
- return
- }
+ var buf [16]byte
+ if _, err := io.ReadFull(rand.Reader, buf[:]); err != nil {
+ logger.Error("Failed to generate CSP nonce", "err", err)
+ ctx.JsonApiErr(500, "Failed to generate CSP nonce", err)
+ }
- var buf [16]byte
- if _, err := io.ReadFull(rand.Reader, buf[:]); err != nil {
- logger.Error("Failed to generate CSP nonce", "err", err)
- ctx.JsonApiErr(500, "Failed to generate CSP nonce", err)
- }
+ nonce := base64.RawStdEncoding.EncodeToString(buf[:])
+ val := strings.ReplaceAll(cfg.CSPTemplate, "$NONCE", fmt.Sprintf("'nonce-%s'", nonce))
- nonce := base64.RawStdEncoding.EncodeToString(buf[:])
- val := strings.ReplaceAll(cfg.CSPTemplate, "$NONCE", fmt.Sprintf("'nonce-%s'", nonce))
-
- re := regexp.MustCompile(`^\w+:(//)?`)
- rootPath := re.ReplaceAllString(cfg.AppURL, "")
- val = strings.ReplaceAll(val, "$ROOT_PATH", rootPath)
- w.Header().Set("Content-Security-Policy", val)
- ctx.RequestNonce = nonce
- logger.Debug("Successfully generated CSP nonce", "nonce", nonce)
+ re := regexp.MustCompile(`^\w+:(//)?`)
+ rootPath := re.ReplaceAllString(cfg.AppURL, "")
+ val = strings.ReplaceAll(val, "$ROOT_PATH", rootPath)
+ rw.Header().Set("Content-Security-Policy", val)
+ ctx.RequestNonce = nonce
+ logger.Debug("Successfully generated CSP nonce", "nonce", nonce)
+ next.ServeHTTP(rw, req)
+ })
}
}
diff --git a/pkg/middleware/gziper.go b/pkg/middleware/gziper.go
index 5427edd1aef..d0248b345b8 100644
--- a/pkg/middleware/gziper.go
+++ b/pkg/middleware/gziper.go
@@ -1,43 +1,81 @@
package middleware
import (
+ "bufio"
+ "compress/gzip"
+ "fmt"
+ "net"
+ "net/http"
"strings"
- "github.com/go-macaron/gzip"
- "github.com/grafana/grafana/pkg/infra/log"
- "gopkg.in/macaron.v1"
+ macaron "gopkg.in/macaron.v1"
)
-const resourcesPath = "/resources"
-
-var gzipIgnoredPathPrefixes = []string{
- "/api/datasources/proxy", // Ignore datasource proxy requests.
- "/api/plugin-proxy/",
- "/metrics",
- "/api/live/ws", // WebSocket does not support gzip compression.
- "/api/live/push", // WebSocket does not support gzip compression.
+type gzipResponseWriter struct {
+ w *gzip.Writer
+ macaron.ResponseWriter
}
-func Gziper() macaron.Handler {
- gziperLogger := log.New("gziper")
- gziper := gzip.Gziper()
+func (grw *gzipResponseWriter) WriteHeader(c int) {
+ grw.Header().Del("Content-Length")
+ grw.ResponseWriter.WriteHeader(c)
+}
- return func(ctx *macaron.Context) {
- requestPath := ctx.Req.URL.RequestURI()
+func (grw gzipResponseWriter) Write(p []byte) (int, error) {
+ if grw.Header().Get("Content-Type") == "" {
+ grw.Header().Set("Content-Type", http.DetectContentType(p))
+ }
+ grw.Header().Del("Content-Length")
+ return grw.w.Write(p)
+}
- for _, pathPrefix := range gzipIgnoredPathPrefixes {
- if strings.HasPrefix(requestPath, pathPrefix) {
+func (grw gzipResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
+ if hijacker, ok := grw.ResponseWriter.(http.Hijacker); ok {
+ return hijacker.Hijack()
+ }
+ return nil, nil, fmt.Errorf("GZIP ResponseWriter doesn't implement the Hijacker interface")
+}
+
+type matcher func(s string) bool
+
+func prefix(p string) matcher { return func(s string) bool { return strings.HasPrefix(s, p) } }
+func substr(p string) matcher { return func(s string) bool { return strings.Contains(s, p) } }
+
+var gzipIgnoredPaths = []matcher{
+ prefix("/api/datasources"),
+ prefix("/api/plugins"),
+ prefix("/api/plugin-proxy/"),
+ prefix("/metrics"),
+ prefix("/api/live/ws"), // WebSocket does not support gzip compression.
+ prefix("/api/live/push"), // WebSocket does not support gzip compression.
+ substr("/resources"),
+}
+
+func Gziper() func(http.Handler) http.Handler {
+ return func(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
+ requestPath := req.URL.RequestURI()
+
+ for _, pathMatcher := range gzipIgnoredPaths {
+ if pathMatcher(requestPath) {
+ fmt.Println("skip path", requestPath)
+ next.ServeHTTP(rw, req)
+ return
+ }
+ }
+
+ if !strings.Contains(req.Header.Get("Accept-Encoding"), "gzip") {
+ next.ServeHTTP(rw, req)
return
}
- }
- // ignore resources
- if (strings.HasPrefix(requestPath, "/api/datasources/") || strings.HasPrefix(requestPath, "/api/plugins/")) && strings.Contains(requestPath, resourcesPath) {
- return
- }
+ grw := &gzipResponseWriter{gzip.NewWriter(rw), rw.(macaron.ResponseWriter)}
+ grw.Header().Set("Content-Encoding", "gzip")
+ grw.Header().Set("Vary", "Accept-Encoding")
- if _, err := ctx.Invoke(gziper); err != nil {
- gziperLogger.Error("Invoking gzip handler failed", "err", err)
- }
+ next.ServeHTTP(grw, req)
+ // We can't really handle close errors at this point and we can't report them to the caller
+ _ = grw.w.Close()
+ })
}
}
diff --git a/pkg/middleware/middleware_test.go b/pkg/middleware/middleware_test.go
index 30ea0b1aff1..f7cbdf5102e 100644
--- a/pkg/middleware/middleware_test.go
+++ b/pkg/middleware/middleware_test.go
@@ -111,7 +111,7 @@ func TestMiddlewareContext(t *testing.T) {
Settings: map[string]interface{}{},
NavTree: []*dtos.NavLink{},
}
- t.Log("Calling HTML", "data", data, "render", c.Render)
+ t.Log("Calling HTML", "data", data)
c.HTML(200, "index-template", data)
t.Log("Returned HTML with code 200")
}
@@ -632,11 +632,8 @@ func middlewareScenario(t *testing.T, desc string, fn scenarioFunc, cbs ...func(
sc.m = macaron.New()
sc.m.Use(AddDefaultResponseHeaders(cfg))
- sc.m.Use(AddCSPHeader(cfg, logger))
- sc.m.Use(macaron.Renderer(macaron.RenderOptions{
- Directory: viewsPath,
- Delims: macaron.Delims{Left: "[[", Right: "]]"},
- }))
+ sc.m.UseMiddleware(AddCSPHeader(cfg, logger))
+ sc.m.UseMiddleware(macaron.Renderer(viewsPath, "[[", "]]"))
ctxHdlr := getContextHandler(t, cfg)
sc.sqlStore = ctxHdlr.SQLStore
diff --git a/pkg/middleware/org_redirect.go b/pkg/middleware/org_redirect.go
index cb1813ade7f..59cbddf58e9 100644
--- a/pkg/middleware/org_redirect.go
+++ b/pkg/middleware/org_redirect.go
@@ -37,7 +37,7 @@ func OrgRedirect(cfg *setting.Cfg) macaron.Handler {
if ctx.IsApiRequest() {
ctx.JsonApiErr(404, "Not found", nil)
} else {
- ctx.Error(404, "Not found")
+ http.Error(ctx.Resp, "Not found", http.StatusNotFound)
}
return
diff --git a/pkg/middleware/rate_limit_test.go b/pkg/middleware/rate_limit_test.go
index c78bbe9136a..109a2e1220c 100644
--- a/pkg/middleware/rate_limit_test.go
+++ b/pkg/middleware/rate_limit_test.go
@@ -32,10 +32,7 @@ func rateLimiterScenario(t *testing.T, desc string, rps int, burst int, fn rateL
cfg := setting.NewCfg()
m := macaron.New()
- m.Use(macaron.Renderer(macaron.RenderOptions{
- Directory: "",
- Delims: macaron.Delims{Left: "[[", Right: "]]"},
- }))
+ m.UseMiddleware(macaron.Renderer("../../public/views", "[[", "]]"))
m.Use(getContextHandler(t, cfg).Middleware)
m.Get("/foo", RateLimit(rps, burst, func() time.Time { return currentTime }), defaultHandler)
diff --git a/pkg/middleware/recovery.go b/pkg/middleware/recovery.go
index 336a3425da9..88a4f8b32e0 100644
--- a/pkg/middleware/recovery.go
+++ b/pkg/middleware/recovery.go
@@ -158,7 +158,7 @@ func Recovery(cfg *setting.Cfg) macaron.Handler {
c.JSON(500, resp)
} else {
- c.HTML(500, cfg.ErrTemplateName)
+ c.HTML(500, cfg.ErrTemplateName, c.Data)
}
}
}()
diff --git a/pkg/middleware/recovery_test.go b/pkg/middleware/recovery_test.go
index 0bc002b8b5d..c62079372b2 100644
--- a/pkg/middleware/recovery_test.go
+++ b/pkg/middleware/recovery_test.go
@@ -65,10 +65,7 @@ func recoveryScenario(t *testing.T, desc string, url string, fn scenarioFunc) {
sc.m.Use(Recovery(cfg))
sc.m.Use(AddDefaultResponseHeaders(cfg))
- sc.m.Use(macaron.Renderer(macaron.RenderOptions{
- Directory: viewsPath,
- Delims: macaron.Delims{Left: "[[", Right: "]]"},
- }))
+ sc.m.UseMiddleware(macaron.Renderer(viewsPath, "[[", "]]"))
sc.userAuthTokenService = auth.NewFakeUserAuthTokenService()
sc.remoteCacheService = remotecache.NewFakeStore(t)
diff --git a/pkg/models/context.go b/pkg/models/context.go
index 42f7036551e..a3bb6838985 100644
--- a/pkg/models/context.go
+++ b/pkg/models/context.go
@@ -36,7 +36,7 @@ func (ctx *ReqContext) Handle(cfg *setting.Cfg, status int, title string, err er
ctx.Data["AppSubUrl"] = cfg.AppSubURL
ctx.Data["Theme"] = "dark"
- ctx.HTML(status, cfg.ErrTemplateName)
+ ctx.HTML(status, cfg.ErrTemplateName, ctx.Data)
}
func (ctx *ReqContext) IsApiRequest() bool {
diff --git a/pkg/services/ngalert/notifier/alertmanager.go b/pkg/services/ngalert/notifier/alertmanager.go
index afd395c71cf..f68cb3a8b6a 100644
--- a/pkg/services/ngalert/notifier/alertmanager.go
+++ b/pkg/services/ngalert/notifier/alertmanager.go
@@ -475,7 +475,7 @@ func (am *Alertmanager) buildReceiverIntegrations(receiver *apimodels.PostableAp
n, err = channels.NewDiscordNotifier(cfg, tmpl)
case "googlechat":
n, err = channels.NewGoogleChatNotifier(cfg, tmpl)
- case "line":
+ case "LINE":
n, err = channels.NewLineNotifier(cfg, tmpl)
case "threema":
n, err = channels.NewThreemaNotifier(cfg, tmpl)
diff --git a/pkg/tests/api/alerting/api_notification_channel_test.go b/pkg/tests/api/alerting/api_notification_channel_test.go
index c7d9e59dc35..ee0c898af1f 100644
--- a/pkg/tests/api/alerting/api_notification_channel_test.go
+++ b/pkg/tests/api/alerting/api_notification_channel_test.go
@@ -723,7 +723,7 @@ const alertmanagerConfig = `
"grafana_managed_receiver_configs": [
{
"name": "line_test",
- "type": "line",
+ "type": "LINE",
"settings": {},
"secureSettings": {
"token": "mysecrettoken"
@@ -1217,7 +1217,7 @@ var expAlertmanagerConfigFromAPI = `
{
"uid": "",
"name": "line_test",
- "type": "line",
+ "type": "LINE",
"disableResolveMessage": false,
"settings": {},
"secureFields": {
diff --git a/pkg/tsdb/testdatasource/testdata/usa-timeseries-wide.txt b/pkg/tsdb/testdatasource/testdata/usa-timeseries-wide.txt
index 53a51e8d091..e6a63a4a612 100644
--- a/pkg/tsdb/testdatasource/testdata/usa-timeseries-wide.txt
+++ b/pkg/tsdb/testdatasource/testdata/usa-timeseries-wide.txt
@@ -3,23 +3,23 @@
Frame[0]
Name:
Dimensions: 7 Fields by 10 Rows
-+-------------------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+
-| Name: Time | Name: foo | Name: foo | Name: foo | Name: bar | Name: bar | Name: bar |
-| Labels: | Labels: state=CA | Labels: state=OR | Labels: state=NV | Labels: state=CA | Labels: state=OR | Labels: state=NV |
-| Type: []time.Time | Type: []float64 | Type: []float64 | Type: []float64 | Type: []float64 | Type: []float64 | Type: []float64 |
-+-------------------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+
-| 2020-01-10 23:00:00 +0000 UTC | 0.7591864109375384 | 0.9882682601465907 | 0 | 0.6508732545187114 | -0.1527280131044415 | 1 |
-| 2020-01-10 23:00:01 +0000 UTC | 0.9913874643906261 | 0.7446917214252602 | 0.5458434494486994 | 0.13096142733310906 | -0.6674086005144695 | 0.8378871813639024 |
-| 2020-01-10 23:00:02 +0000 UTC | 0.9021552854179972 | 0.25966703475349645 | 0.9147104586490411 | -0.4314114520876468 | -0.9656982091017495 | 0.40410985738788996 |
-| 2020-01-10 23:00:03 +0000 UTC | 0.520421234112239 | -0.309548361739801 | 0.9870048864743552 | -0.8539096785287623 | -0.9508837004303972 | -0.16069024262768705 |
-| 2020-01-10 23:00:04 +0000 UTC | -0.03004672347354234 | -0.7784002433814474 | 0.7392870259917509 | -0.9995484952759943 | -0.6277683180152559 | -0.6733904463238785 |
-| 2020-01-10 23:00:05 +0000 UTC | -0.5707727629931729 | -0.994874810059912 | 0.2518733583799049 | -0.8211080641579032 | -0.10111435263232425 | -0.9677602034277024 |
-| 2020-01-10 23:00:06 +0000 UTC | -0.9264396396937303 | -0.8887854574406474 | -0.31720410936455373 | -0.37644334766887727 | 0.4583234781701883 | -0.9483572918485101 |
-| 2020-01-10 23:00:07 +0000 UTC | -0.981731033820365 | -0.4945290734844293 | -0.7834358726049309 | 0.19027395311496903 | 0.8691610871861625 | -0.6214726329580013 |
-| 2020-01-10 23:00:08 +0000 UTC | -0.7187200578767007 | 0.060066314471906294 | -0.9956576407880757 | 0.6952995601938146 | 0.998194388817009 | -0.09309061359945539 |
-| 2020-01-10 23:00:09 +0000 UTC | -0.22268161314765392 | 0.5951866633399958 | -0.8850616758817763 | 0.9748912242737435 | 0.8035874786121313 | 0.46547376927743367 |
-+-------------------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+
++-------------------------------+------------------+------------------+------------------+------------------+------------------+------------------+
+| Name: Time | Name: foo | Name: foo | Name: foo | Name: bar | Name: bar | Name: bar |
+| Labels: | Labels: state=CA | Labels: state=OR | Labels: state=NV | Labels: state=CA | Labels: state=OR | Labels: state=NV |
+| Type: []time.Time | Type: []float64 | Type: []float64 | Type: []float64 | Type: []float64 | Type: []float64 | Type: []float64 |
++-------------------------------+------------------+------------------+------------------+------------------+------------------+------------------+
+| 2020-01-10 23:00:00 +0000 UTC | 0.7591864109 | 0.9882682601 | 0 | 0.6508732545 | -0.1527280131 | 1 |
+| 2020-01-10 23:00:01 +0000 UTC | 0.9913874643 | 0.7446917214 | 0.5458434494 | 0.1309614273 | -0.6674086005 | 0.8378871813 |
+| 2020-01-10 23:00:02 +0000 UTC | 0.9021552854 | 0.2596670347 | 0.9147104586 | -0.431411452 | -0.9656982091 | 0.4041098573 |
+| 2020-01-10 23:00:03 +0000 UTC | 0.5204212341 | -0.3095483617 | 0.9870048864 | -0.8539096785 | -0.9508837004 | -0.1606902426 |
+| 2020-01-10 23:00:04 +0000 UTC | -0.0300467234 | -0.7784002433 | 0.7392870259 | -0.9995484952 | -0.627768318 | -0.6733904463 |
+| 2020-01-10 23:00:05 +0000 UTC | -0.5707727629 | -0.99487481 | 0.2518733583 | -0.8211080641 | -0.1011143526 | -0.9677602034 |
+| 2020-01-10 23:00:06 +0000 UTC | -0.9264396396 | -0.8887854574 | -0.3172041093 | -0.3764433476 | 0.4583234781 | -0.9483572918 |
+| 2020-01-10 23:00:07 +0000 UTC | -0.9817310338 | -0.4945290734 | -0.7834358726 | 0.1902739531 | 0.8691610871 | -0.6214726329 |
+| 2020-01-10 23:00:08 +0000 UTC | -0.7187200578 | 0.0600663144 | -0.9956576407 | 0.6952995601 | 0.9981943888 | -0.0930906135 |
+| 2020-01-10 23:00:09 +0000 UTC | -0.2226816131 | 0.5951866633 | -0.8850616758 | 0.9748912242 | 0.8035874786 | 0.4654737692 |
++-------------------------------+------------------+------------------+------------------+------------------+------------------+------------------+
====== TEST DATA RESPONSE (arrow base64) ======
-FRAME=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
+FRAME=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
diff --git a/pkg/tsdb/testdatasource/testdata/usa-timeseries.txt b/pkg/tsdb/testdatasource/testdata/usa-timeseries.txt
index 7dcdc92daef..38fd2980802 100644
--- a/pkg/tsdb/testdatasource/testdata/usa-timeseries.txt
+++ b/pkg/tsdb/testdatasource/testdata/usa-timeseries.txt
@@ -3,138 +3,138 @@
Frame[0]
Name: foo
Dimensions: 2 Fields by 10 Rows
-+-------------------------------+----------------------+
-| Name: Time | Name: Value |
-| Labels: | Labels: state=CA |
-| Type: []time.Time | Type: []float64 |
-+-------------------------------+----------------------+
-| 2020-01-10 23:00:00 +0000 UTC | 0.7591864109375384 |
-| 2020-01-10 23:00:01 +0000 UTC | 0.9913874643906261 |
-| 2020-01-10 23:00:02 +0000 UTC | 0.9021552854179972 |
-| 2020-01-10 23:00:03 +0000 UTC | 0.520421234112239 |
-| 2020-01-10 23:00:04 +0000 UTC | -0.03004672347354234 |
-| 2020-01-10 23:00:05 +0000 UTC | -0.5707727629931729 |
-| 2020-01-10 23:00:06 +0000 UTC | -0.9264396396937303 |
-| 2020-01-10 23:00:07 +0000 UTC | -0.981731033820365 |
-| 2020-01-10 23:00:08 +0000 UTC | -0.7187200578767007 |
-| 2020-01-10 23:00:09 +0000 UTC | -0.22268161314765392 |
-+-------------------------------+----------------------+
++-------------------------------+------------------+
+| Name: Time | Name: Value |
+| Labels: | Labels: state=CA |
+| Type: []time.Time | Type: []float64 |
++-------------------------------+------------------+
+| 2020-01-10 23:00:00 +0000 UTC | 0.7591864109 |
+| 2020-01-10 23:00:01 +0000 UTC | 0.9913874643 |
+| 2020-01-10 23:00:02 +0000 UTC | 0.9021552854 |
+| 2020-01-10 23:00:03 +0000 UTC | 0.5204212341 |
+| 2020-01-10 23:00:04 +0000 UTC | -0.0300467234 |
+| 2020-01-10 23:00:05 +0000 UTC | -0.5707727629 |
+| 2020-01-10 23:00:06 +0000 UTC | -0.9264396396 |
+| 2020-01-10 23:00:07 +0000 UTC | -0.9817310338 |
+| 2020-01-10 23:00:08 +0000 UTC | -0.7187200578 |
+| 2020-01-10 23:00:09 +0000 UTC | -0.2226816131 |
++-------------------------------+------------------+
Frame[1]
Name: foo
Dimensions: 2 Fields by 10 Rows
-+-------------------------------+----------------------+
-| Name: Time | Name: Value |
-| Labels: | Labels: state=OR |
-| Type: []time.Time | Type: []float64 |
-+-------------------------------+----------------------+
-| 2020-01-10 23:00:00 +0000 UTC | 0.9882682601465907 |
-| 2020-01-10 23:00:01 +0000 UTC | 0.7446917214252602 |
-| 2020-01-10 23:00:02 +0000 UTC | 0.25966703475349645 |
-| 2020-01-10 23:00:03 +0000 UTC | -0.309548361739801 |
-| 2020-01-10 23:00:04 +0000 UTC | -0.7784002433814474 |
-| 2020-01-10 23:00:05 +0000 UTC | -0.994874810059912 |
-| 2020-01-10 23:00:06 +0000 UTC | -0.8887854574406474 |
-| 2020-01-10 23:00:07 +0000 UTC | -0.4945290734844293 |
-| 2020-01-10 23:00:08 +0000 UTC | 0.060066314471906294 |
-| 2020-01-10 23:00:09 +0000 UTC | 0.5951866633399958 |
-+-------------------------------+----------------------+
++-------------------------------+------------------+
+| Name: Time | Name: Value |
+| Labels: | Labels: state=OR |
+| Type: []time.Time | Type: []float64 |
++-------------------------------+------------------+
+| 2020-01-10 23:00:00 +0000 UTC | 0.9882682601 |
+| 2020-01-10 23:00:01 +0000 UTC | 0.7446917214 |
+| 2020-01-10 23:00:02 +0000 UTC | 0.2596670347 |
+| 2020-01-10 23:00:03 +0000 UTC | -0.3095483617 |
+| 2020-01-10 23:00:04 +0000 UTC | -0.7784002433 |
+| 2020-01-10 23:00:05 +0000 UTC | -0.99487481 |
+| 2020-01-10 23:00:06 +0000 UTC | -0.8887854574 |
+| 2020-01-10 23:00:07 +0000 UTC | -0.4945290734 |
+| 2020-01-10 23:00:08 +0000 UTC | 0.0600663144 |
+| 2020-01-10 23:00:09 +0000 UTC | 0.5951866633 |
++-------------------------------+------------------+
Frame[2]
Name: foo
Dimensions: 2 Fields by 10 Rows
-+-------------------------------+----------------------+
-| Name: Time | Name: Value |
-| Labels: | Labels: state=NV |
-| Type: []time.Time | Type: []float64 |
-+-------------------------------+----------------------+
-| 2020-01-10 23:00:00 +0000 UTC | 0 |
-| 2020-01-10 23:00:01 +0000 UTC | 0.5458434494486994 |
-| 2020-01-10 23:00:02 +0000 UTC | 0.9147104586490411 |
-| 2020-01-10 23:00:03 +0000 UTC | 0.9870048864743552 |
-| 2020-01-10 23:00:04 +0000 UTC | 0.7392870259917509 |
-| 2020-01-10 23:00:05 +0000 UTC | 0.2518733583799049 |
-| 2020-01-10 23:00:06 +0000 UTC | -0.31720410936455373 |
-| 2020-01-10 23:00:07 +0000 UTC | -0.7834358726049309 |
-| 2020-01-10 23:00:08 +0000 UTC | -0.9956576407880757 |
-| 2020-01-10 23:00:09 +0000 UTC | -0.8850616758817763 |
-+-------------------------------+----------------------+
++-------------------------------+------------------+
+| Name: Time | Name: Value |
+| Labels: | Labels: state=NV |
+| Type: []time.Time | Type: []float64 |
++-------------------------------+------------------+
+| 2020-01-10 23:00:00 +0000 UTC | 0 |
+| 2020-01-10 23:00:01 +0000 UTC | 0.5458434494 |
+| 2020-01-10 23:00:02 +0000 UTC | 0.9147104586 |
+| 2020-01-10 23:00:03 +0000 UTC | 0.9870048864 |
+| 2020-01-10 23:00:04 +0000 UTC | 0.7392870259 |
+| 2020-01-10 23:00:05 +0000 UTC | 0.2518733583 |
+| 2020-01-10 23:00:06 +0000 UTC | -0.3172041093 |
+| 2020-01-10 23:00:07 +0000 UTC | -0.7834358726 |
+| 2020-01-10 23:00:08 +0000 UTC | -0.9956576407 |
+| 2020-01-10 23:00:09 +0000 UTC | -0.8850616758 |
++-------------------------------+------------------+
Frame[3]
Name: bar
Dimensions: 2 Fields by 10 Rows
-+-------------------------------+----------------------+
-| Name: Time | Name: Value |
-| Labels: | Labels: state=CA |
-| Type: []time.Time | Type: []float64 |
-+-------------------------------+----------------------+
-| 2020-01-10 23:00:00 +0000 UTC | 0.6508732545187114 |
-| 2020-01-10 23:00:01 +0000 UTC | 0.13096142733310906 |
-| 2020-01-10 23:00:02 +0000 UTC | -0.4314114520876468 |
-| 2020-01-10 23:00:03 +0000 UTC | -0.8539096785287623 |
-| 2020-01-10 23:00:04 +0000 UTC | -0.9995484952759943 |
-| 2020-01-10 23:00:05 +0000 UTC | -0.8211080641579032 |
-| 2020-01-10 23:00:06 +0000 UTC | -0.37644334766887727 |
-| 2020-01-10 23:00:07 +0000 UTC | 0.19027395311496903 |
-| 2020-01-10 23:00:08 +0000 UTC | 0.6952995601938146 |
-| 2020-01-10 23:00:09 +0000 UTC | 0.9748912242737435 |
-+-------------------------------+----------------------+
++-------------------------------+------------------+
+| Name: Time | Name: Value |
+| Labels: | Labels: state=CA |
+| Type: []time.Time | Type: []float64 |
++-------------------------------+------------------+
+| 2020-01-10 23:00:00 +0000 UTC | 0.6508732545 |
+| 2020-01-10 23:00:01 +0000 UTC | 0.1309614273 |
+| 2020-01-10 23:00:02 +0000 UTC | -0.431411452 |
+| 2020-01-10 23:00:03 +0000 UTC | -0.8539096785 |
+| 2020-01-10 23:00:04 +0000 UTC | -0.9995484952 |
+| 2020-01-10 23:00:05 +0000 UTC | -0.8211080641 |
+| 2020-01-10 23:00:06 +0000 UTC | -0.3764433476 |
+| 2020-01-10 23:00:07 +0000 UTC | 0.1902739531 |
+| 2020-01-10 23:00:08 +0000 UTC | 0.6952995601 |
+| 2020-01-10 23:00:09 +0000 UTC | 0.9748912242 |
++-------------------------------+------------------+
Frame[4]
Name: bar
Dimensions: 2 Fields by 10 Rows
-+-------------------------------+----------------------+
-| Name: Time | Name: Value |
-| Labels: | Labels: state=OR |
-| Type: []time.Time | Type: []float64 |
-+-------------------------------+----------------------+
-| 2020-01-10 23:00:00 +0000 UTC | -0.1527280131044415 |
-| 2020-01-10 23:00:01 +0000 UTC | -0.6674086005144695 |
-| 2020-01-10 23:00:02 +0000 UTC | -0.9656982091017495 |
-| 2020-01-10 23:00:03 +0000 UTC | -0.9508837004303972 |
-| 2020-01-10 23:00:04 +0000 UTC | -0.6277683180152559 |
-| 2020-01-10 23:00:05 +0000 UTC | -0.10111435263232425 |
-| 2020-01-10 23:00:06 +0000 UTC | 0.4583234781701883 |
-| 2020-01-10 23:00:07 +0000 UTC | 0.8691610871861625 |
-| 2020-01-10 23:00:08 +0000 UTC | 0.998194388817009 |
-| 2020-01-10 23:00:09 +0000 UTC | 0.8035874786121313 |
-+-------------------------------+----------------------+
++-------------------------------+------------------+
+| Name: Time | Name: Value |
+| Labels: | Labels: state=OR |
+| Type: []time.Time | Type: []float64 |
++-------------------------------+------------------+
+| 2020-01-10 23:00:00 +0000 UTC | -0.1527280131 |
+| 2020-01-10 23:00:01 +0000 UTC | -0.6674086005 |
+| 2020-01-10 23:00:02 +0000 UTC | -0.9656982091 |
+| 2020-01-10 23:00:03 +0000 UTC | -0.9508837004 |
+| 2020-01-10 23:00:04 +0000 UTC | -0.627768318 |
+| 2020-01-10 23:00:05 +0000 UTC | -0.1011143526 |
+| 2020-01-10 23:00:06 +0000 UTC | 0.4583234781 |
+| 2020-01-10 23:00:07 +0000 UTC | 0.8691610871 |
+| 2020-01-10 23:00:08 +0000 UTC | 0.9981943888 |
+| 2020-01-10 23:00:09 +0000 UTC | 0.8035874786 |
++-------------------------------+------------------+
Frame[5]
Name: bar
Dimensions: 2 Fields by 10 Rows
-+-------------------------------+----------------------+
-| Name: Time | Name: Value |
-| Labels: | Labels: state=NV |
-| Type: []time.Time | Type: []float64 |
-+-------------------------------+----------------------+
-| 2020-01-10 23:00:00 +0000 UTC | 1 |
-| 2020-01-10 23:00:01 +0000 UTC | 0.8378871813639024 |
-| 2020-01-10 23:00:02 +0000 UTC | 0.40410985738788996 |
-| 2020-01-10 23:00:03 +0000 UTC | -0.16069024262768705 |
-| 2020-01-10 23:00:04 +0000 UTC | -0.6733904463238785 |
-| 2020-01-10 23:00:05 +0000 UTC | -0.9677602034277024 |
-| 2020-01-10 23:00:06 +0000 UTC | -0.9483572918485101 |
-| 2020-01-10 23:00:07 +0000 UTC | -0.6214726329580013 |
-| 2020-01-10 23:00:08 +0000 UTC | -0.09309061359945539 |
-| 2020-01-10 23:00:09 +0000 UTC | 0.46547376927743367 |
-+-------------------------------+----------------------+
++-------------------------------+------------------+
+| Name: Time | Name: Value |
+| Labels: | Labels: state=NV |
+| Type: []time.Time | Type: []float64 |
++-------------------------------+------------------+
+| 2020-01-10 23:00:00 +0000 UTC | 1 |
+| 2020-01-10 23:00:01 +0000 UTC | 0.8378871813 |
+| 2020-01-10 23:00:02 +0000 UTC | 0.4041098573 |
+| 2020-01-10 23:00:03 +0000 UTC | -0.1606902426 |
+| 2020-01-10 23:00:04 +0000 UTC | -0.6733904463 |
+| 2020-01-10 23:00:05 +0000 UTC | -0.9677602034 |
+| 2020-01-10 23:00:06 +0000 UTC | -0.9483572918 |
+| 2020-01-10 23:00:07 +0000 UTC | -0.6214726329 |
+| 2020-01-10 23:00:08 +0000 UTC | -0.0930906135 |
+| 2020-01-10 23:00:09 +0000 UTC | 0.4654737692 |
++-------------------------------+------------------+
====== TEST DATA RESPONSE (arrow base64) ======
-FRAME=QVJST1cxAAD/////mAEAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEDAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAAD4/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAABj///8IAAAADAAAAAMAAABmb28ABAAAAG5hbWUAAAAAAgAAAKQAAAAEAAAAdv///xQAAABsAAAAbAAAAAAAAANsAAAAAgAAACwAAAAEAAAAaP///wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAjP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJDQSJ9AAAGAAAAbGFiZWxzAAAAAAAAiv///wAAAgAFAAAAVmFsdWUAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAATAAAAAAAAApMAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAAAAAAP////+4AAAAFAAAAAAAAAAMABYAFAATAAwABAAMAAAAoAAAAAAAAAAUAAAAAAAAAwMACgAYAAwACAAEAAoAAAAUAAAAWAAAAAoAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAAAAAAAIAAAAKAAAAAAAAAAAAAAAAAAAACgAAAAAAAAAAAAAAAAAAAABg5zW9qOgVACqCcb2o6BUA9BytvajoFQC+t+i9qOgVAIhSJL6o6BUAUu1fvqjoFQAciJu+qOgVAOYi176o6BUAsL0Sv6joFQB6WE6/qOgVRGvRTEFL6D95Gyc0crnvPz8V2cJ03uw/Kv6Ubkqn4D9o6656kcSev4Qa0D3FQ+K/qXtGvmSl7b9vQXczV2rvv7Xo8TTB/+a/meHxwtSAzL8QAAAADAAUABIADAAIAAQADAAAABAAAAAsAAAAOAAAAAAAAwABAAAAqAEAAAAAAADAAAAAAAAAAKAAAAAAAAAAAAAAAAAAAAAAAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAAD4/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAABj///8IAAAADAAAAAMAAABmb28ABAAAAG5hbWUAAAAAAgAAAKQAAAAEAAAAdv///xQAAABsAAAAbAAAAAAAAANsAAAAAgAAACwAAAAEAAAAaP///wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAjP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJDQSJ9AAAGAAAAbGFiZWxzAAAAAAAAiv///wAAAgAFAAAAVmFsdWUAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAATAAAAAAAAApMAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAwAEAAEFSUk9XMQ==
-FRAME=QVJST1cxAAD/////mAEAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEDAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAAD4/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAABj///8IAAAADAAAAAMAAABmb28ABAAAAG5hbWUAAAAAAgAAAKQAAAAEAAAAdv///xQAAABsAAAAbAAAAAAAAANsAAAAAgAAACwAAAAEAAAAaP///wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAjP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJPUiJ9AAAGAAAAbGFiZWxzAAAAAAAAiv///wAAAgAFAAAAVmFsdWUAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAATAAAAAAAAApMAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAAAAAAP////+4AAAAFAAAAAAAAAAMABYAFAATAAwABAAMAAAAoAAAAAAAAAAUAAAAAAAAAwMACgAYAAwACAAEAAoAAAAUAAAAWAAAAAoAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAAAAAAAIAAAAKAAAAAAAAAAAAAAAAAAAACgAAAAAAAAAAAAAAAAAAAABg5zW9qOgVACqCcb2o6BUA9BytvajoFQC+t+i9qOgVAIhSJL6o6BUAUu1fvqjoFQAciJu+qOgVAOYi176o6BUAsL0Sv6joFQB6WE6/qOgVRSQgwuSf7z8u86O7g9TnP2Flh3tintA/DfuM7qPP0788spCgp+jovwFKmrID1u+/f8YbM+5w7L8CW2JFXabfv4cFfxADwa4/zPDB5sQL4z8QAAAADAAUABIADAAIAAQADAAAABAAAAAsAAAAOAAAAAAAAwABAAAAqAEAAAAAAADAAAAAAAAAAKAAAAAAAAAAAAAAAAAAAAAAAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAAD4/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAABj///8IAAAADAAAAAMAAABmb28ABAAAAG5hbWUAAAAAAgAAAKQAAAAEAAAAdv///xQAAABsAAAAbAAAAAAAAANsAAAAAgAAACwAAAAEAAAAaP///wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAjP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJPUiJ9AAAGAAAAbGFiZWxzAAAAAAAAiv///wAAAgAFAAAAVmFsdWUAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAATAAAAAAAAApMAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAwAEAAEFSUk9XMQ==
-FRAME=QVJST1cxAAD/////mAEAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEDAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAAD4/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAABj///8IAAAADAAAAAMAAABmb28ABAAAAG5hbWUAAAAAAgAAAKQAAAAEAAAAdv///xQAAABsAAAAbAAAAAAAAANsAAAAAgAAACwAAAAEAAAAaP///wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAjP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJOViJ9AAAGAAAAbGFiZWxzAAAAAAAAiv///wAAAgAFAAAAVmFsdWUAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAATAAAAAAAAApMAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAAAAAAP////+4AAAAFAAAAAAAAAAMABYAFAATAAwABAAMAAAAoAAAAAAAAAAUAAAAAAAAAwMACgAYAAwACAAEAAoAAAAUAAAAWAAAAAoAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAAAAAAAIAAAAKAAAAAAAAAAAAAAAAAAAACgAAAAAAAAAAAAAAAAAAAABg5zW9qOgVACqCcb2o6BUA9BytvajoFQC+t+i9qOgVAIhSJL6o6BUAUu1fvqjoFQAciJu+qOgVAOYi176o6BUAsL0Sv6joFQB6WE6/qOgVAAAAAAAAAACaxoOujHfhPwqeJt5ORe0/GsmMRYuV7z/Hu99DPajnP5xsPm+xHtA/CSr4dhJN1L+sPmsb6BHpv0pQpmlt3O+/pV0b3WxS7L8QAAAADAAUABIADAAIAAQADAAAABAAAAAsAAAAOAAAAAAAAwABAAAAqAEAAAAAAADAAAAAAAAAAKAAAAAAAAAAAAAAAAAAAAAAAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAAD4/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAABj///8IAAAADAAAAAMAAABmb28ABAAAAG5hbWUAAAAAAgAAAKQAAAAEAAAAdv///xQAAABsAAAAbAAAAAAAAANsAAAAAgAAACwAAAAEAAAAaP///wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAjP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJOViJ9AAAGAAAAbGFiZWxzAAAAAAAAiv///wAAAgAFAAAAVmFsdWUAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAATAAAAAAAAApMAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAwAEAAEFSUk9XMQ==
-FRAME=QVJST1cxAAD/////mAEAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEDAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAAD4/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAABj///8IAAAADAAAAAMAAABiYXIABAAAAG5hbWUAAAAAAgAAAKQAAAAEAAAAdv///xQAAABsAAAAbAAAAAAAAANsAAAAAgAAACwAAAAEAAAAaP///wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAjP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJDQSJ9AAAGAAAAbGFiZWxzAAAAAAAAiv///wAAAgAFAAAAVmFsdWUAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAATAAAAAAAAApMAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAAAAAAP////+4AAAAFAAAAAAAAAAMABYAFAATAAwABAAMAAAAoAAAAAAAAAAUAAAAAAAAAwMACgAYAAwACAAEAAoAAAAUAAAAWAAAAAoAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAAAAAAAIAAAAKAAAAAAAAAAAAAAAAAAAACgAAAAAAAAAAAAAAAAAAAABg5zW9qOgVACqCcb2o6BUA9BytvajoFQC+t+i9qOgVAIhSJL6o6BUAUu1fvqjoFQAciJu+qOgVAOYi176o6BUAsL0Sv6joFQB6WE6/qOgVZfe/JfTT5D+ScrcTWMPAPyuGdcc+nNu/5JrgYzpT67+4Pj8gTfzvvzRIQWuERuq/pzbC1qUX2L8uaPSa5VrIP2qU/tzkP+Y/XDitFE8y7z8QAAAADAAUABIADAAIAAQADAAAABAAAAAsAAAAOAAAAAAAAwABAAAAqAEAAAAAAADAAAAAAAAAAKAAAAAAAAAAAAAAAAAAAAAAAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAAD4/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAABj///8IAAAADAAAAAMAAABiYXIABAAAAG5hbWUAAAAAAgAAAKQAAAAEAAAAdv///xQAAABsAAAAbAAAAAAAAANsAAAAAgAAACwAAAAEAAAAaP///wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAjP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJDQSJ9AAAGAAAAbGFiZWxzAAAAAAAAiv///wAAAgAFAAAAVmFsdWUAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAATAAAAAAAAApMAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAwAEAAEFSUk9XMQ==
-FRAME=QVJST1cxAAD/////mAEAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEDAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAAD4/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAABj///8IAAAADAAAAAMAAABiYXIABAAAAG5hbWUAAAAAAgAAAKQAAAAEAAAAdv///xQAAABsAAAAbAAAAAAAAANsAAAAAgAAACwAAAAEAAAAaP///wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAjP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJPUiJ9AAAGAAAAbGFiZWxzAAAAAAAAiv///wAAAgAFAAAAVmFsdWUAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAATAAAAAAAAApMAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAAAAAAP////+4AAAAFAAAAAAAAAAMABYAFAATAAwABAAMAAAAoAAAAAAAAAAUAAAAAAAAAwMACgAYAAwACAAEAAoAAAAUAAAAWAAAAAoAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAAAAAAAIAAAAKAAAAAAAAAAAAAAAAAAAACgAAAAAAAAAAAAAAAAAAAABg5zW9qOgVACqCcb2o6BUA9BytvajoFQC+t+i9qOgVAIhSJL6o6BUAUu1fvqjoFQAciJu+qOgVAOYi176o6BUAsL0Sv6joFQB6WE6/qOgVuLq7bpeMw7+66whIaVvlv6W6PO7/5u6/jrx0p6Nt7r/75GqVrRbkv4dItlWh4rm/J7du/ytV3T+xc43pKtDrP+Oh4Fs18e8/jqeDFv226T8QAAAADAAUABIADAAIAAQADAAAABAAAAAsAAAAOAAAAAAAAwABAAAAqAEAAAAAAADAAAAAAAAAAKAAAAAAAAAAAAAAAAAAAAAAAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAAD4/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAABj///8IAAAADAAAAAMAAABiYXIABAAAAG5hbWUAAAAAAgAAAKQAAAAEAAAAdv///xQAAABsAAAAbAAAAAAAAANsAAAAAgAAACwAAAAEAAAAaP///wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAjP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJPUiJ9AAAGAAAAbGFiZWxzAAAAAAAAiv///wAAAgAFAAAAVmFsdWUAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAATAAAAAAAAApMAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAwAEAAEFSUk9XMQ==
-FRAME=QVJST1cxAAD/////mAEAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEDAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAAD4/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAABj///8IAAAADAAAAAMAAABiYXIABAAAAG5hbWUAAAAAAgAAAKQAAAAEAAAAdv///xQAAABsAAAAbAAAAAAAAANsAAAAAgAAACwAAAAEAAAAaP///wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAjP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJOViJ9AAAGAAAAbGFiZWxzAAAAAAAAiv///wAAAgAFAAAAVmFsdWUAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAATAAAAAAAAApMAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAAAAAAP////+4AAAAFAAAAAAAAAAMABYAFAATAAwABAAMAAAAoAAAAAAAAAAUAAAAAAAAAwMACgAYAAwACAAEAAoAAAAUAAAAWAAAAAoAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAAAAAAAIAAAAKAAAAAAAAAAAAAAAAAAAACgAAAAAAAAAAAAAAAAAAAABg5zW9qOgVACqCcb2o6BUA9BytvajoFQC+t+i9qOgVAIhSJL6o6BUAUu1fvqjoFQAciJu+qOgVAOYi176o6BUAsL0Sv6joFQB6WE6/qOgVAAAAAAAA8D80QjbH+M/qP7Y4Xpfv3Nk/8KRvdH+RxL/+ywwfaozlvwj0Aj/k9+6/BjMtZPFY7r9vPD2TGuPjv2Jn+VTJ1Le/ulAMflLK3T8QAAAADAAUABIADAAIAAQADAAAABAAAAAsAAAAOAAAAAAAAwABAAAAqAEAAAAAAADAAAAAAAAAAKAAAAAAAAAAAAAAAAAAAAAAAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAAD4/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAABj///8IAAAADAAAAAMAAABiYXIABAAAAG5hbWUAAAAAAgAAAKQAAAAEAAAAdv///xQAAABsAAAAbAAAAAAAAANsAAAAAgAAACwAAAAEAAAAaP///wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAjP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJOViJ9AAAGAAAAbGFiZWxzAAAAAAAAiv///wAAAgAFAAAAVmFsdWUAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAATAAAAAAAAApMAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAwAEAAEFSUk9XMQ==
+FRAME=QVJST1cxAAD/////mAEAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEDAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAAD4/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAABj///8IAAAADAAAAAMAAABmb28ABAAAAG5hbWUAAAAAAgAAAKQAAAAEAAAAdv///xQAAABsAAAAbAAAAAAAAANsAAAAAgAAACwAAAAEAAAAaP///wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAjP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJDQSJ9AAAGAAAAbGFiZWxzAAAAAAAAiv///wAAAgAFAAAAVmFsdWUAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAATAAAAAAAAApMAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAAAAAAP////+4AAAAFAAAAAAAAAAMABYAFAATAAwABAAMAAAAoAAAAAAAAAAUAAAAAAAAAwMACgAYAAwACAAEAAoAAAAUAAAAWAAAAAoAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAAAAAAAIAAAAKAAAAAAAAAAAAAAAAAAAACgAAAAAAAAAAAAAAAAAAAABg5zW9qOgVACqCcb2o6BUA9BytvajoFQC+t+i9qOgVAIhSJL6o6BUAUu1fvqjoFQAciJu+qOgVAOYi176o6BUAsL0Sv6joFQB6WE6/qOgVgULMTEFL6D/apho0crnvPwec1sJ03uw/i0+Tbkqn4D8Xemt5kcSev0lMwz3FQ+K/0Zk5vmSl7b/odHQzV2rvvwte5zTB/+a/6K7XwtSAzL8QAAAADAAUABIADAAIAAQADAAAABAAAAAsAAAAOAAAAAAAAwABAAAAqAEAAAAAAADAAAAAAAAAAKAAAAAAAAAAAAAAAAAAAAAAAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAAD4/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAABj///8IAAAADAAAAAMAAABmb28ABAAAAG5hbWUAAAAAAgAAAKQAAAAEAAAAdv///xQAAABsAAAAbAAAAAAAAANsAAAAAgAAACwAAAAEAAAAaP///wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAjP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJDQSJ9AAAGAAAAbGFiZWxzAAAAAAAAiv///wAAAgAFAAAAVmFsdWUAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAATAAAAAAAAApMAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAwAEAAEFSUk9XMQ==
+FRAME=QVJST1cxAAD/////mAEAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEDAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAAD4/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAABj///8IAAAADAAAAAMAAABmb28ABAAAAG5hbWUAAAAAAgAAAKQAAAAEAAAAdv///xQAAABsAAAAbAAAAAAAAANsAAAAAgAAACwAAAAEAAAAaP///wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAjP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJPUiJ9AAAGAAAAbGFiZWxzAAAAAAAAiv///wAAAgAFAAAAVmFsdWUAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAATAAAAAAAAApMAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAAAAAAP////+4AAAAFAAAAAAAAAAMABYAFAATAAwABAAMAAAAoAAAAAAAAAAUAAAAAAAAAwMACgAYAAwACAAEAAoAAAAUAAAAWAAAAAoAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAAAAAAAIAAAAKAAAAAAAAAAAAAAAAAAAACgAAAAAAAAAAAAAAAAAAAABg5zW9qOgVACqCcb2o6BUA9BytvajoFQC+t+i9qOgVAIhSJL6o6BUAUu1fvqjoFQAciJu+qOgVAOYi176o6BUAsL0Sv6joFQB6WE6/qOgVAb0ZwuSf7z9qeqC7g9TnP+eweHtintA/TgqC7qPP07+PgIWgp+jovwoOkrID1u+/WDAWM+5w7L/TJUtFXabfv+Ll4A8Dwa4/knG85sQL4z8QAAAADAAUABIADAAIAAQADAAAABAAAAAsAAAAOAAAAAAAAwABAAAAqAEAAAAAAADAAAAAAAAAAKAAAAAAAAAAAAAAAAAAAAAAAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAAD4/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAABj///8IAAAADAAAAAMAAABmb28ABAAAAG5hbWUAAAAAAgAAAKQAAAAEAAAAdv///xQAAABsAAAAbAAAAAAAAANsAAAAAgAAACwAAAAEAAAAaP///wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAjP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJPUiJ9AAAGAAAAbGFiZWxzAAAAAAAAiv///wAAAgAFAAAAVmFsdWUAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAATAAAAAAAAApMAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAwAEAAEFSUk9XMQ==
+FRAME=QVJST1cxAAD/////mAEAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEDAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAAD4/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAABj///8IAAAADAAAAAMAAABmb28ABAAAAG5hbWUAAAAAAgAAAKQAAAAEAAAAdv///xQAAABsAAAAbAAAAAAAAANsAAAAAgAAACwAAAAEAAAAaP///wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAjP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJOViJ9AAAGAAAAbGFiZWxzAAAAAAAAiv///wAAAgAFAAAAVmFsdWUAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAATAAAAAAAAApMAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAAAAAAP////+4AAAAFAAAAAAAAAAMABYAFAATAAwABAAMAAAAoAAAAAAAAAAUAAAAAAAAAwMACgAYAAwACAAEAAoAAAAUAAAAWAAAAAoAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAAAAAAAIAAAAKAAAAAAAAAAAAAAAAAAAACgAAAAAAAAAAAAAAAAAAAABg5zW9qOgVACqCcb2o6BUA9BytvajoFQC+t+i9qOgVAIhSJL6o6BUAUu1fvqjoFQAciJu+qOgVAOYi176o6BUAsL0Sv6joFQB6WE6/qOgVAAAAAAAAAAAkFX2ujHfhP4/gH95ORe0/9ZCCRYuV7z+UH9NDPajnP851KG+xHtA/eGvmdhJN1L8vkWob6BHpv2Y1mmlt3O+/ZSAQ3WxS7L8QAAAADAAUABIADAAIAAQADAAAABAAAAAsAAAAOAAAAAAAAwABAAAAqAEAAAAAAADAAAAAAAAAAKAAAAAAAAAAAAAAAAAAAAAAAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAAD4/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAABj///8IAAAADAAAAAMAAABmb28ABAAAAG5hbWUAAAAAAgAAAKQAAAAEAAAAdv///xQAAABsAAAAbAAAAAAAAANsAAAAAgAAACwAAAAEAAAAaP///wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAjP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJOViJ9AAAGAAAAbGFiZWxzAAAAAAAAiv///wAAAgAFAAAAVmFsdWUAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAATAAAAAAAAApMAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAwAEAAEFSUk9XMQ==
+FRAME=QVJST1cxAAD/////mAEAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEDAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAAD4/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAABj///8IAAAADAAAAAMAAABiYXIABAAAAG5hbWUAAAAAAgAAAKQAAAAEAAAAdv///xQAAABsAAAAbAAAAAAAAANsAAAAAgAAACwAAAAEAAAAaP///wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAjP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJDQSJ9AAAGAAAAbGFiZWxzAAAAAAAAiv///wAAAgAFAAAAVmFsdWUAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAATAAAAAAAAApMAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAAAAAAP////+4AAAAFAAAAAAAAAAMABYAFAATAAwABAAMAAAAoAAAAAAAAAAUAAAAAAAAAwMACgAYAAwACAAEAAoAAAAUAAAAWAAAAAoAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAAAAAAAIAAAAKAAAAAAAAAAAAAAAAAAAACgAAAAAAAAAAAAAAAAAAAABg5zW9qOgVACqCcb2o6BUA9BytvajoFQC+t+i9qOgVAIhSJL6o6BUAUu1fvqjoFQAciJu+qOgVAOYi176o6BUAsL0Sv6joFQB6WE6/qOgVDGW9JfTT5D/iPqUTWMPAP5JuXcc+nNu/6KbcYzpT67/ozDQgTfzvv+tSOWuERuq/2Eev1qUX2L96Leya5VrIP5yv8dzkP+Y/vRWjFE8y7z8QAAAADAAUABIADAAIAAQADAAAABAAAAAsAAAAOAAAAAAAAwABAAAAqAEAAAAAAADAAAAAAAAAAKAAAAAAAAAAAAAAAAAAAAAAAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAAD4/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAABj///8IAAAADAAAAAMAAABiYXIABAAAAG5hbWUAAAAAAgAAAKQAAAAEAAAAdv///xQAAABsAAAAbAAAAAAAAANsAAAAAgAAACwAAAAEAAAAaP///wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAjP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJDQSJ9AAAGAAAAbGFiZWxzAAAAAAAAiv///wAAAgAFAAAAVmFsdWUAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAATAAAAAAAAApMAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAwAEAAEFSUk9XMQ==
+FRAME=QVJST1cxAAD/////mAEAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEDAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAAD4/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAABj///8IAAAADAAAAAMAAABiYXIABAAAAG5hbWUAAAAAAgAAAKQAAAAEAAAAdv///xQAAABsAAAAbAAAAAAAAANsAAAAAgAAACwAAAAEAAAAaP///wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAjP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJPUiJ9AAAGAAAAbGFiZWxzAAAAAAAAiv///wAAAgAFAAAAVmFsdWUAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAATAAAAAAAAApMAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAAAAAAP////+4AAAAFAAAAAAAAAAMABYAFAATAAwABAAMAAAAoAAAAAAAAAAUAAAAAAAAAwMACgAYAAwACAAEAAoAAAAUAAAAWAAAAAoAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAAAAAAAIAAAAKAAAAAAAAAAAAAAAAAAAACgAAAAAAAAAAAAAAAAAAAABg5zW9qOgVACqCcb2o6BUA9BytvajoFQC+t+i9qOgVAIhSJL6o6BUAUu1fvqjoFQAciJu+qOgVAOYi176o6BUAsL0Sv6joFQB6WE6/qOgVokm5bpeMw7+g7gZIaVvlvxd9PO7/5u6/DY9wp6Nt7r82zGiVrRbkvw++klWh4rm/F2xb/ytV3T8enIHpKtDrP3BL3ls18e8/ufyBFv226T8QAAAADAAUABIADAAIAAQADAAAABAAAAAsAAAAOAAAAAAAAwABAAAAqAEAAAAAAADAAAAAAAAAAKAAAAAAAAAAAAAAAAAAAAAAAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAAD4/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAABj///8IAAAADAAAAAMAAABiYXIABAAAAG5hbWUAAAAAAgAAAKQAAAAEAAAAdv///xQAAABsAAAAbAAAAAAAAANsAAAAAgAAACwAAAAEAAAAaP///wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAjP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJPUiJ9AAAGAAAAbGFiZWxzAAAAAAAAiv///wAAAgAFAAAAVmFsdWUAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAATAAAAAAAAApMAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAwAEAAEFSUk9XMQ==
+FRAME=QVJST1cxAAD/////mAEAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEDAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAAD4/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAABj///8IAAAADAAAAAMAAABiYXIABAAAAG5hbWUAAAAAAgAAAKQAAAAEAAAAdv///xQAAABsAAAAbAAAAAAAAANsAAAAAgAAACwAAAAEAAAAaP///wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAjP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJOViJ9AAAGAAAAbGFiZWxzAAAAAAAAiv///wAAAgAFAAAAVmFsdWUAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAATAAAAAAAAApMAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAAAAAAP////+4AAAAFAAAAAAAAAAMABYAFAATAAwABAAMAAAAoAAAAAAAAAAUAAAAAAAAAwMACgAYAAwACAAEAAoAAAAUAAAAWAAAAAoAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAAAAAAAIAAAAKAAAAAAAAAAAAAAAAAAAACgAAAAAAAAAAAAAAAAAAAABg5zW9qOgVACqCcb2o6BUA9BytvajoFQC+t+i9qOgVAIhSJL6o6BUAUu1fvqjoFQAciJu+qOgVAOYi176o6BUAsL0Sv6joFQB6WE6/qOgVAAAAAAAA8D/WeS3H+M/qPwEQRpfv3Nk/VWxgdH+RxL/Ygwkfaozlv1cl/z7k9+6/OYgmZPFY7r+yQzWTGuPjvy4NjFTJ1Le/0Qf3fVLK3T8QAAAADAAUABIADAAIAAQADAAAABAAAAAsAAAAOAAAAAAAAwABAAAAqAEAAAAAAADAAAAAAAAAAKAAAAAAAAAAAAAAAAAAAAAAAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAAD4/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAABj///8IAAAADAAAAAMAAABiYXIABAAAAG5hbWUAAAAAAgAAAKQAAAAEAAAAdv///xQAAABsAAAAbAAAAAAAAANsAAAAAgAAACwAAAAEAAAAaP///wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAjP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJOViJ9AAAGAAAAbGFiZWxzAAAAAAAAiv///wAAAgAFAAAAVmFsdWUAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAATAAAAAAAAApMAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAwAEAAEFSUk9XMQ==
diff --git a/pkg/tsdb/testdatasource/testdata/usa-values-as-fields.txt b/pkg/tsdb/testdatasource/testdata/usa-values-as-fields.txt
index 8655d43df6d..7f3359e2f8d 100644
--- a/pkg/tsdb/testdatasource/testdata/usa-values-as-fields.txt
+++ b/pkg/tsdb/testdatasource/testdata/usa-values-as-fields.txt
@@ -3,28 +3,28 @@
Frame[0]
Name: foo
Dimensions: 3 Fields by 1 Rows
-+--------------------+--------------------+----------------------+
-| Name: CA | Name: OR | Name: NV |
-| Labels: | Labels: | Labels: |
-| Type: []float64 | Type: []float64 | Type: []float64 |
-+--------------------+--------------------+----------------------+
-| 0.3455559195129913 | 0.9373322369907638 | -0.48750602508751073 |
-+--------------------+--------------------+----------------------+
++-----------------+-----------------+-----------------+
+| Name: CA | Name: OR | Name: NV |
+| Labels: | Labels: | Labels: |
+| Type: []float64 | Type: []float64 | Type: []float64 |
++-----------------+-----------------+-----------------+
+| 0.3455559195 | 0.9373322369 | -0.487506025 |
++-----------------+-----------------+-----------------+
Frame[1]
Name: bar
Dimensions: 3 Fields by 1 Rows
-+--------------------+--------------------+-------------------+
-| Name: CA | Name: OR | Name: NV |
-| Labels: | Labels: | Labels: |
-| Type: []float64 | Type: []float64 | Type: []float64 |
-+--------------------+--------------------+-------------------+
-| 0.9383981598924473 | 0.3484369060502785 | 0.873119622676856 |
-+--------------------+--------------------+-------------------+
++-----------------+-----------------+-----------------+
+| Name: CA | Name: OR | Name: NV |
+| Labels: | Labels: | Labels: |
+| Type: []float64 | Type: []float64 | Type: []float64 |
++-----------------+-----------------+-----------------+
+| 0.9383981598 | 0.348436906 | 0.8731196226 |
++-----------------+-----------------+-----------------+
====== TEST DATA RESPONSE (arrow base64) ======
-FRAME=QVJST1cxAAD/////sAEAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEDAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAADU/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAAPT+//8IAAAADAAAAAMAAABmb28ABAAAAG5hbWUAAAAAAwAAAMgAAABcAAAABAAAAFb///8UAAAAOAAAADgAAAAAAAADOAAAAAEAAAAEAAAARP///wgAAAAMAAAAAgAAAE5WAAAEAAAAbmFtZQAAAAAAAAAAOv///wAAAgACAAAATlYAAKr///8UAAAAOAAAADgAAAAAAAADOAAAAAEAAAAEAAAAmP///wgAAAAMAAAAAgAAAE9SAAAEAAAAbmFtZQAAAAAAAAAAjv///wAAAgACAAAAT1IAAAAAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABAAAAASAAAAAAAAANIAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAADAAAAAIAAABDQQAABAAAAG5hbWUAAAAAAAAAAAAABgAIAAYABgAAAAAAAgACAAAAQ0EAAP/////oAAAAFAAAAAAAAAAMABYAFAATAAwABAAMAAAAGAAAAAAAAAAUAAAAAAAAAwMACgAYAAwACAAEAAoAAAAUAAAAeAAAAAEAAAAAAAAAAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAgAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAACAAAAAAAAAAAAAAAAwAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAACPXT5OWHdY/S5TrLKD+7T/kpJZ4TDPfvxAAAAAMABQAEgAMAAgABAAMAAAAEAAAACwAAAA8AAAAAAADAAEAAADAAQAAAAAAAPAAAAAAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAADU/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAAPT+//8IAAAADAAAAAMAAABmb28ABAAAAG5hbWUAAAAAAwAAAMgAAABcAAAABAAAAFb///8UAAAAOAAAADgAAAAAAAADOAAAAAEAAAAEAAAARP///wgAAAAMAAAAAgAAAE5WAAAEAAAAbmFtZQAAAAAAAAAAOv///wAAAgACAAAATlYAAKr///8UAAAAOAAAADgAAAAAAAADOAAAAAEAAAAEAAAAmP///wgAAAAMAAAAAgAAAE9SAAAEAAAAbmFtZQAAAAAAAAAAjv///wAAAgACAAAAT1IAAAAAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABAAAAASAAAAAAAAANIAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAADAAAAAIAAABDQQAABAAAAG5hbWUAAAAAAAAAAAAABgAIAAYABgAAAAAAAgACAAAAQ0EAAOABAABBUlJPVzE=
-FRAME=QVJST1cxAAD/////sAEAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEDAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAADU/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAAPT+//8IAAAADAAAAAMAAABiYXIABAAAAG5hbWUAAAAAAwAAAMgAAABcAAAABAAAAFb///8UAAAAOAAAADgAAAAAAAADOAAAAAEAAAAEAAAARP///wgAAAAMAAAAAgAAAE5WAAAEAAAAbmFtZQAAAAAAAAAAOv///wAAAgACAAAATlYAAKr///8UAAAAOAAAADgAAAAAAAADOAAAAAEAAAAEAAAAmP///wgAAAAMAAAAAgAAAE9SAAAEAAAAbmFtZQAAAAAAAAAAjv///wAAAgACAAAAT1IAAAAAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABAAAAASAAAAAAAAANIAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAADAAAAAIAAABDQQAABAAAAG5hbWUAAAAAAAAAAAAABgAIAAYABgAAAAAAAgACAAAAQ0EAAP/////oAAAAFAAAAAAAAAAMABYAFAATAAwABAAMAAAAGAAAAAAAAAAUAAAAAAAAAwMACgAYAAwACAAEAAoAAAAUAAAAeAAAAAEAAAAAAAAAAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAgAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAACAAAAAAAAAAAAAAAAwAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAACKr65NbB+4/yyQNT8pM1j8akxyQmPDrPxAAAAAMABQAEgAMAAgABAAMAAAAEAAAACwAAAA8AAAAAAADAAEAAADAAQAAAAAAAPAAAAAAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAADU/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAAPT+//8IAAAADAAAAAMAAABiYXIABAAAAG5hbWUAAAAAAwAAAMgAAABcAAAABAAAAFb///8UAAAAOAAAADgAAAAAAAADOAAAAAEAAAAEAAAARP///wgAAAAMAAAAAgAAAE5WAAAEAAAAbmFtZQAAAAAAAAAAOv///wAAAgACAAAATlYAAKr///8UAAAAOAAAADgAAAAAAAADOAAAAAEAAAAEAAAAmP///wgAAAAMAAAAAgAAAE9SAAAEAAAAbmFtZQAAAAAAAAAAjv///wAAAgACAAAAT1IAAAAAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABAAAAASAAAAAAAAANIAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAADAAAAAIAAABDQQAABAAAAG5hbWUAAAAAAAAAAAAABgAIAAYABgAAAAAAAgACAAAAQ0EAAOABAABBUlJPVzE=
+FRAME=QVJST1cxAAD/////sAEAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEDAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAADU/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAAPT+//8IAAAADAAAAAMAAABmb28ABAAAAG5hbWUAAAAAAwAAAMgAAABcAAAABAAAAFb///8UAAAAOAAAADgAAAAAAAADOAAAAAEAAAAEAAAARP///wgAAAAMAAAAAgAAAE5WAAAEAAAAbmFtZQAAAAAAAAAAOv///wAAAgACAAAATlYAAKr///8UAAAAOAAAADgAAAAAAAADOAAAAAEAAAAEAAAAmP///wgAAAAMAAAAAgAAAE9SAAAEAAAAbmFtZQAAAAAAAAAAjv///wAAAgACAAAAT1IAAAAAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABAAAAASAAAAAAAAANIAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAADAAAAAIAAABDQQAABAAAAG5hbWUAAAAAAAAAAAAABgAIAAYABgAAAAAAAgACAAAAQ0EAAP/////oAAAAFAAAAAAAAAAMABYAFAATAAwABAAMAAAAGAAAAAAAAAAUAAAAAAAAAwMACgAYAAwACAAEAAoAAAAUAAAAeAAAAAEAAAAAAAAAAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAgAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAACAAAAAAAAAAAAAAAAwAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAPRETJOWHdY/0xrfLKD+7T/fln54TDPfvxAAAAAMABQAEgAMAAgABAAMAAAAEAAAACwAAAA8AAAAAAADAAEAAADAAQAAAAAAAPAAAAAAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAADU/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAAPT+//8IAAAADAAAAAMAAABmb28ABAAAAG5hbWUAAAAAAwAAAMgAAABcAAAABAAAAFb///8UAAAAOAAAADgAAAAAAAADOAAAAAEAAAAEAAAARP///wgAAAAMAAAAAgAAAE5WAAAEAAAAbmFtZQAAAAAAAAAAOv///wAAAgACAAAATlYAAKr///8UAAAAOAAAADgAAAAAAAADOAAAAAEAAAAEAAAAmP///wgAAAAMAAAAAgAAAE9SAAAEAAAAbmFtZQAAAAAAAAAAjv///wAAAgACAAAAT1IAAAAAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABAAAAASAAAAAAAAANIAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAADAAAAAIAAABDQQAABAAAAG5hbWUAAAAAAAAAAAAABgAIAAYABgAAAAAAAgACAAAAQ0EAAOABAABBUlJPVzE=
+FRAME=QVJST1cxAAD/////sAEAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEDAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAADU/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAAPT+//8IAAAADAAAAAMAAABiYXIABAAAAG5hbWUAAAAAAwAAAMgAAABcAAAABAAAAFb///8UAAAAOAAAADgAAAAAAAADOAAAAAEAAAAEAAAARP///wgAAAAMAAAAAgAAAE5WAAAEAAAAbmFtZQAAAAAAAAAAOv///wAAAgACAAAATlYAAKr///8UAAAAOAAAADgAAAAAAAADOAAAAAEAAAAEAAAAmP///wgAAAAMAAAAAgAAAE9SAAAEAAAAbmFtZQAAAAAAAAAAjv///wAAAgACAAAAT1IAAAAAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABAAAAASAAAAAAAAANIAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAADAAAAAIAAABDQQAABAAAAG5hbWUAAAAAAAAAAAAABgAIAAYABgAAAAAAAgACAAAAQ0EAAP/////oAAAAFAAAAAAAAAAMABYAFAATAAwABAAMAAAAGAAAAAAAAAAUAAAAAAAAAwMACgAYAAwACAAEAAoAAAAUAAAAeAAAAAEAAAAAAAAAAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAgAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAACAAAAAAAAAAAAAAAAwAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAG723pNbB+4/wlL/TspM1j/4AhKQmPDrPxAAAAAMABQAEgAMAAgABAAMAAAAEAAAACwAAAA8AAAAAAADAAEAAADAAQAAAAAAAPAAAAAAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAADU/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAAPT+//8IAAAADAAAAAMAAABiYXIABAAAAG5hbWUAAAAAAwAAAMgAAABcAAAABAAAAFb///8UAAAAOAAAADgAAAAAAAADOAAAAAEAAAAEAAAARP///wgAAAAMAAAAAgAAAE5WAAAEAAAAbmFtZQAAAAAAAAAAOv///wAAAgACAAAATlYAAKr///8UAAAAOAAAADgAAAAAAAADOAAAAAEAAAAEAAAAmP///wgAAAAMAAAAAgAAAE9SAAAEAAAAbmFtZQAAAAAAAAAAjv///wAAAgACAAAAT1IAAAAAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABAAAAASAAAAAAAAANIAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAADAAAAAIAAABDQQAABAAAAG5hbWUAAAAAAAAAAAAABgAIAAYABgAAAAAAAgACAAAAQ0EAAOABAABBUlJPVzE=
diff --git a/pkg/tsdb/testdatasource/testdata/usa-values-as-labeled-fields.txt b/pkg/tsdb/testdatasource/testdata/usa-values-as-labeled-fields.txt
index 188181b1b47..c2503b1f5c2 100644
--- a/pkg/tsdb/testdatasource/testdata/usa-values-as-labeled-fields.txt
+++ b/pkg/tsdb/testdatasource/testdata/usa-values-as-labeled-fields.txt
@@ -3,28 +3,28 @@
Frame[0]
Name: foo
Dimensions: 3 Fields by 1 Rows
-+--------------------+--------------------+----------------------+
-| Name: | Name: | Name: |
-| Labels: state=CA | Labels: state=OR | Labels: state=NV |
-| Type: []float64 | Type: []float64 | Type: []float64 |
-+--------------------+--------------------+----------------------+
-| 0.3455559195129913 | 0.9373322369907638 | -0.48750602508751073 |
-+--------------------+--------------------+----------------------+
++------------------+------------------+------------------+
+| Name: | Name: | Name: |
+| Labels: state=CA | Labels: state=OR | Labels: state=NV |
+| Type: []float64 | Type: []float64 | Type: []float64 |
++------------------+------------------+------------------+
+| 0.3455559195 | 0.9373322369 | -0.487506025 |
++------------------+------------------+------------------+
Frame[1]
Name: bar
Dimensions: 3 Fields by 1 Rows
-+--------------------+--------------------+-------------------+
-| Name: | Name: | Name: |
-| Labels: state=CA | Labels: state=OR | Labels: state=NV |
-| Type: []float64 | Type: []float64 | Type: []float64 |
-+--------------------+--------------------+-------------------+
-| 0.9383981598924473 | 0.3484369060502785 | 0.873119622676856 |
-+--------------------+--------------------+-------------------+
++------------------+------------------+------------------+
+| Name: | Name: | Name: |
+| Labels: state=CA | Labels: state=OR | Labels: state=NV |
+| Type: []float64 | Type: []float64 | Type: []float64 |
++------------------+------------------+------------------+
+| 0.9383981598 | 0.348436906 | 0.8731196226 |
++------------------+------------------+------------------+
====== TEST DATA RESPONSE (arrow base64) ======
-FRAME=QVJST1cxAAD/////QAIAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEDAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAABU/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAAHT+//8IAAAADAAAAAMAAABmb28ABAAAAG5hbWUAAAAAAwAAACQBAACMAAAABAAAAPr+//8UAAAAaAAAAGgAAAAAAAADaAAAAAIAAAAoAAAABAAAAMj+//8IAAAADAAAAAAAAAAAAAAABAAAAG5hbWUAAAAA6P7//wgAAAAYAAAADgAAAHsic3RhdGUiOiJOViJ9AAAGAAAAbGFiZWxzAAAAAAAA3v7//wAAAgAAAAAAAAAAAH7///8UAAAAaAAAAGgAAAAAAAADaAAAAAIAAAAoAAAABAAAAEz///8IAAAADAAAAAAAAAAAAAAABAAAAG5hbWUAAAAAbP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJPUiJ9AAAGAAAAbGFiZWxzAAAAAAAAYv///wAAAgAAAAAAAAASABgAFAAAABMADAAAAAgABAASAAAAFAAAAHAAAAB4AAAAAAAAA3gAAAACAAAAMAAAAAQAAADg////CAAAAAwAAAAAAAAAAAAAAAQAAABuYW1lAAAAAAgADAAIAAQACAAAAAgAAAAYAAAADgAAAHsic3RhdGUiOiJDQSJ9AAAGAAAAbGFiZWxzAAAAAAAAAAAGAAgABgAGAAAAAAACAAAAAAAAAAAAAAAAAP/////oAAAAFAAAAAAAAAAMABYAFAATAAwABAAMAAAAGAAAAAAAAAAUAAAAAAAAAwMACgAYAAwACAAEAAoAAAAUAAAAeAAAAAEAAAAAAAAAAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAgAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAACAAAAAAAAAAAAAAAAwAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAACPXT5OWHdY/S5TrLKD+7T/kpJZ4TDPfvxAAAAAMABQAEgAMAAgABAAMAAAAEAAAACwAAAA4AAAAAAADAAEAAABQAgAAAAAAAPAAAAAAAAAAGAAAAAAAAAAAAAAAAAAAAAAACgAMAAAACAAEAAoAAAAIAAAAUAAAAAIAAAAoAAAABAAAAFT+//8IAAAADAAAAAAAAAAAAAAABQAAAHJlZklkAAAAdP7//wgAAAAMAAAAAwAAAGZvbwAEAAAAbmFtZQAAAAADAAAAJAEAAIwAAAAEAAAA+v7//xQAAABoAAAAaAAAAAAAAANoAAAAAgAAACgAAAAEAAAAyP7//wgAAAAMAAAAAAAAAAAAAAAEAAAAbmFtZQAAAADo/v//CAAAABgAAAAOAAAAeyJzdGF0ZSI6Ik5WIn0AAAYAAABsYWJlbHMAAAAAAADe/v//AAACAAAAAAAAAAAAfv///xQAAABoAAAAaAAAAAAAAANoAAAAAgAAACgAAAAEAAAATP///wgAAAAMAAAAAAAAAAAAAAAEAAAAbmFtZQAAAABs////CAAAABgAAAAOAAAAeyJzdGF0ZSI6Ik9SIn0AAAYAAABsYWJlbHMAAAAAAABi////AAACAAAAAAAAABIAGAAUAAAAEwAMAAAACAAEABIAAAAUAAAAcAAAAHgAAAAAAAADeAAAAAIAAAAwAAAABAAAAOD///8IAAAADAAAAAAAAAAAAAAABAAAAG5hbWUAAAAACAAMAAgABAAIAAAACAAAABgAAAAOAAAAeyJzdGF0ZSI6IkNBIn0AAAYAAABsYWJlbHMAAAAAAAAAAAYACAAGAAYAAAAAAAIAAAAAAAAAAABoAgAAQVJST1cx
-FRAME=QVJST1cxAAD/////QAIAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEDAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAABU/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAAHT+//8IAAAADAAAAAMAAABiYXIABAAAAG5hbWUAAAAAAwAAACQBAACMAAAABAAAAPr+//8UAAAAaAAAAGgAAAAAAAADaAAAAAIAAAAoAAAABAAAAMj+//8IAAAADAAAAAAAAAAAAAAABAAAAG5hbWUAAAAA6P7//wgAAAAYAAAADgAAAHsic3RhdGUiOiJOViJ9AAAGAAAAbGFiZWxzAAAAAAAA3v7//wAAAgAAAAAAAAAAAH7///8UAAAAaAAAAGgAAAAAAAADaAAAAAIAAAAoAAAABAAAAEz///8IAAAADAAAAAAAAAAAAAAABAAAAG5hbWUAAAAAbP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJPUiJ9AAAGAAAAbGFiZWxzAAAAAAAAYv///wAAAgAAAAAAAAASABgAFAAAABMADAAAAAgABAASAAAAFAAAAHAAAAB4AAAAAAAAA3gAAAACAAAAMAAAAAQAAADg////CAAAAAwAAAAAAAAAAAAAAAQAAABuYW1lAAAAAAgADAAIAAQACAAAAAgAAAAYAAAADgAAAHsic3RhdGUiOiJDQSJ9AAAGAAAAbGFiZWxzAAAAAAAAAAAGAAgABgAGAAAAAAACAAAAAAAAAAAAAAAAAP/////oAAAAFAAAAAAAAAAMABYAFAATAAwABAAMAAAAGAAAAAAAAAAUAAAAAAAAAwMACgAYAAwACAAEAAoAAAAUAAAAeAAAAAEAAAAAAAAAAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAgAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAACAAAAAAAAAAAAAAAAwAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAACKr65NbB+4/yyQNT8pM1j8akxyQmPDrPxAAAAAMABQAEgAMAAgABAAMAAAAEAAAACwAAAA4AAAAAAADAAEAAABQAgAAAAAAAPAAAAAAAAAAGAAAAAAAAAAAAAAAAAAAAAAACgAMAAAACAAEAAoAAAAIAAAAUAAAAAIAAAAoAAAABAAAAFT+//8IAAAADAAAAAAAAAAAAAAABQAAAHJlZklkAAAAdP7//wgAAAAMAAAAAwAAAGJhcgAEAAAAbmFtZQAAAAADAAAAJAEAAIwAAAAEAAAA+v7//xQAAABoAAAAaAAAAAAAAANoAAAAAgAAACgAAAAEAAAAyP7//wgAAAAMAAAAAAAAAAAAAAAEAAAAbmFtZQAAAADo/v//CAAAABgAAAAOAAAAeyJzdGF0ZSI6Ik5WIn0AAAYAAABsYWJlbHMAAAAAAADe/v//AAACAAAAAAAAAAAAfv///xQAAABoAAAAaAAAAAAAAANoAAAAAgAAACgAAAAEAAAATP///wgAAAAMAAAAAAAAAAAAAAAEAAAAbmFtZQAAAABs////CAAAABgAAAAOAAAAeyJzdGF0ZSI6Ik9SIn0AAAYAAABsYWJlbHMAAAAAAABi////AAACAAAAAAAAABIAGAAUAAAAEwAMAAAACAAEABIAAAAUAAAAcAAAAHgAAAAAAAADeAAAAAIAAAAwAAAABAAAAOD///8IAAAADAAAAAAAAAAAAAAABAAAAG5hbWUAAAAACAAMAAgABAAIAAAACAAAABgAAAAOAAAAeyJzdGF0ZSI6IkNBIn0AAAYAAABsYWJlbHMAAAAAAAAAAAYACAAGAAYAAAAAAAIAAAAAAAAAAABoAgAAQVJST1cx
+FRAME=QVJST1cxAAD/////QAIAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEDAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAABU/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAAHT+//8IAAAADAAAAAMAAABmb28ABAAAAG5hbWUAAAAAAwAAACQBAACMAAAABAAAAPr+//8UAAAAaAAAAGgAAAAAAAADaAAAAAIAAAAoAAAABAAAAMj+//8IAAAADAAAAAAAAAAAAAAABAAAAG5hbWUAAAAA6P7//wgAAAAYAAAADgAAAHsic3RhdGUiOiJOViJ9AAAGAAAAbGFiZWxzAAAAAAAA3v7//wAAAgAAAAAAAAAAAH7///8UAAAAaAAAAGgAAAAAAAADaAAAAAIAAAAoAAAABAAAAEz///8IAAAADAAAAAAAAAAAAAAABAAAAG5hbWUAAAAAbP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJPUiJ9AAAGAAAAbGFiZWxzAAAAAAAAYv///wAAAgAAAAAAAAASABgAFAAAABMADAAAAAgABAASAAAAFAAAAHAAAAB4AAAAAAAAA3gAAAACAAAAMAAAAAQAAADg////CAAAAAwAAAAAAAAAAAAAAAQAAABuYW1lAAAAAAgADAAIAAQACAAAAAgAAAAYAAAADgAAAHsic3RhdGUiOiJDQSJ9AAAGAAAAbGFiZWxzAAAAAAAAAAAGAAgABgAGAAAAAAACAAAAAAAAAAAAAAAAAP/////oAAAAFAAAAAAAAAAMABYAFAATAAwABAAMAAAAGAAAAAAAAAAUAAAAAAAAAwMACgAYAAwACAAEAAoAAAAUAAAAeAAAAAEAAAAAAAAAAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAgAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAACAAAAAAAAAAAAAAAAwAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAPRETJOWHdY/0xrfLKD+7T/fln54TDPfvxAAAAAMABQAEgAMAAgABAAMAAAAEAAAACwAAAA4AAAAAAADAAEAAABQAgAAAAAAAPAAAAAAAAAAGAAAAAAAAAAAAAAAAAAAAAAACgAMAAAACAAEAAoAAAAIAAAAUAAAAAIAAAAoAAAABAAAAFT+//8IAAAADAAAAAAAAAAAAAAABQAAAHJlZklkAAAAdP7//wgAAAAMAAAAAwAAAGZvbwAEAAAAbmFtZQAAAAADAAAAJAEAAIwAAAAEAAAA+v7//xQAAABoAAAAaAAAAAAAAANoAAAAAgAAACgAAAAEAAAAyP7//wgAAAAMAAAAAAAAAAAAAAAEAAAAbmFtZQAAAADo/v//CAAAABgAAAAOAAAAeyJzdGF0ZSI6Ik5WIn0AAAYAAABsYWJlbHMAAAAAAADe/v//AAACAAAAAAAAAAAAfv///xQAAABoAAAAaAAAAAAAAANoAAAAAgAAACgAAAAEAAAATP///wgAAAAMAAAAAAAAAAAAAAAEAAAAbmFtZQAAAABs////CAAAABgAAAAOAAAAeyJzdGF0ZSI6Ik9SIn0AAAYAAABsYWJlbHMAAAAAAABi////AAACAAAAAAAAABIAGAAUAAAAEwAMAAAACAAEABIAAAAUAAAAcAAAAHgAAAAAAAADeAAAAAIAAAAwAAAABAAAAOD///8IAAAADAAAAAAAAAAAAAAABAAAAG5hbWUAAAAACAAMAAgABAAIAAAACAAAABgAAAAOAAAAeyJzdGF0ZSI6IkNBIn0AAAYAAABsYWJlbHMAAAAAAAAAAAYACAAGAAYAAAAAAAIAAAAAAAAAAABoAgAAQVJST1cx
+FRAME=QVJST1cxAAD/////QAIAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEDAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAABU/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAAHT+//8IAAAADAAAAAMAAABiYXIABAAAAG5hbWUAAAAAAwAAACQBAACMAAAABAAAAPr+//8UAAAAaAAAAGgAAAAAAAADaAAAAAIAAAAoAAAABAAAAMj+//8IAAAADAAAAAAAAAAAAAAABAAAAG5hbWUAAAAA6P7//wgAAAAYAAAADgAAAHsic3RhdGUiOiJOViJ9AAAGAAAAbGFiZWxzAAAAAAAA3v7//wAAAgAAAAAAAAAAAH7///8UAAAAaAAAAGgAAAAAAAADaAAAAAIAAAAoAAAABAAAAEz///8IAAAADAAAAAAAAAAAAAAABAAAAG5hbWUAAAAAbP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJPUiJ9AAAGAAAAbGFiZWxzAAAAAAAAYv///wAAAgAAAAAAAAASABgAFAAAABMADAAAAAgABAASAAAAFAAAAHAAAAB4AAAAAAAAA3gAAAACAAAAMAAAAAQAAADg////CAAAAAwAAAAAAAAAAAAAAAQAAABuYW1lAAAAAAgADAAIAAQACAAAAAgAAAAYAAAADgAAAHsic3RhdGUiOiJDQSJ9AAAGAAAAbGFiZWxzAAAAAAAAAAAGAAgABgAGAAAAAAACAAAAAAAAAAAAAAAAAP/////oAAAAFAAAAAAAAAAMABYAFAATAAwABAAMAAAAGAAAAAAAAAAUAAAAAAAAAwMACgAYAAwACAAEAAoAAAAUAAAAeAAAAAEAAAAAAAAAAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAgAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAACAAAAAAAAAAAAAAAAwAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAG723pNbB+4/wlL/TspM1j/4AhKQmPDrPxAAAAAMABQAEgAMAAgABAAMAAAAEAAAACwAAAA4AAAAAAADAAEAAABQAgAAAAAAAPAAAAAAAAAAGAAAAAAAAAAAAAAAAAAAAAAACgAMAAAACAAEAAoAAAAIAAAAUAAAAAIAAAAoAAAABAAAAFT+//8IAAAADAAAAAAAAAAAAAAABQAAAHJlZklkAAAAdP7//wgAAAAMAAAAAwAAAGJhcgAEAAAAbmFtZQAAAAADAAAAJAEAAIwAAAAEAAAA+v7//xQAAABoAAAAaAAAAAAAAANoAAAAAgAAACgAAAAEAAAAyP7//wgAAAAMAAAAAAAAAAAAAAAEAAAAbmFtZQAAAADo/v//CAAAABgAAAAOAAAAeyJzdGF0ZSI6Ik5WIn0AAAYAAABsYWJlbHMAAAAAAADe/v//AAACAAAAAAAAAAAAfv///xQAAABoAAAAaAAAAAAAAANoAAAAAgAAACgAAAAEAAAATP///wgAAAAMAAAAAAAAAAAAAAAEAAAAbmFtZQAAAABs////CAAAABgAAAAOAAAAeyJzdGF0ZSI6Ik9SIn0AAAYAAABsYWJlbHMAAAAAAABi////AAACAAAAAAAAABIAGAAUAAAAEwAMAAAACAAEABIAAAAUAAAAcAAAAHgAAAAAAAADeAAAAAIAAAAwAAAABAAAAOD///8IAAAADAAAAAAAAAAAAAAABAAAAG5hbWUAAAAACAAMAAgABAAIAAAACAAAABgAAAAOAAAAeyJzdGF0ZSI6IkNBIn0AAAYAAABsYWJlbHMAAAAAAAAAAAYACAAGAAYAAAAAAAIAAAAAAAAAAABoAgAAQVJST1cx
diff --git a/pkg/tsdb/testdatasource/testdata/usa-values-as-rows.txt b/pkg/tsdb/testdatasource/testdata/usa-values-as-rows.txt
index e9fb0e991d5..29748c7bf0b 100644
--- a/pkg/tsdb/testdatasource/testdata/usa-values-as-rows.txt
+++ b/pkg/tsdb/testdatasource/testdata/usa-values-as-rows.txt
@@ -3,23 +3,23 @@
Frame[0]
Name:
Dimensions: 4 Fields by 51 Rows
-+----------------+----------------------+----------------------+---------------------+
-| Name: state | Name: foo | Name: bar | Name: baz |
-| Labels: | Labels: | Labels: | Labels: |
-| Type: []string | Type: []float64 | Type: []float64 | Type: []float64 |
-+----------------+----------------------+----------------------+---------------------+
-| AL | 0.6779842417778559 | 0.7350764367743711 | 0.9223316213929472 |
-| AK | 0.9096030926893556 | -0.41547829518515106 | -2.1892914822036755 |
-| AZ | -0.11168671678088725 | -0.9937434665418967 | 0.11238988787472795 |
-| AR | -0.9791366197808846 | -0.2032030506760749 | 4.818513386109159 |
-| CA | -0.4979007493043646 | 0.8672340190756773 | -0.5741250208738828 |
-| CO | 0.6691553056325918 | 0.74312258540819 | 0.9004642286104536 |
-| CT | 0.914501128595007 | -0.4045833483949364 | -2.260352859856979 |
-| DE | -0.09980837850296802 | -0.9950066771537809 | 0.10030925499763493 |
-| DC | -0.976639481217665 | -0.21488444272885437 | 4.544952016140177 |
-| ... | ... | ... | ... |
-+----------------+----------------------+----------------------+---------------------+
++----------------+-----------------+-----------------+-----------------+
+| Name: state | Name: foo | Name: bar | Name: baz |
+| Labels: | Labels: | Labels: | Labels: |
+| Type: []string | Type: []float64 | Type: []float64 | Type: []float64 |
++----------------+-----------------+-----------------+-----------------+
+| AL | 0.6779842417 | 0.7350764367 | 0.9223316213 |
+| AK | 0.9096030926 | -0.4154782951 | -2.1892914822 |
+| AZ | -0.1116867167 | -0.9937434665 | 0.1123898878 |
+| AR | -0.9791366197 | -0.2032030506 | 4.8185133861 |
+| CA | -0.4979007493 | 0.867234019 | -0.5741250208 |
+| CO | 0.6691553056 | 0.7431225854 | 0.9004642286 |
+| CT | 0.9145011285 | -0.4045833483 | -2.2603528598 |
+| DE | -0.0998083785 | -0.9950066771 | 0.1003092549 |
+| DC | -0.9766394812 | -0.2148844427 | 4.5449520161 |
+| ... | ... | ... | ... |
++----------------+-----------------+-----------------+-----------------+
====== TEST DATA RESPONSE (arrow base64) ======
-FRAME=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
+FRAME=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
diff --git a/pkg/tsdb/testdatasource/usa_stats.go b/pkg/tsdb/testdatasource/usa_stats.go
index 729479f665e..57dee9aed72 100644
--- a/pkg/tsdb/testdatasource/usa_stats.go
+++ b/pkg/tsdb/testdatasource/usa_stats.go
@@ -192,7 +192,10 @@ func getStateValues(t time.Time, field string, query usaQuery) []float64 {
values := make([]float64, len(query.States))
for i := range query.States {
tv += incr
- values[i] = fn(float64(int64(tv) % int64(pn)))
+ value := fn(float64(int64(tv) % int64(pn)))
+ // We shall truncate float64 to a certain precision, because otherwise we might
+ // get different cos/tan results across different CPU architectures and compilers.
+ values[i] = float64(int(value*1e10)) / 1e10
}
return values
}
diff --git a/public/app/features/alerting/unified/AmRoutes.test.tsx b/public/app/features/alerting/unified/AmRoutes.test.tsx
index 0f103cc2964..91a379a106e 100644
--- a/public/app/features/alerting/unified/AmRoutes.test.tsx
+++ b/public/app/features/alerting/unified/AmRoutes.test.tsx
@@ -15,20 +15,6 @@ import { DataSourceType, GRAFANA_RULES_SOURCE_NAME } from './utils/datasource';
import userEvent from '@testing-library/user-event';
import { selectOptionInTest } from '@grafana/ui';
-Object.defineProperty(window, 'matchMedia', {
- writable: true,
- value: jest.fn().mockImplementation((query) => ({
- matches: false,
- media: query,
- onchange: null,
- addListener: jest.fn(), // deprecated
- removeListener: jest.fn(), // deprecated
- addEventListener: jest.fn(),
- removeEventListener: jest.fn(),
- dispatchEvent: jest.fn(),
- })),
-});
-
jest.mock('./api/alertmanager');
jest.mock('./utils/config');
diff --git a/public/app/features/alerting/unified/PanelAlertTabContent.test.tsx b/public/app/features/alerting/unified/PanelAlertTabContent.test.tsx
new file mode 100644
index 00000000000..f584e1d80c0
--- /dev/null
+++ b/public/app/features/alerting/unified/PanelAlertTabContent.test.tsx
@@ -0,0 +1,273 @@
+import React from 'react';
+import { locationService, setDataSourceSrv } from '@grafana/runtime';
+import { configureStore } from 'app/store/configureStore';
+import { Provider } from 'react-redux';
+import { Router } from 'react-router-dom';
+import { render } from '@testing-library/react';
+import { PanelAlertTabContent } from './PanelAlertTabContent';
+import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
+import {
+ mockDataSource,
+ MockDataSourceSrv,
+ mockPromAlertingRule,
+ mockPromRuleGroup,
+ mockPromRuleNamespace,
+ mockRulerGrafanaRule,
+} from './mocks';
+import { DataSourceType } from './utils/datasource';
+import { typeAsJestMock } from 'test/helpers/typeAsJestMock';
+import { getAllDataSources } from './utils/config';
+import { fetchRules } from './api/prometheus';
+import { fetchRulerRules } from './api/ruler';
+import { Annotation } from './utils/constants';
+import { byTestId } from 'testing-library-selector';
+import { PrometheusDatasource } from 'app/plugins/datasource/prometheus/datasource';
+import { DataSourceApi } from '@grafana/data';
+import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
+
+jest.mock('./api/prometheus');
+jest.mock('./api/ruler');
+jest.mock('./utils/config');
+
+const dataSources = {
+ prometheus: mockDataSource({
+ name: 'Prometheus',
+ type: DataSourceType.Prometheus,
+ }),
+};
+dataSources.prometheus.meta.alerting = true;
+
+const mocks = {
+ getAllDataSources: typeAsJestMock(getAllDataSources),
+ api: {
+ fetchRules: typeAsJestMock(fetchRules),
+ fetchRulerRules: typeAsJestMock(fetchRulerRules),
+ },
+};
+
+const renderAlertTabContent = (dashboard: DashboardModel, panel: PanelModel) => {
+ const store = configureStore();
+
+ return render(
+
+
+
+
+
+ );
+};
+
+const rules = [
+ mockPromRuleNamespace({
+ name: 'default',
+ groups: [
+ mockPromRuleGroup({
+ name: 'mygroup',
+ rules: [
+ mockPromAlertingRule({
+ name: 'dashboardrule1',
+ annotations: {
+ [Annotation.dashboardUID]: '12',
+ [Annotation.panelID]: '34',
+ },
+ }),
+ ],
+ }),
+ mockPromRuleGroup({
+ name: 'othergroup',
+ rules: [
+ mockPromAlertingRule({
+ name: 'dashboardrule2',
+ annotations: {
+ [Annotation.dashboardUID]: '121',
+ [Annotation.panelID]: '341',
+ },
+ }),
+ ],
+ }),
+ ],
+ }),
+];
+
+const rulerRules = {
+ default: [
+ {
+ name: 'mygroup',
+ rules: [
+ mockRulerGrafanaRule(
+ {
+ annotations: {
+ [Annotation.dashboardUID]: '12',
+ [Annotation.panelID]: '34',
+ },
+ },
+ {
+ title: 'dashboardrule1',
+ }
+ ),
+ ],
+ },
+ {
+ name: 'othergroup',
+ rules: [
+ mockRulerGrafanaRule(
+ {
+ annotations: {
+ [Annotation.dashboardUID]: '121',
+ [Annotation.panelID]: '341',
+ },
+ },
+ {
+ title: 'dashboardrule2',
+ }
+ ),
+ ],
+ },
+ ],
+};
+
+const dashboard = {
+ uid: '12',
+ time: {
+ from: 'now-6h',
+ to: 'now',
+ },
+ meta: {
+ canSave: true,
+ folderId: 1,
+ folderTitle: 'super folder',
+ },
+} as DashboardModel;
+const panel = ({
+ datasource: dataSources.prometheus.uid,
+ title: 'mypanel',
+ editSourceId: 34,
+ targets: [
+ {
+ expr: 'sum(some_metric [$__interval])) by (app)',
+ refId: 'A',
+ },
+ ],
+} as any) as PanelModel;
+
+const ui = {
+ row: byTestId('row'),
+ createButton: byTestId('create-alert-rule-button'),
+};
+
+describe('PanelAlertTabContent', () => {
+ beforeEach(() => {
+ jest.resetAllMocks();
+ mocks.getAllDataSources.mockReturnValue(Object.values(dataSources));
+ const dsService = new MockDataSourceSrv(dataSources);
+ dsService.datasources[dataSources.prometheus.name] = new PrometheusDatasource(
+ dataSources.prometheus
+ ) as DataSourceApi;
+ setDataSourceSrv(dsService);
+ });
+
+ it('Will take into account panel maxDataPoints', async () => {
+ await renderAlertTabContent(dashboard, ({
+ ...panel,
+ maxDataPoints: 100,
+ interval: '10s',
+ } as any) as PanelModel);
+ const button = await ui.createButton.find();
+ const href = button.href;
+ const match = href.match(/alerting\/new\?defaults=(.*)&returnTo=/);
+ expect(match).toHaveLength(2);
+ const defaults = JSON.parse(decodeURIComponent(match![1]));
+ expect(defaults.queries[0].model).toEqual({
+ expr: 'sum(some_metric [5m])) by (app)',
+ refId: 'A',
+ datasource: 'Prometheus',
+ interval: '',
+ intervalMs: 300000,
+ maxDataPoints: 100,
+ });
+ });
+
+ it('Will take into account datasource minInterval', async () => {
+ ((getDatasourceSrv() as any) as MockDataSourceSrv).datasources[dataSources.prometheus.name].interval = '7m';
+
+ await renderAlertTabContent(dashboard, ({
+ ...panel,
+ maxDataPoints: 100,
+ } as any) as PanelModel);
+ const button = await ui.createButton.find();
+ const href = button.href;
+ const match = href.match(/alerting\/new\?defaults=(.*)&returnTo=/);
+ expect(match).toHaveLength(2);
+ const defaults = JSON.parse(decodeURIComponent(match![1]));
+ expect(defaults.queries[0].model).toEqual({
+ expr: 'sum(some_metric [7m])) by (app)',
+ refId: 'A',
+ datasource: 'Prometheus',
+ interval: '',
+ intervalMs: 420000,
+ maxDataPoints: 100,
+ });
+ });
+
+ it('Will render alerts belonging to panel and a button to create alert from panel queries', async () => {
+ mocks.api.fetchRules.mockResolvedValue(rules);
+ mocks.api.fetchRulerRules.mockResolvedValue(rulerRules);
+
+ await renderAlertTabContent(dashboard, panel);
+
+ const rows = await ui.row.findAll();
+ expect(rows).toHaveLength(1);
+ expect(rows[0]).toHaveTextContent(/dashboardrule1/);
+ expect(rows[0]).not.toHaveTextContent(/dashboardrule2/);
+ const button = await ui.createButton.find();
+ const href = button.href;
+ const match = href.match(/alerting\/new\?defaults=(.*)&returnTo=/);
+ expect(match).toHaveLength(2);
+ const defaults = JSON.parse(decodeURIComponent(match![1]));
+ expect(defaults).toEqual({
+ type: 'grafana',
+ folder: { id: 1, title: 'super folder' },
+ queries: [
+ {
+ refId: 'A',
+ queryType: '',
+ relativeTimeRange: { from: 21600, to: 0 },
+ datasourceUid: 'mock-ds-2',
+ model: {
+ expr: 'sum(some_metric [15s])) by (app)',
+ refId: 'A',
+ datasource: 'Prometheus',
+ interval: '',
+ intervalMs: 15000,
+ },
+ },
+ {
+ refId: 'B',
+ datasourceUid: '-100',
+ queryType: '',
+ model: {
+ refId: 'B',
+ hide: false,
+ type: 'classic_conditions',
+ datasource: '__expr__',
+ conditions: [
+ {
+ type: 'query',
+ evaluator: { params: [3], type: 'gt' },
+ operator: { type: 'and' },
+ query: { params: ['A'] },
+ reducer: { params: [], type: 'last' },
+ },
+ ],
+ },
+ },
+ ],
+ name: 'mypanel',
+ condition: 'B',
+ annotations: [
+ { key: '__dashboardUid__', value: '12' },
+ { key: '__panelId__', value: '34' },
+ ],
+ });
+ });
+});
diff --git a/public/app/features/alerting/unified/components/panel-alerts-tab/NewRuleFromPanelButton.tsx b/public/app/features/alerting/unified/components/panel-alerts-tab/NewRuleFromPanelButton.tsx
index fb11e355d08..234dc3c3175 100644
--- a/public/app/features/alerting/unified/components/panel-alerts-tab/NewRuleFromPanelButton.tsx
+++ b/public/app/features/alerting/unified/components/panel-alerts-tab/NewRuleFromPanelButton.tsx
@@ -1,9 +1,10 @@
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
import React, { FC } from 'react';
-import { Alert, LinkButton } from '@grafana/ui';
+import { Alert, LinkButton, Button } from '@grafana/ui';
import { panelToRuleFormValues } from '../../utils/rule-form';
import { useLocation } from 'react-router-dom';
import { urlUtil } from '@grafana/data';
+import { useAsync } from 'react-use';
interface Props {
panel: PanelModel;
@@ -12,8 +13,11 @@ interface Props {
}
export const NewRuleFromPanelButton: FC = ({ dashboard, panel, className }) => {
- const formValues = panelToRuleFormValues(panel, dashboard);
+ const { loading, value: formValues } = useAsync(() => panelToRuleFormValues(panel, dashboard), [panel, dashboard]);
const location = useLocation();
+ if (loading) {
+ return ;
+ }
if (!formValues) {
return (
@@ -29,7 +33,7 @@ export const NewRuleFromPanelButton: FC = ({ dashboard, panel, className
});
return (
-
+
Create alert rule from this panel
);
diff --git a/public/app/features/alerting/unified/mocks.ts b/public/app/features/alerting/unified/mocks.ts
index 70dc72ad90a..bd8c0f0bc20 100644
--- a/public/app/features/alerting/unified/mocks.ts
+++ b/public/app/features/alerting/unified/mocks.ts
@@ -1,5 +1,11 @@
import { DataSourceApi, DataSourceInstanceSettings, DataSourcePluginMeta, ScopedVars } from '@grafana/data';
-import { PromAlertingRuleState, PromRuleType } from 'app/types/unified-alerting-dto';
+import {
+ GrafanaAlertStateDecision,
+ GrafanaRuleDefinition,
+ PromAlertingRuleState,
+ PromRuleType,
+ RulerGrafanaRuleDTO,
+} from 'app/types/unified-alerting-dto';
import { AlertingRule, Alert, RecordingRule, RuleGroup, RuleNamespace } from 'app/types/unified-alerting';
import DatasourceSrv from 'app/features/plugins/datasource_srv';
import { DataSourceSrv, GetDataSourceListFilters } from '@grafana/runtime';
@@ -17,7 +23,7 @@ let nextDataSourceId = 1;
export const mockDataSource = (
partial: Partial = {},
meta: Partial = {}
-): DataSourceInstanceSettings => {
+): DataSourceInstanceSettings => {
const id = partial.id ?? nextDataSourceId++;
return {
@@ -54,6 +60,40 @@ export const mockPromAlert = (partial: Partial = {}): Alert => ({
...partial,
});
+export const mockRulerGrafanaRule = (
+ partial: Partial = {},
+ partialDef: Partial = {}
+): RulerGrafanaRuleDTO => {
+ return {
+ for: '1m',
+ grafana_alert: {
+ uid: '123',
+ title: 'myalert',
+ namespace_uid: '123',
+ namespace_id: 1,
+ condition: 'A',
+ no_data_state: GrafanaAlertStateDecision.Alerting,
+ exec_err_state: GrafanaAlertStateDecision.Alerting,
+ data: [
+ {
+ datasourceUid: '123',
+ refId: 'A',
+ queryType: 'huh',
+ model: {} as any,
+ },
+ ],
+ ...partialDef,
+ },
+ annotations: {
+ message: 'alert with severity "{{.warning}}}"',
+ },
+ labels: {
+ severity: 'warning',
+ },
+ ...partial,
+ };
+};
+
export const mockPromAlertingRule = (partial: Partial = {}): AlertingRule => {
return {
type: PromRuleType.Alerting,
diff --git a/public/app/features/alerting/unified/utils/rule-form.ts b/public/app/features/alerting/unified/utils/rule-form.ts
index e5006b801ea..5ce60e559b7 100644
--- a/public/app/features/alerting/unified/utils/rule-form.ts
+++ b/public/app/features/alerting/unified/utils/rule-form.ts
@@ -1,11 +1,18 @@
-import { DataQuery, rangeUtil, RelativeTimeRange } from '@grafana/data';
+import {
+ DataQuery,
+ rangeUtil,
+ RelativeTimeRange,
+ ScopedVars,
+ getDefaultRelativeTimeRange,
+ TimeRange,
+ IntervalValues,
+} from '@grafana/data';
import { getDataSourceSrv } from '@grafana/runtime';
import { contextSrv } from 'app/core/services/context_srv';
import { getNextRefIdChar } from 'app/core/utils/query';
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
import { ExpressionDatasourceID, ExpressionDatasourceUID } from 'app/features/expressions/ExpressionDatasource';
import { ExpressionQuery, ExpressionQueryType } from 'app/features/expressions/types';
-import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
import { RuleWithLocation } from 'app/types/unified-alerting';
import {
Annotations,
@@ -22,7 +29,6 @@ import { isGrafanaRulesSource } from './datasource';
import { arrayToRecord, recordToArray } from './misc';
import { isAlertingRulerRule, isGrafanaRulerRule } from './rules';
import { parseInterval } from './time';
-import { getDefaultRelativeTimeRange } from '../../../../../../packages/grafana-data';
export const getDefaultFormValues = (): RuleFormValues =>
Object.freeze({
@@ -193,60 +199,81 @@ const getDefaultExpression = (refId: string): AlertQuery => {
};
};
-const dataQueriesToGrafanaQueries = (
+const dataQueriesToGrafanaQueries = async (
queries: DataQuery[],
relativeTimeRange: RelativeTimeRange,
- datasourceName?: string
-): AlertQuery[] => {
- return queries.reduce((queries, target) => {
+ scopedVars: ScopedVars | {},
+ datasourceName?: string,
+ maxDataPoints?: number,
+ minInterval?: string
+): Promise => {
+ const result: AlertQuery[] = [];
+ for (const target of queries) {
const dsName = target.datasource || datasourceName;
+ const datasource = await getDataSourceSrv().get(dsName);
+
+ const range = rangeUtil.relativeToTimeRange(relativeTimeRange);
+ const { interval, intervalMs } = getIntervals(range, minInterval ?? datasource.interval, maxDataPoints);
+ const queryVariables = {
+ __interval: { text: interval, value: interval },
+ __interval_ms: { text: intervalMs, value: intervalMs },
+ ...scopedVars,
+ };
+ const interpolatedTarget = datasource.interpolateVariablesInQueries
+ ? await datasource.interpolateVariablesInQueries([target], queryVariables)[0]
+ : target;
if (dsName) {
// expressions
if (dsName === ExpressionDatasourceID) {
const newQuery: AlertQuery = {
- refId: target.refId,
+ refId: interpolatedTarget.refId,
queryType: '',
relativeTimeRange,
datasourceUid: ExpressionDatasourceUID,
- model: target,
+ model: interpolatedTarget,
};
- return [...queries, newQuery];
+ result.push(newQuery);
// queries
} else {
- const datasource = getDataSourceSrv().getInstanceSettings(target.datasource || datasourceName);
- if (datasource && datasource.meta.alerting) {
+ const datasourceSettings = getDataSourceSrv().getInstanceSettings(dsName);
+ if (datasourceSettings && datasourceSettings.meta.alerting) {
const newQuery: AlertQuery = {
- refId: target.refId,
- queryType: target.queryType ?? '',
+ refId: interpolatedTarget.refId,
+ queryType: interpolatedTarget.queryType ?? '',
relativeTimeRange,
- datasourceUid: datasource.uid,
- model: target,
+ datasourceUid: datasourceSettings.uid,
+ model: {
+ ...interpolatedTarget,
+ maxDataPoints,
+ intervalMs,
+ },
};
- return [...queries, newQuery];
+ result.push(newQuery);
}
}
}
- return queries;
- }, []);
+ }
+ return result;
};
-export const panelToRuleFormValues = (
+export const panelToRuleFormValues = async (
panel: PanelModel,
dashboard: DashboardModel
-): Partial | undefined => {
+): Promise | undefined> => {
const { targets } = panel;
-
- // it seems if default datasource is selected, datasource=null, hah
- const datasourceName =
- panel.datasource === null ? getDatasourceSrv().getInstanceSettings('default')?.name : panel.datasource;
-
if (!panel.editSourceId || !dashboard.uid) {
return undefined;
}
const relativeTimeRange = rangeUtil.timeRangeToRelative(rangeUtil.convertRawToRange(dashboard.time));
- const queries = dataQueriesToGrafanaQueries(targets, relativeTimeRange, datasourceName);
-
+ const queries = await dataQueriesToGrafanaQueries(
+ targets,
+ relativeTimeRange,
+ panel.scopedVars || {},
+ panel.datasource ?? undefined,
+ panel.maxDataPoints ?? undefined,
+ panel.interval ?? undefined
+ );
// if no alerting capable queries are found, can't create a rule
if (!queries.length || !queries.find((query) => query.datasourceUid !== ExpressionDatasourceUID)) {
return undefined;
@@ -269,6 +296,7 @@ export const panelToRuleFormValues = (
: undefined,
queries,
name: panel.title,
+ condition: queries[queries.length - 1].refId,
annotations: [
{
key: Annotation.dashboardUID,
@@ -282,3 +310,17 @@ export const panelToRuleFormValues = (
};
return formValues;
};
+
+export function getIntervals(range: TimeRange, lowLimit?: string, resolution?: number): IntervalValues {
+ if (!resolution) {
+ if (lowLimit && rangeUtil.intervalToMs(lowLimit) > 1000) {
+ return {
+ interval: lowLimit,
+ intervalMs: rangeUtil.intervalToMs(lowLimit),
+ };
+ }
+ return { interval: '1s', intervalMs: 1000 };
+ }
+
+ return rangeUtil.calculateInterval(range, resolution, lowLimit);
+}
diff --git a/public/app/features/dashboard/components/SubMenu/DashboardLinksDashboard.tsx b/public/app/features/dashboard/components/SubMenu/DashboardLinksDashboard.tsx
index b783570759b..203af35cf60 100644
--- a/public/app/features/dashboard/components/SubMenu/DashboardLinksDashboard.tsx
+++ b/public/app/features/dashboard/components/SubMenu/DashboardLinksDashboard.tsx
@@ -1,5 +1,5 @@
import React, { useRef, useState, useLayoutEffect } from 'react';
-import { Icon, Tooltip } from '@grafana/ui';
+import { Icon, ToolbarButton, Tooltip, useStyles2 } from '@grafana/ui';
import { sanitize, sanitizeUrl } from '@grafana/data/src/text/sanitize';
import { getBackendSrv } from 'app/core/services/backend_srv';
import { getLinkSrv } from '../../../panel/panellinks/link_srv';
@@ -7,6 +7,7 @@ import { DashboardLink } from '../../state/DashboardModel';
import { DashboardSearchHit } from 'app/features/search/types';
import { selectors } from '@grafana/e2e-selectors';
import { useAsync } from 'react-use';
+import { css, cx } from '@emotion/css';
interface Props {
link: DashboardLink;
@@ -21,33 +22,45 @@ export const DashboardLinksDashboard: React.FC = (props) => {
const [opened, setOpened] = useState(0);
const resolvedLinks = useResolvedLinks(props, opened);
+ const buttonStyle = useStyles2(
+ (theme) =>
+ css`
+ color: ${theme.colors.text.primary};
+ `
+ );
+
useLayoutEffect(() => {
setDropdownCssClass(getDropdownLocationCssClass(listRef.current));
}, [resolvedLinks]);
if (link.asDropdown) {
return (
-
+
<>
- setOpened(Date.now())}
- className="gf-form-label gf-form-label--dashlink"
+ className={cx('gf-form-label gf-form-label--dashlink', buttonStyle)}
data-placement="bottom"
data-toggle="dropdown"
+ aria-expanded={!!opened}
+ aria-controls="dropdown-list"
+ aria-haspopup="menu"
>
-
+
{linkInfo.title}
-
-
+
+
{resolvedLinks.length > 0 &&
resolvedLinks.map((resolvedLink, index) => {
return (
- -
+
-
{resolvedLink.title}
@@ -68,16 +81,17 @@ export const DashboardLinksDashboard: React.FC = (props) => {
-
+
{resolvedLink.title}
@@ -89,7 +103,6 @@ export const DashboardLinksDashboard: React.FC = (props) => {
interface LinkElementProps {
link: DashboardLink;
- 'aria-label': string;
key: string;
children: JSX.Element;
}
diff --git a/public/app/features/dashboard/state/PanelModel.ts b/public/app/features/dashboard/state/PanelModel.ts
index 1b9bb9db61e..05f2768a659 100644
--- a/public/app/features/dashboard/state/PanelModel.ts
+++ b/public/app/features/dashboard/state/PanelModel.ts
@@ -17,6 +17,8 @@ import {
PanelPluginDataSupport,
ScopedVars,
urlUtil,
+ PanelModel as IPanelModel,
+ DatasourceRef,
} from '@grafana/data';
import { EDIT_PANEL_ID } from 'app/core/constants';
import config from 'app/core/config';
@@ -124,7 +126,7 @@ const defaults: any = {
title: '',
};
-export class PanelModel implements DataConfigSource {
+export class PanelModel implements DataConfigSource, IPanelModel {
/* persisted id, used in URL to identify a panel */
id!: number;
editSourceId?: number;
@@ -144,7 +146,7 @@ export class PanelModel implements DataConfigSource {
panels?: any;
declare targets: DataQuery[];
transformations?: DataTransformerConfig[];
- datasource: string | null = null;
+ datasource: DatasourceRef | null = null;
thresholds?: any;
pluginVersion?: string;
diff --git a/public/app/features/explore/Logs.tsx b/public/app/features/explore/Logs.tsx
index b6850cb76bc..dd4e4874896 100644
--- a/public/app/features/explore/Logs.tsx
+++ b/public/app/features/explore/Logs.tsx
@@ -283,7 +283,7 @@ export class UnthemedLogs extends PureComponent {
forceEscape,
} = this.state;
- const styles = getStyles(theme);
+ const styles = getStyles(theme, wrapLogMessage);
const hasData = logRows && logRows.length > 0;
const hasUnescapedContent = this.checkUnescapedContent(logRows);
@@ -420,7 +420,7 @@ export class UnthemedLogs extends PureComponent {
export const Logs = withTheme2(UnthemedLogs);
-const getStyles = (theme: GrafanaTheme2) => {
+const getStyles = (theme: GrafanaTheme2, wrapLogMessage: boolean) => {
return {
noData: css`
> * {
@@ -450,7 +450,8 @@ const getStyles = (theme: GrafanaTheme2) => {
justify-content: space-between;
`,
logRows: css`
- overflow-x: scroll;
+ overflow-x: ${wrapLogMessage ? 'unset' : 'scroll'};
+ overflow-y: visible;
width: 100%;
`,
infoText: css`
diff --git a/public/app/features/explore/MetaInfoText.tsx b/public/app/features/explore/MetaInfoText.tsx
index cec6a0330a3..0b70201ab87 100644
--- a/public/app/features/explore/MetaInfoText.tsx
+++ b/public/app/features/explore/MetaInfoText.tsx
@@ -1,35 +1,35 @@
import React, { memo } from 'react';
import { css } from '@emotion/css';
-import { GrafanaTheme } from '@grafana/data';
-import { useStyles } from '@grafana/ui';
+import { GrafanaTheme2 } from '@grafana/data';
+import { useStyles2 } from '@grafana/ui';
-const getStyles = (theme: GrafanaTheme) => ({
+const getStyles = (theme: GrafanaTheme2) => ({
metaContainer: css`
flex: 1;
- color: ${theme.colors.textWeak};
- margin-bottom: ${theme.spacing.d};
+ color: ${theme.colors.text.secondary};
+ margin-bottom: ${theme.spacing(2)};
min-width: 30%;
display: flex;
flex-wrap: wrap;
`,
metaItem: css`
- margin-right: ${theme.spacing.d};
- margin-top: ${theme.spacing.xs};
+ margin-right: ${theme.spacing(2)};
+ margin-top: ${theme.spacing(0.5)};
display: flex;
align-items: baseline;
.logs-meta-item__error {
- color: ${theme.palette.red};
+ color: ${theme.colors.error.text};
}
`,
metaLabel: css`
- margin-right: calc(${theme.spacing.d} / 2);
- font-size: ${theme.typography.size.sm};
- font-weight: ${theme.typography.weight.semibold};
+ margin-right: calc(${theme.spacing(2)} / 2);
+ font-size: ${theme.typography.bodySmall.fontSize};
+ font-weight: ${theme.typography.fontWeightMedium};
`,
metaValue: css`
- font-family: ${theme.typography.fontFamily.monospace};
- font-size: ${theme.typography.size.sm};
+ font-family: ${theme.typography.fontFamilyMonospace};
+ font-size: ${theme.typography.bodySmall.fontSize};
`,
});
@@ -39,7 +39,7 @@ export interface MetaItemProps {
}
export const MetaInfoItem = memo(function MetaInfoItem(props: MetaItemProps) {
- const style = useStyles(getStyles);
+ const style = useStyles2(getStyles);
const { label, value } = props;
return (
@@ -55,7 +55,7 @@ export interface MetaInfoTextProps {
}
export const MetaInfoText = memo(function MetaInfoText(props: MetaInfoTextProps) {
- const style = useStyles(getStyles);
+ const style = useStyles2(getStyles);
const { metaItems } = props;
return (
diff --git a/public/app/plugins/datasource/grafana/datasource.ts b/public/app/plugins/datasource/grafana/datasource.ts
index 7750db46625..4a127e59079 100644
--- a/public/app/plugins/datasource/grafana/datasource.ts
+++ b/public/app/plugins/datasource/grafana/datasource.ts
@@ -8,6 +8,7 @@ import {
DataQueryResponse,
DataSourceApi,
DataSourceInstanceSettings,
+ DatasourceRef,
isValidLiveChannelAddress,
parseLiveChannelAddress,
StreamingFrameOptions,
@@ -17,6 +18,7 @@ import {
import { GrafanaAnnotationQuery, GrafanaAnnotationType, GrafanaQuery, GrafanaQueryType } from './types';
import AnnotationQueryEditor from './components/AnnotationQueryEditor';
import { getDashboardSrv } from '../../../features/dashboard/services/DashboardSrv';
+import { isString } from 'lodash';
let counter = 100;
@@ -37,7 +39,11 @@ export class GrafanaDatasource extends DataSourceApi {
return json;
},
prepareQuery(anno: AnnotationQuery): GrafanaQuery {
- return { ...anno, refId: anno.name, queryType: GrafanaQueryType.Annotations };
+ let datasource: DatasourceRef | undefined | null = undefined;
+ if (isString(anno.datasource)) {
+ datasource = anno.datasource as DatasourceRef;
+ }
+ return { ...anno, refId: anno.name, queryType: GrafanaQueryType.Annotations, datasource };
},
};
}
diff --git a/public/app/plugins/datasource/influxdb/datasource.ts b/public/app/plugins/datasource/influxdb/datasource.ts
index caa8bd0de92..fb03acd1457 100644
--- a/public/app/plugins/datasource/influxdb/datasource.ts
+++ b/public/app/plugins/datasource/influxdb/datasource.ts
@@ -182,10 +182,12 @@ export default class InfluxDatasource extends DataSourceWithBackend {
+ // this only works in flux-mode, it should not be called in non-flux-mode
+ if (!this.isFlux) {
+ throw new Error('applyTemplateVariables called in influxql-mode. this should never happen');
+ }
+
return {
...query,
query: this.templateSrv.replace(query.query ?? '', scopedVars), // The raw query text
@@ -344,7 +346,7 @@ export default class InfluxDatasource extends DataSourceWithBackend {
@@ -52,7 +52,7 @@ describe('Graph Panel Migrations', () => {
spaceLength: 10,
stack: false,
steppedLine: false,
- targets: [
+ targets: ([
{
alias: 'Foo datacenter',
labels: 'datacenter=foo,region=us-east-1',
@@ -71,7 +71,7 @@ describe('Graph Panel Migrations', () => {
refId: 'C',
scenarioId: 'random_walk',
},
- ],
+ ] as unknown) as DataQuery[],
thresholds: [],
timeFrom: null,
timeRegions: [],
diff --git a/public/app/plugins/panel/logs/LogsPanel.test.tsx b/public/app/plugins/panel/logs/LogsPanel.test.tsx
index 9274d668cda..029b2392dfe 100644
--- a/public/app/plugins/panel/logs/LogsPanel.test.tsx
+++ b/public/app/plugins/panel/logs/LogsPanel.test.tsx
@@ -1,6 +1,6 @@
import React, { ComponentProps } from 'react';
import { render, screen } from '@testing-library/react';
-import { LoadingState, MutableDataFrame, FieldType } from '@grafana/data';
+import { LoadingState, MutableDataFrame, FieldType, LogsSortOrder } from '@grafana/data';
import { LogsPanel } from './LogsPanel';
type LogsPanelProps = ComponentProps;
@@ -38,6 +38,22 @@ describe('LogsPanel', () => {
expect(screen.getByText(/common_app/i)).toBeInTheDocument();
expect(screen.getByText(/common_job/i)).toBeInTheDocument();
});
+ it('shows common labels on top when descending sort order', () => {
+ const { container } = setup({
+ data: { series: seriesWithCommonLabels },
+ options: { showCommonLabels: true, sortOrder: LogsSortOrder.Descending },
+ });
+
+ expect(container.firstChild?.childNodes[0].textContent).toMatch(/^Common labels:common_appcommon_job/);
+ });
+ it('shows common labels on bottom when ascending sort order', () => {
+ const { container } = setup({
+ data: { series: seriesWithCommonLabels },
+ options: { showCommonLabels: true, sortOrder: LogsSortOrder.Ascending },
+ });
+
+ expect(container.firstChild?.childNodes[0].textContent).toMatch(/Common labels:common_appcommon_job$/);
+ });
it('does not show common labels when showCommonLabels is set to false', () => {
setup({ data: { series: seriesWithCommonLabels }, options: { showCommonLabels: false } });
diff --git a/public/app/plugins/panel/logs/LogsPanel.tsx b/public/app/plugins/panel/logs/LogsPanel.tsx
index 1cfb4db2fda..7446ff246f2 100644
--- a/public/app/plugins/panel/logs/LogsPanel.tsx
+++ b/public/app/plugins/panel/logs/LogsPanel.tsx
@@ -1,7 +1,7 @@
-import React, { useCallback, useMemo } from 'react';
+import React, { useCallback, useMemo, useRef, useLayoutEffect, useState } from 'react';
import { css } from '@emotion/css';
import { LogRows, CustomScrollbar, LogLabels, useStyles2 } from '@grafana/ui';
-import { PanelProps, Field, Labels, GrafanaTheme2 } from '@grafana/data';
+import { PanelProps, Field, Labels, GrafanaTheme2, LogsSortOrder } from '@grafana/data';
import { Options } from './types';
import { dataFrameToLogsModel, dedupLogRows } from 'app/core/logs_model';
import { getFieldLinksForExplore } from 'app/features/explore/utils/links';
@@ -24,7 +24,10 @@ export const LogsPanel: React.FunctionComponent = ({
},
title,
}) => {
- const style = useStyles2(getStyles(title));
+ const isAscending = sortOrder === LogsSortOrder.Ascending;
+ const style = useStyles2(getStyles(title, isAscending));
+ const [scrollTop, setScrollTop] = useState(0);
+ const logsContainerRef = useRef(null);
// Important to memoize stuff here, as panel rerenders a lot for example when resizing.
const [logRows, deduplicatedRows, commonLabels] = useMemo(() => {
@@ -35,6 +38,14 @@ export const LogsPanel: React.FunctionComponent = ({
return [logRows, deduplicatedRows, commonLabels];
}, [data, dedupStrategy]);
+ useLayoutEffect(() => {
+ if (isAscending && logsContainerRef.current) {
+ setScrollTop(logsContainerRef.current.offsetHeight);
+ } else {
+ setScrollTop(0);
+ }
+ }, [isAscending, logRows]);
+
const getFieldLinks = useCallback(
(field: Field, rowIndex: number) => {
return getFieldLinksForExplore({ field, rowIndex, range: data.timeRange });
@@ -50,15 +61,17 @@ export const LogsPanel: React.FunctionComponent = ({
);
}
+ const renderCommonLabels = () => (
+
+ Common labels:
+
+
+ );
+
return (
-
-
- {showCommonLabels && (
-
- Common labels:
-
-
- )}
+
+
+ {showCommonLabels && !isAscending && renderCommonLabels()}
= ({
getFieldLinks={getFieldLinks}
logsSortOrder={sortOrder}
enableLogDetails={enableLogDetails}
+ previewLimit={isAscending ? logRows.length : undefined}
/>
+ {showCommonLabels && isAscending && renderCommonLabels()}
);
};
-const getStyles = (title: string) => (theme: GrafanaTheme2) => ({
+const getStyles = (title: string, isAscending: boolean) => (theme: GrafanaTheme2) => ({
container: css`
margin-bottom: ${theme.spacing(1.5)};
//We can remove this hot-fix when we fix panel menu with no title overflowing top of all panels
margin-top: ${theme.spacing(!title ? 2.5 : 0)};
`,
labelContainer: css`
- margin: ${theme.spacing(0, 0, 0.5, 0.5)};
+ margin: ${isAscending ? theme.spacing(0.5, 0, 0.5, 0) : theme.spacing(0, 0, 0.5, 0.5)};
display: flex;
align-items: center;
`,
diff --git a/public/app/plugins/panel/timeseries/__snapshots__/migrations.test.ts.snap b/public/app/plugins/panel/timeseries/__snapshots__/migrations.test.ts.snap
index e9d8df90a76..fb462dab015 100644
--- a/public/app/plugins/panel/timeseries/__snapshots__/migrations.test.ts.snap
+++ b/public/app/plugins/panel/timeseries/__snapshots__/migrations.test.ts.snap
@@ -489,6 +489,10 @@ Object {
"fill": "dash",
},
"lineWidth": 1,
+ "scaleDistribution": Object {
+ "log": 10,
+ "type": "log",
+ },
"showPoints": "never",
"spanNulls": true,
},
diff --git a/public/app/plugins/panel/timeseries/migrations.test.ts b/public/app/plugins/panel/timeseries/migrations.test.ts
index af12556a9d7..cf7279417df 100644
--- a/public/app/plugins/panel/timeseries/migrations.test.ts
+++ b/public/app/plugins/panel/timeseries/migrations.test.ts
@@ -632,7 +632,7 @@ const stepedColordLine = {
$$hashKey: 'object:39',
format: 'short',
label: null,
- logBase: 1,
+ logBase: 10,
max: null,
min: null,
show: true,
diff --git a/public/app/plugins/panel/timeseries/migrations.ts b/public/app/plugins/panel/timeseries/migrations.ts
index 570ea3d0da6..82a3d77627d 100644
--- a/public/app/plugins/panel/timeseries/migrations.ts
+++ b/public/app/plugins/panel/timeseries/migrations.ts
@@ -22,6 +22,7 @@ import {
LineInterpolation,
LineStyle,
PointVisibility,
+ ScaleDistribution,
StackingMode,
TooltipDisplayMode,
} from '@grafana/ui';
@@ -467,6 +468,15 @@ function getFieldConfigFromOldAxis(obj: any): FieldConfig {
if (obj.label) {
graph.axisLabel = obj.label;
}
+ if (obj.logBase) {
+ const log = obj.logBase as number;
+ if (log === 2 || log === 10) {
+ graph.scaleDistribution = {
+ type: ScaleDistribution.Log,
+ log,
+ };
+ }
+ }
return omitBy(
{
unit: obj.format,
diff --git a/public/app/plugins/panel/timeseries/models.cue b/public/app/plugins/panel/timeseries/models.cue
new file mode 100644
index 00000000000..6fe9d4b43a3
--- /dev/null
+++ b/public/app/plugins/panel/timeseries/models.cue
@@ -0,0 +1,42 @@
+// Copyright 2021 Grafana Labs
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package grafanaschema
+
+import (
+ ui "github.com/grafana/grafana/cue/ui:grafanaschema"
+)
+
+Family: {
+ lineages: [
+ [
+ {
+ PanelOptions: {
+ legend: ui.VizLegendOptions
+ tooltip: ui.VizTooltipOptions
+ }
+ PanelFieldConfig: {
+ ui.LineConfig
+ ui.FillConfig
+ ui.PointsConfig
+ ui.AxisConfig
+ drawStyle?: ui.DrawStyle
+ gradientMode?: ui.GraphGradientMode
+ hideFrom?: ui.HideSeriesConfig
+ }
+ }
+ ]
+ ]
+ migrations: []
+}
diff --git a/public/app/types/unified-alerting-dto.ts b/public/app/types/unified-alerting-dto.ts
index 17f17aeeb88..578fa392ff2 100644
--- a/public/app/types/unified-alerting-dto.ts
+++ b/public/app/types/unified-alerting-dto.ts
@@ -101,12 +101,17 @@ export enum GrafanaAlertStateDecision {
OK = 'OK',
}
+interface AlertDataQuery extends DataQuery {
+ maxDataPoints?: number;
+ intervalMs?: number;
+}
+
export interface AlertQuery {
refId: string;
queryType: string;
relativeTimeRange?: RelativeTimeRange;
datasourceUid: string;
- model: DataQuery;
+ model: AlertDataQuery;
}
export interface PostableGrafanaRuleDefinition {
diff --git a/scripts/lib.star b/scripts/lib.star
index d1935484414..1d24979514b 100644
--- a/scripts/lib.star
+++ b/scripts/lib.star
@@ -17,20 +17,28 @@ def pipeline(
):
if platform != 'windows':
platform_conf = {
- 'os': 'linux',
- 'arch': 'amd64',
+ 'platform': {
+ 'os': 'linux',
+ 'arch': 'amd64'
+ },
+ # A shared cache is used on the host
+ # To avoid issues with parallel builds, we run this repo on single build agents
+ 'node': {
+ 'type': 'no-parallel'
+ }
}
else:
platform_conf = {
- 'os': 'windows',
- 'arch': 'amd64',
- 'version': '1809',
+ 'platform': {
+ 'os': 'windows',
+ 'arch': 'amd64',
+ 'version': '1809',
+ }
}
pipeline = {
'kind': 'pipeline',
'type': 'docker',
- 'platform': platform_conf,
'name': name,
'trigger': trigger,
'services': services,
@@ -39,6 +47,7 @@ def pipeline(
) + steps,
'depends_on': depends_on,
}
+ pipeline.update(platform_conf)
if edition in ('enterprise', 'enterprise2'):
pipeline['image_pull_secrets'] = [pull_secret]
|