Merge "Add Custom Template Type"

This commit is contained in:
Zuul 2018-03-20 21:01:16 +00:00 committed by Gerrit Code Review
commit e86eb350b3
8 changed files with 338 additions and 44 deletions

View File

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

View File

@ -15,12 +15,57 @@
import voluptuous as v
AUTO_INTERVAL = '$__auto_interval'
ALL_CUSTOM = '$__all'
class Base(object):
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]
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
if option.get('text') == 'all' and 'value' not in option:
option['value'] = ALL_CUSTOM
# 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']
return v.Schema(self.options)(options)
def __init__(self):
self.base = {
v.Required('name'): v.All(str, v.Length(min=1)),
v.Required('type'): v.Any('query', 'interval'),
v.Required('type'): v.Any('query', 'interval', 'custom'),
}
def get_schema(self):

View File

@ -0,0 +1,72 @@
# Copyright 2018 Red Hat, Inc.
#
# 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
class Custom(Base):
current = {
v.Required('text'): v.All(str, v.Length(min=1)),
v.Required('value'): v.All([str]),
}
def validate_options(self, options):
options = self._validate_options(options)
if len(options):
selected_options = [x for x in options if x.get('selected')]
# Default to first option as selected (if nothing selected)
if len(selected_options) == 0:
options[0]['selected'] = True
return options
def _validate(self, data):
custom = {
v.Required('current'): v.Any(self.current),
v.Required('includeAll', default=False): v.All(bool),
v.Required('multi', default=False): v.All(bool),
v.Required('options', default=[]): self.validate_options,
v.Required('query', default=''): v.All(str),
v.Optional('allValue'): v.All(str),
v.Optional('hide'): v.All(int, v.Range(min=0, max=2)),
v.Optional('label', default=''): v.All(str),
}
custom.update(self.base)
custom_options_schema = {
v.Required('options', default=[]): self.validate_options,
}
data = v.Schema(custom_options_schema, extra=True)(data)
# If 'query' is not supplied, compose it from the list of options.
if 'query' not in data:
query = [option['text']
for option in data.get('options')
if option['text'] != 'All']
data['query'] = ','.join(query)
if 'current' not in data:
selected = [option['text']
for option in data.get('options')
if option['selected']]
data['current'] = dict(text='+'.join(selected), value=selected)
return v.Schema(custom)(data)
def get_schema(self):
return v.Schema(self._validate)

View File

@ -14,57 +14,18 @@
import voluptuous as v
from grafana_dashboards.schema.template.base import AUTO_INTERVAL
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)
options = self._validate_options(options)
if len(options):
selected_options = [x for x in options if x.get('selected')]

View File

@ -12,19 +12,29 @@
# License for the specific language governing permissions and limitations
# under the License.
import logging
import voluptuous as v
from grafana_dashboards.schema.template.base import Base
LOG = logging.getLogger(__name__)
class Query(Base):
def validate_refresh(self, data):
v.Schema(v.Any(v.All(int, v.Range(min=0, max=2)), bool))(data)
if isinstance(data, bool):
LOG.warn('templating query refresh type bool is deprecated')
return data
def get_schema(self):
query = {
v.Required('includeAll', default=False): v.All(bool),
v.Required('multi', default=False): v.All(bool),
v.Required('query', default=''): v.All(str),
v.Required('refresh', default=False): v.All(bool),
v.Required('refresh', default=0): self.validate_refresh,
v.Optional('datasource'): v.All(str),
v.Optional('hide'): v.All(int, v.Range(min=0, max=2)),
}

View File

@ -10,7 +10,7 @@
"multi": false,
"name": "foobar",
"query": "foobar.*",
"refresh": false,
"refresh": 0,
"type": "query"
}
]

View File

@ -0,0 +1,159 @@
{
"dashboard": {
"new-dashboard": {
"rows": [
{
"collapse": false,
"editable": true,
"height": "250px",
"panels": [
{
"bars": false,
"datasource": "graphite",
"editable": true,
"error": false,
"fill": 1,
"lines": true,
"linewidth": 2,
"percentage": false,
"pointradius": 5,
"points": false,
"span": 12,
"stack": false,
"steppedLine": false,
"targets": [
{
"target": "$hostname.Cpu.cpu_prct_used"
}
],
"title": "no title (click here)",
"type": "graph",
"x-axis": true,
"y-axis": true
}
],
"showTitle": false,
"title": "New row"
}
],
"templating": {
"enabled": true,
"list": [
{
"includeAll": false,
"multi": false,
"name": "hostname",
"query": "*",
"refresh": 2,
"type": "query"
},
{
"current": {
"text": "undercloud",
"value": [
"undercloud"
]
},
"includeAll": false,
"label": "",
"multi": false,
"name": "test_custom_1",
"options": [
{
"selected": true,
"text": "undercloud",
"value": "undercloud"
},
{
"selected": false,
"text": "controller",
"value": "controller"
},
{
"selected": false,
"text": "*",
"value": "*"
}
],
"query": "undercloud,controller,*",
"type": "custom"
},
{
"current": {
"text": "undercloud+controller",
"value": [
"undercloud",
"controller"
]
},
"includeAll": false,
"label": "",
"multi": true,
"name": "test_custom_2",
"options": [
{
"selected": true,
"text": "undercloud",
"value": "undercloud"
},
{
"selected": true,
"text": "controller",
"value": "controller"
},
{
"selected": false,
"text": "*",
"value": "*"
}
],
"query": "undercloud,controller,*",
"type": "custom"
},
{
"current": {
"text": "All",
"value": [
"All"
]
},
"includeAll": true,
"label": "",
"multi": true,
"name": "test_custom_include_all",
"options": [
{
"selected": true,
"text": "All",
"value": "All"
},
{
"selected": false,
"text": "undercloud",
"value": "undercloud"
},
{
"selected": false,
"text": "controller",
"value": "controller"
},
{
"selected": false,
"text": "*",
"value": "*"
}
],
"query": "undercloud,controller,*",
"type": "custom"
}
]
},
"time": {
"from": "2018-02-07T08:42:27.000Z",
"to": "2018-02-07T13:48:32.000Z"
},
"timezone": "utc",
"title": "New dashboard"
}
}
}

View File

@ -0,0 +1,44 @@
dashboard:
time:
from: "2018-02-07T08:42:27.000Z"
to: "2018-02-07T13:48:32.000Z"
templating:
- name: hostname
type: query
query: "*"
refresh: 2
- name: test_custom_1
type: custom
options:
- undercloud
- controller
- "*"
- name: test_custom_2
type: custom
multi: true
options:
- text: undercloud
selected: true
- text: controller
selected: true
- text: "*"
- name: test_custom_include_all
type: custom
includeAll: true
multi: true
options:
- text: All
selected: true
- text: undercloud
- text: controller
- text: "*"
title: New dashboard
rows:
- title: New row
height: 250px
panels:
- title: no title (click here)
type: graph
datasource: graphite
targets:
- target: $hostname.Cpu.cpu_prct_used