Fix :dashboardTime: by introducing tvar precedence

In order for :autoGroupBy: and :dashboardTime: to co-exist in a query,
it's necessary to introduce template variable precedence to the backend.
This is done by adding a `Precedence()` method to the TemplateVariable
interface that returns an ordinal indicating the precedence level of the
template variable. Precedence starts from 0 (highest) proceeding to the
maximum that a `uint` can represent.

A template variable at a given precedence level can expect that all
template variables with higher precedence will have already been
replaced in the query that is passed to its `Exec` call.

For example, :autoGroupBy: has lower precedence than :dashboardTime:
because it needs to know the selected time range for the query. When the
`Exec` method of `GroupByVar` is invoked, it will see the query after
:dashboardTime: has already been replaced, allowing it to extract the
duration successfully.
pull/10616/head
Tim Raymond 2017-06-13 14:42:52 -04:00
parent a7cc6040d3
commit 66be46bf23
4 changed files with 59 additions and 15 deletions

View File

@ -135,6 +135,7 @@ type Range struct {
type TemplateVariable interface {
fmt.Stringer
Name() string // returns the variable name
Precedence() uint // ordinal indicating precedence level for replacement
}
type ExecutableVar interface {
@ -178,6 +179,10 @@ func (t BasicTemplateVar) String() string {
}
}
func (t BasicTemplateVar) Precedence() uint {
return 0
}
type GroupByVar struct {
Var string `json:"tempVar"` // the name of the variable as present in the query
Duration time.Duration `json:"duration,omitempty"` // the Duration supplied by the query
@ -231,6 +236,10 @@ func (g *GroupByVar) Name() string {
return g.Var
}
func (g *GroupByVar) Precedence() uint {
return 1
}
// TemplateID is the unique ID used to identify a template
type TemplateID string

View File

@ -8,11 +8,23 @@ import (
// TemplateReplace replaces templates with values within the query string
func TemplateReplace(query string, templates chronograf.TemplateVars) string {
tvarsByPrecedence := make(map[uint]chronograf.TemplateVars, len(templates))
maxPrecedence := uint(0)
for _, tmp := range templates {
precedence := tmp.Precedence()
if precedence > maxPrecedence {
maxPrecedence = precedence
}
tvarsByPrecedence[precedence] = append(tvarsByPrecedence[precedence], tmp)
}
replaced := query
for prc := uint(0); prc <= maxPrecedence; prc++ {
replacements := []string{}
for _, v := range templates {
for _, v := range tvarsByPrecedence[prc] {
if evar, ok := v.(chronograf.ExecutableVar); ok {
evar.Exec(query)
evar.Exec(replaced)
}
newVal := v.String()
if newVal != "" {
@ -21,6 +33,8 @@ func TemplateReplace(query string, templates chronograf.TemplateVars) string {
}
replacer := strings.NewReplacer(replacements...)
replaced := replacer.Replace(query)
replaced = replacer.Replace(replaced)
}
return replaced
}

View File

@ -147,7 +147,29 @@ func TestTemplateReplace(t *testing.T) {
ReportingInterval: 10 * time.Second,
},
},
want: `SELECT mean(usage_idle) from "cpu" where time > now() - 4320h group by time(1555s)`,
want: `SELECT mean(usage_idle) from "cpu" WHERE time > now() - 4320h group by time(1555s)`,
},
{
name: "auto group by with :dashboardTime:",
query: `SELECT mean(usage_idle) from "cpu" WHERE time > :dashboardTime: :autoGroupBy:`,
vars: chronograf.TemplateVars{
&chronograf.GroupByVar{
Var: ":autoGroupBy:",
Duration: 0 * time.Minute,
Resolution: 1000,
ReportingInterval: 10 * time.Second,
},
&chronograf.BasicTemplateVar{
Var: ":dashboardTime:",
Values: []chronograf.BasicTemplateValue{
{
Type: "constant",
Value: "now() - 4320h",
},
},
},
},
want: `SELECT mean(usage_idle) from "cpu" WHERE time > now() - 4320h group by time(1555s)`,
},
}
for _, tt := range tests {

View File

@ -268,8 +268,7 @@ class DashboardPage extends Component {
}
const templatesIncludingDashTime = (dashboard &&
dashboard.templates.concat(dashboardTime) &&
dashboard.templates.concat(autoGroupBy)) || []
dashboard.templates.concat(dashboardTime).concat(autoGroupBy)) || []
const {selectedCell, isEditMode, isTemplating} = this.state