Merge "Add Custom Template Type"
This commit is contained in:
commit
e86eb350b3
@ -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))
|
||||
|
||||
|
@ -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):
|
||||
|
72
grafana_dashboards/schema/template/custom.py
Normal file
72
grafana_dashboards/schema/template/custom.py
Normal 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)
|
@ -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')]
|
||||
|
@ -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)),
|
||||
}
|
||||
|
@ -10,7 +10,7 @@
|
||||
"multi": false,
|
||||
"name": "foobar",
|
||||
"query": "foobar.*",
|
||||
"refresh": false,
|
||||
"refresh": 0,
|
||||
"type": "query"
|
||||
}
|
||||
]
|
||||
|
159
tests/schema/fixtures/dashboard-0027.json
Normal file
159
tests/schema/fixtures/dashboard-0027.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
44
tests/schema/fixtures/dashboard-0027.yaml
Normal file
44
tests/schema/fixtures/dashboard-0027.yaml
Normal 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
|
Loading…
x
Reference in New Issue
Block a user