Add support for interval template type

This performs some significant coercion because without it, the
interval template type, which should be very easy to use, would
require a substantial amount of yaml boilerplate.

Change-Id: I66ffe944af1b6794089dc536bfd99a1adf5d268e
This commit is contained in:
James E. Blair 2016-01-08 16:42:57 -08:00
parent f95956b3e2
commit ffa9e89794
5 changed files with 263 additions and 1 deletions

View File

@ -15,6 +15,7 @@
import voluptuous as v
from grafana_dashboards.schema.template.base import Base
from grafana_dashboards.schema.template.interval import Interval
from grafana_dashboards.schema.template.query import Query
@ -42,6 +43,8 @@ class Template(object):
if template['type'] == 'query':
schema = Query().get_schema()
if template['type'] == 'interval':
schema = Interval().get_schema()
res['list'].append(schema(template))

View File

@ -20,7 +20,7 @@ class Base(object):
def __init__(self):
self.base = {
v.Required('name'): v.All(str, v.Length(min=1)),
v.Required('type'): v.Any('query'),
v.Required('type'): v.Any('query', 'interval'),
}
def get_schema(self):

View File

@ -0,0 +1,152 @@
# Copyright 2015 IBM Corp.
#
# 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.
import voluptuous as v
from grafana_dashboards.schema.template.base import Base
AUTO_INTERVAL = '$__auto_interval'
class Interval(Base):
option = {
v.Required('text'): v.All(str, v.Length(min=1)),
v.Required('value'): v.All(str, v.Length(min=1)),
v.Required('selected', default=False): v.All(bool),
}
options = [option]
current = {
v.Required('text'): v.All(str, v.Length(min=1)),
v.Required('value'): v.All(str, v.Length(min=1)),
}
def validate_options(self, options):
# Most of the time this is going to be a simple list, so if
# the user supplied a list of strings, let's turn that into
# the requisite list of dicts.
try:
v.Schema([str])(options)
options = [dict(text=o) for o in options]
except v.Invalid:
pass
# Ensure this is a list of dicts before we start messing with
# them.
v.Schema([dict])(options)
# This performs some automatic cleanup to make things easier.
for option in options:
# Let's not make our users type "$__auto_interval". Instead,
# if they specify an option name of 'auto' with no value,
# supply it for them. NB: if a user wants 'auto' with value
# 'foobar', they can just override this by simply including
# 'value: foobar'.
if option.get('text') == 'auto' and 'value' not in option:
option['value'] = AUTO_INTERVAL
# Let's also not make our users type every option twice. For
# each option with a text entry but no value, copy the next
# entry to that value.
if option.get('text') and 'value' not in option:
option['value'] = option['text']
# Now we should have something that matches our actual schema.
options = v.Schema(self.options)(options)
if len(options):
selected_options = [x for x in options if x.get('selected')]
# If the user did not specify any selected options, mark
# the first one as selected.
if len(selected_options) == 0:
options[0]['selected'] = True
elif len(selected_options) > 1:
raise v.Invalid("No more than one option must be selected")
return options
def _validate(self, data):
# This method performs some validation but also coerces some
# values as needed to be friendlier.
interval = {
v.Required('allFormat', default='glob'): v.Any('glob'),
# This will be automatically supplied based on the options:
v.Required('auto'): v.Any(bool),
v.Required('auto_count', default=10): v.Any(int),
# This will be automatically supplied based on the options:
v.Required('current'): v.Any(self.current),
# NOTE(jeblair): I don't know what datasource means in this context
v.Required('datasource', default=None): v.All(None),
v.Required('hideLabel', default=False): v.Any(bool),
v.Required('includeAll', default=False): v.All(bool),
v.Required('label', default=''): v.Any(str),
v.Required('multi', default=False): v.All(bool),
v.Required('multiFormat', default='glob'): v.Any('glob'),
v.Required('options', default=[]): self.validate_options,
# This will be automatically supplied based on the options:
v.Required('query'): v.Any(str),
# NOTE(jeblair): I don't know what refresh means in this context
v.Required('refresh', default=False): v.All(bool),
}
interval.update(self.base)
# Make sure we have the minimum we need before we start
# messing with the data.
rudimentary_interval_schema = {
v.Required('options', default=[]): self.validate_options,
}
data = v.Schema(rudimentary_interval_schema, extra=True)(data)
# There are some values that would be annoying to be required
# to be included by the user because they are calculable from
# other values, and ought to be identical. Therefore, if they
# are not supplied, we will calculate them for the user.
auto = False
selected = None
options = data.get('options', [])
query = []
for option in options:
if option['value'] == AUTO_INTERVAL:
auto = True
else:
query.append(option['value'])
if option.get('selected'):
selected = option
query = ','.join(query)
# If 'auto' is not supplied, set it based on the presense of
# an 'auto' option in the options list.
if 'auto' not in data:
data['auto'] = auto
# If 'current' is not supplied, set it based on which of the
# options was marked 'selected'.
if 'current' not in data:
if selected:
data['current'] = dict(text=selected['text'],
value=selected['value'])
else:
data['current'] = dict()
# If 'query' is not supplied, compose it from the list of options.
if 'query' not in data:
data['query'] = query
data = v.Schema(interval)(data)
return data
def get_schema(self):
return v.Schema(self._validate)

View File

@ -0,0 +1,91 @@
{
"dashboard": {
"new-dashboard": {
"rows": [],
"templating": {
"enabled": true,
"list": [
{
"allFormat": "glob",
"auto": true,
"auto_count": 10,
"current": {
"text": "auto",
"value": "$__auto_interval"
},
"datasource": null,
"hideLabel": false,
"includeAll": false,
"label": "",
"multi": false,
"multiFormat": "glob",
"name": "auto_summarize",
"options": [
{
"selected": true,
"text": "auto",
"value": "$__auto_interval"
},
{
"selected": false,
"text": "1m",
"value": "1m"
},
{
"selected": false,
"text": "1h",
"value": "1h"
},
{
"selected": false,
"text": "1d",
"value": "1d"
}
],
"query": "1m,1h,1d",
"refresh": false,
"type": "interval"
},
{
"allFormat": "glob",
"auto": false,
"auto_count": 10,
"current": {
"text": "1m",
"value": "1m"
},
"datasource": null,
"hideLabel": false,
"includeAll": false,
"label": "",
"multi": false,
"multiFormat": "glob",
"name": "summarize",
"options": [
{
"selected": true,
"text": "1m",
"value": "1m"
},
{
"selected": false,
"text": "1h",
"value": "1h"
},
{
"selected": false,
"text": "1d",
"value": "1d"
}
],
"query": "1m,1h,1d",
"refresh": false,
"type": "interval"
}
]
},
"timezone": "utc",
"title": "New dashboard"
}
}
}

View File

@ -0,0 +1,16 @@
dashboard:
templating:
- name: auto_summarize
type: interval
options:
- auto
- 1m
- 1h
- 1d
- name: summarize
type: interval
options:
- 1m
- 1h
- 1d
title: New dashboard