Merge "Add Custom Template Type"
This commit is contained in:
commit
e86eb350b3
|
@ -15,6 +15,7 @@
|
||||||
import voluptuous as v
|
import voluptuous as v
|
||||||
|
|
||||||
from grafana_dashboards.schema.template.base import Base
|
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.interval import Interval
|
||||||
from grafana_dashboards.schema.template.query import Query
|
from grafana_dashboards.schema.template.query import Query
|
||||||
|
|
||||||
|
@ -45,6 +46,8 @@ class Template(object):
|
||||||
schema = Query().get_schema()
|
schema = Query().get_schema()
|
||||||
if template['type'] == 'interval':
|
if template['type'] == 'interval':
|
||||||
schema = Interval().get_schema()
|
schema = Interval().get_schema()
|
||||||
|
if template['type'] == 'custom':
|
||||||
|
schema = Custom().get_schema()
|
||||||
|
|
||||||
res['list'].append(schema(template))
|
res['list'].append(schema(template))
|
||||||
|
|
||||||
|
|
|
@ -15,12 +15,57 @@
|
||||||
import voluptuous as v
|
import voluptuous as v
|
||||||
|
|
||||||
|
|
||||||
|
AUTO_INTERVAL = '$__auto_interval'
|
||||||
|
ALL_CUSTOM = '$__all'
|
||||||
|
|
||||||
|
|
||||||
class Base(object):
|
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):
|
def __init__(self):
|
||||||
self.base = {
|
self.base = {
|
||||||
v.Required('name'): v.All(str, v.Length(min=1)),
|
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):
|
def get_schema(self):
|
||||||
|
|
|
@ -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
|
import voluptuous as v
|
||||||
|
|
||||||
|
from grafana_dashboards.schema.template.base import AUTO_INTERVAL
|
||||||
from grafana_dashboards.schema.template.base import Base
|
from grafana_dashboards.schema.template.base import Base
|
||||||
|
|
||||||
|
|
||||||
AUTO_INTERVAL = '$__auto_interval'
|
|
||||||
|
|
||||||
|
|
||||||
class Interval(Base):
|
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 = {
|
current = {
|
||||||
v.Required('text'): v.All(str, v.Length(min=1)),
|
v.Required('text'): v.All(str, v.Length(min=1)),
|
||||||
v.Required('value'): v.All(str, v.Length(min=1)),
|
v.Required('value'): v.All(str, v.Length(min=1)),
|
||||||
}
|
}
|
||||||
|
|
||||||
def validate_options(self, options):
|
def validate_options(self, options):
|
||||||
# Most of the time this is going to be a simple list, so if
|
options = self._validate_options(options)
|
||||||
# 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):
|
if len(options):
|
||||||
selected_options = [x for x in options if x.get('selected')]
|
selected_options = [x for x in options if x.get('selected')]
|
||||||
|
|
|
@ -12,19 +12,29 @@
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
import voluptuous as v
|
import voluptuous as v
|
||||||
|
|
||||||
from grafana_dashboards.schema.template.base import Base
|
from grafana_dashboards.schema.template.base import Base
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Query(Base):
|
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):
|
def get_schema(self):
|
||||||
query = {
|
query = {
|
||||||
v.Required('includeAll', default=False): v.All(bool),
|
v.Required('includeAll', default=False): v.All(bool),
|
||||||
v.Required('multi', default=False): v.All(bool),
|
v.Required('multi', default=False): v.All(bool),
|
||||||
v.Required('query', default=''): v.All(str),
|
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('datasource'): v.All(str),
|
||||||
v.Optional('hide'): v.All(int, v.Range(min=0, max=2)),
|
v.Optional('hide'): v.All(int, v.Range(min=0, max=2)),
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
"multi": false,
|
"multi": false,
|
||||||
"name": "foobar",
|
"name": "foobar",
|
||||||
"query": "foobar.*",
|
"query": "foobar.*",
|
||||||
"refresh": false,
|
"refresh": 0,
|
||||||
"type": "query"
|
"type": "query"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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…
Reference in New Issue